//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "cbase.h"
#include "econ_item_inventory.h"
#include "vgui/ILocalize.h"
#include "tier3/tier3.h"
#include "econ_item_system.h"
#include "econ_item.h"
#include "econ_gcmessages.h"
#include "shareddefs.h"
#include "filesystem.h"
#include "econ_item_description.h"				// only for CSteamAccountIDAttributeCollector

#ifdef CLIENT_DLL
#include <igameevents.h>
#include "econ_game_account_client.h"
#include "ienginevgui.h"
#include "econ_ui.h"
#include "item_pickup_panel.h"
#include "econ/econ_item_preset.h"
#include "econ/confirm_dialog.h"
#include "tf_xp_source.h"
#include "tf_notification.h"
#else
#include "props_shared.h"
#include "basemultiplayerplayer.h"
#endif

#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
#include "tf_gcmessages.h"
#include "tf_duel_summary.h"
#include "econ_contribution.h"
#include "tf_player_info.h"
#include "econ/econ_claimcode.h"
#include "tf_wardata.h"
#include "tf_ladder_data.h"
#include "tf_rating_data.h"
#endif

#if defined(TF_DLL) && defined(GAME_DLL)
#include "tf_gc_api.h"
#include "econ/econ_game_account_server.h"
#endif

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

using namespace GCSDK;

#ifdef _DEBUG
ConVar item_inventory_debug( "item_inventory_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
#endif

#ifdef USE_DYNAMIC_ASSET_LOADING
//extern ConVar item_dynamicload;
#endif

#define ITEM_CLIENTACK_FILE			"item_clientacks.txt"

#ifdef _DEBUG
#ifdef CLIENT_DLL
ConVar item_debug_clientacks( "item_debug_clientacks", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
#endif
#endif // _DEBUG

// Result codes strings for GC results.
const char* GCResultString[8] =
{
	"k_EGCMsgResponseOK",			// Request succeeded
	"k_EGCMsgResponseDenied",		// Request denied
	"k_EGCMsgResponseServerError",	// Request failed due to a temporary server error
	"k_EGCMsgResponseTimeout",		// Request timed out
	"k_EGCMsgResponseInvalid",		// Request was corrupt
	"k_EGCMsgResponseNoMatch",		// No item definition matched the request
	"k_EGCMsgResponseUnknownError",	// Request failed with an unknown error
	"k_EGCMsgResponseNotLoggedOn",	// Client not logged on to steam
};

CBasePlayer *GetPlayerBySteamID( const CSteamID &steamID )
{
	CSteamID steamIDPlayer;
	for ( int i = 1; i <= gpGlobals->maxClients; i++ )
	{
		CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
		if ( pPlayer == NULL )
			continue;

		if ( pPlayer->GetSteamID( &steamIDPlayer ) == false )
			continue;

		if ( steamIDPlayer == steamID )
			return pPlayer;
	}
	return NULL;
}

// Inventory Less function.
// Used to sort the inventory items into their positions.
bool CInventoryListLess::Less( const CEconItemView &src1, const CEconItemView &src2, void *pCtx )
{
	int iPos1 = src1.GetInventoryPosition();
	int iPos2 = src2.GetInventoryPosition();

	// Context can be specified to point to a func that extracts the position from the backend position.
	// Necessary if your inventory packs a bunch of info into the position instead of using it just as a position.
	if ( pCtx )
	{
		CPlayerInventory *pInv = (CPlayerInventory*)pCtx;
		iPos1 = pInv->ExtractInventorySortPosition( iPos1 );
		iPos2 = pInv->ExtractInventorySortPosition( iPos2 );
	}

	if ( iPos1 < iPos2 )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CInventoryManager::CInventoryManager( void )
#ifdef CLIENT_DLL
	: m_mapPersonaNamesCache( DefLessFunc( uint32 ) )
	, m_sPersonaStateChangedCallback( this, &CInventoryManager::OnPersonaStateChanged )
	, m_personaNameRequests( DefLessFunc( uint64 ) )
#endif
{
#ifdef CLIENT_DLL
	m_pkvItemClientAckFile = NULL;
	m_bClientAckDirty = false;
	m_iPredictedDiscards = 0;
	m_flNextLoadPresetChange = 0.0f;
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::SteamRequestInventory( CPlayerInventory *pInventory, CSteamID pSteamID, IInventoryUpdateListener *pListener )
{
	// SteamID must be valid
	if ( !pSteamID.IsValid() || !pSteamID.BIndividualAccount() )
	{
		if ( !HushAsserts() )
		{
			Assert( pSteamID.IsValid() );
			Assert( pSteamID.BIndividualAccount() );
		}
		return;
	}

	// If we haven't seen this inventory before, register it
	bool bFound = false;
	for ( int i = 0; i < m_pInventories.Count(); i++ )
	{
		if ( m_pInventories[i].pInventory == pInventory )
		{
			bFound = true;
			break;
		}
	}
	if ( !bFound )
	{
		int iIdx = m_pInventories.AddToTail();
		m_pInventories[iIdx].pInventory = pInventory;
		m_pInventories[iIdx].pListener = pListener;
	}

	// Add the request to our list of pending requests
	int iIdx = m_hPendingInventoryRequests.AddToTail();
	m_hPendingInventoryRequests[iIdx].pID = pSteamID;
	m_hPendingInventoryRequests[iIdx].pInventory = pInventory;

	pInventory->RequestInventory( pSteamID );

	if( pListener )
	{
		pInventory->AddListener( pListener );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called when a gameserver connects to steam.
//-----------------------------------------------------------------------------
void CInventoryManager::GameServerSteamAPIActivated()
{
#if defined(TF_DLL) && defined(GAME_DLL)
	GameCoordinator_NotifyGameState();
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CPlayerInventory *CInventoryManager::GetInventoryForAccount( uint32 iAccountID )
{
	FOR_EACH_VEC( m_pInventories, i )
	{
		if ( m_pInventories[i].pInventory->GetOwner().GetAccountID() == iAccountID )
			return m_pInventories[i].pInventory;
	}
	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::DeregisterInventory( CPlayerInventory *pInventory )
{
	int iCount = m_pInventories.Count();
	for ( int i = iCount-1; i >= 0; i-- )
	{
		if ( m_pInventories[i].pInventory == pInventory )
		{
			m_pInventories.Remove(i);
		}
	}
}


#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CInventoryManager::IsPresetIndexValid( equipped_preset_t unPreset )
{
	const bool bResult = GetItemSchema()->IsValidPreset( unPreset );
	AssertMsg( bResult, "Invalid preset index!" );
	return bResult;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CInventoryManager::LoadPreset( equipped_class_t unClass, equipped_preset_t unPreset )
{
	if ( !IsValidPlayerClass( unClass ) )
		return false;

	if ( !IsPresetIndexValid( unPreset ) )
		return false;

	if ( !GetLocalInventory()->GetSOC() )
		return false;

	if ( m_flNextLoadPresetChange > gpGlobals->realtime )
	{
		Msg( "Loadout change denied. Changing presets too quickly.\n" );
		return false;
	}

	m_flNextLoadPresetChange = gpGlobals->realtime + 0.5f;

	GCSDK::CProtoBufMsg<CMsgSelectPresetForClass> msg( k_EMsgGCPresets_SelectPresetForClass );
	msg.Body().set_class_id( unClass );
	msg.Body().set_preset_id( unPreset );
	GCClientSystem()->BSendMessage( msg );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::UpdateLocalInventory( void )
{
	if ( steamapicontext->SteamUser() && GetLocalInventory() )
	{
		CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
		if ( steamID.IsValid() ) // make sure we're logged in and we know who we are
		{
			SteamRequestInventory( GetLocalInventory(), steamID );
		}
	}	
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::OnPersonaStateChanged( PersonaStateChange_t *info )
{
	if ( ( info->m_nChangeFlags & k_EPersonaChangeName ) != 0 )
		m_personaNameRequests.InsertOrReplace( info->m_ulSteamID, true );
}

#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CInventoryManager::Init( void )
{
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::PostInit( void )
{
	// Initialize the item system.
	ItemSystem()->Init();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::PreInitGC()
{
	REG_SHARED_OBJECT_SUBCLASS( CEconItem );
	
#if defined (CLIENT_DLL)
	REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountClient );
	REG_SHARED_OBJECT_SUBCLASS( CEconItemPerClassPresetData );
	REG_SHARED_OBJECT_SUBCLASS( CSOTFMatchResultPlayerInfo );
	REG_SHARED_OBJECT_SUBCLASS( CXPSource );
	REG_SHARED_OBJECT_SUBCLASS( CTFNotification );
#endif

#if defined(TF_CLIENT_DLL) || defined(TF_DLL)

	REG_SHARED_OBJECT_SUBCLASS( CWarData );
	REG_SHARED_OBJECT_SUBCLASS( CTFDuelSummary );
	REG_SHARED_OBJECT_SUBCLASS( CTFMapContribution );
	REG_SHARED_OBJECT_SUBCLASS( CTFPlayerInfo );
	REG_SHARED_OBJECT_SUBCLASS( CEconClaimCode );
	REG_SHARED_OBJECT_SUBCLASS( CSOTFLadderData );
#endif

#ifdef TF_DLL
	REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountForGameServers );
#endif // TF_DLL
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::PostInitGC()
{
#ifdef CLIENT_DLL
	// The client immediately loads the local player's inventory
	UpdateLocalInventory();
#endif
}


//-----------------------------------------------------------------------------
void CInventoryManager::Shutdown()
{
	int nInventoryCount = m_pInventories.Count();
	for ( int iInventory = 0; iInventory < nInventoryCount; ++iInventory )
	{
		CPlayerInventory *pInventory = m_pInventories[iInventory].pInventory;
		if ( pInventory )
		{
			pInventory->Clear();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::LevelInitPreEntity( void )
{
	// Throw out any testitem definitions
	for ( int i = 0; i < TI_TYPE_COUNT; i++ )
	{
		int iNewDef = TESTITEM_DEFINITIONS_BEGIN_AT + i;
		ItemSystem()->GetItemSchema()->ItemTesting_DiscardTestDefinition( iNewDef );
	}

	// Precache all item models we've got
#ifdef GAME_DLL
	CUtlVector<const char *> vecPrecacheModelStrings;
#endif // GAME_DLL
	const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetItemDefinitionMap();
	FOR_EACH_MAP_FAST( mapItemDefs, i )
	{
		CEconItemDefinition *pData = mapItemDefs[i];

		pData->SetHasBeenLoaded( true );

#ifdef GAME_DLL
		bool bDynamicLoad = false;
#ifdef USE_DYNAMIC_ASSET_LOADING
		bDynamicLoad = true;//item_dynamicload.GetBool();
#endif // USE_DYNAMIC_ASSET_LOADING
		pData->GeneratePrecacheModelStrings( bDynamicLoad, &vecPrecacheModelStrings );

		// Precache the models and the gibs for everything the definition requested.
		FOR_EACH_VEC( vecPrecacheModelStrings, i )
		{
			// Ignore any objects which requested an empty precache string for whatever reason.
			if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] )
			{
				int iModelIndex = CBaseEntity::PrecacheModel( vecPrecacheModelStrings[i] );
				PrecacheGibsForModel( iModelIndex );
			}
		}

		vecPrecacheModelStrings.RemoveAll();

		pData->GeneratePrecacheSoundStrings( bDynamicLoad, &vecPrecacheModelStrings );

		// Precache the sounds for everything
		FOR_EACH_VEC( vecPrecacheModelStrings, i )
		{
			// Ignore any objects which requested an empty precache string for whatever reason.
			if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] )
			{
				CBaseEntity::PrecacheScriptSound( vecPrecacheModelStrings[i] );
			}
		}

		vecPrecacheModelStrings.RemoveAll();
#endif
	}

	// We reset the cached attribute class strings, since it's invalidated by level changes
	ItemSystem()->ResetAttribStringCache();

#ifdef GAME_DLL
	ItemSystem()->ReloadWhitelist();
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::LevelShutdownPostEntity( void )
{
	// We reset the cached attribute class strings, since it's invalidated by level changes
	ItemSystem()->ResetAttribStringCache();
}


//-----------------------------------------------------------------------------
// Purpose: Lets the client know that we're now connected to the GC
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
void CInventoryManager::SendGCConnectedEvent( void )
{
	IGameEvent *event = gameeventmanager->CreateEvent( "gc_connected" );
	if ( event )
	{
		gameeventmanager->FireEventClientSide( event );
	}
}
#endif


#if !defined(NO_STEAM)
//-----------------------------------------------------------------------------
// Purpose: GC Msg handler to receive the dev "new item" response
//-----------------------------------------------------------------------------
class CGCDev_NewItemRequestResponse : public GCSDK::CGCClientJob
{
public:
	CGCDev_NewItemRequestResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}

	virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
	{
		GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );

		if ( msg.Body().m_eResponse == k_EGCMsgResponseOK )
		{
			Msg("Received new item acknowledgement: %s\n", GCResultString[msg.Body().m_eResponse] );
		}
		else
		{
			Warning("Failed to generate new item: %s\n", GCResultString[msg.Body().m_eResponse] );
		}
		return true;
	}

};
GC_REG_JOB( GCSDK::CGCClient, CGCDev_NewItemRequestResponse, "CGCDev_NewItemRequestResponse", k_EMsgGCDev_NewItemRequestResponse, GCSDK::k_EServerTypeGCClient );

#endif	// NO_STEAM

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::RemovePendingRequest( CSteamID *pSteamID )
{
#ifdef CLIENT_DLL
	// Only the client, all requests are for the local player. Clear them all.
	m_hPendingInventoryRequests.Purge();
	return;
#endif

	// On the server, remove all requests for the specified steam id
	int iCount = m_hPendingInventoryRequests.Count();
	for ( int i = iCount-1; i >= 0; i-- )
	{
		if ( m_hPendingInventoryRequests[i].pID == *pSteamID )
		{
			m_hPendingInventoryRequests.Remove(i);
		}
	}
}

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::DropItem( itemid_t iItemID )
{
	static CSchemaAttributeDefHandle pAttrDef_NoDelete( "cannot delete" );

	// Double check that this item can be delete
	CEconItemView *pItem = GetLocalInventory()->GetInventoryItemByItemID( iItemID );
	if ( !pItem || !pAttrDef_NoDelete || pItem->FindAttribute( pAttrDef_NoDelete ) )
	{
		return;
	}
	
	GCSDK::CGCMsg<MsgGCDelete_t> msg( k_EMsgGCDelete );
	msg.Body().m_unItemID = iItemID;
	GCClientSystem()->BSendMessage( msg );

	// Keep track of how many items we've discarded, but haven't received responses for.
	m_iPredictedDiscards++;
}

//-----------------------------------------------------------------------------
// Purpose: Delete any items we can't find static data for. This can happen when we're testing
//			internally, and then remove an item. Shouldn't ever happen in the wild.
//-----------------------------------------------------------------------------
int	CInventoryManager::DeleteUnknowns( CPlayerInventory *pInventory )
{
	// We need to manually walk the main inventory's SOC, because unknown items won't be in the inventory
	GCSDK::CGCClientSharedObjectCache *pSOC = pInventory->GetSOC();
	if ( pSOC )
	{
		int iBadItems = 0;
		CGCClientSharedObjectTypeCache *pTypeCache = pSOC->FindTypeCache( CEconItem::k_nTypeID );
		if( pTypeCache )
		{
			for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ )
			{
				CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem );
				if ( pItem )
				{
					CEconItemDefinition *pData = ItemSystem()->GetStaticDataForItemByDefIndex( pItem->GetDefinitionIndex() );
					if ( !pData )
					{
						DropItem( pItem->GetItemID() );
						iBadItems++;
					}
				}
			}
		}
		return iBadItems;
	}		

	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Tries to move the specified item into the player's backpack.
//			FAILS if the backpack is full. Returns false in that case.
//-----------------------------------------------------------------------------
bool CInventoryManager::SetItemBackpackPosition( CEconItemView *pItem, uint32 iPosition, bool bForceUnequip, bool bAllowOverflow )
{
	CPlayerInventory *pInventory = GetLocalInventory();
	if ( !pInventory )
		return false;

	const int iMaxItems = pInventory->GetMaxItemCount();
	if ( !iPosition )
	{
		// Build a list of empty slots. We track extra slots beyond the backpack for overflow.
		CUtlVector< bool > bFilledSlots;
		bFilledSlots.SetSize( iMaxItems * 2 );
		for ( int i = 0; i < bFilledSlots.Count(); ++i )
		{
			bFilledSlots[i] = false;
		}
		for ( int i = 0; i < pInventory->GetItemCount(); i++ )
		{
			CEconItemView *pTmpItem = pInventory->GetItem(i);
			// Ignore the item we're moving.
			if ( pTmpItem == pItem )
				continue;

			int iBackpackPos = GetBackpackPositionFromBackend( pTmpItem->GetInventoryPosition() );
			if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() )
			{
				bFilledSlots[iBackpackPos] = true;
			}
		}

		// Add predicted filled slots
		for ( int i = 0; i < m_PredictedFilledSlots.Count(); i++ )
		{
			int iBackpackPos = m_PredictedFilledSlots[i];
			if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() )
			{
				bFilledSlots[iBackpackPos] = true;
			}
		}

		// Now find an empty slot
		for ( int i = 1; i < bFilledSlots.Count(); i++ )
		{
			if ( !bFilledSlots[i] )
			{
				iPosition = i;
				break;
			}
		}

		if ( !iPosition )
			return false;
	}

	if ( !bAllowOverflow && iPosition > (uint32)iMaxItems )
		return false;

	//Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iPosition );

	uint32 iBackendPosition = bForceUnequip ? 0 : pItem->GetInventoryPosition();
	SetBackpackPosition( &iBackendPosition, iPosition );
	UpdateInventoryPosition( pInventory, pItem->GetItemID(), iBackendPosition );

	m_PredictedFilledSlots.AddToTail( iPosition );
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::MoveItemToBackpackPosition( CEconItemView *pItem, int iBackpackPosition )
{
	CEconItemView *pOldItem = GetItemByBackpackPosition( iBackpackPosition );
	if ( pOldItem )
	{
		// Move the item in the new spot to our current spot
		SetItemBackpackPosition( pOldItem, GetBackpackPositionFromBackend(pItem->GetInventoryPosition()) );

		//Warning("Moved OLD item %llu to backpack slot: %d\n", pOldItem->GetItemID(), GetBackpackPositionFromBackend(iBackendPosition) );
	}

	// Move the item to the new spot
	SetItemBackpackPosition( pItem, iBackpackPosition );

	//Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iBackpackPosition );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CWaitForBackpackSortFinishDialog : public CGenericWaitingDialog
{
public:
	CWaitForBackpackSortFinishDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
	{
	}

protected:
	virtual void OnTimeout()
	{
		InventoryManager()->SortBackpackFinished();
	}
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::SortBackpackBy( uint32 iSortType )
{
	GCSDK::CProtoBufMsg<CMsgSortItems> msg( k_EMsgGCSortItems );
	msg.Body().set_sort_type( iSortType );
	GCClientSystem()->BSendMessage( msg );

	ShowWaitingDialog( new CWaitForBackpackSortFinishDialog( NULL ), "#BackpackSortExplanation_Title", true, false, 3.0f );
	m_bInBackpackSort = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::SortBackpackFinished( void )
{
	m_bInBackpackSort = false;

	GetLocalInventory()->SendInventoryUpdateEvent();
}

//-----------------------------------------------------------------------------
// Purpose: GC Msg handler to receive the sort finished message
//-----------------------------------------------------------------------------
class CGBackpackSortFinished : public GCSDK::CGCClientJob
{
public:
	CGBackpackSortFinished( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}

	virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
	{
		CloseWaitingDialog();
		InventoryManager()->SortBackpackFinished();
		return true;
	}

};
GC_REG_JOB( GCSDK::CGCClient, CGBackpackSortFinished, "CGBackpackSortFinished", k_EMsgGCBackpackSortFinished, GCSDK::k_EServerTypeGCClient );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::UpdateInventoryPosition( CPlayerInventory *pInventory, uint64 ulItemID, uint32 unNewInventoryPos )
{
	if ( !pInventory->GetInventoryItemByItemID( ulItemID ) )
	{
		Warning("Attempt to update inventory position failure: %s.\n", "could not find matching item ID");
		return;
	}
	if ( !pInventory->GetSOCDataForItem( ulItemID ) )
	{
		Warning("Attempt to update inventory position failure: %s\n", "could not find SOC data for item");
		return;
	}

	// In the incredibly rare case where the GC crashed while sorting our backpack, we won't have gotten
	// a k_EMsgGCBackpackSortFinished message. Assume that if we're requesting a manual move of an item, we're not sorting anymore.
	m_bInBackpackSort = false;

	// TF has multiple ways of using the inventory position bits. For all inventory positions moving forward, assume
	// they're in the new format.
#if defined(TF_CLIENT_DLL) || defined(TF_DLL)
	if ( unNewInventoryPos != 0 )
	{
		unNewInventoryPos |= kBackendPosition_NewFormat;
	}
#endif // defined(TF_CLIENT_DLL) || defined(TF_DLL)

	// Queue a message to be sent to the GC
	CMsgSetItemPositions_ItemPosition *pMsg = m_msgPendingSetItemPositions.add_item_positions();
	pMsg->set_item_id( ulItemID );
	pMsg->set_position( unNewInventoryPos );
}

void CInventoryManager::Update( float frametime )
{

	// Check if we have any pending item position changes that we need to flush out
	if ( m_msgPendingSetItemPositions.item_positions_size() > 0 )
	{
		// !KLUDGE! It would be nice if we could just send this in one line instead of making a copy
		CProtoBufMsg<CMsgSetItemPositions> msg( k_EMsgGCSetItemPositions );
		msg.Body() = m_msgPendingSetItemPositions;
		GCClientSystem()->BSendMessage( msg );

		m_msgPendingSetItemPositions.Clear();
	}

	// Check if we have any pending account lookups to batch up
	if ( m_msgPendingLookupAccountNames.accountids_size() > 0 )
	{
		// !KLUDGE! It would be nice if we could just send this in one line instead of making a copy
		CProtoBufMsg< CMsgLookupMultipleAccountNames > msg( k_EMsgGCLookupMultipleAccountNames );
		msg.Body() = m_msgPendingLookupAccountNames;
		GCClientSystem()->BSendMessage( msg );

		m_msgPendingLookupAccountNames.Clear();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::UpdateInventoryEquippedState( CPlayerInventory *pInventory, uint64 ulItemID, equipped_class_t unClass, equipped_slot_t unSlot )
{
	// passing in INVALID_ITEM_ID means "unequip from this slot"
	if ( ulItemID != INVALID_ITEM_ID )
	{
		if ( !pInventory->GetInventoryItemByItemID( ulItemID ) )
		{
			//Warning("Attempt to update equipped state failure: %s.\n", "could not find matching item ID");
			return;
		}
		if ( !pInventory->GetSOCDataForItem( ulItemID ) )
		{
			//Warning("Attempt to update equipped state failure: %s\n", "could not find SOC data for item");
			return;
		}
	}

	CProtoBufMsg<CMsgAdjustItemEquippedState> msg( k_EMsgGCAdjustItemEquippedState );
	msg.Body().set_item_id( ulItemID );
	msg.Body().set_new_class( unClass );
	msg.Body().set_new_slot( unSlot );
	GCClientSystem()->BSendMessage( msg );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CInventoryManager::ShowItemsPickedUp( bool bForce, bool bReturnToGame, bool bNoPanel )
{
	CPlayerInventory *pLocalInv = GetLocalInventory();
	if ( !pLocalInv )
		return false;

	// Don't bring it up if we're already browsing something in the gameUI
	vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
	if ( !bForce && vgui::ipanel()->IsVisible( gameuiPanel ) )
		return false;

	CUtlVector<CEconItemView*> aItemsFound;

	// Go through the root inventory and find any items that are in the "found" position
	int iCount = pLocalInv->GetItemCount();
	for ( int i = 0; i < iCount; i++ )
	{
		CEconItemView *pTmp = pLocalInv->GetItem(i);
		if ( !pTmp )
			continue;

		if ( pTmp->GetStaticData()->IsHidden() )
			continue;

		uint32 iPosition = pTmp->GetInventoryPosition();
		if ( IsUnacknowledged(iPosition) == false )
			continue;
		if ( GetBackpackPositionFromBackend(iPosition) != 0 )
			continue;

		// Now make sure we haven't got a clientside saved ack for this item.
		// This makes sure we don't show multiple pickups for items that we've found,
		// but haven't been able to move out of unack'd position due to the GC being unavailable.
		if ( HasBeenAckedByClient( pTmp ) )
			continue;

		aItemsFound.AddToTail( pTmp );
	}

	if ( !aItemsFound.Count() )
		return CheckForRoomAndForceDiscard();

	// We're not forcing the player to make room yet. Just show the pickup panel.
	CItemPickupPanel *pItemPanel = bNoPanel ? NULL : EconUI()->OpenItemPickupPanel();

	if ( pItemPanel )
	{
		pItemPanel->SetReturnToGame( bReturnToGame );
	}

	for ( int i = 0; i < aItemsFound.Count(); i++ )
	{
		if ( pItemPanel )
		{
			pItemPanel->AddItem( aItemsFound[i] );
		}
		else 
		{
			AcknowledgeItem( aItemsFound[i] );
		}
	}

	if ( pItemPanel )
	{
		pItemPanel->MoveToFront();
	}
	else
	{
		SaveAckFile();
	}

	aItemsFound.Purge();
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CInventoryManager::CheckForRoomAndForceDiscard( void )
{
	CPlayerInventory *pLocalInv = GetLocalInventory();
	if ( !pLocalInv )
		return false;

	// Go through the inventory and attempt to move any items outside the backpack into valid positions.
	// Remember the first item that we failed to move, so we can force a discard later.
	CEconItemView *pItem = NULL;
	const int iMaxItems = pLocalInv->GetMaxItemCount();
	int iCount = pLocalInv->GetItemCount();
	for ( int i = 0; i < iCount; i++ )
	{
		CEconItemView *pTmp = pLocalInv->GetItem(i);
		if ( !pTmp )
			continue;

		if ( pTmp->GetStaticData()->IsHidden() )
			continue;

		uint32 iPosition = pTmp->GetInventoryPosition();
		if ( IsUnacknowledged(iPosition) || GetBackpackPositionFromBackend(iPosition) > iMaxItems )
		{
			if ( !SetItemBackpackPosition( pTmp, 0, false, false ) )
			{
				pItem = pTmp;
				break;
			}
		}
	}

	// If we're not over the limit, we're done.
	if ( ( iCount - m_iPredictedDiscards ) <= iMaxItems )
		return false;

	if ( !pItem )
		return false;

	// We're forcing the player to make room for items he's found. Bring up that panel with the first item over the limit.
	CItemDiscardPanel *pDiscardPanel = EconUI()->OpenItemDiscardPanel();
	pDiscardPanel->SetItem( pItem );
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Client Acknowledges an item and moves it in to the backpack
//-----------------------------------------------------------------------------
void CInventoryManager::AcknowledgeItem ( CEconItemView *pItem, bool bMoveToBackpack /* = true */ )
{
	SetAckedByClient( pItem );

	int iMethod = GetUnacknowledgedReason( pItem->GetInventoryPosition() ) - 1;
	if ( iMethod >= ARRAYSIZE( g_pszItemPickupMethodStringsUnloc ) || iMethod < 0 )
		iMethod = 0;
	EconUI()->Gamestats_ItemTransaction( IE_ITEM_RECEIVED, pItem, g_pszItemPickupMethodStringsUnloc[iMethod] );

	// Then move it to the first empty backpack position
	if ( bMoveToBackpack )
	{
		SetItemBackpackPosition( pItem, 0, false, true );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CEconItemView *CInventoryManager::GetItemByBackpackPosition( int iBackpackPosition )
{
	CPlayerInventory *pInventory = GetLocalInventory();
	if ( !pInventory )
		return NULL;

	// Backpack positions start from 1
	Assert( iBackpackPosition > 0 && iBackpackPosition <= pInventory->GetMaxItemCount() );
	for ( int i = 0; i < pInventory->GetItemCount(); i++ )
	{
		CEconItemView *pItem = pInventory->GetItem(i);
		if ( GetBackpackPositionFromBackend( pItem->GetInventoryPosition() ) == iBackpackPosition )
			return pItem;
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CInventoryManager::HasBeenAckedByClient( CEconItemView *pItem )
{
	return ( GetAckKeyForItem( pItem ) != NULL );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::SetAckedByClient( CEconItemView *pItem )
{
	VerifyAckFileLoaded();

	static char szTmp[128];
	Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() );
	m_pkvItemClientAckFile->SetInt( szTmp, 1 );

	m_bClientAckDirty = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::SetAckedByGC( CEconItemView *pItem, bool bSave )
{
	KeyValues *pkvItem = GetAckKeyForItem( pItem );
	if ( pkvItem )
	{
		m_pkvItemClientAckFile->RemoveSubKey( pkvItem );
		pkvItem->deleteThis();

		m_bClientAckDirty = true;

		if ( bSave )
		{
			SaveAckFile();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
KeyValues *CInventoryManager::GetAckKeyForItem( CEconItemView *pItem )
{
	VerifyAckFileLoaded();

	static char szTmp[128];
	Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() );
	return m_pkvItemClientAckFile->FindKey( szTmp );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::VerifyAckFileLoaded( void )
{
	if ( m_pkvItemClientAckFile )
		return;

	m_pkvItemClientAckFile = new KeyValues( ITEM_CLIENTACK_FILE );

	ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
		SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;

	if ( pRemoteStorage )
	{
		if ( pRemoteStorage->FileExists(ITEM_CLIENTACK_FILE) )
		{
			int32 nFileSize = pRemoteStorage->GetFileSize( ITEM_CLIENTACK_FILE );

			if ( nFileSize > 0 )
			{
				CUtlBuffer buf( 0, nFileSize );
				if ( pRemoteStorage->FileRead( ITEM_CLIENTACK_FILE, buf.Base(), nFileSize ) == nFileSize )
				{
					buf.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSize );
					m_pkvItemClientAckFile->ReadAsBinary( buf );

#ifdef _DEBUG
					if ( item_debug_clientacks.GetBool() )
					{
						m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_readack.txt", "MOD" );
					}
#endif
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Clean up any item references that we no longer have items for.
//			This ensures that if we delete an item on the backend, we remove it from the ack file.
//-----------------------------------------------------------------------------
void CInventoryManager::CleanAckFile( void )
{
	CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
	if ( !pInventory )
		return;

	if ( !pInventory->RetrievedInventoryFromSteam() )
		return;

	if ( m_pkvItemClientAckFile )
	{
		KeyValues *pKVItem = m_pkvItemClientAckFile->GetFirstSubKey();
		while ( pKVItem != NULL )
		{
			itemid_t ulID = (itemid_t)Q_atoi64( pKVItem->GetName() );
			if ( pInventory->GetInventoryItemByItemID(ulID) == NULL )
			{
				KeyValues *pTmp = pKVItem->GetNextKey();
				m_pkvItemClientAckFile->RemoveSubKey( pKVItem );
				pKVItem->deleteThis();

				m_bClientAckDirty = true;

				pKVItem = pTmp;
			}
			else
			{
				pKVItem = pKVItem->GetNextKey();
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CInventoryManager::SaveAckFile( void )
{
	if ( !m_bClientAckDirty )
		return;
	m_bClientAckDirty = false;

	ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
		SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;

	if ( pRemoteStorage )
	{
		CUtlBuffer buf;
		m_pkvItemClientAckFile->WriteAsBinary( buf );
		pRemoteStorage->FileWrite( ITEM_CLIENTACK_FILE, buf.Base(), buf.TellPut() );

#ifdef _DEBUG
		if ( item_debug_clientacks.GetBool() )
		{
			m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_saveack.txt", "MOD" );
		}
#endif
	}
}

//-----------------------------------------------------------------------------
// Purpose: GC sent name of account down
//-----------------------------------------------------------------------------
class CGCLookupAccountNameResponse : public GCSDK::CGCClientJob
{
public:
	CGCLookupAccountNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
	virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
	{
		GCSDK::CGCMsg<MsgGCLookupAccountNameResponse_t> msg( pNetPacket );

		CUtlString playerName;
		if ( msg.BReadStr( &playerName ) )
		{
			InventoryManager()->PersonaName_Store( msg.Body().m_unAccountID, playerName.Get() );
		}
		return true;
	}
};
GC_REG_JOB( GCSDK::CGCClient, CGCLookupAccountNameResponse, "CGCLookupAccountNameResponse", k_EMsgGCLookupAccountNameResponse, GCSDK::k_EServerTypeGCClient );

class CGCLookupMultipleAccountsNameResponse : public GCSDK::CGCClientJob
{
public:
	CGCLookupMultipleAccountsNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
	virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
	{
		CProtoBufMsg<CMsgLookupMultipleAccountNamesResponse> msg( pNetPacket );
		for ( int i = 0 ; i < msg.Body().accounts_size() ; ++i )
		{
			const CMsgLookupMultipleAccountNamesResponse_Account &account = msg.Body().accounts( i );
			InventoryManager()->PersonaName_Store( account.accountid(), account.persona().c_str() );
		}

		return true;
	}
};
GC_REG_JOB( GCSDK::CGCClient, CGCLookupMultipleAccountsNameResponse, "CGCLookupMultipleAccountsNameResponse", k_EMsgGCLookupMultipleAccountNamesResponse, GCSDK::k_EServerTypeGCClient );

void CInventoryManager::PersonaName_Precache( uint32 unAccountID )
{
	const char *pszName = PersonaName_Get( unAccountID );
	if ( pszName == NULL )
	{
		// Queue request name from GC
		m_msgPendingLookupAccountNames.add_accountids( unAccountID );

		// insert empty string so we don't ask again
		m_mapPersonaNamesCache.Insert( unAccountID, "" );
	}
}

const char *CInventoryManager::PersonaName_Get( uint32 unAccountID )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	// First ask Steam if this is one of friends -- if so we can get an up-to-date persona name.
	{
		const char *pszName = NULL;

		if ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() )
		{
			CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
			steamID.SetAccountID( unAccountID );
			uint64 u64AccountId = steamID.ConvertToUint64();

			// We're covering three states here:
			// 1. We've never asked before. We need to queue up a RequestUserInformation.
			// 2. We've asked before, and we haven't heard back yet
			// 3. We've asked before, we heard back. Don't re-request user information.
			auto index = m_personaNameRequests.Find( u64AccountId );
			if ( !m_personaNameRequests.IsValidIndex( index ) )
			{
				// This is case 1--we've never asked before.

				// If RequestUserInformation returns false, the information is already available.
				// Otherwise, it will arrive later and we need to rebuild the description at that time.
				if ( !steamapicontext->SteamFriends()->RequestUserInformation( steamID, true ) )
				{
					pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
					Assert( pszName ); // Guaranteed by the steam api

					if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 )
					{
						m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName );
						return pszName;
					}
				}
				else
				{
					// This is case 2, we've asked above. 
					m_personaNameRequests.Insert( u64AccountId, false );
				}
			}
			else
			{
				if ( m_personaNameRequests[ index ] )
				{
					// This is case 3.
					pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
					Assert( pszName ); // Guaranteed by the steam api

					if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 )
					{
						m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName );
						return pszName;
					}
				}
			}
		}
	}

	// If that didn't work, ask the server we're playing on if they know this account ID.
	CBasePlayer *pPlayer = GetPlayerByAccountID( unAccountID );
	if ( pPlayer )
	{
		const char *pszPlayerName = pPlayer->GetPlayerName();
		if ( pszPlayerName )
		{
			m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszPlayerName );
			return pszPlayerName;
		}
	}

	// If *that* didn't work, look in our cache populated by the GC (or the above paths). This
	// might be out of date but it's better than nothing.
	int idx = m_mapPersonaNamesCache.Find( unAccountID );
	if ( m_mapPersonaNamesCache.IsValidIndex( idx ) )
	{
		return m_mapPersonaNamesCache[idx].Get();
	}

	return "[unknown]";
}

void CInventoryManager::PersonaName_Store( uint32 unAccountID, const char *pPersonaName )
{
	m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pPersonaName );
}

#endif // CLIENT_DLL


//=======================================================================================================================
// PLAYER INVENTORY
//=======================================================================================================================
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CPlayerInventory::CPlayerInventory( void )
{
	m_bGotItemsFromSteam = false;
	m_iPendingRequests = 0;
	m_aInventoryItems.Purge();
	m_pSOCache = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CPlayerInventory::~CPlayerInventory()
{
	FOR_EACH_VEC( m_vecItemHandles, i )
	{
		m_vecItemHandles[ i ]->InventoryIsBeingDeleted();
	}
	m_vecItemHandles.Purge();

	if ( m_iPendingRequests )
	{
		InventoryManager()->RemovePendingRequest( &m_OwnerID );
		m_iPendingRequests = 0;
	}

	SOClear();

	InventoryManager()->DeregisterInventory( this );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerInventory::SOClear()
{
	if ( m_OwnerID.IsValid() )
	{
		CGCClientSystem *pClientSystem = GCClientSystem();
		Assert ( pClientSystem != NULL );
		if ( pClientSystem != NULL )
		{
			CGCClient *pClient = pClientSystem->GetGCClient();
			Assert ( pClient != NULL );
			pClient->RemoveSOCacheListener( m_OwnerID, this );
		}
	}

	// Somebody registered as a listener through us, but now our Steam ID
	// is changing?  This is bad news.
	Assert( m_vecListeners.Count() == 0 );
	while ( m_vecListeners.Count() > 0 )
	{
		RemoveListener( m_vecListeners[0] );
	}

	// If we were subscribed, we should have gotten our unsubscribe message,
	// and that should have cleared the pointer
	Assert( m_pSOCache == NULL);
	m_pSOCache = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerInventory::AddItemHandle( CEconItemViewHandle* pHandle )
{
	FOR_EACH_VEC( m_vecItemHandles, i )
	{
		if ( m_vecItemHandles[ i ] == pHandle )
		{
			Assert( !"Item handle already in list to track!" );
			return;
		}
	}

	m_vecItemHandles.AddToTail( pHandle );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerInventory::RemoveItemHandle( CEconItemViewHandle* pHandle )
{
	FOR_EACH_VEC( m_vecItemHandles, i )
	{
		if ( m_vecItemHandles[ i ] == pHandle )
		{
			m_vecItemHandles.Remove( i );
			return;
		}
	}

	Assert( !"Could not find item handle to remove!" );
}


void	CPlayerInventory::Clear()
{
	SOClear();
	m_OwnerID = CSteamID();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerInventory::RequestInventory( CSteamID pSteamID )
{
	// Make sure we don't already have somebody else's stuff
	// on hand
	if ( m_OwnerID != pSteamID )
		SOClear();

	// Remember whose inventory we're looking at
	m_OwnerID = pSteamID; 

	// SteamID must be valid
	if ( !m_OwnerID.IsValid() || !m_OwnerID.BIndividualAccount() )
	{
		Assert( m_OwnerID.IsValid() );
		Assert( m_OwnerID.BIndividualAccount() );
		return;
	}

	// If we don't already have an SO cache, then ask the GC for one,
	// and start listening to it.  We will receive our "subscribed" message
	// when the data is valid
	GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, this );
}

void CPlayerInventory::AddListener( GCSDK::ISharedObjectListener *pListener )
{
	Assert( m_OwnerID.IsValid() );
	if ( m_vecListeners.Find( pListener ) < 0 )
	{
		m_vecListeners.AddToTail( pListener );
		GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, pListener );
	}
}


void CPlayerInventory::RemoveListener( GCSDK::ISharedObjectListener *pListener )
{
	if ( m_OwnerID.IsValid() )
	{
		m_vecListeners.FindAndFastRemove( pListener );
		GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerID, pListener );
	}
	else
	{
		Assert( m_vecListeners.Count() == 0 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Helper function to add a new item for a econ item
//-----------------------------------------------------------------------------
bool CPlayerInventory::AddEconItem( CEconItem * pItem, bool bUpdateAckFile, bool bWriteAckFile, bool bCheckForNewItems )
{
	CEconItemView newItem;
	if( !FilloutItemFromEconItem( &newItem, pItem ) )
	{
		return false;
	}

	int iIdx = m_aInventoryItems.Insert( newItem );

	DirtyItemHandles();

	ItemHasBeenUpdated( &m_aInventoryItems[iIdx], bUpdateAckFile, bWriteAckFile );

#ifdef CLIENT_DLL
	if ( bCheckForNewItems && InventoryManager()->GetLocalInventory() == this )
	{
		bool bNotify = IsUnacknowledged( pItem->GetInventoryToken() );
		// ignore Halloween drops
		bNotify &= pItem->GetOrigin() != kEconItemOrigin_HalloweenDrop;
		// only notify for specific reasons
		unacknowledged_item_inventory_positions_t reason = GetUnacknowledgedReason( pItem->GetInventoryToken() );
		switch ( reason )
		{
			case UNACK_ITEM_UNKNOWN:
			case UNACK_ITEM_DROPPED:
			case UNACK_ITEM_SUPPORT:
			case UNACK_ITEM_EARNED:
			case UNACK_ITEM_REFUNDED:
			case UNACK_ITEM_COLLECTION_REWARD:
			case UNACK_ITEM_TRADED:
			case UNACK_ITEM_GIFTED:
			case UNACK_ITEM_QUEST_LOANER:
			case UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD:
				break;
			default:
				bNotify = false;
				break;
		}

		if ( bNotify && !pItem->GetItemDefinition()->IsHidden() )
		{
			OnHasNewItems();
		}		
	}
#endif
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Creates a script item and associates it with this econ item
//-----------------------------------------------------------------------------
void CPlayerInventory::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	if( pObject->GetTypeID() != CEconItem::k_nTypeID )
		return;
	Assert( steamIDOwner == m_OwnerID );
	if ( steamIDOwner != m_OwnerID )
		return;

	// We shouldn't get these notifications unless we're subscribed, right?
	if ( m_pSOCache == NULL)
	{
		Assert( m_pSOCache );
		return;
	}

	// Don't bother unless it's an incremental notification.
	// For mass updates, we'll do everything more efficiently in one place
	if ( eEvent != GCSDK::eSOCacheEvent_Incremental )
	{
		Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed || eEvent == GCSDK::eSOCacheEvent_ListenerAdded );
		return;
	}

	CEconItem *pItem = (CEconItem *)pObject;
	AddEconItem( pItem, true, true, true );
	SendInventoryUpdateEvent();
}


//-----------------------------------------------------------------------------
// Purpose: Updates the script item associated with this econ item
//-----------------------------------------------------------------------------
void CPlayerInventory::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
{
	if( pObject->GetTypeID() != CEconItem::k_nTypeID )
		return;
	Assert( steamIDOwner == m_OwnerID );
	if ( steamIDOwner != m_OwnerID )
		return;

	// We shouldn't get these notifications unless we're subscribed, right?
	if ( m_pSOCache == NULL)
	{
		Assert( m_pSOCache );
		return;
	}

	// Don't bother unless it's an incremental notification.
	// For mass updates, we'll do everything more efficiently in one place
	if ( eEvent != GCSDK::eSOCacheEvent_Incremental )
	{
		Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed );
		return;
	}

	CEconItem *pEconItem = (CEconItem *)pObject;

	bool bChanged = false;
	CEconItemView *pScriptItem = GetInventoryItemByItemID( pEconItem->GetItemID() );
	if ( pScriptItem )
	{
		if ( FilloutItemFromEconItem( pScriptItem, pEconItem ) )
		{
			ItemHasBeenUpdated( pScriptItem, false, false );
		}

		bChanged = true;
	}
	else
	{
		// The item isn't in this inventory right now. But it may need to be 
		// after the update, so try adding it and see if the inventory wants it.
		bChanged = AddEconItem( pEconItem, false, false, false );
	}

	if ( bChanged )
	{
		ResortInventory();

		DirtyItemHandles();

#ifdef CLIENT_DLL
		// Client doesn't update inventory while items are moving in a backpack sort. Does it once at the sort end instead.
		if ( !InventoryManager()->IsInBackpackSort() )
#endif
		{
			SendInventoryUpdateEvent();
		}
#ifdef _DEBUG
		if ( item_inventory_debug.GetBool() )
		{
			DumpInventoryToConsole( true );
		}
#endif
	}
}


//-----------------------------------------------------------------------------
// Purpose: Removes the script item associated with this econ item
//-----------------------------------------------------------------------------
void CPlayerInventory::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
{
	if( pObject->GetTypeID() != CEconItem::k_nTypeID )
		return;
	Assert( steamIDOwner == m_OwnerID );
	if ( steamIDOwner != m_OwnerID )
		return;

	// We shouldn't get these notifications unless we're subscribed, right?
	if ( m_pSOCache == NULL)
	{
		Assert( m_pSOCache );
		return;
	}

	// Don't bother unless it's an incremental notification.
	// For mass updates, we'll do everything more efficiently in one place
	if ( eEvent != GCSDK::eSOCacheEvent_Incremental )
	{
		Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed );
		return;
	}

	CEconItem *pEconItem = (CEconItem *)pObject;
	RemoveItem( pEconItem->GetItemID() );

#ifdef CLIENT_DLL
	InventoryManager()->OnItemDeleted( this );
#endif

	SendInventoryUpdateEvent();
}


//-----------------------------------------------------------------------------
// Purpose: This is our initial notification that this cache has been received
//			from the server.
//-----------------------------------------------------------------------------
void CPlayerInventory::SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
{
	// Make sure we expect notifications about this guy
	Assert( steamIDOwner == m_OwnerID );
	if ( steamIDOwner != m_OwnerID )
		return;

	#ifdef _DEBUG
		Msg("CPlayerInventory::SOCacheSubscribed\n");
	#endif

	// Clear our old inventory
	m_aInventoryItems.Purge();

	DirtyItemHandles();

	// Locate the cache that was just subscribed to
	m_pSOCache = GCClientSystem()->GetSOCache( m_OwnerID );
	if ( m_pSOCache == NULL )
	{
		Assert( m_pSOCache != NULL );
		return;
	}

	// add all the items already in the inventory
	CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindTypeCache( CEconItem::k_nTypeID );
	if( pTypeCache )
	{
		for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ )
		{
			CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem );
			AddEconItem(pItem, true, false, true );
		}
	}

	m_bGotItemsFromSteam = true;

#ifdef CLIENT_DLL
	if ( InventoryManager()->GetLocalInventory() == this )
	{
		// Only validate the local player inventory
		ValidateInventoryPositions();

		// tell the entire client that we're 'connected' to the GC now
		CInventoryManager::SendGCConnectedEvent();
	}
#endif

	ResortInventory();

#ifdef CLIENT_DLL
	// Now that we've read all the items in, write out the ack file (only if we're the local inventory)
	if ( InventoryManager()->GetLocalInventory() == this )
	{
		InventoryManager()->CleanAckFile();
		InventoryManager()->SaveAckFile();
	}
#endif
}

bool CInventoryManager::IsValidPlayerClass( equipped_class_t unClass )
{
	const bool bResult = ItemSystem()->GetItemSchema()->IsValidClass( unClass );
	AssertMsg( bResult, "Invalid player class!" );
	return bResult;
}

//-----------------------------------------------------------------------------
// Purpose: Removes the script item associated with this econ item
//-----------------------------------------------------------------------------
void CPlayerInventory::ValidateInventoryPositions( void )
{
#ifdef TF2
	if ( engine->GetAppID() == 520 )
	{
		TFInventoryManager()->DeleteUnknowns( this );
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerInventory::ItemHasBeenUpdated( CEconItemView *pItem, bool bUpdateAckFile, bool bWriteAckFile )
{
#ifdef CLIENT_DLL
	// Handle the clientside ack file
	if ( bUpdateAckFile && !IsUnacknowledged(pItem->GetInventoryPosition()) )
	{
		if ( InventoryManager()->GetLocalInventory() == this )
		{
			InventoryManager()->SetAckedByGC( pItem, bWriteAckFile );
		}
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPlayerInventory::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
{
	m_pSOCache = NULL;
	m_bGotItemsFromSteam = false;
	m_aInventoryItems.Purge();

	DirtyItemHandles();
}


//-----------------------------------------------------------------------------
// Purpose: On the client this sends the "inventory_updated" event. On the server
//			it does nothing.
//-----------------------------------------------------------------------------
void CPlayerInventory::SendInventoryUpdateEvent()
{
#ifdef CLIENT_DLL
	if( InventoryManager()->GetLocalInventory() == this )
	{
		IGameEvent *event = gameeventmanager->CreateEvent( "inventory_updated" );
		if ( event )
		{
			gameeventmanager->FireEventClientSide( event );
		}
	}
#endif
}


//-----------------------------------------------------------------------------
// Purpose: Fills out all the fields in the script item based on what's in the
//			econ item
//-----------------------------------------------------------------------------
bool CPlayerInventory::FilloutItemFromEconItem( CEconItemView *pScriptItem, CEconItem *pEconItem )
{
	// We need to detect the case where items have been updated & moved bags / positions.
	uint32 iOldPos = pScriptItem->GetInventoryPosition();
	bool bWasInThisBag = ItemShouldBeIncluded( iOldPos );

	// Ignore items that this inventory doesn't care about
	if ( !ItemShouldBeIncluded( pEconItem->GetInventoryToken() ) )
	{
		// The item has been moved out of this bag. Ensure our derived inventory classes know.
		if ( bWasInThisBag )
		{
			// We need to update it before it's removed.
			ItemHasBeenUpdated( pScriptItem, false, false );

			RemoveItem( pEconItem->GetItemID() );
		}

		return false;
	}

	pScriptItem->Init( pEconItem->GetDefinitionIndex(), pEconItem->GetQuality(), pEconItem->GetItemLevel(), pEconItem->GetAccountID() );
	if ( !pScriptItem->IsValid() )
		return false;

	pScriptItem->SetItemID( pEconItem->GetItemID() );

	pScriptItem->SetInventoryPosition( pEconItem->GetInventoryToken() );
	OnItemChangedPosition( pScriptItem, iOldPos );

#if BUILD_ITEM_NAME_AND_DESC
	// Precache account names if we have any. We do this way in advance of any code that might
	// use it (ie., description text building) so that by the time we try that we already have
	// the data setup.
	//
	// We don't worry about yielding here because this inventory code only runs on game
	// clients/servers, not the GC.
	CSteamAccountIDAttributeCollector AccountIDCollector;
	pEconItem->IterateAttributes( &AccountIDCollector );

	FOR_EACH_VEC( AccountIDCollector.GetAccountIDs(), i )
	{
		InventoryManager()->PersonaName_Precache( (AccountIDCollector.GetAccountIDs())[i] );
	}
#endif

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerInventory::DumpInventoryToConsole( bool bRoot )
{
	if ( bRoot )
	{
#ifdef CLIENT_DLL
		Msg("(CLIENT) Inventory:\n");
#else
		Msg("(SERVER) Inventory for account (%d):\n", m_OwnerID.GetAccountID() );
#endif
		Msg("  Version: %llu:\n", m_pSOCache ? m_pSOCache->GetVersion() : -1 );
	}

	int iCount = m_aInventoryItems.Count();
	Msg("   Num items: %d\n", iCount );
	for ( int i = 0; i < iCount; i++ )
	{
		Msg("      %s (ID %llu)\n", m_aInventoryItems[i].GetStaticData()->GetDefinitionName(), m_aInventoryItems[i].GetItemID() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerInventory::RemoveItem( itemid_t iItemID )
{
	int iIndex;
	CEconItemView *pItem = GetInventoryItemByItemID( iItemID, &iIndex );
	if ( pItem )
	{
		ItemIsBeingRemoved( pItem );

		FOR_EACH_VEC( m_vecItemHandles, i )
		{
			m_vecItemHandles[ i ]->MarkDirty();
			m_vecItemHandles[ i ]->ItemIsBeingDeleted( pItem );
		}

		m_aInventoryItems.Remove(iIndex);

#ifdef _DEBUG
		if ( item_inventory_debug.GetBool() )
		{
			DumpInventoryToConsole( true );
		}
#endif
	}

	// Don't need to resort because items will still be in order
}

//-----------------------------------------------------------------------------
// Purpose: Finds the item in our inventory that matches the specified global index
//-----------------------------------------------------------------------------
CEconItemView *CPlayerInventory::GetInventoryItemByItemID( itemid_t iIndex, int *pIndex )
{
	int iCount = m_aInventoryItems.Count();
	for ( int i = 0; i < iCount; i++ )
	{
		if ( m_aInventoryItems[i].GetItemID() == iIndex )
		{
			if ( pIndex )
			{
				*pIndex = i;
			}

			return &m_aInventoryItems[i];
		}
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Finds the item in our inventory that matches the specified global original id
//-----------------------------------------------------------------------------
CEconItemView *CPlayerInventory::GetInventoryItemByOriginalID( itemid_t iOriginalID, int *pIndex /*= NULL*/ )
{
	int iCount = m_aInventoryItems.Count();
	for ( int i = 0; i < iCount; i++ )
	{
		CEconItem *pItem = m_aInventoryItems[i].GetSOCData();
		if ( pItem && pItem->GetOriginalID() == iOriginalID )
		{
			if ( pIndex )
			{
				*pIndex = i;
			}

			return &m_aInventoryItems[i];
		}
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: Finds the item in our inventory in the specified position
//-----------------------------------------------------------------------------
CEconItemView *CPlayerInventory::GetItemByPosition( int iPosition, int *pIndex )
{
	int iCount = m_aInventoryItems.Count();
	for ( int i = 0; i < iCount; i++ )
	{
		if ( m_aInventoryItems[i].GetInventoryPosition() == (unsigned int)iPosition )
		{
			if ( pIndex )
			{
				*pIndex = i;
			}

			return &m_aInventoryItems[i];
		}
	}

	return NULL;
}

// Finds the first item in our backpack with match itemdef
//-----------------------------------------------------------------------------
CEconItemView *CPlayerInventory::FindFirstItembyItemDef( item_definition_index_t iItemDef )
{
	int iCount = m_aInventoryItems.Count();
	for ( int i = 0; i < iCount; i++ )
	{
		//GetItemDefIndex()
		if ( m_aInventoryItems[i].GetItemDefIndex() == iItemDef )
		{
			return &m_aInventoryItems[i];
		}
	}

	return NULL;

}

//-----------------------------------------------------------------------------
// Purpose: Get the index for the item in our inventory utlvector
//-----------------------------------------------------------------------------
int	CPlayerInventory::GetIndexForItem( CEconItemView *pItem )
{
	int iCount = m_aInventoryItems.Count();
	for ( int i = 0; i < iCount; i++ )
	{
		if ( m_aInventoryItems[i].GetItemID() == pItem->GetItemID() )
			return i;
	}

	return -1;
}

//-----------------------------------------------------------------------------
// Purpose: Dirty all the item handles that are registered with us
//-----------------------------------------------------------------------------
void CPlayerInventory::DirtyItemHandles()
{
	FOR_EACH_VEC( m_vecItemHandles, i )
	{
		m_vecItemHandles[ i ]->MarkDirty();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Get the item object cache data for the specified item
//-----------------------------------------------------------------------------
CEconItem	*CPlayerInventory::GetSOCDataForItem( itemid_t iItemID ) 
{ 
	if ( !m_pSOCache )
		return NULL;

	CEconItem soIndex;
	soIndex.SetItemID( iItemID );
	return (CEconItem *)m_pSOCache->FindSharedObject( soIndex );
}

#if defined (_DEBUG) && defined(CLIENT_DLL)
CON_COMMAND_F( item_deleteall, "WARNING: Removes all of the items in your inventory.", FCVAR_CHEAT )
{
	CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
	if ( !pInventory )
		return;

	int iCount = pInventory->GetItemCount();
	for ( int i = 0; i < iCount; i++ )
	{
		CEconItemView *pItem = pInventory->GetItem(i);
		if ( pItem )
		{
			InventoryManager()->DropItem( pItem->GetItemID() );
		}
	}

	InventoryManager()->UpdateLocalInventory();
}
#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CPlayerInventory::GetRecipeCount() const
{
	const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap();

	return mapRecipes.Count();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDef( int iIndex )
{
	if ( !m_pSOCache )
		return NULL;

	if ( iIndex < 0 || iIndex >= GetRecipeCount() )
		return NULL;

	const CEconItemSchema::RecipeDefinitionMap_t& mapRecipes = GetItemSchema()->GetRecipeDefinitionMap();
	
	// Store off separate index for "number of items iterated over" in case something
	// deletes from the recipes map out from under us.
	int j = 0;
	FOR_EACH_MAP_FAST( mapRecipes, i )
	{
		if ( j == iIndex )
			return mapRecipes[i];

		j++;
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDefByDefIndex( uint16 iDefIndex )
{
	if ( !m_pSOCache )
		return NULL;

	// check always-known recipes
	const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap();
	int i = mapRecipes.Find( iDefIndex );
	if ( i != mapRecipes.InvalidIndex() )
		return mapRecipes[i];

	// there are no more SO recipes
	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEconItemViewHandle::SetItem( CEconItemView* pItem )
{
	m_pItem = pItem;

	if ( pItem )
	{
		// Cache the item_id for lookup when our pointer gets dirtied
		m_nItemID = pItem->GetItemID();
		auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() );
		Assert( pInv );
		if ( m_pInv != pInv )
		{
			// If this is a different inventory, unsubscribe.  This can happen if the
			// handle gets reused
			if ( m_pInv )
			{
				m_pInv->RemoveItemHandle( this );
			}

			m_pInv = pInv;

			// Subscribe to the new inventory
			m_pInv->AddItemHandle( this );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Return a pointer to a CEconItemView
//-----------------------------------------------------------------------------
CEconItemView* CEconItemViewHandle::Get() const
{
	// If our pointer is dirty, we need to go get a new pointer
	if ( m_bPointerDirty )
	{
		if ( m_pInv )
		{
			m_pItem = m_pInv->GetInventoryItemByItemID( m_nItemID );
			m_bPointerDirty = false;
		}
	}

	return m_pItem;
}


//-----------------------------------------------------------------------------
// Purpose: Unsubscribe us from future updates
//-----------------------------------------------------------------------------
CEconItemHandle::~CEconItemHandle()
{
	UnsubscribeFromSOEvents();
}


//-----------------------------------------------------------------------------
// Purpose: Save a pointer to the item and register us for SOCache events
//-----------------------------------------------------------------------------
void CEconItemHandle::SetItem( CEconItem* pItem )
{
	UnsubscribeFromSOEvents();

	m_pItem = NULL;
	m_iItemID = INVALID_ITEM_ID;

	if ( pItem )
	{
		auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() );
		if ( pInv )
		{
			m_OwnerSteamID.SetFromUint64( pInv->GetOwner().ConvertToUint64() );
			GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerSteamID, this );
		}

		m_pItem = pItem;
		m_iItemID = pItem->GetID();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Check if out item got deleted.  If it did, mark our pointer as NULL
//			so future dereferences will get NULL instead of a stale pointer.
//-----------------------------------------------------------------------------
void CEconItemHandle::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
{ 
	if( pObject->GetTypeID() != CEconItem::k_nTypeID || m_pItem == NULL )
		return;

	const CEconItem *pItem = (CEconItem *)pObject;

	if ( m_iItemID == pItem->GetID() )
	{
		UnsubscribeFromSOEvents();
		m_pItem = NULL;
		m_iItemID = INVALID_ITEM_ID;
	}
}

void CEconItemHandle::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
{
	if( pObject->GetTypeID() != CEconItem::k_nTypeID )
		return;
	
	CEconItem *pItem = (CEconItem *)pObject;

	if ( m_iItemID == pItem->GetID() )
	{
		SetItem( pItem );
	}
}

void CEconItemHandle::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
{
	if ( pObject->GetTypeID() != CEconItem::k_nTypeID )
		return;

	CEconItem *pItem = (CEconItem *)pObject;

	if ( m_iItemID == pItem->GetID() )
	{
		SetItem( pItem );
	}
}

void CEconItemHandle::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
{
	UnsubscribeFromSOEvents();
}

void CEconItemHandle::UnsubscribeFromSOEvents()
{
	if ( m_OwnerSteamID.GetAccountID() != 0 )
	{
		GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerSteamID, this );
	}
}


#if defined( STAGING_ONLY ) || defined( _DEBUG )
#if defined(CLIENT_DLL)
CON_COMMAND_F( item_dumpinv, "Dumps the contents of a specified client inventory.", FCVAR_CHEAT )
#else
CON_COMMAND_F( item_dumpinv_sv, "Dumps the contents of a specified server inventory.", FCVAR_CHEAT )
#endif
{
#if defined(CLIENT_DLL)
	CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
#else
	CSteamID steamID;
	CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_GetCommandClient() ); 
	pPlayer->GetSteamID( &steamID );
	CPlayerInventory *pInventory = InventoryManager()->GetInventoryForAccount( steamID.GetAccountID() );
#endif
	if ( !pInventory )
	{
		Msg("No inventory found.\n");
		return;
	}

	pInventory->DumpInventoryToConsole( true );
}

#if defined (CLIENT_DLL)

CON_COMMAND_F( item_dumpschema, "Dump the expanded schema for items to a file in sorted order suitable for diffs. Format: item_dumpschema <filename>", FCVAR_CHEAT )
{
	if ( args.ArgC() != 2 )
	{
		Msg("Usage: item_dumpschema <filename>\n");
		return;
	}

	if ( GetItemSchema()->DumpItems(args[1]) )
		Msg("Dump complete, saved in game/tf/%s\n", args[1]);
	else
		Msg("Dump failed (?)\n");
}

CON_COMMAND_F( item_giveitem, "Give an item to the local player. Format: item_giveitem <item definition name> or <item def index>", FCVAR_NONE )
{
	if ( !steamapicontext || !steamapicontext->SteamUser() )
	{
		Msg("Not connected to Steam.\n");
		return;
	}
	CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID();
	if ( !steamIDForPlayer.IsValid() )
	{
		Msg("Failed to find a valid steamID for the local player.\n");
		return;
	}

	int iItemCount = args.ArgC();
	for ( int i = 1; i < iItemCount; ++i )
	{
		// Check to see if args[1] is a number (itemdefid) and if so, translate it to actual itemname
		const char *pszItemname = NULL;
		if ( V_isdigit( args[i][0] ) )
		{
			int iDef = V_atoi( args[i] );
			CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( iDef );
			if ( pItemDef )
			{
				pszItemname = pItemDef->GetItemDefinitionName();
			}
		}
		else
		{
			pszItemname = args[i];
		}

		Msg("Sending request to generate '%s' for Local Player (%llu)\n", pszItemname, steamIDForPlayer.ConvertToUint64() );

		CItemSelectionCriteria criteria;

		GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest );
		msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() );

		criteria.SetIgnoreEnabledFlag( true );
		if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemname, true ) ||
			!criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) )
		{
			Msg("Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" );
			return;
		}
		GCClientSystem()->BSendMessage( msg );
	}
}

CON_COMMAND_F( item_rolllootlist, "Force a loot list rool for the local player. Format: item_rolllootlist <loot list definition name>", FCVAR_NONE )
{
	if ( !steamapicontext || !steamapicontext->SteamUser() )
	{
		Msg("Not connected to Steam.\n");
		return;
	}
	CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID();
	if ( !steamIDForPlayer.IsValid() )
	{
		Msg("Failed to find a valid steamID for the local player.\n");
		return;
	}

	Msg("Sending request to roll '%s' for Local Player (%llu)\n", args[1], steamIDForPlayer.ConvertToUint64() );

	GCSDK::CProtoBufMsg<CMsgDevDebugRollLootRequest> msg( k_EMsgGCDev_DebugRollLootRequest );
	msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() );
	msg.Body().set_loot_list_name( args[1] );
	GCClientSystem()->BSendMessage( msg );
}

#include "econ_item_description.h"
#include "localization_provider.h"

CON_COMMAND_F( item_generate_all_descriptions, "Generate full item descriptions for every item in your backpack. Meant as a code test.", FCVAR_CHEAT )
{
	CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();

	for ( int i = 0; i < pInventory->GetItemCount(); i++ )
	{
		CEconItemDescription desc;
		IEconItemDescription::YieldingFillOutEconItemDescription( &desc, GLocalizationProvider(), pInventory->GetItem( i ) );
	}

	Msg("Done.\n");
}
#endif // CLIENT_DLL

#endif // STAGING_ONLY || _DEBUG


