summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_item_inventory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_item_inventory.cpp')
-rw-r--r--game/shared/tf/tf_item_inventory.cpp1819
1 files changed, 1819 insertions, 0 deletions
diff --git a/game/shared/tf/tf_item_inventory.cpp b/game/shared/tf/tf_item_inventory.cpp
new file mode 100644
index 0000000..57c674a
--- /dev/null
+++ b/game/shared/tf/tf_item_inventory.cpp
@@ -0,0 +1,1819 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "tf_item_inventory.h"
+#include "econ_entity_creation.h"
+#include "tf_item_system.h"
+#include "vgui/ILocalize.h"
+#include "tier3/tier3.h"
+#ifdef CLIENT_DLL
+#include "c_tf_player.h"
+#include "item_pickup_panel.h"
+#include "KeyValues.h"
+#include "filesystem.h"
+#include "character_info_panel.h"
+#include "ienginevgui.h"
+#include "c_tf_gamestats.h"
+#include "econ_notifications.h"
+#include "achievementmgr.h"
+#include "baseachievement.h"
+#include "achievements_tf.h"
+#include "econ/econ_item_preset.h"
+#include "tf_shared_content_manager.h"
+#include "c_playerresource.h"
+#include "quest_log_panel.h"
+#include "backpack_panel.h"
+#include "materialsystem/itexture.h"
+#else
+#include "tf_player.h"
+#endif
+#include "gc_clientsystem.h"
+#include "econ_game_account_client.h"
+#include "gcsdk/gcclientsdk.h"
+#include "econ_gcmessages.h"
+#include "tf_gamerules.h"
+#include "tf_gcmessages.h"
+#include "econ_item.h"
+#include "game_item_schema.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+using namespace GCSDK;
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+CEconNotification_HasNewItems::CEconNotification_HasNewItems() : CEconNotification()
+{
+ m_bHasTriggered = false;
+ m_flExpireTime = -1.0f; // Does not initially expire
+
+ m_bShowInGame = false;
+
+ // Check to see if any items are not drops, if so show in game
+ // do not show in game if the new items are only drops
+ CPlayerInventory *pLocalInv = InventoryManager()->GetLocalInventory();
+ if ( pLocalInv )
+ {
+ // 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 ( InventoryManager()->GetBackpackPositionFromBackend(iPosition) != 0 )
+ continue;
+
+ // Now make sure we haven't got a clientside saved ack for this item.
+ if ( InventoryManager()->HasBeenAckedByClient( pTmp ) )
+ continue;
+
+ // If item is not a drop we want to show the notification otherwise they'll get the notification on death
+ int iFoundMethod = GetUnacknowledgedReason( iPosition );
+ if ( iFoundMethod > UNACK_ITEM_DROPPED )
+ {
+ m_bShowInGame = true;
+ break;
+ }
+ }
+ }
+}
+
+
+CEconNotification_HasNewItems::~CEconNotification_HasNewItems()
+{
+ if ( !m_bHasTriggered )
+ {
+ m_bHasTriggered = true;
+ TFInventoryManager()->ShowItemsPickedUp( true, true, true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+CEconNotification_HasNewItemsOnKill::CEconNotification_HasNewItemsOnKill( int iVictimID )
+{
+ m_bHasTriggered = false;
+ SetLifetime( 3.0f );
+ SetText( "#TF_EnemyDroppedItem" );
+
+ wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
+ g_pVGuiLocalize->ConvertANSIToUnicode( g_PR->GetPlayerName( iVictimID ), wszPlayerName, sizeof( wszPlayerName ) );
+ AddStringToken( "victim", wszPlayerName );
+
+ m_bShowInGame = true;
+}
+
+/*static*/ bool CEconNotification_HasNewItemsOnKill::HasUnacknowledgedItems ()
+{
+ // Check to see if any items are not drops, if so show in game
+ // do not show in game if the new items are only drops
+ CPlayerInventory *pLocalInv = InventoryManager()->GetLocalInventory();
+ if ( pLocalInv )
+ {
+ // 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 ( InventoryManager()->GetBackpackPositionFromBackend( iPosition ) != 0 )
+ continue;
+
+ // Now make sure we haven't got a clientside saved ack for this item.
+ if ( InventoryManager()->HasBeenAckedByClient( pTmp ) )
+ continue;
+
+ // If item is not a drop we want to show the notification otherwise they'll get the notification on death
+ int iFoundMethod = GetUnacknowledgedReason( iPosition );
+ if ( iFoundMethod > UNACK_ITEM_DROPPED )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool AreSlotsConsideredIdentical( EEquipType_t eEquipType, int iBaseSlot, int iTestSlot )
+{
+ if ( eEquipType == EQUIP_TYPE_CLASS )
+ {
+ if ( iBaseSlot == LOADOUT_POSITION_MISC )
+ {
+ return IsMiscSlot( iTestSlot );
+ }
+
+ if ( iBaseSlot == LOADOUT_POSITION_BUILDING )
+ {
+ return IsBuildingSlot( iTestSlot );
+ }
+
+ if ( iBaseSlot == LOADOUT_POSITION_TAUNT )
+ {
+ return IsTauntSlot( iTestSlot );
+ }
+ }
+ else if ( eEquipType == EQUIP_TYPE_ACCOUNT )
+ {
+ if ( iBaseSlot == ACCOUNT_LOADOUT_POSITION_ACCOUNT1 )
+ {
+ return IsQuestSlot( iTestSlot );
+ }
+ }
+
+ return iBaseSlot == iTestSlot;
+}
+
+//-----------------------------------------------------------------------------
+CTFInventoryManager g_TFInventoryManager;
+CInventoryManager *InventoryManager( void )
+{
+ return &g_TFInventoryManager;
+}
+CTFInventoryManager *TFInventoryManager( void )
+{
+ return &g_TFInventoryManager;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFInventoryManager::CTFInventoryManager( void )
+
+{
+}
+
+CTFInventoryManager::~CTFInventoryManager( void )
+{
+ m_pBaseLoadoutItems.PurgeAndDeleteElements();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFInventoryManager::PostInit( void )
+{
+ BaseClass::PostInit();
+ GenerateBaseItems();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Generate & store the base item details for each class & loadout slot
+//-----------------------------------------------------------------------------
+void CTFInventoryManager::GenerateBaseItems( void )
+{
+ // Purge our lists and make new
+ m_pBaseLoadoutItems.PurgeAndDeleteElements();
+
+ // Load a base top level invalid item
+ {
+ m_pDefaultItem = new CEconItemView;
+ m_pDefaultItem->Invalidate();
+ }
+ //
+ const CEconItemSchema::BaseItemDefinitionMap_t& mapItems = GetItemSchema()->GetBaseItemDefinitionMap();
+ int iStart = 0;
+ for ( int it = iStart; it != mapItems.InvalidIndex(); it = mapItems.NextInorder( it ) )
+ {
+ CEconItemView *pItem = new CEconItemView;
+ pItem->Init( mapItems[it]->GetDefinitionIndex(), AE_USE_SCRIPT_VALUE, AE_USE_SCRIPT_VALUE, false );
+ m_pBaseLoadoutItems.AddToTail( pItem );
+ }
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFInventoryManager::EquipItemInLoadout( int iClass, int iSlot, itemid_t iItemID )
+{
+ if ( !steamapicontext || !steamapicontext->SteamUser() )
+ return false;
+
+ // If they pass in a INVALID_ITEM_ID item id, we're just clearing the loadout slot
+ if ( iItemID == INVALID_ITEM_ID )
+ return m_LocalInventory.ClearLoadoutSlot( iClass, iSlot );
+
+ CEconItemView *pItem = m_LocalInventory.GetInventoryItemByItemID( iItemID );
+ if ( !pItem )
+ return false;
+
+ // We check for validity on the GC when we equip items, but we can't really trust anyone
+ // and so we check here as well.
+ if ( !AreSlotsConsideredIdentical( pItem->GetStaticData()->GetEquipType(), pItem->GetStaticData()->GetLoadoutSlot(iClass), iSlot ) )
+ {
+ return false;
+ }
+
+ if ( !pItem->GetStaticData()->CanBeUsedByClass( iClass ) )
+ {
+ return false;
+ }
+
+ // Equip the new item
+ UpdateInventoryEquippedState( &m_LocalInventory, iItemID, iClass, iSlot );
+
+ // TODO: Prediction
+ // Item has been moved, so update our loadout.
+ //m_LoadoutItems[iClass][iSlot] = iItemID;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fills out pList with all inventory items that could fit into the specified loadout slot for a given class
+//-----------------------------------------------------------------------------
+int CTFInventoryManager::GetAllUsableItemsForSlot( int iClass, int iSlot, CUtlVector<CEconItemView*> *pList )
+{
+ bool bIsAccountIndex = iClass == GEconItemSchema().GetAccountIndex();
+ if ( bIsAccountIndex )
+ {
+ Assert( IsQuestSlot( iSlot ) );
+ }
+ else
+ {
+ Assert( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_CLASS_COUNT );
+ Assert( iSlot >= -1 && iSlot < CLASS_LOADOUT_POSITION_COUNT );
+ }
+
+ int iCount = m_LocalInventory.GetItemCount();
+ for ( int i = 0; i < iCount; i++ )
+ {
+ CEconItemView *pItem = m_LocalInventory.GetItem(i);
+ CTFItemDefinition *pItemData = pItem->GetStaticData();
+
+ if ( bIsAccountIndex != ( pItemData->GetEquipType() == EEquipType_t::EQUIP_TYPE_ACCOUNT ) )
+ continue;
+
+ if ( !bIsAccountIndex && !pItemData->CanBeUsedByClass(iClass) )
+ continue;
+
+ // Passing in iSlot of -1 finds all items usable by the class
+ if ( iSlot >= 0 && pItem->GetStaticData()->GetLoadoutSlot( iClass ) != iSlot )
+ continue;
+
+ // Ignore unpack'd items
+ if ( IsUnacknowledged( pItem->GetInventoryPosition() ) )
+ continue;
+
+ pList->AddToTail( m_LocalInventory.GetItem(i) );
+ }
+
+ return pList->Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fills out pList with all quest item in the local inventory
+//-----------------------------------------------------------------------------
+int CTFInventoryManager::GetAllQuestItems( CUtlVector<CEconItemView*> *pList )
+{
+ if ( !pList )
+ return 0;
+
+ pList->RemoveAll();
+
+ for ( int i = 0 ; i < m_LocalInventory.GetItemCount() ; ++i )
+ {
+ CEconItemView *pItem = m_LocalInventory.GetItem( i );
+
+ // Cheapest test
+ if ( pItem->GetItemDefinition()->GetQuestDef() == NULL )
+ continue;
+
+ pList->AddToTail( pItem );
+ }
+
+ return pList->Count();
+}
+
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconItemView *CTFInventoryManager::GetItemInLoadoutForClass( int iClass, int iSlot, CSteamID *pID )
+{
+#ifdef CLIENT_DLL
+ if ( !pID )
+ {
+ // If they didn't specify a steamID, use the local player
+ if ( !steamapicontext || !steamapicontext->SteamUser() )
+ return NULL;
+
+ CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
+ pID = &localSteamID;
+ }
+#endif
+
+ CTFPlayerInventory *pInv = GetInventoryForPlayer( *pID );
+ if ( !pInv )
+ return GetBaseItemForClass( iClass, iSlot );
+
+ return pInv->GetItemInLoadout( iClass, iSlot );
+}
+
+CEconItemView *CTFInventoryManager::GetItemInLoadoutForAccount( int iSlot, CSteamID *pID )
+{
+#ifdef CLIENT_DLL
+ if ( !pID )
+ {
+ // If they didn't specify a steamID, use the local player
+ if ( !steamapicontext || !steamapicontext->SteamUser() )
+ return NULL;
+
+ CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
+ pID = &localSteamID;
+ }
+#endif
+
+ CTFPlayerInventory *pInv = GetInventoryForPlayer( *pID );
+ if ( !pInv )
+ return NULL;
+
+ return pInv->GetItemInLoadout( GEconItemSchema().GetAccountIndex(), iSlot );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerInventory *CTFInventoryManager::GetInventoryForPlayer( const CSteamID &playerID )
+{
+ for ( int i = 0; i < m_pInventories.Count(); i++ )
+ {
+ if ( m_pInventories[i].pInventory->GetOwner() != playerID )
+ continue;
+
+ return assert_cast<CTFPlayerInventory*>( m_pInventories[i].pInventory );
+ }
+
+ return NULL;
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFInventoryManager::GetNumItemPickedUpItems( void )
+{
+ int iResult = 0;
+ int iCount = m_LocalInventory.GetItemCount();
+ for ( int i = 0; i < iCount; i++ )
+ {
+ if ( IsUnacknowledged( m_LocalInventory.GetItem(i)->GetInventoryPosition() ) && !m_LocalInventory.GetItem(i)->GetStaticData()->IsHidden() )
+ {
+ ++iResult;
+ }
+ }
+ return iResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFInventoryManager::ShowItemsPickedUp( bool bForce, bool bReturnToGame, bool bNoPanel )
+{
+ // don't show new items in training, unless forced to do so
+ // i.e. purchased something or traded...
+ if ( bForce == false && TFGameRules() && ( TFGameRules()->IsInTraining() || TFGameRules()->IsCompetitiveMode() ) )
+ {
+ 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 = m_LocalInventory.GetItemCount();
+ for ( int i = 0; i < iCount; i++ )
+ {
+ CEconItemView *pTmp = m_LocalInventory.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.
+ NotificationQueue_Remove( &CEconNotification_HasNewItems::IsNotificationType );
+ CItemPickupPanel *pItemPanel = bNoPanel ? NULL : EconUI()->OpenItemPickupPanel();
+
+ if ( pItemPanel )
+ {
+ pItemPanel->SetReturnToGame( bReturnToGame );
+ }
+
+ // Only acknowledge items if there is no panel
+ // Panel will make calls to acknowledge items itself
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+void CTFInventoryManager::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] );
+
+ if ( iMethod+1 == UNACK_ITEM_PREVIEW_ITEM_PURCHASED )
+ {
+ // If we found a purchased preview item, we want to refresh the store view to remove the discount indicator.
+ CStorePage* pStorePage = dynamic_cast<CStorePage*>( EconUI()->GetStorePanel()->GetActivePage() );
+ if ( pStorePage )
+ {
+ pStorePage->UpdateModelPanels();
+ }
+ }
+
+ // Move it to the first empty backpack position.
+ if ( bMoveToBackpack )
+ {
+ SetItemBackpackPosition( pItem, 0, false, true );
+ }
+}
+
+void CTFInventoryManager::Update( float frametime )
+{
+ TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 );
+ m_LocalInventory.UpdateWeaponSkinRequest();
+
+ BaseClass::Update( frametime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFInventoryManager::ShowItemsCrafted( CUtlVector<itemid_t> *vecCraftedIndices )
+{
+ CUtlVector<CEconItemView*> aItemsFound;
+ FOR_EACH_VEC( *vecCraftedIndices, i )
+ {
+ CEconItemView *pItem = m_LocalInventory.GetInventoryItemByItemID( vecCraftedIndices->Element(i) );
+ if ( pItem )
+ {
+ // 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( pItem ) )
+ {
+ aItemsFound.AddToTail( pItem );
+ }
+ }
+ }
+
+ if ( !aItemsFound.Count() )
+ return;
+
+ NotificationQueue_Remove( &CEconNotification_HasNewItems::IsNotificationType );
+ CItemPickupPanel *pItemPanel = EconUI()->OpenItemPickupPanel();
+ for ( int i = 0; i < aItemsFound.Count(); i++ )
+ {
+ pItemPanel->AddItem( aItemsFound[i] );
+
+ SetAckedByClient( aItemsFound[i] );
+
+#ifdef CLIENT_DLL
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_RECEIVED, aItemsFound[i], "crafted" );
+#endif
+
+ // Then move it to the first empty backpack position
+ SetItemBackpackPosition( aItemsFound[i], 0, false, true );
+ }
+ SaveAckFile();
+ pItemPanel->MoveToFront();
+
+ aItemsFound.Purge();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFInventoryManager::CheckForRoomAndForceDiscard( void )
+{
+ // 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 = m_LocalInventory.GetMaxItemCount();
+ int iCount = m_LocalInventory.GetItemCount();
+ for ( int i = 0; i < iCount; i++ )
+ {
+ CEconItemView *pTmp = m_LocalInventory.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;
+}
+#endif
+
+bool CTFInventoryManager::SlotContainsBaseItems( EEquipType_t eType, int iSlot )
+{
+ Assert( eType != EEquipType_t::EQUIP_TYPE_INVALID );
+
+ if ( eType == EEquipType_t::EQUIP_TYPE_ACCOUNT )
+ {
+ return !IsQuestSlot( iSlot );
+ }
+
+ // Passtime gun
+ if ( (iSlot == LOADOUT_POSITION_UTILITY) && TFGameRules() && TFGameRules()->IsPasstimeMode() )
+ {
+ return true;
+ }
+
+ // Halloween spellbook
+ if ( iSlot == LOADOUT_POSITION_ACTION )
+ {
+ if ( TFGameRules() && TFGameRules()->IsUsingSpells() )
+ return true;
+
+ if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() )
+ return true;
+ }
+#ifdef STAGING_ONLY
+ return ( ( iSlot < LOADOUT_POSITION_HEAD && iSlot != LOADOUT_POSITION_UTILITY && !IsTauntSlot( iSlot ) ) // Allow utility slots to be empty
+ || iSlot == LOADOUT_POSITION_PDA3 );
+#else // STAGING_ONLY
+ // Normal game
+ return iSlot < LOADOUT_POSITION_HEAD;
+#endif // #else
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the item data for the base item in the loadout slot for a given class
+//-----------------------------------------------------------------------------
+CEconItemView *CTFInventoryManager::GetBaseItemForClass( int iClass, int iSlot )
+{
+ // There is no base account item
+ if ( iClass == GEconItemSchema().GetAccountIndex() )
+ return m_pDefaultItem;
+
+ if ( !HushAsserts() )
+ {
+ AssertMsg( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_CLASS_COUNT, "Invalid TF_CLASS_: %d", iClass );
+ }
+ Assert( iSlot >= 0 && iSlot < CLASS_LOADOUT_POSITION_COUNT );
+
+ if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_CLASS_COUNT || iSlot < 0 || iSlot >= CLASS_LOADOUT_POSITION_COUNT )
+ return m_pDefaultItem;
+
+ // Halloween spellbook
+ if ( iSlot == LOADOUT_POSITION_ACTION )
+ {
+ CUtlVector< item_definition_index_t > stockActionItemDefIndices;
+
+ static CSchemaItemDefHandle pItemDef_SpellBook( "TF_WEAPON_SPELLBOOK" );
+ if ( TFGameRules() && TFGameRules()->IsUsingSpells() && pItemDef_SpellBook )
+ {
+ stockActionItemDefIndices.AddToTail( pItemDef_SpellBook->GetDefinitionIndex() );
+ }
+
+ static CSchemaItemDefHandle pItemDef_GrapplingHook( "TF_WEAPON_GRAPPLINGHOOK" );
+ if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() && pItemDef_GrapplingHook )
+ {
+ stockActionItemDefIndices.AddToTail( pItemDef_GrapplingHook->GetDefinitionIndex() );
+ }
+
+ static CSchemaItemDefHandle pItemDef_MvMCanteen( "Default Power Up Canteen (MvM)" );
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pItemDef_MvMCanteen )
+ {
+ stockActionItemDefIndices.AddToTail( pItemDef_MvMCanteen->GetDefinitionIndex() );
+ }
+
+ // Traverse List
+ for ( CEconItemView *pActionItem : m_pBaseLoadoutItems )
+ {
+ for ( item_definition_index_t defIndex : stockActionItemDefIndices )
+ {
+ if ( pActionItem->GetItemDefIndex() == defIndex )
+ return pActionItem;
+ }
+ }
+ }
+
+ if ( iSlot >= LOADOUT_POSITION_HEAD )
+ return m_pDefaultItem;
+
+ // Traverse List
+ FOR_EACH_VEC( m_pBaseLoadoutItems, iItem )
+ {
+ if ( m_pBaseLoadoutItems[iItem]->GetItemDefinition()->GetLoadoutSlot( iClass ) == iSlot )
+ return m_pBaseLoadoutItems[iItem];
+ }
+
+ return m_pDefaultItem;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fills out the vector with the sets that are currently active on the specified player & class
+//-----------------------------------------------------------------------------
+void CTFInventoryManager::GetActiveSets( CUtlVector<const CEconItemSetDefinition *> *pItemSets, CSteamID steamIDForPlayer, int iClass )
+{
+ pItemSets->Purge();
+
+ CEconItemSchema *pSchema = ItemSystem()->GetItemSchema();
+ if ( !pSchema )
+ return;
+
+ // Loop through all the items we have equipped, and build a list of only set items
+ // Accumulate a list of our equipped set items.
+ CUtlVector<CEconItemView*> equippedSetItems;
+ for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
+ {
+ CEconItemView *pItem = NULL;
+
+#ifdef GAME_DLL
+ // On the server we need to look at what the player actually has equipped
+ // because they might be on a tournament server that's using an item_whitelist
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerBySteamID( steamIDForPlayer ) );
+ if ( pPlayer && pPlayer->Inventory() )
+ {
+ pItem = pPlayer->GetEquippedItemForLoadoutSlot( i );
+ }
+#else
+ pItem = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i, &steamIDForPlayer );
+#endif
+
+ if ( !pItem )
+ continue;
+
+ CEconItemDefinition* pData = pItem->GetStaticData();
+ if ( !pData )
+ continue;
+
+ // Ignore items that don't have set bonuses.
+ if ( !pData->GetItemSetDefinition() )
+ continue;
+
+ // Make sure this item isn't failing a Holiday restriction before giving out the set bonus!
+ if ( pData->GetHolidayRestriction() )
+ {
+ int iHolidayRestriction = UTIL_GetHolidayForString( pData->GetHolidayRestriction() );
+ if ( iHolidayRestriction != kHoliday_None && (!TFGameRules() || !TFGameRules()->IsHolidayActive( iHolidayRestriction )) )
+ continue;
+ }
+
+ equippedSetItems.AddToTail( pItem );
+ }
+
+ // Find out which sets to apply.
+ CUtlVector<const char*> testedSets;
+ for ( int inv = 0; inv < equippedSetItems.Count(); inv++ )
+ {
+ CEconItemView *pItem = equippedSetItems[inv];
+ if ( !pItem )
+ continue;
+
+ const CEconItemSetDefinition *pItemSet = pItem->GetStaticData()->GetItemSetDefinition();
+ if ( !pItemSet )
+ continue;
+
+ if ( testedSets.HasElement( pItemSet->m_pszName ) )
+ continue; // Don't try to apply set bonuses we have already tested.
+
+ testedSets.AddToTail( pItemSet->m_pszName );
+
+ // Count how much of this set we have equipped.
+ int iSetItemsEquipped = 0;
+ for ( int i=0; i<pItemSet->m_iItemDefs.Count(); i++ )
+ {
+ unsigned int iIndex = pItemSet->m_iItemDefs[i];
+
+ for ( int j=0; j<equippedSetItems.Count(); j++ )
+ {
+ const CEconItemView *pTestItem = equippedSetItems[j];
+ const item_definition_index_t unEquippedItemSetDefIndex = pTestItem->GetItemDefinition()->GetSetItemRemap();
+
+ if ( iIndex == unEquippedItemSetDefIndex )
+ {
+ iSetItemsEquipped++;
+ break;
+ }
+ }
+ }
+
+ // Note: this logic will break if we ever have sets that have misc-slot items where we can have multiple items
+ // of the same type with different remaps equipped.
+ if ( iSetItemsEquipped == pItemSet->m_iItemDefs.Count() )
+ {
+ // The entire set is equipped.
+ pItemSets->AddToTail( pItemSet );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We're generating a base item. We need to add the game-specific keys to the criteria so that it'll find the right base item.
+//-----------------------------------------------------------------------------
+void CTFInventoryManager::AddBaseItemCriteria( baseitemcriteria_t *pCriteria, CItemSelectionCriteria *pSelectionCriteria )
+{
+ pSelectionCriteria->BAddCondition( "used_by_classes", k_EOperator_Subkey_Contains, ItemSystem()->GetItemSchema()->GetClassUsabilityStrings()[pCriteria->iClass], true );
+ pSelectionCriteria->BAddCondition( "item_slot", k_EOperator_String_EQ, ItemSystem()->GetItemSchema()->GetLoadoutStrings( EEquipType_t::EQUIP_TYPE_CLASS )[pCriteria->iSlot], true );
+}
+
+
+//=======================================================================================================================
+// TF PLAYER INVENTORY
+//=======================================================================================================================
+// Inventory Less function.
+// Used to sort the inventory items into their positions.
+class CTFInventoryListLess
+{
+public:
+ bool Less( const CEconItemView &src1, const CEconItemView &src2, void *pCtx )
+ {
+ if ( src1.GetInventoryPosition() > src2.GetInventoryPosition() )
+ return true;
+
+ return false;
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerInventory::CTFPlayerInventory()
+{
+ m_aInventoryItems.SetLessContext( this );
+#ifdef CLIENT_DLL
+ for ( int i = 0; i < TF_TEAM_COUNT; ++i )
+ m_CachedBaseTextureLowRes[ i ].SetLessFunc( DefLessFunc( itemid_t ) );
+#endif
+
+ memset( m_LoadoutItems, LOADOUT_SLOT_USE_BASE_ITEM, sizeof( m_LoadoutItems ) );
+ memset( m_AccountLoadoutItems, LOADOUT_SLOT_USE_BASE_ITEM, sizeof( m_AccountLoadoutItems ) );
+ ClearClassLoadoutChangeTracking();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerInventory::~CTFPlayerInventory()
+{
+#ifdef CLIENT_DLL
+ for ( int iTeam = 0; iTeam < TF_TEAM_COUNT; ++iTeam )
+ {
+ FOR_EACH_MAP_FAST( m_CachedBaseTextureLowRes[ iTeam ], i )
+ m_CachedBaseTextureLowRes[ iTeam ][ i ]->Release();
+ m_CachedBaseTextureLowRes[ iTeam ].RemoveAll();
+ }
+#endif
+}
+
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::CheckSaxtonMaskAchievement( const CEconItem *pEconItem )
+{
+ if ( pEconItem )
+ {
+ if ( pEconItem->GetDefinitionIndex() == 277 && pEconItem->GetOrigin() == kEconItemOrigin_Crafted ) // Saxton mask is item index 277
+ {
+ g_AchievementMgrTF.OnAchievementEvent( ACHIEVEMENT_TF_HALLOWEEN_CRAFT_SAXTON_MASK );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::UpdateCachedServerLoadoutItems()
+{
+ V_memcpy( m_CachedServerLoadoutItems, m_LoadoutItems, sizeof( itemid_t ) * ARRAYSIZE( m_CachedServerLoadoutItems ) * ARRAYSIZE( m_CachedServerLoadoutItems[0] ) );
+}
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
+{
+ BaseClass::SOUpdated( steamIDOwner, pObject, eEvent );
+
+#ifdef CLIENT_DLL
+ if ( pObject->GetTypeID() != CEconItem::k_nTypeID )
+ return;
+
+ // Clear out any predicted backpack slots when items move into them
+ CEconItem *pEconItem = (CEconItem *)pObject;
+ if ( eEvent == eSOCacheEvent_Incremental )
+ {
+ EconUI()->GetBackpackPanel()->MarkItemIDDirty( pEconItem->GetItemID() );
+ }
+ int iBackpackPos = TFInventoryManager()->GetBackpackPositionFromBackend( pEconItem->GetInventoryToken() );
+ TFInventoryManager()->PredictedBackpackPosFilled( iBackpackPos );
+#endif // CLIENT_DLL
+}
+
+void CTFPlayerInventory::OnHasNewItems()
+{
+ BaseClass::OnHasNewItems();
+#ifdef CLIENT_DLL
+ if ( TFGameRules() && TFGameRules()->IsInTraining() )
+ return;
+
+ NotificationQueue_Remove( &CEconNotification_HasNewItems::IsNotificationType );
+ CEconNotification_HasNewItems *pNotification = new CEconNotification_HasNewItems();
+ pNotification->SetText( "TF_HasNewItems" );
+ pNotification->SetLifetime( 7.0f );
+ NotificationQueue_Add( pNotification );
+#endif
+}
+
+void CTFPlayerInventory::OnHasNewQuest()
+{
+#ifdef CLIENT_DLL
+
+#endif
+}
+
+#ifdef _DEBUG
+#ifdef CLIENT_DLL
+CON_COMMAND( cl_newitem_test, "Tests the new item ui notification." )
+{
+ if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
+ return;
+
+ CEconNotification_HasNewItems *pNotification = new CEconNotification_HasNewItems();
+ pNotification->SetText( "TF_HasNewItems" );
+ pNotification->SetLifetime( 7.0f );
+ NotificationQueue_Add( pNotification );
+}
+#endif
+#endif // _DEBUG
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::ValidateInventoryPositions( void )
+{
+ BaseClass::ValidateInventoryPositions();
+
+#ifdef CLIENT_DLL
+ bool bHasNewItems = false;
+ const int iMaxItems = GetMaxItemCount();
+ // First, check for duplicate positions
+ int iCount = m_aInventoryItems.Count();
+ for ( int i = iCount-1; i >= 0; i-- )
+ {
+ CEconItemView *pEconItemView = &m_aInventoryItems[i];
+
+ CheckSaxtonMaskAchievement( pEconItemView->GetSOCData() );
+
+ uint32 iPosition = pEconItemView->GetInventoryPosition();
+
+ // Waiting to be acknowledged?
+ if ( IsUnacknowledged(iPosition) )
+ {
+ if ( !pEconItemView->GetStaticData()->IsHidden() )
+ {
+ bHasNewItems = true;
+ }
+ continue;
+ }
+
+ bool bInvalidSlot = false;
+ if ( !IsNewPositionFormat(iPosition) )
+ {
+ ConvertOldFormatInventoryToNew();
+ break;
+ }
+
+ // Inside the backpack?
+ if ( i < (iCount-1) )
+ {
+ // We're not in an invalid slot yet. But if we're in the same position as another item, we should be moved too.
+ int iPos1 = TFInventoryManager()->GetBackpackPositionFromBackend(iPosition);
+ int iPos2 = TFInventoryManager()->GetBackpackPositionFromBackend(m_aInventoryItems[i+1].GetInventoryPosition());
+ if ( iPos1 == iPos2 )
+ {
+ Warning("WARNING: Found item in a duplicate backpack position. Moving to the backpack end.\n" );
+ bInvalidSlot = true;
+ }
+ }
+
+ // Make sure it's not outside the backpack extents
+ if ( !bInvalidSlot )
+ {
+ bInvalidSlot = (TFInventoryManager()->GetBackpackPositionFromBackend(iPosition) > iMaxItems);
+ }
+
+ if ( bInvalidSlot )
+ {
+ // The item is NOT hidden and is in an invalid slot. Move it back to the backpack.
+ if ( pEconItemView->GetItemDefinition() && !pEconItemView->GetItemDefinition()->IsHidden() && !TFInventoryManager()->SetItemBackpackPosition( pEconItemView, 0, true ) )
+ {
+ // We failed to move it to the backpack, because the player has no room.
+ // Force them to "refind" the item, which will make them throw something out.
+ TFInventoryManager()->UpdateInventoryPosition( this, pEconItemView->GetItemID(), GetUnacknowledgedPositionFor(UNACK_ITEM_DROPPED) );
+ }
+ }
+
+ // Make sure it isn't equipped by any invalid classes.
+ for ( int j = TF_FIRST_NORMAL_CLASS; j <= TF_LAST_NORMAL_CLASS; j++ )
+ {
+ if ( !pEconItemView->GetStaticData()->CanBeUsedByClass( j ) &&
+ pEconItemView->IsEquippedForClass( j ) )
+ {
+ // Unequip this item from this class.
+ InventoryManager()->UpdateInventoryEquippedState( this, INVALID_ITEM_ID, j, pEconItemView->GetEquippedPositionForClass( j ) );
+ }
+ }
+ }
+
+ if ( bHasNewItems )
+ {
+ OnHasNewItems();
+ }
+#endif
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::ConvertOldFormatInventoryToNew( void )
+{
+ uint32 iBackpackPos = 1;
+
+ // Loop through all items in the inventory. Move them all to the backpack, and in order.
+ int iCount = m_aInventoryItems.Count();
+ for ( int i = 0; i < iCount; i++ )
+ {
+ uint32 iPosition = m_aInventoryItems[i].GetInventoryPosition();
+
+ // Waiting to be acknowledged?
+ if ( IsUnacknowledged(iPosition) )
+ continue;
+
+ TFInventoryManager()->SetItemBackpackPosition( &m_aInventoryItems[i], iBackpackPos, true );
+ iBackpackPos++;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a script item and associates it with this econ item
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
+{
+ BaseClass::SOCreated( steamIDOwner, pObject, eEvent );
+
+ if ( pObject->GetTypeID() != CEconItem::k_nTypeID )
+ return;
+
+ CEconItem *pEconItem = (CEconItem *)pObject;
+ // CEconItem *pEconItem = assert_cast<CEconItem*>( pObject );
+
+#ifdef CLIENT_DLL
+ if ( InventoryManager()->GetLocalInventory() == this && GetOwner() == steamIDOwner )
+ {
+ if ( pObject->GetTypeID() == CEconItem::k_nTypeID )
+ {
+ CheckSaxtonMaskAchievement( (CEconItem*)pObject );
+ }
+ }
+
+// CSteamID ownerSteamID( pEconItem->GetAccountID(), GetUniverse(), k_EAccountTypeIndividual );
+// if ( ownerSteamID == ClientSteamContext().GetLocalPlayerSteamID() )
+// {
+// CheckSaxtonMaskAchievement( pEconItem );
+// }
+
+ static CSchemaItemDefHandle pItemDef_HardyLaurel( "The Hardy Laurel" );
+ if ( pEconItem->GetItemDefinition() == pItemDef_HardyLaurel )
+ {
+ if ( TFSharedContentManager() )
+ {
+ TFSharedContentManager()->OfferSharedVision( TF_VISION_FILTER_ROME, pEconItem->GetAccountID() );
+ }
+ }
+ #else
+ // Summer 2015 Operation Pass so players can display the coin
+ //tagES remove this when we have a coin equip slot
+ static CSchemaItemDefHandle pItemDef_Summer2015Operation( "Activated Summer 2015 Operation Pass" );
+ if ( pEconItem->GetItemDefinition() == pItemDef_Summer2015Operation )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerBySteamID( GetOwner() ) );
+ if ( pPlayer )
+ {
+ pPlayer->SetCampaignMedalActive( CAMPAIGN_MEDAL_SUMMER2015 );
+ }
+ }
+
+ // Invasion Community Update Pass so players can display the coin
+ //tagES remove this when we have a coin equip slot
+ static CSchemaItemDefHandle pItemDef_InvasionPass( "Activated Invasion Pass" );
+ if ( pEconItem->GetItemDefinition() == pItemDef_InvasionPass )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerBySteamID( GetOwner() ) );
+ if ( pPlayer )
+ {
+ pPlayer->SetCampaignMedalActive( CAMPAIGN_MEDAL_INVASION );
+ }
+ }
+
+ // Halloween Pass so players can display the coin
+ //tagES remove this when we have a coin equip slot
+ static CSchemaItemDefHandle pItemDef_HalloweenPass( "Activated Halloween Pass" );
+ if ( pEconItem->GetItemDefinition() == pItemDef_HalloweenPass )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerBySteamID( GetOwner() ) );
+ if ( pPlayer )
+ {
+ pPlayer->SetCampaignMedalActive( CAMPAIGN_MEDAL_HALLOWEEN );
+ }
+ }
+
+ // Winter2016 Pass so players can display the stamp
+ //tagES remove this when we have a coin equip slot
+ static CSchemaItemDefHandle pItemDef_Winter2016Pass( "Activated Operation Tough Break Pass" );
+ if ( pEconItem->GetItemDefinition() == pItemDef_Winter2016Pass )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerBySteamID( GetOwner() ) );
+ if ( pPlayer )
+ {
+ pPlayer->SetCampaignMedalActive( CAMPAIGN_MEDAL_WINTER2016 );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::DumpInventoryToConsole( bool bRoot )
+{
+ if ( bRoot )
+ {
+ Msg("========================================\n");
+#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) at backpack slot %d\n", m_aInventoryItems[i].GetStaticData()->GetDefinitionName(), m_aInventoryItems[i].GetItemID(), TFInventoryManager()->GetBackpackPositionFromBackend( m_aInventoryItems[i].GetInventoryPosition() ) );
+
+ int iEquipped = 0;
+ for ( equipped_class_t eq = TF_FIRST_NORMAL_CLASS; eq < TF_LAST_NORMAL_CLASS; eq++ )
+ {
+ if ( m_aInventoryItems[i].IsEquippedForClass( eq ) )
+ {
+ if ( iEquipped == 0 )
+ {
+ iEquipped++;
+ Msg(" -> EQUIPPED: ");
+ }
+ Msg("%s ", g_aPlayerClassNames_NonLocalized[eq] );
+ }
+ }
+
+ if ( iEquipped )
+ {
+ Msg("\n");
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::ClearClassLoadoutChangeTracking( void )
+{
+ memset(m_bLoadoutChanged,0, sizeof(m_bLoadoutChanged));
+#ifdef CLIENT_DLL
+ UpdateCachedServerLoadoutItems();
+#endif // CLIENT_DLL
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Find the low res
+//-----------------------------------------------------------------------------
+ITexture *CTFPlayerInventory::GetWeaponSkinBaseLowRes( itemid_t nItemId, int iTeam ) const
+{
+ Assert( iTeam >= 0 && iTeam < TF_TEAM_COUNT );
+ int index = m_CachedBaseTextureLowRes[ iTeam ].Find( nItemId );
+ if ( index == m_CachedBaseTextureLowRes[ iTeam ].InvalidIndex() )
+ return NULL;
+
+ return m_CachedBaseTextureLowRes[ iTeam ][ index ];
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the first item in the local user's inventory with the given def index
+//-----------------------------------------------------------------------------
+CEconItemView *CTFPlayerInventory::GetFirstItemOfItemDef( item_definition_index_t nDefIndex, CPlayerInventory* pInventory )
+{
+#ifdef CLIENT_DLL
+ if ( pInventory == NULL )
+ {
+ pInventory = InventoryManager()->GetLocalInventory();
+ }
+#endif
+
+ if ( !pInventory )
+ return NULL;
+
+ for ( int i = 0; i < pInventory->GetItemCount(); i++ )
+ {
+ CEconItemView *pItem = pInventory->GetItem(i);
+ if ( pItem->GetItemDefIndex() == nDefIndex )
+ {
+ return pItem;
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the item in the specified loadout slot for a given class
+//-----------------------------------------------------------------------------
+CEconItemView *CTFPlayerInventory::GetItemInLoadout( int iClass, int iSlot )
+{
+ if ( iSlot < 0 || iSlot >= CLASS_LOADOUT_POSITION_COUNT )
+ return NULL;
+
+ if ( iClass == GEconItemSchema().GetAccountIndex() )
+ {
+ return GetInventoryItemByItemID( m_AccountLoadoutItems[ iSlot ] );
+ }
+ else
+ {
+ if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS )
+ return NULL;
+
+ // If we don't have an item in the loadout at that slot, we return the base item
+ if ( m_LoadoutItems[iClass][iSlot] != LOADOUT_SLOT_USE_BASE_ITEM )
+ {
+ CEconItemView *pItem = GetInventoryItemByItemID( m_LoadoutItems[iClass][iSlot] );
+
+ // To protect against users lying to the backend about the position of their items,
+ // we need to validate their position on the server when we retrieve them.
+ if ( pItem && AreSlotsConsideredIdentical( pItem->GetStaticData()->GetEquipType(), pItem->GetStaticData()->GetLoadoutSlot( iClass ), iSlot ) )
+ return pItem;
+ }
+ }
+
+ return TFInventoryManager()->GetBaseItemForClass( iClass, iSlot );
+}
+
+#ifdef CLIENT_DLL
+CEconItemView *CTFPlayerInventory::GetCacheServerItemInLoadout( int iClass, int iSlot )
+{
+ if ( iSlot < 0 || iSlot >= CLASS_LOADOUT_POSITION_COUNT )
+ return NULL;
+ if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS )
+ return NULL;
+
+ // If we don't have an item in the loadout at that slot, we return the base item
+ if ( m_CachedServerLoadoutItems[iClass][iSlot] != LOADOUT_SLOT_USE_BASE_ITEM )
+ {
+ CEconItemView *pItem = GetInventoryItemByItemID( m_CachedServerLoadoutItems[iClass][iSlot] );
+
+ // To protect against users lying to the backend about the position of their items,
+ // we need to validate their position on the server when we retrieve them.
+ if ( pItem && AreSlotsConsideredIdentical( pItem->GetStaticData()->GetEquipType(), pItem->GetStaticData()->GetLoadoutSlot( iClass ), iSlot ) )
+ return pItem;
+ }
+
+ return TFInventoryManager()->GetBaseItemForClass( iClass, iSlot );
+}
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static CEconGameAccountClient *GetSOCacheGameAccountClient( CGCClientSharedObjectCache *pSOCache )
+{
+ if ( !pSOCache )
+ return NULL;
+
+ return pSOCache->GetSingleton<CEconGameAccountClient>();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerInventory::GetPreviewItemDef( void ) const
+{
+ CEconGameAccountClient *pGameAccountClient = GetSOCacheGameAccountClient( m_pSOCache );
+ if ( !pGameAccountClient )
+ return 0;
+
+ return pGameAccountClient->Obj().preview_item_def();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerInventory::CanPurchaseItems( int iItemCount ) const
+{
+ // If we're not a free trial account, we fall back to our default logic of "do
+ // we have enough empty slots?".
+ CEconGameAccountClient *pGameAccountClient = GetSOCacheGameAccountClient( m_pSOCache );
+ if ( !pGameAccountClient || !pGameAccountClient->Obj().trial_account() )
+ return BaseClass::CanPurchaseItems( iItemCount );
+
+ // We're a free trial account, so when we purchase these items, our inventory
+ // will actually expand. We check to make sure that we have room for these
+ // items against what will be our new maximum backpack size, not our current
+ // backpack limit.
+ int iNewItemCount = GetItemCount() + iItemCount,
+ iAfterPurchaseMaxItemCount = DEFAULT_NUM_BACKPACK_SLOTS
+ + (pGameAccountClient ? pGameAccountClient->Obj().additional_backpack_slots() : 0);
+
+ return iNewItemCount <= iAfterPurchaseMaxItemCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerInventory::GetMaxItemCount( void ) const
+{
+ int iMaxItems = DEFAULT_NUM_BACKPACK_SLOTS;
+ CEconGameAccountClient *pGameAccountClient = GetSOCacheGameAccountClient( m_pSOCache );
+ if ( pGameAccountClient )
+ {
+ if ( pGameAccountClient->Obj().trial_account() )
+ {
+ iMaxItems = DEFAULT_NUM_BACKPACK_SLOTS_FREE_TRIAL_ACCOUNT;
+ }
+ iMaxItems += pGameAccountClient->Obj().additional_backpack_slots();
+ }
+ return MIN( iMaxItems, MAX_NUM_BACKPACK_SLOTS );
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Removes any item in a loadout slot. If the slot has a base item,
+// the player essentially returns to using that item.
+//-----------------------------------------------------------------------------
+bool CTFPlayerInventory::ClearLoadoutSlot( int iClass, int iSlot )
+{
+ if ( iSlot < 0 || iSlot >= CLASS_LOADOUT_POSITION_COUNT )
+ return false;
+
+ if ( iClass == GEconItemSchema().GetAccountIndex() )
+ {
+ if ( m_AccountLoadoutItems[iSlot] == LOADOUT_SLOT_USE_BASE_ITEM )
+ return false;
+ }
+ else
+ {
+ if ( iClass < TF_FIRST_NORMAL_CLASS || iClass >= TF_LAST_NORMAL_CLASS )
+ return false;
+
+ if ( m_LoadoutItems[iClass][iSlot] == LOADOUT_SLOT_USE_BASE_ITEM )
+ return false;
+ }
+
+ CEconItemView *pItemInSlot = GetItemInLoadout( iClass, iSlot );
+ if ( !pItemInSlot )
+ return false;
+
+ InventoryManager()->UpdateInventoryEquippedState( this, INVALID_ITEM_ID, iClass, iSlot );
+
+ // TODO: Prediction
+ // It's been moved to the backpack, so clear out loadout entry
+ //m_LoadoutItems[iClass][iSlot] = LOADOUT_SLOT_USE_BASE_ITEM;
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Update weapon skin request list
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::UpdateWeaponSkinRequest()
+{
+ TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 );
+
+ FOR_EACH_VEC_BACK( m_vecWeaponSkinRequestList, i )
+ {
+ SkinRequest_t &req = m_vecWeaponSkinRequestList[i];
+ CEconItemView *pItem = GetInventoryItemByItemID( req.m_nID );
+ Assert( pItem );
+ if ( !pItem )
+ {
+ m_vecWeaponSkinRequestList.Remove( i );
+ continue;
+ }
+
+ // Have we loaded this one yet? If not, do it here.
+ if ( mdlcache->IsErrorModel( req.m_hModel ) )
+ {
+ const char *pszModelName = pItem->GetPlayerDisplayModel( 0, 0 );
+ if ( pszModelName )
+ {
+ MDLHandle_t hItemMDL = mdlcache->FindMDL( pszModelName );
+ if ( mdlcache->IsErrorModel( hItemMDL ) )
+ {
+ AssertMsg( 0, "failed to find %s from mdlcache", pszModelName );
+ m_vecWeaponSkinRequestList.Remove( i );
+ continue;
+ }
+
+ req.m_hModel = hItemMDL;
+ }
+ else
+ {
+ AssertMsg( 0, "failed to precache weapon skin because there's no model" );
+ m_vecWeaponSkinRequestList.Remove( i );
+ }
+ }
+
+ // Draw!
+ {
+ CMDL mdl;
+ mdl.SetMDL( req.m_hModel );
+ mdl.m_pProxyData = static_cast<IClientRenderable*>( pItem );
+
+ pItem->SetWeaponSkinUseLowRes( true );
+ int nRestoreTeam = pItem->GetTeamNumber();
+ pItem->SetTeamNumber( req.m_nTeam );
+
+ matrix3x4_t matIdentity;
+ SetIdentityMatrix( matIdentity );
+ IMaterial *pOverrideMaterial = pItem->GetMaterialOverride( pItem->GetTeamNumber() );
+
+ if ( pOverrideMaterial )
+ modelrender->ForcedMaterialOverride( pOverrideMaterial );
+
+ mdl.Draw( matIdentity );
+
+ if ( pOverrideMaterial )
+ modelrender->ForcedMaterialOverride( NULL );
+
+ pItem->SetTeamNumber( nRestoreTeam );
+ pItem->SetWeaponSkinUseLowRes( false );
+ }
+
+ // Don't remove until it's complete.
+ if ( pItem->GetWeaponSkinBase() )
+ {
+ Assert( pItem->GetWeaponSkinBaseCompositor() == NULL );
+ ITexture* pTex = pItem->GetWeaponSkinBase();
+ if ( pTex )
+ {
+ pTex->AddRef(); // We need to hold a ref to the texture.
+ m_CachedBaseTextureLowRes[ req.m_nTeam ].Insert( req.m_nID, pTex );
+ pItem->SetWeaponSkinBase( NULL ); // Clean up.
+ }
+
+ // We do RED, then BLUE. Then drop it out of the list.
+ if ( req.m_nTeam == TF_TEAM_BLUE )
+ {
+ Assert( !mdlcache->IsErrorModel( req.m_hModel ) );
+ mdlcache->Release( req.m_hModel );
+ m_vecWeaponSkinRequestList.Remove( i );
+ }
+ else
+ {
+ Assert( req.m_nTeam == TF_TEAM_RED );
+ req.m_nTeam = TF_TEAM_BLUE;
+ }
+ }
+
+ // Only do one of these per frame to avoid hitching.
+ break;
+ }
+}
+
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::ItemHasBeenUpdated( CEconItemView *pItem, bool bUpdateAckFile, bool bWriteAckFile )
+{
+ Assert( pItem );
+
+ BaseClass::ItemHasBeenUpdated( pItem, bUpdateAckFile, bWriteAckFile );
+ const CEconItem *pEconItem = pItem->GetSOCData();
+
+#ifdef CLIENT_DLL
+ static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" );
+ bool bLocalInv = InventoryManager()->GetLocalInventory() == this;
+#endif // CLIENT_DLL
+
+ for ( equipped_class_t iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ equipped_slot_t unSlot = pEconItem ? pEconItem->GetEquippedPositionForClass( iClass ) : INVALID_EQUIPPED_SLOT;
+
+ Assert( GetInventoryItemByItemID( pItem->GetItemID() ) );
+
+ bool bThisClassLoadoutChanged = UpdateEquipStateForClass( pItem->GetItemID(), unSlot, m_LoadoutItems[iClass], ARRAYSIZE( m_LoadoutItems[iClass] ) );
+
+ if ( bThisClassLoadoutChanged )
+ {
+ m_bLoadoutChanged[iClass] = true;
+
+#ifdef CLIENT_DLL
+ // if we can inspect this item, it has unique skin.
+ // draw it once per team to tell the system to generate unique skin
+ float flInspect = 0;
+ if ( bLocalInv && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrib_WeaponAllowInspect, &flInspect ) )
+ {
+ SkinRequest_t req = { TF_TEAM_RED, pItem->GetItemID(), MDLHANDLE_INVALID };
+ m_vecWeaponSkinRequestList.AddToTail( req );
+ }
+#endif // CLIENT_DLL
+ }
+ }
+
+ // Update account items as well
+ equipped_slot_t unSlot = pEconItem ? pEconItem->GetEquippedPositionForClass( GEconItemSchema().GetAccountIndex() ) : INVALID_EQUIPPED_SLOT;
+ UpdateEquipStateForClass( pItem->GetItemID(), unSlot, m_AccountLoadoutItems, ARRAYSIZE( m_AccountLoadoutItems ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerInventory::UpdateEquipStateForClass( const itemid_t& itemID, equipped_slot_t nSlot, itemid_t *pLoadout, int nCount )
+{
+ bool bThisClassLoadoutChanged = false;
+
+ // For each slot this item may have been equipped in, set to the base item, unless that slot
+ // is the current slot. (ie., if an item moves from slot 10 to slot 12, slot 10 will be set to
+ // base item, and slot 12 will be set to this item ID)
+ for ( int i = 0; i < nCount; i++ )
+ {
+ itemid_t& refLoadoutItemID = *( pLoadout + i );
+
+ if ( i == nSlot )
+ {
+ // This may be an invalid slot for the item, but CTFPlayerInventory::InventoryReceived()
+ // will have detected that and sent off a request already to move it. The response
+ // to that will clear this loadout slot.
+ refLoadoutItemID = itemID;
+ bThisClassLoadoutChanged = true;
+ }
+ else if ( refLoadoutItemID == itemID )
+ {
+ refLoadoutItemID = LOADOUT_SLOT_USE_BASE_ITEM;
+ bThisClassLoadoutChanged = true;
+ }
+ }
+
+ return bThisClassLoadoutChanged;
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::PostSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
+{
+ BaseClass::PostSOUpdate( steamIDOwner, eEvent );
+
+ VerifyChangedLoadoutsAreValid();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
+{
+ BaseClass::SOCacheSubscribed( steamIDOwner, eEvent );
+
+ VerifyChangedLoadoutsAreValid();
+ UpdateCachedServerLoadoutItems();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Helper function to add a new item for a econ item
+//-----------------------------------------------------------------------------
+bool CTFPlayerInventory::AddEconItem( CEconItem * pItem, bool bUpdateAckFile, bool bWriteAckFile, bool bCheckForNewItems )
+{
+ if ( BaseClass::AddEconItem( pItem, bUpdateAckFile, bWriteAckFile, bCheckForNewItems ) )
+ {
+ if ( bCheckForNewItems && InventoryManager()->GetLocalInventory() == this )
+ {
+ if ( IsUnacknowledged( pItem->GetInventoryToken() ) )
+ {
+ OnHasNewQuest();
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::VerifyLoadoutItemsAreValid( int iClass )
+{
+ // Important note: currently this function walks the loadout slots in order causing slots towards
+ // the beginning of the list to take priority over slots later in the list. This means that currently
+ // weapons take priority over hats, hats take priority over misc slots, etc. which is all well and good.
+ // If later we want the order in which slots claim their equip regions to change, we'll want to change
+ // the iteration order here and also change GenerateEquipRegionMaskUpToSlot(), which is used for
+ // filling out the UI.
+ equip_region_mask_t unCumulativeRegionMask = 0;
+ for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ )
+ {
+ CEconItemView *pEquippedItemView = GetItemInLoadout( iClass, i );
+ if ( !pEquippedItemView )
+ continue;
+
+ // Does this item use the same regions as some item that we already have equipped?
+ equip_region_mask_t unItemEquipMask = pEquippedItemView->GetItemDefinition()->GetEquipRegionMask();
+ if ( unItemEquipMask & unCumulativeRegionMask )
+ {
+ // Unequip this item. This will wind up calling into ::ItemHasBeenUpdated() once the
+ // unequip makes it to the GC and back.
+ InventoryManager()->UpdateInventoryEquippedState( this, INVALID_ITEM_ID, iClass, pEquippedItemView->GetEquippedPositionForClass( iClass ) );
+ }
+ else
+ {
+ unCumulativeRegionMask |= unItemEquipMask;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::VerifyChangedLoadoutsAreValid()
+{
+ // We maybe changed equip state for an item and it's possible if we're sending bad messages and/or
+ // hacking state that we'll now have conflicting items equipped. Walk all of our items for this class
+ // to verify our state is good.
+ for ( equipped_class_t iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( m_bLoadoutChanged[iClass] )
+ {
+ VerifyLoadoutItemsAreValid( iClass );
+ }
+ }
+}
+#endif // CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerInventory::ItemIsBeingRemoved( CEconItemView *pItem )
+{
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( pItem->IsEquippedForClass( iClass ) )
+ {
+ for ( int iSlot = 0; iSlot < CLASS_LOADOUT_POSITION_COUNT; iSlot++ )
+ {
+ if ( m_LoadoutItems[iClass][iSlot] == pItem->GetItemID() )
+ {
+ m_LoadoutItems[iClass][iSlot] = LOADOUT_SLOT_USE_BASE_ITEM;
+ m_bLoadoutChanged[iClass] = true;
+ }
+ }
+ }
+ }
+}
+
+
+//=======================================================================================================================
+// TF PLAYER INVENTORY
+//=======================================================================================================================
+
+#ifdef CLIENT_DLL
+// WTF: Declaring this inline caused a compiler bug.
+CTFPlayerInventory *CTFInventoryManager::GetLocalTFInventory( void )
+{
+ return &m_LocalInventory;
+}
+#endif
+
+#ifdef _DEBUG
+#if defined(CLIENT_DLL)
+CON_COMMAND_F( item_dumpinv_other, "Dumps the contents of a specified client inventory. Format: item_dumpinv_other <player index>", FCVAR_NONE )
+{
+ if ( args.ArgC() > 1 )
+ {
+ C_TFPlayer *pOther = ToTFPlayer( UTIL_PlayerByIndex( atoi(args[1]) ) );
+ if ( pOther )
+ {
+ pOther->Inventory()->DumpInventoryToConsole( true );
+ return;
+ }
+ }
+ Msg("Couldn't find specified player.\nFormat: item_dumpinv_other <player index>\n");
+}
+#endif
+#endif // _DEBUG
+
+#ifdef _DEBUG
+#if defined(CLIENT_DLL)
+CON_COMMAND_F( item_dumpinv, "Dumps the contents of a specified client inventory. Format: item_dumpinv", FCVAR_CHEAT )
+#else
+CON_COMMAND_F( item_dumpinv_sv, "Dumps the contents of a specified server inventory. Format: item_dumpinv_sv", FCVAR_CHEAT )
+#endif
+{
+#if defined(CLIENT_DLL)
+ CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
+#else
+ CSteamID steamID;
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_GetCommandClient() );
+ pPlayer->GetSteamID( &steamID );
+#endif
+
+ CPlayerInventory *pInventory = TFInventoryManager()->GetInventoryForPlayer( steamID );
+ if ( !pInventory )
+ {
+ Msg("No inventory for that player.\n");
+ return;
+ }
+
+ pInventory->DumpInventoryToConsole( true );
+}
+#endif
+
+#ifdef _DEBUG
+#if defined(CLIENT_DLL)
+CON_COMMAND_F( item_deleteunknowns, "Deletes all items in your inventory that we don't have static data for. Useful for removing items that have been removed from the backend.", FCVAR_CHEAT )
+{
+ int iDeleted = TFInventoryManager()->DeleteUnknowns( InventoryManager()->GetLocalInventory() );
+ Msg("Deleted %d unknown items.\n", iDeleted);
+}
+#endif
+#endif
+
+
+#if defined( TF_CLIENT_DLL )
+CON_COMMAND( load_itempreset, "Equip all items for a given preset on the player." )
+{
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pPlayer )
+ return;
+
+ if ( args.ArgC() != 2 )
+ {
+ Msg( "Usage: \"load_itempreset <preset index>\" - <preset index> can be 0 to 3." );
+ return;
+ }
+
+ if ( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) )
+ return;
+
+ equipped_class_t unClass = pPlayer->GetPlayerClass()->GetClassIndex();
+ equipped_preset_t unPreset = atoi( args[1] );
+ if ( TFInventoryManager()->LoadPreset( unClass, unPreset ) )
+ {
+ // Tell the GC to tell server that we should respawn if we're in a respawn room
+ extern ConVar tf_respawn_on_loadoutchanges;
+ if ( tf_respawn_on_loadoutchanges.GetBool() )
+ {
+ GCSDK::CGCMsg< ::MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ }
+}
+#endif // TF_CLIENT_DLL