Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2340 lines
70 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "econ_item_inventory.h"
  8. #include "vgui/ILocalize.h"
  9. #include "tier3/tier3.h"
  10. #include "econ_item_system.h"
  11. #include "econ_item.h"
  12. #include "econ_gcmessages.h"
  13. #include "shareddefs.h"
  14. #include "filesystem.h"
  15. #include "econ_item_description.h" // only for CSteamAccountIDAttributeCollector
  16. #ifdef CLIENT_DLL
  17. #include <igameevents.h>
  18. #include "econ_game_account_client.h"
  19. #include "ienginevgui.h"
  20. #include "econ_ui.h"
  21. #include "item_pickup_panel.h"
  22. #include "econ/econ_item_preset.h"
  23. #include "econ/confirm_dialog.h"
  24. #include "tf_xp_source.h"
  25. #include "tf_notification.h"
  26. #else
  27. #include "props_shared.h"
  28. #include "basemultiplayerplayer.h"
  29. #endif
  30. #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
  31. #include "tf_gcmessages.h"
  32. #include "tf_duel_summary.h"
  33. #include "econ_contribution.h"
  34. #include "tf_player_info.h"
  35. #include "econ/econ_claimcode.h"
  36. #include "tf_wardata.h"
  37. #include "tf_ladder_data.h"
  38. #include "tf_rating_data.h"
  39. #endif
  40. #if defined(TF_DLL) && defined(GAME_DLL)
  41. #include "tf_gc_api.h"
  42. #include "econ/econ_game_account_server.h"
  43. #endif
  44. // memdbgon must be the last include file in a .cpp file!!!
  45. #include "tier0/memdbgon.h"
  46. using namespace GCSDK;
  47. #ifdef _DEBUG
  48. ConVar item_inventory_debug( "item_inventory_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
  49. #endif
  50. #ifdef USE_DYNAMIC_ASSET_LOADING
  51. //extern ConVar item_dynamicload;
  52. #endif
  53. #define ITEM_CLIENTACK_FILE "item_clientacks.txt"
  54. #ifdef _DEBUG
  55. #ifdef CLIENT_DLL
  56. ConVar item_debug_clientacks( "item_debug_clientacks", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE );
  57. #endif
  58. #endif // _DEBUG
  59. // Result codes strings for GC results.
  60. const char* GCResultString[8] =
  61. {
  62. "k_EGCMsgResponseOK", // Request succeeded
  63. "k_EGCMsgResponseDenied", // Request denied
  64. "k_EGCMsgResponseServerError", // Request failed due to a temporary server error
  65. "k_EGCMsgResponseTimeout", // Request timed out
  66. "k_EGCMsgResponseInvalid", // Request was corrupt
  67. "k_EGCMsgResponseNoMatch", // No item definition matched the request
  68. "k_EGCMsgResponseUnknownError", // Request failed with an unknown error
  69. "k_EGCMsgResponseNotLoggedOn", // Client not logged on to steam
  70. };
  71. CBasePlayer *GetPlayerBySteamID( const CSteamID &steamID )
  72. {
  73. CSteamID steamIDPlayer;
  74. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  75. {
  76. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  77. if ( pPlayer == NULL )
  78. continue;
  79. if ( pPlayer->GetSteamID( &steamIDPlayer ) == false )
  80. continue;
  81. if ( steamIDPlayer == steamID )
  82. return pPlayer;
  83. }
  84. return NULL;
  85. }
  86. // Inventory Less function.
  87. // Used to sort the inventory items into their positions.
  88. bool CInventoryListLess::Less( const CEconItemView &src1, const CEconItemView &src2, void *pCtx )
  89. {
  90. int iPos1 = src1.GetInventoryPosition();
  91. int iPos2 = src2.GetInventoryPosition();
  92. // Context can be specified to point to a func that extracts the position from the backend position.
  93. // Necessary if your inventory packs a bunch of info into the position instead of using it just as a position.
  94. if ( pCtx )
  95. {
  96. CPlayerInventory *pInv = (CPlayerInventory*)pCtx;
  97. iPos1 = pInv->ExtractInventorySortPosition( iPos1 );
  98. iPos2 = pInv->ExtractInventorySortPosition( iPos2 );
  99. }
  100. if ( iPos1 < iPos2 )
  101. return true;
  102. return false;
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose:
  106. //-----------------------------------------------------------------------------
  107. CInventoryManager::CInventoryManager( void )
  108. #ifdef CLIENT_DLL
  109. : m_mapPersonaNamesCache( DefLessFunc( uint32 ) )
  110. , m_sPersonaStateChangedCallback( this, &CInventoryManager::OnPersonaStateChanged )
  111. , m_personaNameRequests( DefLessFunc( uint64 ) )
  112. #endif
  113. {
  114. #ifdef CLIENT_DLL
  115. m_pkvItemClientAckFile = NULL;
  116. m_bClientAckDirty = false;
  117. m_iPredictedDiscards = 0;
  118. m_flNextLoadPresetChange = 0.0f;
  119. #endif
  120. }
  121. //-----------------------------------------------------------------------------
  122. // Purpose:
  123. //-----------------------------------------------------------------------------
  124. void CInventoryManager::SteamRequestInventory( CPlayerInventory *pInventory, CSteamID pSteamID, IInventoryUpdateListener *pListener )
  125. {
  126. // SteamID must be valid
  127. if ( !pSteamID.IsValid() || !pSteamID.BIndividualAccount() )
  128. {
  129. if ( !HushAsserts() )
  130. {
  131. Assert( pSteamID.IsValid() );
  132. Assert( pSteamID.BIndividualAccount() );
  133. }
  134. return;
  135. }
  136. // If we haven't seen this inventory before, register it
  137. bool bFound = false;
  138. for ( int i = 0; i < m_pInventories.Count(); i++ )
  139. {
  140. if ( m_pInventories[i].pInventory == pInventory )
  141. {
  142. bFound = true;
  143. break;
  144. }
  145. }
  146. if ( !bFound )
  147. {
  148. int iIdx = m_pInventories.AddToTail();
  149. m_pInventories[iIdx].pInventory = pInventory;
  150. m_pInventories[iIdx].pListener = pListener;
  151. }
  152. // Add the request to our list of pending requests
  153. int iIdx = m_hPendingInventoryRequests.AddToTail();
  154. m_hPendingInventoryRequests[iIdx].pID = pSteamID;
  155. m_hPendingInventoryRequests[iIdx].pInventory = pInventory;
  156. pInventory->RequestInventory( pSteamID );
  157. if( pListener )
  158. {
  159. pInventory->AddListener( pListener );
  160. }
  161. }
  162. //-----------------------------------------------------------------------------
  163. // Purpose: Called when a gameserver connects to steam.
  164. //-----------------------------------------------------------------------------
  165. void CInventoryManager::GameServerSteamAPIActivated()
  166. {
  167. #if defined(TF_DLL) && defined(GAME_DLL)
  168. GameCoordinator_NotifyGameState();
  169. #endif
  170. }
  171. //-----------------------------------------------------------------------------
  172. // Purpose:
  173. //-----------------------------------------------------------------------------
  174. CPlayerInventory *CInventoryManager::GetInventoryForAccount( uint32 iAccountID )
  175. {
  176. FOR_EACH_VEC( m_pInventories, i )
  177. {
  178. if ( m_pInventories[i].pInventory->GetOwner().GetAccountID() == iAccountID )
  179. return m_pInventories[i].pInventory;
  180. }
  181. return NULL;
  182. }
  183. //-----------------------------------------------------------------------------
  184. // Purpose:
  185. //-----------------------------------------------------------------------------
  186. void CInventoryManager::DeregisterInventory( CPlayerInventory *pInventory )
  187. {
  188. int iCount = m_pInventories.Count();
  189. for ( int i = iCount-1; i >= 0; i-- )
  190. {
  191. if ( m_pInventories[i].pInventory == pInventory )
  192. {
  193. m_pInventories.Remove(i);
  194. }
  195. }
  196. }
  197. #ifdef CLIENT_DLL
  198. //-----------------------------------------------------------------------------
  199. // Purpose:
  200. //-----------------------------------------------------------------------------
  201. bool CInventoryManager::IsPresetIndexValid( equipped_preset_t unPreset )
  202. {
  203. const bool bResult = GetItemSchema()->IsValidPreset( unPreset );
  204. AssertMsg( bResult, "Invalid preset index!" );
  205. return bResult;
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose:
  209. //-----------------------------------------------------------------------------
  210. bool CInventoryManager::LoadPreset( equipped_class_t unClass, equipped_preset_t unPreset )
  211. {
  212. if ( !IsValidPlayerClass( unClass ) )
  213. return false;
  214. if ( !IsPresetIndexValid( unPreset ) )
  215. return false;
  216. if ( !GetLocalInventory()->GetSOC() )
  217. return false;
  218. if ( m_flNextLoadPresetChange > gpGlobals->realtime )
  219. {
  220. Msg( "Loadout change denied. Changing presets too quickly.\n" );
  221. return false;
  222. }
  223. m_flNextLoadPresetChange = gpGlobals->realtime + 0.5f;
  224. GCSDK::CProtoBufMsg<CMsgSelectPresetForClass> msg( k_EMsgGCPresets_SelectPresetForClass );
  225. msg.Body().set_class_id( unClass );
  226. msg.Body().set_preset_id( unPreset );
  227. GCClientSystem()->BSendMessage( msg );
  228. return true;
  229. }
  230. //-----------------------------------------------------------------------------
  231. // Purpose:
  232. //-----------------------------------------------------------------------------
  233. void CInventoryManager::UpdateLocalInventory( void )
  234. {
  235. if ( steamapicontext->SteamUser() && GetLocalInventory() )
  236. {
  237. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  238. if ( steamID.IsValid() ) // make sure we're logged in and we know who we are
  239. {
  240. SteamRequestInventory( GetLocalInventory(), steamID );
  241. }
  242. }
  243. }
  244. //-----------------------------------------------------------------------------
  245. // Purpose:
  246. //-----------------------------------------------------------------------------
  247. void CInventoryManager::OnPersonaStateChanged( PersonaStateChange_t *info )
  248. {
  249. if ( ( info->m_nChangeFlags & k_EPersonaChangeName ) != 0 )
  250. m_personaNameRequests.InsertOrReplace( info->m_ulSteamID, true );
  251. }
  252. #endif
  253. //-----------------------------------------------------------------------------
  254. // Purpose:
  255. //-----------------------------------------------------------------------------
  256. bool CInventoryManager::Init( void )
  257. {
  258. return true;
  259. }
  260. //-----------------------------------------------------------------------------
  261. // Purpose:
  262. //-----------------------------------------------------------------------------
  263. void CInventoryManager::PostInit( void )
  264. {
  265. // Initialize the item system.
  266. ItemSystem()->Init();
  267. }
  268. //-----------------------------------------------------------------------------
  269. // Purpose:
  270. //-----------------------------------------------------------------------------
  271. void CInventoryManager::PreInitGC()
  272. {
  273. REG_SHARED_OBJECT_SUBCLASS( CEconItem );
  274. #if defined (CLIENT_DLL)
  275. REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountClient );
  276. REG_SHARED_OBJECT_SUBCLASS( CEconItemPerClassPresetData );
  277. REG_SHARED_OBJECT_SUBCLASS( CSOTFMatchResultPlayerInfo );
  278. REG_SHARED_OBJECT_SUBCLASS( CXPSource );
  279. REG_SHARED_OBJECT_SUBCLASS( CTFNotification );
  280. #endif
  281. #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
  282. REG_SHARED_OBJECT_SUBCLASS( CWarData );
  283. REG_SHARED_OBJECT_SUBCLASS( CTFDuelSummary );
  284. REG_SHARED_OBJECT_SUBCLASS( CTFMapContribution );
  285. REG_SHARED_OBJECT_SUBCLASS( CTFPlayerInfo );
  286. REG_SHARED_OBJECT_SUBCLASS( CEconClaimCode );
  287. REG_SHARED_OBJECT_SUBCLASS( CSOTFLadderData );
  288. #endif
  289. #ifdef TF_DLL
  290. REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountForGameServers );
  291. #endif // TF_DLL
  292. }
  293. //-----------------------------------------------------------------------------
  294. // Purpose:
  295. //-----------------------------------------------------------------------------
  296. void CInventoryManager::PostInitGC()
  297. {
  298. #ifdef CLIENT_DLL
  299. // The client immediately loads the local player's inventory
  300. UpdateLocalInventory();
  301. #endif
  302. }
  303. //-----------------------------------------------------------------------------
  304. void CInventoryManager::Shutdown()
  305. {
  306. int nInventoryCount = m_pInventories.Count();
  307. for ( int iInventory = 0; iInventory < nInventoryCount; ++iInventory )
  308. {
  309. CPlayerInventory *pInventory = m_pInventories[iInventory].pInventory;
  310. if ( pInventory )
  311. {
  312. pInventory->Clear();
  313. }
  314. }
  315. }
  316. //-----------------------------------------------------------------------------
  317. // Purpose:
  318. //-----------------------------------------------------------------------------
  319. void CInventoryManager::LevelInitPreEntity( void )
  320. {
  321. // Throw out any testitem definitions
  322. for ( int i = 0; i < TI_TYPE_COUNT; i++ )
  323. {
  324. int iNewDef = TESTITEM_DEFINITIONS_BEGIN_AT + i;
  325. ItemSystem()->GetItemSchema()->ItemTesting_DiscardTestDefinition( iNewDef );
  326. }
  327. // Precache all item models we've got
  328. #ifdef GAME_DLL
  329. CUtlVector<const char *> vecPrecacheModelStrings;
  330. #endif // GAME_DLL
  331. const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetItemDefinitionMap();
  332. FOR_EACH_MAP_FAST( mapItemDefs, i )
  333. {
  334. CEconItemDefinition *pData = mapItemDefs[i];
  335. pData->SetHasBeenLoaded( true );
  336. #ifdef GAME_DLL
  337. bool bDynamicLoad = false;
  338. #ifdef USE_DYNAMIC_ASSET_LOADING
  339. bDynamicLoad = true;//item_dynamicload.GetBool();
  340. #endif // USE_DYNAMIC_ASSET_LOADING
  341. pData->GeneratePrecacheModelStrings( bDynamicLoad, &vecPrecacheModelStrings );
  342. // Precache the models and the gibs for everything the definition requested.
  343. FOR_EACH_VEC( vecPrecacheModelStrings, i )
  344. {
  345. // Ignore any objects which requested an empty precache string for whatever reason.
  346. if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] )
  347. {
  348. int iModelIndex = CBaseEntity::PrecacheModel( vecPrecacheModelStrings[i] );
  349. PrecacheGibsForModel( iModelIndex );
  350. }
  351. }
  352. vecPrecacheModelStrings.RemoveAll();
  353. pData->GeneratePrecacheSoundStrings( bDynamicLoad, &vecPrecacheModelStrings );
  354. // Precache the sounds for everything
  355. FOR_EACH_VEC( vecPrecacheModelStrings, i )
  356. {
  357. // Ignore any objects which requested an empty precache string for whatever reason.
  358. if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] )
  359. {
  360. CBaseEntity::PrecacheScriptSound( vecPrecacheModelStrings[i] );
  361. }
  362. }
  363. vecPrecacheModelStrings.RemoveAll();
  364. #endif
  365. }
  366. // We reset the cached attribute class strings, since it's invalidated by level changes
  367. ItemSystem()->ResetAttribStringCache();
  368. #ifdef GAME_DLL
  369. ItemSystem()->ReloadWhitelist();
  370. #endif
  371. }
  372. //-----------------------------------------------------------------------------
  373. // Purpose:
  374. //-----------------------------------------------------------------------------
  375. void CInventoryManager::LevelShutdownPostEntity( void )
  376. {
  377. // We reset the cached attribute class strings, since it's invalidated by level changes
  378. ItemSystem()->ResetAttribStringCache();
  379. }
  380. //-----------------------------------------------------------------------------
  381. // Purpose: Lets the client know that we're now connected to the GC
  382. //-----------------------------------------------------------------------------
  383. #ifdef CLIENT_DLL
  384. void CInventoryManager::SendGCConnectedEvent( void )
  385. {
  386. IGameEvent *event = gameeventmanager->CreateEvent( "gc_connected" );
  387. if ( event )
  388. {
  389. gameeventmanager->FireEventClientSide( event );
  390. }
  391. }
  392. #endif
  393. #if !defined(NO_STEAM)
  394. //-----------------------------------------------------------------------------
  395. // Purpose: GC Msg handler to receive the dev "new item" response
  396. //-----------------------------------------------------------------------------
  397. class CGCDev_NewItemRequestResponse : public GCSDK::CGCClientJob
  398. {
  399. public:
  400. CGCDev_NewItemRequestResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  401. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  402. {
  403. GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
  404. if ( msg.Body().m_eResponse == k_EGCMsgResponseOK )
  405. {
  406. Msg("Received new item acknowledgement: %s\n", GCResultString[msg.Body().m_eResponse] );
  407. }
  408. else
  409. {
  410. Warning("Failed to generate new item: %s\n", GCResultString[msg.Body().m_eResponse] );
  411. }
  412. return true;
  413. }
  414. };
  415. GC_REG_JOB( GCSDK::CGCClient, CGCDev_NewItemRequestResponse, "CGCDev_NewItemRequestResponse", k_EMsgGCDev_NewItemRequestResponse, GCSDK::k_EServerTypeGCClient );
  416. #endif // NO_STEAM
  417. //-----------------------------------------------------------------------------
  418. // Purpose:
  419. //-----------------------------------------------------------------------------
  420. void CInventoryManager::RemovePendingRequest( CSteamID *pSteamID )
  421. {
  422. #ifdef CLIENT_DLL
  423. // Only the client, all requests are for the local player. Clear them all.
  424. m_hPendingInventoryRequests.Purge();
  425. return;
  426. #endif
  427. // On the server, remove all requests for the specified steam id
  428. int iCount = m_hPendingInventoryRequests.Count();
  429. for ( int i = iCount-1; i >= 0; i-- )
  430. {
  431. if ( m_hPendingInventoryRequests[i].pID == *pSteamID )
  432. {
  433. m_hPendingInventoryRequests.Remove(i);
  434. }
  435. }
  436. }
  437. #ifdef CLIENT_DLL
  438. //-----------------------------------------------------------------------------
  439. // Purpose:
  440. //-----------------------------------------------------------------------------
  441. void CInventoryManager::DropItem( itemid_t iItemID )
  442. {
  443. static CSchemaAttributeDefHandle pAttrDef_NoDelete( "cannot delete" );
  444. // Double check that this item can be delete
  445. CEconItemView *pItem = GetLocalInventory()->GetInventoryItemByItemID( iItemID );
  446. if ( !pItem || !pAttrDef_NoDelete || pItem->FindAttribute( pAttrDef_NoDelete ) )
  447. {
  448. return;
  449. }
  450. GCSDK::CGCMsg<MsgGCDelete_t> msg( k_EMsgGCDelete );
  451. msg.Body().m_unItemID = iItemID;
  452. GCClientSystem()->BSendMessage( msg );
  453. // Keep track of how many items we've discarded, but haven't received responses for.
  454. m_iPredictedDiscards++;
  455. }
  456. //-----------------------------------------------------------------------------
  457. // Purpose: Delete any items we can't find static data for. This can happen when we're testing
  458. // internally, and then remove an item. Shouldn't ever happen in the wild.
  459. //-----------------------------------------------------------------------------
  460. int CInventoryManager::DeleteUnknowns( CPlayerInventory *pInventory )
  461. {
  462. // We need to manually walk the main inventory's SOC, because unknown items won't be in the inventory
  463. GCSDK::CGCClientSharedObjectCache *pSOC = pInventory->GetSOC();
  464. if ( pSOC )
  465. {
  466. int iBadItems = 0;
  467. CGCClientSharedObjectTypeCache *pTypeCache = pSOC->FindTypeCache( CEconItem::k_nTypeID );
  468. if( pTypeCache )
  469. {
  470. for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ )
  471. {
  472. CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem );
  473. if ( pItem )
  474. {
  475. CEconItemDefinition *pData = ItemSystem()->GetStaticDataForItemByDefIndex( pItem->GetDefinitionIndex() );
  476. if ( !pData )
  477. {
  478. DropItem( pItem->GetItemID() );
  479. iBadItems++;
  480. }
  481. }
  482. }
  483. }
  484. return iBadItems;
  485. }
  486. return 0;
  487. }
  488. //-----------------------------------------------------------------------------
  489. // Purpose: Tries to move the specified item into the player's backpack.
  490. // FAILS if the backpack is full. Returns false in that case.
  491. //-----------------------------------------------------------------------------
  492. bool CInventoryManager::SetItemBackpackPosition( CEconItemView *pItem, uint32 iPosition, bool bForceUnequip, bool bAllowOverflow )
  493. {
  494. CPlayerInventory *pInventory = GetLocalInventory();
  495. if ( !pInventory )
  496. return false;
  497. const int iMaxItems = pInventory->GetMaxItemCount();
  498. if ( !iPosition )
  499. {
  500. // Build a list of empty slots. We track extra slots beyond the backpack for overflow.
  501. CUtlVector< bool > bFilledSlots;
  502. bFilledSlots.SetSize( iMaxItems * 2 );
  503. for ( int i = 0; i < bFilledSlots.Count(); ++i )
  504. {
  505. bFilledSlots[i] = false;
  506. }
  507. for ( int i = 0; i < pInventory->GetItemCount(); i++ )
  508. {
  509. CEconItemView *pTmpItem = pInventory->GetItem(i);
  510. // Ignore the item we're moving.
  511. if ( pTmpItem == pItem )
  512. continue;
  513. int iBackpackPos = GetBackpackPositionFromBackend( pTmpItem->GetInventoryPosition() );
  514. if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() )
  515. {
  516. bFilledSlots[iBackpackPos] = true;
  517. }
  518. }
  519. // Add predicted filled slots
  520. for ( int i = 0; i < m_PredictedFilledSlots.Count(); i++ )
  521. {
  522. int iBackpackPos = m_PredictedFilledSlots[i];
  523. if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() )
  524. {
  525. bFilledSlots[iBackpackPos] = true;
  526. }
  527. }
  528. // Now find an empty slot
  529. for ( int i = 1; i < bFilledSlots.Count(); i++ )
  530. {
  531. if ( !bFilledSlots[i] )
  532. {
  533. iPosition = i;
  534. break;
  535. }
  536. }
  537. if ( !iPosition )
  538. return false;
  539. }
  540. if ( !bAllowOverflow && iPosition > (uint32)iMaxItems )
  541. return false;
  542. //Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iPosition );
  543. uint32 iBackendPosition = bForceUnequip ? 0 : pItem->GetInventoryPosition();
  544. SetBackpackPosition( &iBackendPosition, iPosition );
  545. UpdateInventoryPosition( pInventory, pItem->GetItemID(), iBackendPosition );
  546. m_PredictedFilledSlots.AddToTail( iPosition );
  547. return true;
  548. }
  549. //-----------------------------------------------------------------------------
  550. // Purpose:
  551. //-----------------------------------------------------------------------------
  552. void CInventoryManager::MoveItemToBackpackPosition( CEconItemView *pItem, int iBackpackPosition )
  553. {
  554. CEconItemView *pOldItem = GetItemByBackpackPosition( iBackpackPosition );
  555. if ( pOldItem )
  556. {
  557. // Move the item in the new spot to our current spot
  558. SetItemBackpackPosition( pOldItem, GetBackpackPositionFromBackend(pItem->GetInventoryPosition()) );
  559. //Warning("Moved OLD item %llu to backpack slot: %d\n", pOldItem->GetItemID(), GetBackpackPositionFromBackend(iBackendPosition) );
  560. }
  561. // Move the item to the new spot
  562. SetItemBackpackPosition( pItem, iBackpackPosition );
  563. //Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iBackpackPosition );
  564. }
  565. //-----------------------------------------------------------------------------
  566. // Purpose:
  567. //-----------------------------------------------------------------------------
  568. class CWaitForBackpackSortFinishDialog : public CGenericWaitingDialog
  569. {
  570. public:
  571. CWaitForBackpackSortFinishDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
  572. {
  573. }
  574. protected:
  575. virtual void OnTimeout()
  576. {
  577. InventoryManager()->SortBackpackFinished();
  578. }
  579. };
  580. //-----------------------------------------------------------------------------
  581. // Purpose:
  582. //-----------------------------------------------------------------------------
  583. void CInventoryManager::SortBackpackBy( uint32 iSortType )
  584. {
  585. GCSDK::CProtoBufMsg<CMsgSortItems> msg( k_EMsgGCSortItems );
  586. msg.Body().set_sort_type( iSortType );
  587. GCClientSystem()->BSendMessage( msg );
  588. ShowWaitingDialog( new CWaitForBackpackSortFinishDialog( NULL ), "#BackpackSortExplanation_Title", true, false, 3.0f );
  589. m_bInBackpackSort = true;
  590. }
  591. //-----------------------------------------------------------------------------
  592. // Purpose:
  593. //-----------------------------------------------------------------------------
  594. void CInventoryManager::SortBackpackFinished( void )
  595. {
  596. m_bInBackpackSort = false;
  597. GetLocalInventory()->SendInventoryUpdateEvent();
  598. }
  599. //-----------------------------------------------------------------------------
  600. // Purpose: GC Msg handler to receive the sort finished message
  601. //-----------------------------------------------------------------------------
  602. class CGBackpackSortFinished : public GCSDK::CGCClientJob
  603. {
  604. public:
  605. CGBackpackSortFinished( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  606. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  607. {
  608. CloseWaitingDialog();
  609. InventoryManager()->SortBackpackFinished();
  610. return true;
  611. }
  612. };
  613. GC_REG_JOB( GCSDK::CGCClient, CGBackpackSortFinished, "CGBackpackSortFinished", k_EMsgGCBackpackSortFinished, GCSDK::k_EServerTypeGCClient );
  614. //-----------------------------------------------------------------------------
  615. // Purpose:
  616. //-----------------------------------------------------------------------------
  617. void CInventoryManager::UpdateInventoryPosition( CPlayerInventory *pInventory, uint64 ulItemID, uint32 unNewInventoryPos )
  618. {
  619. if ( !pInventory->GetInventoryItemByItemID( ulItemID ) )
  620. {
  621. Warning("Attempt to update inventory position failure: %s.\n", "could not find matching item ID");
  622. return;
  623. }
  624. if ( !pInventory->GetSOCDataForItem( ulItemID ) )
  625. {
  626. Warning("Attempt to update inventory position failure: %s\n", "could not find SOC data for item");
  627. return;
  628. }
  629. // In the incredibly rare case where the GC crashed while sorting our backpack, we won't have gotten
  630. // a k_EMsgGCBackpackSortFinished message. Assume that if we're requesting a manual move of an item, we're not sorting anymore.
  631. m_bInBackpackSort = false;
  632. // TF has multiple ways of using the inventory position bits. For all inventory positions moving forward, assume
  633. // they're in the new format.
  634. #if defined(TF_CLIENT_DLL) || defined(TF_DLL)
  635. if ( unNewInventoryPos != 0 )
  636. {
  637. unNewInventoryPos |= kBackendPosition_NewFormat;
  638. }
  639. #endif // defined(TF_CLIENT_DLL) || defined(TF_DLL)
  640. // Queue a message to be sent to the GC
  641. CMsgSetItemPositions_ItemPosition *pMsg = m_msgPendingSetItemPositions.add_item_positions();
  642. pMsg->set_item_id( ulItemID );
  643. pMsg->set_position( unNewInventoryPos );
  644. }
  645. void CInventoryManager::Update( float frametime )
  646. {
  647. // Check if we have any pending item position changes that we need to flush out
  648. if ( m_msgPendingSetItemPositions.item_positions_size() > 0 )
  649. {
  650. // !KLUDGE! It would be nice if we could just send this in one line instead of making a copy
  651. CProtoBufMsg<CMsgSetItemPositions> msg( k_EMsgGCSetItemPositions );
  652. msg.Body() = m_msgPendingSetItemPositions;
  653. GCClientSystem()->BSendMessage( msg );
  654. m_msgPendingSetItemPositions.Clear();
  655. }
  656. // Check if we have any pending account lookups to batch up
  657. if ( m_msgPendingLookupAccountNames.accountids_size() > 0 )
  658. {
  659. // !KLUDGE! It would be nice if we could just send this in one line instead of making a copy
  660. CProtoBufMsg< CMsgLookupMultipleAccountNames > msg( k_EMsgGCLookupMultipleAccountNames );
  661. msg.Body() = m_msgPendingLookupAccountNames;
  662. GCClientSystem()->BSendMessage( msg );
  663. m_msgPendingLookupAccountNames.Clear();
  664. }
  665. }
  666. //-----------------------------------------------------------------------------
  667. // Purpose:
  668. //-----------------------------------------------------------------------------
  669. void CInventoryManager::UpdateInventoryEquippedState( CPlayerInventory *pInventory, uint64 ulItemID, equipped_class_t unClass, equipped_slot_t unSlot )
  670. {
  671. // passing in INVALID_ITEM_ID means "unequip from this slot"
  672. if ( ulItemID != INVALID_ITEM_ID )
  673. {
  674. if ( !pInventory->GetInventoryItemByItemID( ulItemID ) )
  675. {
  676. //Warning("Attempt to update equipped state failure: %s.\n", "could not find matching item ID");
  677. return;
  678. }
  679. if ( !pInventory->GetSOCDataForItem( ulItemID ) )
  680. {
  681. //Warning("Attempt to update equipped state failure: %s\n", "could not find SOC data for item");
  682. return;
  683. }
  684. }
  685. CProtoBufMsg<CMsgAdjustItemEquippedState> msg( k_EMsgGCAdjustItemEquippedState );
  686. msg.Body().set_item_id( ulItemID );
  687. msg.Body().set_new_class( unClass );
  688. msg.Body().set_new_slot( unSlot );
  689. GCClientSystem()->BSendMessage( msg );
  690. }
  691. //-----------------------------------------------------------------------------
  692. // Purpose:
  693. //-----------------------------------------------------------------------------
  694. bool CInventoryManager::ShowItemsPickedUp( bool bForce, bool bReturnToGame, bool bNoPanel )
  695. {
  696. CPlayerInventory *pLocalInv = GetLocalInventory();
  697. if ( !pLocalInv )
  698. return false;
  699. // Don't bring it up if we're already browsing something in the gameUI
  700. vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
  701. if ( !bForce && vgui::ipanel()->IsVisible( gameuiPanel ) )
  702. return false;
  703. CUtlVector<CEconItemView*> aItemsFound;
  704. // Go through the root inventory and find any items that are in the "found" position
  705. int iCount = pLocalInv->GetItemCount();
  706. for ( int i = 0; i < iCount; i++ )
  707. {
  708. CEconItemView *pTmp = pLocalInv->GetItem(i);
  709. if ( !pTmp )
  710. continue;
  711. if ( pTmp->GetStaticData()->IsHidden() )
  712. continue;
  713. uint32 iPosition = pTmp->GetInventoryPosition();
  714. if ( IsUnacknowledged(iPosition) == false )
  715. continue;
  716. if ( GetBackpackPositionFromBackend(iPosition) != 0 )
  717. continue;
  718. // Now make sure we haven't got a clientside saved ack for this item.
  719. // This makes sure we don't show multiple pickups for items that we've found,
  720. // but haven't been able to move out of unack'd position due to the GC being unavailable.
  721. if ( HasBeenAckedByClient( pTmp ) )
  722. continue;
  723. aItemsFound.AddToTail( pTmp );
  724. }
  725. if ( !aItemsFound.Count() )
  726. return CheckForRoomAndForceDiscard();
  727. // We're not forcing the player to make room yet. Just show the pickup panel.
  728. CItemPickupPanel *pItemPanel = bNoPanel ? NULL : EconUI()->OpenItemPickupPanel();
  729. if ( pItemPanel )
  730. {
  731. pItemPanel->SetReturnToGame( bReturnToGame );
  732. }
  733. for ( int i = 0; i < aItemsFound.Count(); i++ )
  734. {
  735. if ( pItemPanel )
  736. {
  737. pItemPanel->AddItem( aItemsFound[i] );
  738. }
  739. else
  740. {
  741. AcknowledgeItem( aItemsFound[i] );
  742. }
  743. }
  744. if ( pItemPanel )
  745. {
  746. pItemPanel->MoveToFront();
  747. }
  748. else
  749. {
  750. SaveAckFile();
  751. }
  752. aItemsFound.Purge();
  753. return true;
  754. }
  755. //-----------------------------------------------------------------------------
  756. // Purpose:
  757. //-----------------------------------------------------------------------------
  758. bool CInventoryManager::CheckForRoomAndForceDiscard( void )
  759. {
  760. CPlayerInventory *pLocalInv = GetLocalInventory();
  761. if ( !pLocalInv )
  762. return false;
  763. // Go through the inventory and attempt to move any items outside the backpack into valid positions.
  764. // Remember the first item that we failed to move, so we can force a discard later.
  765. CEconItemView *pItem = NULL;
  766. const int iMaxItems = pLocalInv->GetMaxItemCount();
  767. int iCount = pLocalInv->GetItemCount();
  768. for ( int i = 0; i < iCount; i++ )
  769. {
  770. CEconItemView *pTmp = pLocalInv->GetItem(i);
  771. if ( !pTmp )
  772. continue;
  773. if ( pTmp->GetStaticData()->IsHidden() )
  774. continue;
  775. uint32 iPosition = pTmp->GetInventoryPosition();
  776. if ( IsUnacknowledged(iPosition) || GetBackpackPositionFromBackend(iPosition) > iMaxItems )
  777. {
  778. if ( !SetItemBackpackPosition( pTmp, 0, false, false ) )
  779. {
  780. pItem = pTmp;
  781. break;
  782. }
  783. }
  784. }
  785. // If we're not over the limit, we're done.
  786. if ( ( iCount - m_iPredictedDiscards ) <= iMaxItems )
  787. return false;
  788. if ( !pItem )
  789. return false;
  790. // We're forcing the player to make room for items he's found. Bring up that panel with the first item over the limit.
  791. CItemDiscardPanel *pDiscardPanel = EconUI()->OpenItemDiscardPanel();
  792. pDiscardPanel->SetItem( pItem );
  793. return true;
  794. }
  795. //-----------------------------------------------------------------------------
  796. // Purpose: Client Acknowledges an item and moves it in to the backpack
  797. //-----------------------------------------------------------------------------
  798. void CInventoryManager::AcknowledgeItem ( CEconItemView *pItem, bool bMoveToBackpack /* = true */ )
  799. {
  800. SetAckedByClient( pItem );
  801. int iMethod = GetUnacknowledgedReason( pItem->GetInventoryPosition() ) - 1;
  802. if ( iMethod >= ARRAYSIZE( g_pszItemPickupMethodStringsUnloc ) || iMethod < 0 )
  803. iMethod = 0;
  804. EconUI()->Gamestats_ItemTransaction( IE_ITEM_RECEIVED, pItem, g_pszItemPickupMethodStringsUnloc[iMethod] );
  805. // Then move it to the first empty backpack position
  806. if ( bMoveToBackpack )
  807. {
  808. SetItemBackpackPosition( pItem, 0, false, true );
  809. }
  810. }
  811. //-----------------------------------------------------------------------------
  812. // Purpose:
  813. //-----------------------------------------------------------------------------
  814. CEconItemView *CInventoryManager::GetItemByBackpackPosition( int iBackpackPosition )
  815. {
  816. CPlayerInventory *pInventory = GetLocalInventory();
  817. if ( !pInventory )
  818. return NULL;
  819. // Backpack positions start from 1
  820. Assert( iBackpackPosition > 0 && iBackpackPosition <= pInventory->GetMaxItemCount() );
  821. for ( int i = 0; i < pInventory->GetItemCount(); i++ )
  822. {
  823. CEconItemView *pItem = pInventory->GetItem(i);
  824. if ( GetBackpackPositionFromBackend( pItem->GetInventoryPosition() ) == iBackpackPosition )
  825. return pItem;
  826. }
  827. return NULL;
  828. }
  829. //-----------------------------------------------------------------------------
  830. // Purpose:
  831. //-----------------------------------------------------------------------------
  832. bool CInventoryManager::HasBeenAckedByClient( CEconItemView *pItem )
  833. {
  834. return ( GetAckKeyForItem( pItem ) != NULL );
  835. }
  836. //-----------------------------------------------------------------------------
  837. // Purpose:
  838. //-----------------------------------------------------------------------------
  839. void CInventoryManager::SetAckedByClient( CEconItemView *pItem )
  840. {
  841. VerifyAckFileLoaded();
  842. static char szTmp[128];
  843. Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() );
  844. m_pkvItemClientAckFile->SetInt( szTmp, 1 );
  845. m_bClientAckDirty = true;
  846. }
  847. //-----------------------------------------------------------------------------
  848. // Purpose:
  849. //-----------------------------------------------------------------------------
  850. void CInventoryManager::SetAckedByGC( CEconItemView *pItem, bool bSave )
  851. {
  852. KeyValues *pkvItem = GetAckKeyForItem( pItem );
  853. if ( pkvItem )
  854. {
  855. m_pkvItemClientAckFile->RemoveSubKey( pkvItem );
  856. pkvItem->deleteThis();
  857. m_bClientAckDirty = true;
  858. if ( bSave )
  859. {
  860. SaveAckFile();
  861. }
  862. }
  863. }
  864. //-----------------------------------------------------------------------------
  865. // Purpose:
  866. //-----------------------------------------------------------------------------
  867. KeyValues *CInventoryManager::GetAckKeyForItem( CEconItemView *pItem )
  868. {
  869. VerifyAckFileLoaded();
  870. static char szTmp[128];
  871. Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() );
  872. return m_pkvItemClientAckFile->FindKey( szTmp );
  873. }
  874. //-----------------------------------------------------------------------------
  875. // Purpose:
  876. //-----------------------------------------------------------------------------
  877. void CInventoryManager::VerifyAckFileLoaded( void )
  878. {
  879. if ( m_pkvItemClientAckFile )
  880. return;
  881. m_pkvItemClientAckFile = new KeyValues( ITEM_CLIENTACK_FILE );
  882. ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
  883. SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
  884. if ( pRemoteStorage )
  885. {
  886. if ( pRemoteStorage->FileExists(ITEM_CLIENTACK_FILE) )
  887. {
  888. int32 nFileSize = pRemoteStorage->GetFileSize( ITEM_CLIENTACK_FILE );
  889. if ( nFileSize > 0 )
  890. {
  891. CUtlBuffer buf( 0, nFileSize );
  892. if ( pRemoteStorage->FileRead( ITEM_CLIENTACK_FILE, buf.Base(), nFileSize ) == nFileSize )
  893. {
  894. buf.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSize );
  895. m_pkvItemClientAckFile->ReadAsBinary( buf );
  896. #ifdef _DEBUG
  897. if ( item_debug_clientacks.GetBool() )
  898. {
  899. m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_readack.txt", "MOD" );
  900. }
  901. #endif
  902. }
  903. }
  904. }
  905. }
  906. }
  907. //-----------------------------------------------------------------------------
  908. // Purpose: Clean up any item references that we no longer have items for.
  909. // This ensures that if we delete an item on the backend, we remove it from the ack file.
  910. //-----------------------------------------------------------------------------
  911. void CInventoryManager::CleanAckFile( void )
  912. {
  913. CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
  914. if ( !pInventory )
  915. return;
  916. if ( !pInventory->RetrievedInventoryFromSteam() )
  917. return;
  918. if ( m_pkvItemClientAckFile )
  919. {
  920. KeyValues *pKVItem = m_pkvItemClientAckFile->GetFirstSubKey();
  921. while ( pKVItem != NULL )
  922. {
  923. itemid_t ulID = (itemid_t)Q_atoi64( pKVItem->GetName() );
  924. if ( pInventory->GetInventoryItemByItemID(ulID) == NULL )
  925. {
  926. KeyValues *pTmp = pKVItem->GetNextKey();
  927. m_pkvItemClientAckFile->RemoveSubKey( pKVItem );
  928. pKVItem->deleteThis();
  929. m_bClientAckDirty = true;
  930. pKVItem = pTmp;
  931. }
  932. else
  933. {
  934. pKVItem = pKVItem->GetNextKey();
  935. }
  936. }
  937. }
  938. }
  939. //-----------------------------------------------------------------------------
  940. // Purpose:
  941. //-----------------------------------------------------------------------------
  942. void CInventoryManager::SaveAckFile( void )
  943. {
  944. if ( !m_bClientAckDirty )
  945. return;
  946. m_bClientAckDirty = false;
  947. ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
  948. SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
  949. if ( pRemoteStorage )
  950. {
  951. CUtlBuffer buf;
  952. m_pkvItemClientAckFile->WriteAsBinary( buf );
  953. pRemoteStorage->FileWrite( ITEM_CLIENTACK_FILE, buf.Base(), buf.TellPut() );
  954. #ifdef _DEBUG
  955. if ( item_debug_clientacks.GetBool() )
  956. {
  957. m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_saveack.txt", "MOD" );
  958. }
  959. #endif
  960. }
  961. }
  962. //-----------------------------------------------------------------------------
  963. // Purpose: GC sent name of account down
  964. //-----------------------------------------------------------------------------
  965. class CGCLookupAccountNameResponse : public GCSDK::CGCClientJob
  966. {
  967. public:
  968. CGCLookupAccountNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  969. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  970. {
  971. GCSDK::CGCMsg<MsgGCLookupAccountNameResponse_t> msg( pNetPacket );
  972. CUtlString playerName;
  973. if ( msg.BReadStr( &playerName ) )
  974. {
  975. InventoryManager()->PersonaName_Store( msg.Body().m_unAccountID, playerName.Get() );
  976. }
  977. return true;
  978. }
  979. };
  980. GC_REG_JOB( GCSDK::CGCClient, CGCLookupAccountNameResponse, "CGCLookupAccountNameResponse", k_EMsgGCLookupAccountNameResponse, GCSDK::k_EServerTypeGCClient );
  981. class CGCLookupMultipleAccountsNameResponse : public GCSDK::CGCClientJob
  982. {
  983. public:
  984. CGCLookupMultipleAccountsNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  985. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  986. {
  987. CProtoBufMsg<CMsgLookupMultipleAccountNamesResponse> msg( pNetPacket );
  988. for ( int i = 0 ; i < msg.Body().accounts_size() ; ++i )
  989. {
  990. const CMsgLookupMultipleAccountNamesResponse_Account &account = msg.Body().accounts( i );
  991. InventoryManager()->PersonaName_Store( account.accountid(), account.persona().c_str() );
  992. }
  993. return true;
  994. }
  995. };
  996. GC_REG_JOB( GCSDK::CGCClient, CGCLookupMultipleAccountsNameResponse, "CGCLookupMultipleAccountsNameResponse", k_EMsgGCLookupMultipleAccountNamesResponse, GCSDK::k_EServerTypeGCClient );
  997. void CInventoryManager::PersonaName_Precache( uint32 unAccountID )
  998. {
  999. const char *pszName = PersonaName_Get( unAccountID );
  1000. if ( pszName == NULL )
  1001. {
  1002. // Queue request name from GC
  1003. m_msgPendingLookupAccountNames.add_accountids( unAccountID );
  1004. // insert empty string so we don't ask again
  1005. m_mapPersonaNamesCache.Insert( unAccountID, "" );
  1006. }
  1007. }
  1008. const char *CInventoryManager::PersonaName_Get( uint32 unAccountID )
  1009. {
  1010. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  1011. // First ask Steam if this is one of friends -- if so we can get an up-to-date persona name.
  1012. {
  1013. const char *pszName = NULL;
  1014. if ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() )
  1015. {
  1016. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  1017. steamID.SetAccountID( unAccountID );
  1018. uint64 u64AccountId = steamID.ConvertToUint64();
  1019. // We're covering three states here:
  1020. // 1. We've never asked before. We need to queue up a RequestUserInformation.
  1021. // 2. We've asked before, and we haven't heard back yet
  1022. // 3. We've asked before, we heard back. Don't re-request user information.
  1023. auto index = m_personaNameRequests.Find( u64AccountId );
  1024. if ( !m_personaNameRequests.IsValidIndex( index ) )
  1025. {
  1026. // This is case 1--we've never asked before.
  1027. // If RequestUserInformation returns false, the information is already available.
  1028. // Otherwise, it will arrive later and we need to rebuild the description at that time.
  1029. if ( !steamapicontext->SteamFriends()->RequestUserInformation( steamID, true ) )
  1030. {
  1031. pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
  1032. Assert( pszName ); // Guaranteed by the steam api
  1033. if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 )
  1034. {
  1035. m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName );
  1036. return pszName;
  1037. }
  1038. }
  1039. else
  1040. {
  1041. // This is case 2, we've asked above.
  1042. m_personaNameRequests.Insert( u64AccountId, false );
  1043. }
  1044. }
  1045. else
  1046. {
  1047. if ( m_personaNameRequests[ index ] )
  1048. {
  1049. // This is case 3.
  1050. pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID );
  1051. Assert( pszName ); // Guaranteed by the steam api
  1052. if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 )
  1053. {
  1054. m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName );
  1055. return pszName;
  1056. }
  1057. }
  1058. }
  1059. }
  1060. }
  1061. // If that didn't work, ask the server we're playing on if they know this account ID.
  1062. CBasePlayer *pPlayer = GetPlayerByAccountID( unAccountID );
  1063. if ( pPlayer )
  1064. {
  1065. const char *pszPlayerName = pPlayer->GetPlayerName();
  1066. if ( pszPlayerName )
  1067. {
  1068. m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszPlayerName );
  1069. return pszPlayerName;
  1070. }
  1071. }
  1072. // If *that* didn't work, look in our cache populated by the GC (or the above paths). This
  1073. // might be out of date but it's better than nothing.
  1074. int idx = m_mapPersonaNamesCache.Find( unAccountID );
  1075. if ( m_mapPersonaNamesCache.IsValidIndex( idx ) )
  1076. {
  1077. return m_mapPersonaNamesCache[idx].Get();
  1078. }
  1079. return "[unknown]";
  1080. }
  1081. void CInventoryManager::PersonaName_Store( uint32 unAccountID, const char *pPersonaName )
  1082. {
  1083. m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pPersonaName );
  1084. }
  1085. #endif // CLIENT_DLL
  1086. //=======================================================================================================================
  1087. // PLAYER INVENTORY
  1088. //=======================================================================================================================
  1089. //-----------------------------------------------------------------------------
  1090. // Purpose:
  1091. //-----------------------------------------------------------------------------
  1092. CPlayerInventory::CPlayerInventory( void )
  1093. {
  1094. m_bGotItemsFromSteam = false;
  1095. m_iPendingRequests = 0;
  1096. m_aInventoryItems.Purge();
  1097. m_pSOCache = NULL;
  1098. }
  1099. //-----------------------------------------------------------------------------
  1100. // Purpose:
  1101. //-----------------------------------------------------------------------------
  1102. CPlayerInventory::~CPlayerInventory()
  1103. {
  1104. FOR_EACH_VEC( m_vecItemHandles, i )
  1105. {
  1106. m_vecItemHandles[ i ]->InventoryIsBeingDeleted();
  1107. }
  1108. m_vecItemHandles.Purge();
  1109. if ( m_iPendingRequests )
  1110. {
  1111. InventoryManager()->RemovePendingRequest( &m_OwnerID );
  1112. m_iPendingRequests = 0;
  1113. }
  1114. SOClear();
  1115. InventoryManager()->DeregisterInventory( this );
  1116. }
  1117. //-----------------------------------------------------------------------------
  1118. // Purpose:
  1119. //-----------------------------------------------------------------------------
  1120. void CPlayerInventory::SOClear()
  1121. {
  1122. if ( m_OwnerID.IsValid() )
  1123. {
  1124. CGCClientSystem *pClientSystem = GCClientSystem();
  1125. Assert ( pClientSystem != NULL );
  1126. if ( pClientSystem != NULL )
  1127. {
  1128. CGCClient *pClient = pClientSystem->GetGCClient();
  1129. Assert ( pClient != NULL );
  1130. pClient->RemoveSOCacheListener( m_OwnerID, this );
  1131. }
  1132. }
  1133. // Somebody registered as a listener through us, but now our Steam ID
  1134. // is changing? This is bad news.
  1135. Assert( m_vecListeners.Count() == 0 );
  1136. while ( m_vecListeners.Count() > 0 )
  1137. {
  1138. RemoveListener( m_vecListeners[0] );
  1139. }
  1140. // If we were subscribed, we should have gotten our unsubscribe message,
  1141. // and that should have cleared the pointer
  1142. Assert( m_pSOCache == NULL);
  1143. m_pSOCache = NULL;
  1144. }
  1145. //-----------------------------------------------------------------------------
  1146. // Purpose:
  1147. //-----------------------------------------------------------------------------
  1148. void CPlayerInventory::AddItemHandle( CEconItemViewHandle* pHandle )
  1149. {
  1150. FOR_EACH_VEC( m_vecItemHandles, i )
  1151. {
  1152. if ( m_vecItemHandles[ i ] == pHandle )
  1153. {
  1154. Assert( !"Item handle already in list to track!" );
  1155. return;
  1156. }
  1157. }
  1158. m_vecItemHandles.AddToTail( pHandle );
  1159. }
  1160. //-----------------------------------------------------------------------------
  1161. // Purpose:
  1162. //-----------------------------------------------------------------------------
  1163. void CPlayerInventory::RemoveItemHandle( CEconItemViewHandle* pHandle )
  1164. {
  1165. FOR_EACH_VEC( m_vecItemHandles, i )
  1166. {
  1167. if ( m_vecItemHandles[ i ] == pHandle )
  1168. {
  1169. m_vecItemHandles.Remove( i );
  1170. return;
  1171. }
  1172. }
  1173. Assert( !"Could not find item handle to remove!" );
  1174. }
  1175. void CPlayerInventory::Clear()
  1176. {
  1177. SOClear();
  1178. m_OwnerID = CSteamID();
  1179. }
  1180. //-----------------------------------------------------------------------------
  1181. // Purpose:
  1182. //-----------------------------------------------------------------------------
  1183. void CPlayerInventory::RequestInventory( CSteamID pSteamID )
  1184. {
  1185. // Make sure we don't already have somebody else's stuff
  1186. // on hand
  1187. if ( m_OwnerID != pSteamID )
  1188. SOClear();
  1189. // Remember whose inventory we're looking at
  1190. m_OwnerID = pSteamID;
  1191. // SteamID must be valid
  1192. if ( !m_OwnerID.IsValid() || !m_OwnerID.BIndividualAccount() )
  1193. {
  1194. Assert( m_OwnerID.IsValid() );
  1195. Assert( m_OwnerID.BIndividualAccount() );
  1196. return;
  1197. }
  1198. // If we don't already have an SO cache, then ask the GC for one,
  1199. // and start listening to it. We will receive our "subscribed" message
  1200. // when the data is valid
  1201. GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, this );
  1202. }
  1203. void CPlayerInventory::AddListener( GCSDK::ISharedObjectListener *pListener )
  1204. {
  1205. Assert( m_OwnerID.IsValid() );
  1206. if ( m_vecListeners.Find( pListener ) < 0 )
  1207. {
  1208. m_vecListeners.AddToTail( pListener );
  1209. GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, pListener );
  1210. }
  1211. }
  1212. void CPlayerInventory::RemoveListener( GCSDK::ISharedObjectListener *pListener )
  1213. {
  1214. if ( m_OwnerID.IsValid() )
  1215. {
  1216. m_vecListeners.FindAndFastRemove( pListener );
  1217. GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerID, pListener );
  1218. }
  1219. else
  1220. {
  1221. Assert( m_vecListeners.Count() == 0 );
  1222. }
  1223. }
  1224. //-----------------------------------------------------------------------------
  1225. // Purpose: Helper function to add a new item for a econ item
  1226. //-----------------------------------------------------------------------------
  1227. bool CPlayerInventory::AddEconItem( CEconItem * pItem, bool bUpdateAckFile, bool bWriteAckFile, bool bCheckForNewItems )
  1228. {
  1229. CEconItemView newItem;
  1230. if( !FilloutItemFromEconItem( &newItem, pItem ) )
  1231. {
  1232. return false;
  1233. }
  1234. int iIdx = m_aInventoryItems.Insert( newItem );
  1235. DirtyItemHandles();
  1236. ItemHasBeenUpdated( &m_aInventoryItems[iIdx], bUpdateAckFile, bWriteAckFile );
  1237. #ifdef CLIENT_DLL
  1238. if ( bCheckForNewItems && InventoryManager()->GetLocalInventory() == this )
  1239. {
  1240. bool bNotify = IsUnacknowledged( pItem->GetInventoryToken() );
  1241. // ignore Halloween drops
  1242. bNotify &= pItem->GetOrigin() != kEconItemOrigin_HalloweenDrop;
  1243. // only notify for specific reasons
  1244. unacknowledged_item_inventory_positions_t reason = GetUnacknowledgedReason( pItem->GetInventoryToken() );
  1245. switch ( reason )
  1246. {
  1247. case UNACK_ITEM_UNKNOWN:
  1248. case UNACK_ITEM_DROPPED:
  1249. case UNACK_ITEM_SUPPORT:
  1250. case UNACK_ITEM_EARNED:
  1251. case UNACK_ITEM_REFUNDED:
  1252. case UNACK_ITEM_COLLECTION_REWARD:
  1253. case UNACK_ITEM_TRADED:
  1254. case UNACK_ITEM_GIFTED:
  1255. case UNACK_ITEM_QUEST_LOANER:
  1256. case UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD:
  1257. break;
  1258. default:
  1259. bNotify = false;
  1260. break;
  1261. }
  1262. if ( bNotify && !pItem->GetItemDefinition()->IsHidden() )
  1263. {
  1264. OnHasNewItems();
  1265. }
  1266. }
  1267. #endif
  1268. return true;
  1269. }
  1270. //-----------------------------------------------------------------------------
  1271. // Purpose: Creates a script item and associates it with this econ item
  1272. //-----------------------------------------------------------------------------
  1273. void CPlayerInventory::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
  1274. {
  1275. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  1276. if( pObject->GetTypeID() != CEconItem::k_nTypeID )
  1277. return;
  1278. Assert( steamIDOwner == m_OwnerID );
  1279. if ( steamIDOwner != m_OwnerID )
  1280. return;
  1281. // We shouldn't get these notifications unless we're subscribed, right?
  1282. if ( m_pSOCache == NULL)
  1283. {
  1284. Assert( m_pSOCache );
  1285. return;
  1286. }
  1287. // Don't bother unless it's an incremental notification.
  1288. // For mass updates, we'll do everything more efficiently in one place
  1289. if ( eEvent != GCSDK::eSOCacheEvent_Incremental )
  1290. {
  1291. Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed || eEvent == GCSDK::eSOCacheEvent_ListenerAdded );
  1292. return;
  1293. }
  1294. CEconItem *pItem = (CEconItem *)pObject;
  1295. AddEconItem( pItem, true, true, true );
  1296. SendInventoryUpdateEvent();
  1297. }
  1298. //-----------------------------------------------------------------------------
  1299. // Purpose: Updates the script item associated with this econ item
  1300. //-----------------------------------------------------------------------------
  1301. void CPlayerInventory::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
  1302. {
  1303. if( pObject->GetTypeID() != CEconItem::k_nTypeID )
  1304. return;
  1305. Assert( steamIDOwner == m_OwnerID );
  1306. if ( steamIDOwner != m_OwnerID )
  1307. return;
  1308. // We shouldn't get these notifications unless we're subscribed, right?
  1309. if ( m_pSOCache == NULL)
  1310. {
  1311. Assert( m_pSOCache );
  1312. return;
  1313. }
  1314. // Don't bother unless it's an incremental notification.
  1315. // For mass updates, we'll do everything more efficiently in one place
  1316. if ( eEvent != GCSDK::eSOCacheEvent_Incremental )
  1317. {
  1318. Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed );
  1319. return;
  1320. }
  1321. CEconItem *pEconItem = (CEconItem *)pObject;
  1322. bool bChanged = false;
  1323. CEconItemView *pScriptItem = GetInventoryItemByItemID( pEconItem->GetItemID() );
  1324. if ( pScriptItem )
  1325. {
  1326. if ( FilloutItemFromEconItem( pScriptItem, pEconItem ) )
  1327. {
  1328. ItemHasBeenUpdated( pScriptItem, false, false );
  1329. }
  1330. bChanged = true;
  1331. }
  1332. else
  1333. {
  1334. // The item isn't in this inventory right now. But it may need to be
  1335. // after the update, so try adding it and see if the inventory wants it.
  1336. bChanged = AddEconItem( pEconItem, false, false, false );
  1337. }
  1338. if ( bChanged )
  1339. {
  1340. ResortInventory();
  1341. DirtyItemHandles();
  1342. #ifdef CLIENT_DLL
  1343. // Client doesn't update inventory while items are moving in a backpack sort. Does it once at the sort end instead.
  1344. if ( !InventoryManager()->IsInBackpackSort() )
  1345. #endif
  1346. {
  1347. SendInventoryUpdateEvent();
  1348. }
  1349. #ifdef _DEBUG
  1350. if ( item_inventory_debug.GetBool() )
  1351. {
  1352. DumpInventoryToConsole( true );
  1353. }
  1354. #endif
  1355. }
  1356. }
  1357. //-----------------------------------------------------------------------------
  1358. // Purpose: Removes the script item associated with this econ item
  1359. //-----------------------------------------------------------------------------
  1360. void CPlayerInventory::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
  1361. {
  1362. if( pObject->GetTypeID() != CEconItem::k_nTypeID )
  1363. return;
  1364. Assert( steamIDOwner == m_OwnerID );
  1365. if ( steamIDOwner != m_OwnerID )
  1366. return;
  1367. // We shouldn't get these notifications unless we're subscribed, right?
  1368. if ( m_pSOCache == NULL)
  1369. {
  1370. Assert( m_pSOCache );
  1371. return;
  1372. }
  1373. // Don't bother unless it's an incremental notification.
  1374. // For mass updates, we'll do everything more efficiently in one place
  1375. if ( eEvent != GCSDK::eSOCacheEvent_Incremental )
  1376. {
  1377. Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed );
  1378. return;
  1379. }
  1380. CEconItem *pEconItem = (CEconItem *)pObject;
  1381. RemoveItem( pEconItem->GetItemID() );
  1382. #ifdef CLIENT_DLL
  1383. InventoryManager()->OnItemDeleted( this );
  1384. #endif
  1385. SendInventoryUpdateEvent();
  1386. }
  1387. //-----------------------------------------------------------------------------
  1388. // Purpose: This is our initial notification that this cache has been received
  1389. // from the server.
  1390. //-----------------------------------------------------------------------------
  1391. void CPlayerInventory::SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
  1392. {
  1393. // Make sure we expect notifications about this guy
  1394. Assert( steamIDOwner == m_OwnerID );
  1395. if ( steamIDOwner != m_OwnerID )
  1396. return;
  1397. #ifdef _DEBUG
  1398. Msg("CPlayerInventory::SOCacheSubscribed\n");
  1399. #endif
  1400. // Clear our old inventory
  1401. m_aInventoryItems.Purge();
  1402. DirtyItemHandles();
  1403. // Locate the cache that was just subscribed to
  1404. m_pSOCache = GCClientSystem()->GetSOCache( m_OwnerID );
  1405. if ( m_pSOCache == NULL )
  1406. {
  1407. Assert( m_pSOCache != NULL );
  1408. return;
  1409. }
  1410. // add all the items already in the inventory
  1411. CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindTypeCache( CEconItem::k_nTypeID );
  1412. if( pTypeCache )
  1413. {
  1414. for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ )
  1415. {
  1416. CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem );
  1417. AddEconItem(pItem, true, false, true );
  1418. }
  1419. }
  1420. m_bGotItemsFromSteam = true;
  1421. #ifdef CLIENT_DLL
  1422. if ( InventoryManager()->GetLocalInventory() == this )
  1423. {
  1424. // Only validate the local player inventory
  1425. ValidateInventoryPositions();
  1426. // tell the entire client that we're 'connected' to the GC now
  1427. CInventoryManager::SendGCConnectedEvent();
  1428. }
  1429. #endif
  1430. ResortInventory();
  1431. #ifdef CLIENT_DLL
  1432. // Now that we've read all the items in, write out the ack file (only if we're the local inventory)
  1433. if ( InventoryManager()->GetLocalInventory() == this )
  1434. {
  1435. InventoryManager()->CleanAckFile();
  1436. InventoryManager()->SaveAckFile();
  1437. }
  1438. #endif
  1439. }
  1440. bool CInventoryManager::IsValidPlayerClass( equipped_class_t unClass )
  1441. {
  1442. const bool bResult = ItemSystem()->GetItemSchema()->IsValidClass( unClass );
  1443. AssertMsg( bResult, "Invalid player class!" );
  1444. return bResult;
  1445. }
  1446. //-----------------------------------------------------------------------------
  1447. // Purpose: Removes the script item associated with this econ item
  1448. //-----------------------------------------------------------------------------
  1449. void CPlayerInventory::ValidateInventoryPositions( void )
  1450. {
  1451. #ifdef TF2
  1452. if ( engine->GetAppID() == 520 )
  1453. {
  1454. TFInventoryManager()->DeleteUnknowns( this );
  1455. }
  1456. #endif
  1457. }
  1458. //-----------------------------------------------------------------------------
  1459. // Purpose:
  1460. //-----------------------------------------------------------------------------
  1461. void CPlayerInventory::ItemHasBeenUpdated( CEconItemView *pItem, bool bUpdateAckFile, bool bWriteAckFile )
  1462. {
  1463. #ifdef CLIENT_DLL
  1464. // Handle the clientside ack file
  1465. if ( bUpdateAckFile && !IsUnacknowledged(pItem->GetInventoryPosition()) )
  1466. {
  1467. if ( InventoryManager()->GetLocalInventory() == this )
  1468. {
  1469. InventoryManager()->SetAckedByGC( pItem, bWriteAckFile );
  1470. }
  1471. }
  1472. #endif
  1473. }
  1474. //-----------------------------------------------------------------------------
  1475. // Purpose:
  1476. //-----------------------------------------------------------------------------
  1477. void CPlayerInventory::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
  1478. {
  1479. m_pSOCache = NULL;
  1480. m_bGotItemsFromSteam = false;
  1481. m_aInventoryItems.Purge();
  1482. DirtyItemHandles();
  1483. }
  1484. //-----------------------------------------------------------------------------
  1485. // Purpose: On the client this sends the "inventory_updated" event. On the server
  1486. // it does nothing.
  1487. //-----------------------------------------------------------------------------
  1488. void CPlayerInventory::SendInventoryUpdateEvent()
  1489. {
  1490. #ifdef CLIENT_DLL
  1491. if( InventoryManager()->GetLocalInventory() == this )
  1492. {
  1493. IGameEvent *event = gameeventmanager->CreateEvent( "inventory_updated" );
  1494. if ( event )
  1495. {
  1496. gameeventmanager->FireEventClientSide( event );
  1497. }
  1498. }
  1499. #endif
  1500. }
  1501. //-----------------------------------------------------------------------------
  1502. // Purpose: Fills out all the fields in the script item based on what's in the
  1503. // econ item
  1504. //-----------------------------------------------------------------------------
  1505. bool CPlayerInventory::FilloutItemFromEconItem( CEconItemView *pScriptItem, CEconItem *pEconItem )
  1506. {
  1507. // We need to detect the case where items have been updated & moved bags / positions.
  1508. uint32 iOldPos = pScriptItem->GetInventoryPosition();
  1509. bool bWasInThisBag = ItemShouldBeIncluded( iOldPos );
  1510. // Ignore items that this inventory doesn't care about
  1511. if ( !ItemShouldBeIncluded( pEconItem->GetInventoryToken() ) )
  1512. {
  1513. // The item has been moved out of this bag. Ensure our derived inventory classes know.
  1514. if ( bWasInThisBag )
  1515. {
  1516. // We need to update it before it's removed.
  1517. ItemHasBeenUpdated( pScriptItem, false, false );
  1518. RemoveItem( pEconItem->GetItemID() );
  1519. }
  1520. return false;
  1521. }
  1522. pScriptItem->Init( pEconItem->GetDefinitionIndex(), pEconItem->GetQuality(), pEconItem->GetItemLevel(), pEconItem->GetAccountID() );
  1523. if ( !pScriptItem->IsValid() )
  1524. return false;
  1525. pScriptItem->SetItemID( pEconItem->GetItemID() );
  1526. pScriptItem->SetInventoryPosition( pEconItem->GetInventoryToken() );
  1527. OnItemChangedPosition( pScriptItem, iOldPos );
  1528. #if BUILD_ITEM_NAME_AND_DESC
  1529. // Precache account names if we have any. We do this way in advance of any code that might
  1530. // use it (ie., description text building) so that by the time we try that we already have
  1531. // the data setup.
  1532. //
  1533. // We don't worry about yielding here because this inventory code only runs on game
  1534. // clients/servers, not the GC.
  1535. CSteamAccountIDAttributeCollector AccountIDCollector;
  1536. pEconItem->IterateAttributes( &AccountIDCollector );
  1537. FOR_EACH_VEC( AccountIDCollector.GetAccountIDs(), i )
  1538. {
  1539. InventoryManager()->PersonaName_Precache( (AccountIDCollector.GetAccountIDs())[i] );
  1540. }
  1541. #endif
  1542. return true;
  1543. }
  1544. //-----------------------------------------------------------------------------
  1545. // Purpose:
  1546. //-----------------------------------------------------------------------------
  1547. void CPlayerInventory::DumpInventoryToConsole( bool bRoot )
  1548. {
  1549. if ( bRoot )
  1550. {
  1551. #ifdef CLIENT_DLL
  1552. Msg("(CLIENT) Inventory:\n");
  1553. #else
  1554. Msg("(SERVER) Inventory for account (%d):\n", m_OwnerID.GetAccountID() );
  1555. #endif
  1556. Msg(" Version: %llu:\n", m_pSOCache ? m_pSOCache->GetVersion() : -1 );
  1557. }
  1558. int iCount = m_aInventoryItems.Count();
  1559. Msg(" Num items: %d\n", iCount );
  1560. for ( int i = 0; i < iCount; i++ )
  1561. {
  1562. Msg(" %s (ID %llu)\n", m_aInventoryItems[i].GetStaticData()->GetDefinitionName(), m_aInventoryItems[i].GetItemID() );
  1563. }
  1564. }
  1565. //-----------------------------------------------------------------------------
  1566. // Purpose:
  1567. //-----------------------------------------------------------------------------
  1568. void CPlayerInventory::RemoveItem( itemid_t iItemID )
  1569. {
  1570. int iIndex;
  1571. CEconItemView *pItem = GetInventoryItemByItemID( iItemID, &iIndex );
  1572. if ( pItem )
  1573. {
  1574. ItemIsBeingRemoved( pItem );
  1575. FOR_EACH_VEC( m_vecItemHandles, i )
  1576. {
  1577. m_vecItemHandles[ i ]->MarkDirty();
  1578. m_vecItemHandles[ i ]->ItemIsBeingDeleted( pItem );
  1579. }
  1580. m_aInventoryItems.Remove(iIndex);
  1581. #ifdef _DEBUG
  1582. if ( item_inventory_debug.GetBool() )
  1583. {
  1584. DumpInventoryToConsole( true );
  1585. }
  1586. #endif
  1587. }
  1588. // Don't need to resort because items will still be in order
  1589. }
  1590. //-----------------------------------------------------------------------------
  1591. // Purpose: Finds the item in our inventory that matches the specified global index
  1592. //-----------------------------------------------------------------------------
  1593. CEconItemView *CPlayerInventory::GetInventoryItemByItemID( itemid_t iIndex, int *pIndex )
  1594. {
  1595. int iCount = m_aInventoryItems.Count();
  1596. for ( int i = 0; i < iCount; i++ )
  1597. {
  1598. if ( m_aInventoryItems[i].GetItemID() == iIndex )
  1599. {
  1600. if ( pIndex )
  1601. {
  1602. *pIndex = i;
  1603. }
  1604. return &m_aInventoryItems[i];
  1605. }
  1606. }
  1607. return NULL;
  1608. }
  1609. //-----------------------------------------------------------------------------
  1610. // Finds the item in our inventory that matches the specified global original id
  1611. //-----------------------------------------------------------------------------
  1612. CEconItemView *CPlayerInventory::GetInventoryItemByOriginalID( itemid_t iOriginalID, int *pIndex /*= NULL*/ )
  1613. {
  1614. int iCount = m_aInventoryItems.Count();
  1615. for ( int i = 0; i < iCount; i++ )
  1616. {
  1617. CEconItem *pItem = m_aInventoryItems[i].GetSOCData();
  1618. if ( pItem && pItem->GetOriginalID() == iOriginalID )
  1619. {
  1620. if ( pIndex )
  1621. {
  1622. *pIndex = i;
  1623. }
  1624. return &m_aInventoryItems[i];
  1625. }
  1626. }
  1627. return NULL;
  1628. }
  1629. //-----------------------------------------------------------------------------
  1630. // Purpose: Finds the item in our inventory in the specified position
  1631. //-----------------------------------------------------------------------------
  1632. CEconItemView *CPlayerInventory::GetItemByPosition( int iPosition, int *pIndex )
  1633. {
  1634. int iCount = m_aInventoryItems.Count();
  1635. for ( int i = 0; i < iCount; i++ )
  1636. {
  1637. if ( m_aInventoryItems[i].GetInventoryPosition() == (unsigned int)iPosition )
  1638. {
  1639. if ( pIndex )
  1640. {
  1641. *pIndex = i;
  1642. }
  1643. return &m_aInventoryItems[i];
  1644. }
  1645. }
  1646. return NULL;
  1647. }
  1648. // Finds the first item in our backpack with match itemdef
  1649. //-----------------------------------------------------------------------------
  1650. CEconItemView *CPlayerInventory::FindFirstItembyItemDef( item_definition_index_t iItemDef )
  1651. {
  1652. int iCount = m_aInventoryItems.Count();
  1653. for ( int i = 0; i < iCount; i++ )
  1654. {
  1655. //GetItemDefIndex()
  1656. if ( m_aInventoryItems[i].GetItemDefIndex() == iItemDef )
  1657. {
  1658. return &m_aInventoryItems[i];
  1659. }
  1660. }
  1661. return NULL;
  1662. }
  1663. //-----------------------------------------------------------------------------
  1664. // Purpose: Get the index for the item in our inventory utlvector
  1665. //-----------------------------------------------------------------------------
  1666. int CPlayerInventory::GetIndexForItem( CEconItemView *pItem )
  1667. {
  1668. int iCount = m_aInventoryItems.Count();
  1669. for ( int i = 0; i < iCount; i++ )
  1670. {
  1671. if ( m_aInventoryItems[i].GetItemID() == pItem->GetItemID() )
  1672. return i;
  1673. }
  1674. return -1;
  1675. }
  1676. //-----------------------------------------------------------------------------
  1677. // Purpose: Dirty all the item handles that are registered with us
  1678. //-----------------------------------------------------------------------------
  1679. void CPlayerInventory::DirtyItemHandles()
  1680. {
  1681. FOR_EACH_VEC( m_vecItemHandles, i )
  1682. {
  1683. m_vecItemHandles[ i ]->MarkDirty();
  1684. }
  1685. }
  1686. //-----------------------------------------------------------------------------
  1687. // Purpose: Get the item object cache data for the specified item
  1688. //-----------------------------------------------------------------------------
  1689. CEconItem *CPlayerInventory::GetSOCDataForItem( itemid_t iItemID )
  1690. {
  1691. if ( !m_pSOCache )
  1692. return NULL;
  1693. CEconItem soIndex;
  1694. soIndex.SetItemID( iItemID );
  1695. return (CEconItem *)m_pSOCache->FindSharedObject( soIndex );
  1696. }
  1697. #if defined (_DEBUG) && defined(CLIENT_DLL)
  1698. CON_COMMAND_F( item_deleteall, "WARNING: Removes all of the items in your inventory.", FCVAR_CHEAT )
  1699. {
  1700. CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
  1701. if ( !pInventory )
  1702. return;
  1703. int iCount = pInventory->GetItemCount();
  1704. for ( int i = 0; i < iCount; i++ )
  1705. {
  1706. CEconItemView *pItem = pInventory->GetItem(i);
  1707. if ( pItem )
  1708. {
  1709. InventoryManager()->DropItem( pItem->GetItemID() );
  1710. }
  1711. }
  1712. InventoryManager()->UpdateLocalInventory();
  1713. }
  1714. #endif
  1715. //-----------------------------------------------------------------------------
  1716. // Purpose:
  1717. //-----------------------------------------------------------------------------
  1718. int CPlayerInventory::GetRecipeCount() const
  1719. {
  1720. const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap();
  1721. return mapRecipes.Count();
  1722. }
  1723. //-----------------------------------------------------------------------------
  1724. // Purpose:
  1725. //-----------------------------------------------------------------------------
  1726. const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDef( int iIndex )
  1727. {
  1728. if ( !m_pSOCache )
  1729. return NULL;
  1730. if ( iIndex < 0 || iIndex >= GetRecipeCount() )
  1731. return NULL;
  1732. const CEconItemSchema::RecipeDefinitionMap_t& mapRecipes = GetItemSchema()->GetRecipeDefinitionMap();
  1733. // Store off separate index for "number of items iterated over" in case something
  1734. // deletes from the recipes map out from under us.
  1735. int j = 0;
  1736. FOR_EACH_MAP_FAST( mapRecipes, i )
  1737. {
  1738. if ( j == iIndex )
  1739. return mapRecipes[i];
  1740. j++;
  1741. }
  1742. return NULL;
  1743. }
  1744. //-----------------------------------------------------------------------------
  1745. // Purpose:
  1746. //-----------------------------------------------------------------------------
  1747. const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDefByDefIndex( uint16 iDefIndex )
  1748. {
  1749. if ( !m_pSOCache )
  1750. return NULL;
  1751. // check always-known recipes
  1752. const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap();
  1753. int i = mapRecipes.Find( iDefIndex );
  1754. if ( i != mapRecipes.InvalidIndex() )
  1755. return mapRecipes[i];
  1756. // there are no more SO recipes
  1757. return NULL;
  1758. }
  1759. //-----------------------------------------------------------------------------
  1760. // Purpose:
  1761. //-----------------------------------------------------------------------------
  1762. void CEconItemViewHandle::SetItem( CEconItemView* pItem )
  1763. {
  1764. m_pItem = pItem;
  1765. if ( pItem )
  1766. {
  1767. // Cache the item_id for lookup when our pointer gets dirtied
  1768. m_nItemID = pItem->GetItemID();
  1769. auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() );
  1770. Assert( pInv );
  1771. if ( m_pInv != pInv )
  1772. {
  1773. // If this is a different inventory, unsubscribe. This can happen if the
  1774. // handle gets reused
  1775. if ( m_pInv )
  1776. {
  1777. m_pInv->RemoveItemHandle( this );
  1778. }
  1779. m_pInv = pInv;
  1780. // Subscribe to the new inventory
  1781. m_pInv->AddItemHandle( this );
  1782. }
  1783. }
  1784. }
  1785. //-----------------------------------------------------------------------------
  1786. // Purpose: Return a pointer to a CEconItemView
  1787. //-----------------------------------------------------------------------------
  1788. CEconItemView* CEconItemViewHandle::Get() const
  1789. {
  1790. // If our pointer is dirty, we need to go get a new pointer
  1791. if ( m_bPointerDirty )
  1792. {
  1793. if ( m_pInv )
  1794. {
  1795. m_pItem = m_pInv->GetInventoryItemByItemID( m_nItemID );
  1796. m_bPointerDirty = false;
  1797. }
  1798. }
  1799. return m_pItem;
  1800. }
  1801. //-----------------------------------------------------------------------------
  1802. // Purpose: Unsubscribe us from future updates
  1803. //-----------------------------------------------------------------------------
  1804. CEconItemHandle::~CEconItemHandle()
  1805. {
  1806. UnsubscribeFromSOEvents();
  1807. }
  1808. //-----------------------------------------------------------------------------
  1809. // Purpose: Save a pointer to the item and register us for SOCache events
  1810. //-----------------------------------------------------------------------------
  1811. void CEconItemHandle::SetItem( CEconItem* pItem )
  1812. {
  1813. UnsubscribeFromSOEvents();
  1814. m_pItem = NULL;
  1815. m_iItemID = INVALID_ITEM_ID;
  1816. if ( pItem )
  1817. {
  1818. auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() );
  1819. if ( pInv )
  1820. {
  1821. m_OwnerSteamID.SetFromUint64( pInv->GetOwner().ConvertToUint64() );
  1822. GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerSteamID, this );
  1823. }
  1824. m_pItem = pItem;
  1825. m_iItemID = pItem->GetID();
  1826. }
  1827. }
  1828. //-----------------------------------------------------------------------------
  1829. // Purpose: Check if out item got deleted. If it did, mark our pointer as NULL
  1830. // so future dereferences will get NULL instead of a stale pointer.
  1831. //-----------------------------------------------------------------------------
  1832. void CEconItemHandle::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
  1833. {
  1834. if( pObject->GetTypeID() != CEconItem::k_nTypeID || m_pItem == NULL )
  1835. return;
  1836. const CEconItem *pItem = (CEconItem *)pObject;
  1837. if ( m_iItemID == pItem->GetID() )
  1838. {
  1839. UnsubscribeFromSOEvents();
  1840. m_pItem = NULL;
  1841. m_iItemID = INVALID_ITEM_ID;
  1842. }
  1843. }
  1844. void CEconItemHandle::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
  1845. {
  1846. if( pObject->GetTypeID() != CEconItem::k_nTypeID )
  1847. return;
  1848. CEconItem *pItem = (CEconItem *)pObject;
  1849. if ( m_iItemID == pItem->GetID() )
  1850. {
  1851. SetItem( pItem );
  1852. }
  1853. }
  1854. void CEconItemHandle::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
  1855. {
  1856. if ( pObject->GetTypeID() != CEconItem::k_nTypeID )
  1857. return;
  1858. CEconItem *pItem = (CEconItem *)pObject;
  1859. if ( m_iItemID == pItem->GetID() )
  1860. {
  1861. SetItem( pItem );
  1862. }
  1863. }
  1864. void CEconItemHandle::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
  1865. {
  1866. UnsubscribeFromSOEvents();
  1867. }
  1868. void CEconItemHandle::UnsubscribeFromSOEvents()
  1869. {
  1870. if ( m_OwnerSteamID.GetAccountID() != 0 )
  1871. {
  1872. GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerSteamID, this );
  1873. }
  1874. }
  1875. #if defined( STAGING_ONLY ) || defined( _DEBUG )
  1876. #if defined(CLIENT_DLL)
  1877. CON_COMMAND_F( item_dumpinv, "Dumps the contents of a specified client inventory.", FCVAR_CHEAT )
  1878. #else
  1879. CON_COMMAND_F( item_dumpinv_sv, "Dumps the contents of a specified server inventory.", FCVAR_CHEAT )
  1880. #endif
  1881. {
  1882. #if defined(CLIENT_DLL)
  1883. CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
  1884. #else
  1885. CSteamID steamID;
  1886. CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_GetCommandClient() );
  1887. pPlayer->GetSteamID( &steamID );
  1888. CPlayerInventory *pInventory = InventoryManager()->GetInventoryForAccount( steamID.GetAccountID() );
  1889. #endif
  1890. if ( !pInventory )
  1891. {
  1892. Msg("No inventory found.\n");
  1893. return;
  1894. }
  1895. pInventory->DumpInventoryToConsole( true );
  1896. }
  1897. #if defined (CLIENT_DLL)
  1898. 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 )
  1899. {
  1900. if ( args.ArgC() != 2 )
  1901. {
  1902. Msg("Usage: item_dumpschema <filename>\n");
  1903. return;
  1904. }
  1905. if ( GetItemSchema()->DumpItems(args[1]) )
  1906. Msg("Dump complete, saved in game/tf/%s\n", args[1]);
  1907. else
  1908. Msg("Dump failed (?)\n");
  1909. }
  1910. CON_COMMAND_F( item_giveitem, "Give an item to the local player. Format: item_giveitem <item definition name> or <item def index>", FCVAR_NONE )
  1911. {
  1912. if ( !steamapicontext || !steamapicontext->SteamUser() )
  1913. {
  1914. Msg("Not connected to Steam.\n");
  1915. return;
  1916. }
  1917. CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID();
  1918. if ( !steamIDForPlayer.IsValid() )
  1919. {
  1920. Msg("Failed to find a valid steamID for the local player.\n");
  1921. return;
  1922. }
  1923. int iItemCount = args.ArgC();
  1924. for ( int i = 1; i < iItemCount; ++i )
  1925. {
  1926. // Check to see if args[1] is a number (itemdefid) and if so, translate it to actual itemname
  1927. const char *pszItemname = NULL;
  1928. if ( V_isdigit( args[i][0] ) )
  1929. {
  1930. int iDef = V_atoi( args[i] );
  1931. CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( iDef );
  1932. if ( pItemDef )
  1933. {
  1934. pszItemname = pItemDef->GetItemDefinitionName();
  1935. }
  1936. }
  1937. else
  1938. {
  1939. pszItemname = args[i];
  1940. }
  1941. Msg("Sending request to generate '%s' for Local Player (%llu)\n", pszItemname, steamIDForPlayer.ConvertToUint64() );
  1942. CItemSelectionCriteria criteria;
  1943. GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest );
  1944. msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() );
  1945. criteria.SetIgnoreEnabledFlag( true );
  1946. if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemname, true ) ||
  1947. !criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) )
  1948. {
  1949. Msg("Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" );
  1950. return;
  1951. }
  1952. GCClientSystem()->BSendMessage( msg );
  1953. }
  1954. }
  1955. CON_COMMAND_F( item_rolllootlist, "Force a loot list rool for the local player. Format: item_rolllootlist <loot list definition name>", FCVAR_NONE )
  1956. {
  1957. if ( !steamapicontext || !steamapicontext->SteamUser() )
  1958. {
  1959. Msg("Not connected to Steam.\n");
  1960. return;
  1961. }
  1962. CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID();
  1963. if ( !steamIDForPlayer.IsValid() )
  1964. {
  1965. Msg("Failed to find a valid steamID for the local player.\n");
  1966. return;
  1967. }
  1968. Msg("Sending request to roll '%s' for Local Player (%llu)\n", args[1], steamIDForPlayer.ConvertToUint64() );
  1969. GCSDK::CProtoBufMsg<CMsgDevDebugRollLootRequest> msg( k_EMsgGCDev_DebugRollLootRequest );
  1970. msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() );
  1971. msg.Body().set_loot_list_name( args[1] );
  1972. GCClientSystem()->BSendMessage( msg );
  1973. }
  1974. #include "econ_item_description.h"
  1975. #include "localization_provider.h"
  1976. CON_COMMAND_F( item_generate_all_descriptions, "Generate full item descriptions for every item in your backpack. Meant as a code test.", FCVAR_CHEAT )
  1977. {
  1978. CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
  1979. for ( int i = 0; i < pInventory->GetItemCount(); i++ )
  1980. {
  1981. CEconItemDescription desc;
  1982. IEconItemDescription::YieldingFillOutEconItemDescription( &desc, GLocalizationProvider(), pInventory->GetItem( i ) );
  1983. }
  1984. Msg("Done.\n");
  1985. }
  1986. #endif // CLIENT_DLL
  1987. #endif // STAGING_ONLY || _DEBUG