summaryrefslogtreecommitdiff
path: root/game/shared/econ/econ_item_inventory.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/econ/econ_item_inventory.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/econ/econ_item_inventory.cpp')
-rw-r--r--game/shared/econ/econ_item_inventory.cpp2340
1 files changed, 2340 insertions, 0 deletions
diff --git a/game/shared/econ/econ_item_inventory.cpp b/game/shared/econ/econ_item_inventory.cpp
new file mode 100644
index 0000000..77082ec
--- /dev/null
+++ b/game/shared/econ/econ_item_inventory.cpp
@@ -0,0 +1,2340 @@
+//========= 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
+
+