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
2340 lines
70 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "econ_item_inventory.h" |
|
#include "vgui/ILocalize.h" |
|
#include "tier3/tier3.h" |
|
#include "econ_item_system.h" |
|
#include "econ_item.h" |
|
#include "econ_gcmessages.h" |
|
#include "shareddefs.h" |
|
#include "filesystem.h" |
|
#include "econ_item_description.h" // only for CSteamAccountIDAttributeCollector |
|
|
|
#ifdef CLIENT_DLL |
|
#include <igameevents.h> |
|
#include "econ_game_account_client.h" |
|
#include "ienginevgui.h" |
|
#include "econ_ui.h" |
|
#include "item_pickup_panel.h" |
|
#include "econ/econ_item_preset.h" |
|
#include "econ/confirm_dialog.h" |
|
#include "tf_xp_source.h" |
|
#include "tf_notification.h" |
|
#else |
|
#include "props_shared.h" |
|
#include "basemultiplayerplayer.h" |
|
#endif |
|
|
|
#if defined(TF_CLIENT_DLL) || defined(TF_DLL) |
|
#include "tf_gcmessages.h" |
|
#include "tf_duel_summary.h" |
|
#include "econ_contribution.h" |
|
#include "tf_player_info.h" |
|
#include "econ/econ_claimcode.h" |
|
#include "tf_wardata.h" |
|
#include "tf_ladder_data.h" |
|
#include "tf_rating_data.h" |
|
#endif |
|
|
|
#if defined(TF_DLL) && defined(GAME_DLL) |
|
#include "tf_gc_api.h" |
|
#include "econ/econ_game_account_server.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
using namespace GCSDK; |
|
|
|
#ifdef _DEBUG |
|
ConVar item_inventory_debug( "item_inventory_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
#endif |
|
|
|
#ifdef USE_DYNAMIC_ASSET_LOADING |
|
//extern ConVar item_dynamicload; |
|
#endif |
|
|
|
#define ITEM_CLIENTACK_FILE "item_clientacks.txt" |
|
|
|
#ifdef _DEBUG |
|
#ifdef CLIENT_DLL |
|
ConVar item_debug_clientacks( "item_debug_clientacks", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); |
|
#endif |
|
#endif // _DEBUG |
|
|
|
// Result codes strings for GC results. |
|
const char* GCResultString[8] = |
|
{ |
|
"k_EGCMsgResponseOK", // Request succeeded |
|
"k_EGCMsgResponseDenied", // Request denied |
|
"k_EGCMsgResponseServerError", // Request failed due to a temporary server error |
|
"k_EGCMsgResponseTimeout", // Request timed out |
|
"k_EGCMsgResponseInvalid", // Request was corrupt |
|
"k_EGCMsgResponseNoMatch", // No item definition matched the request |
|
"k_EGCMsgResponseUnknownError", // Request failed with an unknown error |
|
"k_EGCMsgResponseNotLoggedOn", // Client not logged on to steam |
|
}; |
|
|
|
CBasePlayer *GetPlayerBySteamID( const CSteamID &steamID ) |
|
{ |
|
CSteamID steamIDPlayer; |
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
if ( pPlayer == NULL ) |
|
continue; |
|
|
|
if ( pPlayer->GetSteamID( &steamIDPlayer ) == false ) |
|
continue; |
|
|
|
if ( steamIDPlayer == steamID ) |
|
return pPlayer; |
|
} |
|
return NULL; |
|
} |
|
|
|
// Inventory Less function. |
|
// Used to sort the inventory items into their positions. |
|
bool CInventoryListLess::Less( const CEconItemView &src1, const CEconItemView &src2, void *pCtx ) |
|
{ |
|
int iPos1 = src1.GetInventoryPosition(); |
|
int iPos2 = src2.GetInventoryPosition(); |
|
|
|
// Context can be specified to point to a func that extracts the position from the backend position. |
|
// Necessary if your inventory packs a bunch of info into the position instead of using it just as a position. |
|
if ( pCtx ) |
|
{ |
|
CPlayerInventory *pInv = (CPlayerInventory*)pCtx; |
|
iPos1 = pInv->ExtractInventorySortPosition( iPos1 ); |
|
iPos2 = pInv->ExtractInventorySortPosition( iPos2 ); |
|
} |
|
|
|
if ( iPos1 < iPos2 ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CInventoryManager::CInventoryManager( void ) |
|
#ifdef CLIENT_DLL |
|
: m_mapPersonaNamesCache( DefLessFunc( uint32 ) ) |
|
, m_sPersonaStateChangedCallback( this, &CInventoryManager::OnPersonaStateChanged ) |
|
, m_personaNameRequests( DefLessFunc( uint64 ) ) |
|
#endif |
|
{ |
|
#ifdef CLIENT_DLL |
|
m_pkvItemClientAckFile = NULL; |
|
m_bClientAckDirty = false; |
|
m_iPredictedDiscards = 0; |
|
m_flNextLoadPresetChange = 0.0f; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::SteamRequestInventory( CPlayerInventory *pInventory, CSteamID pSteamID, IInventoryUpdateListener *pListener ) |
|
{ |
|
// SteamID must be valid |
|
if ( !pSteamID.IsValid() || !pSteamID.BIndividualAccount() ) |
|
{ |
|
if ( !HushAsserts() ) |
|
{ |
|
Assert( pSteamID.IsValid() ); |
|
Assert( pSteamID.BIndividualAccount() ); |
|
} |
|
return; |
|
} |
|
|
|
// If we haven't seen this inventory before, register it |
|
bool bFound = false; |
|
for ( int i = 0; i < m_pInventories.Count(); i++ ) |
|
{ |
|
if ( m_pInventories[i].pInventory == pInventory ) |
|
{ |
|
bFound = true; |
|
break; |
|
} |
|
} |
|
if ( !bFound ) |
|
{ |
|
int iIdx = m_pInventories.AddToTail(); |
|
m_pInventories[iIdx].pInventory = pInventory; |
|
m_pInventories[iIdx].pListener = pListener; |
|
} |
|
|
|
// Add the request to our list of pending requests |
|
int iIdx = m_hPendingInventoryRequests.AddToTail(); |
|
m_hPendingInventoryRequests[iIdx].pID = pSteamID; |
|
m_hPendingInventoryRequests[iIdx].pInventory = pInventory; |
|
|
|
pInventory->RequestInventory( pSteamID ); |
|
|
|
if( pListener ) |
|
{ |
|
pInventory->AddListener( pListener ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when a gameserver connects to steam. |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::GameServerSteamAPIActivated() |
|
{ |
|
#if defined(TF_DLL) && defined(GAME_DLL) |
|
GameCoordinator_NotifyGameState(); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CPlayerInventory *CInventoryManager::GetInventoryForAccount( uint32 iAccountID ) |
|
{ |
|
FOR_EACH_VEC( m_pInventories, i ) |
|
{ |
|
if ( m_pInventories[i].pInventory->GetOwner().GetAccountID() == iAccountID ) |
|
return m_pInventories[i].pInventory; |
|
} |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::DeregisterInventory( CPlayerInventory *pInventory ) |
|
{ |
|
int iCount = m_pInventories.Count(); |
|
for ( int i = iCount-1; i >= 0; i-- ) |
|
{ |
|
if ( m_pInventories[i].pInventory == pInventory ) |
|
{ |
|
m_pInventories.Remove(i); |
|
} |
|
} |
|
} |
|
|
|
|
|
#ifdef CLIENT_DLL |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CInventoryManager::IsPresetIndexValid( equipped_preset_t unPreset ) |
|
{ |
|
const bool bResult = GetItemSchema()->IsValidPreset( unPreset ); |
|
AssertMsg( bResult, "Invalid preset index!" ); |
|
return bResult; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CInventoryManager::LoadPreset( equipped_class_t unClass, equipped_preset_t unPreset ) |
|
{ |
|
if ( !IsValidPlayerClass( unClass ) ) |
|
return false; |
|
|
|
if ( !IsPresetIndexValid( unPreset ) ) |
|
return false; |
|
|
|
if ( !GetLocalInventory()->GetSOC() ) |
|
return false; |
|
|
|
if ( m_flNextLoadPresetChange > gpGlobals->realtime ) |
|
{ |
|
Msg( "Loadout change denied. Changing presets too quickly.\n" ); |
|
return false; |
|
} |
|
|
|
m_flNextLoadPresetChange = gpGlobals->realtime + 0.5f; |
|
|
|
GCSDK::CProtoBufMsg<CMsgSelectPresetForClass> msg( k_EMsgGCPresets_SelectPresetForClass ); |
|
msg.Body().set_class_id( unClass ); |
|
msg.Body().set_preset_id( unPreset ); |
|
GCClientSystem()->BSendMessage( msg ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::UpdateLocalInventory( void ) |
|
{ |
|
if ( steamapicontext->SteamUser() && GetLocalInventory() ) |
|
{ |
|
CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); |
|
if ( steamID.IsValid() ) // make sure we're logged in and we know who we are |
|
{ |
|
SteamRequestInventory( GetLocalInventory(), steamID ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::OnPersonaStateChanged( PersonaStateChange_t *info ) |
|
{ |
|
if ( ( info->m_nChangeFlags & k_EPersonaChangeName ) != 0 ) |
|
m_personaNameRequests.InsertOrReplace( info->m_ulSteamID, true ); |
|
} |
|
|
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CInventoryManager::Init( void ) |
|
{ |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::PostInit( void ) |
|
{ |
|
// Initialize the item system. |
|
ItemSystem()->Init(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::PreInitGC() |
|
{ |
|
REG_SHARED_OBJECT_SUBCLASS( CEconItem ); |
|
|
|
#if defined (CLIENT_DLL) |
|
REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountClient ); |
|
REG_SHARED_OBJECT_SUBCLASS( CEconItemPerClassPresetData ); |
|
REG_SHARED_OBJECT_SUBCLASS( CSOTFMatchResultPlayerInfo ); |
|
REG_SHARED_OBJECT_SUBCLASS( CXPSource ); |
|
REG_SHARED_OBJECT_SUBCLASS( CTFNotification ); |
|
#endif |
|
|
|
#if defined(TF_CLIENT_DLL) || defined(TF_DLL) |
|
|
|
REG_SHARED_OBJECT_SUBCLASS( CWarData ); |
|
REG_SHARED_OBJECT_SUBCLASS( CTFDuelSummary ); |
|
REG_SHARED_OBJECT_SUBCLASS( CTFMapContribution ); |
|
REG_SHARED_OBJECT_SUBCLASS( CTFPlayerInfo ); |
|
REG_SHARED_OBJECT_SUBCLASS( CEconClaimCode ); |
|
REG_SHARED_OBJECT_SUBCLASS( CSOTFLadderData ); |
|
#endif |
|
|
|
#ifdef TF_DLL |
|
REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountForGameServers ); |
|
#endif // TF_DLL |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::PostInitGC() |
|
{ |
|
#ifdef CLIENT_DLL |
|
// The client immediately loads the local player's inventory |
|
UpdateLocalInventory(); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::Shutdown() |
|
{ |
|
int nInventoryCount = m_pInventories.Count(); |
|
for ( int iInventory = 0; iInventory < nInventoryCount; ++iInventory ) |
|
{ |
|
CPlayerInventory *pInventory = m_pInventories[iInventory].pInventory; |
|
if ( pInventory ) |
|
{ |
|
pInventory->Clear(); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::LevelInitPreEntity( void ) |
|
{ |
|
// Throw out any testitem definitions |
|
for ( int i = 0; i < TI_TYPE_COUNT; i++ ) |
|
{ |
|
int iNewDef = TESTITEM_DEFINITIONS_BEGIN_AT + i; |
|
ItemSystem()->GetItemSchema()->ItemTesting_DiscardTestDefinition( iNewDef ); |
|
} |
|
|
|
// Precache all item models we've got |
|
#ifdef GAME_DLL |
|
CUtlVector<const char *> vecPrecacheModelStrings; |
|
#endif // GAME_DLL |
|
const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetItemDefinitionMap(); |
|
FOR_EACH_MAP_FAST( mapItemDefs, i ) |
|
{ |
|
CEconItemDefinition *pData = mapItemDefs[i]; |
|
|
|
pData->SetHasBeenLoaded( true ); |
|
|
|
#ifdef GAME_DLL |
|
bool bDynamicLoad = false; |
|
#ifdef USE_DYNAMIC_ASSET_LOADING |
|
bDynamicLoad = true;//item_dynamicload.GetBool(); |
|
#endif // USE_DYNAMIC_ASSET_LOADING |
|
pData->GeneratePrecacheModelStrings( bDynamicLoad, &vecPrecacheModelStrings ); |
|
|
|
// Precache the models and the gibs for everything the definition requested. |
|
FOR_EACH_VEC( vecPrecacheModelStrings, i ) |
|
{ |
|
// Ignore any objects which requested an empty precache string for whatever reason. |
|
if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] ) |
|
{ |
|
int iModelIndex = CBaseEntity::PrecacheModel( vecPrecacheModelStrings[i] ); |
|
PrecacheGibsForModel( iModelIndex ); |
|
} |
|
} |
|
|
|
vecPrecacheModelStrings.RemoveAll(); |
|
|
|
pData->GeneratePrecacheSoundStrings( bDynamicLoad, &vecPrecacheModelStrings ); |
|
|
|
// Precache the sounds for everything |
|
FOR_EACH_VEC( vecPrecacheModelStrings, i ) |
|
{ |
|
// Ignore any objects which requested an empty precache string for whatever reason. |
|
if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] ) |
|
{ |
|
CBaseEntity::PrecacheScriptSound( vecPrecacheModelStrings[i] ); |
|
} |
|
} |
|
|
|
vecPrecacheModelStrings.RemoveAll(); |
|
#endif |
|
} |
|
|
|
// We reset the cached attribute class strings, since it's invalidated by level changes |
|
ItemSystem()->ResetAttribStringCache(); |
|
|
|
#ifdef GAME_DLL |
|
ItemSystem()->ReloadWhitelist(); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::LevelShutdownPostEntity( void ) |
|
{ |
|
// We reset the cached attribute class strings, since it's invalidated by level changes |
|
ItemSystem()->ResetAttribStringCache(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Lets the client know that we're now connected to the GC |
|
//----------------------------------------------------------------------------- |
|
#ifdef CLIENT_DLL |
|
void CInventoryManager::SendGCConnectedEvent( void ) |
|
{ |
|
IGameEvent *event = gameeventmanager->CreateEvent( "gc_connected" ); |
|
if ( event ) |
|
{ |
|
gameeventmanager->FireEventClientSide( event ); |
|
} |
|
} |
|
#endif |
|
|
|
|
|
#if !defined(NO_STEAM) |
|
//----------------------------------------------------------------------------- |
|
// Purpose: GC Msg handler to receive the dev "new item" response |
|
//----------------------------------------------------------------------------- |
|
class CGCDev_NewItemRequestResponse : public GCSDK::CGCClientJob |
|
{ |
|
public: |
|
CGCDev_NewItemRequestResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} |
|
|
|
virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) |
|
{ |
|
GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); |
|
|
|
if ( msg.Body().m_eResponse == k_EGCMsgResponseOK ) |
|
{ |
|
Msg("Received new item acknowledgement: %s\n", GCResultString[msg.Body().m_eResponse] ); |
|
} |
|
else |
|
{ |
|
Warning("Failed to generate new item: %s\n", GCResultString[msg.Body().m_eResponse] ); |
|
} |
|
return true; |
|
} |
|
|
|
}; |
|
GC_REG_JOB( GCSDK::CGCClient, CGCDev_NewItemRequestResponse, "CGCDev_NewItemRequestResponse", k_EMsgGCDev_NewItemRequestResponse, GCSDK::k_EServerTypeGCClient ); |
|
|
|
#endif // NO_STEAM |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::RemovePendingRequest( CSteamID *pSteamID ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
// Only the client, all requests are for the local player. Clear them all. |
|
m_hPendingInventoryRequests.Purge(); |
|
return; |
|
#endif |
|
|
|
// On the server, remove all requests for the specified steam id |
|
int iCount = m_hPendingInventoryRequests.Count(); |
|
for ( int i = iCount-1; i >= 0; i-- ) |
|
{ |
|
if ( m_hPendingInventoryRequests[i].pID == *pSteamID ) |
|
{ |
|
m_hPendingInventoryRequests.Remove(i); |
|
} |
|
} |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::DropItem( itemid_t iItemID ) |
|
{ |
|
static CSchemaAttributeDefHandle pAttrDef_NoDelete( "cannot delete" ); |
|
|
|
// Double check that this item can be delete |
|
CEconItemView *pItem = GetLocalInventory()->GetInventoryItemByItemID( iItemID ); |
|
if ( !pItem || !pAttrDef_NoDelete || pItem->FindAttribute( pAttrDef_NoDelete ) ) |
|
{ |
|
return; |
|
} |
|
|
|
GCSDK::CGCMsg<MsgGCDelete_t> msg( k_EMsgGCDelete ); |
|
msg.Body().m_unItemID = iItemID; |
|
GCClientSystem()->BSendMessage( msg ); |
|
|
|
// Keep track of how many items we've discarded, but haven't received responses for. |
|
m_iPredictedDiscards++; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Delete any items we can't find static data for. This can happen when we're testing |
|
// internally, and then remove an item. Shouldn't ever happen in the wild. |
|
//----------------------------------------------------------------------------- |
|
int CInventoryManager::DeleteUnknowns( CPlayerInventory *pInventory ) |
|
{ |
|
// We need to manually walk the main inventory's SOC, because unknown items won't be in the inventory |
|
GCSDK::CGCClientSharedObjectCache *pSOC = pInventory->GetSOC(); |
|
if ( pSOC ) |
|
{ |
|
int iBadItems = 0; |
|
CGCClientSharedObjectTypeCache *pTypeCache = pSOC->FindTypeCache( CEconItem::k_nTypeID ); |
|
if( pTypeCache ) |
|
{ |
|
for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ ) |
|
{ |
|
CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem ); |
|
if ( pItem ) |
|
{ |
|
CEconItemDefinition *pData = ItemSystem()->GetStaticDataForItemByDefIndex( pItem->GetDefinitionIndex() ); |
|
if ( !pData ) |
|
{ |
|
DropItem( pItem->GetItemID() ); |
|
iBadItems++; |
|
} |
|
} |
|
} |
|
} |
|
return iBadItems; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tries to move the specified item into the player's backpack. |
|
// FAILS if the backpack is full. Returns false in that case. |
|
//----------------------------------------------------------------------------- |
|
bool CInventoryManager::SetItemBackpackPosition( CEconItemView *pItem, uint32 iPosition, bool bForceUnequip, bool bAllowOverflow ) |
|
{ |
|
CPlayerInventory *pInventory = GetLocalInventory(); |
|
if ( !pInventory ) |
|
return false; |
|
|
|
const int iMaxItems = pInventory->GetMaxItemCount(); |
|
if ( !iPosition ) |
|
{ |
|
// Build a list of empty slots. We track extra slots beyond the backpack for overflow. |
|
CUtlVector< bool > bFilledSlots; |
|
bFilledSlots.SetSize( iMaxItems * 2 ); |
|
for ( int i = 0; i < bFilledSlots.Count(); ++i ) |
|
{ |
|
bFilledSlots[i] = false; |
|
} |
|
for ( int i = 0; i < pInventory->GetItemCount(); i++ ) |
|
{ |
|
CEconItemView *pTmpItem = pInventory->GetItem(i); |
|
// Ignore the item we're moving. |
|
if ( pTmpItem == pItem ) |
|
continue; |
|
|
|
int iBackpackPos = GetBackpackPositionFromBackend( pTmpItem->GetInventoryPosition() ); |
|
if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() ) |
|
{ |
|
bFilledSlots[iBackpackPos] = true; |
|
} |
|
} |
|
|
|
// Add predicted filled slots |
|
for ( int i = 0; i < m_PredictedFilledSlots.Count(); i++ ) |
|
{ |
|
int iBackpackPos = m_PredictedFilledSlots[i]; |
|
if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() ) |
|
{ |
|
bFilledSlots[iBackpackPos] = true; |
|
} |
|
} |
|
|
|
// Now find an empty slot |
|
for ( int i = 1; i < bFilledSlots.Count(); i++ ) |
|
{ |
|
if ( !bFilledSlots[i] ) |
|
{ |
|
iPosition = i; |
|
break; |
|
} |
|
} |
|
|
|
if ( !iPosition ) |
|
return false; |
|
} |
|
|
|
if ( !bAllowOverflow && iPosition > (uint32)iMaxItems ) |
|
return false; |
|
|
|
//Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iPosition ); |
|
|
|
uint32 iBackendPosition = bForceUnequip ? 0 : pItem->GetInventoryPosition(); |
|
SetBackpackPosition( &iBackendPosition, iPosition ); |
|
UpdateInventoryPosition( pInventory, pItem->GetItemID(), iBackendPosition ); |
|
|
|
m_PredictedFilledSlots.AddToTail( iPosition ); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::MoveItemToBackpackPosition( CEconItemView *pItem, int iBackpackPosition ) |
|
{ |
|
CEconItemView *pOldItem = GetItemByBackpackPosition( iBackpackPosition ); |
|
if ( pOldItem ) |
|
{ |
|
// Move the item in the new spot to our current spot |
|
SetItemBackpackPosition( pOldItem, GetBackpackPositionFromBackend(pItem->GetInventoryPosition()) ); |
|
|
|
//Warning("Moved OLD item %llu to backpack slot: %d\n", pOldItem->GetItemID(), GetBackpackPositionFromBackend(iBackendPosition) ); |
|
} |
|
|
|
// Move the item to the new spot |
|
SetItemBackpackPosition( pItem, iBackpackPosition ); |
|
|
|
//Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iBackpackPosition ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
class CWaitForBackpackSortFinishDialog : public CGenericWaitingDialog |
|
{ |
|
public: |
|
CWaitForBackpackSortFinishDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent ) |
|
{ |
|
} |
|
|
|
protected: |
|
virtual void OnTimeout() |
|
{ |
|
InventoryManager()->SortBackpackFinished(); |
|
} |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::SortBackpackBy( uint32 iSortType ) |
|
{ |
|
GCSDK::CProtoBufMsg<CMsgSortItems> msg( k_EMsgGCSortItems ); |
|
msg.Body().set_sort_type( iSortType ); |
|
GCClientSystem()->BSendMessage( msg ); |
|
|
|
ShowWaitingDialog( new CWaitForBackpackSortFinishDialog( NULL ), "#BackpackSortExplanation_Title", true, false, 3.0f ); |
|
m_bInBackpackSort = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::SortBackpackFinished( void ) |
|
{ |
|
m_bInBackpackSort = false; |
|
|
|
GetLocalInventory()->SendInventoryUpdateEvent(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: GC Msg handler to receive the sort finished message |
|
//----------------------------------------------------------------------------- |
|
class CGBackpackSortFinished : public GCSDK::CGCClientJob |
|
{ |
|
public: |
|
CGBackpackSortFinished( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} |
|
|
|
virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) |
|
{ |
|
CloseWaitingDialog(); |
|
InventoryManager()->SortBackpackFinished(); |
|
return true; |
|
} |
|
|
|
}; |
|
GC_REG_JOB( GCSDK::CGCClient, CGBackpackSortFinished, "CGBackpackSortFinished", k_EMsgGCBackpackSortFinished, GCSDK::k_EServerTypeGCClient ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::UpdateInventoryPosition( CPlayerInventory *pInventory, uint64 ulItemID, uint32 unNewInventoryPos ) |
|
{ |
|
if ( !pInventory->GetInventoryItemByItemID( ulItemID ) ) |
|
{ |
|
Warning("Attempt to update inventory position failure: %s.\n", "could not find matching item ID"); |
|
return; |
|
} |
|
if ( !pInventory->GetSOCDataForItem( ulItemID ) ) |
|
{ |
|
Warning("Attempt to update inventory position failure: %s\n", "could not find SOC data for item"); |
|
return; |
|
} |
|
|
|
// In the incredibly rare case where the GC crashed while sorting our backpack, we won't have gotten |
|
// a k_EMsgGCBackpackSortFinished message. Assume that if we're requesting a manual move of an item, we're not sorting anymore. |
|
m_bInBackpackSort = false; |
|
|
|
// TF has multiple ways of using the inventory position bits. For all inventory positions moving forward, assume |
|
// they're in the new format. |
|
#if defined(TF_CLIENT_DLL) || defined(TF_DLL) |
|
if ( unNewInventoryPos != 0 ) |
|
{ |
|
unNewInventoryPos |= kBackendPosition_NewFormat; |
|
} |
|
#endif // defined(TF_CLIENT_DLL) || defined(TF_DLL) |
|
|
|
// Queue a message to be sent to the GC |
|
CMsgSetItemPositions_ItemPosition *pMsg = m_msgPendingSetItemPositions.add_item_positions(); |
|
pMsg->set_item_id( ulItemID ); |
|
pMsg->set_position( unNewInventoryPos ); |
|
} |
|
|
|
void CInventoryManager::Update( float frametime ) |
|
{ |
|
|
|
// Check if we have any pending item position changes that we need to flush out |
|
if ( m_msgPendingSetItemPositions.item_positions_size() > 0 ) |
|
{ |
|
// !KLUDGE! It would be nice if we could just send this in one line instead of making a copy |
|
CProtoBufMsg<CMsgSetItemPositions> msg( k_EMsgGCSetItemPositions ); |
|
msg.Body() = m_msgPendingSetItemPositions; |
|
GCClientSystem()->BSendMessage( msg ); |
|
|
|
m_msgPendingSetItemPositions.Clear(); |
|
} |
|
|
|
// Check if we have any pending account lookups to batch up |
|
if ( m_msgPendingLookupAccountNames.accountids_size() > 0 ) |
|
{ |
|
// !KLUDGE! It would be nice if we could just send this in one line instead of making a copy |
|
CProtoBufMsg< CMsgLookupMultipleAccountNames > msg( k_EMsgGCLookupMultipleAccountNames ); |
|
msg.Body() = m_msgPendingLookupAccountNames; |
|
GCClientSystem()->BSendMessage( msg ); |
|
|
|
m_msgPendingLookupAccountNames.Clear(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::UpdateInventoryEquippedState( CPlayerInventory *pInventory, uint64 ulItemID, equipped_class_t unClass, equipped_slot_t unSlot ) |
|
{ |
|
// passing in INVALID_ITEM_ID means "unequip from this slot" |
|
if ( ulItemID != INVALID_ITEM_ID ) |
|
{ |
|
if ( !pInventory->GetInventoryItemByItemID( ulItemID ) ) |
|
{ |
|
//Warning("Attempt to update equipped state failure: %s.\n", "could not find matching item ID"); |
|
return; |
|
} |
|
if ( !pInventory->GetSOCDataForItem( ulItemID ) ) |
|
{ |
|
//Warning("Attempt to update equipped state failure: %s\n", "could not find SOC data for item"); |
|
return; |
|
} |
|
} |
|
|
|
CProtoBufMsg<CMsgAdjustItemEquippedState> msg( k_EMsgGCAdjustItemEquippedState ); |
|
msg.Body().set_item_id( ulItemID ); |
|
msg.Body().set_new_class( unClass ); |
|
msg.Body().set_new_slot( unSlot ); |
|
GCClientSystem()->BSendMessage( msg ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CInventoryManager::ShowItemsPickedUp( bool bForce, bool bReturnToGame, bool bNoPanel ) |
|
{ |
|
CPlayerInventory *pLocalInv = GetLocalInventory(); |
|
if ( !pLocalInv ) |
|
return false; |
|
|
|
// Don't bring it up if we're already browsing something in the gameUI |
|
vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL ); |
|
if ( !bForce && vgui::ipanel()->IsVisible( gameuiPanel ) ) |
|
return false; |
|
|
|
CUtlVector<CEconItemView*> aItemsFound; |
|
|
|
// Go through the root inventory and find any items that are in the "found" position |
|
int iCount = pLocalInv->GetItemCount(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
CEconItemView *pTmp = pLocalInv->GetItem(i); |
|
if ( !pTmp ) |
|
continue; |
|
|
|
if ( pTmp->GetStaticData()->IsHidden() ) |
|
continue; |
|
|
|
uint32 iPosition = pTmp->GetInventoryPosition(); |
|
if ( IsUnacknowledged(iPosition) == false ) |
|
continue; |
|
if ( GetBackpackPositionFromBackend(iPosition) != 0 ) |
|
continue; |
|
|
|
// Now make sure we haven't got a clientside saved ack for this item. |
|
// This makes sure we don't show multiple pickups for items that we've found, |
|
// but haven't been able to move out of unack'd position due to the GC being unavailable. |
|
if ( HasBeenAckedByClient( pTmp ) ) |
|
continue; |
|
|
|
aItemsFound.AddToTail( pTmp ); |
|
} |
|
|
|
if ( !aItemsFound.Count() ) |
|
return CheckForRoomAndForceDiscard(); |
|
|
|
// We're not forcing the player to make room yet. Just show the pickup panel. |
|
CItemPickupPanel *pItemPanel = bNoPanel ? NULL : EconUI()->OpenItemPickupPanel(); |
|
|
|
if ( pItemPanel ) |
|
{ |
|
pItemPanel->SetReturnToGame( bReturnToGame ); |
|
} |
|
|
|
for ( int i = 0; i < aItemsFound.Count(); i++ ) |
|
{ |
|
if ( pItemPanel ) |
|
{ |
|
pItemPanel->AddItem( aItemsFound[i] ); |
|
} |
|
else |
|
{ |
|
AcknowledgeItem( aItemsFound[i] ); |
|
} |
|
} |
|
|
|
if ( pItemPanel ) |
|
{ |
|
pItemPanel->MoveToFront(); |
|
} |
|
else |
|
{ |
|
SaveAckFile(); |
|
} |
|
|
|
aItemsFound.Purge(); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CInventoryManager::CheckForRoomAndForceDiscard( void ) |
|
{ |
|
CPlayerInventory *pLocalInv = GetLocalInventory(); |
|
if ( !pLocalInv ) |
|
return false; |
|
|
|
// Go through the inventory and attempt to move any items outside the backpack into valid positions. |
|
// Remember the first item that we failed to move, so we can force a discard later. |
|
CEconItemView *pItem = NULL; |
|
const int iMaxItems = pLocalInv->GetMaxItemCount(); |
|
int iCount = pLocalInv->GetItemCount(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
CEconItemView *pTmp = pLocalInv->GetItem(i); |
|
if ( !pTmp ) |
|
continue; |
|
|
|
if ( pTmp->GetStaticData()->IsHidden() ) |
|
continue; |
|
|
|
uint32 iPosition = pTmp->GetInventoryPosition(); |
|
if ( IsUnacknowledged(iPosition) || GetBackpackPositionFromBackend(iPosition) > iMaxItems ) |
|
{ |
|
if ( !SetItemBackpackPosition( pTmp, 0, false, false ) ) |
|
{ |
|
pItem = pTmp; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// If we're not over the limit, we're done. |
|
if ( ( iCount - m_iPredictedDiscards ) <= iMaxItems ) |
|
return false; |
|
|
|
if ( !pItem ) |
|
return false; |
|
|
|
// We're forcing the player to make room for items he's found. Bring up that panel with the first item over the limit. |
|
CItemDiscardPanel *pDiscardPanel = EconUI()->OpenItemDiscardPanel(); |
|
pDiscardPanel->SetItem( pItem ); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Client Acknowledges an item and moves it in to the backpack |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::AcknowledgeItem ( CEconItemView *pItem, bool bMoveToBackpack /* = true */ ) |
|
{ |
|
SetAckedByClient( pItem ); |
|
|
|
int iMethod = GetUnacknowledgedReason( pItem->GetInventoryPosition() ) - 1; |
|
if ( iMethod >= ARRAYSIZE( g_pszItemPickupMethodStringsUnloc ) || iMethod < 0 ) |
|
iMethod = 0; |
|
EconUI()->Gamestats_ItemTransaction( IE_ITEM_RECEIVED, pItem, g_pszItemPickupMethodStringsUnloc[iMethod] ); |
|
|
|
// Then move it to the first empty backpack position |
|
if ( bMoveToBackpack ) |
|
{ |
|
SetItemBackpackPosition( pItem, 0, false, true ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CEconItemView *CInventoryManager::GetItemByBackpackPosition( int iBackpackPosition ) |
|
{ |
|
CPlayerInventory *pInventory = GetLocalInventory(); |
|
if ( !pInventory ) |
|
return NULL; |
|
|
|
// Backpack positions start from 1 |
|
Assert( iBackpackPosition > 0 && iBackpackPosition <= pInventory->GetMaxItemCount() ); |
|
for ( int i = 0; i < pInventory->GetItemCount(); i++ ) |
|
{ |
|
CEconItemView *pItem = pInventory->GetItem(i); |
|
if ( GetBackpackPositionFromBackend( pItem->GetInventoryPosition() ) == iBackpackPosition ) |
|
return pItem; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CInventoryManager::HasBeenAckedByClient( CEconItemView *pItem ) |
|
{ |
|
return ( GetAckKeyForItem( pItem ) != NULL ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::SetAckedByClient( CEconItemView *pItem ) |
|
{ |
|
VerifyAckFileLoaded(); |
|
|
|
static char szTmp[128]; |
|
Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() ); |
|
m_pkvItemClientAckFile->SetInt( szTmp, 1 ); |
|
|
|
m_bClientAckDirty = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::SetAckedByGC( CEconItemView *pItem, bool bSave ) |
|
{ |
|
KeyValues *pkvItem = GetAckKeyForItem( pItem ); |
|
if ( pkvItem ) |
|
{ |
|
m_pkvItemClientAckFile->RemoveSubKey( pkvItem ); |
|
pkvItem->deleteThis(); |
|
|
|
m_bClientAckDirty = true; |
|
|
|
if ( bSave ) |
|
{ |
|
SaveAckFile(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
KeyValues *CInventoryManager::GetAckKeyForItem( CEconItemView *pItem ) |
|
{ |
|
VerifyAckFileLoaded(); |
|
|
|
static char szTmp[128]; |
|
Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() ); |
|
return m_pkvItemClientAckFile->FindKey( szTmp ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::VerifyAckFileLoaded( void ) |
|
{ |
|
if ( m_pkvItemClientAckFile ) |
|
return; |
|
|
|
m_pkvItemClientAckFile = new KeyValues( ITEM_CLIENTACK_FILE ); |
|
|
|
ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface( |
|
SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL; |
|
|
|
if ( pRemoteStorage ) |
|
{ |
|
if ( pRemoteStorage->FileExists(ITEM_CLIENTACK_FILE) ) |
|
{ |
|
int32 nFileSize = pRemoteStorage->GetFileSize( ITEM_CLIENTACK_FILE ); |
|
|
|
if ( nFileSize > 0 ) |
|
{ |
|
CUtlBuffer buf( 0, nFileSize ); |
|
if ( pRemoteStorage->FileRead( ITEM_CLIENTACK_FILE, buf.Base(), nFileSize ) == nFileSize ) |
|
{ |
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSize ); |
|
m_pkvItemClientAckFile->ReadAsBinary( buf ); |
|
|
|
#ifdef _DEBUG |
|
if ( item_debug_clientacks.GetBool() ) |
|
{ |
|
m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_readack.txt", "MOD" ); |
|
} |
|
#endif |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Clean up any item references that we no longer have items for. |
|
// This ensures that if we delete an item on the backend, we remove it from the ack file. |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::CleanAckFile( void ) |
|
{ |
|
CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); |
|
if ( !pInventory ) |
|
return; |
|
|
|
if ( !pInventory->RetrievedInventoryFromSteam() ) |
|
return; |
|
|
|
if ( m_pkvItemClientAckFile ) |
|
{ |
|
KeyValues *pKVItem = m_pkvItemClientAckFile->GetFirstSubKey(); |
|
while ( pKVItem != NULL ) |
|
{ |
|
itemid_t ulID = (itemid_t)Q_atoi64( pKVItem->GetName() ); |
|
if ( pInventory->GetInventoryItemByItemID(ulID) == NULL ) |
|
{ |
|
KeyValues *pTmp = pKVItem->GetNextKey(); |
|
m_pkvItemClientAckFile->RemoveSubKey( pKVItem ); |
|
pKVItem->deleteThis(); |
|
|
|
m_bClientAckDirty = true; |
|
|
|
pKVItem = pTmp; |
|
} |
|
else |
|
{ |
|
pKVItem = pKVItem->GetNextKey(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CInventoryManager::SaveAckFile( void ) |
|
{ |
|
if ( !m_bClientAckDirty ) |
|
return; |
|
m_bClientAckDirty = false; |
|
|
|
ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface( |
|
SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL; |
|
|
|
if ( pRemoteStorage ) |
|
{ |
|
CUtlBuffer buf; |
|
m_pkvItemClientAckFile->WriteAsBinary( buf ); |
|
pRemoteStorage->FileWrite( ITEM_CLIENTACK_FILE, buf.Base(), buf.TellPut() ); |
|
|
|
#ifdef _DEBUG |
|
if ( item_debug_clientacks.GetBool() ) |
|
{ |
|
m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_saveack.txt", "MOD" ); |
|
} |
|
#endif |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: GC sent name of account down |
|
//----------------------------------------------------------------------------- |
|
class CGCLookupAccountNameResponse : public GCSDK::CGCClientJob |
|
{ |
|
public: |
|
CGCLookupAccountNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} |
|
virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) |
|
{ |
|
GCSDK::CGCMsg<MsgGCLookupAccountNameResponse_t> msg( pNetPacket ); |
|
|
|
CUtlString playerName; |
|
if ( msg.BReadStr( &playerName ) ) |
|
{ |
|
InventoryManager()->PersonaName_Store( msg.Body().m_unAccountID, playerName.Get() ); |
|
} |
|
return true; |
|
} |
|
}; |
|
GC_REG_JOB( GCSDK::CGCClient, CGCLookupAccountNameResponse, "CGCLookupAccountNameResponse", k_EMsgGCLookupAccountNameResponse, GCSDK::k_EServerTypeGCClient ); |
|
|
|
class CGCLookupMultipleAccountsNameResponse : public GCSDK::CGCClientJob |
|
{ |
|
public: |
|
CGCLookupMultipleAccountsNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} |
|
virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) |
|
{ |
|
CProtoBufMsg<CMsgLookupMultipleAccountNamesResponse> msg( pNetPacket ); |
|
for ( int i = 0 ; i < msg.Body().accounts_size() ; ++i ) |
|
{ |
|
const CMsgLookupMultipleAccountNamesResponse_Account &account = msg.Body().accounts( i ); |
|
InventoryManager()->PersonaName_Store( account.accountid(), account.persona().c_str() ); |
|
} |
|
|
|
return true; |
|
} |
|
}; |
|
GC_REG_JOB( GCSDK::CGCClient, CGCLookupMultipleAccountsNameResponse, "CGCLookupMultipleAccountsNameResponse", k_EMsgGCLookupMultipleAccountNamesResponse, GCSDK::k_EServerTypeGCClient ); |
|
|
|
void CInventoryManager::PersonaName_Precache( uint32 unAccountID ) |
|
{ |
|
const char *pszName = PersonaName_Get( unAccountID ); |
|
if ( pszName == NULL ) |
|
{ |
|
// Queue request name from GC |
|
m_msgPendingLookupAccountNames.add_accountids( unAccountID ); |
|
|
|
// insert empty string so we don't ask again |
|
m_mapPersonaNamesCache.Insert( unAccountID, "" ); |
|
} |
|
} |
|
|
|
const char *CInventoryManager::PersonaName_Get( uint32 unAccountID ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
// First ask Steam if this is one of friends -- if so we can get an up-to-date persona name. |
|
{ |
|
const char *pszName = NULL; |
|
|
|
if ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() ) |
|
{ |
|
CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); |
|
steamID.SetAccountID( unAccountID ); |
|
uint64 u64AccountId = steamID.ConvertToUint64(); |
|
|
|
// We're covering three states here: |
|
// 1. We've never asked before. We need to queue up a RequestUserInformation. |
|
// 2. We've asked before, and we haven't heard back yet |
|
// 3. We've asked before, we heard back. Don't re-request user information. |
|
auto index = m_personaNameRequests.Find( u64AccountId ); |
|
if ( !m_personaNameRequests.IsValidIndex( index ) ) |
|
{ |
|
// This is case 1--we've never asked before. |
|
|
|
// If RequestUserInformation returns false, the information is already available. |
|
// Otherwise, it will arrive later and we need to rebuild the description at that time. |
|
if ( !steamapicontext->SteamFriends()->RequestUserInformation( steamID, true ) ) |
|
{ |
|
pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); |
|
Assert( pszName ); // Guaranteed by the steam api |
|
|
|
if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 ) |
|
{ |
|
m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName ); |
|
return pszName; |
|
} |
|
} |
|
else |
|
{ |
|
// This is case 2, we've asked above. |
|
m_personaNameRequests.Insert( u64AccountId, false ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_personaNameRequests[ index ] ) |
|
{ |
|
// This is case 3. |
|
pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); |
|
Assert( pszName ); // Guaranteed by the steam api |
|
|
|
if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 ) |
|
{ |
|
m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName ); |
|
return pszName; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// If that didn't work, ask the server we're playing on if they know this account ID. |
|
CBasePlayer *pPlayer = GetPlayerByAccountID( unAccountID ); |
|
if ( pPlayer ) |
|
{ |
|
const char *pszPlayerName = pPlayer->GetPlayerName(); |
|
if ( pszPlayerName ) |
|
{ |
|
m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszPlayerName ); |
|
return pszPlayerName; |
|
} |
|
} |
|
|
|
// If *that* didn't work, look in our cache populated by the GC (or the above paths). This |
|
// might be out of date but it's better than nothing. |
|
int idx = m_mapPersonaNamesCache.Find( unAccountID ); |
|
if ( m_mapPersonaNamesCache.IsValidIndex( idx ) ) |
|
{ |
|
return m_mapPersonaNamesCache[idx].Get(); |
|
} |
|
|
|
return "[unknown]"; |
|
} |
|
|
|
void CInventoryManager::PersonaName_Store( uint32 unAccountID, const char *pPersonaName ) |
|
{ |
|
m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pPersonaName ); |
|
} |
|
|
|
#endif // CLIENT_DLL |
|
|
|
|
|
//======================================================================================================================= |
|
// PLAYER INVENTORY |
|
//======================================================================================================================= |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CPlayerInventory::CPlayerInventory( void ) |
|
{ |
|
m_bGotItemsFromSteam = false; |
|
m_iPendingRequests = 0; |
|
m_aInventoryItems.Purge(); |
|
m_pSOCache = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CPlayerInventory::~CPlayerInventory() |
|
{ |
|
FOR_EACH_VEC( m_vecItemHandles, i ) |
|
{ |
|
m_vecItemHandles[ i ]->InventoryIsBeingDeleted(); |
|
} |
|
m_vecItemHandles.Purge(); |
|
|
|
if ( m_iPendingRequests ) |
|
{ |
|
InventoryManager()->RemovePendingRequest( &m_OwnerID ); |
|
m_iPendingRequests = 0; |
|
} |
|
|
|
SOClear(); |
|
|
|
InventoryManager()->DeregisterInventory( this ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::SOClear() |
|
{ |
|
if ( m_OwnerID.IsValid() ) |
|
{ |
|
CGCClientSystem *pClientSystem = GCClientSystem(); |
|
Assert ( pClientSystem != NULL ); |
|
if ( pClientSystem != NULL ) |
|
{ |
|
CGCClient *pClient = pClientSystem->GetGCClient(); |
|
Assert ( pClient != NULL ); |
|
pClient->RemoveSOCacheListener( m_OwnerID, this ); |
|
} |
|
} |
|
|
|
// Somebody registered as a listener through us, but now our Steam ID |
|
// is changing? This is bad news. |
|
Assert( m_vecListeners.Count() == 0 ); |
|
while ( m_vecListeners.Count() > 0 ) |
|
{ |
|
RemoveListener( m_vecListeners[0] ); |
|
} |
|
|
|
// If we were subscribed, we should have gotten our unsubscribe message, |
|
// and that should have cleared the pointer |
|
Assert( m_pSOCache == NULL); |
|
m_pSOCache = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::AddItemHandle( CEconItemViewHandle* pHandle ) |
|
{ |
|
FOR_EACH_VEC( m_vecItemHandles, i ) |
|
{ |
|
if ( m_vecItemHandles[ i ] == pHandle ) |
|
{ |
|
Assert( !"Item handle already in list to track!" ); |
|
return; |
|
} |
|
} |
|
|
|
m_vecItemHandles.AddToTail( pHandle ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::RemoveItemHandle( CEconItemViewHandle* pHandle ) |
|
{ |
|
FOR_EACH_VEC( m_vecItemHandles, i ) |
|
{ |
|
if ( m_vecItemHandles[ i ] == pHandle ) |
|
{ |
|
m_vecItemHandles.Remove( i ); |
|
return; |
|
} |
|
} |
|
|
|
Assert( !"Could not find item handle to remove!" ); |
|
} |
|
|
|
|
|
void CPlayerInventory::Clear() |
|
{ |
|
SOClear(); |
|
m_OwnerID = CSteamID(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::RequestInventory( CSteamID pSteamID ) |
|
{ |
|
// Make sure we don't already have somebody else's stuff |
|
// on hand |
|
if ( m_OwnerID != pSteamID ) |
|
SOClear(); |
|
|
|
// Remember whose inventory we're looking at |
|
m_OwnerID = pSteamID; |
|
|
|
// SteamID must be valid |
|
if ( !m_OwnerID.IsValid() || !m_OwnerID.BIndividualAccount() ) |
|
{ |
|
Assert( m_OwnerID.IsValid() ); |
|
Assert( m_OwnerID.BIndividualAccount() ); |
|
return; |
|
} |
|
|
|
// If we don't already have an SO cache, then ask the GC for one, |
|
// and start listening to it. We will receive our "subscribed" message |
|
// when the data is valid |
|
GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, this ); |
|
} |
|
|
|
void CPlayerInventory::AddListener( GCSDK::ISharedObjectListener *pListener ) |
|
{ |
|
Assert( m_OwnerID.IsValid() ); |
|
if ( m_vecListeners.Find( pListener ) < 0 ) |
|
{ |
|
m_vecListeners.AddToTail( pListener ); |
|
GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, pListener ); |
|
} |
|
} |
|
|
|
|
|
void CPlayerInventory::RemoveListener( GCSDK::ISharedObjectListener *pListener ) |
|
{ |
|
if ( m_OwnerID.IsValid() ) |
|
{ |
|
m_vecListeners.FindAndFastRemove( pListener ); |
|
GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerID, pListener ); |
|
} |
|
else |
|
{ |
|
Assert( m_vecListeners.Count() == 0 ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helper function to add a new item for a econ item |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerInventory::AddEconItem( CEconItem * pItem, bool bUpdateAckFile, bool bWriteAckFile, bool bCheckForNewItems ) |
|
{ |
|
CEconItemView newItem; |
|
if( !FilloutItemFromEconItem( &newItem, pItem ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
int iIdx = m_aInventoryItems.Insert( newItem ); |
|
|
|
DirtyItemHandles(); |
|
|
|
ItemHasBeenUpdated( &m_aInventoryItems[iIdx], bUpdateAckFile, bWriteAckFile ); |
|
|
|
#ifdef CLIENT_DLL |
|
if ( bCheckForNewItems && InventoryManager()->GetLocalInventory() == this ) |
|
{ |
|
bool bNotify = IsUnacknowledged( pItem->GetInventoryToken() ); |
|
// ignore Halloween drops |
|
bNotify &= pItem->GetOrigin() != kEconItemOrigin_HalloweenDrop; |
|
// only notify for specific reasons |
|
unacknowledged_item_inventory_positions_t reason = GetUnacknowledgedReason( pItem->GetInventoryToken() ); |
|
switch ( reason ) |
|
{ |
|
case UNACK_ITEM_UNKNOWN: |
|
case UNACK_ITEM_DROPPED: |
|
case UNACK_ITEM_SUPPORT: |
|
case UNACK_ITEM_EARNED: |
|
case UNACK_ITEM_REFUNDED: |
|
case UNACK_ITEM_COLLECTION_REWARD: |
|
case UNACK_ITEM_TRADED: |
|
case UNACK_ITEM_GIFTED: |
|
case UNACK_ITEM_QUEST_LOANER: |
|
case UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD: |
|
break; |
|
default: |
|
bNotify = false; |
|
break; |
|
} |
|
|
|
if ( bNotify && !pItem->GetItemDefinition()->IsHidden() ) |
|
{ |
|
OnHasNewItems(); |
|
} |
|
} |
|
#endif |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates a script item and associates it with this econ item |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
if( pObject->GetTypeID() != CEconItem::k_nTypeID ) |
|
return; |
|
Assert( steamIDOwner == m_OwnerID ); |
|
if ( steamIDOwner != m_OwnerID ) |
|
return; |
|
|
|
// We shouldn't get these notifications unless we're subscribed, right? |
|
if ( m_pSOCache == NULL) |
|
{ |
|
Assert( m_pSOCache ); |
|
return; |
|
} |
|
|
|
// Don't bother unless it's an incremental notification. |
|
// For mass updates, we'll do everything more efficiently in one place |
|
if ( eEvent != GCSDK::eSOCacheEvent_Incremental ) |
|
{ |
|
Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed || eEvent == GCSDK::eSOCacheEvent_ListenerAdded ); |
|
return; |
|
} |
|
|
|
CEconItem *pItem = (CEconItem *)pObject; |
|
AddEconItem( pItem, true, true, true ); |
|
SendInventoryUpdateEvent(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Updates the script item associated with this econ item |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
if( pObject->GetTypeID() != CEconItem::k_nTypeID ) |
|
return; |
|
Assert( steamIDOwner == m_OwnerID ); |
|
if ( steamIDOwner != m_OwnerID ) |
|
return; |
|
|
|
// We shouldn't get these notifications unless we're subscribed, right? |
|
if ( m_pSOCache == NULL) |
|
{ |
|
Assert( m_pSOCache ); |
|
return; |
|
} |
|
|
|
// Don't bother unless it's an incremental notification. |
|
// For mass updates, we'll do everything more efficiently in one place |
|
if ( eEvent != GCSDK::eSOCacheEvent_Incremental ) |
|
{ |
|
Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed ); |
|
return; |
|
} |
|
|
|
CEconItem *pEconItem = (CEconItem *)pObject; |
|
|
|
bool bChanged = false; |
|
CEconItemView *pScriptItem = GetInventoryItemByItemID( pEconItem->GetItemID() ); |
|
if ( pScriptItem ) |
|
{ |
|
if ( FilloutItemFromEconItem( pScriptItem, pEconItem ) ) |
|
{ |
|
ItemHasBeenUpdated( pScriptItem, false, false ); |
|
} |
|
|
|
bChanged = true; |
|
} |
|
else |
|
{ |
|
// The item isn't in this inventory right now. But it may need to be |
|
// after the update, so try adding it and see if the inventory wants it. |
|
bChanged = AddEconItem( pEconItem, false, false, false ); |
|
} |
|
|
|
if ( bChanged ) |
|
{ |
|
ResortInventory(); |
|
|
|
DirtyItemHandles(); |
|
|
|
#ifdef CLIENT_DLL |
|
// Client doesn't update inventory while items are moving in a backpack sort. Does it once at the sort end instead. |
|
if ( !InventoryManager()->IsInBackpackSort() ) |
|
#endif |
|
{ |
|
SendInventoryUpdateEvent(); |
|
} |
|
#ifdef _DEBUG |
|
if ( item_inventory_debug.GetBool() ) |
|
{ |
|
DumpInventoryToConsole( true ); |
|
} |
|
#endif |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes the script item associated with this econ item |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
if( pObject->GetTypeID() != CEconItem::k_nTypeID ) |
|
return; |
|
Assert( steamIDOwner == m_OwnerID ); |
|
if ( steamIDOwner != m_OwnerID ) |
|
return; |
|
|
|
// We shouldn't get these notifications unless we're subscribed, right? |
|
if ( m_pSOCache == NULL) |
|
{ |
|
Assert( m_pSOCache ); |
|
return; |
|
} |
|
|
|
// Don't bother unless it's an incremental notification. |
|
// For mass updates, we'll do everything more efficiently in one place |
|
if ( eEvent != GCSDK::eSOCacheEvent_Incremental ) |
|
{ |
|
Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed ); |
|
return; |
|
} |
|
|
|
CEconItem *pEconItem = (CEconItem *)pObject; |
|
RemoveItem( pEconItem->GetItemID() ); |
|
|
|
#ifdef CLIENT_DLL |
|
InventoryManager()->OnItemDeleted( this ); |
|
#endif |
|
|
|
SendInventoryUpdateEvent(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This is our initial notification that this cache has been received |
|
// from the server. |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
// Make sure we expect notifications about this guy |
|
Assert( steamIDOwner == m_OwnerID ); |
|
if ( steamIDOwner != m_OwnerID ) |
|
return; |
|
|
|
#ifdef _DEBUG |
|
Msg("CPlayerInventory::SOCacheSubscribed\n"); |
|
#endif |
|
|
|
// Clear our old inventory |
|
m_aInventoryItems.Purge(); |
|
|
|
DirtyItemHandles(); |
|
|
|
// Locate the cache that was just subscribed to |
|
m_pSOCache = GCClientSystem()->GetSOCache( m_OwnerID ); |
|
if ( m_pSOCache == NULL ) |
|
{ |
|
Assert( m_pSOCache != NULL ); |
|
return; |
|
} |
|
|
|
// add all the items already in the inventory |
|
CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindTypeCache( CEconItem::k_nTypeID ); |
|
if( pTypeCache ) |
|
{ |
|
for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ ) |
|
{ |
|
CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem ); |
|
AddEconItem(pItem, true, false, true ); |
|
} |
|
} |
|
|
|
m_bGotItemsFromSteam = true; |
|
|
|
#ifdef CLIENT_DLL |
|
if ( InventoryManager()->GetLocalInventory() == this ) |
|
{ |
|
// Only validate the local player inventory |
|
ValidateInventoryPositions(); |
|
|
|
// tell the entire client that we're 'connected' to the GC now |
|
CInventoryManager::SendGCConnectedEvent(); |
|
} |
|
#endif |
|
|
|
ResortInventory(); |
|
|
|
#ifdef CLIENT_DLL |
|
// Now that we've read all the items in, write out the ack file (only if we're the local inventory) |
|
if ( InventoryManager()->GetLocalInventory() == this ) |
|
{ |
|
InventoryManager()->CleanAckFile(); |
|
InventoryManager()->SaveAckFile(); |
|
} |
|
#endif |
|
} |
|
|
|
bool CInventoryManager::IsValidPlayerClass( equipped_class_t unClass ) |
|
{ |
|
const bool bResult = ItemSystem()->GetItemSchema()->IsValidClass( unClass ); |
|
AssertMsg( bResult, "Invalid player class!" ); |
|
return bResult; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes the script item associated with this econ item |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::ValidateInventoryPositions( void ) |
|
{ |
|
#ifdef TF2 |
|
if ( engine->GetAppID() == 520 ) |
|
{ |
|
TFInventoryManager()->DeleteUnknowns( this ); |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::ItemHasBeenUpdated( CEconItemView *pItem, bool bUpdateAckFile, bool bWriteAckFile ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
// Handle the clientside ack file |
|
if ( bUpdateAckFile && !IsUnacknowledged(pItem->GetInventoryPosition()) ) |
|
{ |
|
if ( InventoryManager()->GetLocalInventory() == this ) |
|
{ |
|
InventoryManager()->SetAckedByGC( pItem, bWriteAckFile ); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
m_pSOCache = NULL; |
|
m_bGotItemsFromSteam = false; |
|
m_aInventoryItems.Purge(); |
|
|
|
DirtyItemHandles(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: On the client this sends the "inventory_updated" event. On the server |
|
// it does nothing. |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::SendInventoryUpdateEvent() |
|
{ |
|
#ifdef CLIENT_DLL |
|
if( InventoryManager()->GetLocalInventory() == this ) |
|
{ |
|
IGameEvent *event = gameeventmanager->CreateEvent( "inventory_updated" ); |
|
if ( event ) |
|
{ |
|
gameeventmanager->FireEventClientSide( event ); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fills out all the fields in the script item based on what's in the |
|
// econ item |
|
//----------------------------------------------------------------------------- |
|
bool CPlayerInventory::FilloutItemFromEconItem( CEconItemView *pScriptItem, CEconItem *pEconItem ) |
|
{ |
|
// We need to detect the case where items have been updated & moved bags / positions. |
|
uint32 iOldPos = pScriptItem->GetInventoryPosition(); |
|
bool bWasInThisBag = ItemShouldBeIncluded( iOldPos ); |
|
|
|
// Ignore items that this inventory doesn't care about |
|
if ( !ItemShouldBeIncluded( pEconItem->GetInventoryToken() ) ) |
|
{ |
|
// The item has been moved out of this bag. Ensure our derived inventory classes know. |
|
if ( bWasInThisBag ) |
|
{ |
|
// We need to update it before it's removed. |
|
ItemHasBeenUpdated( pScriptItem, false, false ); |
|
|
|
RemoveItem( pEconItem->GetItemID() ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
pScriptItem->Init( pEconItem->GetDefinitionIndex(), pEconItem->GetQuality(), pEconItem->GetItemLevel(), pEconItem->GetAccountID() ); |
|
if ( !pScriptItem->IsValid() ) |
|
return false; |
|
|
|
pScriptItem->SetItemID( pEconItem->GetItemID() ); |
|
|
|
pScriptItem->SetInventoryPosition( pEconItem->GetInventoryToken() ); |
|
OnItemChangedPosition( pScriptItem, iOldPos ); |
|
|
|
#if BUILD_ITEM_NAME_AND_DESC |
|
// Precache account names if we have any. We do this way in advance of any code that might |
|
// use it (ie., description text building) so that by the time we try that we already have |
|
// the data setup. |
|
// |
|
// We don't worry about yielding here because this inventory code only runs on game |
|
// clients/servers, not the GC. |
|
CSteamAccountIDAttributeCollector AccountIDCollector; |
|
pEconItem->IterateAttributes( &AccountIDCollector ); |
|
|
|
FOR_EACH_VEC( AccountIDCollector.GetAccountIDs(), i ) |
|
{ |
|
InventoryManager()->PersonaName_Precache( (AccountIDCollector.GetAccountIDs())[i] ); |
|
} |
|
#endif |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::DumpInventoryToConsole( bool bRoot ) |
|
{ |
|
if ( bRoot ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
Msg("(CLIENT) Inventory:\n"); |
|
#else |
|
Msg("(SERVER) Inventory for account (%d):\n", m_OwnerID.GetAccountID() ); |
|
#endif |
|
Msg(" Version: %llu:\n", m_pSOCache ? m_pSOCache->GetVersion() : -1 ); |
|
} |
|
|
|
int iCount = m_aInventoryItems.Count(); |
|
Msg(" Num items: %d\n", iCount ); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
Msg(" %s (ID %llu)\n", m_aInventoryItems[i].GetStaticData()->GetDefinitionName(), m_aInventoryItems[i].GetItemID() ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::RemoveItem( itemid_t iItemID ) |
|
{ |
|
int iIndex; |
|
CEconItemView *pItem = GetInventoryItemByItemID( iItemID, &iIndex ); |
|
if ( pItem ) |
|
{ |
|
ItemIsBeingRemoved( pItem ); |
|
|
|
FOR_EACH_VEC( m_vecItemHandles, i ) |
|
{ |
|
m_vecItemHandles[ i ]->MarkDirty(); |
|
m_vecItemHandles[ i ]->ItemIsBeingDeleted( pItem ); |
|
} |
|
|
|
m_aInventoryItems.Remove(iIndex); |
|
|
|
#ifdef _DEBUG |
|
if ( item_inventory_debug.GetBool() ) |
|
{ |
|
DumpInventoryToConsole( true ); |
|
} |
|
#endif |
|
} |
|
|
|
// Don't need to resort because items will still be in order |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the item in our inventory that matches the specified global index |
|
//----------------------------------------------------------------------------- |
|
CEconItemView *CPlayerInventory::GetInventoryItemByItemID( itemid_t iIndex, int *pIndex ) |
|
{ |
|
int iCount = m_aInventoryItems.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
if ( m_aInventoryItems[i].GetItemID() == iIndex ) |
|
{ |
|
if ( pIndex ) |
|
{ |
|
*pIndex = i; |
|
} |
|
|
|
return &m_aInventoryItems[i]; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds the item in our inventory that matches the specified global original id |
|
//----------------------------------------------------------------------------- |
|
CEconItemView *CPlayerInventory::GetInventoryItemByOriginalID( itemid_t iOriginalID, int *pIndex /*= NULL*/ ) |
|
{ |
|
int iCount = m_aInventoryItems.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
CEconItem *pItem = m_aInventoryItems[i].GetSOCData(); |
|
if ( pItem && pItem->GetOriginalID() == iOriginalID ) |
|
{ |
|
if ( pIndex ) |
|
{ |
|
*pIndex = i; |
|
} |
|
|
|
return &m_aInventoryItems[i]; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the item in our inventory in the specified position |
|
//----------------------------------------------------------------------------- |
|
CEconItemView *CPlayerInventory::GetItemByPosition( int iPosition, int *pIndex ) |
|
{ |
|
int iCount = m_aInventoryItems.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
if ( m_aInventoryItems[i].GetInventoryPosition() == (unsigned int)iPosition ) |
|
{ |
|
if ( pIndex ) |
|
{ |
|
*pIndex = i; |
|
} |
|
|
|
return &m_aInventoryItems[i]; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
// Finds the first item in our backpack with match itemdef |
|
//----------------------------------------------------------------------------- |
|
CEconItemView *CPlayerInventory::FindFirstItembyItemDef( item_definition_index_t iItemDef ) |
|
{ |
|
int iCount = m_aInventoryItems.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
//GetItemDefIndex() |
|
if ( m_aInventoryItems[i].GetItemDefIndex() == iItemDef ) |
|
{ |
|
return &m_aInventoryItems[i]; |
|
} |
|
} |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the index for the item in our inventory utlvector |
|
//----------------------------------------------------------------------------- |
|
int CPlayerInventory::GetIndexForItem( CEconItemView *pItem ) |
|
{ |
|
int iCount = m_aInventoryItems.Count(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
if ( m_aInventoryItems[i].GetItemID() == pItem->GetItemID() ) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Dirty all the item handles that are registered with us |
|
//----------------------------------------------------------------------------- |
|
void CPlayerInventory::DirtyItemHandles() |
|
{ |
|
FOR_EACH_VEC( m_vecItemHandles, i ) |
|
{ |
|
m_vecItemHandles[ i ]->MarkDirty(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the item object cache data for the specified item |
|
//----------------------------------------------------------------------------- |
|
CEconItem *CPlayerInventory::GetSOCDataForItem( itemid_t iItemID ) |
|
{ |
|
if ( !m_pSOCache ) |
|
return NULL; |
|
|
|
CEconItem soIndex; |
|
soIndex.SetItemID( iItemID ); |
|
return (CEconItem *)m_pSOCache->FindSharedObject( soIndex ); |
|
} |
|
|
|
#if defined (_DEBUG) && defined(CLIENT_DLL) |
|
CON_COMMAND_F( item_deleteall, "WARNING: Removes all of the items in your inventory.", FCVAR_CHEAT ) |
|
{ |
|
CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); |
|
if ( !pInventory ) |
|
return; |
|
|
|
int iCount = pInventory->GetItemCount(); |
|
for ( int i = 0; i < iCount; i++ ) |
|
{ |
|
CEconItemView *pItem = pInventory->GetItem(i); |
|
if ( pItem ) |
|
{ |
|
InventoryManager()->DropItem( pItem->GetItemID() ); |
|
} |
|
} |
|
|
|
InventoryManager()->UpdateLocalInventory(); |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CPlayerInventory::GetRecipeCount() const |
|
{ |
|
const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap(); |
|
|
|
return mapRecipes.Count(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDef( int iIndex ) |
|
{ |
|
if ( !m_pSOCache ) |
|
return NULL; |
|
|
|
if ( iIndex < 0 || iIndex >= GetRecipeCount() ) |
|
return NULL; |
|
|
|
const CEconItemSchema::RecipeDefinitionMap_t& mapRecipes = GetItemSchema()->GetRecipeDefinitionMap(); |
|
|
|
// Store off separate index for "number of items iterated over" in case something |
|
// deletes from the recipes map out from under us. |
|
int j = 0; |
|
FOR_EACH_MAP_FAST( mapRecipes, i ) |
|
{ |
|
if ( j == iIndex ) |
|
return mapRecipes[i]; |
|
|
|
j++; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDefByDefIndex( uint16 iDefIndex ) |
|
{ |
|
if ( !m_pSOCache ) |
|
return NULL; |
|
|
|
// check always-known recipes |
|
const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap(); |
|
int i = mapRecipes.Find( iDefIndex ); |
|
if ( i != mapRecipes.InvalidIndex() ) |
|
return mapRecipes[i]; |
|
|
|
// there are no more SO recipes |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CEconItemViewHandle::SetItem( CEconItemView* pItem ) |
|
{ |
|
m_pItem = pItem; |
|
|
|
if ( pItem ) |
|
{ |
|
// Cache the item_id for lookup when our pointer gets dirtied |
|
m_nItemID = pItem->GetItemID(); |
|
auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() ); |
|
Assert( pInv ); |
|
if ( m_pInv != pInv ) |
|
{ |
|
// If this is a different inventory, unsubscribe. This can happen if the |
|
// handle gets reused |
|
if ( m_pInv ) |
|
{ |
|
m_pInv->RemoveItemHandle( this ); |
|
} |
|
|
|
m_pInv = pInv; |
|
|
|
// Subscribe to the new inventory |
|
m_pInv->AddItemHandle( this ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return a pointer to a CEconItemView |
|
//----------------------------------------------------------------------------- |
|
CEconItemView* CEconItemViewHandle::Get() const |
|
{ |
|
// If our pointer is dirty, we need to go get a new pointer |
|
if ( m_bPointerDirty ) |
|
{ |
|
if ( m_pInv ) |
|
{ |
|
m_pItem = m_pInv->GetInventoryItemByItemID( m_nItemID ); |
|
m_bPointerDirty = false; |
|
} |
|
} |
|
|
|
return m_pItem; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Unsubscribe us from future updates |
|
//----------------------------------------------------------------------------- |
|
CEconItemHandle::~CEconItemHandle() |
|
{ |
|
UnsubscribeFromSOEvents(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Save a pointer to the item and register us for SOCache events |
|
//----------------------------------------------------------------------------- |
|
void CEconItemHandle::SetItem( CEconItem* pItem ) |
|
{ |
|
UnsubscribeFromSOEvents(); |
|
|
|
m_pItem = NULL; |
|
m_iItemID = INVALID_ITEM_ID; |
|
|
|
if ( pItem ) |
|
{ |
|
auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() ); |
|
if ( pInv ) |
|
{ |
|
m_OwnerSteamID.SetFromUint64( pInv->GetOwner().ConvertToUint64() ); |
|
GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerSteamID, this ); |
|
} |
|
|
|
m_pItem = pItem; |
|
m_iItemID = pItem->GetID(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check if out item got deleted. If it did, mark our pointer as NULL |
|
// so future dereferences will get NULL instead of a stale pointer. |
|
//----------------------------------------------------------------------------- |
|
void CEconItemHandle::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
if( pObject->GetTypeID() != CEconItem::k_nTypeID || m_pItem == NULL ) |
|
return; |
|
|
|
const CEconItem *pItem = (CEconItem *)pObject; |
|
|
|
if ( m_iItemID == pItem->GetID() ) |
|
{ |
|
UnsubscribeFromSOEvents(); |
|
m_pItem = NULL; |
|
m_iItemID = INVALID_ITEM_ID; |
|
} |
|
} |
|
|
|
void CEconItemHandle::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
if( pObject->GetTypeID() != CEconItem::k_nTypeID ) |
|
return; |
|
|
|
CEconItem *pItem = (CEconItem *)pObject; |
|
|
|
if ( m_iItemID == pItem->GetID() ) |
|
{ |
|
SetItem( pItem ); |
|
} |
|
} |
|
|
|
void CEconItemHandle::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
if ( pObject->GetTypeID() != CEconItem::k_nTypeID ) |
|
return; |
|
|
|
CEconItem *pItem = (CEconItem *)pObject; |
|
|
|
if ( m_iItemID == pItem->GetID() ) |
|
{ |
|
SetItem( pItem ); |
|
} |
|
} |
|
|
|
void CEconItemHandle::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) |
|
{ |
|
UnsubscribeFromSOEvents(); |
|
} |
|
|
|
void CEconItemHandle::UnsubscribeFromSOEvents() |
|
{ |
|
if ( m_OwnerSteamID.GetAccountID() != 0 ) |
|
{ |
|
GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerSteamID, this ); |
|
} |
|
} |
|
|
|
|
|
#if defined( STAGING_ONLY ) || defined( _DEBUG ) |
|
#if defined(CLIENT_DLL) |
|
CON_COMMAND_F( item_dumpinv, "Dumps the contents of a specified client inventory.", FCVAR_CHEAT ) |
|
#else |
|
CON_COMMAND_F( item_dumpinv_sv, "Dumps the contents of a specified server inventory.", FCVAR_CHEAT ) |
|
#endif |
|
{ |
|
#if defined(CLIENT_DLL) |
|
CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); |
|
#else |
|
CSteamID steamID; |
|
CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_GetCommandClient() ); |
|
pPlayer->GetSteamID( &steamID ); |
|
CPlayerInventory *pInventory = InventoryManager()->GetInventoryForAccount( steamID.GetAccountID() ); |
|
#endif |
|
if ( !pInventory ) |
|
{ |
|
Msg("No inventory found.\n"); |
|
return; |
|
} |
|
|
|
pInventory->DumpInventoryToConsole( true ); |
|
} |
|
|
|
#if defined (CLIENT_DLL) |
|
|
|
CON_COMMAND_F( item_dumpschema, "Dump the expanded schema for items to a file in sorted order suitable for diffs. Format: item_dumpschema <filename>", FCVAR_CHEAT ) |
|
{ |
|
if ( args.ArgC() != 2 ) |
|
{ |
|
Msg("Usage: item_dumpschema <filename>\n"); |
|
return; |
|
} |
|
|
|
if ( GetItemSchema()->DumpItems(args[1]) ) |
|
Msg("Dump complete, saved in game/tf/%s\n", args[1]); |
|
else |
|
Msg("Dump failed (?)\n"); |
|
} |
|
|
|
CON_COMMAND_F( item_giveitem, "Give an item to the local player. Format: item_giveitem <item definition name> or <item def index>", FCVAR_NONE ) |
|
{ |
|
if ( !steamapicontext || !steamapicontext->SteamUser() ) |
|
{ |
|
Msg("Not connected to Steam.\n"); |
|
return; |
|
} |
|
CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID(); |
|
if ( !steamIDForPlayer.IsValid() ) |
|
{ |
|
Msg("Failed to find a valid steamID for the local player.\n"); |
|
return; |
|
} |
|
|
|
int iItemCount = args.ArgC(); |
|
for ( int i = 1; i < iItemCount; ++i ) |
|
{ |
|
// Check to see if args[1] is a number (itemdefid) and if so, translate it to actual itemname |
|
const char *pszItemname = NULL; |
|
if ( V_isdigit( args[i][0] ) ) |
|
{ |
|
int iDef = V_atoi( args[i] ); |
|
CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( iDef ); |
|
if ( pItemDef ) |
|
{ |
|
pszItemname = pItemDef->GetItemDefinitionName(); |
|
} |
|
} |
|
else |
|
{ |
|
pszItemname = args[i]; |
|
} |
|
|
|
Msg("Sending request to generate '%s' for Local Player (%llu)\n", pszItemname, steamIDForPlayer.ConvertToUint64() ); |
|
|
|
CItemSelectionCriteria criteria; |
|
|
|
GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest ); |
|
msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); |
|
|
|
criteria.SetIgnoreEnabledFlag( true ); |
|
if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemname, true ) || |
|
!criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) ) |
|
{ |
|
Msg("Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" ); |
|
return; |
|
} |
|
GCClientSystem()->BSendMessage( msg ); |
|
} |
|
} |
|
|
|
CON_COMMAND_F( item_rolllootlist, "Force a loot list rool for the local player. Format: item_rolllootlist <loot list definition name>", FCVAR_NONE ) |
|
{ |
|
if ( !steamapicontext || !steamapicontext->SteamUser() ) |
|
{ |
|
Msg("Not connected to Steam.\n"); |
|
return; |
|
} |
|
CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID(); |
|
if ( !steamIDForPlayer.IsValid() ) |
|
{ |
|
Msg("Failed to find a valid steamID for the local player.\n"); |
|
return; |
|
} |
|
|
|
Msg("Sending request to roll '%s' for Local Player (%llu)\n", args[1], steamIDForPlayer.ConvertToUint64() ); |
|
|
|
GCSDK::CProtoBufMsg<CMsgDevDebugRollLootRequest> msg( k_EMsgGCDev_DebugRollLootRequest ); |
|
msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); |
|
msg.Body().set_loot_list_name( args[1] ); |
|
GCClientSystem()->BSendMessage( msg ); |
|
} |
|
|
|
#include "econ_item_description.h" |
|
#include "localization_provider.h" |
|
|
|
CON_COMMAND_F( item_generate_all_descriptions, "Generate full item descriptions for every item in your backpack. Meant as a code test.", FCVAR_CHEAT ) |
|
{ |
|
CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); |
|
|
|
for ( int i = 0; i < pInventory->GetItemCount(); i++ ) |
|
{ |
|
CEconItemDescription desc; |
|
IEconItemDescription::YieldingFillOutEconItemDescription( &desc, GLocalizationProvider(), pInventory->GetItem( i ) ); |
|
} |
|
|
|
Msg("Done.\n"); |
|
} |
|
#endif // CLIENT_DLL |
|
|
|
#endif // STAGING_ONLY || _DEBUG |
|
|
|
|
|
|