diff options
Diffstat (limited to 'game/shared/econ')
61 files changed, 45112 insertions, 0 deletions
diff --git a/game/shared/econ/attribute_manager.cpp b/game/shared/econ/attribute_manager.cpp new file mode 100644 index 0000000..9c142d3 --- /dev/null +++ b/game/shared/econ/attribute_manager.cpp @@ -0,0 +1,881 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "attribute_manager.h" +#include "gamestringpool.h" +#include "saverestore.h" +#include "saverestore_utlvector.h" +#include "fmtstr.h" +#include "KeyValues.h" +#include "econ_item_system.h" + +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + #include "tf_gamerules.h" // attribute cache flushing; can be generalized if/when Dota needs similar functionality +#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + +#define PROVIDER_PARITY_BITS 6 +#define PROVIDER_PARITY_MASK ((1<<PROVIDER_PARITY_BITS)-1) + +//================================================================================================================== +// ATTRIBUTE MANAGER SAVE/LOAD & NETWORKING +//=================================================================================================================== +BEGIN_DATADESC_NO_BASE( CAttributeManager ) + DEFINE_UTLVECTOR( m_Providers, FIELD_EHANDLE ), + DEFINE_UTLVECTOR( m_Receivers, FIELD_EHANDLE ), + DEFINE_FIELD( m_iReapplyProvisionParity, FIELD_INTEGER ), + DEFINE_FIELD( m_hOuter, FIELD_EHANDLE ), + // DEFINE_FIELD( m_bPreventLoopback, FIELD_BOOLEAN ), // Don't need to save + DEFINE_FIELD( m_ProviderType, FIELD_INTEGER ), +END_DATADESC() + +BEGIN_DATADESC( CAttributeContainer ) + DEFINE_EMBEDDED( m_Item ), +END_DATADESC() + +#ifndef DOTA_DLL +BEGIN_DATADESC( CAttributeContainerPlayer ) +END_DATADESC() +#endif + +#ifndef CLIENT_DLL +EXTERN_SEND_TABLE( DT_ScriptCreatedItem ); +#else +EXTERN_RECV_TABLE( DT_ScriptCreatedItem ); +#endif + +BEGIN_NETWORK_TABLE_NOBASE( CAttributeManager, DT_AttributeManager ) +#ifndef CLIENT_DLL + SendPropEHandle( SENDINFO(m_hOuter) ), + SendPropInt( SENDINFO(m_ProviderType), 4, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_iReapplyProvisionParity), PROVIDER_PARITY_BITS, SPROP_UNSIGNED ), +#else + RecvPropEHandle( RECVINFO(m_hOuter) ), + RecvPropInt( RECVINFO(m_ProviderType) ), + RecvPropInt( RECVINFO(m_iReapplyProvisionParity) ), +#endif +END_NETWORK_TABLE() + +BEGIN_NETWORK_TABLE_NOBASE( CAttributeContainer, DT_AttributeContainer ) +#ifndef CLIENT_DLL + SendPropEHandle( SENDINFO(m_hOuter) ), + SendPropInt( SENDINFO(m_ProviderType), 4, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_iReapplyProvisionParity), PROVIDER_PARITY_BITS, SPROP_UNSIGNED ), + SendPropDataTable(SENDINFO_DT(m_Item), &REFERENCE_SEND_TABLE(DT_ScriptCreatedItem)), +#else + RecvPropEHandle( RECVINFO(m_hOuter) ), + RecvPropInt( RECVINFO(m_ProviderType) ), + RecvPropInt( RECVINFO(m_iReapplyProvisionParity) ), + RecvPropDataTable(RECVINFO_DT(m_Item), 0, &REFERENCE_RECV_TABLE(DT_ScriptCreatedItem)), +#endif +END_NETWORK_TABLE() + +#ifndef DOTA_DLL +BEGIN_NETWORK_TABLE_NOBASE( CAttributeContainerPlayer, DT_AttributeContainerPlayer ) +#ifndef CLIENT_DLL + SendPropEHandle( SENDINFO(m_hOuter) ), + SendPropInt( SENDINFO(m_ProviderType), 4, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_iReapplyProvisionParity), PROVIDER_PARITY_BITS, SPROP_UNSIGNED ), + SendPropEHandle( SENDINFO(m_hPlayer) ), +#else + RecvPropEHandle( RECVINFO(m_hOuter) ), + RecvPropInt( RECVINFO(m_ProviderType) ), + RecvPropInt( RECVINFO(m_iReapplyProvisionParity) ), + RecvPropEHandle( RECVINFO( m_hPlayer ) ), +#endif +END_NETWORK_TABLE() +#endif + +template< class T > T AttributeConvertFromFloat( float flValue ) +{ + return static_cast<T>( flValue ); +} + +template<> float AttributeConvertFromFloat<float>( float flValue ) +{ + return flValue; +} + +template<> int AttributeConvertFromFloat<int>( float flValue ) +{ + return RoundFloatToInt( flValue ); +} + +//----------------------------------------------------------------------------- +// All fields in the object are all initialized to 0. +//----------------------------------------------------------------------------- +void *CAttributeManager::operator new( size_t stAllocateBlock ) +{ + // call into engine to get memory + Assert( stAllocateBlock != 0 ); + void *pMem = malloc( stAllocateBlock ); + memset( pMem, 0, stAllocateBlock ); + return pMem; +}; + +void *CAttributeManager::operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ) +{ + // call into engine to get memory + Assert( stAllocateBlock != 0 ); + void *pMem = malloc( stAllocateBlock ); + memset( pMem, 0, stAllocateBlock ); + return pMem; +} + +CAttributeManager::CAttributeManager() +{ + m_nCalls = 0; + m_nCurrentTick = 0; +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeManager::OnPreDataChanged( DataUpdateType_t updateType ) +{ + m_iOldReapplyProvisionParity = m_iReapplyProvisionParity; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeManager::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( m_iReapplyProvisionParity != m_iOldReapplyProvisionParity ) + { + // We've changed who we're providing to in some way. Reapply it. + IHasAttributes *pAttribInterface = GetAttribInterface( GetOuter() ); + if ( pAttribInterface ) + { + pAttribInterface->ReapplyProvision(); + } + + ClearCache(); + + m_iOldReapplyProvisionParity = m_iReapplyProvisionParity.Get(); + } +} +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Call this inside your entity's Spawn() +//----------------------------------------------------------------------------- +void CAttributeManager::InitializeAttributes( CBaseEntity *pEntity ) +{ + Assert( GetAttribInterface( pEntity ) ); + m_hOuter = pEntity; + m_bPreventLoopback = false; +} + +//===================================================================================================== +// ATTRIBUTE PROVIDERS +//===================================================================================================== +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeManager::ProvideTo( CBaseEntity *pProvider ) +{ + IHasAttributes *pOwnerAttribInterface = GetAttribInterface( pProvider ); + if ( pOwnerAttribInterface ) + { + pOwnerAttribInterface->GetAttributeManager()->AddProvider( m_hOuter.Get() ); + +#ifndef CLIENT_DLL + m_iReapplyProvisionParity = (m_iReapplyProvisionParity + 1) & PROVIDER_PARITY_MASK; + NetworkStateChanged(); +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeManager::StopProvidingTo( CBaseEntity *pProvider ) +{ + IHasAttributes *pOwnerAttribInterface = GetAttribInterface( pProvider ); + if ( pOwnerAttribInterface ) + { + pOwnerAttribInterface->GetAttributeManager()->RemoveProvider( m_hOuter.Get() ); +#ifndef CLIENT_DLL + m_iReapplyProvisionParity = (m_iReapplyProvisionParity + 1) & PROVIDER_PARITY_MASK; + NetworkStateChanged(); +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeManager::AddProvider( CBaseEntity *pProvider ) +{ + // Make sure he's not already in our list, and prevent circular provision + Assert( !IsBeingProvidedToBy(pProvider) ); + Assert( !IsProvidingTo(pProvider) ); + + // Ensure he's allowed to provide + IHasAttributes *pProviderAttrInterface = GetAttribInterface( pProvider ); + Assert( pProviderAttrInterface ); + + m_Providers.AddToTail( pProvider ); + pProviderAttrInterface->GetAttributeManager()->m_Receivers.AddToTail( GetOuter() ); + + ClearCache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeManager::RemoveProvider( CBaseEntity *pProvider ) +{ + Assert( pProvider ); + + IHasAttributes *pProviderAttrInterface = GetAttribInterface( pProvider ); + Assert( pProviderAttrInterface ); + + if ( !IsBeingProvidedToBy( pProvider ) ) + return; + + Assert( pProviderAttrInterface->GetAttributeManager()->IsProvidingTo( GetOuter() ) ); + Assert( pProviderAttrInterface->GetAttributeManager()->m_Receivers.Find( GetOuter() ) != pProviderAttrInterface->GetAttributeManager()->m_Receivers.InvalidIndex() ); + + m_Providers.FindAndFastRemove( pProvider ); + pProviderAttrInterface->GetAttributeManager()->m_Receivers.FindAndFastRemove( GetOuter() ); + + ClearCache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeManager::ClearCache( void ) +{ + if ( m_bPreventLoopback ) + return; + + m_CachedResults.Purge(); + + m_bPreventLoopback = true; + + // Tell all providers relying on me that they need to wipe their cache too + FOR_EACH_VEC( m_Receivers, i ) + { + IHasAttributes *pAttribInterface = GetAttribInterface( m_Receivers[i].Get() ); + if ( pAttribInterface ) + { + pAttribInterface->GetAttributeManager()->ClearCache(); + } + } + + // Tell our owner that he needs to clear his too, in case he has attributes affecting him + IHasAttributes *pMyAttribInterface = GetAttribInterface( m_hOuter.Get().Get() ); + if ( pMyAttribInterface ) + { + pMyAttribInterface->GetAttributeManager()->ClearCache(); + } + + m_bPreventLoopback = false; + +#ifndef CLIENT_DLL + // Force out client to clear their cache as well + m_iReapplyProvisionParity = (m_iReapplyProvisionParity + 1) & PROVIDER_PARITY_MASK; + NetworkStateChanged(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CAttributeManager::GetGlobalCacheVersion() const +{ +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + return TFGameRules() ? TFGameRules()->GetGlobalAttributeCacheVersion() : 0; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this entity is providing attributes to the specified entity +//----------------------------------------------------------------------------- +bool CAttributeManager::IsProvidingTo( CBaseEntity *pEntity ) const +{ + IHasAttributes *pAttribInterface = GetAttribInterface( pEntity ); + if ( pAttribInterface ) + { + if ( pAttribInterface->GetAttributeManager()->IsBeingProvidedToBy( GetOuter() ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this entity is being provided attributes by the specified entity +//----------------------------------------------------------------------------- +bool CAttributeManager::IsBeingProvidedToBy( CBaseEntity *pEntity ) const +{ + return ( m_Providers.Find( pEntity ) != m_Providers.InvalidIndex() ); +} + +//===================================================================================================== +// ATTRIBUTE HOOKS +//===================================================================================================== + +//----------------------------------------------------------------------------- +// Purpose: Wrapper that checks to see if we've already got the result in our cache +//----------------------------------------------------------------------------- +float CAttributeManager::ApplyAttributeFloatWrapper( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook, CUtlVector<CBaseEntity*> *pItemList ) +{ + VPROF_BUDGET( "CAttributeManager::ApplyAttributeFloatWrapper", VPROF_BUDGETGROUP_ATTRIBUTES ); + +#ifdef DEBUG + AssertMsg1( m_nCalls != 5000, "%d calls for attributes in a single tick. This is slow and bad.", m_nCalls ); + + if( m_nCurrentTick != gpGlobals->tickcount ) + { + m_nCalls = 0; + m_nCurrentTick = gpGlobals->tickcount; + } + + ++m_nCalls; +#endif + + // Have we requested a global attribute cache flush? + const int iGlobalCacheVersion = GetGlobalCacheVersion(); + if ( m_iCacheVersion != iGlobalCacheVersion ) + { + ClearCache(); + m_iCacheVersion = iGlobalCacheVersion; + } + + // We can't cache off item references so if we asked for them we need to execute the whole slow path. + if ( !pItemList ) + { + int iCount = m_CachedResults.Count(); + for ( int i = iCount-1; i >= 0; i-- ) + { + if ( m_CachedResults[i].iAttribHook == iszAttribHook ) + { + if ( m_CachedResults[i].in.fl == flValue ) + return m_CachedResults[i].out.fl; + + // We've got a cached result for a different flIn value. Remove the cached result to + // prevent stacking up entries for different requests (i.e. crit chance) + m_CachedResults.Remove(i); + break; + } + } + } + + // Wasn't in cache, or we need item references. Do the work. + float flResult = ApplyAttributeFloat( flValue, pInitiator, iszAttribHook, pItemList ); + + // Add it to our cache if we didn't ask for item references. We could add the result value here + // even if we did but we'd need to walk the cache to search for an old entry to overwrite first. + if ( !pItemList ) + { + int iIndex = m_CachedResults.AddToTail(); + m_CachedResults[iIndex].in.fl = flValue; + m_CachedResults[iIndex].out.fl = flResult; + m_CachedResults[iIndex].iAttribHook = iszAttribHook; + } + + return flResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Wrapper that checks to see if we've already got the result in our cache +//----------------------------------------------------------------------------- +string_t CAttributeManager::ApplyAttributeStringWrapper( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook, CUtlVector<CBaseEntity*> *pItemList /*= NULL*/ ) +{ + // Have we requested a global attribute cache flush? + const int iGlobalCacheVersion = GetGlobalCacheVersion(); + if ( m_iCacheVersion != iGlobalCacheVersion ) + { + ClearCache(); + m_iCacheVersion = iGlobalCacheVersion; + } + + // We can't cache off item references so if we asked for them we need to execute the whole slow path. + if ( !pItemList ) + { + int iCount = m_CachedResults.Count(); + for ( int i = iCount-1; i >= 0; i-- ) + { + if ( m_CachedResults[i].iAttribHook == iszAttribHook ) + { + if ( m_CachedResults[i].in.isz == iszValue ) + { + return m_CachedResults[i].out.isz; + } + + // We've got a cached result for a different flIn value. Remove the cached result to + // prevent stacking up entries for different requests (i.e. crit chance) + m_CachedResults.Remove(i); + break; + } + } + } + + // Wasn't in cache, or we need item references. Do the work. + string_t iszOut = ApplyAttributeString( iszValue, pInitiator, iszAttribHook, pItemList ); + + // Add it to our cache if we didn't ask for item references. We could add the result value here + // even if we did but we'd need to walk the cache to search for an old entry to overwrite first. + if ( !pItemList ) + { + int iIndex = m_CachedResults.AddToTail(); + m_CachedResults[iIndex].in.isz = iszValue; + m_CachedResults[iIndex].out.isz = iszOut; + m_CachedResults[iIndex].iAttribHook = iszAttribHook; + } + + return iszOut; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CAttributeManager::ApplyAttributeFloat( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook, CUtlVector<CBaseEntity*> *pItemList ) +{ + VPROF_BUDGET( "CAttributeManager::ApplyAttributeFloat", VPROF_BUDGETGROUP_ATTRIBUTES ); + + if ( m_bPreventLoopback || !GetOuter() ) + return flValue; + + // We need to prevent loopback between two items both providing to the same entity. + m_bPreventLoopback = true; + + IHasAttributes *pInitiatorAttribInterface = GetAttribInterface( pInitiator ); + + // See if we have any providers. If we do, tell them to apply. + FOR_EACH_VEC( m_Providers, iHook ) + { + CBaseEntity *pProvider = m_Providers[iHook].Get(); + + if ( !pProvider ) + continue; + + if ( pProvider == pInitiator ) + continue; + + IHasAttributes *pAttribInterface = GetAttribInterface( pProvider ); + Assert( pAttribInterface ); + + // Don't allow weapons to provide to other weapons being carried by the same person + if ( pInitiatorAttribInterface && + pAttribInterface->GetAttributeManager()->GetProviderType() == PROVIDER_WEAPON && + pInitiatorAttribInterface->GetAttributeManager()->GetProviderType() == PROVIDER_WEAPON ) + { + continue; + } + + flValue = pAttribInterface->GetAttributeManager()->ApplyAttributeFloat( flValue, pInitiator, iszAttribHook, pItemList ); + } + + // Then see if our owner has any attributes he wants to apply as well. + // i.e. An aura is providing attributes to this weapon's carrier. + IHasAttributes *pMyAttribInterface = GetAttribInterface( m_hOuter.Get().Get() ); + Assert( pMyAttribInterface ); + + if ( pMyAttribInterface && pMyAttribInterface->GetAttributeOwner() ) + { + IHasAttributes *pOwnerAttribInterface = GetAttribInterface( pMyAttribInterface->GetAttributeOwner() ); + if ( pOwnerAttribInterface ) + { + flValue = pOwnerAttribInterface->GetAttributeManager()->ApplyAttributeFloat( flValue, pInitiator, iszAttribHook, pItemList ); + } + } + + m_bPreventLoopback = false; + + return flValue; +} + +string_t CAttributeManager::ApplyAttributeString( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook /*= NULL_STRING*/, CUtlVector<CBaseEntity*> *pItemList /*= NULL*/ ) +{ + VPROF_BUDGET( "CAttributeManager::ApplyAttributeString", VPROF_BUDGETGROUP_ATTRIBUTES ); + + if ( m_bPreventLoopback || !GetOuter() ) + return iszValue; + + // We need to prevent loopback between two items both providing to the same entity. + m_bPreventLoopback = true; + + IHasAttributes *pInitiatorAttribInterface = GetAttribInterface( pInitiator ); + + // See if we have any providers. If we do, tell them to apply. + FOR_EACH_VEC( m_Providers, iHook ) + { + CBaseEntity *pProvider = m_Providers[iHook].Get(); + + if ( !pProvider ) + continue; + + if ( pProvider == pInitiator ) + continue; + + IHasAttributes *pAttribInterface = GetAttribInterface( pProvider ); + Assert( pAttribInterface ); + + // Don't allow weapons to provide to other weapons being carried by the same person + if ( pInitiatorAttribInterface && + pAttribInterface->GetAttributeManager()->GetProviderType() == PROVIDER_WEAPON && + pInitiatorAttribInterface->GetAttributeManager()->GetProviderType() == PROVIDER_WEAPON ) + { + continue; + } + + iszValue = pAttribInterface->GetAttributeManager()->ApplyAttributeString( iszValue, pInitiator, iszAttribHook, pItemList ); + } + + // Then see if our owner has any attributes he wants to apply as well. + // i.e. An aura is providing attributes to this weapon's carrier. + IHasAttributes *pMyAttribInterface = GetAttribInterface( m_hOuter.Get().Get() ); + Assert( pMyAttribInterface ); + + if ( pMyAttribInterface->GetAttributeOwner() ) + { + IHasAttributes *pOwnerAttribInterface = GetAttribInterface( pMyAttribInterface->GetAttributeOwner() ); + if ( pOwnerAttribInterface ) + { + iszValue = pOwnerAttribInterface->GetAttributeManager()->ApplyAttributeString( iszValue, pInitiator, iszAttribHook, pItemList ); + } + } + + m_bPreventLoopback = false; + + return iszValue; +} + +//===================================================================================================== +// ATTRIBUTE CONTAINER +//===================================================================================================== + +//----------------------------------------------------------------------------- +// Purpose: Call this inside your entity's Spawn() +//----------------------------------------------------------------------------- +void CAttributeContainer::InitializeAttributes( CBaseEntity *pEntity ) +{ + BaseClass::InitializeAttributes( pEntity ); + +#ifndef CLIENT_DLL + /* + if ( !m_Item.IsValid() ) + { + Warning("Item '%s' not setup correctly. Attempting to create attributes on an unitialized item.\n", m_hOuter.Get()->GetDebugName() ); + } + */ +#endif + + m_Item.GetAttributeList()->SetManager( this ); + + OnAttributeValuesChanged(); +} + +static void ApplyAttribute( const CEconItemAttributeDefinition *pAttributeDef, float& flValue, const float flValueModifier ) +{ + Assert( pAttributeDef ); + Assert( pAttributeDef->GetAttributeType() ); + AssertMsg1( pAttributeDef->GetAttributeType()->BSupportsGameplayModificationAndNetworking(), "Attempt to hook the value of attribute '%s' which doesn't support hooking! Pull the value of the attribute directly using FindAttribute()!", pAttributeDef->GetDefinitionName() ); + + const int iAttrDescFormat = pAttributeDef->GetDescriptionFormat(); + + switch ( iAttrDescFormat ) + { + case ATTDESCFORM_VALUE_IS_PERCENTAGE: + case ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE: + { + flValue *= flValueModifier; + } + break; + + case ATTDESCFORM_VALUE_IS_ADDITIVE: + case ATTDESCFORM_VALUE_IS_ADDITIVE_PERCENTAGE: + case ATTDESCFORM_VALUE_IS_PARTICLE_INDEX: + { + flValue += flValueModifier; + } + break; + + case ATTDESCFORM_VALUE_IS_KILLSTREAK_IDLEEFFECT_INDEX: + case ATTDESCFORM_VALUE_IS_KILLSTREAKEFFECT_INDEX: + case ATTDESCFORM_VALUE_IS_FROM_LOOKUP_TABLE: + { + flValue = flValueModifier; + } + break; + + case ATTDESCFORM_VALUE_IS_OR: + { + int iTmp = flValue; + iTmp |= (int)flValueModifier; + flValue = iTmp; + } + break; + + case ATTDESCFORM_VALUE_IS_DATE: + Assert( !"Attempt to apply date attribute in ApplyAttribute()." ); // No-one should be hooking date descriptions + break; + + default: + // Unknown value format. + AssertMsg1( false, "Unknown attribute value type %i in ApplyAttribute().", iAttrDescFormat ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Given two attributes, return a collated value. +//----------------------------------------------------------------------------- +float CollateAttributeValues( const CEconItemAttributeDefinition *pAttrDef1, const float flAttribValue1, const CEconItemAttributeDefinition *pAttrDef2, const float flAttribValue2 ) +{ + Assert( pAttrDef1 ); + Assert( pAttrDef2 ); + AssertMsg2( !Q_stricmp( pAttrDef1->GetAttributeClass(), pAttrDef2->GetAttributeClass() ), "We can only collate attributes of matching definitions: mismatch between '%s' / '%s'!", pAttrDef1->GetAttributeClass(), pAttrDef2->GetAttributeClass() ); + AssertMsg2( pAttrDef1->GetDescriptionFormat() == pAttrDef2->GetDescriptionFormat(), "We can only collate attributes of matching description format: mismatch between '%u' / '%u'!", pAttrDef1->GetDescriptionFormat(), pAttrDef2->GetDescriptionFormat() ); + + const int iAttrDescFormat = pAttrDef1->GetDescriptionFormat(); + + float flValue = 0; + switch ( iAttrDescFormat ) + { + case ATTDESCFORM_VALUE_IS_PERCENTAGE: + case ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE: + { + flValue = 1.0; + } + break; + + case ATTDESCFORM_VALUE_IS_ADDITIVE: + case ATTDESCFORM_VALUE_IS_ADDITIVE_PERCENTAGE: + case ATTDESCFORM_VALUE_IS_FROM_LOOKUP_TABLE: + case ATTDESCFORM_VALUE_IS_OR: + { + flValue = 0; + } + break; + + case ATTDESCFORM_VALUE_IS_DATE: + Assert( !"Attempt to apply date attribute in CollateAttributeValues()." ); // No-one should be hooking date descriptions + break; + + default: + // Unknown value format. + AssertMsg1( false, "Unknown attribute value type %i in ApplyAttribute().", iAttrDescFormat ); + break; + } + + ApplyAttribute( pAttrDef1, flValue, flAttribValue1 ); + ApplyAttribute( pAttrDef2, flValue, flAttribValue2 ); + + return flValue; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconItemAttributeIterator_ApplyAttributeFloat : public CEconItemSpecificAttributeIterator +{ +public: + CEconItemAttributeIterator_ApplyAttributeFloat( CBaseEntity *pOuter, float flInitialValue, string_t iszAttribHook, CUtlVector<CBaseEntity *> *pItemList ) + : m_pOuter( pOuter ) + , m_flValue( flInitialValue ) + , m_iszAttribHook( iszAttribHook ) + , m_pItemList( pItemList ) + { + Assert( pOuter ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) + { + COMPILE_TIME_ASSERT( sizeof( value ) == sizeof( float ) ); + + Assert( pAttrDef ); + + if ( pAttrDef->GetCachedClass() != m_iszAttribHook ) + return true; + + if ( m_pItemList && !m_pItemList->HasElement( m_pOuter ) ) + { + m_pItemList->AddToTail( m_pOuter ); + } + + ApplyAttribute( pAttrDef, m_flValue, *reinterpret_cast<float *>( &value ) ); + + // We assume that each attribute can only be in the attribute list for a single item once, but we're + // iterating over attribute *classes* here, not unique attribute types, so we carry on looking. + return true; + } + + float GetResultValue() const + { + return m_flValue; + } + +private: + CBaseEntity *m_pOuter; + float m_flValue; + string_t m_iszAttribHook; + CUtlVector<CBaseEntity *> *m_pItemList; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CAttributeContainer::ApplyAttributeFloat( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook, CUtlVector<CBaseEntity*> *pItemList ) +{ + if ( m_bPreventLoopback || !GetOuter() ) + return flValue; + + // We need to prevent loopback between two items both providing to the same entity. + m_bPreventLoopback = true; + + // ... + CEconItemAttributeIterator_ApplyAttributeFloat it( GetOuter(), flValue, iszAttribHook, pItemList ); + m_Item.IterateAttributes( &it ); + + m_bPreventLoopback = false; + + return BaseClass::ApplyAttributeFloat( it.GetResultValue(), pInitiator, iszAttribHook, pItemList ); +} + +#ifndef DOTA_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CAttributeContainerPlayer::ApplyAttributeFloat( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook, CUtlVector<CBaseEntity*> *pItemList ) +{ + if ( m_bPreventLoopback || !GetOuter() ) + return flValue; + + m_bPreventLoopback = true; + + CEconItemAttributeIterator_ApplyAttributeFloat it( GetOuter(), flValue, iszAttribHook, pItemList ); + + CBasePlayer *pPlayer = GetPlayer(); + if ( pPlayer ) + { + pPlayer->m_AttributeList.IterateAttributes( &it ); + } + + m_bPreventLoopback = false; + + return BaseClass::ApplyAttributeFloat( it.GetResultValue(), pInitiator, iszAttribHook, pItemList ); +} + + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconItemAttributeIterator_ApplyAttributeString : public CEconItemSpecificAttributeIterator +{ +public: + CEconItemAttributeIterator_ApplyAttributeString( CBaseEntity *pOuter, string_t iszInitialValue, string_t iszAttribHook, CUtlVector<CBaseEntity *> *pItemList ) + : m_pOuter( pOuter ) + , m_iszValue( iszInitialValue ) + , m_iszAttribHook( iszAttribHook ) + , m_pItemList( pItemList ) + , m_bFoundString( false ) + { + Assert( pOuter ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) + { + COMPILE_TIME_ASSERT( sizeof( value ) == sizeof( float ) ); + + // Do we want to process attribute of this type? + Assert( pAttrDef ); + Assert( pAttrDef->GetCachedClass() != m_iszAttribHook ); + //AssertMsg( 0, "OnIterateAttributeValue of type CAttribute_String, we shouldn't get here." ); + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& value ) + { + Assert( pAttrDef ); + + if ( pAttrDef->GetCachedClass() != m_iszAttribHook ) + return true; + + if ( FoundString() ) + return true; + + m_iszValue = AllocPooledString( value.value().c_str() ); + + m_bFoundString = true; + + return true; + } + + string_t GetResultValue() + { + return m_iszValue; + } + +private: + bool FoundString() + { + // Implement something for the case where there's more than one of the same attribute + AssertMsg( !m_bFoundString, "Already found a string attribute with %s class, return the first attribute found.", STRING( m_iszAttribHook ) ); + + return m_bFoundString; + } + + CBaseEntity *m_pOuter; + string_t m_iszValue; + string_t m_iszAttribHook; + CUtlVector<CBaseEntity *> *m_pItemList; + bool m_bFoundString; +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +string_t CAttributeContainer::ApplyAttributeString( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook /*= NULL_STRING*/, CUtlVector<CBaseEntity*> *pItemList /*= NULL*/ ) +{ + if ( m_bPreventLoopback || !GetOuter() ) + return iszValue; + + // We need to prevent loopback between two items both providing to the same entity. + m_bPreventLoopback = true; + + // ... + CEconItemAttributeIterator_ApplyAttributeString it( GetOuter(), iszValue, iszAttribHook, pItemList ); + m_Item.IterateAttributes( &it ); + + m_bPreventLoopback = false; + + return BaseClass::ApplyAttributeString( it.GetResultValue(), pInitiator, iszAttribHook, pItemList ); +} + + +string_t CAttributeContainerPlayer::ApplyAttributeString( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook /*= NULL_STRING*/, CUtlVector<CBaseEntity*> *pItemList /*= NULL*/ ) +{ + if ( m_bPreventLoopback || !GetOuter() ) + return iszValue; + + m_bPreventLoopback = true; + + CEconItemAttributeIterator_ApplyAttributeString it( GetOuter(), iszValue, iszAttribHook, pItemList ); + + CBasePlayer *pPlayer = GetPlayer(); + if ( pPlayer ) + { + pPlayer->m_AttributeList.IterateAttributes( &it ); + } + + m_bPreventLoopback = false; + + return BaseClass::ApplyAttributeString( it.GetResultValue(), pInitiator, iszAttribHook, pItemList ); +} diff --git a/game/shared/econ/attribute_manager.h b/game/shared/econ/attribute_manager.h new file mode 100644 index 0000000..eb83675 --- /dev/null +++ b/game/shared/econ/attribute_manager.h @@ -0,0 +1,301 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Attributable entities contain one of these, which handles game specific handling: +// - Save / Restore +// - Networking +// - Attribute providers +// - Application of attribute effects +// +//============================================================================= + +#ifndef ATTRIBUTE_MANAGER_H +#define ATTRIBUTE_MANAGER_H +#ifdef _WIN32 +#pragma once +#endif + +#include "econ_item_view.h" +#include "ihasattributes.h" +#include "tf_gcmessages.h" + +// Provider types +enum attributeprovidertypes_t +{ + PROVIDER_GENERIC, + PROVIDER_WEAPON, +}; + +float CollateAttributeValues( const CEconItemAttributeDefinition *pAttrDef1, const float flAttribValue1, const CEconItemAttributeDefinition *pAttrDef2, const float flAttribValue2 ); + +// Retrieve the IHasAttributes pointer from a Base Entity. This function checks for NULL entities +// and asserts the return value is == to dynamic_cast< IHasAttributes * >( pEntity ). +inline IHasAttributes *GetAttribInterface( CBaseEntity *pEntity ) +{ + IHasAttributes *pAttribInterface = pEntity ? pEntity->GetHasAttributesInterfacePtr() : NULL; + // If this assert hits it most likely means that m_pAttribInterface has not been set + // in the leaf class constructor for this object. See CTFPlayer::CTFPlayer() for an + // example. + Assert( pAttribInterface == dynamic_cast< IHasAttributes *>( pEntity ) ); + return pAttribInterface; +} + +//----------------------------------------------------------------------------- +// Macros for hooking the application of attributes +#define CALL_ATTRIB_HOOK( vartype, retval, hookName, who, itemlist ) \ + retval = CAttributeManager::AttribHookValue<vartype>( retval, #hookName, static_cast<const CBaseEntity*>( who ), itemlist, true ); + +#define CALL_ATTRIB_HOOK_INT( retval, hookName ) CALL_ATTRIB_HOOK( int, retval, hookName, this, NULL ) +#define CALL_ATTRIB_HOOK_FLOAT( retval, hookName ) CALL_ATTRIB_HOOK( float, retval, hookName, this, NULL ) +#define CALL_ATTRIB_HOOK_STRING( retval, hookName ) CALL_ATTRIB_HOOK( CAttribute_String, retval, hookName, this, NULL ) +#define CALL_ATTRIB_HOOK_INT_ON_OTHER( other, retval, hookName ) CALL_ATTRIB_HOOK( int, retval, hookName, other, NULL ) +#define CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( other, retval, hookName ) CALL_ATTRIB_HOOK( float, retval, hookName, other, NULL ) +#define CALL_ATTRIB_HOOK_STRING_ON_OTHER( other, retval, hookName ) CALL_ATTRIB_HOOK( CAttribute_String, retval, hookName, other, NULL ) +#define CALL_ATTRIB_HOOK_INT_ON_OTHER_WITH_ITEMS( other, retval, items_array, hookName ) CALL_ATTRIB_HOOK( int, retval, hookName, other, items_array ) +#define CALL_ATTRIB_HOOK_FLOAT_ON_OTHER_WITH_ITEMS( other, retval, items_array, hookName ) CALL_ATTRIB_HOOK( float, retval, hookName, other, items_array ) +#define CALL_ATTRIB_HOOK_STRING_ON_OTHER_WITH_ITEMS( other, retval, items_array, hookName ) CALL_ATTRIB_HOOK( CAttribute_String, retval, hookName, other, items_array ) + +template< class T > T AttributeConvertFromFloat( float flValue ); +template<> float AttributeConvertFromFloat<float>( float flValue ); +template<> int AttributeConvertFromFloat<int>( float flValue ); + +//----------------------------------------------------------------------------- +// Purpose: Base Attribute manager. +// This class knows how to apply attribute effects that have been +// provided to its owner by other entities, but doesn't contain attributes itself. +//----------------------------------------------------------------------------- +class CAttributeManager +{ + DECLARE_CLASS_NOBASE( CAttributeManager ); +public: + DECLARE_DATADESC(); + DECLARE_EMBEDDED_NETWORKVAR(); + + CAttributeManager(); + virtual ~CAttributeManager() {} + + // Call this inside your entity's Spawn() + virtual void InitializeAttributes( CBaseEntity *pEntity ); + + CBaseEntity *GetOuter( void ) const { return m_hOuter.Get(); } + + //-------------------------------------------------------- + // Attribute providers. + // Other entities that are providing attributes to this entity (i.e. weapons being carried by a player) + void ProvideTo( CBaseEntity *pProvider ); + void StopProvidingTo( CBaseEntity *pProvider ); + +protected: + // Not to be called directly. Use ProvideTo() or StopProvidingTo() above. + void AddProvider( CBaseEntity *pProvider ); + void RemoveProvider( CBaseEntity *pProvider ); + +public: + // Return true if this entity is providing attributes to the specified entity + bool IsProvidingTo( CBaseEntity *pEntity ) const; + + // Return true if this entity is being provided attributes by the specified entity + bool IsBeingProvidedToBy( CBaseEntity *pEntity ) const; + + // Provider types are used to prevent specified providers supplying to certain initiators + void SetProviderType( attributeprovidertypes_t tType ) { m_ProviderType = tType; } + attributeprovidertypes_t GetProviderType( void ) const { return m_ProviderType; } + + //-------------------------------------------------------- + // Attribute hook. Use the CALL_ATTRIB_HOOK macros above. + template <class T> static T AttribHookValue( T TValue, const char *pszAttribHook, const CBaseEntity *pEntity, CUtlVector<CBaseEntity*> *pItemList = NULL, bool bIsGlobalConstString = false ) + { + VPROF_BUDGET( "CAttributeManager::AttribHookValue", VPROF_BUDGETGROUP_ATTRIBUTES ); + + // Do we have a hook? + if ( pszAttribHook == NULL || pszAttribHook[0] == '\0' ) + return TValue; + + // Verify that we have an entity, at least as "this" + if ( pEntity == NULL ) + return TValue; + + IHasAttributes *pAttribInterface = GetAttribInterface( (CBaseEntity*) pEntity ); + AssertMsg( pAttribInterface, "If you hit this, you've probably got a hook incorrectly setup, because the entity it's hooking on doesn't know about attributes." ); + if ( pAttribInterface == NULL ) + return TValue; + + // Hook base attribute. + T Scratch; + AttribHookValueInternal( Scratch, TValue, pszAttribHook, pEntity, pAttribInterface, pItemList, bIsGlobalConstString ); + + return Scratch; + } + +private: + template <class T> static void TypedAttribHookValueInternal( T& out, T TValue, string_t iszAttribHook, const CBaseEntity *pEntity, IHasAttributes *pAttribInterface, CUtlVector<CBaseEntity*> *pItemList ) + { + float flValue = pAttribInterface->GetAttributeManager()->ApplyAttributeFloatWrapper( static_cast<float>( TValue ), const_cast<CBaseEntity *>( pEntity ), iszAttribHook, pItemList ); + + out = AttributeConvertFromFloat<T>( flValue ); + } + + static void TypedAttribHookValueInternal( CAttribute_String& out, const CAttribute_String& TValue, string_t iszAttribHook, const CBaseEntity *pEntity, IHasAttributes *pAttribInterface, CUtlVector<CBaseEntity*> *pItemList ) + { + string_t iszIn = AllocPooledString( TValue.value().c_str() ); + string_t iszOut = pAttribInterface->GetAttributeManager()->ApplyAttributeStringWrapper( iszIn, const_cast<CBaseEntity *>( pEntity ), iszAttribHook, pItemList ); + const char* pszOut = STRING( iszOut ); + // STRING() returns different value for server and client + // server will return "" for NULL_STRING + // client will return NULL for NULL_STRING + if ( pszOut ) + { + out.set_value( pszOut ); + } + else + { + out.set_value( "" ); + } + } + + template <class T> static void AttribHookValueInternal( T& out, T TValue, const char *pszAttribHook, const CBaseEntity *pEntity, IHasAttributes *pAttribInterface, CUtlVector<CBaseEntity*> *pItemList, bool bIsGlobalConstString ) + { + Assert( pszAttribHook ); + Assert( pszAttribHook[0] ); + Assert( pEntity ); + Assert( pAttribInterface ); + Assert( GetAttribInterface( (CBaseEntity*) pEntity ) == pAttribInterface ); + Assert( pAttribInterface->GetAttributeManager() ); + + string_t iszAttribHook = bIsGlobalConstString ? AllocPooledString_StaticConstantStringPointer( pszAttribHook ) : AllocPooledString( pszAttribHook ); + return TypedAttribHookValueInternal( out, TValue, iszAttribHook, pEntity, pAttribInterface, pItemList ); + } + int m_nCurrentTick; + int m_nCalls; + +public: + virtual float ApplyAttributeFloat( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook = NULL_STRING, CUtlVector<CBaseEntity*> *pItemList = NULL ); + virtual string_t ApplyAttributeString( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook = NULL_STRING, CUtlVector<CBaseEntity*> *pItemList = NULL ); + + //-------------------------------------------------------- + // Networking +#ifdef CLIENT_DLL + virtual void OnPreDataChanged( DataUpdateType_t updateType ); + virtual void OnDataChanged( DataUpdateType_t updateType ); +#endif + + //-------------------------------------------------------- + // memory handling + void *operator new( size_t stAllocateBlock ); + void *operator new( size_t stAllocateBlock, int nBlockUse, const char *pFileName, int nLine ); + +protected: + CUtlVector<EHANDLE> m_Providers; // entities that we receive attribute data *from* + CUtlVector<EHANDLE> m_Receivers; // entities that we provide attribute data *to* + CNetworkVarForDerived( int, m_iReapplyProvisionParity ); + CNetworkVarForDerived( EHANDLE, m_hOuter ); + bool m_bPreventLoopback; + CNetworkVarForDerived( attributeprovidertypes_t, m_ProviderType ); + int m_iCacheVersion; // maps to gamerules counter for global cache flushing + +public: + virtual void OnAttributeValuesChanged() + { + ClearCache(); + } + +private: + void ClearCache(); + int GetGlobalCacheVersion() const; + + virtual float ApplyAttributeFloatWrapper( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook, CUtlVector<CBaseEntity*> *pItemList = NULL ); + virtual string_t ApplyAttributeStringWrapper( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook, CUtlVector<CBaseEntity*> *pItemList = NULL ); + + // Cached attribute results + // We cache off requests for data, and wipe the cache whenever our providers change. + union cached_attribute_types + { + float fl; + string_t isz; + }; + + struct cached_attribute_t + { + string_t iAttribHook; + cached_attribute_types in; + cached_attribute_types out; + }; + CUtlVector<cached_attribute_t> m_CachedResults; + +#ifdef CLIENT_DLL +public: + // Data received from the server + int m_iOldReapplyProvisionParity; +#endif +}; + +//----------------------------------------------------------------------------- +// Purpose: This is an attribute manager that also knows how to contain attributes. +//----------------------------------------------------------------------------- +class CAttributeContainer : public CAttributeManager +{ +public: + DECLARE_DATADESC(); + DECLARE_CLASS( CAttributeContainer, CAttributeManager ); + DECLARE_EMBEDDED_NETWORKVAR(); + + virtual void InitializeAttributes( CBaseEntity *pEntity ); + + //-------------------------------------------------------- + // Attribute hook. Use the CALL_ATTRIB_HOOK macros above. + virtual float ApplyAttributeFloat( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook = NULL_STRING, CUtlVector<CBaseEntity*> *pItemList = NULL ) OVERRIDE; + virtual string_t ApplyAttributeString( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook = NULL_STRING, CUtlVector<CBaseEntity*> *pItemList = NULL ) OVERRIDE; + + CEconItemView *GetItem( void ) { return &m_Item; } + const CEconItemView *GetItem( void ) const { return &m_Item; } + void SetItem( const CEconItemView *pItem ) { m_Item.CopyFrom( *pItem ); } + + virtual void OnAttributeValuesChanged() + { + BaseClass::OnAttributeValuesChanged(); + + m_Item.OnAttributeValuesChanged(); + } + +private: + CNetworkVarEmbedded( CEconItemView, m_Item ); +}; + +//----------------------------------------------------------------------------- +// Purpose: An attribute manager that uses a player's shared attributes. +//----------------------------------------------------------------------------- + +#ifndef DOTA_DLL +class CAttributeContainerPlayer : public CAttributeManager +{ +public: + DECLARE_DATADESC(); + DECLARE_CLASS( CAttributeContainerPlayer, CAttributeManager ); + DECLARE_EMBEDDED_NETWORKVAR(); + + virtual float ApplyAttributeFloat( float flValue, CBaseEntity *pInitiator, string_t iszAttribHook = NULL_STRING, CUtlVector<CBaseEntity*> *pItemList = NULL ) OVERRIDE; + virtual string_t ApplyAttributeString( string_t iszValue, CBaseEntity *pInitiator, string_t iszAttribHook = NULL_STRING, CUtlVector<CBaseEntity*> *pItemList = NULL ) OVERRIDE; + + CBasePlayer* GetPlayer( void ) { return m_hPlayer; } + void SetPlayer( CBasePlayer *pPlayer ) { m_hPlayer = pPlayer; } + + virtual void OnAttributeValuesChanged() + { + BaseClass::OnAttributeValuesChanged(); + + m_hPlayer->NetworkStateChanged(); + } + +private: + CNetworkHandle( CBasePlayer, m_hPlayer ); +}; +#endif + +#ifdef CLIENT_DLL +EXTERN_RECV_TABLE( DT_AttributeManager ); +EXTERN_RECV_TABLE( DT_AttributeContainer ); +#else +EXTERN_SEND_TABLE( DT_AttributeManager ); +EXTERN_SEND_TABLE( DT_AttributeContainer ); +#endif + +#endif // ATTRIBUTE_MANAGER_H diff --git a/game/shared/econ/econ_claimcode.cpp b/game/shared/econ/econ_claimcode.cpp new file mode 100644 index 0000000..94f15a9 --- /dev/null +++ b/game/shared/econ/econ_claimcode.cpp @@ -0,0 +1,126 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Code for the CEconClaimCode object +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "econ_item_tools.h" +#include "econ_claimcode.h" + +using namespace GCSDK; + +#ifdef GC +IMPLEMENT_CLASS_MEMPOOL( CEconClaimCode, 10 * 1000, UTLMEMORYPOOL_GROW_SLOW ); + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +bool CEconClaimCode::BYieldingAddInsertToTransaction( GCSDK::CSQLAccess & sqlAccess ) +{ + CSchAssignedClaimCode schCode; + WriteToRecord( &schCode ); + return CSchemaSharedObjectHelper::BYieldingAddInsertToTransaction( sqlAccess, &schCode ); +} + +void CEconClaimCode::WriteToRecord( CSchAssignedClaimCode *pClaimCode ) +{ + pClaimCode->m_unAccountID = Obj().account_id(); + pClaimCode->m_unCodeType = Obj().code_type(); + pClaimCode->m_rtime32TimeAcquired = Obj().time_acquired(); + WRITE_VAR_CHAR_FIELD( *pClaimCode, VarCharCode, Obj().code().c_str() ); +} + +void CEconClaimCode::ReadFromRecord( const CSchAssignedClaimCode & code ) +{ + const char *pchCode = READ_VAR_CHAR_FIELD( code, m_VarCharCode ); + Obj().set_code_type( code.m_unCodeType ); + Obj().set_time_acquired( code.m_rtime32TimeAcquired ); + Obj().set_code( pchCode ); +} + + +bool BBuildRedemptionURL( CEconClaimCode *pClaimCode, CUtlString &redemptionURL ) +{ + const CEconItemDefinition *pItemDef = GEconManager()->GetItemSchema()->GetItemDefinition( pClaimCode->Obj().code_type() ); + if ( pItemDef ) + { + const char *pOriginalURL = pItemDef->GetDefinitionString( "redeem_url" ); + const char *code = pClaimCode->Obj().code().c_str(); + char url[1024]; + if ( Q_StrSubst( pOriginalURL, "CLAIMCODE", code, url, sizeof( url ) ) ) + { + redemptionURL = url; + return true; + } + } + return false; +} + +// ----------------------------------------------------------------------------- +// Purpose: Gets a summary of what's going on with the GC +// ----------------------------------------------------------------------------- +class CJobWG_GetPromoCodes : public CGCGameBaseWGJob +{ +public: + CJobWG_GetPromoCodes( CGCGameBase *pGC ) : CGCGameBaseWGJob( pGC ) {} + + virtual bool BYieldingRunJobFromRequest( KeyValues *pkvRequest, KeyValues *pkvResponse ); + +private: +}; + + +bool CJobWG_GetPromoCodes::BYieldingRunJobFromRequest( KeyValues *pkvRequest, KeyValues *pkvResponse ) +{ + CSteamID actorID( pkvRequest->GetUint64( "token/steamid" ) ); + + KeyValues *pkvPromoCodes = pkvResponse->FindKey( "promo_codes", true ); + + CSharedObjectCache *pSOCache = m_pGCGameBase->YieldingFindOrLoadSOCache( actorID ); + if ( pSOCache == NULL ) + return true; + + CSharedObjectTypeCache *pTypeCache = pSOCache->FindBaseTypeCache( k_EEconTypeClaimCode ); + if ( pTypeCache == NULL ) + return true; + + for ( uint32 i = 0; i < pTypeCache->GetCount(); ++i ) + { + CEconClaimCode *pClaimCode = (CEconClaimCode*)pTypeCache->GetObject( i ); + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( pClaimCode->Obj().code_type() ); + if ( pItemDef == NULL ) + continue; + + const CEconTool_ClaimCode *pEconClaimCodeTool = pItemDef->GetTypedEconTool<CEconTool_ClaimCode>(); + if ( pEconClaimCodeTool == NULL ) + continue; + + const char *pClaimCodeName = pEconClaimCodeTool->GetClaimType(); + if ( pClaimCodeName == NULL ) + continue; + + CUtlString claimURL; + if ( BBuildRedemptionURL( pClaimCode, claimURL ) == false ) + { + SetErrorMessage( pkvResponse, CFmtStr( "Unable to construct redemption url for: %s", pClaimCodeName ), k_EResultFail ); + continue; + } + const char *code = pClaimCode->Obj().code().c_str(); + // finally populate the key values + KeyValues *pkvPromoCode = pkvPromoCodes->CreateNewKey(); + pkvPromoCode->SetString( "code_name", pClaimCodeName ); + pkvPromoCode->SetInt( "timestamp", pClaimCode->Obj().time_acquired() ); + pkvPromoCode->SetString( "code", code ); + pkvPromoCode->SetString( "redeem_url", claimURL.Get() ); + } + + return true; +} + +DECLARE_GCWG_JOB( CGCEcon, CJobWG_GetPromoCodes, "GetPromoCodes", k_EGCWebApiPriv_Session ) +END_DECLARE_GCWG_JOB( CJobWG_GetPromoCodes); + +#endif diff --git a/game/shared/econ/econ_claimcode.h b/game/shared/econ/econ_claimcode.h new file mode 100644 index 0000000..06b85f6 --- /dev/null +++ b/game/shared/econ/econ_claimcode.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds the CEconClaimCode object +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ECON_CLAIMCODE_H +#define ECON_CLAIMCODE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gcsdk/protobufsharedobject.h" + +//--------------------------------------------------------------------------------- +// Purpose: All the account-level information that the GC tracks for TF +//--------------------------------------------------------------------------------- +class CEconClaimCode : public GCSDK::CProtoBufSharedObject< CSOEconClaimCode, k_EEconTypeClaimCode > +{ +#ifdef GC + DECLARE_CLASS_MEMPOOL( CEconClaimCode ); +#endif + +public: + +#ifdef GC + virtual bool BYieldingAddInsertToTransaction( GCSDK::CSQLAccess & sqlAccess ); + + void WriteToRecord( CSchAssignedClaimCode *pClaimCode ); + void ReadFromRecord( const CSchAssignedClaimCode & mapContribution ); +#endif +}; + +#ifdef GC +bool BBuildRedemptionURL( CEconClaimCode *pClaimCode, CUtlString &redemptionURL ); +#endif + +#endif // ECON_CLAIMCODE_H + diff --git a/game/shared/econ/econ_contribution.cpp b/game/shared/econ/econ_contribution.cpp new file mode 100644 index 0000000..25dc060 --- /dev/null +++ b/game/shared/econ/econ_contribution.cpp @@ -0,0 +1,64 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Code for the CTFMapContribution object +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "econ_contribution.h" + +using namespace GCSDK; + +#ifdef GC +IMPLEMENT_CLASS_MEMPOOL( CTFMapContribution, 10 * 1000, UTLMEMORYPOOL_GROW_SLOW ); + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +bool CTFMapContribution::BYieldingAddInsertToTransaction( GCSDK::CSQLAccess & sqlAccess ) +{ + CSchMapContribution schMapContribution; + WriteToRecord( &schMapContribution ); + return CSchemaSharedObjectHelper::BYieldingAddInsertToTransaction( sqlAccess, &schMapContribution ); +} + +bool CTFMapContribution::BYieldingAddWriteToTransaction( GCSDK::CSQLAccess & sqlAccess, const CUtlVector< int > &fields ) +{ + CSchMapContribution schMapContribution; + WriteToRecord( &schMapContribution ); + CColumnSet csDatabaseDirty( schMapContribution.GetPSchema()->GetRecordInfo() ); + csDatabaseDirty.MakeEmpty(); + if ( fields.HasElement( CSOTFMapContribution::kContributionLevelFieldNumber ) ) + { + csDatabaseDirty.BAddColumn( CSchMapContribution::k_iField_unContributionLevel ); + } + return CSchemaSharedObjectHelper::BYieldingAddWriteToTransaction( sqlAccess, &schMapContribution, csDatabaseDirty ); +} + +bool CTFMapContribution::BYieldingAddRemoveToTransaction( GCSDK::CSQLAccess & sqlAccess ) +{ + CSchMapContribution schMapContribution; + WriteToRecord( &schMapContribution ); + return CSchemaSharedObjectHelper::BYieldingAddRemoveToTransaction( sqlAccess, &schMapContribution ); +} + +void CTFMapContribution::WriteToRecord( CSchMapContribution *pMapContribution ) const +{ + pMapContribution->m_unAccountID = Obj().account_id(); + pMapContribution->m_unDefIndex = Obj().def_index(); + pMapContribution->m_unContributionLevel = Obj().contribution_level(); +} + + +void CTFMapContribution::ReadFromRecord( const CSchMapContribution & mapContribution ) +{ + Obj().set_account_id( mapContribution.m_unAccountID ); + Obj().set_def_index( mapContribution.m_unDefIndex ); + Obj().set_contribution_level( mapContribution.m_unContributionLevel ); +} + + + +#endif diff --git a/game/shared/econ/econ_contribution.h b/game/shared/econ/econ_contribution.h new file mode 100644 index 0000000..b46f87f --- /dev/null +++ b/game/shared/econ/econ_contribution.h @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds the CTFMapContribution object +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TFMAPCONTRIBUTION_H +#define TFMAPCONTRIBUTION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gcsdk/protobufsharedobject.h" +#include "tf_gcmessages.h" + +namespace GCSDK +{ + class CSQLAccess; +}; + +//--------------------------------------------------------------------------------- +// Purpose: All the account-level information that the GC tracks for TF +//--------------------------------------------------------------------------------- +class CTFMapContribution : public GCSDK::CProtoBufSharedObject< CSOTFMapContribution, k_EEconTypeMapContribution > +{ +#ifdef GC + DECLARE_CLASS_MEMPOOL( CTFMapContribution ); +#endif + +public: + CTFMapContribution() {} + +#ifdef GC + virtual bool BYieldingAddInsertToTransaction( GCSDK::CSQLAccess & sqlAccess ); + virtual bool BYieldingAddWriteToTransaction( GCSDK::CSQLAccess & sqlAccess, const CUtlVector< int > &fields ); + virtual bool BYieldingAddRemoveToTransaction( GCSDK::CSQLAccess & sqlAccess ); + + void WriteToRecord( CSchMapContribution *pMapContribution ) const; + void ReadFromRecord( const CSchMapContribution & mapContribution ); +#endif +}; + +#endif // TFMAPCONTRIBUTION_H + diff --git a/game/shared/econ/econ_dynamic_recipe.cpp b/game/shared/econ/econ_dynamic_recipe.cpp new file mode 100644 index 0000000..ee6fb17 --- /dev/null +++ b/game/shared/econ/econ_dynamic_recipe.cpp @@ -0,0 +1,250 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Functions related to dynamic recipes +// +//============================================================================= + + +#include "cbase.h" +#include "econ_dynamic_recipe.h" +#ifndef GC_DLL + #include "quest_objective_manager.h" +#endif + +// This pattern was chosen to not be: +// - a valid string acceptable for user-input (ie., custom name) +// - a sensical float bit pattern +// - a common int bit pattern +// - meaningful Unicode data +const char *g_pszAttrEncodeSeparator = "|\x01\x02\x01\x03|\x01\x02\x01\x03|"; + +CRecipeComponentMatchingIterator::CRecipeComponentMatchingIterator( const IEconItemInterface *pSourceItem, + const IEconItemInterface *pTargetItem ) + : m_pSourceItem( pSourceItem ) + , m_pTargetItem( pTargetItem ) + , m_bIgnoreCompleted( true ) + , m_nInputsTotal( 0 ) + , m_nInputsFulfilled( 0 ) + , m_nOutputsTotal( 0 ) +{} + +bool CRecipeComponentMatchingIterator::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) +{ + // Don't count ourselves as a match! + if ( m_pSourceItem && m_pTargetItem && m_pSourceItem->GetID() == m_pTargetItem->GetID() ) + return true; + + // If this isn't a match and the item isn't NULL, we skip. We consider NULL to mean + // that we want to tally ALL attributes of this type + if ( !DefinedItemAttribMatch( value, m_pTargetItem ) && m_pTargetItem != NULL ) + return true; + + // Dont let non-craftable items through + if ( m_pTargetItem && !m_pTargetItem->IsUsableInCrafting() ) + return true; + + // Is this an output? + if ( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) + { + m_vecMatchingOutputs.AddToTail( pAttrDef ); + m_nOutputsTotal += value.num_required(); + } + else + { + m_vecMatchingInputs.AddToTail( pAttrDef ); + m_nInputsTotal += value.num_required(); + m_nInputsFulfilled += value.num_fulfilled(); + } + + return true; +} + + +bool DefinedItemAttribMatch( const CAttribute_DynamicRecipeComponent& attribValue, const IEconItemInterface* pItem ) +{ + if ( !pItem ) + return false; + + // If our fulfilled count is what our item count is, then we're done. We dont want any more matches. + if ( attribValue.num_fulfilled() == attribValue.num_required() ) + return false; + + // If the item_def flag is set, and the item's item_def doesnt match then not a match + if ( ( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET ) && + ( attribValue.def_index() != (uint32)pItem->GetItemDefIndex() ) ) + return false; + + // If the quality flag is set, and the item's quality doesn't match, then not a match + if ( ( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_QUALITY_SET ) && + ( attribValue.item_quality() != (uint32)pItem->GetQuality() ) ) + return false; + + // check if we have ALL required attributes + if ( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ATTRIBUTE_SET_ALL ) + { + CUtlVector<CEconItem::attribute_t> vecAttribs; + if( !DecodeAttributeStringIntoAttributes( attribValue, vecAttribs ) ) + { + AssertMsg2( 0, "%s: Unable to decode dynamic recipe attributes on item %llu", __FUNCTION__, pItem->GetID() ); + return false; + } + + FOR_EACH_VEC( vecAttribs, i ) + { + const CEconItemAttributeDefinition *pAttr = GetItemSchema()->GetAttributeDefinition( vecAttribs[i].m_unDefinitionIndex ); + Assert( pAttr ); + uint32 itemAttributeValue; + if ( !pAttr || !pItem->FindAttribute( pAttr, &itemAttributeValue ) || itemAttributeValue != vecAttribs[i].m_value.asUint32 ) + { + return false; + } + } + } + // check if we have ANY required attributes + else if ( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ATTRIBUTE_SET_ANY ) + { + CUtlVector<CEconItem::attribute_t> vecAttribs; + if( !DecodeAttributeStringIntoAttributes( attribValue, vecAttribs ) ) + { + AssertMsg2( 0, "%s: Unable to decode dynamic recipe attributes on item %llu", __FUNCTION__, pItem->GetID() ); + return false; + } + + bool bHasAnyMatchingAttributes = false; + FOR_EACH_VEC( vecAttribs, i ) + { + const CEconItemAttributeDefinition *pAttr = GetItemSchema()->GetAttributeDefinition( vecAttribs[i].m_unDefinitionIndex ); + Assert( pAttr ); + uint32 itemAttributeValue; + if ( pAttr && pItem->FindAttribute( pAttr, &itemAttributeValue ) && itemAttributeValue == vecAttribs[i].m_value.asUint32 ) + { + bHasAnyMatchingAttributes = true; + break; + } + } + + if ( !bHasAnyMatchingAttributes ) + { + return false; + } + } + + return true; +} + +bool DecodeAttributeStringIntoAttributes( const CAttribute_DynamicRecipeComponent& attribValue, CUtlVector<CEconItem::attribute_t>& vecAttribs ) +{ + CUtlStringList vecAttributeStrings; // Automatically free'd + V_SplitString( attribValue.attributes_string().c_str(), g_pszAttrEncodeSeparator, vecAttributeStrings ); + + if( vecAttributeStrings.Count() % 2 != 0 ) + { + AssertMsg1( 0, "%s: Uneven count of encoded attribute strings!", __FUNCTION__ ); + return false; + } + + for( int j = 0; j< vecAttributeStrings.Count(); j+=2 ) + { + // Get the attribute definition that's stored in the string, and its type + attrib_definition_index_t index = Q_atoi( vecAttributeStrings[j] ); + const CEconItemAttributeDefinition *pAttrDef = GEconItemSchema().GetAttributeDefinition( index ); + if ( !pAttrDef ) + { +#ifdef GC + EmitError( SPEW_GC, __FUNCTION__ ": Unable to find attribute definition '%s' (index %d)!\n", vecAttributeStrings[j], j ); +#endif + return false; + } + + CEconItem::attribute_t& attrib = vecAttribs[vecAttribs.AddToTail()]; + attrib.m_unDefinitionIndex = pAttrDef->GetDefinitionIndex(); + + // Now have the attribute read in the value stored in the string + const ISchemaAttributeType* pAttrType = pAttrDef->GetAttributeType(); + pAttrType->InitializeNewEconAttributeValue( &attrib.m_value ); + + // Don't fail us now! + const char* pszAttribValue = vecAttributeStrings[j+1]; + if ( !pAttrType->BConvertStringToEconAttributeValue( pAttrDef, pszAttribValue, &attrib.m_value ) ) + { +#ifdef GC + EmitError( SPEW_GC, __FUNCTION__ ": Unable to parse attribute value '%s' for attribute '%s'!\n", pszAttribValue, pAttrDef->GetDefinitionName() ); +#endif + return false; + } + } + + return true; +} + +bool DecodeItemFromEncodedAttributeString( const CAttribute_DynamicRecipeComponent& attribValue, CEconItem* pItem ) +{ + // If the item_def flag is set, set that item def + if ( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET ) + { + pItem->SetDefinitionIndex( attribValue.def_index() ); + } + else + { + // If the flag is not set, then we want the item name to be generic. In english, we want to just call it "item". + CAttribute_String attrStr; + attrStr.set_value( "#TF_ItemName_Item" ); + + static CSchemaAttributeDefHandle pAttrDef_ItemNameTextOverride( "item name text override" ); + pItem->SetDynamicAttributeValue( pAttrDef_ItemNameTextOverride, attrStr ); + } + + // If the quality flag is set, take the quality + if ( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_QUALITY_SET ) + { + pItem->SetQuality( attribValue.item_quality() ); + + // If there's no item def set and the quality specified is "unique", we want to be explicit + // and have the item description actually say "unique item" so there's no confusion as to what + // item quality we want as an input. + if ( !( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET ) + && pItem->GetQuality() == AE_UNIQUE ) + { + CAttribute_String attrStr; + attrStr.set_value( "#unique" ); + + static CSchemaAttributeDefHandle pAttrDef_QualityTextOverride( "quality text override" ); + pItem->SetDynamicAttributeValue( pAttrDef_QualityTextOverride, attrStr ); + } + } + else + { + // If no quality was specified, we want to explicity say that we'll accept ANY quality. + pItem->SetQuality( AE_UNIQUE ); + CAttribute_String attrStr; + attrStr.set_value( "#TF_QualityText_Any" ); + + static CSchemaAttributeDefHandle pAttrDef_QualityTextOverride( "quality text override" ); + pItem->SetDynamicAttributeValue( pAttrDef_QualityTextOverride, attrStr ); + } + pItem->SetFlags( 0 ); + + // Get all the attributes encoded into the attribute + CUtlVector<CEconItem::attribute_t> vecAttribs; + if( !DecodeAttributeStringIntoAttributes( attribValue, vecAttribs ) ) + { + AssertMsg1( 0, " %s : Unable to decode dynamic recipe attributes", __FUNCTION__ ); + return false; + } + + // Apply the attributes to the item + FOR_EACH_VEC( vecAttribs, j ) + { + // We don't expect to get here with any missing attributes. + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( vecAttribs[j].m_unDefinitionIndex ); + Assert( pAttrDef ); + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + pAttrType->LoadEconAttributeValue( pItem, pAttrDef, vecAttribs[j].m_value ); + + // Free up our attribute memory now that we're done with it. + pAttrType->UnloadEconAttributeValue( &vecAttribs[j].m_value ); + } + + return true; +} diff --git a/game/shared/econ/econ_dynamic_recipe.h b/game/shared/econ/econ_dynamic_recipe.h new file mode 100644 index 0000000..9927ab9 --- /dev/null +++ b/game/shared/econ/econ_dynamic_recipe.h @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Functions related to dynamic recipes +// +//============================================================================= + +#ifndef ECON_DYNAMIC_RECIPE +#define ECON_DYNAMIC_RECIPE +#ifdef _WIN32 +#pragma once +#endif + +#include "tf_gcmessages.h" +#include "game_item_schema.h" +#include "econ_item.h" + +extern const char *g_pszAttrEncodeSeparator; + +//----------------------------------------------------------------------------- +// Purpose: Stores off all CAttribute_DynamicRecipeComponent attributes +// that consider m_pTargetItem to be a match. If NULL is passed in for pTargetItem +// then we consider all attributes to be a match +//----------------------------------------------------------------------------- +class CRecipeComponentMatchingIterator : public CEconItemSpecificAttributeIterator +{ +public: + CRecipeComponentMatchingIterator( const IEconItemInterface *pSourceItem, + const IEconItemInterface *pTargetItem ); + + void SetSourceItem( const IEconItemInterface *m_pSourceItem ); + void SetTargetItem( const IEconItemInterface *pTargetItem ); + void SetIgnoreCompleted( bool bIgnoreCompleted ) { m_bIgnoreCompleted = bIgnoreCompleted; } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, + const CAttribute_DynamicRecipeComponent& value ) OVERRIDE; + + const CUtlVector< const CEconItemAttributeDefinition* >& GetMatchingComponentInputs() const { return m_vecMatchingInputs; } + const CUtlVector< const CEconItemAttributeDefinition* >& GetMatchingComponentOutputs() const { return m_vecMatchingOutputs; } + + int GetTotalInputs() const { return m_nInputsTotal; } + int GetInputsFulfilled() const { return m_nInputsFulfilled; } + int GetTotalOutputs() const { return m_nOutputsTotal; } +private: + + const IEconItemInterface *m_pSourceItem; + const IEconItemInterface *m_pTargetItem; + bool m_bIgnoreCompleted; + + CUtlVector< const CEconItemAttributeDefinition* > m_vecMatchingInputs; + CUtlVector< const CEconItemAttributeDefinition* > m_vecMatchingOutputs; + + int m_nInputsTotal; + int m_nInputsFulfilled; + int m_nOutputsTotal; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Given a CAttribute_DynamicRecipeComponent and a IEconItemInterface, +// returns whether the item pass the criteria of the attribute +//----------------------------------------------------------------------------- +bool DefinedItemAttribMatch( const CAttribute_DynamicRecipeComponent& attribValue, const IEconItemInterface* pItem ); + +//----------------------------------------------------------------------------- +// Purpose: Decodes the encoded attributes in attribValue and applies those attributes to pItem +// Returns true on success, false if anything fails +//----------------------------------------------------------------------------- +bool DecodeAttributeStringIntoAttributes( const CAttribute_DynamicRecipeComponent& attribValue, CUtlVector<CEconItem::attribute_t>& vecAttribs); + +//----------------------------------------------------------------------------- +// Purpose: Decodes the encoded attributes in attribValue forms pItem into the +// item that it describes +//----------------------------------------------------------------------------- +bool DecodeItemFromEncodedAttributeString( const CAttribute_DynamicRecipeComponent& attribValue, CEconItem* pItem ); + +#endif //ECON_DYNAMIC_RECIPE diff --git a/game/shared/econ/econ_entity.cpp b/game/shared/econ/econ_entity.cpp new file mode 100644 index 0000000..041ad99 --- /dev/null +++ b/game/shared/econ/econ_entity.cpp @@ -0,0 +1,2078 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "econ_entity_creation.h" +#include "vgui/ILocalize.h" +#include "tier3/tier3.h" + +#if defined( CLIENT_DLL ) +#define UTIL_VarArgs VarArgs + +#include "econ_item_inventory.h" +#include "model_types.h" +#include "eventlist.h" +#include "networkstringtable_clientdll.h" +#include "cdll_util.h" + +#if defined(TF_CLIENT_DLL) +#include "c_tf_player.h" +#include "tf_gamerules.h" +#include "c_playerresource.h" +#include "tf_shareddefs.h" +#endif + +#else // defined( CLIENT_DLL ) + +#include "activitylist.h" + +#if defined(TF_DLL) +#include "tf_player.h" +#endif +#endif // defined( CLIENT_DLL ) + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_NETWORKCLASS_ALIASED( EconEntity, DT_EconEntity ) +IMPLEMENT_NETWORKCLASS_ALIASED( BaseAttributableItem, DT_BaseAttributableItem ) + +#if defined( CLIENT_DLL ) +bool ParseItemKeyvalue( void *pObject, typedescription_t *pFields, int iNumFields, const char *szKeyName, const char *szValue ); +#endif + +#if defined(_DEBUG) +extern ConVar item_debug; +extern ConVar item_debug_validation; +#endif + +#if !defined( CLIENT_DLL ) + #define DEFINE_ECON_ENTITY_NETWORK_TABLE() \ + SendPropDataTable( SENDINFO_DT( m_AttributeManager ), &REFERENCE_SEND_TABLE(DT_AttributeContainer) ), +#else + #define DEFINE_ECON_ENTITY_NETWORK_TABLE() \ + RecvPropDataTable( RECVINFO_DT( m_AttributeManager ), 0, &REFERENCE_RECV_TABLE(DT_AttributeContainer) ), +#endif // CLIENT_DLL + +BEGIN_NETWORK_TABLE( CEconEntity , DT_EconEntity ) + DEFINE_ECON_ENTITY_NETWORK_TABLE() + +#if defined(TF_DLL) + SendPropBool( SENDINFO( m_bValidatedAttachedEntity ) ), +#elif defined(TF_CLIENT_DLL) + RecvPropBool( RECVINFO( m_bValidatedAttachedEntity ) ), +#endif // TF_DLL || TF_CLIENT_DLL + +END_NETWORK_TABLE() + +BEGIN_DATADESC( CEconEntity ) +END_DATADESC() + +// +// Duplicating CEconEntity's network table and data description for backwards compat with demos. +// NOTE: NOTE_RENAMED_RECVTABLE() will not work with this class. +// +BEGIN_NETWORK_TABLE( CBaseAttributableItem, DT_BaseAttributableItem ) + DEFINE_ECON_ENTITY_NETWORK_TABLE() +END_NETWORK_TABLE() + +BEGIN_DATADESC( CBaseAttributableItem ) +END_DATADESC() + +#ifdef TF_CLIENT_DLL +extern ConVar cl_flipviewmodels; +#ifdef STAGING_ONLY +ConVar unusual_force_weapon_effect( "unusual_force_weapon_effect", "-1", FCVAR_CHEAT, "Set to force an Unusual effect on your weapon (Primary, Secondary, Melee)" ); +ConVar unusual_force_cosmetic_effect( "unusual_force_cosmetic_effect", "-1", FCVAR_CHEAT, "Set to force an Unusual effect on your equipped cosmetics" ); +#endif +#endif + + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DrawEconEntityAttachedModels( CBaseAnimating *pEnt, CEconEntity *pAttachedModelSource, const ClientModelRenderInfo_t *pInfo, int iMatchDisplayFlags ) +{ +#ifndef DOTA_DLL + if ( !pEnt || !pAttachedModelSource || !pInfo ) + return; + + // This flag says we should turn off the material overrides for attachments. + IMaterial* pMaterialOverride = NULL; + OverrideType_t nMaterialOverrideType = OVERRIDE_NORMAL; + + if ( ( pInfo->flags & STUDIO_NO_OVERRIDE_FOR_ATTACH ) != 0 ) + { + modelrender->GetMaterialOverride( &pMaterialOverride, &nMaterialOverrideType ); + modelrender->ForcedMaterialOverride( NULL, nMaterialOverrideType ); + } + + // Draw our attached models as well + for ( int i = 0; i < pAttachedModelSource->m_vecAttachedModels.Size(); i++ ) + { + const AttachedModelData_t& attachedModel = pAttachedModelSource->m_vecAttachedModels[i]; + + if ( attachedModel.m_pModel && (attachedModel.m_iModelDisplayFlags & iMatchDisplayFlags) ) + { + ClientModelRenderInfo_t infoAttached = *pInfo; + + infoAttached.pRenderable = pEnt; + infoAttached.instance = MODEL_INSTANCE_INVALID; + infoAttached.entity_index = pEnt->index; + infoAttached.pModel = attachedModel.m_pModel; + + infoAttached.pModelToWorld = &infoAttached.modelToWorld; + + // Turns the origin + angles into a matrix + AngleMatrix( infoAttached.angles, infoAttached.origin, infoAttached.modelToWorld ); + + DrawModelState_t state; + matrix3x4_t *pBoneToWorld; + bool bMarkAsDrawn = modelrender->DrawModelSetup( infoAttached, &state, NULL, &pBoneToWorld ); + pEnt->DoInternalDrawModel( &infoAttached, ( bMarkAsDrawn && ( infoAttached.flags & STUDIO_RENDER ) ) ? &state : NULL, pBoneToWorld ); + } + } + + if ( pMaterialOverride != NULL ) + modelrender->ForcedMaterialOverride( pMaterialOverride, nMaterialOverrideType ); +#endif +} +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconEntity::CEconEntity() +{ + m_pAttributes = this; + + // Inform base entity system that we can deal with dynamic models + EnableDynamicModels(); +#ifdef GAME_DLL + m_iOldOwnerClass = 0; +#endif + +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) + m_bValidatedAttachedEntity = false; +#endif // TF_DLL || TF_CLIENT_DLL + +#ifdef CLIENT_DLL + m_flFlexDelayTime = 0.0f; + m_flFlexDelayedWeight = NULL; + m_cFlexDelayedWeight = 0; + m_iNumOwnerValidationRetries = 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconEntity::~CEconEntity() +{ +#ifdef CLIENT_DLL + SetParticleSystemsVisible( PARTICLE_SYSTEM_STATE_NOT_VISIBLE ); + delete [] m_flFlexDelayedWeight; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStudioHdr * CEconEntity::OnNewModel() +{ + CStudioHdr* hdr = BaseClass::OnNewModel(); + +#ifdef GAME_DLL + // Adjust class-specific bodygroup after model load if we have a model and a class + if ( hdr && m_iOldOwnerClass > 0 ) + { + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + if ( pItem && pItem->IsValid() && pItem->GetStaticData()->UsesPerClassBodygroups( GetTeamNumber() ) ) + { + // Classes start at 1, bodygroups at 0 + SetBodygroup( 1, m_iOldOwnerClass - 1 ); + } + } +#endif + +#ifdef TF_CLIENT_DLL + m_bValidatedOwner = false; // require item validation to re-run + + // If we're carried by a player, let him know he should recalc his bodygroups. + C_TFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + pPlayer->SetBodygroupsDirty(); + } + + // allocate room for delayed flex weights + delete [] m_flFlexDelayedWeight; + m_flFlexDelayedWeight = NULL; + m_cFlexDelayedWeight = 0; + if ( hdr && hdr->numflexcontrollers() ) + { + m_cFlexDelayedWeight = hdr->numflexcontrollers(); + m_flFlexDelayedWeight = new float[ m_cFlexDelayedWeight ]; + memset( m_flFlexDelayedWeight, 0, sizeof( float ) * m_cFlexDelayedWeight ); + + C_BaseFlex::LinkToGlobalFlexControllers( hdr ); + } +#endif // TF_CLIENT_DLL + + return hdr; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::InitializeAttributes( void ) +{ + m_AttributeManager.InitializeAttributes( this ); + m_AttributeManager.SetProviderType( PROVIDER_WEAPON ); + +#ifdef CLIENT_DLL + // Check particle systems + CUtlVector<const attachedparticlesystem_t *> vecParticles; + GetEconParticleSystems( &vecParticles ); + m_bHasParticleSystems = vecParticles.Count() > 0; + + if ( !m_bClientside ) + return; +#else + m_AttributeManager.GetItem()->InitNetworkedDynamicAttributesForDemos(); +#endif +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::DebugDescribe( void ) +{ + CEconItemView *pScriptItem = GetAttributeContainer()->GetItem(); + + Msg("============================================\n"); + char tempstr[1024]; +// FIXME: ILocalize::ConvertUnicodeToANSI( pScriptItem->GetItemName(), tempstr, sizeof(tempstr) ); + const char *pszQualityString = EconQuality_GetQualityString( (EEconItemQuality)pScriptItem->GetItemQuality() ); + Msg("%s \"%s\" (level %d)\n", pszQualityString ? pszQualityString : "[unknown]", tempstr, pScriptItem->GetItemLevel() ); + // FIXME: ILocalize::ConvertUnicodeToANSI( pScriptItem->GetAttributeDescription(), tempstr, sizeof(tempstr) ); + Msg("%s", tempstr ); + Msg("\n============================================\n"); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::UpdateOnRemove( void ) +{ + SetOwnerEntity( NULL ); + ReapplyProvision(); + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::ReapplyProvision( void ) +{ +#ifdef GAME_DLL + UpdateModelToClass(); +#endif + + CBaseEntity *pNewOwner = GetOwnerEntity(); + if ( pNewOwner == m_hOldProvidee.Get() ) + return; + + // Remove ourselves from the old providee's list + if ( m_hOldProvidee.Get() ) + { + GetAttributeManager()->StopProvidingTo( m_hOldProvidee.Get() ); + } + + // Add ourselves to our new owner's provider list + if ( pNewOwner ) + { + GetAttributeManager()->ProvideTo( pNewOwner ); + } + + m_hOldProvidee = pNewOwner; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CEconEntity::TranslateViewmodelHandActivity( Activity actBase ) +{ + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + if ( pItem && pItem->IsValid() ) + { + GameItemDefinition_t *pStaticData = pItem->GetStaticData(); + if ( pStaticData && pStaticData->ShouldAttachToHands() ) + { + return TranslateViewmodelHandActivityInternal(actBase); + } + } + + return actBase; +} + +#if !defined( CLIENT_DLL ) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::OnOwnerClassChange( void ) +{ +#ifdef TF_DLL + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer && pPlayer->GetPlayerClass()->GetClassIndex() != m_iOldOwnerClass ) + { + UpdateModelToClass(); + } +#endif +} + +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconEntity::CalculateVisibleClassFor( CBaseCombatCharacter *pPlayer ) +{ +#ifdef TF_DLL + CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer ); + return (pTFPlayer ? pTFPlayer->GetPlayerClass()->GetClassIndex() : 0); +#else + return 0; +#endif +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: double duty function - sets up model for current player class, and +// also sets bodygroups if the correct model is fully loaded. +//----------------------------------------------------------------------------- +void CEconEntity::UpdateModelToClass( void ) +{ +#ifdef TF_DLL + MDLCACHE_CRITICAL_SECTION(); + + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + m_iOldOwnerClass = CalculateVisibleClassFor( pPlayer ); + if ( !pPlayer ) + return; + + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + if ( !pItem->IsValid() ) + return; + + const char *pszModel = NULL; + + // If we attach to hands, we need to use the hand models + if ( pItem->GetStaticData()->ShouldAttachToHands() ) + { + pszModel = pPlayer->GetPlayerClass()->GetHandModelName( 0 ); + } + else + { + int nTeam = pPlayer->GetTeamNumber(); + CTFWearable *pWearable = dynamic_cast< CTFWearable*>( this ); + if ( pWearable && pWearable->IsDisguiseWearable() ) + { + nTeam = pPlayer->m_Shared.GetDisguiseTeam(); + } + + pszModel = pItem->GetPlayerDisplayModel( m_iOldOwnerClass, nTeam ); + } + if ( pszModel && pszModel[0] ) + { + if ( V_stricmp( STRING( GetModelName() ), pszModel ) != 0 ) + { + if ( pItem->GetStaticData()->IsContentStreamable() ) + { + modelinfo->RegisterDynamicModel( pszModel, IsClient() ); + + const char *pszModelAlt = pItem->GetStaticData()->GetPlayerDisplayModelAlt( m_iOldOwnerClass ); + if ( pszModelAlt && pszModelAlt[0] ) + { + modelinfo->RegisterDynamicModel( pszModelAlt, IsClient() ); + } + + if ( pItem->GetVisionFilteredDisplayModel() && pItem->GetVisionFilteredDisplayModel()[ 0 ] != '\0' ) + { + modelinfo->RegisterDynamicModel( pItem->GetVisionFilteredDisplayModel(), IsClient() ); + } + } + + SetModel( pszModel ); + } + } + + if ( GetModelPtr() && pItem->GetStaticData()->UsesPerClassBodygroups( GetTeamNumber() ) ) + { + // Classes start at 1, bodygroups at 0, so we shift them all back 1. + SetBodygroup( 1, (m_iOldOwnerClass-1) ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::PlayAnimForPlaybackEvent( wearableanimplayback_t iPlayback ) +{ + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + if ( !pItem->IsValid() || !GetOwnerEntity() ) + return; + + int iTeamNum = GetOwnerEntity()->GetTeamNumber(); + int iActivities = pItem->GetStaticData()->GetNumPlaybackActivities( iTeamNum ); + for ( int i = 0; i < iActivities; i++ ) + { + activity_on_wearable_t *pData = pItem->GetStaticData()->GetPlaybackActivityData( iTeamNum, i ); + if ( pData && pData->iPlayback == iPlayback && pData->pszActivity ) + { + // If this is the first time we've tried to use it, find the activity + if ( pData->iActivity == kActivityLookup_Unknown ) + { + pData->iActivity = ActivityList_IndexForName( pData->pszActivity ); + } + + int sequence = SelectWeightedSequence( (Activity)pData->iActivity ); + if ( sequence != ACTIVITY_NOT_AVAILABLE ) + { + ResetSequence( sequence ); + SetCycle( 0 ); + +#if !defined( CLIENT_DLL ) + if ( IsUsingClientSideAnimation() ) + { + ResetClientsideFrame(); + } +#endif + } + return; + } + } +} + +#endif // !CLIENT_DLL + +#if defined( TF_CLIENT_DLL ) +// It's okay to draw attached entities with these models. +const char* g_modelWhiteList[] = +{ + "models/weapons/w_models/w_toolbox.mdl", + "models/weapons/w_models/w_sapper.mdl", + + // Canteens can change model based on the powerup type... all of these alternates are ok! + "models/player/items/mvm_loot/all_class/mvm_flask_krit.mdl", + "models/player/items/mvm_loot/all_class/mvm_flask_uber.mdl", + "models/player/items/mvm_loot/all_class/mvm_flask_tele.mdl", + "models/player/items/mvm_loot/all_class/mvm_flask_ammo.mdl", + "models/player/items/mvm_loot/all_class/mvm_flask_build.mdl", + + TF_WEAPON_TAUNT_FRONTIER_JUSTICE_GUITAR_MODEL, + + "models/workshop/weapons/c_models/c_paratooper_pack/c_paratrooper_pack.mdl", + "models/workshop/weapons/c_models/c_paratooper_pack/c_paratrooper_pack_open.mdl", + +}; + +#define HALLOWEEN_KART_MODEL "models/player/items/taunts/bumpercar/parts/bumpercar.mdl" +#define HALLOWEEN_KART_CAGE_MODEL "models/props_halloween/bumpercar_cage.mdl" +#endif + +#if defined( CLIENT_DLL ) +//----------------------------------------------------------------------------- +// Purpose: TF prevents drawing of any entity attached to players that aren't items in the inventory of the player. +// This is to prevent servers creating fake cosmetic items and attaching them to players. +//----------------------------------------------------------------------------- +bool CEconEntity::ValidateEntityAttachedToPlayer( bool &bShouldRetry ) +{ + bShouldRetry = false; + + // We only use this variable in debug or on the client. +#if defined( _DEBUG ) || defined( TF_CLIENT_DLL ) + bool bItemDebugValidation = false; +#endif // defined( _DEBUG ) || defined( TF_CLIENT_DLL ) + +#ifdef _DEBUG + bItemDebugValidation = item_debug_validation.GetBool(); + + // Always valid in debug if item_debug_validation is disabled + if ( !bItemDebugValidation ) + return true; +#endif // _DEBUG + +#if defined( TF_CLIENT_DLL ) + // Always valid in item testing mode + if ( TFGameRules()->IsInItemTestingMode() ) + return true; + + C_TFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); + + // If we're not carried by a player, we're not valid. This prevents them + // parenting hats to ents that they then parent to the player. + if ( !pOwner ) + { + //Msg( "NO OWNER SET! %i\n", m_iNumOwnerValidationRetries ); + bShouldRetry = ( m_iNumOwnerValidationRetries < 500 ); + m_iNumOwnerValidationRetries++; + return false; + } + + C_BaseEntity *pVM = pOwner->GetViewModel(); + + // The owner entity must also be a move parent of this entity. + bool bPlayerIsParented = false; + C_BaseEntity *pEntity = this; + while ( (pEntity = pEntity->GetMoveParent()) != NULL ) + { + if ( pOwner == pEntity || pVM == pEntity ) + { + bPlayerIsParented = true; + break; + } + } + + if ( !bPlayerIsParented ) + { + //Msg( "NOT PARENTED! %i\n", m_iNumOwnerValidationRetries ); + bShouldRetry = ( m_iNumOwnerValidationRetries < 500 ); + m_iNumOwnerValidationRetries++; + return false; + } + + m_iNumOwnerValidationRetries = 0; + + // We only need this in debug (for item_debug_validation) or PvE mode + bool bOwnerIsBot = pOwner->IsABot(); // THIS IS INSECURE -- DO NOT USE THIS OUTSIDE OF DEBUG OR PVE MODE + + // Allow bots to use anything in PvE mode + if ( bOwnerIsBot && TFGameRules()->IsPVEModeActive() ) + return true; + + int iClass = pOwner->GetPlayerClass()->GetClassIndex(); + int iTeam = pOwner->GetTeamNumber(); + + // Allow all weapons parented to the local player + if ( pOwner == C_BasePlayer::GetLocalPlayer() ) + { + // They can change the owner entity, so we have to keep checking. + bShouldRetry = true; + return true; + } + + // HACK: For now, if our owner is a disguised spy, we assume everything is valid. + if ( (pOwner->m_Shared.InCond( TF_COND_DISGUISED ) || pOwner->m_Shared.InCond( TF_COND_DISGUISING )) && iClass == TF_CLASS_SPY ) + { + bShouldRetry = true; // Keep checking in case the player switches class or becomes no longer disguised + return true; + } + + // If our owner is a disguised spy, we validate everything based + // on the items carried by the person we're disguised as. + /*if ( pOwner->m_Shared.InCond( TF_COND_DISGUISED ) ) + { + // DAMN: This won't work. If our disguise target is a player we've never seen before, + // we won't have a client entity, and hence we don't have their inventory. + C_TFPlayer *pDisguiseTarget = ToTFPlayer( pOwner->m_Shared.GetDisguiseTarget() ); + if ( pDisguiseTarget && pDisguiseTarget != pOwner ) + { + pOwner = pDisguiseTarget; + iClass = pOwner->GetPlayerClass()->GetClassIndex(); + } + else + { + // We're not disguised as a specific player. Make sure we lookup base weapons with the disguise class. + iClass = pOwner->m_Shared.GetDisguiseClass(); + } + } + */ + +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) + if ( m_bValidatedAttachedEntity ) + return true; +#endif // TF_DLL || TF_CLIENT_DLL + + const char *pszClientModel = modelinfo->GetModelName( GetModel() ); + if ( pszClientModel && g_modelWhiteList[0] ) + { + // Certain builder models are okay to have. + for ( int i=0; i<ARRAYSIZE( g_modelWhiteList ); ++i ) + { + if ( FStrEq( pszClientModel, g_modelWhiteList[i] ) ) + return true; + } + } + + // Halloween Karts + if ( pOwner->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) + { + if ( FStrEq( pszClientModel, HALLOWEEN_KART_MODEL ) ) + return true; + + if ( FStrEq( pszClientModel, HALLOWEEN_KART_CAGE_MODEL ) ) + return true; + } + + // If our player doesn't have an inventory, we're not valid. + CTFPlayerInventory *pInv = pOwner->Inventory(); + if ( !pInv ) + return false; + + // check if this is a custom taunt prop + if ( pOwner->m_Shared.InCond( TF_COND_TAUNTING ) ) + { + const char* pszCustomTauntProp = NULL; + int iClassTaunt = pOwner->GetPlayerClass()->GetClassIndex(); + CEconItemView *pMiscItemView = pInv->GetItemInLoadout( iClassTaunt, pOwner->GetActiveTauntSlot() ); + if ( pMiscItemView && pMiscItemView->IsValid() ) + { + if ( pMiscItemView->GetStaticData()->GetTauntData() ) + { + pszCustomTauntProp = pMiscItemView->GetStaticData()->GetTauntData()->GetProp( iClassTaunt ); + if ( pszCustomTauntProp ) + { + return true; + } + } + } + } + + // If we've lost connection to the GC, let's just trust the server to avoid breaking the appearance for everyone. + bool bSkipInventoryCheck = bItemDebugValidation && bOwnerIsBot; // will always be false in release builds + if ( ( !pInv->GetSOC() || !pInv->GetSOC()->BIsInitialized() ) && !bSkipInventoryCheck ) + { + bShouldRetry = true; + return true; + } + + CEconItemView *pScriptItem = GetAttributeContainer()->GetItem(); + + // If the item isn't valid, we're probably an extra wearable for another item. See if our model is + // a model specified as the extra wearable for any of the items we have equipped. + if ( !pScriptItem->IsValid() ) + { + // Uninitialized client models return their model as '?' + if ( pszClientModel && pszClientModel[0] != '?' ) + { + CSteamID steamIDForPlayer; + pOwner->GetSteamID( &steamIDForPlayer ); + + for ( int i = 0; i < CLASS_LOADOUT_POSITION_COUNT; i++ ) + { + CEconItemView *pItem = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i, &steamIDForPlayer ); + if ( pItem && pItem->IsValid() ) + { + const char *pszAttached = pItem->GetExtraWearableModel(); + if ( pszAttached && pszAttached[0] ) + { + if ( FStrEq( pszClientModel, pszAttached ) ) + return true; + } + + pszAttached = pItem->GetExtraWearableViewModel(); + if ( pszAttached && pszAttached[0] ) + { + if ( FStrEq( pszClientModel, pszAttached ) ) + return true; + } + } + } + } + else if ( pszClientModel && pszClientModel[0] == '?' ) + { + bShouldRetry = true; + } + + return false; + } + + // Skip this check for bots if item_debug_validation is enabled. + if ( !pInv->GetInventoryItemByItemID( pScriptItem->GetItemID() ) && !bSkipInventoryCheck ) + { + // If it's a base item, we allow it. + CEconItemView *pBaseItem = TFInventoryManager()->GetBaseItemForClass( iClass, pScriptItem->GetStaticData()->GetLoadoutSlot(iClass) ); + if ( *pScriptItem != *pBaseItem ) + { + const wchar_t *pwzItemName = pScriptItem->GetItemName(); + + char szItemName[ MAX_ITEM_NAME_LENGTH ]; + ILocalize::ConvertUnicodeToANSI( pwzItemName, szItemName, sizeof( szItemName ) ); + +#ifdef _DEBUG + Warning("Item '%s' attached to %s, but it's not in his inventory.\n", szItemName, pOwner->GetPlayerName() ); +#endif + return false; + } + } + + // Our model has to match the model in our script + const char *pszScriptModel = pScriptItem->GetWorldDisplayModel(); + if ( !pszScriptModel ) + { + pszScriptModel = pScriptItem->GetPlayerDisplayModel( iClass, iTeam ); + } + + if ( pszClientModel && pszClientModel[0] && pszClientModel[0] != '?' ) + { + // A model was set on the entity, let's make sure it matches the model in the script. + if ( !pszScriptModel || !pszScriptModel[0] ) + return false; + + if ( FStrEq( pszClientModel, pszScriptModel ) == false ) + { +#if defined( STAGING_ONLY ) && defined _DEBUG + CUtlString strScriptModel( pszScriptModel ); + strScriptModel.FixSlashes(); + CUtlString strClientModel( pszClientModel ); + strClientModel.FixSlashes(); + AssertMsg( strScriptModel != strClientModel, "Model path separator differs between script and client!" ); +#endif // STAGING_ONLY + // The regular model didn't work...let's try the Alt version if it exists + const char *pszScriptModelAlt = pScriptItem->GetStaticData()->GetPlayerDisplayModelAlt( iClass ); + if ( !pszScriptModelAlt || !pszScriptModelAlt[0] || ( FStrEq( pszClientModel, pszScriptModelAlt ) == false ) ) + { + // The Alt model didn't work... let's try the vision filtered display model if it happens to exist + pszScriptModel = pScriptItem->GetVisionFilteredDisplayModel(); + if ( !pszScriptModel || !pszScriptModel[0] ) + return false; + + if ( FStrEq( pszClientModel, pszScriptModel ) == false ) + { + return false; + } + } + } + } + else + { + // The client model was not set, so check that there isn't a model set in the script either. + if ( pszScriptModel && pszScriptModel[0] ) + { + if ( pszClientModel[0] == '?' ) + bShouldRetry = true; + + return false; + } + } + + return true; +#else + return false; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a material override for this entity via code +//----------------------------------------------------------------------------- +void CEconEntity::SetMaterialOverride( int team, const char *pszMaterial ) +{ + if ( team >= 0 && team < TEAM_VISUAL_SECTIONS ) + { + m_MaterialOverrides[ team ].Init( pszMaterial, TEXTURE_GROUP_CLIENT_EFFECTS ); + } +} + + +//----------------------------------------------------------------------------- +void CEconEntity::SetMaterialOverride( int team, CMaterialReference &ref ) +{ + if ( team >= 0 && team < TEAM_VISUAL_SECTIONS ) + { + m_MaterialOverrides[ team ].Init( ref ); + } +} + + +// Deal with recording +void CEconEntity::GetToolRecordingState( KeyValues *msg ) +{ +#ifndef _XBOX + BaseClass::GetToolRecordingState( msg ); + + bool bUseOverride = ( GetTeamNumber() >= 0 && GetTeamNumber() < TEAM_VISUAL_SECTIONS ) && m_MaterialOverrides[ GetTeamNumber() ].IsValid(); + if ( bUseOverride ) + { + msg->SetString( "materialOverride", m_MaterialOverrides[ GetTeamNumber() ]->GetName() ); + } +#endif +} + + +#ifndef DOTA_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_ViewmodelAttachmentModel::SetOuter( CEconEntity *pOuter ) +{ + m_hOuter = pOuter; + SetOwnerEntity( pOuter ); + + CEconItemView *pItem = pOuter->GetAttributeContainer()->GetItem(); + if ( pItem->IsValid() ) + { + m_bAlwaysFlip = pItem->GetStaticData()->ShouldFlipViewmodels(); + } +} + +bool C_ViewmodelAttachmentModel::InitializeAsClientEntity( const char *pszModelName, RenderGroup_t renderGroup ) +{ + if ( !BaseClass::InitializeAsClientEntity( pszModelName, renderGroup ) ) + return false; + + AddEffects( EF_BONEMERGE ); + AddEffects( EF_BONEMERGE_FASTCULL ); + + // Invisible by default, and made visible->drawn->made invisible when the viewmodel is drawn + AddEffects( EF_NODRAW ); + return true; +} + +int C_ViewmodelAttachmentModel::InternalDrawModel( int flags ) +{ +#ifdef TF_CLIENT_DLL + CMatRenderContextPtr pRenderContext( materials ); + if ( cl_flipviewmodels.GetBool() != m_bAlwaysFlip ) + { + pRenderContext->CullMode( MATERIAL_CULLMODE_CW ); + } +#endif + int r = BaseClass::InternalDrawModel( flags ); + +#ifdef TF_CLIENT_DLL + pRenderContext->CullMode( MATERIAL_CULLMODE_CCW ); +#endif + + return r; +} + +bool C_ViewmodelAttachmentModel::OnPostInternalDrawModel( ClientModelRenderInfo_t *pInfo ) +{ + if ( !BaseClass::OnPostInternalDrawModel( pInfo ) ) + return false; + + if ( !m_hOuter ) + return true; + if ( !m_hOuter->GetAttributeContainer() ) + return true; + if ( !m_hOuter->GetAttributeContainer()->GetItem() ) + return true; + + DrawEconEntityAttachedModels( this, GetOuter(), pInfo, kAttachedModelDisplayFlag_ViewModel ); + return true; +} + +void C_ViewmodelAttachmentModel::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) +{ + BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask ); + + // Will it blend? + + if ( !m_hOuter ) + return; + + m_hOuter->ViewModelAttachmentBlending( hdr, pos, q, currentTime, boneMask ); +} + +void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); +void C_ViewmodelAttachmentModel::FormatViewModelAttachment( int nAttachment, matrix3x4_t &attachmentToWorld ) +{ + Vector vecOrigin; + MatrixPosition( attachmentToWorld, vecOrigin ); + ::FormatViewModelAttachment( vecOrigin, false ); + PositionMatrix( vecOrigin, attachmentToWorld ); +} +int C_ViewmodelAttachmentModel::GetSkin( void ) +{ + if ( m_hOuter != NULL ) + { + CBaseCombatWeapon *pWeapon = m_hOuter->MyCombatWeaponPointer(); + + if ( pWeapon ) + { + int nSkin = pWeapon->GetSkinOverride(); + if ( nSkin != -1 ) + { + return nSkin; + } + } + else + { + // some models like the Festive Targe don't have combat pointers but they still need to get the correct skin + if ( m_hOuter->GetAttributeContainer() ) + { + CEconItemView *pItem = m_hOuter->GetAttributeContainer()->GetItem(); + if ( pItem && pItem->IsValid() && GetOwnerViaInterface() ) + { + return pItem->GetSkin( GetOwnerViaInterface()->GetTeamNumber(), true ); + } + } + } + } + + return BaseClass::GetSkin(); +} + +#endif // !defined( DOTA_DLL ) + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::Release( void ) +{ +#ifdef CLIENT_DLL + SetParticleSystemsVisible( PARTICLE_SYSTEM_STATE_NOT_VISIBLE ); + + // Remove all effects associated with this econ entity, not just turn them off + C_BaseEntity *pEffectOwnerWM = this; + C_BaseEntity *pEffectOwnerVM = NULL; + + bool bExtraWearable = false; + bool bExtraWearableVM = false; + + CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase*>( this ); + if ( pWeapon ) + { + pEffectOwnerVM = pWeapon->GetPlayerOwner() ? pWeapon->GetPlayerOwner()->GetViewModel() : NULL; + if ( pWeapon->m_hExtraWearable.Get() ) + { + pEffectOwnerWM = pWeapon->m_hExtraWearable.Get(); + bExtraWearable = true; + } + + if ( pWeapon->m_hExtraWearableViewModel.Get() ) + { + pEffectOwnerVM = pWeapon->m_hExtraWearableViewModel.Get(); + bExtraWearableVM = true; + } + // always kill all effects for VM + if ( pEffectOwnerVM ) + { + pEffectOwnerVM->ParticleProp()->StopEmission( NULL, false, true ); + } + } + + pEffectOwnerWM->ParticleProp()->StopEmission( NULL, false, true ); + +#endif + if ( m_hViewmodelAttachment ) + { + m_hViewmodelAttachment->Release(); + } + + BaseClass::Release(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::SetDormant( bool bDormant ) +{ + // If I'm burning, stop the burning sounds + if ( !IsDormant() && bDormant && m_nParticleSystemsCreated != PARTICLE_SYSTEM_STATE_NOT_VISIBLE ) + { + SetParticleSystemsVisible( PARTICLE_SYSTEM_STATE_NOT_VISIBLE ); + } + + BaseClass::SetDormant( bDormant ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::OnPreDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnPreDataChanged( type ); + + m_iOldTeam = m_iTeamNum; +} + +IMaterial *CreateTempMaterialForPlayerLogo( int iPlayerIndex, player_info_t *info, char *texname, int nchars ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::OnDataChanged( DataUpdateType_t updateType ) +{ + // If we were just created, setup from the script files + if ( updateType == DATA_UPDATE_CREATED ) + { + InitializeAttributes(); + m_nParticleSystemsCreated = PARTICLE_SYSTEM_STATE_NOT_VISIBLE; + m_bAttachmentDirty = true; + } + + BaseClass::OnDataChanged( updateType ); + + GetAttributeContainer()->OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + CEconItemView *pItem = m_AttributeManager.GetItem(); + +#if defined(_DEBUG) + if ( item_debug.GetBool() ) + { + DebugDescribe(); + } +#endif + + // Find & cache for easy leaf code usage + for ( int team = 0; team < TEAM_VISUAL_SECTIONS; team++ ) + { + const char *pszMaterial = pItem->GetStaticData()->GetMaterialOverride( team ); + if ( pszMaterial ) + { + m_MaterialOverrides[team].Init( pszMaterial, TEXTURE_GROUP_CLIENT_EFFECTS ); + } + } + +#ifdef TF_CLIENT_DLL + // If we're carried by a player, let him know he should recalc his bodygroups. + C_TFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + pPlayer->SetBodygroupsDirty(); + } + + //Warning("Forcing recalc of visiblity for %d\n", entindex()); + m_bValidatedOwner = false; + m_iNumOwnerValidationRetries = 0; + UpdateVisibility(); +#endif // TF_CLIENT_DLL + } + + UpdateAttachmentModels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::UpdateAttachmentModels( void ) +{ +#ifndef DOTA_DLL + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + GameItemDefinition_t *pItemDef = pItem && pItem->IsValid() ? pItem->GetStaticData() : NULL; + + // Update the state of additional model attachments + m_vecAttachedModels.Purge(); + if ( pItemDef && AttachmentModelsShouldBeVisible() ) + { + int iTeamNumber = GetTeamNumber(); + { + int iAttachedModels = pItemDef->GetNumAttachedModels( iTeamNumber ); + for ( int i = 0; i < iAttachedModels; i++ ) + { + attachedmodel_t *pModel = pItemDef->GetAttachedModelData( iTeamNumber, i ); + + int iModelIndex = modelinfo->GetModelIndex( pModel->m_pszModelName ); + if ( iModelIndex >= 0 ) + { + AttachedModelData_t attachedModelData; + attachedModelData.m_pModel = modelinfo->GetModel( iModelIndex ); + attachedModelData.m_iModelDisplayFlags = pModel->m_iModelDisplayFlags; + m_vecAttachedModels.AddToTail( attachedModelData ); + } + } + } + + // Check for Festive attachedmodels for festivized weapons + { + int iAttachedModels = pItemDef->GetNumAttachedModelsFestivized( iTeamNumber ); + if ( iAttachedModels ) + { + int iFestivized = 0; + CALL_ATTRIB_HOOK_INT( iFestivized, is_festivized ); + if ( iFestivized ) + { + for ( int i = 0; i < iAttachedModels; i++ ) + { + attachedmodel_t *pModel = pItemDef->GetAttachedModelDataFestivized( iTeamNumber, i ); + + int iModelIndex = modelinfo->GetModelIndex( pModel->m_pszModelName ); + if ( iModelIndex >= 0 ) + { + AttachedModelData_t attachedModelData; + attachedModelData.m_pModel = modelinfo->GetModel( iModelIndex ); + attachedModelData.m_iModelDisplayFlags = pModel->m_iModelDisplayFlags; + m_vecAttachedModels.AddToTail( attachedModelData ); + } + } + } + } + } + } + + // Update the state of attachment models for this item + bool bItemNeedsAttachment = pItemDef && (pItemDef->ShouldAttachToHands() || pItemDef->ShouldAttachToHandsVMOnly()); + if ( bItemNeedsAttachment ) + { + bool bShouldShowAttachment = false; + CBasePlayer *pOwner = ToBasePlayer( GetOwnerEntity() ); + if ( pOwner && !pOwner->ShouldDrawThisPlayer() ) + { + // Drawing the viewmodel + bShouldShowAttachment = true; + } + + if ( bShouldShowAttachment && AttachmentModelsShouldBeVisible() ) + { + if ( !m_hViewmodelAttachment ) + { + C_BaseViewModel *vm = pOwner->GetViewModel( 0 ); + if ( vm ) + { + C_ViewmodelAttachmentModel *pEnt = new class C_ViewmodelAttachmentModel; + if ( !pEnt ) + return; + + pEnt->SetOuter( this ); + + int iClass = 0; +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + CTFPlayer *pTFPlayer = ToTFPlayer( pOwner ); + if ( pTFPlayer ) + { + iClass = pTFPlayer->GetPlayerClass()->GetClassIndex(); + } +#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + if ( pEnt->InitializeAsClientEntity( pItem->GetPlayerDisplayModel( iClass, pOwner->GetTeamNumber() ), RENDER_GROUP_VIEW_MODEL_OPAQUE ) == false ) + return; + + m_hViewmodelAttachment = pEnt; + m_hViewmodelAttachment->SetParent( vm ); + m_hViewmodelAttachment->SetLocalOrigin( vec3_origin ); + m_hViewmodelAttachment->UpdatePartitionListEntry(); + m_hViewmodelAttachment->CollisionProp()->UpdatePartition(); + m_hViewmodelAttachment->UpdateVisibility(); + + m_bAttachmentDirty = true; + } + } + else if ( m_hViewmodelAttachment ) + { + // If a player changes team, we may need to update the skin on the attachment weapon model + if ( m_iOldTeam != m_iTeamNum ) + { + m_bAttachmentDirty = true; + } + } + + // We can't pull data from the viewmodel until we're actually the active weapon. + if ( m_bAttachmentDirty && m_hViewmodelAttachment ) + { + pOwner = ToBasePlayer( GetOwnerEntity() ); + C_BaseViewModel *vm = pOwner->GetViewModel( 0 ); + if ( vm && vm->GetWeapon() == this ) + { + m_hViewmodelAttachment->m_nSkin = vm->GetSkin(); + m_bAttachmentDirty = false; + } + } + return; + } + } + + // If we get here we shouldn't have an attachment. + if ( m_hViewmodelAttachment ) + { + m_hViewmodelAttachment->Release(); + } + +#endif // !defined( DOTA_DLL ) +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::HasCustomParticleSystems( void ) const +{ + return m_bHasParticleSystems; +} + +//-----------------------------------------------------------------z------------ +// Purpose: Create / Destroy particle systems on this item as appropriate +//----------------------------------------------------------------------------- +void CEconEntity::UpdateParticleSystems( void ) +{ + if ( !HasCustomParticleSystems() ) + return; + + ParticleSystemState_t nVisible = PARTICLE_SYSTEM_STATE_NOT_VISIBLE; + if ( IsEffectActive( EF_NODRAW ) || !ShouldDraw() ) + { + nVisible = PARTICLE_SYSTEM_STATE_NOT_VISIBLE; + } + else if ( !GetOwnerEntity() && !IsDormant() ) + { + nVisible = PARTICLE_SYSTEM_STATE_VISIBLE; + } + else if ( GetOwnerEntity() && !GetOwnerEntity()->IsDormant() && GetOwnerEntity()->IsPlayer() && GetOwnerEntity()->IsAlive() ) + { + nVisible = PARTICLE_SYSTEM_STATE_VISIBLE; + } + + if ( nVisible == PARTICLE_SYSTEM_STATE_NOT_VISIBLE ) + { +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) + // Make sure the entity we're attaching to is being drawn + CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( this ); + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer && pLocalPlayer == GetOwnerEntity() && pLocalPlayer->GetViewModel() && pLocalPlayer->GetViewModel()->GetWeapon() == pWeapon && !C_BasePlayer::ShouldDrawLocalPlayer() ) + { + nVisible = PARTICLE_SYSTEM_STATE_VISIBLE_VM; + } +#endif + } + + if ( nVisible != PARTICLE_SYSTEM_STATE_NOT_VISIBLE && !ShouldDrawParticleSystems() ) + { + nVisible = PARTICLE_SYSTEM_STATE_NOT_VISIBLE; + } + + SetParticleSystemsVisible( nVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::ShouldDrawParticleSystems( void ) +{ +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) + C_TFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + bool bStealthed = pPlayer->m_Shared.IsStealthed(); + if ( bStealthed ) + return false; + bool bDisguised = pPlayer->m_Shared.InCond( TF_COND_DISGUISED ); + if ( bDisguised ) + { + CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase*>( this ); + bool bDisguiseWeapon = pWeapon && pWeapon->m_bDisguiseWeapon; + if ( !bDisguiseWeapon ) + { + return false; + } + } + } +#endif + + // Make sure the entity we're attaching to is being drawn + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + C_BaseEntity *pEffectOwner = this; + if ( pLocalPlayer == GetOwnerEntity() && pLocalPlayer->GetViewModel() && !C_BasePlayer::ShouldDrawLocalPlayer() ) + { + pEffectOwner = pLocalPlayer->GetViewModel(); + } + + if ( !pEffectOwner->ShouldDraw() ) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + if ( !InternalFireEvent( origin, angles, event, options ) ) + { + BaseClass::FireEvent( origin, angles, event, options ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + return InternalFireEvent( origin, angles, event, options ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::InternalFireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + switch( event ) + { + case AE_CL_BODYGROUP_SET_VALUE_CMODEL_WPN: + if ( m_hViewmodelAttachment ) + { + // Translate it into a set bodygroup event on our attached weapon + m_hViewmodelAttachment->FireEvent( origin, angles, AE_CL_BODYGROUP_SET_VALUE, options ); + } + return true; + break; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Does this model use delayed flex weights? +//----------------------------------------------------------------------------- +bool CEconEntity::UsesFlexDelayedWeights() +{ + return m_flFlexDelayedWeight != NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Rendering callback to allow the client to set up all the model specific flex weights +//----------------------------------------------------------------------------- +void CEconEntity::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) +{ + if ( GetModelPtr() && GetModelPtr()->numflexcontrollers() ) + { + if ( IsEffectActive( EF_BONEMERGE ) && GetMoveParent() ) + { + C_BaseFlex *pParentFlex = dynamic_cast<C_BaseFlex*>( GetMoveParent() ); + if ( pParentFlex ) + { + // BUGBUG: We have a bug with SetCustomModel that causes a disagreement between the studio header here and the one used in l_studio.cpp CModelRender::DrawModelExecute + // So when we hit that case, let's not do any work because otherwise we'd crash since the array sizes (m_flFlexDelayedWeight vs pFlexWeights) don't match. + // Note that this check is duplicated in C_BaseFlex::SetupLocalWeights. + AssertMsg( nFlexWeightCount == m_cFlexDelayedWeight, "Disagreement between the number of flex weights. Do the studio headers match?" ); + if ( nFlexWeightCount != m_cFlexDelayedWeight ) + { + return; + } + + if ( pParentFlex->SetupGlobalWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ) ) + { + // convert the flex controllers into actual flex values + C_BaseFlex::RunFlexRules( GetModelPtr(), pFlexWeights ); + + // aim the eyes + // SetViewTarget( hdr ); // FIXME: Not enough info yet + + // process local versions of the delay weights + if ( pFlexDelayedWeights ) + { + C_BaseFlex::RunFlexDelay( nFlexWeightCount, pFlexWeights, m_flFlexDelayedWeight, m_flFlexDelayTime ); + memcpy( pFlexDelayedWeights, m_flFlexDelayedWeight, sizeof( float ) * nFlexWeightCount ); + } + return; + } + } + } + } + + BaseClass::SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ); + return; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void cc_dump_particlemanifest() +{ + Msg("Dumping particle list:\n"); + for ( int i = 0; i < g_pParticleSystemMgr->GetParticleSystemCount(); i++ ) + { + const char *pParticleSystemName = g_pParticleSystemMgr->GetParticleSystemNameFromIndex(i); + Msg(" %d: %s\n", i, pParticleSystemName ); + } +} +static ConCommand dump_particlemanifest( "dump_particlemanifest", cc_dump_particlemanifest, "Dump the list of particles loaded.", FCVAR_CHEAT ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::GetEconParticleSystems( CUtlVector<const attachedparticlesystem_t *> *out_pvecParticleSystems ) const +{ + Assert( out_pvecParticleSystems ); + + const CEconItemView *pEconItemView = m_AttributeManager.GetItem(); + if ( pEconItemView ) + { + const GameItemDefinition_t *pItemDef = pEconItemView->GetStaticData(); + + // Count static particles included in the item definition -- these are things like + // the kritzkrieg particles or the milk splash particles. + const int iStaticParticleCount = pItemDef->GetNumAttachedParticles( GetTeamNumber() ); + for ( int i = 0; i < iStaticParticleCount; i++ ) + { + out_pvecParticleSystems->AddToTail( pItemDef->GetAttachedParticleData( GetTeamNumber(), i ) ); + } + + // Do we have a particle effect that goes along with our specific quality? Self-made + // and community items have a sparkle, for example. + const int iQualityParticleType = pEconItemView->GetQualityParticleType(); + if ( iQualityParticleType > 0 ) + { + out_pvecParticleSystems->AddToTail( GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType ) ); + } + } + + // Do we have particle systems added on via static attributes (ie., pipe smoke)? + // Note that this is functionally identical to the dynamic unusual particles. We don't support + // having multiple attributes of the same type with independent values so we split these out + // at a higher level, limiting ourself to one of each. + int iStaticParticleEffect = 0; + CALL_ATTRIB_HOOK_INT( iStaticParticleEffect, set_attached_particle_static ); + if ( iStaticParticleEffect > 0 ) + { + out_pvecParticleSystems->AddToTail( GetItemSchema()->GetAttributeControlledParticleSystem( iStaticParticleEffect ) ); + } + + // Do we have particle systems added on dynamically (ie., unusuals?)? + int iDynamicParticleEffect = 0; + int iIsThrowableTrail = 0; + CALL_ATTRIB_HOOK_INT( iDynamicParticleEffect, set_attached_particle ); + CALL_ATTRIB_HOOK_INT( iIsThrowableTrail, throwable_particle_trail_only ); + +#if defined(TF_CLIENT_DLL) +#ifdef STAGING_ONLY + if ( pEconItemView ) + { + const GameItemDefinition_t *pItemDef = pEconItemView->GetStaticData(); + + int iSlot = pItemDef->GetLoadoutSlot( 0 ); + if ( unusual_force_weapon_effect.GetInt() > 0 ) + { + if ( iSlot == LOADOUT_POSITION_PRIMARY || iSlot == LOADOUT_POSITION_SECONDARY || iSlot == LOADOUT_POSITION_MELEE ) + { + iDynamicParticleEffect = unusual_force_weapon_effect.GetInt(); + } + } + if ( unusual_force_cosmetic_effect.GetInt() > 0 ) + { + if ( iSlot == LOADOUT_POSITION_MISC ) + { + iDynamicParticleEffect = unusual_force_cosmetic_effect.GetInt(); + } + } + } +#endif +#endif + + if ( iDynamicParticleEffect > 0 && !iIsThrowableTrail ) + { + attachedparticlesystem_t *pSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iDynamicParticleEffect ); + + if ( pSystem ) + { +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) + // TF Team Color Particles + static char pszFullname[256]; + if ( GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pSystem->pszSystemName, "_teamcolor_red" )) + { + V_StrSubst( pSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pszFullname, 256 ); + pSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pszFullname ); + + } + else if ( GetTeamNumber() == TF_TEAM_RED && V_stristr( pSystem->pszSystemName, "_teamcolor_blue" )) + { + // Guard against accidentally giving out the blue team color (support tool) + V_StrSubst( pSystem->pszSystemName, "_teamcolor_blue", "_teamcolor_red", pszFullname, 256 ); + pSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pszFullname ); + } +#endif + if ( pSystem ) + { + out_pvecParticleSystems->AddToTail( pSystem ); + } + } + } + + // Scan the particle system + // - Clean up our list in case we fed in bad data from the schema or wherever. + for ( int i = out_pvecParticleSystems->Count() - 1; i >= 0; i-- ) + { + if ( !(*out_pvecParticleSystems)[i] || + !(*out_pvecParticleSystems)[i]->pszSystemName || + !(*out_pvecParticleSystems)[i]->pszSystemName[0] ) + { + out_pvecParticleSystems->FastRemove( i ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::SetParticleSystemsVisible( ParticleSystemState_t nState ) +{ + if ( nState == m_nParticleSystemsCreated ) + { + bool bDirty = false; + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) + CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( this ); + if ( pWeapon ) + { + if ( pWeapon->m_hExtraWearable.Get() ) + { + bDirty = !( pWeapon->m_hExtraWearable->m_nParticleSystemsCreated == nState ); + pWeapon->m_hExtraWearable->m_nParticleSystemsCreated = nState; + } + + if ( pWeapon->m_hExtraWearableViewModel.Get() ) + { + bDirty = !( pWeapon->m_hExtraWearableViewModel->m_nParticleSystemsCreated == nState ); + pWeapon->m_hExtraWearableViewModel->m_nParticleSystemsCreated = nState; + } + } +#endif + + if ( !bDirty ) + { + return; + } + } + + CUtlVector<const attachedparticlesystem_t *> vecParticleSystems; + GetEconParticleSystems( &vecParticleSystems ); + + FOR_EACH_VEC( vecParticleSystems, i ) + { + const attachedparticlesystem_t *pSystem = vecParticleSystems[i]; + Assert( pSystem ); + Assert( pSystem->pszSystemName ); + Assert( pSystem->pszSystemName[0] ); + + // Ignore custom particles. Weapons handle them in custom fashions. + if ( pSystem->iCustomType ) + continue; + + UpdateSingleParticleSystem( nState != PARTICLE_SYSTEM_STATE_NOT_VISIBLE, pSystem ); + } + + m_nParticleSystemsCreated = nState; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconEntity::UpdateSingleParticleSystem( bool bVisible, const attachedparticlesystem_t *pSystem ) +{ + Assert( pSystem ); + + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pLocalPlayer ) + return; + + C_BaseEntity *pEffectOwnerWM = this; + C_BaseEntity *pEffectOwnerVM = NULL; + + bool bExtraWearable = false; + bool bExtraWearableVM = false; + + CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( this ); + if ( pWeapon ) + { + pEffectOwnerVM = pWeapon->GetPlayerOwner() ? pWeapon->GetPlayerOwner()->GetViewModel() : NULL; + if ( pWeapon->m_hExtraWearable.Get() ) + { + pEffectOwnerWM = pWeapon->m_hExtraWearable.Get(); + bExtraWearable = true; + } + + if ( pWeapon->m_hExtraWearableViewModel.Get() ) + { + pEffectOwnerVM = pWeapon->m_hExtraWearableViewModel.Get(); + bExtraWearableVM = true; + } + } + + C_BaseEntity *pEffectOwner = pEffectOwnerWM; + bool bIsVM = false; + C_BasePlayer *pOwner = ToBasePlayer(GetOwnerEntity()); + bool bDrawThisEffect = true; + if ( !pOwner->ShouldDrawThisPlayer() ) + { + // only draw effects designated for this + if ( !pSystem->bDrawInViewModel ) + { + bDrawThisEffect = false; + } + + C_BaseViewModel *pLocalPlayerVM = pLocalPlayer->GetViewModel(); + if ( pLocalPlayerVM && pLocalPlayerVM->GetOwningWeapon() == this ) + { + bIsVM = true; + pEffectOwner = pEffectOwnerVM; + } + } + + const char *pszAttachmentName = pSystem->pszControlPoints[0]; + if ( bIsVM && bExtraWearableVM ) + pszAttachmentName = "attach_fob_v"; + if ( !bIsVM && bExtraWearable ) + pszAttachmentName = "attach_fob"; + + int iAttachment = INVALID_PARTICLE_ATTACHMENT; + if ( pszAttachmentName && pszAttachmentName[0] && pEffectOwner->GetBaseAnimating() ) + { + iAttachment = pEffectOwner->GetBaseAnimating()->LookupAttachment( pszAttachmentName ); + } + + // Stop it on both the viewmodel & the world model, because it may be removed due to first/thirdperson switch + // Get Full name + const CEconItemView *pEconItemView = m_AttributeManager.GetItem(); + static char pszTempName[256]; + static char pszTempNameVM[256]; + const char* pszSystemName = pSystem->pszSystemName; + + + // Weapon Remap for a Base Effect to be used on a specific weapon + if ( pSystem->bUseSuffixName && pEconItemView && pEconItemView->GetItemDefinition()->GetParticleSuffix() ) + { + V_strcpy_safe( pszTempName, pszSystemName ); + V_strcat_safe( pszTempName, "_" ); + V_strcat_safe( pszTempName, pEconItemView->GetItemDefinition()->GetParticleSuffix() ); + pszSystemName = pszTempName; + } + + if ( pSystem->bHasViewModelSpecificEffect ) + { + V_strcpy_safe( pszTempNameVM, pszSystemName ); + V_strcat_safe( pszTempNameVM, "_vm" ); + + // VM doesnt exist so fall back to regular + if ( g_pParticleSystemMgr->FindParticleSystem( pszTempNameVM ) == NULL ) + { + V_strcpy_safe( pszTempNameVM, pszSystemName ); + } + + if ( bIsVM ) + { + pszSystemName = pszTempNameVM; + } + } + + // Check that the effect is valid + if ( g_pParticleSystemMgr->FindParticleSystem( pszSystemName ) == NULL ) + return; + + if ( iAttachment != INVALID_PARTICLE_ATTACHMENT ) + { + pEffectOwnerWM->ParticleProp()->StopParticlesWithNameAndAttachment( pszSystemName, iAttachment, true ); + + if ( pEffectOwnerVM ) + { + if ( pSystem->bHasViewModelSpecificEffect ) + { + pEffectOwnerVM->ParticleProp()->StopParticlesWithNameAndAttachment( pszTempNameVM, iAttachment, true ); + } + pEffectOwnerVM->ParticleProp()->StopParticlesWithNameAndAttachment( pszSystemName, iAttachment, true ); + } + } + else + { + pEffectOwnerWM->ParticleProp()->StopParticlesNamed( pszSystemName, true ); + + if ( pEffectOwnerVM ) + { + if ( pSystem->bHasViewModelSpecificEffect ) + { + pEffectOwnerVM->ParticleProp()->StopParticlesNamed( pszTempNameVM, true ); + } + pEffectOwnerVM->ParticleProp()->StopParticlesNamed( pszSystemName, true ); + } + } + + if ( !bDrawThisEffect ) + return; + + // do not generate a viewmodel effect if there is no weapon else it is in your face + if ( !pWeapon && bIsVM ) + { + Assert( 0 ); + Warning( "Cannot create a Viewmodel Particle Effect [%s] when there is no Viewmodel Weapon", pszSystemName ); + return; + } + + if ( bVisible && pEffectOwner ) + { + HPARTICLEFFECT pEffect = NULL; + // We can't have fastcull on if we want particles attached to us + //if ( !bIsVM ) + { + RemoveEffects( EF_BONEMERGE_FASTCULL ); + } + + if ( iAttachment != INVALID_PARTICLE_ATTACHMENT ) + { + pEffect = pEffectOwner->ParticleProp()->Create( pszSystemName, PATTACH_POINT_FOLLOW, pszAttachmentName ); + } + else + { + // Attachments can fall back to following root bones if the attachment point wasn't found + if ( pSystem->bFollowRootBone ) + { + pEffect = pEffectOwner->ParticleProp()->Create( pszSystemName, PATTACH_ROOTBONE_FOLLOW ); + } + else + { + pEffect = pEffectOwner->ParticleProp()->Create( pszSystemName, PATTACH_ABSORIGIN_FOLLOW ); + } + } + + if ( pEffect ) + { + // update the control points if necessary + for ( int i=1; i<ARRAYSIZE( pSystem->pszControlPoints ); ++i ) + { + const char *pszControlPointName = pSystem->pszControlPoints[i]; + if ( pszControlPointName && pszControlPointName[0] != '\0' ) + { + pEffectOwner->ParticleProp()->AddControlPoint( pEffect, i, this, PATTACH_POINT_FOLLOW, pszControlPointName ); + } + } + + if ( bIsVM ) + { + pEffect->SetIsViewModelEffect( true ); + ClientLeafSystem()->SetRenderGroup( pEffect->RenderHandle(), RENDER_GROUP_VIEW_MODEL_TRANSLUCENT ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::InitializeAsClientEntity( const char *pszModelName, RenderGroup_t renderGroup ) +{ + m_bClientside = true; + return BaseClass::InitializeAsClientEntity( pszModelName, renderGroup ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get an econ material override for the given team. +// Returns: NULL if there is no override. +//----------------------------------------------------------------------------- +IMaterial* CEconEntity::GetEconWeaponMaterialOverride( int iTeam ) +{ + if ( iTeam >= 0 && iTeam < TEAM_VISUAL_SECTIONS && m_MaterialOverrides[ iTeam ].IsValid() ) + return m_MaterialOverrides[ iTeam ]; + + return NULL; +} + + +bool CEconEntity::ShouldDraw() +{ + if ( ShouldHideForVisionFilterFlags() ) + { + return false; + } + + return BaseClass::ShouldDraw(); +} + +bool CEconEntity::ShouldHideForVisionFilterFlags( void ) +{ + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + if ( pItem && pItem->IsValid() ) + { + CEconItemDefinition *pData = pItem->GetStaticData(); + if ( pData ) + { + int nVisionFilterFlags = pData->GetVisionFilterFlags(); + if ( nVisionFilterFlags != 0 ) + { + // Only visible if the local player has an item that allows them to see it (Pyro Goggles) + if ( !IsLocalPlayerUsingVisionFilterFlags( nVisionFilterFlags, true ) ) + { + // They didn't have the correct vision flags + return true; + } + } + } + } + + return false; +} + +bool CEconEntity::IsTransparent( void ) +{ +#ifdef TF_CLIENT_DLL + C_TFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + return pPlayer->IsTransparent(); + } +#endif // TF_CLIENT_DLL + + return BaseClass::IsTransparent(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::ViewModel_IsTransparent( void ) +{ + if ( m_hViewmodelAttachment != NULL && m_hViewmodelAttachment->IsTransparent() ) + { + return true; + } + return IsTransparent(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::ViewModel_IsUsingFBTexture( void ) +{ + if ( m_hViewmodelAttachment != NULL && m_hViewmodelAttachment->UsesPowerOfTwoFrameBufferTexture() ) + { + return true; + } + return UsesPowerOfTwoFrameBufferTexture(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::IsOverridingViewmodel( void ) +{ + bool bUseOverride = (GetTeamNumber() >= 0 && GetTeamNumber() < TEAM_VISUAL_SECTIONS) && m_MaterialOverrides[GetTeamNumber()].IsValid(); + bUseOverride = bUseOverride || (m_hViewmodelAttachment != NULL) || ( m_AttributeManager.GetItem()->GetStaticData()->GetNumAttachedModels( GetTeamNumber() ) > 0 ); + return bUseOverride; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconEntity::DrawOverriddenViewmodel( C_BaseViewModel *pViewmodel, int flags ) +{ + int ret = 0; +#ifndef DOTA_DLL + bool bIsAttachmentTranslucent = m_hViewmodelAttachment.Get() ? m_hViewmodelAttachment->IsTransparent() : false; + bool bUseOverride = false; + + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + bool bAttachesToHands = ( pItem->IsValid() && (pItem->GetStaticData()->ShouldAttachToHands() || pItem->GetStaticData()->ShouldAttachToHandsVMOnly())); + + // If the attachment is translucent, we need to render the viewmodel first + if ( bIsAttachmentTranslucent ) + { + ret = pViewmodel->DrawOverriddenViewmodel( flags ); + } + + if ( flags & STUDIO_RENDER ) + { + // If there is some other material override, it's probably the client asking for us to render invuln or the + // spy cloaking. Those are way more important than ours, so do them instead. + IMaterial* pOverrideMaterial = NULL; + OverrideType_t nDontcare = OVERRIDE_NORMAL; + modelrender->GetMaterialOverride( &pOverrideMaterial, &nDontcare ); + bool bIgnoreOverride = pOverrideMaterial != NULL; + + bUseOverride = !bIgnoreOverride && (GetTeamNumber() >= 0 && GetTeamNumber() < TEAM_VISUAL_SECTIONS) && m_MaterialOverrides[GetTeamNumber()].IsValid(); + if ( bUseOverride ) + { + modelrender->ForcedMaterialOverride( m_MaterialOverrides[GetTeamNumber()] ); + flags |= STUDIO_NO_OVERRIDE_FOR_ATTACH; + } + + if ( m_hViewmodelAttachment ) + { + m_hViewmodelAttachment->RemoveEffects( EF_NODRAW ); + m_hViewmodelAttachment->DrawModel( flags ); + m_hViewmodelAttachment->AddEffects( EF_NODRAW ); + } + + // if we are attached to the hands, then we DO NOT want have an override material when we draw our view model + if ( bAttachesToHands && bUseOverride ) + { + modelrender->ForcedMaterialOverride( NULL ); + bUseOverride = false; + } + } + + if ( !bIsAttachmentTranslucent ) + { + ret = pViewmodel->DrawOverriddenViewmodel( flags ); + } + + if ( bUseOverride ) + { + modelrender->ForcedMaterialOverride( NULL ); + } +#endif // !defined( DOTA_DLL ) + return ret; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ) +{ + if ( !BaseClass::OnInternalDrawModel( pInfo ) ) + return false; + + DrawEconEntityAttachedModels( this, this, pInfo, kAttachedModelDisplayFlag_WorldModel ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconEntity::LookupAttachment( const char *pAttachmentName ) +{ + if ( m_hViewmodelAttachment ) + return m_hViewmodelAttachment->LookupAttachment( pAttachmentName ); + + return BaseClass::LookupAttachment( pAttachmentName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::GetAttachment( int number, matrix3x4_t &matrix ) +{ + if ( m_hViewmodelAttachment ) + return m_hViewmodelAttachment->GetAttachment( number, matrix ); + + return BaseClass::GetAttachment( number, matrix ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::GetAttachment( int number, Vector &origin ) +{ + if ( m_hViewmodelAttachment ) + return m_hViewmodelAttachment->GetAttachment( number, origin ); + + return BaseClass::GetAttachment( number, origin ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::GetAttachment( int number, Vector &origin, QAngle &angles ) +{ + if ( m_hViewmodelAttachment ) + return m_hViewmodelAttachment->GetAttachment( number, origin, angles ); + + return BaseClass::GetAttachment( number, origin, angles ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconEntity::GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ) +{ + if ( m_hViewmodelAttachment ) + return m_hViewmodelAttachment->GetAttachmentVelocity( number, originVel, angleVel ); + + return BaseClass::GetAttachmentVelocity( number, originVel, angleVel ); +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Hides or shows masked bodygroups associated with this item. +//----------------------------------------------------------------------------- +bool CEconEntity::UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState ) +{ + if ( !pOwner ) + return false; + + CAttributeContainer *pCont = GetAttributeContainer(); + if ( !pCont ) + return false; + + CEconItemView *pItem = pCont->GetItem(); + if ( !pItem ) + return false; + + const CEconItemDefinition *pItemDef = pItem->GetItemDefinition(); + if ( !pItemDef ) + return false; + + int iNumBodyGroups = pItemDef->GetNumModifiedBodyGroups( 0 ); + for ( int i=0; i<iNumBodyGroups; ++i ) + { + int iBody = 0; + const char *pszBodyGroup = pItemDef->GetModifiedBodyGroup( 0, i, iBody ); + if ( iBody != iState ) + continue; + + int iBodyGroup = pOwner->FindBodygroupByName( pszBodyGroup ); + + if ( iBodyGroup == -1 ) + continue; + + pOwner->SetBodygroup( iBodyGroup, iState ); + } + + // Handle per-style bodygroup hiding + const CEconStyleInfo *pStyle = pItemDef->GetStyleInfo( pItem->GetStyle() ); + if ( pStyle ) + { + FOR_EACH_VEC( pStyle->GetAdditionalHideBodygroups(), i ) + { + int iBodyGroup = pOwner->FindBodygroupByName( pStyle->GetAdditionalHideBodygroups()[i] ); + + if ( iBodyGroup == -1 ) + continue; + + pOwner->SetBodygroup( iBodyGroup, iState ); + } + + // should we override this model bodygroup + if ( pStyle->GetBodygroupName() != NULL ) + { + int iBodyGroup = pOwner->FindBodygroupByName( pStyle->GetBodygroupName() ); + if ( iBodyGroup != -1 ) + { + SetBodygroup( iBodyGroup, pStyle->GetBodygroupSubmodelIndex() ); + } + } + } + + // Handle world model bodygroup overrides + int iBodyOverride = pItemDef->GetWorldmodelBodygroupOverride( pOwner->GetTeamNumber() ); + int iBodyStateOverride = pItemDef->GetWorldmodelBodygroupStateOverride( pOwner->GetTeamNumber() ); + if ( iBodyOverride > -1 && iBodyStateOverride > -1 ) + { + pOwner->SetBodygroup( iBodyOverride, iBodyStateOverride ); + } + + // Handle view model bodygroup overrides + iBodyOverride = pItemDef->GetViewmodelBodygroupOverride( pOwner->GetTeamNumber() ); + iBodyStateOverride = pItemDef->GetViewmodelBodygroupStateOverride( pOwner->GetTeamNumber() ); + if ( iBodyOverride > -1 && iBodyStateOverride > -1 ) + { + CBasePlayer *pPlayer = ToBasePlayer( pOwner ); + if ( pPlayer ) + { + CBaseViewModel *pVM = pPlayer->GetViewModel(); + if ( pVM && pVM->GetModelPtr() ) + { + pVM->SetBodygroup( iBodyOverride, iBodyStateOverride ); + } + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseAttributableItem::CBaseAttributableItem() +{ +} diff --git a/game/shared/econ/econ_entity.h b/game/shared/econ/econ_entity.h new file mode 100644 index 0000000..e0c2453 --- /dev/null +++ b/game/shared/econ/econ_entity.h @@ -0,0 +1,229 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef ECON_ENTITY_H +#define ECON_ENTITY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ihasattributes.h" +#include "ihasowner.h" +#include "attribute_manager.h" +#include "econ_item_view.h" + +#if defined( CLIENT_DLL ) +#define CEconEntity C_EconEntity +#define CBaseAttributableItem C_BaseAttributableItem + +// Additional attachments. +struct AttachedModelData_t +{ + const model_t *m_pModel; + int m_iModelDisplayFlags; +}; + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconEntity : public CBaseAnimating, public IHasAttributes +{ + DECLARE_CLASS( CEconEntity, CBaseAnimating ); +public: + DECLARE_NETWORKCLASS(); + DECLARE_DATADESC(); + CEconEntity(); + ~CEconEntity(); + + void InitializeAttributes( void ); + void DebugDescribe( void ); + Activity TranslateViewmodelHandActivity( Activity actBase ); + virtual void UpdateOnRemove( void ); + + virtual CStudioHdr * OnNewModel(); + +#if !defined( CLIENT_DLL ) + virtual void GiveTo( CBaseEntity *pOther ) {} + void OnOwnerClassChange( void ); + void UpdateModelToClass( void ); + void PlayAnimForPlaybackEvent( wearableanimplayback_t iPlayback ); + virtual int CalculateVisibleClassFor( CBaseCombatCharacter *pPlayer ); + +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) + void MarkAttachedEntityAsValidated() { m_bValidatedAttachedEntity = true; } +#endif // TF_DLL || TF_CLIENT_DLL + +#else + enum ParticleSystemState_t + { + PARTICLE_SYSTEM_STATE_NOT_VISIBLE, + PARTICLE_SYSTEM_STATE_VISIBLE, + PARTICLE_SYSTEM_STATE_VISIBLE_VM + }; + + virtual void Release(); + virtual void SetDormant( bool bDormant ); + virtual void OnPreDataChanged( DataUpdateType_t type ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool ShouldShowToolTip( void ) { return true; } + virtual bool InitializeAsClientEntity( const char *pszModelName, RenderGroup_t renderGroup ); + virtual bool OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ); + virtual IMaterial *GetEconWeaponMaterialOverride( int iTeam ) OVERRIDE; + virtual void FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ); + virtual bool OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ); + bool InternalFireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ); + + // Custom flex controllers + virtual bool UsesFlexDelayedWeights( void ); + virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ); + float m_flFlexDelayTime; + float * m_flFlexDelayedWeight; + int m_cFlexDelayedWeight; + + // Custom particle attachments + bool HasCustomParticleSystems( void ) const; + void UpdateParticleSystems( void ); + virtual bool ShouldDrawParticleSystems( void ); + void SetParticleSystemsVisible( ParticleSystemState_t bVisible ); + void UpdateSingleParticleSystem( bool bVisible, const attachedparticlesystem_t *pSystem ); + virtual void UpdateAttachmentModels( void ); + virtual bool AttachmentModelsShouldBeVisible( void ) { return true; } + void GetEconParticleSystems( CUtlVector<const attachedparticlesystem_t *> *out_pvecParticleSystems ) const; + + // Model swaping + bool ShouldDraw( void ); + bool ShouldHideForVisionFilterFlags( void ); + + virtual bool IsTransparent( void ) OVERRIDE; + + // Viewmodel overriding + virtual bool ViewModel_IsTransparent( void ); + virtual bool ViewModel_IsUsingFBTexture( void ); + virtual bool IsOverridingViewmodel( void ); + virtual int DrawOverriddenViewmodel( C_BaseViewModel *pViewmodel, int flags ); + + // Attachments + bool WantsToOverrideViewmodelAttachments( void ) { return (m_hViewmodelAttachment != NULL); } + virtual int LookupAttachment( const char *pAttachmentName ); + virtual bool GetAttachment( const char *szName, Vector &absOrigin ) { return BaseClass::GetAttachment(szName,absOrigin); } + virtual bool GetAttachment( const char *szName, Vector &absOrigin, QAngle &absAngles ) { return BaseClass::GetAttachment(szName,absOrigin,absAngles); } + virtual bool GetAttachment( int number, matrix3x4_t &matrix ); + virtual bool GetAttachment( int number, Vector &origin ); + virtual bool GetAttachment( int number, Vector &origin, QAngle &angles ); + virtual bool GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ); + + C_BaseAnimating *GetViewmodelAttachment( void ) { return m_hViewmodelAttachment.Get(); } + virtual void ViewModelAttachmentBlending( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) {} + + void SetWaitingToLoad( bool bWaiting ); + + virtual bool ValidateEntityAttachedToPlayer( bool &bShouldRetry ); + + virtual void SetMaterialOverride( int team, const char *pszMaterial ); + virtual void SetMaterialOverride( int team, CMaterialReference &ref ); + + // Deal with recording + virtual void GetToolRecordingState( KeyValues *msg ); + +#endif + +public: + // IHasAttributes + CAttributeManager *GetAttributeManager( void ) { return &m_AttributeManager; } + CAttributeContainer *GetAttributeContainer( void ) { return &m_AttributeManager; } + const CAttributeContainer *GetAttributeContainer( void ) const { return &m_AttributeManager; } + CBaseEntity *GetAttributeOwner( void ) { return GetOwnerEntity(); } + CAttributeList *GetAttributeList( void ) { return m_AttributeManager.GetItem()->GetAttributeList(); } + virtual void ReapplyProvision( void ); + + virtual bool UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState ); + +protected: + virtual Activity TranslateViewmodelHandActivityInternal( Activity actBase ) { return actBase; } + +protected: + CNetworkVarEmbedded( CAttributeContainer, m_AttributeManager ); + +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) + CNetworkVar( bool, m_bValidatedAttachedEntity ); +#endif // TF_DLL || TF_CLIENT_DLL + +#ifdef CLIENT_DLL + bool m_bClientside; + ParticleSystemState_t m_nParticleSystemsCreated; + CMaterialReference m_MaterialOverrides[TEAM_VISUAL_SECTIONS]; + CHandle<C_BaseAnimating> m_hViewmodelAttachment; + int m_iOldTeam; + bool m_bAttachmentDirty; + int m_nUnloadedModelIndex; + int m_iNumOwnerValidationRetries; +#endif + + bool m_bHasParticleSystems; + EHANDLE m_hOldProvidee; + +#ifdef GAME_DLL + int m_iOldOwnerClass; // Used to detect class changes on items that have per-class models +#endif + +protected: +#ifdef CLIENT_DLL + +public: + + CUtlVector<AttachedModelData_t> m_vecAttachedModels; + +#endif // CLIENT_DLL +}; + +#define ITEM_PICKUP_BOX_BLOAT 24 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CBaseAttributableItem : public CEconEntity +{ + DECLARE_CLASS( CBaseAttributableItem, CEconEntity ); +public: + DECLARE_NETWORKCLASS(); + DECLARE_DATADESC(); + + CBaseAttributableItem(); +}; + +#if defined( CLIENT_DLL ) +#ifndef DOTA_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_ViewmodelAttachmentModel : public C_BaseAnimating, public IHasOwner +{ + DECLARE_CLASS( C_ViewmodelAttachmentModel, C_BaseAnimating ); +public: + void SetOuter( CEconEntity *pOuter ); + CHandle<CEconEntity> GetOuter( void ) { return m_hOuter; } + bool InitializeAsClientEntity( const char *pszModelName, RenderGroup_t renderGroup ); + int InternalDrawModel( int flags ); + bool OnPostInternalDrawModel( ClientModelRenderInfo_t *pInfo ); + virtual void StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ); + + virtual CBaseEntity *GetOwnerViaInterface( void ) { return GetOuter()->GetAttributeOwner(); } + + virtual void FormatViewModelAttachment( int nAttachment, matrix3x4_t &attachmentToWorld ); + + virtual int GetSkin( void ); + +private: + CHandle<CEconEntity> m_hOuter; + bool m_bAlwaysFlip; +}; +#endif // !defined( DOTA_DLL ) +#endif // defined( CLIENT_DLL ) + +#endif // ECON_ENTITY_H diff --git a/game/shared/econ/econ_entity_creation.cpp b/game/shared/econ/econ_entity_creation.cpp new file mode 100644 index 0000000..2461005 --- /dev/null +++ b/game/shared/econ/econ_entity_creation.cpp @@ -0,0 +1,180 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "econ_entity_creation.h" +#include "utldict.h" +#include "filesystem.h" +#include "gamestringpool.h" +#include "KeyValues.h" +#include "attribute_manager.h" +#include "vgui/ILocalize.h" +#include "tier3/tier3.h" +#include "util_shared.h" + +#ifdef TF_CLIENT_DLL +#include "c_tf_player.h" +#endif // TF_CLIENT_DLL + +//================================================================================== +// GENERATION SYSTEM +//================================================================================== +CItemGeneration g_ItemGenerationSystem; +CItemGeneration *ItemGeneration( void ) +{ + return &g_ItemGenerationSystem; +} + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CItemGeneration::CItemGeneration( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Generate a random item matching the specified criteria +//----------------------------------------------------------------------------- +CBaseEntity *CItemGeneration::GenerateRandomItem( CItemSelectionCriteria *pCriteria, const Vector &vecOrigin, const QAngle &vecAngles ) +{ + entityquality_t iQuality; + int iChosenItem = ItemSystem()->GenerateRandomItem( pCriteria, &iQuality ); + if ( iChosenItem == INVALID_ITEM_DEF_INDEX ) + return NULL; + + return SpawnItem( iChosenItem, vecOrigin, vecAngles, pCriteria->GetItemLevel(), iQuality, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Generate a random item matching the specified definition index +//----------------------------------------------------------------------------- +CBaseEntity *CItemGeneration::GenerateItemFromDefIndex( int iDefIndex, const Vector &vecOrigin, const QAngle &vecAngles ) +{ + return SpawnItem( iDefIndex, vecOrigin, vecAngles, 1, AE_UNIQUE, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Generate an item from the specified item data +//----------------------------------------------------------------------------- +CBaseEntity *CItemGeneration::GenerateItemFromScriptData( const CEconItemView *pData, const Vector &vecOrigin, const QAngle &vecAngles, const char *pszOverrideClassName ) +{ + return SpawnItem( pData, vecOrigin, vecAngles, pszOverrideClassName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Generate the base item for a class's loadout slot +//----------------------------------------------------------------------------- +CBaseEntity *CItemGeneration::GenerateBaseItem( struct baseitemcriteria_t *pCriteria ) +{ + int iChosenItem = ItemSystem()->GenerateBaseItem( pCriteria ); + if ( iChosenItem == INVALID_ITEM_DEF_INDEX ) + return NULL; + + return SpawnItem( iChosenItem, vec3_origin, vec3_angle, 1, AE_NORMAL, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new instance of the chosen item +//----------------------------------------------------------------------------- +CBaseEntity *CItemGeneration::SpawnItem( int iChosenItem, const Vector &vecAbsOrigin, const QAngle &vecAbsAngles, int iItemLevel, entityquality_t entityQuality, const char *pszOverrideClassName ) +{ + CEconItemDefinition *pData = ItemSystem()->GetStaticDataForItemByDefIndex( iChosenItem ); + if ( !pData ) + return NULL; + + if ( !pszOverrideClassName ) + { + pszOverrideClassName = pData->GetItemClass(); + } + + if ( !pszOverrideClassName ) + return NULL; + + CBaseEntity *pItem = CreateEntityByName( pszOverrideClassName ); + if ( !pItem ) + return NULL; + + // Set the item level & quality + IHasAttributes *pItemInterface = GetAttribInterface( pItem ); + Assert( pItemInterface ); + if ( pItemInterface ) + { + // Setup the script item. Don't generate attributes here, because it'll be done during entity spawn. + CEconItemView *pScriptItem = pItemInterface->GetAttributeContainer()->GetItem(); + pScriptItem->Init( iChosenItem, entityQuality, iItemLevel, false ); + } + + return PostSpawnItem( pItem, pItemInterface, vecAbsOrigin, vecAbsAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create a base entity for the specified item data +//----------------------------------------------------------------------------- +CBaseEntity *CItemGeneration::SpawnItem( const CEconItemView *pData, const Vector &vecAbsOrigin, const QAngle &vecAbsAngles, const char *pszOverrideClassName ) +{ + if ( !pData->GetStaticData() ) + return NULL; + + if ( !pszOverrideClassName ) + { + pszOverrideClassName = pData->GetStaticData()->GetItemClass(); + } + + if ( !pszOverrideClassName ) + return NULL; + + CBaseEntity *pItem = CreateEntityByName( pszOverrideClassName ); + if ( !pItem ) + return NULL; + + // Set the item level & quality + IHasAttributes *pItemInterface = GetAttribInterface( pItem ); + Assert( pItemInterface ); + if ( pItemInterface ) + { + pItemInterface->GetAttributeContainer()->SetItem( pData ); + } + + return PostSpawnItem( pItem, pItemInterface, vecAbsOrigin, vecAbsAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CItemGeneration::PostSpawnItem( CBaseEntity *pItem, IHasAttributes *pItemInterface, const Vector &vecAbsOrigin, const QAngle &vecAbsAngles ) +{ +#ifdef CLIENT_DLL + const char *pszPlayerModel = NULL; + if ( pItemInterface ) + { + CEconItemView *pScriptItem = pItemInterface->GetAttributeContainer()->GetItem(); + + int iClass = 0; + int iTeam = 0; +#ifdef TF_CLIENT_DLL + C_TFPlayer *pTFPlayer = ToTFPlayer( GetPlayerByAccountID( pScriptItem->GetAccountID() ) ); + if ( pTFPlayer ) + { + iClass = pTFPlayer->GetPlayerClass()->GetClassIndex(); + iTeam = pTFPlayer->GetTeamNumber(); + } +#endif // TF_CLIENT_DLL + pszPlayerModel = pScriptItem->GetPlayerDisplayModel( iClass, iTeam ); + } + + // If we create a clientside item, we need to force it to initialize attributes + if ( pItem->InitializeAsClientEntity( pszPlayerModel, RENDER_GROUP_OPAQUE_ENTITY ) == false ) + return NULL; +#endif // CLIENT_DLL + + pItem->SetAbsOrigin( vecAbsOrigin ); + pItem->SetAbsAngles( vecAbsAngles ); + + pItem->Spawn(); + pItem->Activate(); + return pItem; +} + diff --git a/game/shared/econ/econ_entity_creation.h b/game/shared/econ/econ_entity_creation.h new file mode 100644 index 0000000..2abf8c6 --- /dev/null +++ b/game/shared/econ/econ_entity_creation.h @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef ITEM_CREATION_H +#define ITEM_CREATION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "igamesystem.h" +#include "econ_item_system.h" +#include "econ_entity.h" + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) +#include "tf_shareddefs.h" +#endif + +//----------------------------------------------------------------------------- +// Purpose: Game system that handles initializing the item system, and generating items as full game entities +//----------------------------------------------------------------------------- +class CItemGeneration : public CAutoGameSystem +{ +public: + CItemGeneration( void ); + + // Generate a random item matching the specified criteria + CBaseEntity *GenerateRandomItem( CItemSelectionCriteria *pCriteria, const Vector &vecOrigin, const QAngle &vecAngles ); + + // Generate a random item matching the specified definition index + CBaseEntity *GenerateItemFromDefIndex( int iDefIndex, const Vector &vecOrigin, const QAngle &vecAngles ); + + // Generate an item from the specified item data + CBaseEntity *GenerateItemFromScriptData( const CEconItemView *pData, const Vector &vecOrigin, const QAngle &vecAngles, const char *pszOverrideClassName ); + + // Generate the base item for a class's loadout slot + CBaseEntity *GenerateBaseItem( struct baseitemcriteria_t *pCriteria ); + +private: + // Create a new instance of the chosen item + CBaseEntity *SpawnItem( int iChosenItem, const Vector &vecAbsOrigin, const QAngle &vecAbsAngles, int iItemLevel, entityquality_t entityQuality, const char *pszOverrideClassName ); + CBaseEntity *SpawnItem( const CEconItemView *pData, const Vector &vecAbsOrigin, const QAngle &vecAbsAngles, const char *pszOverrideClassName ); + CBaseEntity *PostSpawnItem( CBaseEntity *pItem, IHasAttributes *pItemInterface, const Vector &vecAbsOrigin, const QAngle &vecAbsAngles ); +}; + +extern CItemGeneration *ItemGeneration( void ); + +#endif // ITEM_CREATION_H
\ No newline at end of file diff --git a/game/shared/econ/econ_experiment.cpp b/game/shared/econ/econ_experiment.cpp new file mode 100644 index 0000000..b6118b8 --- /dev/null +++ b/game/shared/econ/econ_experiment.cpp @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "econ_experiment.h" + +using namespace GCSDK; + +#ifdef GC_DLL +IMPLEMENT_CLASS_MEMPOOL( CEconExperiment, 10 * 10000, UTLMEMORYPOOL_GROW_SLOW ); +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" diff --git a/game/shared/econ/econ_experiment.h b/game/shared/econ/econ_experiment.h new file mode 100644 index 0000000..20a09a2 --- /dev/null +++ b/game/shared/econ/econ_experiment.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds the CEconExperiment +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef TFEXPERIMENT_H +#define TFEXPERIMENT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gcsdk/schemasharedobject.h" + +//--------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------- +class CEconExperiment : public GCSDK::CSchemaSharedObject< CSchExperiment, k_EEconTypeExperiment > +{ +#ifdef GC_DLL + DECLARE_CLASS_MEMPOOL( CEconExperiment ); +#endif + +}; + +#endif // TFEXPERIMENT_H diff --git a/game/shared/econ/econ_game_account.cpp b/game/shared/econ/econ_game_account.cpp new file mode 100644 index 0000000..2c8a923 --- /dev/null +++ b/game/shared/econ/econ_game_account.cpp @@ -0,0 +1,17 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Code for the CEconGameAccount object +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +using namespace GCSDK; + +#ifdef GC_DLL +IMPLEMENT_CLASS_MEMPOOL( CEconGameAccount, 10 * 1000, UTLMEMORYPOOL_GROW_SLOW ); +#endif +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + diff --git a/game/shared/econ/econ_game_account.h b/game/shared/econ/econ_game_account.h new file mode 100644 index 0000000..a6b2a84 --- /dev/null +++ b/game/shared/econ/econ_game_account.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds the CEconGameAccount object +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ECON_GAME_ACCOUNT_H +#define ECON_GAME_ACCOUNT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gcsdk/schemasharedobject.h" +#include "rtime.h" + +enum +{ + kGameAccountFlags_ConvertedUniques = 1 << 0, + kGameAccountFlags_MadeFirstPurchase = 1 << 1, + kGameAccountFlags_ConvertItemFlagsToOrigin = 1 << 2, + kGameAccountFlags_ConvertPackageItemGrants = 1 << 3, + kGameAccountFlags_RemoveCafeOrSchoolItems = 1 << 4, + kGameAccountFlags_CleanupItemNames = 1 << 5, + kGameAccountFlags_NeedToChooseMostHelpfulFriend = 1 << 6, + kGameAccountFlags_DONT_USE_THIS_BUI_LIES = 1 << 7, // some accounts might have this set! it used to be the "needs to thank a friend" bit + kGameAccountFlags_OwnedGameServersDisabled = 1 << 8, + kGameAccountFlags_UpdatedEquippedSlots = 1 << 9, + kGameAccountFlags_MadeFirstWebPurchase = 1 << 10, + kGameAccountFlags_UpdatedPresetOriginalItemIDs = 1 << 11, + kGameAccountFlags_GC_UpgradedToPremium = 1 << 12, // we did something (used an item, whatever) on the GC that means the GC has decided we're premium regardless of what Steam says + // Deprecated + // kGameAccountFlags_InitializedSkillRating = 1 << 13, + // kGameAccountFlags_InitializedSkillRating6v6 = 1 << 14, + // kGameAccountFlags_InitializedSkillRating9v9 = 1 << 15, + kGameAccountFlags_InitializedKickBucket = 1 << 16, +}; + +//--------------------------------------------------------------------------------- +// Purpose: All the account-level information that the GC tracks +//--------------------------------------------------------------------------------- +class CEconGameAccount : public GCSDK::CSchemaSharedObject< CSchGameAccount, k_EEconTypeGameAccount > +{ +#ifdef GC_DLL + DECLARE_CLASS_MEMPOOL( CEconGameAccount ); +#endif + +public: + CEconGameAccount() {} + CEconGameAccount( uint32 unAccountID ) + { + Obj().m_unAccountID = unAccountID; + Obj().m_rtime32FirstPlayed = CRTime::RTime32TimeCur(); + } +}; + +#endif //ECON_GAME_ACCOUNT_H diff --git a/game/shared/econ/econ_game_account_client.cpp b/game/shared/econ/econ_game_account_client.cpp new file mode 100644 index 0000000..d92e6cf --- /dev/null +++ b/game/shared/econ/econ_game_account_client.cpp @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Code for the CEconGameAccountClient object +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +using namespace GCSDK; + +#ifdef GC +IMPLEMENT_CLASS_MEMPOOL( CEconGameAccountClient, 10 * 1000, UTLMEMORYPOOL_GROW_SLOW ); +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + diff --git a/game/shared/econ/econ_game_account_client.h b/game/shared/econ/econ_game_account_client.h new file mode 100644 index 0000000..3720868 --- /dev/null +++ b/game/shared/econ/econ_game_account_client.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds the CEconGameAccountClient object +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ECON_GAME_ACCOUNT_CLIENT_H +#define ECON_GAME_ACCOUNT_CLIENT_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gcsdk/protobufsharedobject.h" +#include "base_gcmessages.pb.h" + +//--------------------------------------------------------------------------------- +// Purpose: All the account-level information that the GC tracks +//--------------------------------------------------------------------------------- +class CEconGameAccountClient : public GCSDK::CProtoBufSharedObject< CSOEconGameAccountClient, k_EEconTypeGameAccountClient > +{ +#ifdef GC + DECLARE_CLASS_MEMPOOL( CEconGameAccountClient ); +public: + virtual bool BIsDatabaseBacked() const { return false; } +#endif +}; + +#endif //ECON_GAME_ACCOUNT_CLIENT_H diff --git a/game/shared/econ/econ_game_account_server.cpp b/game/shared/econ/econ_game_account_server.cpp new file mode 100644 index 0000000..4ce65ba --- /dev/null +++ b/game/shared/econ/econ_game_account_server.cpp @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Code for the CEconGameAccount object +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "econ_game_account_server.h" + +using namespace GCSDK; + +#ifdef GC_DLL +//--------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------- +IMPLEMENT_CLASS_MEMPOOL( CEconGameServerAccount, 100, UTLMEMORYPOOL_GROW_SLOW ); + +void GameServerAccount_GenerateIdentityToken( char* pIdentityToken, uint32 unMaxChars ) +{ + static const char s_ValidChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890./+!$%^-_+?<>()&~:"; + const int nLastValidIndex = ARRAYSIZE(s_ValidChars) - 2; // last = size - 1, minus another one for null terminator + + // create a randomized token + for ( uint32 i = 0; i < unMaxChars - 1; ++i ) + { + pIdentityToken[i] = s_ValidChars[ RandomInt( 0, nLastValidIndex ) ]; + } + pIdentityToken[unMaxChars - 1] = 0; +} + +//--------------------------------------------------------------------------------- +// Purpose: Selective account-level data for game servers +//--------------------------------------------------------------------------------- +IMPLEMENT_CLASS_MEMPOOL( CEconGameAccountForGameServers, 10 * 1000, UTLMEMORYPOOL_GROW_SLOW ); +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" diff --git a/game/shared/econ/econ_game_account_server.h b/game/shared/econ/econ_game_account_server.h new file mode 100644 index 0000000..01c1cc4 --- /dev/null +++ b/game/shared/econ/econ_game_account_server.h @@ -0,0 +1,110 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds the CEconGameServerAccount object +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ECON_GAME_SERVER_ACCOUNT_H +#define ECON_GAME_SERVER_ACCOUNT_H +#ifdef _WIN32 +#pragma once +#endif + +enum eGameServerOrigin +{ + kGSAOrigin_Player = 0, + kGSAOrigin_Support = 1, + kGSAOrigin_AutoRegister = 2, // for valve-owned servers +}; + +enum eGameServerScoreStanding +{ + kGSStanding_Good, + kGSStanding_Bad, +}; + +enum eGameServerScoreStandingTrend +{ + kGSStandingTrend_Up, + kGSStandingTrend_SteadyUp, + kGSStandingTrend_Steady, + kGSStandingTrend_SteadyDown, + kGSStandingTrend_Down, +}; + +#ifdef GC +#include "gcsdk/schemasharedobject.h" + +//--------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------- +class CEconGameServerAccount : public GCSDK::CSchemaSharedObject< CSchGameServerAccount, k_EEconTypeGameServerAccount > +{ +#ifdef GC_DLL + DECLARE_CLASS_MEMPOOL( CEconGameServerAccount ); +#endif + +public: + CEconGameServerAccount() {} + CEconGameServerAccount( uint32 unAccountID ) + { + Obj().m_unAccountID = unAccountID; + } +}; + +void GameServerAccount_GenerateIdentityToken( char* pIdentityToken, uint32 unMaxChars ); +#endif // GC + +inline const char *GameServerAccount_GetStandingString( eGameServerScoreStanding standing ) +{ + const char *pStanding = "Good"; + switch ( standing ) + { + case kGSStanding_Good: + pStanding = "Good"; + break; + case kGSStanding_Bad: + pStanding = "Bad"; + break; + } // switch + return pStanding; +} + +inline const char *GameServerAccount_GetStandingTrendString( eGameServerScoreStandingTrend trend ) +{ + const char *pStandingTrend = "Steady"; + switch ( trend ) + { + case kGSStandingTrend_Up: + pStandingTrend = "Upward Fast"; + break; + case kGSStandingTrend_SteadyUp: + pStandingTrend = "Slightly Upward"; + break; + case kGSStandingTrend_Steady: + pStandingTrend = "Steady"; + break; + case kGSStandingTrend_SteadyDown: + pStandingTrend = "Slightly Downward"; + break; + case kGSStandingTrend_Down: + pStandingTrend = "Downward Fast"; + break; + } // switch + return pStandingTrend; +} + +//--------------------------------------------------------------------------------- +// Purpose: Selective account-level data for game servers +//--------------------------------------------------------------------------------- +class CEconGameAccountForGameServers : public GCSDK::CProtoBufSharedObject < CSOEconGameAccountForGameServers, k_EEconTypeGameAccountForGameServers > +{ +#ifdef GC + DECLARE_CLASS_MEMPOOL( CEconGameAccountForGameServers ); +public: + virtual bool BIsDatabaseBacked() const { return false; } +#endif +}; + +#endif //ECON_GAME_SERVER_ACCOUNT_H diff --git a/game/shared/econ/econ_gcmessages.h b/game/shared/econ/econ_gcmessages.h new file mode 100644 index 0000000..c5e5d7a --- /dev/null +++ b/game/shared/econ/econ_gcmessages.h @@ -0,0 +1,345 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This file defines all of our over-the-wire net protocols for the +// Game Coordinator for the item system. Note that we never use types +// with undefined length (like int). Always use an explicit type +// (like int32). +// +//============================================================================= + +#ifndef ITEM_GCMESSAGES_H +#define ITEM_GCMESSAGES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "econ_gcmessages.pb.h" + +#pragma pack( push, 1 ) + + +// generic zero-length message struct +struct MsgGCEmpty_t +{ + +}; + +// k_EMsgGCSetItemPosition +struct MsgGCSetItemPosition_t +{ + uint64 m_unItemID; + uint32 m_unNewPosition; +}; + +// k_EMsgGCCraft +struct MsgGCCraft_t +{ + int16 m_nRecipeDefIndex; + uint16 m_nItemCount; + // list of m_nItemCount uint64 item IDs +}; + +// k_EMsgGCDelete +struct MsgGCDelete_t +{ + uint64 m_unItemID; +}; + +// k_EMsgGCCraftResponse +struct MsgGCStandardResponse_t +{ + int16 m_nResponseIndex; + uint32 m_eResponse; +}; + +// k_EMsgGCVerifyCacheSubscription +struct MsgGCVerifyCacheSubscription_t +{ + uint64 m_ulSteamID; +}; + +// k_EMsgGCNameItem +struct MsgGCNameItem_t +{ + uint64 m_unToolItemID; // the Nametag item + uint64 m_unSubjectItemID; // the item to be renamed + bool m_bDescription; + // Varchar: Item name +}; + +// k_EMsgGCNameBaseItem +struct MsgGCNameBaseItem_t +{ + uint64 m_unToolItemID; // the Nametag item + uint32 m_unBaseItemDefinitionID; // the base item definition to be renamed + bool m_bDescription; + // Varchar: Item name +}; + +// k_EMsgGCUnlockCrate +struct MsgGCUnlockCrate_t +{ + uint64 m_unToolItemID; // the crate key + uint64 m_unSubjectItemID; // the crate to be decoded +}; + +// k_EMsgGCPaintItem +struct MsgGCPaintItem_t +{ + uint64 m_unToolItemID; // the Paint Can item + uint64 m_unSubjectItemID; // the item to be painted +}; + +// k_EMsgGCGiftWrapItem +struct MsgGCGiftWrapItem_t +{ + uint64 m_unToolItemID; // the Gift Wrap item + uint64 m_unSubjectItemID; // the item to be wrapped +}; + +// k_EMsgGCDeliverGift +struct MsgGCDeliverGift_t +{ + uint64 m_unGiftID; + uint64 m_ulGiverSteamID; + uint64 m_ulTargetSteamID; +}; + +// k_EMsgGCUnwrapGiftRequest +struct MsgGCUnwrapGiftRequest_t +{ + uint64 m_unItemID; +}; + +// k_EMsgGCMOTDRequest +struct MsgGCMOTDRequest_t +{ + RTime32 m_nLastMOTDRequest; // Time at which the client last asked for MOTDs. GC will send back all MOTDs posted since. + int16 m_eLanguage; +}; + +// k_EMsgGCMOTDRequestResponse +struct MsgGCMOTDRequestResponse_t +{ + int16 m_nEntries; +}; + +// k_EMsgGCCustomizeItemTexture +struct MsgGCCustomizeItemTexture_t +{ + uint64 m_unToolItemID; // the tool + uint64 m_unSubjectItemID; // the item wants the texture + uint64 m_unImageUGCHandle; // cloud ID of image file (UGCHandle_t) +}; + +// k_EMsgGCSetItemStyle +struct MsgGCSetItemStyle_t +{ + uint64 m_unItemID; + uint8 m_iStyle; +}; + +// k_EMsgGCItemPreviewCheckStatus +struct MsgGCCheckItemPreviewStatus_t +{ + uint32 m_unItemDefIndex; +}; + +// k_EMsgGCItemPreviewCheckStatusResponse +struct MsgGCItemPreviewCheckStatusResponse_t +{ + uint32 m_unItemDefIndex; + uint32 m_eResponse; + RTime32 m_timePreviewTime; +}; + +// k_EMsgGCItemPreviewRequest +struct MsgGCItemPreviewRequest_t +{ + uint32 m_unItemDefIndex; +}; + +// k_EMsgGCItemPreviewRequestResponse +struct MsgGCItemPreviewRequestResponse_t +{ + uint32 m_unItemDefIndex; + uint32 m_eResponse; +}; + +// k_EMsgGCItemPreviewExpire +struct MsgGCItemPreviewExpire_t +{ + +}; + +// k_EMsgGCItemPreviewExpireNotification +struct MsgGCItemPreviewExpireNotification_t +{ + uint32 m_unItemDefIndex; +}; + +//----------------------------------------------------------------------------- + +// k_EMsgGCUseItemResponse +enum EGCMsgUseItemResponse +{ + k_EGCMsgUseItemResponse_ItemUsed = 0, + k_EGCMsgUseItemResponse_GiftNoOtherPlayers = 1, + k_EGCMsgUseItemResponse_ServerError = 2, + k_EGCMsgUseItemResponse_MiniGameAlreadyStarted = 3, + k_EGCMsgUseItemResponse_ItemUsed_ItemsGranted = 4, + k_EGCMsgUseItemResponse_CannotBeUsedByAccount = 5, + k_EGCMsgUseItemResponse_ForceSizeInt = 0x7FFFFFFF +}; + +// k_EMsgGCUseItemResponse +struct MsgGCUseItemResponse_t +{ + uint32 m_eResponse; +}; + +// k_EMsgGCSpawnItem +struct MsgGCSpawnItem_t +{ + uint64 m_ulInitiatorSteamID; + uint32 m_unItemDefinitionID; + // other data dynamically added: + // string of initiator name +}; + +// k_EMsgGCRespawnPostLoadoutChange +struct MsgGCRespawnPostLoadoutChange_t +{ + uint64 m_ulInitiatorSteamID; +}; + +// k_EMsgGCRemoveItemName +struct MsgGCRemoveItemName_t +{ + uint64 m_unItemID; + bool m_bDescription; +}; + +//----------------------------------------------------------------------------- +// Trading + +// k_EMsgGCTrading_InitiateTradeRequest +struct MsgGCTrading_InitiateTradeRequest_t +{ + uint32 m_unTradeRequestID; + uint64 m_ulOtherSteamID; + // @note player A's name as string when sent to party B +}; + +enum EGCMsgInitiateTradeResponse +{ + k_EGCMsgInitiateTradeResponse_Accepted = 0, + k_EGCMsgInitiateTradeResponse_Declined = 1, + k_EGCMsgInitiateTradeResponse_VAC_Banned_Initiator = 2, + k_EGCMsgInitiateTradeResponse_VAC_Banned_Target = 3, + k_EGCMsgInitiateTradeResponse_Target_Already_Trading = 4, + k_EGCMsgInitiateTradeResponse_Disabled = 5, + k_EGCMsgInitiateTradeResponse_NotLoggedIn = 6, + k_EGCMsgInitiateTradeResponse_Cancel = 7, + k_EGCMsgInitiateTradeResponse_TooSoon = 8, + k_EGCMsgInitiateTradeResponse_TooSoonPenalty = 9, + k_EGCMsgInitiateTradeResponse_Trade_Banned_Initiator = 10, + k_EGCMsgInitiateTradeResponse_Trade_Banned_Target = 11, + k_EGCMsgInitiateTradeResponse_Free_Account_Initiator_DEPRECATED = 12, // free accounts can initiate trades now + k_EGCMsgInitiateTradeResponse_Shared_Account_Initiator= 13, + k_EGCMsgInitiateTradeResponse_Service_Unavailable = 14, + k_EGCMsgInitiateTradeResponse_Target_Blocked = 15, + k_EGCMsgInitiateTradeResponse_NeedVerifiedEmail = 16, + k_EGCMsgInitiateTradeResponse_NeedSteamGuard = 17, + k_EGCMsgInitiateTradeResponse_SteamGuardDuration = 18, + k_EGCMsgInitiateTradeResponse_TheyCannotTrade = 19, + k_EGCMsgInitiateTradeResponse_Recent_Password_Reset = 20, + k_EGCMsgInitiateTradeResponse_Using_New_Device = 21, + k_EGCMsgInitiateTradeResponse_Sent_Invalid_Cookie = 22, + + k_EGCMsgInitiateTradeResponse_Count, + k_EGCMsgInitiateTradeResponse_ForceSizeInt = 0x7FFFFFFF +}; + +// k_EMsgGCTrading_InitiateTradeResponse +struct MsgGCTrading_InitiateTradeResponse_t +{ + uint32 m_eResponse; + uint32 m_unTradeRequestID; +}; + +// k_EMsgGCTrading_StartSession +struct MsgGCTrading_StartSession_t +{ + uint32 m_unSessionVersion; + uint64 m_ulSteamIDPartyA; + uint64 m_ulSteamIDPartyB; + // @note strings from player names will be added to the message +}; + +// k_EMsgGCTrading_CancelSession +struct MsgGCTrading_CancelSession_t +{ +}; + +// k_EMsgGCUsedClaimCodeItem +struct MsgGCUsedClaimCodeItem_t +{ + // string of URL +}; + +//----------------------------------------------------------------------------- +// ServerBrowser messages + +enum EGCMsgServerBrowser +{ + k_EGCMsgServerBrowser_FromServerBrowser = 0, + k_EGCMsgServerBrowser_FromAutoAskDialog = 1, +}; + +// k_EMsgGCServerBrowser_FavoriteServer +// k_EMsgGCServerBrowser_BlacklistServer +struct MsgGCServerBrowser_Server_t +{ + uint32 m_unIP; + int m_usPort; + uint8 m_ubSource; // 0=serverbrowser, 1=auto-ask dialog +}; + +//----------------------------------------------------------------------------- +// Public facing loot lists. + +// k_EMsgGC_RevolvingLootList +struct MsgGC_RevolvingLootList_t +{ + uint8 m_usListID; // Id of this list. + // Var Data: + // Serialized Lootlist KV +}; + + +// k_EMsgGCLookupAccount +struct MsgGCLookupAccount_t +{ + uint16 m_uiFindType; + + // Var Data + // string containing Persona / URL / etc +}; + +// k_EMsgGCLookupAccountName +struct MsgGCLookupAccountName_t +{ + uint32 m_unAccountID; +}; + +// k_EMsgGCLookupAccountNameResponse +struct MsgGCLookupAccountNameResponse_t +{ + uint32 m_unAccountID; + // string containing persona name +}; + +#pragma pack( pop ) + +#endif diff --git a/game/shared/econ/econ_gcmessages.proto b/game/shared/econ/econ_gcmessages.proto new file mode 100644 index 0000000..19cb487 --- /dev/null +++ b/game/shared/econ/econ_gcmessages.proto @@ -0,0 +1,644 @@ +//====== Copyright 1996-2010, Valve Corporation, All rights reserved. ======= +// +// Purpose: The file defines our Google Protocol Buffers which are used in over +// the wire messages between servers as well as between the TF GC and TF gameservers +// and clients. +// +//============================================================================= + +// We care more about speed than code size +option optimize_for = SPEED; + +// We don't use the service generation functionality +option cc_generic_services = false; + + +// +// STYLE NOTES: +// +// Use CamelCase CMsgMyMessageName style names for messages. +// +// Use lowercase _ delimited names like my_steam_id for field names, this is non-standard for Steam, +// but plays nice with the Google formatted code generation. +// +// Try not to use required fields ever. Only do so if you are really really sure you'll never want them removed. +// Optional should be preffered as it will make versioning easier and cleaner in the future if someone refactors +// your message and wants to remove or rename fields. +// +// Use fixed64 for JobId_t, GID_t, or SteamID. This is appropriate for any field that is normally +// going to be larger than 2^56. Otherwise use int64 for 64 bit values that are frequently smaller +// than 2^56 as it will safe space on the wire in those cases. +// +// Similar to fixed64, use fixed32 for RTime32 or other 32 bit values that are frequently larger than +// 2^28. It will save space in those cases, otherwise use int32 which will safe space for smaller values. +// An exception to this rule for RTime32 is if the value will frequently be zero rather than set to an actual +// time. +// + +import "steammessages.proto"; + +enum EGCItemMsg +{ + k_EMsgGCBase = 1000; + k_EMsgGCSetSingleItemPosition = 1001; // uses old-school struct for a single item. Prefer k_EMsgGCSetItemPositions + k_EMsgGCCraft = 1002; + k_EMsgGCCraftResponse = 1003; + k_EMsgGCDelete = 1004; + k_EMsgGCVerifyCacheSubscription = 1005; // sent by gameservers who don't have a cache they expect + k_EMsgGCNameItem = 1006; + k_EMsgGCUnlockCrate = 1007; // used by decoder rings to unlock supply crates + k_EMsgGCUnlockCrateResponse = 1008; + k_EMsgGCPaintItem = 1009; // used by paint cans to paint items + k_EMsgGCPaintItemResponse = 1010; + k_EMsgGCGoldenWrenchBroadcast = 1011; // sent to all users when a Golden Wrench is crafted or deleted + k_EMsgGCMOTDRequest = 1012; // client is asking for a set of MOTDs + k_EMsgGCMOTDRequestResponse = 1013; + +// k_EMsgGCAddItemToSocket_DEPRECATED = 1014; +// k_EMsgGCAddItemToSocketResponse_DEPRECATED = 1015; +// k_EMsgGCAddSocketToBaseItem_DEPRECATED = 1016; +// k_EMsgGCAddSocketToItem_DEPRECATED = 1017; +// k_EMsgGCAddSocketToItemResponse_DEPRECATED = 1018; + + k_EMsgGCNameBaseItem = 1019; + k_EMsgGCNameBaseItemResponse = 1020; + + k_EMsgGCRemoveSocketItem_DEPRECATED = 1021; + k_EMsgGCRemoveSocketItemResponse_DEPRECATED = 1022; + + k_EMsgGCCustomizeItemTexture = 1023; + k_EMsgGCCustomizeItemTextureResponse = 1024; + k_EMsgGCUseItemRequest = 1025; // client/game server => GC + k_EMsgGCUseItemResponse = 1026; // GC => client/game server + +// k_EMsgGCSpawnItem_DEPRECATED = 1028; // GC => game server + k_EMsgGCRespawnPostLoadoutChange = 1029; // client => GC => game server + k_EMsgGCRemoveItemName = 1030; // client => GC + k_EMsgGCRemoveItemPaint = 1031; // client => GC + k_EMsgGCGiftWrapItem = 1032; // client => GC (the player requests an item to be gift wrapped) + k_EMsgGCGiftWrapItemResponse = 1033; // GC => client (confirmation that an item was gift wrapped) + k_EMsgGCDeliverGift = 1034; + k_EMsgGCDeliverGiftResponseReceiver = 1036; + k_EMsgGCUnwrapGiftRequest = 1037; + k_EMsgGCUnwrapGiftResponse = 1038; + k_EMsgGCSetItemStyle = 1039; + + k_EMsgGCUsedClaimCodeItem = 1040; + k_EMsgGCSortItems = 1041; + + k_EMsgGC_RevolvingLootList_DEPRECATED= 1042; // GC => client; revolving loot list + + k_EMsgGCLookupAccount = 1043; // client is requesting a lookup of an account + k_EMsgGCLookupAccountResponse = 1044; + k_EMsgGCLookupAccountName = 1045; // old-school struct for single account. client is requesting a lookup of an account name + k_EMsgGCLookupAccountNameResponse = 1046; + + //k_EMsgGCStartupCheck = 1047; // GC => client + //k_EMsgGCStartupCheckResponse = 1048; // client => GC + k_EMsgGCUpdateItemSchema = 1049; // GC => client + k_EMsgGCRequestInventoryRefresh = 1050; // client => GC + + k_EMsgGCRemoveCustomTexture = 1051; // client => GC + k_EMsgGCRemoveCustomTextureResponse = 1052; // GC => client + k_EMsgGCRemoveMakersMark = 1053; // client => GC + k_EMsgGCRemoveMakersMarkResponse = 1054; // GC => client + k_EMsgGCRemoveUniqueCraftIndex = 1055; // client => GC + k_EMsgGCRemoveUniqueCraftIndexResponse = 1056; // GC => client + + k_EMsgGCSaxxyBroadcast = 1057; // sent to all users when a Saxxy is deleted + + k_EMsgGCBackpackSortFinished = 1058; // GC => client + k_EMsgGCAdjustItemEquippedState = 1059; // GC => client +// k_EMsgGCRequestItemSchemaData_DEPRECATED = 1060; // client => GC Should only be used in dev universe + + k_EMsgGCCollectItem = 1061; + + k_EMsgGCItemAcknowledged = 1062; // sent to a dedicated server when a client acknowledges an item + + // item presets + k_EMsgGCPresets_SelectPresetForClass = 1063; // client => GC + k_EMsgGCPresets_SetItemPosition = 1064; // client => GC + + // Abuse reporting + k_EMsgGC_ReportAbuse = 1065; // client => GC + k_EMsgGC_ReportAbuseResponse = 1066; // GC => client + + // more item presets + k_EMsgGCPresets_SelectPresetForClassReply = 1067; // GC => client + + // item naming broadcast + k_EMsgGCNameItemNotification = 1068; // GC => client + +// !FIXME! DOTAMERGE +// these messages are particular to DOTA, or +// conflict with corresponding TF messages +// +// k_EMsgGCGiftedItems = 1027; // GC => game server +// k_EMsgGCDeliverGiftResponseGiver = 1035; +// +// k_EMsgGCApplyConsumableEffects = 1069; +// +// k_EMsgGCConsumableExhausted = 1070; +// k_EMsgGCApplyStrangePart = 1073; // GC => client +// k_EMsgGCShowItemsPickedUp = 1071; +// +// // generic broadcast +// k_EMsgGCClientDisplayNotification = 1072; // GC => client +// +//// OBSOLETE k_EMsgGC_IncrementKillCountAttribute = 1074; // client => GC +// k_EMsgGC_IncrementKillCountResponse = 1075; // GC => client +// k_EMsgGCApplyPennantUpgrade = 1076; // GC => client +// +// k_EMsgGCSetItemPositions = 1077; // client => GC; protobuf batched item position update +// +// k_EMsgGCUnlockItemStyle = 1080; +// k_EMsgGCUnlockItemStyleResponse = 1081; +// +// k_EMsgGCFulfillDynamicRecipeComponent = 1082; +// k_EMsgGCFulfillDynamicRecipeComponentResponse = 1083; +// k_EMsgGCApplyEggEssence = 1078; +// k_EMsgGCNameEggEssenceResponse = 1079; +// +// k_EMsgGCClientRequestMarketData = 1084; +// k_EMsgGCClientRequestMarketDataResponse = 1085; +// k_EMsgGCExtractGems = 1086; +// k_EMsgGCAddSocket = 1087; // client -> GC +// k_EMsgGCAddItemToSocket = 1088; // client -> GC +// k_EMsgGCAddItemToSocketResponse = 1089; // GC -> client +// k_EMsgGCAddSocketResponse = 1090; // GC -> client +// +// k_EMsgGCResetStrangeGemCount = 1091; // client -> GC + +// << DOTA + +// TF >> + + // generic broadcast + k_EMsgGCClientDisplayNotification = 1069; // GC => client + + k_EMsgGCApplyStrangePart = 1070; // GC => client + k_EMsgGC_IncrementKillCountAttribute = 1071; // client => GC + k_EMsgGC_IncrementKillCountResponse = 1072; // GC => client + k_EMsgGCRemoveStrangePart = 1073; // GC => client + k_EMsgGCResetStrangeScores = 1074; // client => GC + + k_EMsgGCGiftedItems = 1075; // GC => game server + + k_EMsgGCApplyUpgradeCard = 1077; // client => GC + k_EMsgGCRemoveUpgradeCard = 1078; // client => GC + + k_EMsgGCApplyStrangeRestriction = 1079; // client => GC + + k_EMsgGCClientRequestMarketData = 1080; // client => GC + k_EMsgGCClientRequestMarketDataResponse = 1081; // GC => client + + k_EMsgGCApplyXifier = 1082; // client => GC + k_EMsgGCApplyXifierResponse = 1083; // GC => Client + + k_EMsgGC_TrackUniquePlayerPairEvent = 1084; // client => GC + k_EMsgGCFulfillDynamicRecipeComponent = 1085; // client => GC + k_EMsgGCFulfillDynamicRecipeComponentResponse = 1086; // GC => client + + k_EMsgGCSetItemEffectVerticalOffset = 1087; // client => GC + k_EMsgGCSetHatEffectUseHeadOrigin = 1088; // client => GC + + k_EMsgGCItemEaterRecharger = 1089; // client => GC + k_EMsgGCItemEaterRechargerResponse = 1090; // GC => Client + + k_EMsgGCApplyBaseItemXifier = 1091; // client => GC + + k_EMsgGCApplyClassTransmogrifier = 1092; // client => GC + k_EMsgGCApplyHalloweenSpellbookPage = 1093; // client => GC + + k_EMsgGCRemoveKillStreak = 1094; // client => GC + k_EMsgGCRemoveKillStreakResponse = 1095; // GC => client + + k_EMsgGCTFSpecificItemBroadcast = 1096; // GC => client (broadcast) + k_EMsgGC_IncrementKillCountAttribute_Multiple = 1097; // client (game server) => GC + k_EMsgGCDeliverGiftResponseGiver = 1098; + + k_EMsgGCSetItemPositions = 1100; // client => GC; protobuf batched item position update + +// << TF + + k_EMsgGCLookupMultipleAccountNames = 1101; + k_EMsgGCLookupMultipleAccountNamesResponse = 1102; + + // trading! + k_EMsgGCTradingBase = 1500; + k_EMsgGCTrading_InitiateTradeRequest = 1501; // client A -> GC and then GC -> client B + k_EMsgGCTrading_InitiateTradeResponse = 1502; // client B -> GC or GC -> client A + k_EMsgGCTrading_StartSession = 1503; // GC -> client A & B +// k_EMsgGCTrading_SetItem = 1504; // client -> GC +// k_EMsgGCTrading_RemoveItem = 1505; // client -> GC +// k_EMsgGCTrading_UpdateTradeInfo = 1506; // GC -> client A & B in response to SetItem or RemoveItem message +// k_EMsgGCTrading_SetReadiness = 1507; // client -> GC +// k_EMsgGCTrading_ReadinessResponse = 1508; // GC -> client A & B + k_EMsgGCTrading_SessionClosed = 1509; // GC -> client A & B + k_EMsgGCTrading_CancelSession = 1510; // client -> GC +// k_EMsgGCTrading_TradeChatMsg = 1511; // client -> GC and then GC -> other client +// k_EMsgGCTrading_ConfirmOffer = 1512; // client -> GC +// k_EMsgGCTrading_TradeTypingChatMsg = 1513; // client -> GC and then GC -> other client + k_EMsgGCTrading_InitiateTradeRequestResponse = 1514; // GC -> client + + // serverbrowser messages + k_EMsgGCServerBrowser_FavoriteServer = 1601; + k_EMsgGCServerBrowser_BlacklistServer = 1602; + + // rentals & previews + k_EMsgGCServerRentalsBase = 1700; + k_EMsgGCItemPreviewCheckStatus = 1701; + k_EMsgGCItemPreviewStatusResponse = 1702; + k_EMsgGCItemPreviewRequest = 1703; + k_EMsgGCItemPreviewRequestResponse = 1704; + k_EMsgGCItemPreviewExpire = 1705; + k_EMsgGCItemPreviewExpireNotification = 1706; + +// !FIXME! DOTAMERGE +// This message is 1707 in DOTA, but it came from TF, where it was 1707 at one time, then switched to 1708 +// when the message format changed. +// k_EMsgGCItemPreviewItemBoughtNotification = 1707; + k_EMsgGCItemPreviewItemBoughtNotification = 1708; + + // Development only messages + k_EMsgGCDev_NewItemRequest = 2001; + k_EMsgGCDev_NewItemRequestResponse = 2002; + k_EMsgGCDev_DebugRollLootRequest = 2003; + + // Microtransaction messages + k_EMsgGCStoreGetUserData = 2500; // Gets the current price sheet from the GC + k_EMsgGCStoreGetUserDataResponse = 2501; // Response + k_EMsgGCStorePurchaseInit_DEPRECATED = 2502; // Initiate a purchase (old pre-protobuff format -- deprecated!) + k_EMsgGCStorePurchaseInitResponse_DEPRECATED = 2503; // Response + +// !FIXME! DOTAMERGE +// These messages have different values in TF and DOTA. +// k_EMsgGCStorePurchaseFinalize = 2504; // Finalize a purchase +// k_EMsgGCStorePurchaseFinalizeResponse = 2505; // Response +// k_EMsgGCStorePurchaseCancel = 2506; // Cancel a purchase +// k_EMsgGCStorePurchaseCancelResponse = 2507; // Response + k_EMsgGCStorePurchaseFinalize = 2512; // Finalize a purchase + k_EMsgGCStorePurchaseFinalizeResponse = 2513; // Response + k_EMsgGCStorePurchaseCancel = 2514; // Cancel a purchase + k_EMsgGCStorePurchaseCancelResponse = 2515; // Response + + k_EMsgGCStorePurchaseQueryTxn = 2508; // Query the status of a transaction + k_EMsgGCStorePurchaseQueryTxnResponse = 2509; // Response + k_EMsgGCStorePurchaseInit = 2510; // Initiate a purchase + k_EMsgGCStorePurchaseInitResponse = 2511; // Response + +// !FIXME! DOTAMERGE +// Conflict with TF messages +// k_EMsgGCBannedWordListRequest = 2512; // Request a list of new banned words +// k_EMsgGCBannedWordListResponse = 2513; // response to a request, or a push of a new banned word update to clients +// k_EMsgGCToGCBannedWordListBroadcast = 2514; // sent from GC to GC so the main GC can broadcast a banned word change to clients +// k_EMsgGCToGCBannedWordListUpdated = 2515; // sent from GC to other GCs so that they can be kept in sync with banned word list updates + + k_EMsgGCToGCDirtySDOCache = 2516; // when an SDO cache needs to be dirtied on another GC + k_EMsgGCToGCDirtyMultipleSDOCache = 2517; // when a list of SDO caches needs to be dirtied on another GC + + k_EMsgGCToGCUpdateSQLKeyValue = 2518; // when a key value changes and needs to be updated on other GCs +// k_EMsgGCToGCIsTrustedServer = 2519; // Is the specified server trusted? +// k_EMsgGCToGCIsTrustedServerResponse = 2520; // response to whether or not this is a trusted server + k_EMsgGCToGCBroadcastConsoleCommand = 2521; // run a console command remotely on another GC from a GC + + k_EMsgGCServerVersionUpdated = 2522; // Sent when the active version of a server changes so servers can restart + + k_EMsgGCApplyAutograph = 2523; // + k_EMsgGCToGCWebAPIAccountChanged = 2524; + k_EMsgGCRequestAnnouncements = 2525; // + k_EMsgGCRequestAnnouncementsResponse = 2526; // + k_EMsgGCRequestPassportItemGrant = 2527; + + k_EMsgGCClientVersionUpdated = 2528; // Sent when the client doesn't match the appropriate version + + k_EMsgGCItemPurgatory_FinalizePurchase = 2531; // Sent for Korean government requirement - move a purchased item from the "maybe box" (referred to as item purgatory in code) to the backpack + k_EMsgGCItemPurgatory_FinalizePurchaseResponse = 2532; + k_EMsgGCItemPurgatory_RefundPurchase = 2533; + k_EMsgGCItemPurgatory_RefundPurchaseResponse = 2534; + + k_EMsgGCToGCPlayerStrangeCountAdjustments = 2535; + + k_EMsgGCRequestStoreSalesData = 2536; // get which items are currently on sale + k_EMsgGCRequestStoreSalesDataResponse = 2537; + k_EMsgGCRequestStoreSalesDataUpToDateResponse = 2538; + + k_EMsgGCToGCPingRequest = 2539; + k_EMsgGCToGCPingResponse = 2540; + + k_EMsgGCToGCGetUserSessionServer = 2541; // GC->GC, see what the steam ID is of the server that this user is on + k_EMsgGCToGCGetUserSessionServerResponse = 2542; // --response + k_EMsgGCToGCGetUserServerMembers = 2543; // GC->GC, what members are on the server and spectating + k_EMsgGCToGCGetUserServerMembersResponse = 2544; // --response + + k_EMsgGCToGCGrantSelfMadeItemToAccount = 2555; // GC->GC, via SQL message queue, grant one specific self-made item to this contributor account ID + k_EMsgGCToGCThankedByNewUser = 2556; // GC->GC, via SQL message queue, this account was thanked by a new user account, so grant a thanked item or level up the current item + + k_EMsgGCShuffleCrateContents = 2557; // game client->GC, shuffle the contents of the line item loot list for this crate + + k_EMsgGCQuestObjective_Progress = 2558; // client/game -> GC, report progress in a quest objective + k_EMsgGCQuestCompleted = 2559; // GC -> client, report completion of a quest + + k_EMsgGCApplyDuckToken = 2560; // client => GC + + k_EMsgGCQuestComplete_Request = 2561; // client -> GC + k_EMsgGCQuestObjective_PointsChange = 2562; // server -> GC + k_EMsgGCQuestObjective_RequestLoanerItems = 2564; // client -> GC + k_EMsgGCQuestObjective_RequestLoanerResponse = 2565; // GC -> client + + k_EMsgGCApplyStrangeCountTransfer = 2566; // client => GC + k_EMsgGCCraftCollectionUpgrade = 2567; // client => GC + k_EMsgGCCraftHalloweenOffering = 2568; // client => GC + + k_EMsgGCQuestDiscard_Request = 2569; // client => GC + + k_EMsgGCRemoveGiftedBy = 2570; // client => GC + k_EMsgGCRemoveGiftedByResponse = 2571; // GC => client + + k_EMsgGCRemoveFestivizer = 2572; // client => GC + k_EMsgGCRemoveFestivizerResponse = 2573; // GC => client + + k_EMsgGCCraftCommonStatClock = 2574; // client => GC + + // Game specific messages start at 5000 and GCGameBase messages start at 3000 + // So all these must be < 3000 +}; + +enum EGCMsgResponse +{ + k_EGCMsgResponseOK = 0; // Request succeeded + k_EGCMsgResponseDenied = 1; // Request denied + k_EGCMsgResponseServerError = 2; // Request failed due to a temporary server error + k_EGCMsgResponseTimeout = 3; // Request timed out + k_EGCMsgResponseInvalid = 4; // Request was corrupt + k_EGCMsgResponseNoMatch = 5; // No item definition matched the request + k_EGCMsgResponseUnknownError = 6; // Request failed with an unknown error + k_EGCMsgResponseNotLoggedOn = 7; // Client not logged on to steam + k_EGCMsgFailedToCreate = 8; // Failed to create whatever object the GC was asked to create + +// k_EGCMsgResponseForceSizeInt = 0x7FFFFFFF +}; + +enum EUnlockStyle +{ + k_UnlockStyle_Succeeded = 0; + k_UnlockStyle_Failed_PreReq = 1; + k_UnlockStyle_Failed_CantAfford = 2; + k_UnlockStyle_Failed_CantCommit = 3; + k_UnlockStyle_Failed_CantLockCache = 4; + k_UnlockStyle_Failed_CantAffordAttrib = 5; + k_UnlockStyle_Failed_CantAffordGem = 6; +}; + +enum EItemPurgatoryResponse_Finalize +{ + k_ItemPurgatoryResponse_Finalize_Succeeded = 0; + k_ItemPurgatoryResponse_Finalize_Failed_Incomplete = 1; // Some but not all finalized + k_ItemPurgatoryResponse_Finalize_Failed_ItemsNotInPurgatory = 2; // Item ID's sent up were not in purgatory + k_ItemPurgatoryResponse_Finalize_Failed_CouldNotFindItems = 3; // One or more items do not belong to the given steam ID or were deleted, etc. + k_ItemPurgatoryResponse_Finalize_Failed_NoSOCache = 4; // Couldn't load the user's SO cache + k_ItemPurgatoryResponse_Finalize_BackpackFull = 5; // Backpack was full. We may have finalized some items. +}; + +enum EItemPurgatoryResponse_Refund +{ + k_ItemPurgatoryResponse_Refund_Succeeded = 0; + k_ItemPurgatoryResponse_Refund_Failed_ItemNotInPurgatory = 1; // Item ID's sent up were not in purgatory + k_ItemPurgatoryResponse_Refund_Failed_CouldNotFindItem = 2; // One or more items do not belong to the given steam ID or were deleted, etc. + k_ItemPurgatoryResponse_Refund_Failed_NoSOCache = 3; // Couldn't load the user's SO cache + k_ItemPurgatoryResponse_Refund_Failed_NoDetail = 4; // Generic error to avoid giving the client too much detail + k_ItemPurgatoryResponse_Refund_Failed_NexonWebAPI = 5; // The Nexon WebAPI failed +}; + +// +// k_EMsgGCApplyAutograph +// +message CMsgApplyAutograph +{ + optional uint64 autograph_item_id = 1; // which autograph? + optional uint64 item_item_id = 2; // which item is getting this autograph +}; + + +// k_EMsgGCToGCPlayerStrangeCountAdjustments +// Used in the GC SQL Msg queue and in match signout +message CMsgEconPlayerStrangeCountAdjustment +{ + message CStrangeCountAdjustment + { + optional uint32 event_type = 1; + optional uint64 item_id = 2; + optional uint32 adjustment = 3; + }; + + optional uint32 account_id = 1; + repeated CStrangeCountAdjustment strange_count_adjustments = 2; +}; + + +// +// k_EMsgGCItemPurgatory_FinalizePurchase +// +message CMsgRequestItemPurgatory_FinalizePurchase +{ + repeated uint64 item_ids = 1; +}; + +// +// k_EMsgGCItemPurgatory_FinalizePurchaseResponse +// +message CMsgRequestItemPurgatory_FinalizePurchaseResponse +{ + optional uint32 result = 1; +}; + +// +// k_EMsgGCItemPurgatory_RefundPurchase +// +message CMsgRequestItemPurgatory_RefundPurchase +{ + optional uint64 item_id = 1; +}; + +// +// k_EMsgGCItemPurgatory_RefundPurchaseResponse +// +message CMsgRequestItemPurgatory_RefundPurchaseResponse +{ + optional uint32 result = 1; +}; + +message CMsgCraftingResponse +{ + repeated uint64 item_ids = 1; +}; + +// k_EMsgGCRequestStoreSalesData +message CMsgGCRequestStoreSalesData +{ + optional uint32 version = 1; // the last received version (used to identify when sales data may have changed) + optional uint32 currency = 2; // which currency we want the sales values for +}; + +// k_EMsgGCRequestStoreSalesDataResponse +message CMsgGCRequestStoreSalesDataResponse +{ + message Price + { + optional uint32 item_def = 1; + optional uint32 price = 2; + }; + repeated Price sale_price = 1; // the list of items on sale and their sale price for the requested currency + optional uint32 version = 2; // the version of this data, future sale requests should provide this value so redundant requests can be ignored + optional uint32 expiration_time = 3; // the time after which this sale is likely to expire. It could expire sooner than this if the GC restarts +}; + +// k_EMsgGCRequestStoreSalesDataUpToDateResponse +message CMsgGCRequestStoreSalesDataUpToDateResponse +{ + optional uint32 version = 1; // the last received version (used to identify when sales data may have changed) + optional uint32 expiration_time = 2; // the time after which this sale is likely to expire. It could expire sooner than this if the GC restarts +}; + +// k_EMsgGCToGCPingRequest +message CMsgGCToGCPingRequest +{ +}; + +// k_EMsgGCToGCPingResponse +message CMsgGCToGCPingResponse +{ +}; + +// k_EMsgGCToGCGetUserSessionServer +message CMsgGCToGCGetUserSessionServer +{ + optional uint32 account_id = 1; // the user to lookup the server information for +}; + +// k_EMsgGCToGCGetUserSessionServerResponse +message CMsgGCToGCGetUserSessionServerResponse +{ + optional fixed64 server_steam_id = 1; // zero if this user is not online or not on a server +}; + +// k_EMsgGCToGCGetUserServerMembers +message CMsgGCToGCGetUserServerMembers +{ + optional uint32 account_id = 1; // the account ID to look up the server and spectators from + optional uint32 max_spectators = 2; // do you want spectators? If so what is the limit of how many will be returned (otherwise specify zero) +}; + +// k_EMsgGCToGCGetUserServerMembersResponse +message CMsgGCToGCGetUserServerMembersResponse +{ + repeated uint32 member_account_id = 1; // the list of other server members or spectators +}; + +// k_EMsgGCLookupMultipleAccountNames +message CMsgLookupMultipleAccountNames +{ + repeated uint32 accountids = 1 [ packed=true ]; +}; + +// k_EMsgGCLookupMultipleAccountNamesResponse +message CMsgLookupMultipleAccountNamesResponse +{ + message Account + { + optional uint32 accountid = 1; + optional string persona = 2; + } + repeated Account accounts = 1; +}; + +// k_EMsgGCToGCGrantSelfMadeItemToAccount +message CMsgGCToGCGrantSelfMadeItemToAccount +{ + optional uint32 item_def_index = 1; + optional uint32 accountid = 2; +} + +// k_EMsgGCToGCThankedByNewUser +message CMsgGCToGCThankedByNewUser +{ + optional uint32 new_user_accountid = 1; // guy who did the thanking + optional uint32 thanked_user_accountid = 2; // guy who was thanked +} + +// k_EMsgGCShuffleCrateContents +message CMsgGCShuffleCrateContents +{ + optional uint64 crate_item_id = 1; + optional string user_code_string = 2; +} + +// k_EMsgGCQuestObjective_Progress +message CMsgGCQuestObjective_Progress +{ + optional uint64 quest_item_id = 1; + optional uint32 quest_attrib_index = 2; + optional uint32 delta = 3; + optional fixed64 owner_steamid = 4; +} + +// CMsgGCQuestObjective_PointsChange +message CMsgGCQuestObjective_PointsChange +{ + optional uint64 quest_item_id = 1; + optional uint32 standard_points = 2; + optional uint32 bonus_points = 3; + optional fixed64 owner_steamid = 4; + optional bool update_base_points = 5 [default = false]; +} + +// k_EMsgGCQuestComplete_Request +message CMsgGCQuestComplete_Request +{ + optional uint64 quest_item_id = 1; +} + +// k_EMsgGCQuestCompleted +message CMsgGCQuestCompleted +{ +} + +// k_EMsgGCQuestObjective_RequestLoanerItems +message CMsgGCQuestObjective_RequestLoanerItems +{ + optional uint64 quest_item_id = 1; +} + +// k_EMsgGCQuestObjective_RequestLoanerResponse +message CMsgGCQuestObjective_RequestLoanerResponse +{ +} + +// k_EMsgGCCraftCollectionUpgrader +message CMsgCraftCollectionUpgrade +{ + repeated uint64 item_id = 1; // list of item ids +}; + +// k_EMsgGCCraftHalloweenOffering +message CMsgCraftHalloweenOffering +{ + optional uint64 tool_id = 1; // tool that is invoking this call + repeated uint64 item_id = 2; // list of item ids +}; + +// k_EMsgGCCraftCommonStatClock +message CMsgCraftCommonStatClock +{ + optional uint64 tool_id = 1; // tool that is invoking this call + repeated uint64 item_id = 2; // list of item ids +}; + +// k_EMsgGCQuestDiscard_Request +message CMsgGCQuestDiscard_Request +{ + optional uint64 quest_item_id = 1; +} + +// Do not remove this comment due to a bug on the Mac OS X protobuf compiler - lol + diff --git a/game/shared/econ/econ_holidays.cpp b/game/shared/econ/econ_holidays.cpp new file mode 100644 index 0000000..afe1278 --- /dev/null +++ b/game/shared/econ/econ_holidays.cpp @@ -0,0 +1,415 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#include "cbase.h" + +#include "rtime.h" +#include "econ_holidays.h" + +//----------------------------------------------------------------------------- +// Purpose: Interface that answers the simple question "on the passed-in time, +// would this holiday be active?". Any caching of calculations is left +// up to subclasses. +//----------------------------------------------------------------------------- +class IIsHolidayActive +{ +public: + IIsHolidayActive( const char *pszHolidayName ) : m_pszHolidayName( pszHolidayName ) { } + virtual ~IIsHolidayActive ( ) { } + virtual bool IsActive( const CRTime& timeCurrent ) = 0; + + const char *GetHolidayName() const { return m_pszHolidayName; } + +private: + const char *m_pszHolidayName; +}; + +//----------------------------------------------------------------------------- +// Purpose: Always-disabled. Dummy event needed to map to slot zero for "disabled +// holiday". +//----------------------------------------------------------------------------- +class CNoHoliday : public IIsHolidayActive +{ +public: + CNoHoliday() : IIsHolidayActive( "none" ) { } + + virtual bool IsActive( const CRTime& timeCurrent ) + { + return false; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: A holiday that lasts exactly one and only one day. +//----------------------------------------------------------------------------- +class CSingleDayHoliday : public IIsHolidayActive +{ +public: + CSingleDayHoliday( const char *pszName, int iMonth, int iDay ) + : IIsHolidayActive( pszName ) + , m_iMonth( iMonth ) + , m_iDay( iDay ) + { + // + } + + virtual bool IsActive( const CRTime& timeCurrent ) + { + return m_iMonth == timeCurrent.GetMonth() + && m_iDay == timeCurrent.GetDayOfMonth(); + } + +private: + int m_iMonth; + int m_iDay; +}; + +//----------------------------------------------------------------------------- +// Purpose: We want "week long" holidays to encompass at least two weekends, +// so that players get plenty of time interacting with the holiday +// features. +//----------------------------------------------------------------------------- +class CWeeksBasedHoliday : public IIsHolidayActive +{ +public: + CWeeksBasedHoliday( const char *pszName, int iMonth, int iDay, int iExtraWeeks ) + : IIsHolidayActive( pszName ) + , m_iMonth( iMonth ) + , m_iDay( iDay ) + , m_iExtraWeeks( iExtraWeeks ) + , m_iCachedCalculatedYear( 0 ) + { + // We'll calculate the interval the first time we call IsActive(). + } + + void RecalculateTimeActiveInterval( int iYear ) + { + // Get the date of the holiday. + tm holiday_tm = { }; + holiday_tm.tm_mday = m_iDay; + holiday_tm.tm_mon = m_iMonth - 1; + holiday_tm.tm_year = iYear - 1900; // convert to years since 1900 + mktime( &holiday_tm ); + + // The event starts on the first Friday at least four days prior to the holiday. + tm start_time_tm( holiday_tm ); + start_time_tm.tm_mday -= 4; // Move back four days. + mktime( &start_time_tm ); + int days_offset = start_time_tm.tm_wday - kFriday; // Find the nearest prior Friday. + if ( days_offset < 0 ) + days_offset += 7; + start_time_tm.tm_mday -= days_offset; + time_t start_time = mktime( &start_time_tm ); + + // The event ends on the first Monday after the holiday, maybe plus some additional fudge + // time. + tm end_time_tm( holiday_tm ); + days_offset = 7 - (end_time_tm.tm_wday - kMonday); + if ( days_offset >= 7 ) + days_offset -= 7; + end_time_tm.tm_mday += days_offset + 7 * m_iExtraWeeks; + time_t end_time = mktime( &end_time_tm ); + +#ifdef GC_DLL + char rgchDateStartBuf[ 128 ]; + BGetLocalFormattedDate( start_time, rgchDateStartBuf, sizeof( rgchDateStartBuf) ); + + char rgchDateEndBuf[ 128 ]; + BGetLocalFormattedDate( end_time, rgchDateEndBuf, sizeof( rgchDateEndBuf ) ); + + EmitInfo( GCSDK::SPEW_GC, 4, LOG_ALWAYS, "Holiday - '%s' event starts on '%s' and ends on '%s'.\n", GetHolidayName(), rgchDateStartBuf, rgchDateEndBuf ); +#endif // GC_DLL + + m_timeStart = start_time; + m_timeEnd = end_time; + + // We're done and our interval data is cached. + m_iCachedCalculatedYear = iYear; + } + + virtual bool IsActive( const CRTime& timeCurrent ) + { + const int iCurrentYear = timeCurrent.GetYear(); + if ( m_iCachedCalculatedYear != iCurrentYear ) + RecalculateTimeActiveInterval( iCurrentYear ); + + return timeCurrent.GetRTime32() > m_timeStart + && timeCurrent.GetRTime32() < m_timeEnd; + } + +private: + static const int kMonday = 1; + static const int kFriday = 5; + + int m_iMonth; + int m_iDay; + int m_iExtraWeeks; + + // Filled out from RecalculateTimeActiveInterval(). + int m_iCachedCalculatedYear; + + RTime32 m_timeStart; + RTime32 m_timeEnd; +}; + +//----------------------------------------------------------------------------- +// Purpose: A holiday that repeats on a certain time interval, like "every N days" +// or "once every two months" or, uh, "any time there's a full moon". +//----------------------------------------------------------------------------- +class CCyclicalHoliday : public IIsHolidayActive +{ +public: + CCyclicalHoliday( const char *pszName, int iMonth, int iDay, int iYear, float fCycleLengthInDays, float fBonusTimeInDays ) + : IIsHolidayActive( pszName ) + , m_fCycleLengthInDays( fCycleLengthInDays ) + , m_fBonusTimeInDays( fBonusTimeInDays ) + { + // When is our initial interval? + tm holiday_tm = { }; + holiday_tm.tm_mday = iDay; + holiday_tm.tm_mon = iMonth - 1; + holiday_tm.tm_year = iYear - 1900; // convert to years since 1900 + m_timeInitial = mktime( &holiday_tm ); + } + + virtual bool IsActive( const CRTime& timeCurrent ) + { + // Days-to-seconds conversion. + const int iSecondsPerDay = 24 * 60 * 60; + + // Convert our cycle/buffer times to seconds. + const int iCycleLengthInSeconds = (int)(m_fCycleLengthInDays * iSecondsPerDay); + const int iBufferTimeInSeconds = (int)(m_fBonusTimeInDays * iSecondsPerDay); + + // How long has it been since we started this cycle? + int iSecondsIntoCycle = (timeCurrent.GetRTime32() - m_timeInitial) % iCycleLengthInSeconds; + + // If we're within the buffer period right after the start of a cycle, we're active. + if ( iSecondsIntoCycle < iBufferTimeInSeconds ) + return true; + + // If we're within the buffer period towards the end of a cycle, we're active. + if ( iSecondsIntoCycle > iCycleLengthInSeconds - iBufferTimeInSeconds ) + return true; + + // Alas, normal mode for us. + return false; + } + +private: + time_t m_timeInitial ; + + float m_fCycleLengthInDays; + float m_fBonusTimeInDays; +}; + +//----------------------------------------------------------------------------- +// Purpose: A pseudo-holiday that is active when either of its child holidays +// is active. Works through pointers but does not manage memory. +//----------------------------------------------------------------------------- +class COrHoliday : public IIsHolidayActive +{ +public: + COrHoliday( const char *pszName, IIsHolidayActive *pA, IIsHolidayActive *pB ) + : IIsHolidayActive( pszName ) + , m_pA( pA ) + , m_pB( pB ) + { + Assert( pA ); + Assert( pB ); + Assert( pA != pB ); + } + + virtual bool IsActive( const CRTime& timeCurrent ) + { + return m_pA->IsActive( timeCurrent ) + || m_pB->IsActive( timeCurrent ); + } + +private: + IIsHolidayActive *m_pA; + IIsHolidayActive *m_pB; +}; + +//----------------------------------------------------------------------------- +// Purpose: Holiday that is defined by a start and end date +//----------------------------------------------------------------------------- +class CDateBasedHoliday : public IIsHolidayActive +{ +public: + CDateBasedHoliday( const char *pszName, const char *pszStartTime, const char *pszEndTime ) + : IIsHolidayActive( pszName ) + { + m_rtStartTime = CRTime::RTime32FromString( pszStartTime ); + m_rtEndTime = CRTime::RTime32FromString( pszEndTime ); + } + + virtual bool IsActive( const CRTime& timeCurrent ) + { + return ( ( timeCurrent >= m_rtStartTime ) && ( timeCurrent <= m_rtEndTime ) ); + } + + RTime32 GetEndRTime() const + { + return m_rtEndTime.GetRTime32(); + } + + +private: + CRTime m_rtStartTime; + CRTime m_rtEndTime; +}; + +//----------------------------------------------------------------------------- +// Purpose: Holiday that is defined by a start and end date with no year specified +//----------------------------------------------------------------------------- +class CDateBasedHolidayNoSpecificYear : public IIsHolidayActive +{ +public: + CDateBasedHolidayNoSpecificYear( const char *pszName, const char *pszStartTime, const char *pszEndTime ) + : IIsHolidayActive( pszName ) + , m_pszStartTime( pszStartTime ) + , m_pszEndTime( pszEndTime ) + , m_iCachedYear( -1 ) + { + } + + virtual bool IsActive( const CRTime& timeCurrent ) + { + const int iYear = timeCurrent.GetYear(); + + if ( iYear != m_iCachedYear ) + { + char m_szStartTime[k_RTimeRenderBufferSize]; + char m_szEndTime[k_RTimeRenderBufferSize]; + + V_sprintf_safe( m_szStartTime, "%d-%s", iYear, m_pszStartTime ); + V_sprintf_safe( m_szEndTime, "%d-%s", iYear, m_pszEndTime ); + + m_iCachedYear = iYear; + m_rtCachedStartTime = CRTime::RTime32FromString( m_szStartTime ); + m_rtCachedEndTime = CRTime::RTime32FromString( m_szEndTime ); + } + + return ( ( timeCurrent >= m_rtCachedStartTime ) && ( timeCurrent <= m_rtCachedEndTime ) ); + } + +private: + const char *m_pszStartTime; + const char *m_pszEndTime; + + int m_iCachedYear; + CRTime m_rtCachedStartTime; + CRTime m_rtCachedEndTime; +}; + +//----------------------------------------------------------------------------- +// Purpose: Actual holiday implementation objects. +//----------------------------------------------------------------------------- + +static CNoHoliday g_Holiday_NoHoliday; + +static CDateBasedHolidayNoSpecificYear g_Holiday_TF2Birthday ( "birthday", "08-23", "08-25" ); + +static CDateBasedHoliday g_Holiday_Halloween ( "halloween", "2016-10-19", "2016-11-18" ); + +static CDateBasedHoliday g_Holiday_Christmas ( "christmas", "2016-11-28", "2017-01-12" ); + +static CDateBasedHolidayNoSpecificYear g_Holiday_ValentinesDay ( "valentines", "02-13", "02-15" ); + +static CDateBasedHoliday g_Holiday_MeetThePyro ( "meet_the_pyro", "2012-06-26", "2012-07-05" ); + /* starting date cycle length in days bonus time in days on both sides */ +static CCyclicalHoliday g_Holiday_FullMoon ( "fullmoon", 5, 21, 2016, 29.53f, 1.0f ); + // note: the cycle length is 29.5 instead of 29.53 so that the time calculations always start at noon based on the way CCyclicalHoliday works +static COrHoliday g_Holiday_HalloweenOrFullMoon ( "halloween_or_fullmoon", &g_Holiday_Halloween, &g_Holiday_FullMoon ); + +static COrHoliday g_Holiday_HalloweenOrFullMoonOrValentines ( "halloween_or_fullmoon_or_valentines", &g_Holiday_HalloweenOrFullMoon, &g_Holiday_ValentinesDay ); + +static CDateBasedHolidayNoSpecificYear g_Holiday_AprilFools ( "april_fools", "03-31", "04-02" ); + +static CDateBasedHoliday g_Holiday_EndOfTheLine ( "eotl_launch", "2014-12-03", "2015-01-05" ); + +static CDateBasedHoliday g_Holiday_CommunityUpdate ( "community_update", "2015-09-01", "2015-11-05" ); + +// ORDER NEEDS TO MATCH enum EHoliday +static IIsHolidayActive *s_HolidayChecks[] = +{ + &g_Holiday_NoHoliday, // kHoliday_None + &g_Holiday_TF2Birthday, // kHoliday_TFBirthday + &g_Holiday_Halloween, // kHoliday_Halloween + &g_Holiday_Christmas, // kHoliday_Christmas + &g_Holiday_CommunityUpdate, // kHoliday_CommunityUpdate + &g_Holiday_EndOfTheLine, // kHoliday_EOTL + &g_Holiday_ValentinesDay, // kHoliday_Valentines + &g_Holiday_MeetThePyro, // kHoliday_MeetThePyro + &g_Holiday_FullMoon, // kHoliday_FullMoon + &g_Holiday_HalloweenOrFullMoon, // kHoliday_HalloweenOrFullMoon + &g_Holiday_HalloweenOrFullMoonOrValentines, // kHoliday_HalloweenOrFullMoonOrValentines + &g_Holiday_AprilFools, // kHoliday_AprilFools +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( s_HolidayChecks ) == kHolidayCount ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool EconHolidays_IsHolidayActive( int iHolidayIndex, const CRTime& timeCurrent ) +{ + if ( iHolidayIndex < 0 || iHolidayIndex >= kHolidayCount ) + return false; + + Assert( s_HolidayChecks[iHolidayIndex] ); + if ( !s_HolidayChecks[iHolidayIndex] ) + return false; + + return s_HolidayChecks[iHolidayIndex]->IsActive( timeCurrent ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int EconHolidays_GetHolidayForString( const char* pszHolidayName ) +{ + for ( int iHoliday = 0; iHoliday < kHolidayCount; ++iHoliday ) + { + Assert( s_HolidayChecks[iHoliday] ); + if ( s_HolidayChecks[iHoliday] && + 0 == Q_stricmp( pszHolidayName, s_HolidayChecks[iHoliday]->GetHolidayName() ) ) + { + return iHoliday; + } + } + + return kHoliday_None; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *EconHolidays_GetActiveHolidayString() +{ + CRTime timeNow; + timeNow.SetToCurrentTime(); + timeNow.SetToGMT( true ); + + for ( int iHoliday = 0; iHoliday < kHolidayCount; iHoliday++ ) + { + if ( EconHolidays_IsHolidayActive( iHoliday, timeNow ) ) + { + Assert( s_HolidayChecks[iHoliday] ); + return s_HolidayChecks[iHoliday]->GetHolidayName(); + } + } + + // No holidays currently active. + return NULL; +} + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) || defined(TF_GC_DLL) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +RTime32 EconHolidays_TerribleHack_GetHalloweenEndData() +{ + return g_Holiday_Halloween.GetEndRTime(); +} +#endif // defined(TF_CLIENT_DLL) || defined(TF_DLL) || defined(TF_GC_DLL) diff --git a/game/shared/econ/econ_holidays.h b/game/shared/econ/econ_holidays.h new file mode 100644 index 0000000..bba409f --- /dev/null +++ b/game/shared/econ/econ_holidays.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#ifndef ECON_HOLIDAYS_H +#define ECON_HOLIDAYS_H + +#ifdef _WIN32 +#pragma once +#endif + +bool EconHolidays_IsHolidayActive( int iHolidayIndex, const class CRTime& timeCurrent ); +int EconHolidays_GetHolidayForString( const char* pszHolidayName ); +const char *EconHolidays_GetActiveHolidayString(); + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) || defined(TF_GC_DLL) +RTime32 EconHolidays_TerribleHack_GetHalloweenEndData(); +#endif // defined(TF_CLIENT_DLL) || defined(TF_DLL) || defined(TF_GC_DLL) + +#endif // ECON_HOLIDAYS_H
\ No newline at end of file diff --git a/game/shared/econ/econ_item.cpp b/game/shared/econ/econ_item.cpp new file mode 100644 index 0000000..a5ec004 --- /dev/null +++ b/game/shared/econ/econ_item.cpp @@ -0,0 +1,2765 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: CEconItem, a shared object for econ items +// +//============================================================================= + +#include "cbase.h" +#include "econ_item.h" +#include "econ_item_schema.h" +#include "rtime.h" +#include "gcsdk/enumutils.h" +#include "smartptr.h" + +#ifdef GC_DLL +#include "gcsdk/sqlaccess/sqlaccess.h" +#include "econ/localization_provider.h" +#endif + +#if defined( TF_CLIENT_DLL ) || defined( TF_DLL ) +#include "tf_gcmessages.h" +#endif + +using namespace GCSDK; + +#ifdef GC_DLL +IMPLEMENT_CLASS_MEMPOOL( CEconItem, 100 * 1000, UTLMEMORYPOOL_GROW_SLOW ); +IMPLEMENT_CLASS_MEMPOOL( CEconItemCustomData, 50 * 1000, UTLMEMORYPOOL_GROW_SLOW ); +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern int EconWear_ToIntCategory( float flWear ); +/*static*/ const schema_attribute_stat_bucket_t *CSchemaAttributeStats::m_pHead; + +//----------------------------------------------------------------------------- +// Purpose: Utility function to convert datafile strings to ints. +//----------------------------------------------------------------------------- +int StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings, bool bDontAssert ) +{ + if ( !szValue || !szValue[0] ) + return -1; + + for ( int i = 0; i < iNumStrings; i++ ) + { + if ( !Q_stricmp(szValue, pValueStrings[i]) ) + return i; + } + + if ( !bDontAssert ) + { + Assert( !"Missing value in StringFieldToInt()!" ); + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Utility function to convert datafile strings to ints. +//----------------------------------------------------------------------------- +int StringFieldToInt( const char *szValue, const CUtlVector<const char *>& vecValueStrings, bool bDontAssert ) +{ + return StringFieldToInt( szValue, (const char **)&vecValueStrings[0], vecValueStrings.Count(), bDontAssert ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItem::CEconItem() + : BaseClass( ) + , m_pCustomData( NULL ) + , m_ulID( INVALID_ITEM_ID ) + , m_unStyle( 0 ) + , m_pszSmallIcon( NULL ) + , m_pszLargeIcon( NULL ) +{ + Init(); +} + +CEconItem::CEconItem( const CEconItem& rhs ) + : BaseClass( ) + , m_pCustomData( NULL ) + , m_ulID( INVALID_ITEM_ID ) + , m_unStyle( 0 ) + , m_pszSmallIcon( NULL ) + , m_pszLargeIcon( NULL ) +{ + Init(); + (*this) = rhs; +} + +void CEconItem::Init() +{ + memset( &m_dirtyBits, 0, sizeof( m_dirtyBits ) ); + +#ifdef GC + // to set defaults + CSchItem item; + item.m_ulID = INVALID_ITEM_ID; + DeserializeFromSchemaItem( item ); + + COMPILE_TIME_ASSERT( sizeof( m_ulID ) == sizeof( item.m_ulID ) ); + COMPILE_TIME_ASSERT( sizeof( m_unAccountID ) == sizeof( item.m_unAccountID ) ); + COMPILE_TIME_ASSERT( sizeof( m_unDefIndex ) == sizeof( item.m_unDefIndex ) ); + COMPILE_TIME_ASSERT( sizeof( m_unLevel ) == sizeof( item.m_unLevel ) ); + COMPILE_TIME_ASSERT( sizeof( m_nQuality ) == sizeof( item.m_nQuality ) ); + COMPILE_TIME_ASSERT( sizeof( m_unInventory ) == sizeof( item.m_unInventory ) ); + COMPILE_TIME_ASSERT( sizeof( m_unFlags ) == sizeof( item.m_unFlags ) ); + COMPILE_TIME_ASSERT( sizeof( m_unOrigin ) == sizeof( item.m_unOrigin ) ); + COMPILE_TIME_ASSERT( sizeof( m_unStyle ) == sizeof( item.m_unStyle ) ); + // @note (Tom Bui): we need to know about any new fields + // Need to add new fields to: + // CEconItem::operator= + // CEconItem::SerializeToSchemaItem + // CEconItem::DeserializeFromSchemaItem + // CEconItem::SerializeToProtoBufItem + // CEconItem::DeserializeFromProtoBufItem + // CEconManager::BYieldingLoadSOCache + // CEconGetPlayerItemsJob::BYieldingHandleGetPlayerItemsV001 + COMPILE_TIME_ASSERT( CSchItem::k_iFieldMax == 13 ); + + m_bEquippedThisGameServerSession = false; +#endif +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItem::~CEconItem() +{ + // Free up any memory we may have allocated for our singleton attribute. Any other attributes + // will be cleaned up as part of freeing the custom data object itself. + if ( m_dirtyBits.m_bHasAttribSingleton ) + { + CEconItemCustomData::FreeAttributeMemory( &m_CustomAttribSingleton ); + } + + // Free up any custom data we may have allocated. This will catch any attributes not + // in our singleton. + if ( m_pCustomData ) + { + delete m_pCustomData; + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItemCustomData::~CEconItemCustomData() +{ + FOR_EACH_VEC( m_vecAttributes, i ) + { + FreeAttributeMemory( &m_vecAttributes[i] ); + } + + if ( m_pInteriorItem ) + { + delete m_pInteriorItem; + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::CopyAttributesFrom( const CEconItem& source ) +{ + // Copy attributes -- each new instance needs to be allocated and then copied into by somewhere + // that knows what the actual type is. Rather than do anything type-specific here, we just have each + // attribute serialize it's value to a bytestream and then deserialize it. This is as safe as we can + // make it but sort of silly wasteful. + for ( int i = 0; i < source.GetDynamicAttributeCountInternal(); i++ ) + { + const attribute_t& attr = source.GetDynamicAttributeInternal( i ); + + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( attr.m_unDefinitionIndex ); + Assert( pAttrDef ); + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + std::string sBytes; + pAttrType->ConvertEconAttributeValueToByteStream( attr.m_value, &sBytes ); + pAttrType->LoadByteStreamToEconAttributeValue( this, pAttrDef, sBytes ); + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItem &CEconItem::operator=( const CEconItem& rhs ) +{ + // We do destructive operations on our local object, including freeing attribute memory, as part of + // the copy, so we force self-copies to be a no-op. + if ( &rhs == this ) + return *this; + + m_ulID = rhs.m_ulID; + SetOriginalID( rhs.GetOriginalID() ); + m_unAccountID = rhs.m_unAccountID; + m_unDefIndex = rhs.m_unDefIndex; + m_unLevel = rhs.m_unLevel; + m_nQuality = rhs.m_nQuality; + m_unInventory = rhs.m_unInventory; + SetQuantity( rhs.GetQuantity() ); + m_unFlags = rhs.m_unFlags; + m_unOrigin = rhs.m_unOrigin; + m_unStyle = rhs.m_unStyle; + m_EquipInstanceSingleton = rhs.m_EquipInstanceSingleton; + + // If we have memory allocated for a single attribute we free it manually. + if ( m_dirtyBits.m_bHasAttribSingleton ) + { + CEconItemCustomData::FreeAttributeMemory( &m_CustomAttribSingleton ); + } + + // Copy over our dirty bits but manually reset our attribute singleton state -- if we did have one, + // we just deleted it above (and might replace it below); if we didn't have one, this won't affect + // anything. Either way, because we have no attribute memory allocated at this point, we need this + // to be reflected in the dirty bits so that if we do copy attributes, we copy them into the correct + // place (either the singleton or the custom data, to be allocated later). + m_dirtyBits = rhs.m_dirtyBits; + m_dirtyBits.m_bHasAttribSingleton = false; + + // Free any custom memory we've allocated. This will also remove any custom attributes. + if ( rhs.m_pCustomData == NULL ) + { + delete m_pCustomData; + m_pCustomData = NULL; + } + else + { + // Check for and copy in the equip instances from CustomData + EnsureCustomDataExists(); + m_pCustomData->m_vecEquipped = rhs.m_pCustomData->m_vecEquipped; + } + + CopyAttributesFrom( rhs ); + + // Reset our material overrides, they'll be set again on demand as needed. + ResetMaterialOverrides(); + + return *this; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SetItemID( itemid_t ulID ) +{ + uint64 ulOldID = m_ulID; + m_ulID = ulID; + // only overwrite if we don't have an original id currently and we are a new item cloned off an old item + if ( ulOldID != INVALID_ITEM_ID && ulOldID != ulID && ( m_pCustomData == NULL || m_pCustomData->m_ulOriginalID == INVALID_ITEM_ID ) && ulID != INVALID_ITEM_ID && ulOldID != INVALID_ITEM_ID ) + { + SetOriginalID( ulOldID ); + } + + ResetMaterialOverrides(); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +itemid_t CEconItem::GetOriginalID() const +{ + if ( m_pCustomData != NULL && m_pCustomData->m_ulOriginalID != INVALID_ITEM_ID ) + return m_pCustomData->m_ulOriginalID; + return m_ulID; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SetOriginalID( itemid_t ulOriginalID ) +{ + if ( ulOriginalID != m_ulID ) + { + EnsureCustomDataExists(); + m_pCustomData->m_ulOriginalID = ulOriginalID; + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +int CEconItem::GetQuantity() const +{ + if ( m_pCustomData != NULL ) + return m_pCustomData->m_unQuantity; + return 1; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SetQuantity( uint16 unQuantity ) +{ + if ( m_pCustomData ) + { + m_pCustomData->m_unQuantity = unQuantity; + } + else if ( unQuantity > 1 ) + { + EnsureCustomDataExists(); + m_pCustomData->m_unQuantity = unQuantity; + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +static const char *GetCustomNameOrAttributeDesc( const CEconItem *pItem, const CEconItemAttributeDefinition *pAttrDef ) +{ + if ( !pAttrDef ) + { + // If we didn't specify the attribute in the schema we can't possibly have an + // answer. This isn't really an error in that case. + return NULL; + } + + const char *pszStrContents; + if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( pItem, pAttrDef, &pszStrContents ) ) + return pszStrContents; + + return NULL; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +static void SetCustomNameOrDescAttribute( CEconItem *pItem, const CEconItemAttributeDefinition *pAttrDef, const char *pszNewValue ) +{ + Assert( pItem ); + + if ( !pAttrDef ) + { + // If we didn't specify the attribute in the schema, that's fine if we're setting + // the empty name/description string, but it isn't fine if we're trying to set + // actual content. + AssertMsg( !pszNewValue, "Attempt to set non-empty value for custom name/desc with no attribute present." ); + return; + } + + // Removing existing value? + if ( !pszNewValue || !pszNewValue[0] ) + { + pItem->RemoveDynamicAttribute( pAttrDef ); + return; + } + + CAttribute_String attrStr; + attrStr.set_value( pszNewValue ); + + pItem->SetDynamicAttributeValue( pAttrDef, attrStr ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const char *CEconItem::GetCustomName() const +{ + static CSchemaAttributeDefHandle pAttrDef_CustomName( "custom name attr" ); + + return GetCustomNameOrAttributeDesc( this, pAttrDef_CustomName ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SetCustomName( const char *pName ) +{ + static CSchemaAttributeDefHandle pAttrDef_CustomName( "custom name attr" ); + + SetCustomNameOrDescAttribute( this, pAttrDef_CustomName, pName ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::IsEquipped() const +{ + for ( int i = 0; i < GetEquippedInstanceCount(); i++ ) + { + const EquippedInstance_t &curEquipInstance = GetEquippedInstance( i ); + Assert( curEquipInstance.m_unEquippedSlot != INVALID_EQUIPPED_SLOT ); + + if ( GetItemSchema()->IsValidClass( curEquipInstance.m_unEquippedClass ) ) + return true; + } + + return false; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::IsEquippedForClass( equipped_class_t unClass ) const +{ + return NULL != FindEquippedInstanceForClass( unClass ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +equipped_slot_t CEconItem::GetEquippedPositionForClass( equipped_class_t unClass ) const +{ + const EquippedInstance_t *pInstance = FindEquippedInstanceForClass( unClass ); + if ( pInstance ) + return pInstance->m_unEquippedSlot; + + return INVALID_EQUIPPED_SLOT; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const CEconItem::EquippedInstance_t *CEconItem::FindEquippedInstanceForClass( equipped_class_t nClass ) const +{ + for ( int i = 0; i < GetEquippedInstanceCount(); i++ ) + { + const EquippedInstance_t &curEquipInstance = GetEquippedInstance( i ); + if ( curEquipInstance.m_unEquippedClass == nClass ) + return &curEquipInstance; + } + + return NULL; +} + + + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void CEconItem::InternalVerifyEquipInstanceIntegrity() const +{ + if ( m_dirtyBits.m_bHasEquipSingleton ) + { + Assert( !m_pCustomData ); + Assert( m_EquipInstanceSingleton.m_unEquippedSlot != INVALID_EQUIPPED_SLOT ); + } + else if ( m_pCustomData ) + { + FOR_EACH_VEC( m_pCustomData->m_vecEquipped, i ) + { + Assert( m_pCustomData->m_vecEquipped[i].m_unEquippedSlot != INVALID_EQUIPPED_SLOT ); + + for ( int j = i + 1; j < m_pCustomData->m_vecEquipped.Count(); j++ ) + { + Assert( m_pCustomData->m_vecEquipped[i].m_unEquippedClass != m_pCustomData->m_vecEquipped[j].m_unEquippedClass ); + } + } + } + else + { + Assert( GetEquippedInstanceCount() == 0 ); + } +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void CEconItem::Equip( equipped_class_t unClass, equipped_slot_t unSlot ) +{ + Assert( GetItemSchema()->IsValidClass( unClass ) ); + Assert( GetItemSchema()->IsValidItemSlot( unSlot, unClass ) ); + + // First, make sure we don't have this item already equipped for this class. + UnequipFromClass( unClass ); + + // If we have no instances of this item equipped, we want to shove this into the + // first empty slot we can find. If we already have a custom data allocated, we + // use that. If not, we want to use the singleton if we can. Otherwise, we make + // a new custom data and fall back to using that. + if ( m_pCustomData ) + { + m_pCustomData->m_vecEquipped.AddToTail( EquippedInstance_t( unClass, unSlot ) ); + } + else if ( !m_dirtyBits.m_bHasEquipSingleton ) + { + m_EquipInstanceSingleton = EquippedInstance_t( unClass, unSlot ); + m_dirtyBits.m_bHasEquipSingleton = true; + } + else + { + EnsureCustomDataExists(); + m_pCustomData->m_vecEquipped.AddToTail( EquippedInstance_t( unClass, unSlot ) ); + } + + InternalVerifyEquipInstanceIntegrity(); + +#ifdef GC_DLL + m_bEquippedThisGameServerSession = true; +#endif // GC_DLL + +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void CEconItem::Unequip() +{ + if ( m_dirtyBits.m_bHasEquipSingleton ) + { + Assert( !m_pCustomData ); + m_dirtyBits.m_bHasEquipSingleton = false; + } + else if ( m_pCustomData ) + { + m_pCustomData->m_vecEquipped.Purge(); + } + + InternalVerifyEquipInstanceIntegrity(); +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void CEconItem::UnequipFromClass( equipped_class_t unClass ) +{ + Assert( GetItemSchema()->IsValidClass( unClass ) ); + + // If we only have a single equipped class... + if ( m_dirtyBits.m_bHasEquipSingleton ) + { + // ...and that's the class we're trying to remove from... + if ( m_EquipInstanceSingleton.m_unEquippedClass == unClass ) + { + // ...we now have no equipped classes! + m_dirtyBits.m_bHasEquipSingleton = false; + } + } + else if ( m_pCustomData ) + { + // ...otherwise, if we have multiple equipped classes... + FOR_EACH_VEC( m_pCustomData->m_vecEquipped, i ) + { + // ...then look through our list to find out if we have this class... + if ( m_pCustomData->m_vecEquipped[i].m_unEquippedClass == unClass ) + { + // ...and if we do, remove it. + m_pCustomData->m_vecEquipped.FastRemove( i ); + break; + } + } + } + + InternalVerifyEquipInstanceIntegrity(); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +int CEconItem::GetEquippedInstanceCount() const +{ + if ( m_pCustomData ) + return m_pCustomData->m_vecEquipped.Count(); + else + return m_dirtyBits.m_bHasEquipSingleton ? 1 : 0; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const CEconItem::EquippedInstance_t &CEconItem::GetEquippedInstance( int iIdx ) const +{ + Assert( iIdx >= 0 && iIdx < GetEquippedInstanceCount() ); + + if ( m_pCustomData ) + return m_pCustomData->m_vecEquipped[iIdx]; + else + return m_EquipInstanceSingleton; +} +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const char *CEconItem::GetCustomDesc() const +{ + static CSchemaAttributeDefHandle pAttrDef_CustomDesc( "custom desc attr" ); + + return GetCustomNameOrAttributeDesc( this, pAttrDef_CustomDesc ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SetCustomDesc( const char *pDesc ) +{ + static CSchemaAttributeDefHandle pAttrDef_CustomDesc( "custom desc attr" ); + + SetCustomNameOrDescAttribute( this, pAttrDef_CustomDesc, pDesc ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::GetInUse() const +{ + return ( m_dirtyBits.m_bInUse ) != 0; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SetInUse( bool bInUse ) +{ + if ( bInUse ) + { + m_dirtyBits.m_bInUse = 1; + } + else + { + m_dirtyBits.m_bInUse = 0; + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const GameItemDefinition_t *CEconItem::GetItemDefinition() const +{ + const CEconItemDefinition *pRet = GetItemSchema()->GetItemDefinition( GetDefinitionIndex() ); + const GameItemDefinition_t *pTypedRet = dynamic_cast<const GameItemDefinition_t *>( pRet ); + + AssertMsg( pRet == pTypedRet, "Item definition of inappropriate type." ); + + return pTypedRet; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::IsTradable() const +{ + return !m_dirtyBits.m_bInUse + && IEconItemInterface::IsTradable(); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::AdoptMoreRestrictedTradabilityFromItem( const CEconItem *pOther, uint32 nTradabilityFlagsToAccept /*= 0xFFFFFFFF*/ ) +{ + if ( !pOther ) + return; + + int nOtherUntradability = pOther->GetUntradabilityFlags() & nTradabilityFlagsToAccept; + RTime32 otherUntradableTime = pOther->GetTradableAfterDateTime(); + // Become untradable if the other item is untradable + AdoptMoreRestrictedTradability( nOtherUntradability, otherUntradableTime ); +} + +// -------------------------------------------------------------------------- +// Purpose: Given untradability flags and a untradable time, set this item's +// untradability. This does not clear existing untradabilty. +// -------------------------------------------------------------------------- +void CEconItem::AdoptMoreRestrictedTradability( uint32 nTradabilityFlags, RTime32 nUntradableTime ) +{ + static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" ); + static CSchemaAttributeDefHandle pAttrib_TradableAfter( "tradable after date" ); + + if ( !pAttrib_CannotTrade || !pAttrib_TradableAfter ) + return; + + // We're already permanently untradable. We can't get more untradable, so we're done. + if ( GetUntradabilityFlags() & k_Untradability_Permanent ) + return; + + if( nTradabilityFlags & k_Untradability_Permanent ) + { + SetDynamicAttributeValue( pAttrib_CannotTrade, 0u ); + } + else if ( nTradabilityFlags & k_Untradability_Temporary && nUntradableTime > GetTradableAfterDateTime() ) + { + // Take the "tradable after date" if it's larger than ours + SetDynamicAttributeValue( pAttrib_TradableAfter, nUntradableTime ); + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::IsMarketable() const +{ + return !m_dirtyBits.m_bInUse + && IEconItemInterface::IsMarketable(); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::IsCommodity() const +{ + return !m_dirtyBits.m_bInUse + && IEconItemInterface::IsCommodity(); +} + +void CEconItem::IterateAttributes( IEconItemAttributeIterator *pIterator ) const +{ + Assert( pIterator ); + + // custom attributes? + for ( int i = 0; i < GetDynamicAttributeCountInternal(); i++ ) + { + const attribute_t &attrib = GetDynamicAttributeInternal( i ); + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( attrib.m_unDefinitionIndex ); + if ( !pAttrDef ) + continue; + + if ( !pAttrDef->GetAttributeType()->OnIterateAttributeValue( pIterator, pAttrDef, attrib.m_value ) ) + return; + } + + // in static attributes? + const CEconItemDefinition *pItemDef = GetItemDefinition(); + if ( !pItemDef ) + return; + + pItemDef->IterateAttributes( pIterator ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +style_index_t CEconItem::GetStyle() const +{ + static CSchemaAttributeDefHandle pAttrDef_ItemStyleOverride( "item style override" ); + float fStyleOverride = 0.f; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, pAttrDef_ItemStyleOverride, &fStyleOverride ) ) + { + return fStyleOverride; + } + + static CSchemaAttributeDefHandle pAttrDef_ItemStyleStrange( "style changes on strange level" ); + uint32 iMaxStyle = 0; + if ( pAttrDef_ItemStyleStrange && FindAttribute( pAttrDef_ItemStyleStrange, &iMaxStyle ) ) + { + // Use the strange prefix if the weapon has one. + uint32 unScore = 0; + if ( !FindAttribute( GetKillEaterAttr_Score( 0 ), &unScore ) ) + return 0; + + // What type of event are we tracking and how does it describe itself? + uint32 unKillEaterEventType = 0; + // This will overwrite our default 0 value if we have a value set but leave it if not. + float fKillEaterEventType; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, GetKillEaterAttr_Type( 0 ), &fKillEaterEventType ) ) + { + unKillEaterEventType = fKillEaterEventType; + } + + const char *pszLevelingDataName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( unKillEaterEventType ); + if ( !pszLevelingDataName ) + { + pszLevelingDataName = KILL_EATER_RANK_LEVEL_BLOCK_NAME; + } + + const CItemLevelingDefinition *pLevelDef = GetItemSchema()->GetItemLevelForScore( pszLevelingDataName, unScore ); + if ( !pLevelDef ) + return 0; + + return Min( pLevelDef->GetLevel(), iMaxStyle ); + } + + return m_unStyle; +} + +const char* CEconItem::FindIconURL( bool bLarge ) const +{ + const char* pszSize = bLarge ? "l" : "s"; + + static CSchemaAttributeDefHandle pAttrDef_IsFestivized( "is_festivized" ); + bool bIsFestivized = pAttrDef_IsFestivized ? FindAttribute( pAttrDef_IsFestivized ) : false; + + const CEconItemDefinition *pDef = GetItemDefinition(); + + // Go through and figure out all the different decorations on + // this item and construct the key to lookup the icon. + // NOTE: These are not currently composable, so they return out when + // a match is found. Once items are more composable, we'll want + // to keep adding all the components together to get the fully + // composed icon (ie. add the strange token, and the festive token, etc.) + const CEconItemPaintKitDefinition* pPaintKitDef = pDef->GetCustomPainkKitDefinition(); + if ( pPaintKitDef ) + { + float flWear = 0; + GetCustomPaintKitWear( flWear ); + int iWearIndex = EconWear_ToIntCategory( flWear ); + const char* pszFmtStr = bIsFestivized ? "%s%sw%df" : "%s%sw%d"; + + const char* pszValue = pDef->GetIconURL( CFmtStr( pszFmtStr, pszSize, pPaintKitDef->GetName(), iWearIndex ) ); + if ( pszValue ) + return pszValue; + } + + const CEconStyleInfo *pStyle = pDef->GetStyleInfo( GetStyle() ); + if ( pStyle ) + { + const char* pszValue = pDef->GetIconURL( CFmtStr( "%ss%d", pszSize, GetStyle() ) ); + if ( pszValue ) + return pszValue; + } + + if ( bIsFestivized ) + { + const char* pszValue = pDef->GetIconURL( CFmtStr( "%sf", pszSize ) ); + if ( pszValue ) + return pszValue; + } + + return pDef->GetIconURL( CFmtStr( "%s", pszSize ) ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const char *CEconItem::GetIconURLSmall() const +{ + if ( m_pszSmallIcon == NULL ) + { + m_pszSmallIcon = FindIconURL( false ); + } + + return m_pszSmallIcon; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const char *CEconItem::GetIconURLLarge() const +{ + if ( m_pszLargeIcon == NULL ) + { + m_pszLargeIcon = FindIconURL( true ); + } + + return m_pszLargeIcon; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::IsUsableInCrafting() const +{ + return !m_dirtyBits.m_bInUse + && IEconItemInterface::IsUsableInCrafting(); +} + +#ifdef GC_DLL +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +RTime32 CEconItem::GetAssetInfoExpirationCacheExpirationTime() const +{ + return GetTradableAfterDateTime(); +} +#endif // GC_DLL + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +int CEconItem::GetDynamicAttributeCountInternal() const +{ + if ( m_pCustomData ) + return m_pCustomData->m_vecAttributes.Count(); + else + return m_dirtyBits.m_bHasAttribSingleton ? 1 : 0; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItem::attribute_t &CEconItem::GetMutableDynamicAttributeInternal( int iAttrIndexIntoArray ) +{ + Assert( iAttrIndexIntoArray >= 0 ); + Assert( iAttrIndexIntoArray < GetDynamicAttributeCountInternal() ); + + if ( m_pCustomData ) + return m_pCustomData->m_vecAttributes[ iAttrIndexIntoArray ]; + else + return m_CustomAttribSingleton; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItem::attribute_t *CEconItem::FindDynamicAttributeInternal( const CEconItemAttributeDefinition *pAttrDef ) +{ + Assert( pAttrDef ); + + if ( m_pCustomData ) + { + FOR_EACH_VEC( m_pCustomData->m_vecAttributes, i ) + { + if ( m_pCustomData->m_vecAttributes[i].m_unDefinitionIndex == pAttrDef->GetDefinitionIndex() ) + return &m_pCustomData->m_vecAttributes[i]; + } + } + else if ( m_dirtyBits.m_bHasAttribSingleton ) + { + if ( m_CustomAttribSingleton.m_unDefinitionIndex == pAttrDef->GetDefinitionIndex() ) + return &m_CustomAttribSingleton; + } + + return NULL; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItem::attribute_t &CEconItem::AddDynamicAttributeInternal() +{ + if ( 0 == GetDynamicAttributeCountInternal() && NULL == m_pCustomData ) + { + m_dirtyBits.m_bHasAttribSingleton = true; + return m_CustomAttribSingleton; + } + else + { + EnsureCustomDataExists(); + return m_pCustomData->m_vecAttributes[ m_pCustomData->m_vecAttributes.AddToTail() ]; + } +} + +// -------------------------------------------------------------------------- +void CEconItem::SetDynamicMaxTimeAttributeValue( const CEconItemAttributeDefinition *pAttrDef, RTime32 rtTime ) +{ + RTime32 rtExistingTime = 0; + if ( FindAttribute( pAttrDef, &rtExistingTime ) ) + { + //we have the attribute already, and see if the value exceeds what we are going to set + if ( rtExistingTime >= rtTime ) + return; + } + + //it doesn't so we need to update + SetDynamicAttributeValue( pAttrDef, rtTime ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SetTradableAfterDateTime( RTime32 rtTime ) +{ + //don't bother if the time is in the past (this also covers the 0 case) + if( rtTime < CRTime::RTime32TimeCur() ) + return; + + //the attribute we are going to assign + static CSchemaAttributeDefHandle pAttrib_TradableAfter( "tradable after date" ); + if( !pAttrib_TradableAfter ) + return; + + //see if we have a STATIC cannot trade attribute (ignore dynamic, because that could change and be used + // to short out the trade restriction). + + //This is currently disabled so we can measure whether or not this is beneficial and if the savings justifies the corner case risk this exposes - JohnO 1/12/15 + /* + const GameItemDefinition_t* pItemDef = GetItemDefinition(); + if( pItemDef ) + { + static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" ); + uint32 unCannotTrade = 0; + if( ::FindAttribute( pItemDef, pAttrib_CannotTrade, &unCannotTrade ) ) + { + return; + } + } + */ + + //now set it to the maximum time + SetDynamicMaxTimeAttributeValue( pAttrib_TradableAfter, rtTime ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::RemoveDynamicAttribute( const CEconItemAttributeDefinition *pAttrDef ) +{ + Assert( pAttrDef ); + Assert( pAttrDef->GetDefinitionIndex() != INVALID_ATTRIB_DEF_INDEX ); + + if ( m_pCustomData ) + { + for ( int i = 0; i < m_pCustomData->m_vecAttributes.Count(); i++ ) + { + if ( m_pCustomData->m_vecAttributes[i].m_unDefinitionIndex == pAttrDef->GetDefinitionIndex() ) + { + CEconItemCustomData::FreeAttributeMemory( &m_pCustomData->m_vecAttributes[i] ); + m_pCustomData->m_vecAttributes.FastRemove( i ); + return; + } + } + } + else if ( m_dirtyBits.m_bHasAttribSingleton ) + { + if ( m_CustomAttribSingleton.m_unDefinitionIndex == pAttrDef->GetDefinitionIndex() ) + { + CEconItemCustomData::FreeAttributeMemory( &m_CustomAttribSingleton ); + m_dirtyBits.m_bHasAttribSingleton = false; + } + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +/*static*/ void CEconItemCustomData::FreeAttributeMemory( CEconItem::attribute_t *pAttrib ) +{ + Assert( pAttrib ); + + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( pAttrib->m_unDefinitionIndex ); + Assert( pAttrDef ); + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + pAttrType->UnloadEconAttributeValue( &pAttrib->m_value ); +} + +// -------------------------------------------------------------------------- +// Purpose: Frees any unused memory in the internal structures +// -------------------------------------------------------------------------- +void CEconItem::Compact() +{ + if ( m_pCustomData ) + { + m_pCustomData->m_vecAttributes.Compact(); + m_pCustomData->m_vecEquipped.Compact(); + } +} + +#ifdef GC_DLL +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +static bool BInsertEquippedInstanceSQL( const CEconItem *pItem, const CEconItem::EquippedInstance_t& equipInst, CSQLAccess& sqlAccess ) +{ + Assert( pItem ); + + const char *pszUpdateOrInsert = "MERGE EquipInstance AS tblEquipInstance " + "USING (SELECT ? AS AccountID, ? AS ClassID, ? AS SlotID) AS tblEquipInstance_NewRow " + "ON (tblEquipInstance.AccountID = tblEquipInstance_NewRow.AccountID AND " + "tblEquipInstance.ClassID = tblEquipInstance_NewRow.ClassID AND " + "tblEquipInstance.SlotID = tblEquipInstance_NewRow.SlotID) " + "WHEN MATCHED THEN " + "UPDATE SET tblEquipInstance.ItemID = ? " + "WHEN NOT MATCHED BY TARGET THEN " + "INSERT (AccountID, ClassID, SlotID, ItemID) VALUES (?, ?, ?, ?);"; + + sqlAccess.AddBindParam( pItem->GetAccountID() ); // USING (SELECT ... AS AccountID) + sqlAccess.AddBindParam( equipInst.m_unEquippedClass ); // USING (SELECT ... AS ClassID) + sqlAccess.AddBindParam( equipInst.m_unEquippedSlot ); // USING (SELECT ... AS SlotID) + sqlAccess.AddBindParam( pItem->GetItemID() ); // UPDATE SET ... + + sqlAccess.AddBindParam( pItem->GetAccountID() ); // INSERT (AccountID) + sqlAccess.AddBindParam( equipInst.m_unEquippedClass ); // INSERT (ClassID) + sqlAccess.AddBindParam( equipInst.m_unEquippedSlot ); // INSERT (SlotID) + sqlAccess.AddBindParam( pItem->GetItemID() ); // INSERT (ItemID) + + return sqlAccess.BYieldingExecute( "BYieldingOnHandledPeriodicScoreTimePeriod", pszUpdateOrInsert ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +static bool BInsertAllEquippedInstancesSQL( const CEconItem *pItem, CSQLAccess& sqlAccess ) +{ + Assert( pItem ); + + for ( int i = 0; i < pItem->GetEquippedInstanceCount(); i++ ) + { + if ( !BInsertEquippedInstanceSQL( pItem, pItem->GetEquippedInstance( i ), sqlAccess ) ) + return false; + } + + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: Adds non-Item-table inserts to the SQL insert for this object +// -------------------------------------------------------------------------- +bool CEconItem::BYieldingAddInsertToTransaction( CSQLAccess & sqlAccess ) +{ + // @note Tom Bui: This could be simplified greatly, but let's see if it is an issue + CSchItem item; + SerializeToSchemaItem( item ); + if( !CSchemaSharedObjectHelper::BYieldingAddInsertToTransaction( sqlAccess, &item ) ) + return false; + + // attributes get to written to any number of joined tables based on type + for ( int i = 0; i < GetDynamicAttributeCountInternal(); i++ ) + { + const attribute_t &attrib = GetDynamicAttributeInternal( i ); + + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( attrib.m_unDefinitionIndex ); + if ( !pAttrDef ) + { + EmitError( SPEW_GC, "CEconItem::BYieldingAddInsertToTransaction(): attempt to insert unknown attribute ID %d.\n", attrib.m_unDefinitionIndex ); + return false; + } + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + CPlainAutoPtr<CRecordBase> pAttrRecord( pAttrType->CreateTypedSchRecord() ); + pAttrType->ConvertEconAttributeValueToSch( GetItemID(), pAttrDef, attrib.m_value, pAttrRecord.Get() ); + if( !sqlAccess.BYieldingInsertRecord( pAttrRecord.Get() ) ) + return false; + } + + // currently-equipped positions get written to a joined table + if ( !BInsertAllEquippedInstancesSQL( this, sqlAccess ) ) + return false; + + // add audit record for the create + CEconItemDefinition *pItemDef = GEconManager()->GetItemSchema()->GetItemDefinition( GetDefinitionIndex() ); + if ( pItemDef ) + { + const char* pDatabaseAuditTableName = pItemDef->GetDatabaseAuditTableName(); + if ( pDatabaseAuditTableName ) + { + CFmtStr1024 sStatement; + sStatement.sprintf( "INSERT INTO %s (ItemID) VALUES (%llu)", pDatabaseAuditTableName, m_ulID ); + uint32 nRows; + bool bRet = sqlAccess.BYieldingExecute( sStatement, sStatement, &nRows ); + if ( bRet == false ) + { + CSteamID steamID( m_unAccountID, GGCHost()->GetUniverse(), k_EAccountTypeIndividual ); + EmitError( SPEW_GC, "Failed to add item audit to table %s for %s and item %llu.\n", pDatabaseAuditTableName, steamID.Render(), m_ulID ); + return false; + } + } + } + + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +static bool BRemoveEquipInstancesFromSQL( CSQLAccess& sqlAccess, uint32 unAccountID, itemid_t unItemID ) +{ + CSchEquipInstance schEquipInstanceWhere; + schEquipInstanceWhere.m_unAccountID = unAccountID; + schEquipInstanceWhere.m_ulItemID = unItemID; + + return sqlAccess.BYieldingDeleteRecords( schEquipInstanceWhere, CSET_2_COL( CSchEquipInstance, k_iField_unAccountID, k_iField_ulItemID ) ); +} + +//---------------------------------------------------------------------------- +// Purpose: Writes the non-PK fields on the object to the database +//---------------------------------------------------------------------------- +bool CEconItem::BYieldingAddWriteToTransaction( CSQLAccess & sqlAccess, const CUtlVector< int > &fields ) +{ + Assert( sqlAccess.BInTransaction() ); + + if( IsForeign() ) + { + CSchForeignItem schForeignItem; + schForeignItem.m_ulID = GetItemID(); + schForeignItem.m_unInventory = GetInventoryToken(); + return CSchemaSharedObjectHelper::BYieldingAddWriteToTransaction( sqlAccess, &schForeignItem, CSET_1_COL( CSchForeignItem, k_iField_unInventory ) ); + } + + // Non-foreign items. + CSchItem item; + SerializeToSchemaItem( item ); + CColumnSet csDatabaseDirty( item.GetPRecordInfo() ); + GetDirtyColumnSet( fields, csDatabaseDirty ); + + // Write out base item properties if any are dirty. It's possible to get here with only dirty attributes, + // in which case we want to neither write to our item properties nor abort here if the write fails. + if ( !csDatabaseDirty.IsEmpty() && !CSchemaSharedObjectHelper::BYieldingAddWriteToTransaction( sqlAccess, &item, csDatabaseDirty ) ) + return false; + + // Write out additional attribute properties? + FOR_EACH_VEC( fields, i ) + { + uint32 iFieldID = fields[i] & 0x0000ffff, // low 16 bits are the ID + iFieldIDType = fields[i] & 0xffff0000; // high 16 bits are the ID type + + if ( iFieldIDType == kUpdateFieldIDType_FieldID ) + { + // Most item field IDs will be updated above in SerializeToSchemaItem(). Anything that needs custom + // writing behavior should handle it here. + + // If we've changed our equip state, remove all of our previous equipped instances (if any) and write + // out our new rows (if any). + if ( iFieldID == CSOEconItem::kEquippedStateFieldNumber ) + { + if ( !BRemoveEquipInstancesFromSQL( sqlAccess, GetAccountID(), GetID() ) ) + return false; + + if ( !BInsertAllEquippedInstancesSQL( this, sqlAccess ) ) + return false; + } + } + else if ( iFieldIDType == kUpdateFieldIDType_AttributeID ) + { + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( iFieldID ); + Assert( pAttrDef ); + + // Did we dirty an attribute and then remove it entirely? + const attribute_t *pEconAttr = FindDynamicAttributeInternal( pAttrDef ); + if ( !pEconAttr ) + continue; + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + CPlainAutoPtr<CRecordBase> pAttrRecord( pAttrType->CreateTypedSchRecord() ); + pAttrType->ConvertEconAttributeValueToSch( GetItemID(), pAttrDef, pEconAttr->m_value, pAttrRecord.Get() ); + + CColumnSet csWhere( pAttrRecord->GetPRecordInfo() ); + csWhere.BAddColumn( 0 ); + csWhere.BAddColumn( 1 ); + + CColumnSet csUpdate( CColumnSet::Inverse( csWhere ) ); + Assert( !csUpdate.IsEmpty() ); + + if ( !sqlAccess.BYieldingUpdateRecord( *pAttrRecord.Get(), csWhere, csUpdate ) ) + return false; + } + // Our field ID is something we don't recognize. + else + { + AssertMsg( false, "Unknown field ID type in CEconItem::BYieldingAddWriteToTransaction()." ); + } + } + + return true; +} + + +// -------------------------------------------------------------------------- +// Purpose: "deletes" an object by setting its owner to 0. +// -------------------------------------------------------------------------- +bool CEconItem::BYieldingAddRemoveToTransaction( CSQLAccess & sqlAccess ) +{ + Assert( sqlAccess.BInTransaction() ); + + // we never remove econ items in the server + sqlAccess.AddBindParam( GetItemID() ); + if( !sqlAccess.BYieldingExecute( NULL, "UPDATE Item SET AccountId = 0 WHERE ID = ?" ) ) + return false; + + if ( !BRemoveEquipInstancesFromSQL( sqlAccess, GetAccountID(), GetItemID() ) ) + return false; + + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: Load the interior item from the database. +// -------------------------------------------------------------------------- +bool CEconItem::BYieldingLoadInteriorItem() +{ + static CSchemaAttributeDefHandle pAttribLow( "referenced item id low" ); + static CSchemaAttributeDefHandle pAttribHigh( "referenced item id high" ); + + // see if it's already loaded + if ( m_pCustomData && m_pCustomData->m_pInteriorItem ) + return true; + + // we require the low 32 bits... + uint32 unAttribValueBitsLow; + if ( !FindAttribute( pAttribLow, &unAttribValueBitsLow ) ) + return false; + + // ...but default the high 32 bits to 0 if not present + uint32 unAttribValueBitsHigh = 0; + FindAttribute( pAttribHigh, &unAttribValueBitsHigh ); // return value ignored + + COMPILE_TIME_ASSERT( sizeof( uint64 ) == sizeof( itemid_t ) ); + uint64 wrappedItemId = ( uint64( unAttribValueBitsHigh ) << 32 ) | uint64( unAttribValueBitsLow ); + + // allocate scratch object to try and fill with DB contents + CEconItem *pNewItem = new CEconItem; + if ( !pNewItem->BYieldingSerializeFromDatabase( wrappedItemId ) ) + { + delete pNewItem; + return false; + } + + // DB load succeeded -- make sure if we're treating this item ID as an interior item, it isn't + // currently owned by someone. this is a non-fatal error (it could happen if support rolls back + // transactions in weird ways) but it probably means we have something weird going on + if ( pNewItem->GetAccountID() != 0 ) + { + // ... + } + + // everything loaded alright so hook our reference up + EnsureCustomDataExists(); + m_pCustomData->m_pInteriorItem = pNewItem; + return true; +} + + +// -------------------------------------------------------------------------- +// Purpose: Directly set the interior item +// -------------------------------------------------------------------------- +void CEconItem::SetInteriorItem( CEconItem* pInteriorItem ) +{ + EnsureCustomDataExists(); + m_pCustomData->m_pInteriorItem = pInteriorItem; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItem* CEconItem::YieldingGetInteriorItem() +{ + if ( !m_pCustomData || !m_pCustomData->m_pInteriorItem ) + { + if ( !BYieldingLoadInteriorItem() ) + { + Assert( !GetInteriorItem() ); + } + } + + return GetInteriorItem(); +} + +#endif // GC_DLL + +CEconItem* CEconItem::GetInteriorItem() +{ + return m_pCustomData ? m_pCustomData->m_pInteriorItem : NULL; +} + +// -------------------------------------------------------------------------- +// Purpose: This item has been traded. Give it an opportunity to update any internal +// properties in response. +// -------------------------------------------------------------------------- +void CEconItem::OnTraded( uint32 unTradabilityDelaySeconds ) +{ + // if Steam wants us to impose a tradability delay on the item + if ( unTradabilityDelaySeconds != 0 ) + { + RTime32 rtTradableAfter = ( ( CRTime::RTime32TimeCur() / k_nSecondsPerDay ) * k_nSecondsPerDay ) + unTradabilityDelaySeconds; + SetTradableAfterDateTime( rtTradableAfter ); + } + else + { + // If we have a "tradable after date" attribute and we were just traded, remove the date + // limit as we're obviously past it. + static CSchemaAttributeDefHandle pAttrib_TradableAfter( "tradable after date" ); + RemoveDynamicAttribute( pAttrib_TradableAfter ); + } + + OnTransferredOwnership(); +} + +// -------------------------------------------------------------------------- +// Purpose: Ownership of this item has changed, so do whatever things are necessary +// -------------------------------------------------------------------------- +void CEconItem::OnTransferredOwnership() +{ + // Reset all our strange scores. + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + const CEconItemAttributeDefinition *pAttrDef = GetKillEaterAttr_Score(i); + + // Skip over any attributes our schema doesn't understand. We ideally wouldn't ever + // have this happen but if it does we don't want to ignore other valid attributes. + if ( !pAttrDef ) + continue; + + // Ignore any attributes we don't have on this item. + if ( !FindAttribute( pAttrDef ) ) + continue; + + // Zero out the value of this stat attribute. + SetDynamicAttributeValue( pAttrDef, 0u ); + } + + // Free accounts have the ability to trade any item out that they received in a trade. + SetFlag( kEconItemFlag_CanBeTradedByFreeAccounts ); +} + +// -------------------------------------------------------------------------- +// Purpose: This item has been traded. Give it an opportunity to update any internal +// properties in response. +// -------------------------------------------------------------------------- +void CEconItem::OnReceivedFromMarket( bool bFromRollback ) +{ + OnTransferredOwnership(); + +#ifdef GC_DLL + static CSchemaAttributeDefHandle pAttrib_TradableAfter( "tradable after date" ); + if ( !bFromRollback && GEconManager()->GetTradableAfterDurationForPurchase() != 0 ) + { + // Add "tradable after date" attribute for items received from the market, since the funds + // used to purchase the item may be untrusted + RTime32 rtTradableAfter = GEconManager()->GetTradableAfterDateForPurchase(); + SetDynamicAttributeValue( pAttrib_TradableAfter, rtTradableAfter ); + } + // Do we need to remove this attribute on rollback? + /*else + { + RemoveDynamicAttribute( pAttrib_TradableAfter ); + }*/ +#endif // GC_DLL +} + +// -------------------------------------------------------------------------- +// Purpose: Parses the bits required to create a econ item from the message. +// Overloaded to include support for attributes. +// -------------------------------------------------------------------------- +bool CEconItem::BParseFromMessage( const CUtlBuffer & buffer ) +{ + CSOEconItem msgItem; + if( !msgItem.ParseFromArray( buffer.Base(), buffer.TellMaxPut() ) ) + return false; + + DeserializeFromProtoBufItem( msgItem ); + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: Parses the bits required to create a econ item from the message. +// Overloaded to include support for attributes. +// -------------------------------------------------------------------------- +bool CEconItem::BParseFromMessage( const std::string &buffer ) +{ + CSOEconItem msgItem; + if( !msgItem.ParseFromString( buffer ) ) + return false; + + DeserializeFromProtoBufItem( msgItem ); + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: Overrides all the fields in msgLocal that are present in the +// network message +//---------------------------------------------------------------------------- +bool CEconItem::BUpdateFromNetwork( const CSharedObject & objUpdate ) +{ + const CEconItem & econObjUpdate = (const CEconItem &)objUpdate; + + *this = econObjUpdate; + + return true; +} + +#ifdef GC +//---------------------------------------------------------------------------- +// Purpose: Adds the relevant bits to update this object to the message. This +// must include any relevant information about which fields are being +// updated. This is called once for all subscribers. +//---------------------------------------------------------------------------- +bool CEconItem::BAddToMessage( CUtlBuffer & bufOutput ) const +{ + VPROF_BUDGET( "CEconItem::BAddToMessage::CUtlBuffer", VPROF_BUDGETGROUP_STEAM ); + // StaticAssert( sizeof( int ) >= sizeof( uint32 ) ); + + CSOEconItem msg; + SerializeToProtoBufItem( msg ); + return CProtoBufSharedObjectBase::SerializeToBuffer( msg, bufOutput ); +} + +//---------------------------------------------------------------------------- +// Purpose: Adds the relevant bits to update this object to the message. This +// must include any relevant information about which fields are being +// updated. This is called once for all subscribers. +//---------------------------------------------------------------------------- +bool CEconItem::BAddToMessage( std::string *pBuffer ) const +{ + VPROF_BUDGET( "CEconItem::BAddToMessage::std::string", VPROF_BUDGETGROUP_STEAM ); + // StaticAssert( sizeof( int ) >= sizeof( uint32 ) ); + + CSOEconItem msg; + SerializeToProtoBufItem( msg ); + return msg.SerializeToString( pBuffer ); +} + +//---------------------------------------------------------------------------- +// Purpose: Adds just the item ID to the message so that the client can find +// which item to destroy +//---------------------------------------------------------------------------- +bool CEconItem::BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const +{ + CSOEconItem msgItem; + msgItem.set_id( GetItemID() ); + CProtoBufSharedObjectBase::SerializeToBuffer( msgItem, bufDestroy ); + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: Adds just the item ID to the message so that the client can find +// which item to destroy +//---------------------------------------------------------------------------- +bool CEconItem::BAddDestroyToMessage( std::string *pBuffer ) const +{ + CSOEconItem msgItem; + msgItem.set_id( GetItemID() ); + return msgItem.SerializeToString( pBuffer ); +} +#endif //GC + +//---------------------------------------------------------------------------- +// Purpose: Returns true if this is less than than the object in soRHS. This +// comparison is deterministic, but it may not be pleasing to a user +// since it is just going to compare raw memory. If you need a sort +// that is user-visible you will need to do it at a higher level that +// actually knows what the data in these objects means. +//---------------------------------------------------------------------------- +bool CEconItem::BIsKeyLess( const CSharedObject & soRHS ) const +{ + Assert( GetTypeID() == soRHS.GetTypeID() ); + const CEconItem & soSchemaRHS = (const CEconItem &)soRHS; + + return m_ulID < soSchemaRHS.m_ulID; +} + +//---------------------------------------------------------------------------- +// Purpose: Copy the data from the specified schema shared object into this. +// Both objects must be of the same type. +//---------------------------------------------------------------------------- +void CEconItem::Copy( const CSharedObject & soRHS ) +{ + *this = (const CEconItem &)soRHS; +} + +//---------------------------------------------------------------------------- +// Purpose: Dumps diagnostic information about the shared object +//---------------------------------------------------------------------------- +void CEconItem::Dump() const +{ + CSOEconItem msgItem; + SerializeToProtoBufItem( msgItem ); + CProtoBufSharedObjectBase::Dump( msgItem ); +} + + +//---------------------------------------------------------------------------- +// Purpose: Return short, identifying string about the object +//---------------------------------------------------------------------------- +CUtlString CEconItem::GetDebugString() const +{ + CUtlString result; + result.Format( "[CEconItem: ID=%llu, DefIdx=%d]", GetItemID(), GetDefinitionIndex() ); + return result; +} + + +#ifdef GC +//----------------------------------------------------------------------------- +// Purpose: Deserializes an item from a KV object +// Input: pKVItem - Pointer to the KV structure that represents an item +// schema - Econ item schema used for decoding human readable names +// pVecErrors - Pointer to a vector where human readable errors will +// be added +// Output: True if the item deserialized successfully, false otherwise +//----------------------------------------------------------------------------- +bool CEconItem::BDeserializeFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors ) +{ + Assert( pVecErrors ); + + Assert( NULL != pKVItem ); + if ( NULL == pKVItem ) + return false; + + // The basic properties + SetItemID( pKVItem->GetUint64( "ID", INVALID_ITEM_ID ) ); + SetInventoryToken( pKVItem->GetInt( "InventoryPos", GetUnacknowledgedPositionFor(UNACK_ITEM_DROPPED) ) ); // Start by assuming it's a drop + SetQuantity( pKVItem->GetInt( "Quantity", 1 ) ); + + // Look up the following properties based on names from the schema + const char *pchDefName = pKVItem->GetString( "DefName" ); + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pchDefName ); + if( !pItemDef ) + { + pVecErrors->AddToTail( CUtlString( CFmtStr( "Item definition \"%s\" not found", pchDefName ) ) ); + + // we can't do any reasonable validation with no item def, so just stop here + return false; + } + + SetDefinitionIndex( pItemDef->GetDefinitionIndex() ); + + const char *pchQualityName = pKVItem->GetString( "QualityName" ); + if( !pchQualityName || ! *pchQualityName ) + { + // set the default quality for the definition + if( pItemDef->GetQuality() == k_unItemQuality_Any ) + { + pVecErrors->AddToTail( CUtlString( CFmtStr( "Quality was not specified and this item def doesn't define one either." ) ) ); + } + else + { + SetQuality( pItemDef->GetQuality() ); + } + } + else if ( !GetItemSchema()->BGetItemQualityFromName( pchQualityName, &m_nQuality ) || k_unItemQuality_Any == GetQuality() ) + { + pVecErrors->AddToTail( CUtlString( CFmtStr( "Quality \"%s\" not found", pchQualityName ) ) ); + } + + // make sure the level is sane + SetItemLevel( pKVItem->GetInt( "Level", pItemDef->GetMinLevel() ) ); + + // read the flags + uint8 unFlags = GetFlags(); + if( pKVItem->GetInt( "flag_cannot_trade", 0 ) ) + { + unFlags |= kEconItemFlag_CannotTrade; + } + else + { + unFlags = unFlags & ~kEconItemFlag_CannotTrade; + } + if( pKVItem->GetInt( "flag_cannot_craft", 0 ) ) + { + unFlags |= kEconItemFlag_CannotBeUsedInCrafting; + } + else + { + unFlags = unFlags & ~kEconItemFlag_CannotBeUsedInCrafting; + } + if( pKVItem->GetInt( "flag_non_economy", 0 ) ) + { + unFlags |= kEconItemFlag_NonEconomy; + } + else + { + unFlags = unFlags & ~kEconItemFlag_NonEconomy; + } + SetFlag( unFlags ); + + // Deserialize the attributes + KeyValues *pKVAttributes = pKVItem->FindKey( "Attributes" ); + if ( NULL != pKVAttributes ) + { + FOR_EACH_SUBKEY( pKVAttributes, pKVAttr ) + { + // Try to load each line into an attribute in memory. It's possible that if we fail to successfully + // load some attribute contents here we'll leak small amounts of memory, but if that happens we're + // going to fail to start up anyway so we don't really care. + static_attrib_t staticAttrib; + if ( !staticAttrib.BInitFromKV_SingleLine( __FUNCTION__, pKVAttr, pVecErrors ) ) + continue; + + const CEconItemAttributeDefinition *pAttrDef = staticAttrib.GetAttributeDefinition(); + Assert( pAttrDef ); + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + // Load the attribute contents into memory on the item. + pAttrType->LoadEconAttributeValue( this, pAttrDef, staticAttrib.m_value ); + + // Free up our temp loading memory. + pAttrType->UnloadEconAttributeValue( &staticAttrib.m_value ); + } + } + + return ( NULL == pVecErrors || 0 == pVecErrors->Count() ); +} + + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::BYieldingSerializeFromDatabase( itemid_t ulItemID ) +{ + static CColumnSet csItem( CSET_FULL( CSchItem ) ); + + enum + { + kQueryResultSet_SingleItem_Item = 0, + kQueryResultSet_SingleItem_Attributes_Base = 1, + + kQueryResultSet_SingleItemCount + }; + + static uint32 sExpectedResults = 0; + static CFmtStrMax sLoadQuery; + if ( 0 == sLoadQuery.Length() ) + { + sLoadQuery.Append( + "DECLARE @ItemID BIGINT " + "SET @ItemID = ? " ); + + // Load the item itself. + TSQLCmdStr sStatement; + BuildSelectStatementText( &sStatement, csItem ); + sStatement.Append( " WHERE ID = @ItemID " ); + sLoadQuery.Append( sStatement ); + DbgVerify( kQueryResultSet_SingleItem_Item == sExpectedResults++ ); + + // Load any associated attributes. + DbgVerify( kQueryResultSet_SingleItem_Attributes_Base == sExpectedResults++ ); + { + const CUtlVector<attr_type_t>& vecAttributeTypes = GetItemSchema()->GetAttributeTypes(); + + FOR_EACH_VEC( vecAttributeTypes, i ) + { + // We could actually skip loading the "ItemID" column here because we're only loading one item + // of data, but we would have to do some work because we don't actually know the CSch types here + // so it doesn't seem worth it. We'll have to update the LoadSQLAttributesToEconItem() code if + // we ever do this. + BuildSelectStatementText( &sStatement, vecAttributeTypes[i].m_pAttrType->GetFullColumnSet() ); + sStatement.Append( " WHERE ItemID = @ItemID " ); + sLoadQuery.Append( sStatement ); + } + } + } + Assert( kQueryResultSet_SingleItemCount == sExpectedResults ); + + // Load item and associated attributes from SQL. + CSQLAccess sqlAccess; + sqlAccess.AddBindParam( ulItemID ); + if ( !sqlAccess.BYieldingExecute( "CEconItem::BYieldingSerializeFromDatabase", sLoadQuery.Get() ) ) + { + EmitError( SPEW_GC, __FUNCTION__ ": failed to execute SQL load for item ID %llu\n", ulItemID ); + return false; + } + + // ... + if ( sqlAccess.GetResultSetCount() < sExpectedResults ) + { + EmitError( SPEW_GC, __FUNCTION__ ": unable to interior item ID %llu. Got %d results back, but expected %d!\n", ulItemID, sqlAccess.GetResultSetCount(), sExpectedResults ); + return false; + } + + // Make sure we only got one item back. + const uint32 nRowCount = sqlAccess.GetResultSetRowCount( kQueryResultSet_SingleItem_Item ); + if ( nRowCount != 1 ) + { + EmitError( SPEW_GC, __FUNCTION__ ": invalid number of rows returned from SQL loading item ID %llu: %d\n", ulItemID, nRowCount ); + return false; + } + + // Make sure we understand what sort of item this is. We can't make any forward progress loading if we don't know + // what definition its attached to. + CSchItem schItem; + sqlAccess.GetResultRecord( kQueryResultSet_SingleItem_Item, 0 ).BWriteToRecord( &schItem, csItem ); + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( schItem.m_unDefIndex ); + if ( !pItemDef ) + { + EmitError( SPEW_GC, __FUNCTION__ ": unable to find item definition %d for item %llu!\n", schItem.m_unDefIndex, schItem.m_ulID ); + return false; + } + + // We have everything we need to complete the load here so default initialize -- free up attribute memory, current + // state, etc. + *this = CEconItem(); + + // Deserialize from DB schema. + DeserializeFromSchemaItem( schItem ); + + // Load attributes. + CEconManager::CEconItemAttributeLoader AttributeLoader; + AttributeLoader.LoadSQLAttributesToEconItem( this, sqlAccess, kQueryResultSet_SingleItem_Attributes_Base ); + + // Done. + Compact(); + + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SerializeToSchemaItem( CSchItem &item ) const +{ + VPROF_BUDGET( "CEconItem::SerializeToSchemaItem()", VPROF_BUDGETGROUP_STEAM ); + + item.m_ulID = m_ulID; + item.m_ulOriginalID = GetOriginalID(); + item.m_unAccountID = m_unAccountID; + item.m_unDefIndex = m_unDefIndex; + item.m_unLevel = m_unLevel; + item.m_nQuality = m_nQuality; + item.m_unInventory = m_unInventory; + item.m_unQuantity = GetQuantity(); + item.m_unFlags = m_unFlags; + item.m_unOrigin = m_unOrigin; + item.m_unStyle = m_unStyle; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::DeserializeFromSchemaItem( const CSchItem &item ) +{ + VPROF_BUDGET( "CEconItem::DeserializeFromSchemaItem()", VPROF_BUDGETGROUP_STEAM ); + + m_ulID = item.m_ulID; + SetOriginalID( item.m_ulOriginalID ? item.m_ulOriginalID : item.m_ulID ); + m_unAccountID = item.m_unAccountID; + m_unDefIndex = item.m_unDefIndex; + m_unLevel = item.m_unLevel; + m_nQuality = item.m_nQuality; + m_unInventory = item.m_unInventory; + SetQuantity( item.m_unQuantity ); + m_unFlags = item.m_unFlags; + m_unOrigin = item.m_unOrigin; + m_unStyle = item.m_unStyle; + + // set name if any, or remove if non-existent + SetCustomName( READ_VAR_CHAR_FIELD( item, m_VarCharCustomName ) ); + + // set desc if any, or remove if non-existent + SetCustomDesc( READ_VAR_CHAR_FIELD( item, m_VarCharCustomDesc ) ); +} +#endif // GC + + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::SerializeToProtoBufItem( CSOEconItem &msgItem ) const +{ + VPROF_BUDGET( "CEconItem::SerializeToProtoBufItem()", VPROF_BUDGETGROUP_STEAM ); + + msgItem.set_id( m_ulID ); + if( m_ulID != GetOriginalID() ) + msgItem.set_original_id( GetOriginalID() ); + msgItem.set_account_id( m_unAccountID ); + msgItem.set_def_index( m_unDefIndex ); + msgItem.set_level( m_unLevel ); + msgItem.set_quality( m_nQuality ); + msgItem.set_inventory( m_unInventory ); + msgItem.set_quantity( GetQuantity() ); + msgItem.set_flags( m_unFlags ); + msgItem.set_origin( m_unOrigin ); + msgItem.set_style( m_unStyle ); + msgItem.set_in_use( m_dirtyBits.m_bInUse ); + + for( int nAttr = 0; nAttr < GetDynamicAttributeCountInternal(); nAttr++ ) + { + const attribute_t & attr = GetDynamicAttributeInternal( nAttr ); + + // skip over attributes we don't understand + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( attr.m_unDefinitionIndex ); + if ( !pAttrDef ) + continue; + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + CSOEconItemAttribute *pMsgAttr = msgItem.add_attribute(); + pMsgAttr->set_def_index( attr.m_unDefinitionIndex ); + + std::string sBytes; + pAttrType->ConvertEconAttributeValueToByteStream( attr.m_value, &sBytes ); + pMsgAttr->set_value_bytes( sBytes ); + } + + msgItem.set_contains_equipped_state_v2( true ); + for ( int i = 0; i < GetEquippedInstanceCount(); i++ ) + { + const EquippedInstance_t &instance = GetEquippedInstance( i ); + CSOEconItemEquipped *pMsgEquipped = msgItem.add_equipped_state(); + pMsgEquipped->set_new_class( instance.m_unEquippedClass ); + pMsgEquipped->set_new_slot( instance.m_unEquippedSlot ); + } + + if ( m_pCustomData ) + { + const char *pszCustomName = GetCustomName(); + if ( pszCustomName ) + { + msgItem.set_custom_name( pszCustomName ); + } + + const char *pszCustomDesc = GetCustomDesc(); + if ( pszCustomDesc ) + { + msgItem.set_custom_desc( pszCustomDesc ); + } + + const CEconItem *pInteriorItem = GetInteriorItem(); + if ( pInteriorItem ) + { + CSOEconItem *pMsgInteriorItem = msgItem.mutable_interior_item(); + pInteriorItem->SerializeToProtoBufItem( *pMsgInteriorItem ); + } + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::DeserializeFromProtoBufItem( const CSOEconItem &msgItem ) +{ + VPROF_BUDGET( "CEconItem::DeserializeFromProtoBufItem()", VPROF_BUDGETGROUP_STEAM ); + + // Start by resetting + SAFE_DELETE( m_pCustomData ); + m_dirtyBits.m_bHasAttribSingleton = false; + m_dirtyBits.m_bHasEquipSingleton = false; + + // Now copy from the message + m_ulID = msgItem.id(); + SetOriginalID( msgItem.has_original_id() ? msgItem.original_id() : m_ulID ); + m_unAccountID = msgItem.account_id(); + m_unDefIndex = msgItem.def_index(); + m_unLevel = msgItem.level(); + m_nQuality = msgItem.quality(); + m_unInventory = msgItem.inventory(); + SetQuantity( msgItem.quantity() ); + m_unFlags = msgItem.flags(); + m_unOrigin = msgItem.origin(); + m_unStyle = msgItem.style(); + + m_dirtyBits.m_bInUse = msgItem.in_use() ? 1 : 0; + + // set name if any + if( msgItem.has_custom_name() ) + { + SetCustomName( msgItem.custom_name().c_str() ); + } + + // set desc if any + if( msgItem.has_custom_desc() ) + { + SetCustomDesc( msgItem.custom_desc().c_str() ); + } + + // read the attributes + for( int nAttr = 0; nAttr < msgItem.attribute_size(); nAttr++ ) + { + // skip over old-format messages + const CSOEconItemAttribute& msgAttr = msgItem.attribute( nAttr ); + if ( msgAttr.has_value() || !msgAttr.has_value_bytes() ) + continue; + + // skip over attributes we don't understand + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( msgAttr.def_index() ); + if ( !pAttrDef ) + continue; + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + pAttrType->LoadByteStreamToEconAttributeValue( this, pAttrDef, msgAttr.value_bytes() ); + } + + // Check to see if the item has an interior object. + if ( msgItem.has_interior_item() ) + { + EnsureCustomDataExists(); + + m_pCustomData->m_pInteriorItem = new CEconItem(); + m_pCustomData->m_pInteriorItem->DeserializeFromProtoBufItem( msgItem.interior_item() ); + } + + // update equipped state + if ( msgItem.has_contains_equipped_state_v2() && msgItem.contains_equipped_state_v2() ) + { + // unequip from everything... + Unequip(); + + // ...and re-equip to whatever our current state is + for ( int i = 0; i < msgItem.equipped_state_size(); i++ ) + { + Equip( msgItem.equipped_state(i).new_class(), msgItem.equipped_state(i).new_slot() ); + } + } +} + +#ifdef GC +#include "econ/localization_provider.h" +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::GetDirtyColumnSet( const CUtlVector< int > &fields, CColumnSet &cs ) const +{ + cs.MakeEmpty(); + /* @note Tom Bui: We know only the inventory/position field and the quantity field can be dirty... + for ( int nField = 0; nField < CSchItem::k_iFieldMax; ++nField ) + { + if ( bDatabase ? BIsFieldDatabaseDirty( nField ) : BIsFieldNetworkDirty( nField ) ) + { + cs.BAddColumn( nField ); + } + } + */ + if( fields.HasElement( CSOEconItem::kInventoryFieldNumber ) ) + cs.BAddColumn( CSchItem::k_iField_unInventory ); + if( fields.HasElement( CSOEconItem::kQuantityFieldNumber ) ) + cs.BAddColumn( CSchItem::k_iField_unQuantity ); + if( fields.HasElement( CSOEconItem::kStyleFieldNumber ) ) + cs.BAddColumn( CSchItem::k_iField_unStyle ); +} + + +// -------------------------------------------------------------------------- +// Purpose: Exports the item for use in another app +// -------------------------------------------------------------------------- +void CEconItem::ExportToAPI( CWebAPIValues *pValues ) const +{ + pValues->CreateChildObject( "id" )->SetUInt64Value( m_ulID ); + pValues->CreateChildObject( "def_index" )->SetUInt32Value( m_unDefIndex ); + pValues->CreateChildObject( "level" )->SetUInt32Value( m_unLevel ); + pValues->CreateChildObject( "quality" )->SetInt32Value( m_nQuality ); + + if( GetCustomName() ) + pValues->CreateChildObject( "custom_name" )->SetStringValue( GetCustomName() ); + if( GetCustomDesc() ) + pValues->CreateChildObject( "custom_desc" )->SetStringValue( GetCustomDesc() ); +} + + +// -------------------------------------------------------------------------- +// Purpose: Imports a view of the item from another app +// -------------------------------------------------------------------------- +bool CEconItem::BImportFromAPI( CWebAPIValues *pValues ) +{ + m_unLevel = pValues->GetChildUInt32Value( "level", 1 ); + m_nQuality = pValues->GetChildUInt32Value( "quality", GetItemSchema()->GetDefaultQuality() ); + + CUtlString sValue; + pValues->GetChildStringValue( sValue, "custom_name", "" ); + CGCLocalizationProvider::BEnsureCleanUTF8Truncation( sValue.GetForModify() ); + if( !sValue.IsEmpty() ) + SetCustomName( sValue.Get() ); + pValues->GetChildStringValue( sValue, "custom_desc", "" ); + CGCLocalizationProvider::BEnsureCleanUTF8Truncation( sValue.GetForModify() ); + if( !sValue.IsEmpty() ) + SetCustomDesc( sValue.Get() ); + + return true; +} + +#endif + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItem::EnsureCustomDataExists() +{ + if ( m_pCustomData == NULL ) + { + m_pCustomData = new CEconItemCustomData(); + + if ( m_dirtyBits.m_bHasEquipSingleton ) + { + m_pCustomData->m_vecEquipped.AddToTail( m_EquipInstanceSingleton ); + m_EquipInstanceSingleton = EquippedInstance_t(); + m_dirtyBits.m_bHasEquipSingleton = false; + } + if ( m_dirtyBits.m_bHasAttribSingleton ) + { + m_pCustomData->m_vecAttributes.AddToTail( m_CustomAttribSingleton ); + m_dirtyBits.m_bHasAttribSingleton = false; + } + } +} + +#ifdef GC_DLL +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +static bool BYieldingAddAuditRecordImpl( GCSDK::CSQLAccess *sqlAccess, uint64 ulItemID, uint32 unOwnerID, EItemAction eAction, uint32 unData ) +{ + CEconUserSession *pUserSession = GGCGameBase()->FindEconUserSession( CSteamID( unOwnerID, GGCHost()->GetUniverse(), k_EAccountTypeIndividual ) ); + CGCGSSession *pGSSession = pUserSession && pUserSession->GetSteamIDGS().IsValid() ? GGCEcon()->FindGSSession( pUserSession->GetSteamIDGS() ) : NULL; + + // Prepare the audit record + CSchItemAudit schItemAudit; + schItemAudit.m_ulItemID = ulItemID; + schItemAudit.m_RTime32Stamp = CRTime::RTime32TimeCur(); + schItemAudit.m_eAction = eAction; + schItemAudit.m_unOwnerID = unOwnerID; + schItemAudit.m_unData = unData; + schItemAudit.m_unServerIP = pGSSession ? pGSSession->GetAddr() : 0; + schItemAudit.m_usServerPort = pGSSession ? pGSSession->GetPort() : 0; + + return sqlAccess->BYieldingInsertRecord( &schItemAudit ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItem::CAuditEntry::BAddAuditEntryToTransaction( CSQLAccess& sqlAccess, const CEconItem *pItem ) const +{ + return BYieldingAddAuditRecordImpl( &sqlAccess, pItem->GetID(), pItem->GetAccountID(), m_eAction, m_unData ); +} + +// -------------------------------------------------------------------------- +// Purpose: Adds an audit record to the DB for an item +// -------------------------------------------------------------------------- +void YieldingAddAuditRecord( GCSDK::CSQLAccess *sqlAccess, CEconItem *pItem, uint32 unOwnerID, EItemAction eAction, uint32 unData ) +{ + YieldingAddAuditRecord( sqlAccess, pItem->GetItemID(), unOwnerID, eAction, unData ); +} + +// -------------------------------------------------------------------------- +// Purpose: Adds an audit record to the DB for an item +// -------------------------------------------------------------------------- +void YieldingAddAuditRecord( GCSDK::CSQLAccess *sqlAccess, uint64 ulItemID, uint32 unOwnerID, EItemAction eAction, uint32 unData ) +{ + Verify( BYieldingAddAuditRecordImpl( sqlAccess, ulItemID, unOwnerID, eAction, unData ) ); +} + + +// -------------------------------------------------------------------------- +// Purpose: Adds an item to the DB +// -------------------------------------------------------------------------- +bool YieldingAddItemToDatabase( CEconItem *pItem, const CSteamID & steamID, EItemAction eAction, uint32 unData ) +{ + CSQLAccess sqlAccess; + sqlAccess.BBeginTransaction( "YieldingAddItemToDatabase" ); + + pItem->SetAccountID( steamID.GetAccountID() ); + if( !pItem->BYieldingAddInsertToTransaction( sqlAccess ) ) + { + sqlAccess.RollbackTransaction(); + return false; + } + + YieldingAddAuditRecord( &sqlAccess, pItem, steamID.GetAccountID(), eAction, unData ); + + // commit the item and audit record + if( !sqlAccess.BCommitTransaction() ) + { + EmitError( SPEW_GC, "Failed to add item for %s\n", steamID.Render() ); + return false; + } + + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +static CEconItem *FindEquippedItemForClassAndSlot( CEconSharedObjectCache *pSOCache, equipped_class_t unClass, equipped_slot_t unSlot ) +{ + VPROF_BUDGET( "FindEquippedItemForClassAndSlot()", VPROF_BUDGETGROUP_STEAM ); + Assert( pSOCache ); + + CGCSharedObjectTypeCache *pItemTypeCache = pSOCache->FindTypeCache( k_EEconTypeItem ); + + const uint32 unCount = pItemTypeCache ? pItemTypeCache->GetCount() : 0; + for ( uint32 i = 0; i < unCount; i++ ) + { + CEconItem *pItem = static_cast<CEconItem *>( pItemTypeCache->GetObject( i ) ); + Assert( pItem ); + + for ( int j = 0; j < pItem->GetEquippedInstanceCount(); j++ ) + { + const CEconItem::EquippedInstance_t& equipInst = pItem->GetEquippedInstance( j ); + + if ( equipInst.m_unEquippedClass == unClass && equipInst.m_unEquippedSlot == unSlot ) + return pItem; + } + } + + return NULL; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +/*static*/ void CEconItemEquipInstanceHelpers::AssignItemToSlot( CEconSharedObjectCache *pSOCache, CEconItem *pItem, equipped_class_t unClass, equipped_slot_t unSlot, CEconUserSession *pOptionalSession /* = NULL */ ) +{ + VPROF_BUDGET( "CEconEquipInstance::AssignItemToSlot()", VPROF_BUDGETGROUP_STEAM ); + + Assert( pSOCache ); + Assert( GetItemSchema()->IsValidClass( unClass ) ); + Assert( GetItemSchema()->IsValidItemSlot( unSlot, unClass ) ); + + CEconItem *pPreviousEquippedItem = FindEquippedItemForClassAndSlot( pSOCache, unClass, unSlot ); + + // skip out on expensive work if we're going to equip the item that's already in that slot + if ( pItem == pPreviousEquippedItem ) + return; + + // do we have an item to unequip first + if ( pPreviousEquippedItem ) + { + pPreviousEquippedItem->UnequipFromClass( unClass ); + pSOCache->DirtyObjectField( pPreviousEquippedItem, CSOEconItem::kEquippedStateFieldNumber ); + GGCBase()->AddCacheToWritebackQueue( pSOCache ); + + const bool bPreviousItemIsNowEquipped = pPreviousEquippedItem->IsEquipped(); + + if ( pOptionalSession && !bPreviousItemIsNowEquipped ) + { + pOptionalSession->OnEquippedStateChange( pPreviousEquippedItem, false ); + } + } + + // are we equipping a new item to this slot, or were we only responsible for unequipping + // the existing item? + if ( pItem ) + { + // notify the session, in case it wants to do anything; the default implementation + // will update the equipped state on the item but other types may have additional + // behavior + const bool bItemWasEquipped = pItem->IsEquipped(); + + pItem->Equip( unClass, unSlot ); + pSOCache->DirtyObjectField( pItem, CSOEconItem::kEquippedStateFieldNumber ); + GGCBase()->AddCacheToWritebackQueue( pSOCache ); + + const bool bItemIsEquipped = pItem->IsEquipped(); + Assert( bItemIsEquipped ); + + // did our equipped state change? this can happen if we equip ourself for the + // first time for this account right now. Keep in mind that this can be either + // a "is newly-equipped" or a "was equipped but is now not" state transition. + if ( pOptionalSession && (bItemWasEquipped != bItemIsEquipped) ) + { + pOptionalSession->OnEquippedStateChange( pItem, bItemIsEquipped ); + } + } +} + +#define ITEM_ACTION( x ) { x, #x } + +ENUMSTRINGS_START( EItemAction ) + ITEM_ACTION( k_EItemActionGSCreate ), + ITEM_ACTION( k_EItemActionUnpurchase ), + ITEM_ACTION( k_EItemActionDelete ), + ITEM_ACTION( k_EItemActionAwardAchievement ), + ITEM_ACTION( k_EItemActionBanned ), + ITEM_ACTION( k_EItemActionQuantityChanged ), + ITEM_ACTION( k_EItemActionRestored ), + ITEM_ACTION( k_EItemActionAwardTime ), + ITEM_ACTION( k_EItemActionManualCreate ), + ITEM_ACTION( k_EItemActionDrop ), + ITEM_ACTION( k_EItemActionPickUp ), + ITEM_ACTION( k_EItemActionCraftDestroy ), + ITEM_ACTION( k_EItemActionCraftCreate ), + ITEM_ACTION( k_EItemActionLimitExceeded ), + ITEM_ACTION( k_EItemActionPurchase ), + ITEM_ACTION( k_EItemActionNameChanged_Add ), + ITEM_ACTION( k_EItemActionUnlockCrate_Add ), + ITEM_ACTION( k_EItemActionPaintItem_Add ), + ITEM_ACTION( k_EItemActionAutoGrantItem ), + ITEM_ACTION( k_EItemActionCrossGameAchievement ), + ITEM_ACTION( k_EItemActionAddItemToSocket_Add ), + ITEM_ACTION( k_EItemActionAddSocketToItem_Add ), + ITEM_ACTION( k_EItemActionRemoveSocketItem_Add ), + ITEM_ACTION( k_EItemActionCustomizeItemTexture_Add ), + ITEM_ACTION( k_EItemActionItemTraded_Add ), + ITEM_ACTION( k_EItemActionUseItem ), + ITEM_ACTION( k_EItemActionAwardGift_Receiver ), + ITEM_ACTION( k_EItemActionNameChanged_Remove ), + ITEM_ACTION( k_EItemActionUnlockCrate_Remove ), + ITEM_ACTION( k_EItemActionPaintItem_Remove ), + ITEM_ACTION( k_EItemActionAddItemToSocket_Remove ), + ITEM_ACTION( k_EItemActionAddSocketToItem_Remove ), + ITEM_ACTION( k_EItemActionRemoveSocketItem_Remove ), + ITEM_ACTION( k_EItemActionCustomizeItemTexture_Remove ), + ITEM_ACTION( k_EItemActionItemTraded_Remove ), + ITEM_ACTION( k_EItemActionUnpackItemBundle ), + ITEM_ACTION( k_EItemActionCreateItemFromBundle ), + ITEM_ACTION( k_EItemActionAwardStorePromotionItem ), + ITEM_ACTION( k_EItemActionConvertItem ), + ITEM_ACTION( k_EItemActionEarnedItem ), + ITEM_ACTION( k_EItemActionAwardGift_Giver ), + ITEM_ACTION( k_EItemActionRefundedItem ), + ITEM_ACTION( k_EItemActionAwardThirdPartyPromo ), + ITEM_ACTION( k_EItemActionRemoveItemName_Remove ), + ITEM_ACTION( k_EItemActionRemoveItemName_Add ), + ITEM_ACTION( k_EItemActionRemoveItemPaint_Remove ), + ITEM_ACTION( k_EItemActionRemoveItemPaint_Add ), + ITEM_ACTION( k_EItemActionHalloweenDrop ), + ITEM_ACTION( k_EItemActionSteamWorkshopContributor ), + ITEM_ACTION( k_EItemActionManualOwnershipChange ), + ITEM_ACTION( k_EItemActionSupportDelete ), + ITEM_ACTION( k_EItemActionSupportCreatedByUndo ), + ITEM_ACTION( k_EItemActionSupportDeletedByUndo ), + ITEM_ACTION( k_EItemActionSupportQuantityChangedByUndo ), + ITEM_ACTION( k_EItemActionSupportRename_Add ), + ITEM_ACTION( k_EItemActionSupportRename_Remove ), + ITEM_ACTION( k_EItemActionSupportDescribe_Add ), + ITEM_ACTION( k_EItemActionSupportDescribe_Remove ), + + ITEM_ACTION( k_EItemActionStrangePartApply_Add ), + ITEM_ACTION( k_EItemActionStrangePartApply_Remove ), + ITEM_ACTION( k_EItemActionStrangeScoreReset_Add ), + ITEM_ACTION( k_EItemActionStrangeScoreReset_Remove ), + ITEM_ACTION( k_EItemActionStrangePartRemove_Add ), + ITEM_ACTION( k_EItemActionStrangePartRemove_Remove ), + + ITEM_ACTION( k_EItemActionStrangeRestrictionApply_Add ), + ITEM_ACTION( k_EItemActionStrangeRestrictionApply_Remove ), + ITEM_ACTION( k_EItemActionTransmogrify_Add ), + ITEM_ACTION( k_EItemActionTransmogrify_Remove ), + ITEM_ACTION( k_EItemActionHalloweenSpellPageAdd_Add ), + ITEM_ACTION( k_EItemActionHalloweenSpellPageAdd_Remove ), + + ITEM_ACTION( k_EItemActionSupportStrangify_Add ), + ITEM_ACTION( k_EItemActionSupportStrangify_Remove ), + + ITEM_ACTION( k_EItemActionUpgradeCardApply_Add ), + ITEM_ACTION( k_EItemActionUpgradeCardApply_Remove ), + ITEM_ACTION( k_EItemActionUpgradeCardRemove_Add ), + ITEM_ACTION( k_EItemActionUpgradeCardRemove_Remove ), + +#ifdef STAGING_ONLY + ITEM_ACTION( k_EItemActionDev_ClientLootListRoll ), +#endif + + ITEM_ACTION( k_EItemActionPeriodicScoreReward_Add ), + ITEM_ACTION( k_EItemActionPeriodicScoreReward_Remove ), + + ITEM_ACTION( k_EItemActionGiftWrap_Add ), + ITEM_ACTION( k_EItemActionGiftWrap_Remove ), + ITEM_ACTION( k_EItemActionGiftDelivery_Add ), + ITEM_ACTION( k_EItemActionGiftDelivery_Remove ), + ITEM_ACTION( k_EItemActionGiftUnwrap_Add ), + ITEM_ACTION( k_EItemActionGiftUnwrap_Remove ), + ITEM_ACTION( k_EItemActionPackageItem ), + ITEM_ACTION( k_EItemActionPackageItem_Revoked ), + ITEM_ACTION( k_EItemActionHandleMapToken ), + ITEM_ACTION( k_EItemActionCafeOrSchoolItem_Remove ), + ITEM_ACTION( k_EItemActionVACBanned_Remove ), + ITEM_ACTION( k_EItemActionUpgradeThirdPartyPromo ), + ITEM_ACTION( k_EItemActionExpired ), + ITEM_ACTION( k_EItemActionTradeRollback_Add ), + ITEM_ACTION( k_EItemActionTradeRollback_Remove ), + ITEM_ACTION( k_EItemActionCDKeyGrant ), + ITEM_ACTION( k_EItemActionCDKeyRevoke ), + ITEM_ACTION( k_EItemActionWeddingRing_Add ), + ITEM_ACTION( k_EItemActionWeddingRing_Remove ), + ITEM_ACTION( k_EItemActionWeddingRing_AddPartner ), + + ITEM_ACTION( k_EItemActionEconSetUnowned ), + ITEM_ACTION( k_EItemActionEconSetOwned ), + ITEM_ACTION( k_EItemActionStrangifyItem_Add ), + ITEM_ACTION( k_EItemActionStrangifyItem_Remove ), + + ITEM_ACTION( k_EItemActionRemoveItemCraftIndex_Remove ), + ITEM_ACTION( k_EItemActionRemoveItemCraftIndex_Add ), + ITEM_ACTION( k_EItemActionRemoveItemMakersMark_Remove ), + ITEM_ACTION( k_EItemActionRemoveItemMakersMark_Add ), + ITEM_ACTION( k_EItemActionRemoveItemKillStreak_Remove ), + ITEM_ACTION( k_EItemActionRemoveItemKillStreak_Add ), + + ITEM_ACTION( k_EItemActionCollectItem_RemoveCollection ), + ITEM_ACTION( k_EItemActionCollectItem_UpdateCollection ), + ITEM_ACTION( k_EItemActionCollectItem_CollectedItem ), + ITEM_ACTION( k_EItemActionCollectItem_RedeemCollectionReward ), + + ITEM_ACTION( k_EItemActionPreviewItem_BeginPreviewPeriod ), + ITEM_ACTION( k_EItemActionPreviewItem_EndPreviewPeriodExpired ), + ITEM_ACTION( k_EItemActionPreviewItem_EndPreviewPeriodItemBought ), + + ITEM_ACTION( k_EItemActionMvM_ChallengeCompleted_RemoveTicket ), + ITEM_ACTION( k_EItemActionMvM_ChallengeCompleted_GrantBadge ), + ITEM_ACTION( k_EItemActionMvM_ChallengeCompleted_UpdateBadgeStamps_Remove ), + ITEM_ACTION( k_EItemActionMvM_ChallengeCompleted_UpdateBadgeStamps_Add ), + ITEM_ACTION( k_EItemActionMvM_ChallengeCompleted_GrantMissionCompletionLoot ), + ITEM_ACTION( k_EItemActionMvM_RemoveSquadSurplusVoucher ), + ITEM_ACTION( k_EItemActionMvM_AwardSquadSurplus_Receiver ), + ITEM_ACTION( k_EItemActionMvM_AwardSquadSurplus_Giver ), + ITEM_ACTION( k_EItemActionMvM_ChallengeCompleted_GrantTourCompletionLoot ), + ITEM_ACTION( k_EItemActionMvM_AwardHelpANoobBonus_Helper ), + + ITEM_ACTION( k_EItemActionHalloween_UpdateMerasmusLootLevel_Add ), + ITEM_ACTION( k_EItemActionHalloween_UpdateMerasmusLootLevel_Remove ), + + ITEM_ACTION( k_EItemActionConsumeItem_Consume_ToolRemove ), + ITEM_ACTION( k_EItemActionConsumeItem_Consume_ToolAdd ), + ITEM_ACTION( k_EItemActionConsumeItem_Consume_InputRemove ), + ITEM_ACTION( k_EItemActionConsumeItem_Complete_OutputAdd ), + ITEM_ACTION( k_EItemActionConsumeItem_Complete_ToolRemove ), + + ITEM_ACTION( k_EItemActionItemEaterRecharge_Add ), + ITEM_ACTION( k_EItemActionItemEaterRecharge_Remove ), + + ITEM_ACTION( k_EItemActionSupportAddOrModifyAttribute_Remove ), + ITEM_ACTION( k_EItemActionSupportAddOrModifyAttribute_Add ), + + ITEM_ACTION( k_EItemAction_UpdateDuckBadgeLevel_Add ), + ITEM_ACTION( k_EItemAction_UpdateDuckBadgeLevel_Remove ), + + ITEM_ACTION( k_EItemAction_OperationPass_Add ), + + ITEM_ACTION( k_EItemActionSpyVsEngyWar_JoinedWar ), + + ITEM_ACTION( k_EItemActionMarket_Add ), + ITEM_ACTION( k_EItemActionMarket_Remove ), + + ITEM_ACTION( k_EItemAction_QuestComplete_Reward ), + ITEM_ACTION( k_EItemAction_QuestComplete_Remove ), + + ITEM_ACTION( k_EItemActionStrangeCountTransfer_Add ), + ITEM_ACTION( k_EItemActionStrangeCountTransfer_Remove ), + + ITEM_ACTION( k_EItemActionCraftCollectionUpgrade_Add ), + ITEM_ACTION( k_EItemActionCraftCollectionUpgrade_Remove ), + + ITEM_ACTION( k_EItemActionCraftHalloweenOffering_Add ), + ITEM_ACTION( k_EItemActionCraftHalloweenOffering_Remove ), + + ITEM_ACTION( k_EItemActionRemoveItemGiftedBy_Remove ), + ITEM_ACTION( k_EItemActionRemoveItemGiftedBy_Add ), + + ITEM_ACTION( k_EItemActionAddParticleVerticalAttr_Remove ), + ITEM_ACTION( k_EItemActionAddParticleVerticalAttr_Add ), + + ITEM_ACTION( k_EItemActionAddParticleUseHeadOriginAttr_Remove ), + ITEM_ACTION( k_EItemActionAddParticleUseHeadOriginAttr_Add ), + + ITEM_ACTION( k_EItemActionRemoveItemDynamicAttr_Remove ), + ITEM_ACTION( k_EItemActionRemoveItemDynamicAttr_Add ), + + ITEM_ACTION( k_EItemActionCraftStatClockTradeUp_Remove ), + ITEM_ACTION( k_EItemActionCraftStatClockTradeUp_Add ), + + ITEM_ACTION( k_EItemActionViralCompetitiveBetaPass_Drop ), + + ITEM_ACTION( k_EItemActionSupportDeleteAttribute_Remove ), + ITEM_ACTION( k_EItemActionSupportDeleteAttribute_Add ), + +ENUMSTRINGS_END( EItemAction ) + +const char *PchFriendlyNameFromEItemAction( EItemAction eItemAction, EItemActionMissingBehavior eMissingBehavior ) +{ + Assert( eMissingBehavior == kEItemAction_FriendlyNameLookup_ReturnDummyStringIfMissing || eMissingBehavior == kEItemAction_FriendlyNameLookup_ReturnNULLIfMissing ); + + switch( eItemAction ) + { + + case k_EItemActionGSCreate: return "Created by Gameserver"; + case k_EItemActionUnpurchase: return "Unpurchase"; + case k_EItemActionDelete: return "Deleted by Owner"; + case k_EItemActionAwardAchievement: return "Achievement Award"; + case k_EItemActionBanned: return "Banned"; + case k_EItemActionQuantityChanged: return "Quantity Changed"; + case k_EItemActionRestored: return "Restored"; + case k_EItemActionAwardTime: return "Timed Drop"; + case k_EItemActionManualCreate: return "Created by Support"; + case k_EItemActionDrop: return "Dropped"; + case k_EItemActionPickUp: return "Picked up"; + case k_EItemActionCraftDestroy: return "Crafted"; + case k_EItemActionCraftCreate: return "Crafted"; + case k_EItemActionLimitExceeded: return "Destroyed by Backpack Limit"; + case k_EItemActionPurchase: return "Purchase"; + case k_EItemActionNameChanged_Add: return "Added by Name Change"; + case k_EItemActionUnlockCrate_Add: return "Added by Crate Unlock"; + case k_EItemActionPaintItem_Add: return "Added by Paint"; + case k_EItemActionAutoGrantItem: return "Autogrant"; + case k_EItemActionCrossGameAchievement: return "Cross-Game Achievement Award"; + case k_EItemActionAddItemToSocket_Add: return "Added to Socket"; + case k_EItemActionAddSocketToItem_Add: return "Added by Socket"; + case k_EItemActionRemoveSocketItem_Add: return "Removed by Socket"; + case k_EItemActionCustomizeItemTexture_Add: return "Added by Custom Texture"; + case k_EItemActionItemTraded_Add: return "Trade"; + case k_EItemActionUseItem: return "Used"; + case k_EItemActionAwardGift_Receiver: return "Gift Received"; + case k_EItemActionNameChanged_Remove: return "Removed by Name Change"; + case k_EItemActionUnlockCrate_Remove: return "Removed by Crate Unlock"; + case k_EItemActionPaintItem_Remove: return "Removed by Paint"; + case k_EItemActionAddItemToSocket_Remove: return "Removed from Socket"; + case k_EItemActionAddSocketToItem_Remove: return "Removed by Socket"; + case k_EItemActionRemoveSocketItem_Remove: return "Removed Socket Item"; + case k_EItemActionCustomizeItemTexture_Remove: return "Removed by Custom Texture"; + case k_EItemActionItemTraded_Remove: return "Trade"; + case k_EItemActionUnpackItemBundle: return "Unpacked Bundle"; + case k_EItemActionCreateItemFromBundle: return "Added from Bundle"; + case k_EItemActionAwardStorePromotionItem: return "Store Promo Item"; + case k_EItemActionConvertItem: return "Converted"; + case k_EItemActionEarnedItem: return "Earned"; + case k_EItemActionAwardGift_Giver: return "Gift Sent"; + case k_EItemActionRefundedItem: return "Item Refunded"; + case k_EItemActionAwardThirdPartyPromo: return "Third-Party Promo"; + case k_EItemActionRemoveItemName_Remove: return "Removed by Name Removal"; + case k_EItemActionRemoveItemName_Add: return "Added by Name Removal"; + case k_EItemActionRemoveItemPaint_Remove: return "Removed by Paint Removal"; + case k_EItemActionRemoveItemPaint_Add: return "Added by Paint Removal"; + case k_EItemActionHalloweenDrop: return "Halloween Drop"; + case k_EItemActionSteamWorkshopContributor: return "Steam Workshop Contributor"; + case k_EItemActionManualOwnershipChange: return "Support Manual Ownership Change"; + case k_EItemActionSupportDelete: return "Deleted by Support"; + case k_EItemActionSupportCreatedByUndo: return "Created by Undo"; + case k_EItemActionSupportDeletedByUndo: return "Deleted by Undo"; + case k_EItemActionSupportQuantityChangedByUndo: return "Quantity Changed by Undo"; + case k_EItemActionSupportRename_Add: return "Added by Support Rename"; + case k_EItemActionSupportRename_Remove: return "Removed by Support Rename"; + case k_EItemActionSupportDescribe_Add: return "Added by Support Describe"; + case k_EItemActionSupportDescribe_Remove: return "Removed by Support Describe"; + + case k_EItemActionStrangePartApply_Add: return "Added by Strange Part Apply"; + case k_EItemActionStrangePartApply_Remove: return "Removed by Strange Part Apply"; + case k_EItemActionStrangeScoreReset_Add: return "Added by Strange Score Reset"; + case k_EItemActionStrangeScoreReset_Remove: return "Removed by Strange Score Reset"; + case k_EItemActionStrangePartRemove_Add: return "Added by Strange Part Remove"; + case k_EItemActionStrangePartRemove_Remove: return "Removed by Strange Part Remove"; + + case k_EItemActionStrangeRestrictionApply_Add: return "Added by Strange Restriction Apply"; + case k_EItemActionStrangeRestrictionApply_Remove: return "Removed by Strange Restriction Apply"; + case k_EItemActionTransmogrify_Add: return "Added by Transmogrify Apply"; + case k_EItemActionTransmogrify_Remove: return "Removed by Transmogrify Apply"; + case k_EItemActionHalloweenSpellPageAdd_Add: return "Added by Halloween Spell Page Apply"; + case k_EItemActionHalloweenSpellPageAdd_Remove: return "Removed by Halloween Spell Page Apply"; + + case k_EItemActionSupportStrangify_Add: return "Added by Support Strangify"; + case k_EItemActionSupportStrangify_Remove: return "Removed by Support Strangify"; + + case k_EItemActionUpgradeCardApply_Add: return "Added by Upgrade Card Apply"; + case k_EItemActionUpgradeCardApply_Remove: return "Removed by Upgrade Card Apply"; + case k_EItemActionUpgradeCardRemove_Add: return "Added by Upgrade Card Remove"; + case k_EItemActionUpgradeCardRemove_Remove: return "Removed by Upgrade Card Remove"; + +#ifdef STAGING_ONLY + case k_EItemActionDev_ClientLootListRoll: return "Dev: Client Loot List Roll"; +#endif + + case k_EItemActionPeriodicScoreReward_Add: return "Periodic Score System Reward Grant Add"; + case k_EItemActionPeriodicScoreReward_Remove: return "Periodic Score System Reward Remove"; + + case k_EItemActionGiftWrap_Add: return "Wrapped Gift - Added"; + case k_EItemActionGiftWrap_Remove: return "Wrapped Gift - Remove"; + case k_EItemActionGiftDelivery_Add: return "Gift Delivered - Add"; + case k_EItemActionGiftDelivery_Remove: return "Gift Delivered - Remove"; + case k_EItemActionGiftUnwrap_Add: return "Unwrapped Gift - Added"; + case k_EItemActionGiftUnwrap_Remove: return "Unwrapped Gift - Remove"; + case k_EItemActionPackageItem: return "Added by Steam Purchase"; + case k_EItemActionPackageItem_Revoked: return "Revoked by Steam Purchase"; + case k_EItemActionHandleMapToken: return "Map Token Applied"; + case k_EItemActionCafeOrSchoolItem_Remove: return "Cafe or School Removal"; + case k_EItemActionVACBanned_Remove: return "VAC Ban removal"; + case k_EItemActionUpgradeThirdPartyPromo: return "Third-Party Promo Upgrade"; + case k_EItemActionExpired: return "Expired"; + case k_EItemActionTradeRollback_Add: return "Trade Rollback"; + case k_EItemActionTradeRollback_Remove: return "Trade Rollback"; + case k_EItemActionCDKeyGrant: return "CD Key Grant"; + case k_EItemActionCDKeyRevoke: return "CD Key Rollback"; + case k_EItemActionWeddingRing_Add: return "Wedding Ring Add"; + case k_EItemActionWeddingRing_Remove: return "Wedding Ring Remove"; + case k_EItemActionWeddingRing_AddPartner: return "Wedding Ring Add (Partner)"; + + case k_EItemActionEconSetUnowned: return "Econ SetUnowned"; + case k_EItemActionEconSetOwned: return "Econ SetOwned"; + + case k_EItemActionStrangifyItem_Add: return "Added by Strangify Apply"; + case k_EItemActionStrangifyItem_Remove: return "Removed by Strangify Apply"; + + case k_EItemActionRemoveItemCraftIndex_Remove: return "Removed by Craft Index Removal"; + case k_EItemActionRemoveItemCraftIndex_Add: return "Added by Craft Index Removal"; + case k_EItemActionRemoveItemMakersMark_Remove: return "Removed by Maker's Mark Removal"; + case k_EItemActionRemoveItemMakersMark_Add: return "Added by Maker's Mark Removal"; + case k_EItemActionRemoveItemKillStreak_Remove: return "Removed by KillStreak Removal"; + case k_EItemActionRemoveItemKillStreak_Add: return "Added by KillStreak Removal"; + + case k_EItemActionCollectItem_CollectedItem: return "Item Collection: Added to a Collection"; + case k_EItemActionCollectItem_RemoveCollection: return "Item Collection: Removed Old Collection Item"; + case k_EItemActionCollectItem_UpdateCollection: return "Item Collection: Created New Collection Item"; + case k_EItemActionCollectItem_RedeemCollectionReward: return "Item Collection: Redeemed Reward"; + + case k_EItemActionPreviewItem_BeginPreviewPeriod: return "Item Preview: Begin Preview Period"; + case k_EItemActionPreviewItem_EndPreviewPeriodExpired: return "Item Preview: End Preview Period by Expiration"; + case k_EItemActionPreviewItem_EndPreviewPeriodItemBought: return "Item Preview: End Preview Period by Item Purchase"; + + case k_EItemActionMvM_ChallengeCompleted_RemoveTicket: return "Removed MvM Ticket"; + case k_EItemActionMvM_ChallengeCompleted_GrantBadge: return "Granted MvM Badge"; + case k_EItemActionMvM_ChallengeCompleted_UpdateBadgeStamps_Add: return "Added by MvM Stamp Update"; + case k_EItemActionMvM_ChallengeCompleted_UpdateBadgeStamps_Remove: return "Removed by MvM Stamp Update"; + case k_EItemActionMvM_ChallengeCompleted_GrantMissionCompletionLoot: return "Added by MvM Mission Completion"; + case k_EItemActionMvM_RemoveSquadSurplusVoucher: return "Removed MvM squad surplus voucher"; + case k_EItemActionMvM_AwardSquadSurplus_Receiver: return "Received MvM squad surplus"; + case k_EItemActionMvM_AwardSquadSurplus_Giver: return "Generated MvM squad surplus"; + case k_EItemActionMvM_ChallengeCompleted_GrantTourCompletionLoot: return "Added by MvM Tour Completion"; + case k_EItemActionMvM_AwardHelpANoobBonus_Helper: return "Added by MvM Help-A-Noob"; + + case k_EItemActionHalloween_UpdateMerasmusLootLevel_Add: return "Updating Halloween Merasmus Loot - Adding New item"; + case k_EItemActionHalloween_UpdateMerasmusLootLevel_Remove: return "Updating Halloween Merasmus Loot Level - Removing Old Item"; + + case k_EItemActionConsumeItem_Consume_ToolRemove: return "Dynamic Recipe: Consuming Input Item - Removing Old Recipe-Item"; + case k_EItemActionConsumeItem_Consume_ToolAdd: return "Dynamic Recipe: Consuming Input Item - Adding New Recipe-Item"; + case k_EItemActionConsumeItem_Consume_InputRemove: return "Dynamic Recipe: Consuming Input Item - Removing Input item"; + case k_EItemActionConsumeItem_Complete_OutputAdd: return "Dynamic Recipe: Recipe Complete - Adding Output Item"; + case k_EItemActionConsumeItem_Complete_ToolRemove: return "Dynamic Recipe: Recipe Complete - Removing Recipe-Item"; + + case k_EItemActionItemEaterRecharge_Add: return "Added by Item Eater Recharger"; + case k_EItemActionItemEaterRecharge_Remove: return "Removed by Item Eater Recharger"; + + case k_EItemActionSupportAddOrModifyAttribute_Remove: return "Removed by Support Add/Modify Attribute"; + case k_EItemActionSupportAddOrModifyAttribute_Add: return "Added by Support Add/Modify Attribute"; + + case k_EItemAction_UpdateDuckBadgeLevel_Add: return "Updating Duck Badge Level - Adding New item"; + case k_EItemAction_UpdateDuckBadgeLevel_Remove: return "Updating Duck Badge Level - Removing Old Item"; + + case k_EItemActionSpyVsEngyWar_JoinedWar: return "Added by joining the Spy vs. Engy War"; + + case k_EItemAction_OperationPass_Add: return "Adding Operation Pass"; + + case k_EItemAction_QuestDrop: return "Quest Drop"; + + case k_EItemActionMarket_Add: return "Market - Add"; + case k_EItemActionMarket_Remove: return "Market - Remove"; + + case k_EItemAction_QuestComplete_Reward: return "Added from completing a quest"; + case k_EItemAction_QuestComplete_Remove: return "Removed from completing a quest"; + + case k_EItemActionStrangeCountTransfer_Add: return "Added by using a Strange Count Transfer tool"; + case k_EItemActionStrangeCountTransfer_Remove: return "Removed by using a Strange Count Transfer tool"; + + case k_EItemActionCraftCollectionUpgrade_Add: return "Added by using Craft Collection Upgrade"; + case k_EItemActionCraftCollectionUpgrade_Remove: return "Removed by using Craft Collection Upgrade"; + + case k_EItemActionCraftHalloweenOffering_Add: return "Added by using Craft Halloween Offering"; + case k_EItemActionCraftHalloweenOffering_Remove: return "Removed by using Craft Halloween Offering"; + + case k_EItemActionRemoveItemGiftedBy_Remove: return "Removed by Gifted By Removal"; + case k_EItemActionRemoveItemGiftedBy_Add: return "Added by Gifted By Removal"; + + case k_EItemActionAddParticleVerticalAttr_Remove: return "Removed by Particle Vertical Attr Add"; + case k_EItemActionAddParticleVerticalAttr_Add: return "Added by Particle Vertical Attr Add"; + + case k_EItemActionAddParticleUseHeadOriginAttr_Remove: return "Removed by Particle Use Head Origin Attr Add"; + case k_EItemActionAddParticleUseHeadOriginAttr_Add: return "Added by Particle Use Head Origin Attr Add"; + + case k_EItemActionRemoveItemDynamicAttr_Remove: return "Removed by Dynamic Attribute Removal"; + case k_EItemActionRemoveItemDynamicAttr_Add: return "Added by Dynamic Attribute Removal"; + + case k_EItemActionCraftStatClockTradeUp_Add: return "Added through Stat Clock Trade-Up"; + case k_EItemActionCraftStatClockTradeUp_Remove: return "Consumed through Stat Clock Trade-Up"; + case k_EItemActionViralCompetitiveBetaPass_Drop: return "Added via Competitive Beta Invite Drop"; + + case k_EItemActionSupportDeleteAttribute_Remove: return "Removed by Support Delete Attribute"; + case k_EItemActionSupportDeleteAttribute_Add: return "Added by Support Delete Attribute"; + + default: + return eMissingBehavior == kEItemAction_FriendlyNameLookup_ReturnDummyStringIfMissing + ? PchNameFromEItemAction( eItemAction ) + : PchNameFromEItemActionUnsafe( eItemAction ); + } + +} + +const char *PchLocalizedNameFromEItemAction( EItemAction eAction, CLocalizationProvider &localizationProvider ) +{ + const char *pchAction = PchNameFromEItemAction( eAction ); + if ( !V_strncmp( pchAction, "k_EItemAction", 13 ) ) + { + CFmtStr fmtLocKey( "ItemHistory_Action_%s", &pchAction[13] ); + locchar_t *pchLocalizedAction = localizationProvider.Find( fmtLocKey.String() ); + if ( pchLocalizedAction != NULL ) + { + return pchLocalizedAction; + } + } + + if ( BIsActionDestructive( eAction ) ) + { + return localizationProvider.Find( "ItemHistory_Action_GenericRemove" ); + } + else if ( BIsActionCreative( eAction ) ) + { + return localizationProvider.Find( "ItemHistory_Action_GenericAdd" ); + } + else + { + return "Unknown"; + } +} + +ENUMSTRINGS_START( eEconItemOrigin ) +{ kEconItemOrigin_Drop, "Timed Drop" }, +{ kEconItemOrigin_Achievement, "Achievement" }, +{ kEconItemOrigin_Purchased, "Purchased" }, +{ kEconItemOrigin_Traded, "Traded" }, +{ kEconItemOrigin_Crafted, "Crafted" }, +{ kEconItemOrigin_StorePromotion, "Store Promotion" }, +{ kEconItemOrigin_Gifted, "Gifted" }, +{ kEconItemOrigin_SupportGranted, "Support Granted" }, +{ kEconItemOrigin_FoundInCrate, "Found in Crate" }, +{ kEconItemOrigin_Earned, "Earned" }, +{ kEconItemOrigin_ThirdPartyPromotion, "Third-Party Promotion" }, +{ kEconItemOrigin_GiftWrapped, "Wrapped Gift" }, +{ kEconItemOrigin_HalloweenDrop, "Halloween Drop" }, +{ kEconItemOrigin_PackageItem, "Steam Purchase" }, +{ kEconItemOrigin_Foreign, "Foreign Item" }, +{ kEconItemOrigin_CDKey, "CD Key" }, +{ kEconItemOrigin_CollectionReward, "Collection Reward" }, +{ kEconItemOrigin_PreviewItem, "Preview Item" }, +{ kEconItemOrigin_SteamWorkshopContribution, "Steam Workshop Contribution" }, +{ kEconItemOrigin_PeriodicScoreReward, "Periodic score reward" }, +{ kEconItemOrigin_MvMMissionCompletionReward, "MvM Badge completion reward" }, +{ kEconItemOrigin_MvMSquadSurplusReward, "MvM Squad surplus reward" }, +{ kEconItemOrigin_RecipeOutput, "Recipe output" }, +{ kEconItemOrigin_QuestDrop, "Quest Drop" }, +{ kEconItemOrigin_QuestLoanerItem, "Quest Loaner Item" }, +{ kEconItemOrigin_TradeUp, "Trade-Up" }, +{ kEconItemOrigin_ViralCompetitiveBetaPassSpread, "Viral Competitive Beta Pass Spread" }, +ENUMSTRINGS_END( eEconItemOrigin ) + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCrateLootListWrapper::BAttemptCrateSeriesInitialization( const IEconItemInterface *pEconItem ) +{ + Assert( m_pLootList == NULL ); + + // Find out what series this crate belongs to. + static CSchemaAttributeDefHandle pAttr_CrateSeries( "set supply crate series" ); + if ( !pAttr_CrateSeries ) + return false; + + int iCrateSeries; + { + float fCrateSeries; // crate series ID is stored as a float internally because we hate ourselves + if ( !FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttr_CrateSeries, &fCrateSeries ) || fCrateSeries == 0.0f ) + return false; + + iCrateSeries = fCrateSeries; + } + + // Our index is an index into the revolving-loot-lists list. From that list we'll be able to + // get a loot list name, which we'll use to look up the actual contents. + const CEconItemSchema::RevolvingLootListDefinitionMap_t& mapRevolvingLootLists = GetItemSchema()->GetRevolvingLootLists(); + int idx = mapRevolvingLootLists.Find( iCrateSeries ); + if ( !mapRevolvingLootLists.IsValidIndex( idx ) ) + return false; + + const char *pszLootList = mapRevolvingLootLists.Element( idx ); + + // Get the loot list. + m_pLootList = GetItemSchema()->GetLootListByName( pszLootList ); + m_unAuditDetailData = iCrateSeries; + + return m_pLootList != NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCrateLootListWrapper::BAttemptLootListStringInitialization( const IEconItemInterface *pEconItem ) +{ + Assert( m_pLootList == NULL ); + + // Find out what series this crate belongs to. + static CSchemaAttributeDefHandle pAttr_LootListName( "loot list name" ); + if ( !pAttr_LootListName ) + return false; + + CAttribute_String str; + if ( !pEconItem->FindAttribute( pAttr_LootListName, &str ) ) + return false; + + m_pLootList = GetItemSchema()->GetLootListByName( str.value().c_str() ); + + return m_pLootList != NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCrateLootListWrapper::BAttemptLineItemInitialization( const IEconItemInterface *pEconItem ) +{ + Assert( m_pLootList == NULL ); + + // Do we have at least one line item specified? + if ( !pEconItem->FindAttribute( CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItems[0] ) ) + return false; + + m_pLootList = new CAttributeLineItemLootList( pEconItem ); + m_bIsDynamicallyAllocatedLootList = true; + + return true; +} diff --git a/game/shared/econ/econ_item.h b/game/shared/econ/econ_item.h new file mode 100644 index 0000000..ae3270e --- /dev/null +++ b/game/shared/econ/econ_item.h @@ -0,0 +1,864 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: CEconItem, a shared object for econ items +// +//============================================================================= + +#ifndef ECONITEM_H +#define ECONITEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gcsdk/gcclientsdk.h" +#include "base_gcmessages.pb.h" + +#include "econ_item_constants.h" +#include "econ_item_interface.h" +#include "econ_item_schema.h" + +#include <typeinfo> // needed for typeid() + +#define ENABLE_TYPED_ATTRIBUTE_PARANOIA 1 + +#ifdef GC_DLL +class CSchItem; +class CEconSharedObjectCache; +#endif + +namespace GCSDK +{ + class CColumnSet; +#ifdef GC_DLL + class CWebAPIValues; +#endif +}; + +class CEconItem; +class CSOEconItem; +class CEconItemCustomData; +class CEconSessionItemAudit; + +//----------------------------------------------------------------------------- +// Stats tracking for the attributes attached to CEconItem instances. +//----------------------------------------------------------------------------- +struct schema_attribute_stat_bucket_t +{ + const schema_attribute_stat_bucket_t *m_pNext; + + const char *m_pszDesc; + uint64 m_unLiveInlineCount; + uint64 m_unLifetimeInlineCount; + uint64 m_unLiveHeapCount; + uint64 m_unLifetimeHeapCount; + + void OnAllocateInlineInstance() { m_unLiveInlineCount++; m_unLifetimeInlineCount++; } + void OnFreeInlineInstance() { Assert( m_unLiveInlineCount > 0 ); m_unLiveInlineCount--; } + void OnAllocateHeapInstance() { m_unLiveHeapCount++; m_unLifetimeHeapCount++; } + void OnFreeHeapInstance() { Assert( m_unLiveHeapCount ); m_unLiveHeapCount--; } +}; + +class CSchemaAttributeStats +{ +public: + template < typename TAttribStatsStorageClass, typename TAttribInMemoryType > + static void RegisterAttributeType() + { + TAttribStatsStorageClass::s_InstanceStats.m_pszDesc = typeid( TAttribInMemoryType ).name(); + TAttribStatsStorageClass::s_InstanceStats.m_pNext = m_pHead; + + m_pHead = &TAttribStatsStorageClass::s_InstanceStats; + } + + static const schema_attribute_stat_bucket_t *GetFirstStatBucket() + { + return m_pHead; + } + +private: + static const schema_attribute_stat_bucket_t *m_pHead; +}; + +//----------------------------------------------------------------------------- +// Base class interface for attributes of a certain in-memory type. +//----------------------------------------------------------------------------- +unsigned int Internal_GetAttributeTypeUniqueIdentifierNextValue(); + +template < typename T > +unsigned int GetAttributeTypeUniqueIdentifier() +{ + static unsigned int s_unUniqueCounter = Internal_GetAttributeTypeUniqueIdentifierNextValue(); + return s_unUniqueCounter; +} + +//----------------------------------------------------------------------------- +// Base class interface for attributes of a certain in-memory type. +//----------------------------------------------------------------------------- +template < typename TAttribInMemoryType > +class ISchemaAttributeTypeBase : public ISchemaAttributeType +{ + friend class CSchemaAttributeStats; + +public: + ISchemaAttributeTypeBase() + { + CSchemaAttributeStats::RegisterAttributeType< ISchemaAttributeTypeBase<TAttribInMemoryType>, TAttribInMemoryType >(); + + // The implementation of the attributes-in-memory system is such that it may or may not behave according to + // expectations. Rather than have to stare at all the details to answer questions about where memory is allocated + // or managed, or when it will be freed, for all our current use cases it makes more sense to just disable raw + // pointer types from being an attribute-in-memory type and instead steer people towards this message explaining + // why. + COMPILE_TIME_ASSERT( !IsPointerType<TAttribInMemoryType>::kValue ); + } + +#ifdef GC_DLL + // By default, without a specific type we don't support any sort of custom value generation, so all we can do + // to load an attribute is to copy the value out from the generic format (union) and turn it into whatever our + // type is, and then add that type to the item as an attribute. + // + // Unlike most of the functions in this class, this is not meant to be a catch-all default implementation but + // is instead a base implementation. Subclasses are intended to override to add or change functionality. + virtual void LoadOrGenerateEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + AssertMsg( !staticAttrib.m_pKVCustomData, "Default implementation of LoadOrGenerateEconAttributeValue() doesn't support custom value generation!" ); + AssertMsg( pGameAccount || !staticAttrib.m_pKVCustomData, "Cannot run custom logic with no game account object! Passing in NULL for pGameAccount is only supported when we know we won't be running custom value generation code!" ); + + LoadEconAttributeValue( pTargetItem, pAttrDef, staticAttrib.m_value ); + } + + // By default, we dont generate any custom value + virtual void GenerateEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount, attribute_data_union_t* out_pValue ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( pGameAccount ); + Assert( out_pValue ); + } +#endif // GC_DLL + + virtual void LoadEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value ) const OVERRIDE; + + // Returns a unique identifier per run based on the type of <TAttribInMemoryType>. + virtual unsigned int GetTypeUniqueIdentifier() const OVERRIDE + { + return GetAttributeTypeUniqueIdentifier<TAttribInMemoryType>(); + } + + // Takes the value specified in [typedValue] and stores it in the most appropriate way + // somewhere attached to [out_pValue]. This may hit the heap. The storage itself is + // intended to be opaque but can be reversed by calling GetTypedValueContentsFromEconAttributeValue(). + void ConvertTypedValueToEconAttributeValue( const TAttribInMemoryType& typedValue, attribute_data_union_t *out_pValue ) const + { + // If our type is smaller than an int, we don't know how to copy the memory into our flat structure. We could write + // this code but we have no use case for it now so this is set up to fail so if someone does come up with a use case + // they know where to fix. + COMPILE_TIME_ASSERT( sizeof( TAttribInMemoryType ) >= sizeof( uint32 ) ); + + // Do we fit in the bottom 32-bits? + if ( sizeof( TAttribInMemoryType ) <= sizeof( uint32 ) ) + { + *reinterpret_cast<TAttribInMemoryType *>( &out_pValue->asUint32 ) = typedValue; + } + // What about in the full 64-bits (if we're running a 64-bit build)? + else if ( sizeof( TAttribInMemoryType ) <= sizeof( void * ) ) + { + *reinterpret_cast<TAttribInMemoryType *>( &out_pValue->asBlobPointer ) = typedValue; + } + // We're too big for our flat structure. We need to allocate space somewhere outside our attribute instance and point + // to that. + else + { + Assert( out_pValue->asBlobPointer ); + *reinterpret_cast<TAttribInMemoryType *>( out_pValue->asBlobPointer ) = typedValue; + } + } + + // Guaranteed to return a valid reference (or assert/crash if calling code is behaving inappropriately and calling + // this before an attribute value is allocated/set). + const TAttribInMemoryType& GetTypedValueContentsFromEconAttributeValue( const attribute_data_union_t& value ) const + { + COMPILE_TIME_ASSERT( sizeof( TAttribInMemoryType ) >= sizeof( uint32 ) ); + + // Do we fit in the bottom 32-bits? + if ( sizeof( TAttribInMemoryType ) <= sizeof( uint32 ) ) + return *reinterpret_cast<const TAttribInMemoryType *>( &value.asUint32 ); + + // What about in the full 64-bits (if we're running a 64-bit build)? + if ( sizeof( TAttribInMemoryType ) <= sizeof( void * ) ) + return *reinterpret_cast<const TAttribInMemoryType *>( &value.asBlobPointer ); + + // We don't expect to get to a "read value" call without having written a value, which would + // have allocated this memory. + Assert( value.asBlobPointer ); + + return *reinterpret_cast<const TAttribInMemoryType *>( value.asBlobPointer ); + } + + void ConvertEconAttributeValueToTypedValue( const attribute_data_union_t& value, TAttribInMemoryType *out_pTypedValue ) const + { + Assert( out_pTypedValue ); + + *out_pTypedValue = GetTypedValueContentsFromEconAttributeValue( value ); + } + + void InitializeNewEconAttributeValue( attribute_data_union_t *out_pValue ) const OVERRIDE + { + if ( sizeof( TAttribInMemoryType ) <= sizeof( uint32 ) ) + { + new( &out_pValue->asUint32 ) TAttribInMemoryType; + s_InstanceStats.OnAllocateInlineInstance(); + } + else if ( sizeof( TAttribInMemoryType ) <= sizeof( void * ) ) + { + new( &out_pValue->asBlobPointer ) TAttribInMemoryType; + s_InstanceStats.OnAllocateInlineInstance(); + } + else + { + out_pValue->asBlobPointer = reinterpret_cast<byte *>( new TAttribInMemoryType ); + s_InstanceStats.OnAllocateHeapInstance(); + } + } + + virtual void UnloadEconAttributeValue( attribute_data_union_t *out_pValue ) const OVERRIDE + { + COMPILE_TIME_ASSERT( sizeof( TAttribInMemoryType ) >= sizeof( uint32 ) ); + + // For smaller types, anything that fits inside the bits of a void pointer, we store the contents + // inline and only have to worry about calling the correct destructor. We check against the small-/ + // size/medium-size values separately to not worry about which bits we're storing the uint32 in. + if ( sizeof( TAttribInMemoryType ) <= sizeof( uint32 ) ) + { + (reinterpret_cast<TAttribInMemoryType *>( &out_pValue->asUint32 ))->~TAttribInMemoryType(); + s_InstanceStats.OnFreeInlineInstance(); + } + else if ( sizeof( TAttribInMemoryType ) <= sizeof( void * ) ) + { + (reinterpret_cast<TAttribInMemoryType *>( &out_pValue->asBlobPointer ))->~TAttribInMemoryType(); + s_InstanceStats.OnFreeInlineInstance(); + } + // For larger types, we have the memory stored on the heap somewhere. We don't have to manually + // destruct, but we do have to manually free. + else + { + Assert( out_pValue->asBlobPointer ); + + delete reinterpret_cast<TAttribInMemoryType *>( out_pValue->asBlobPointer ); + s_InstanceStats.OnFreeHeapInstance(); + } + } + + virtual bool OnIterateAttributeValue( IEconItemAttributeIterator *pIterator, const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value ) const OVERRIDE + { + Assert( pIterator ); + Assert( pAttrDef ); + + // Call the appropriate virtual function on our iterator based on whatever type we represent. + return pIterator->OnIterateAttributeValue( pAttrDef, GetTypedValueContentsFromEconAttributeValue( value ) ); + } + + virtual void LoadByteStreamToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const std::string& sBytes ) const OVERRIDE; + virtual void ConvertEconAttributeValueToByteStream( const attribute_data_union_t& value, ::std::string *out_psBytes ) const; + + virtual void ConvertTypedValueToByteStream( const TAttribInMemoryType& typedValue, ::std::string *out_psBytes ) const = 0; + virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, TAttribInMemoryType *out_pTypedValue ) const = 0; + +private: + static schema_attribute_stat_bucket_t s_InstanceStats; +}; + +// This function exists only to back-convert code that relies on the old untyped +// attribute system, doing things like shoving floating-point bits into a uint32 +// value in the database. +// +// There is no reason to use this function moving forward! If you're writing new +// code and calling this function seems like the only way to get the effect you +// want, it probably just means that there is no attribute type for what you're +// trying to do yet. +template < typename T > uint32 WrapDeprecatedUntypedEconItemAttribute( T tValue ) { COMPILE_TIME_ASSERT( sizeof( T ) == sizeof( uint32 ) ); return *reinterpret_cast<uint32 *>( &tValue ); } + +template < typename TAttribInMemoryType > +schema_attribute_stat_bucket_t ISchemaAttributeTypeBase<TAttribInMemoryType>::s_InstanceStats; + +class CEconItem : public GCSDK::CSharedObject, public CMaterialOverrideContainer< IEconItemInterface > +{ +#ifdef GC_DLL + DECLARE_CLASS_MEMPOOL( CEconItem ); +#endif + +public: + typedef GCSDK::CSharedObject BaseClass; + + struct attribute_t + { + attrib_definition_index_t m_unDefinitionIndex; // stored as ints here for memory efficiency on the GC + attribute_data_union_t m_value; + + private: + void operator=( const attribute_t& rhs ); + }; + + struct EquippedInstance_t + { + EquippedInstance_t() : m_unEquippedClass( 0 ), m_unEquippedSlot( INVALID_EQUIPPED_SLOT ) {} + EquippedInstance_t( equipped_class_t unClass, equipped_slot_t unSlot ) : m_unEquippedClass( unClass ), m_unEquippedSlot( unSlot ) {} + equipped_class_t m_unEquippedClass; + equipped_slot_t m_unEquippedSlot; + }; + +#ifdef GC_DLL + class CAuditEntry + { + public: + CAuditEntry( EItemAction eAction, uint32 unData ) : m_eAction( eAction ), m_unData( unData ) { } + + bool BAddAuditEntryToTransaction( CSQLAccess& sqlAccess, const CEconItem *pItem ) const; + + private: + EItemAction m_eAction; + uint32 m_unData; + }; + + // Set only the top 16 bits for field ID types! These will be or'd into the index of + // the field itself and then pulled apart later. + enum + { + kUpdateFieldIDType_FieldID = 0x00000000, // this must stay as 0 for legacy code + kUpdateFieldIDType_AttributeID = 0x00010000, + }; +#endif // GC_DLL + + const static int k_nTypeID = k_EEconTypeItem; + virtual int GetTypeID() const { return k_nTypeID; } + + CEconItem(); + CEconItem( const CEconItem& rhs ); + virtual ~CEconItem(); + + CEconItem &operator=( const CEconItem& rhs ); + + //called to determine if this item is tradable or not. This will return the time after which it can be traded. If 0 it can be traded. This is + //needed since the base implementation of this is protected + RTime32 GetTradableAfterDateTime() const { return IEconItemInterface::GetTradableAfterDateTime(); } + + //called to set a tradable after date/time value onto this item (this avoids a lot of potential inefficiencies around this process) + void SetTradableAfterDateTime( RTime32 rtTime ); + + // IEconItemInterface interface. + const GameItemDefinition_t *GetItemDefinition() const; +public: + + virtual void IterateAttributes( class IEconItemAttributeIterator *pIterator ) const OVERRIDE; + virtual itemid_t GetID() const { return GetItemID(); } + + // Accessors/Settors + itemid_t GetItemID() const { return m_ulID; } + void SetItemID( uint64 ulID ); + + itemid_t GetOriginalID() const; + void SetOriginalID( uint64 ulOriginalID ); + + uint32 GetAccountID() const { return m_unAccountID; } + void SetAccountID( uint32 unAccountID ) { m_unAccountID = unAccountID; } + + uint32 GetDefinitionIndex() const { return m_unDefIndex; } + void SetDefinitionIndex( uint32 unDefinitionIndex ) { m_unDefIndex = unDefinitionIndex; } + + uint32 GetItemLevel() const { return m_unLevel; } + void SetItemLevel( uint32 unItemLevel ) { m_unLevel = unItemLevel; } + + int32 GetQuality() const { return m_nQuality; } + void SetQuality( int32 nQuality ) { m_nQuality = nQuality; } + + uint32 GetInventoryToken() const { return m_unInventory; } + void SetInventoryToken( uint32 unToken ) { m_unInventory = unToken; } + + int GetQuantity() const; + void SetQuantity( uint16 unQuantity ); + + uint8 GetFlags() const { return m_unFlags; } + void SetFlags( uint8 unFlags ) { m_unFlags = unFlags; } + + void SetFlag( uint8 unFlag ) { m_unFlags |= unFlag; } + void ClearFlag( uint8 unFlag ) { m_unFlags &= ~unFlag; } + bool CheckFlags( uint8 unFlags ) const { return ( m_unFlags & unFlags ) != 0; } + + eEconItemOrigin GetOrigin() const { return (eEconItemOrigin)m_unOrigin; } + void SetOrigin( eEconItemOrigin unOrigin ) { m_unOrigin = unOrigin; Assert( m_unOrigin == unOrigin ); } + bool IsForeign() const { return m_unOrigin == kEconItemOrigin_Foreign; } + + style_index_t GetStyle() const; + void SetStyle( uint8 unStyle ) { m_unStyle = unStyle; DirtyIconURL(); } + + const char *GetIconURLSmall() const; + const char *GetIconURLLarge() const; + + const char *GetCustomName() const; + void SetCustomName( const char *pName ); + + const char *GetCustomDesc() const; + void SetCustomDesc( const char *pDesc ); + + bool IsEquipped() const; + bool IsEquippedForClass( equipped_class_t unClass ) const; + equipped_slot_t GetEquippedPositionForClass( equipped_class_t unClass ) const; + + void Equip( equipped_class_t unClass, equipped_slot_t unSlot ); + void Unequip(); + void UnequipFromClass( equipped_class_t unClass ); + + // This should really only used for the WebAPIs, debugging, etc. Data manipulation during gameplay should use + // the above functions. + int GetEquippedInstanceCount() const; + const EquippedInstance_t &GetEquippedInstance( int iIdx ) const; + + virtual bool GetInUse() const; + void SetInUse( bool bInUse ); + + bool IsTradable() const; + bool IsMarketable() const; + bool IsCommodity() const; + + void AdoptMoreRestrictedTradabilityFromItem( const CEconItem *pOther, uint32 nTradabilityFlagsToAccept = 0xFFFFFFFF ); + void AdoptMoreRestrictedTradability( uint32 nTradabilityFlags, RTime32 nUntradableTime ); + bool IsUsableInCrafting() const; + +#ifdef GC_DLL + RTime32 GetAssetInfoExpirationCacheExpirationTime() const; +#endif // GC_DLL + + // -------------------------------------------------------------------------------------------- + // Typed attributes. These are methods for accessing and setting values of attributes with + // some semblance of type information and type safety. + // -------------------------------------------------------------------------------------------- + + // Assign the value of the attribute [pAttrDef] to [value]. Passing in a type for [value] that + // doesn't match the storage type specified by the attribute definition will fail asserts a bunch + // of asserts all the way down the stack and may or may not crash -- it would be nice to make this + // fail asserts at compile time. + // + // This function has undefined results (besides asserting) if called to add a dynamic version of + // an attrib that's already specified statically. + template < typename T > + void SetDynamicAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const T& value ) + { + Assert( pAttrDef ); + + const ISchemaAttributeTypeBase<T> *pAttrType = GetTypedAttributeType<T>( pAttrDef ); +#ifdef GC_DLL + // The GC is expected to always have internally-consistent information and so be able to access the + // type information of any attribute if we started up successfully. + Assert( pAttrType ); +#else + // Game clients and servers may be running code that doesn't have all of the types for the new attributes + // for a GC that just propped. Because we're not authoritative over items here, about the best we can do + // here is abort entirely. This means that the client may not display certain attributes at all, or even + // have them in the attribute list in memory, but we don't understand those attributes anyway. + if ( !pAttrType ) + return; +#endif + + // Fail right off the bat if we're trying to write a dynamic attribute value for an item that already + // has this as a static value. + AssertMsg4( !::FindAttribute( GetItemDefinition(), pAttrDef ), + "Item id %llu (%s) attempting to set dynamic attribute value for '%s' (%d) when static attribute exists!", + GetItemID(), GetItemDefinition()->GetDefinitionName(), pAttrDef->GetDefinitionName(), pAttrDef->GetDefinitionIndex() ); + + // Alright, we have a data type match so we can safely store data. Some types may need to initialize + // their data to a current state if it's the first time we're writing to this value (as opposed to + // updating an existing value). + attribute_t *pEconAttrib = FindDynamicAttributeInternal( pAttrDef ); + + if ( !pEconAttrib ) + { + pEconAttrib = &(AddDynamicAttributeInternal()); + pEconAttrib->m_unDefinitionIndex = pAttrDef->GetDefinitionIndex(); + pAttrType->InitializeNewEconAttributeValue( &pEconAttrib->m_value ); + } + + pAttrType->ConvertTypedValueToEconAttributeValue( value, &pEconAttrib->m_value ); + +#if ENABLE_TYPED_ATTRIBUTE_PARANOIA + // Paranoia!: make sure that our read/write functions are mirrored correctly, and that if we attempt + // to read back a value we get something identical to what we just wrote. We do this via converting + // to strings and then comparing those because there may or not be equality comparisons for our type + // T that make sense (ie., protobufs). + { + T readValue; + DbgVerify( FindAttribute( pAttrDef, &readValue ) ); + + std::string sBytes, sReadBytes; + pAttrType->ConvertTypedValueToByteStream( value, &sBytes ); + pAttrType->ConvertTypedValueToByteStream( readValue, &sReadBytes ); + AssertMsg1( sBytes == sReadBytes, "SetDynamicAttributeValue(): read/write mismatch for attribute '%s'.", pAttrDef->GetDefinitionName() ); + } +#endif // ENABLE_TYPED_ATTRIBUTE_PARANOIA + } + + // Called to set a time stamp dynamic attribute on this item. But it will first check the current value assigned to this item, and will + // only set it if this new time extends beyond the current one + void SetDynamicMaxTimeAttributeValue( const CEconItemAttributeDefinition *pAttrDef, RTime32 rtTime ); + + // Remove an instance of an attribute from this item. This will also free any dynamic memory associated + // with that instance if any was allocated. + void RemoveDynamicAttribute( const CEconItemAttributeDefinition *pAttrDef ); + + // Copy all attributes and values in a type-safe way from [source] to ourself. Attributes that we have + // that don't exist on [source] will maintain their current values. All other attributes will get their + // values set to whatever [source] specifies. + void CopyAttributesFrom( const CEconItem& source ); + + bool BHasDynamicAttributes() const { return GetDynamicAttributeCountInternal() > 0; } + +private: + const char* FindIconURL( bool bLarge ) const; + + void Init(); + + template < typename T > + static const ISchemaAttributeTypeBase<T> *GetTypedAttributeType( const CEconItemAttributeDefinition *pAttrDef ) + { + // Make sure the type of data we're passing in matches the type of data we're claiming that we can + // store in the attribute definition. + const ISchemaAttributeType *pIAttr = pAttrDef->GetAttributeType(); + Assert( pIAttr ); + Assert( pIAttr->GetTypeUniqueIdentifier() == GetAttributeTypeUniqueIdentifier<T>() ); + +#if ENABLE_TYPED_ATTRIBUTE_PARANOIA + return dynamic_cast<const ISchemaAttributeTypeBase<T> *>( pIAttr ); +#else + return static_cast<const ISchemaAttributeTypeBase<T> *>( pIAttr ); +#endif + } + +public: + void Compact(); + +#ifdef GC + bool BDeserializeFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors ); +#endif // GC + +#ifdef GC_DLL + void ExportToAPI( GCSDK::CWebAPIValues *pValues ) const; + bool BImportFromAPI( GCSDK::CWebAPIValues *pValues ); +#endif // GC_DLL + + // these are overridden to handle attributes +#ifdef GC_DLL + virtual bool BYieldingAddInsertToTransaction( GCSDK::CSQLAccess & sqlAccess ); + virtual bool BYieldingAddWriteToTransaction( GCSDK::CSQLAccess & sqlAccess, const CUtlVector< int > &fields ); + virtual bool BYieldingAddRemoveToTransaction( GCSDK::CSQLAccess & sqlAccess ); + + void SerializeToSchemaItem( CSchItem &item ) const; + void DeserializeFromSchemaItem( const CSchItem &item ); + + void SetInteriorItem( CEconItem* pInteriorItem ); +#endif // GC_DLL + virtual bool BParseFromMessage( const CUtlBuffer &buffer ) OVERRIDE; + virtual bool BParseFromMessage( const std::string &buffer ) OVERRIDE; + virtual bool BUpdateFromNetwork( const CSharedObject & objUpdate ) OVERRIDE; + +#ifdef GC + virtual bool BAddToMessage( CUtlBuffer & bufOutput ) const OVERRIDE; + virtual bool BAddToMessage( std::string *pBuffer ) const OVERRIDE; // short cut to remove an extra copy + virtual bool BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const OVERRIDE; + virtual bool BAddDestroyToMessage( std::string *pBuffer ) const OVERRIDE; + + bool BYieldingSerializeFromDatabase( itemid_t ulItemID ); +#endif + + virtual bool BIsKeyLess( const CSharedObject & soRHS ) const ; + virtual void Copy( const CSharedObject & soRHS ); + virtual void Dump() const; + virtual CUtlString GetDebugString() const OVERRIDE; + + void SerializeToProtoBufItem( CSOEconItem &msgItem ) const; + void DeserializeFromProtoBufItem( const CSOEconItem &msgItem ); + +#ifdef GC_DLL + CEconItem* YieldingGetInteriorItem(); + const CEconItem* YieldingGetInteriorItem() const { return const_cast<CEconItem *>(this)->YieldingGetInteriorItem(); } + + void SetEquippedThisGameServerSession( bool bEquipped ) { m_bEquippedThisGameServerSession = bEquipped; } + bool EquippedThisGameServerSession() const { return m_bEquippedThisGameServerSession; } +#endif + + // Non-yielding -- will return current interior item if it exists and is already loaded + // but will make no attempt to load. + CEconItem* GetInteriorItem(); + const CEconItem* GetInteriorItem() const { return const_cast<CEconItem *>(this)->GetInteriorItem(); } + + const CEconItemCustomData* GetCustomData() const { return m_pCustomData; } + + void OnTraded( uint32 unTradabilityDelaySeconds ); + void OnReceivedFromMarket( bool bFromRollback ); + +protected: + + // Call this when the appearance of this item changes (ex. paintkit, style, festive). This will + // cause the icon to be lazily re-evaluated (ie. so that changing the style will change the icon) + void DirtyIconURL() { m_pszLargeIcon = NULL; m_pszSmallIcon = NULL; } + // CSharedObject + // adapted from CSchemaSharedObject + void GetDirtyColumnSet( const CUtlVector< int > &fields, GCSDK::CColumnSet &cs ) const; + + void EnsureCustomDataExists(); + + bool BYieldingLoadInteriorItem(); + + void OnTransferredOwnership(); + + // Internal attribute interface. + friend class CWebAPIStringExporterAttributeIterator; + friend class CAttributeToStringIterator; + + attribute_t& AddDynamicAttributeInternal(); // add another chunk of data to our internal storage to store a new attribute -- initialization is the responsibility of the caller + attribute_t *FindDynamicAttributeInternal( const CEconItemAttributeDefinition *pAttrDef ); // search for an instance of a dynamic attribute with this definition -- ignores static properties, etc. and will return NULL if not found + int GetDynamicAttributeCountInternal() const; // how many attributes are there attached to this instance? + attribute_t& GetMutableDynamicAttributeInternal( int iAttrIndexIntoArray ); // get a writable version of our attribute memory base chunk (added by AddDynamicAttributeInternal) for this index (same "array" as GetDynamicAttributeCountInternal) + const attribute_t& GetDynamicAttributeInternal( int iAttrIndexIntoArray ) const // read-only version of our attribute memory base chunk for this index (same "array" as GetDynamicAttributeCountInternal) + { + return const_cast<CEconItem *>( this )->GetMutableDynamicAttributeInternal( iAttrIndexIntoArray ); + } + + const EquippedInstance_t *FindEquippedInstanceForClass( equipped_class_t nClass ) const; + void InternalVerifyEquipInstanceIntegrity() const; + + struct dirty_bits_t + { + // other + uint8 m_bInUse : 1; + uint8 m_bHasEquipSingleton : 1; + uint8 m_bHasAttribSingleton: 1; + }; + + mutable const char* m_pszSmallIcon; + mutable const char* m_pszLargeIcon; +public: + // data that is most commonly changed + uint64 m_ulID; // Item ID + uint32 m_unAccountID; // Item Owner + uint32 m_unInventory; // App managed int representing inventory placement + item_definition_index_t m_unDefIndex; // Item definition index + uint8 m_unLevel; // Item Level + uint8 m_nQuality; // Item quality (rarity) + uint8 m_unFlags; // Flags + uint8 m_unOrigin; // Origin (eEconItemOrigin) + style_index_t m_unStyle; // Style + + dirty_bits_t m_dirtyBits; // dirty bits + + // Fields that we often have zero or one of, but not often more + EquippedInstance_t m_EquipInstanceSingleton; // Where the item is equipped. Valid only if m_bHasEquipSingleton and there is no custom data + attribute_t m_CustomAttribSingleton; // Custom attribute. Valid only if m_bHasAttribSingleton and there is no custom data + + // optional data (custom name, additional attributes, etc.) + CEconItemCustomData *m_pCustomData; + +#ifdef GC_DLL +private: + bool m_bEquippedThisGameServerSession; +#endif // GC_DLL +}; + +//----------------------------------------------------------------------------- +// Purpose: Storage for data that is not commonly changed in CEconItem, primarily +// as a memory savings mechanism. +//----------------------------------------------------------------------------- +class CEconItemCustomData +{ +public: + CEconItemCustomData() + : m_pInteriorItem( NULL ) + , m_ulOriginalID( INVALID_ITEM_ID ) + , m_unQuantity( 1 ) + , m_vecAttributes( /* grow size: */ 1, /* init size: */ 0 ) + , m_vecEquipped( /* grow size: */ 1, /* init size: */ 0 ) + {} + + ~CEconItemCustomData(); + + CUtlVector< CEconItem::attribute_t > m_vecAttributes; + CEconItem* m_pInteriorItem; + uint64 m_ulOriginalID; // Original Item ID + uint16 m_unQuantity; // Consumable stack count (ammo, money, etc) + + CUtlVector<CEconItem::EquippedInstance_t> m_vecEquipped; + + static void FreeAttributeMemory( CEconItem::attribute_t *pAttrib ); + +#ifdef GC_DLL + DECLARE_CLASS_MEMPOOL( CEconItemCustomData ); +#endif +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < typename TAttribInMemoryType > +/*virtual*/ void ISchemaAttributeTypeBase<TAttribInMemoryType>::LoadByteStreamToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const std::string& sBytes ) const +{ + Assert( pTargetItem ); + Assert( pAttrDef ); + + TAttribInMemoryType typedValue; + ConvertByteStreamToTypedValue( sBytes, &typedValue ); + + pTargetItem->SetDynamicAttributeValue( pAttrDef, typedValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < typename TAttribInMemoryType > +/*virtual*/ void ISchemaAttributeTypeBase<TAttribInMemoryType>::ConvertEconAttributeValueToByteStream( const attribute_data_union_t& value, ::std::string *out_psBytes ) const +{ + ConvertTypedValueToByteStream( GetTypedValueContentsFromEconAttributeValue( value ), out_psBytes ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < typename TAttribInMemoryType > +/*virtual*/ void ISchemaAttributeTypeBase<TAttribInMemoryType>::LoadEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value ) const +{ + pTargetItem->SetDynamicAttributeValue( pAttrDef, GetTypedValueContentsFromEconAttributeValue( value ) ); +} + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct CEconItemEquipInstanceHelpers +{ + static void AssignItemToSlot( CEconSharedObjectCache *pSOCache, CEconItem *pItem, equipped_class_t unClass, equipped_slot_t unSlot, CEconUserSession *pOptionalSession = NULL ); +}; +#endif // GC_DLL + +void YieldingAddAuditRecord( GCSDK::CSQLAccess *sqlAccess, CEconItem *pItem, uint32 unOwnerID, EItemAction eAction, uint32 unData ); +void YieldingAddAuditRecord( GCSDK::CSQLAccess *sqlAccess, uint64 ulItemID, uint32 unOwnerID, EItemAction eAction, uint32 unData ); +bool YieldingAddItemToDatabase( CEconItem *pItem, const CSteamID & steamID, EItemAction eAction, uint32 unData ); + +//----------------------------------------------------------------------------- +// Purpose: wrap the idea of "get a loot list from this item"; some loot lists +// are static definitions and some are temporary heap-allocated objects +// and this means you don't care which you're dealing with until we +// come up with a better interface +//----------------------------------------------------------------------------- +class CCrateLootListWrapper +{ +public: + CCrateLootListWrapper( const IEconItemInterface *pEconItem ) + : m_pLootList( NULL ) + , m_unAuditDetailData( 0 ) + , m_bIsDynamicallyAllocatedLootList( false ) + { + Assert( pEconItem ); + + if ( !BAttemptCrateSeriesInitialization( pEconItem ) + && !BAttemptLootListStringInitialization( pEconItem ) + && !BAttemptLineItemInitialization( pEconItem ) ) + { + // We don't actually have anything to do here. We'll return NULL when someone asks for our + // loot list and we're done. + } + } + + ~CCrateLootListWrapper() + { + if ( m_bIsDynamicallyAllocatedLootList ) + { + delete m_pLootList; + } + } + + const IEconLootList *GetEconLootList() const + { + return m_pLootList; + } + + uint32 GetAuditDetailData() const + { + return m_unAuditDetailData; + } + +private: + CCrateLootListWrapper( const CCrateLootListWrapper& ); // intentionally unimplemented + void operator=( const CCrateLootListWrapper& ); // intentionally unimplemented + +private: + // Look for an attribute that specifies a crate series. + MUST_CHECK_RETURN bool BAttemptCrateSeriesInitialization( const IEconItemInterface *pEconItem ); + + // Look for an attribute that specifies a loot list by string name. + MUST_CHECK_RETURN bool BAttemptLootListStringInitialization( const IEconItemInterface *pEconItem ); + + // Look for a line-item-per-attribute list. + MUST_CHECK_RETURN bool BAttemptLineItemInitialization( const IEconItemInterface *pEconItem ); + +private: + const IEconLootList *m_pLootList; + uint32 m_unAuditDetailData; + bool m_bIsDynamicallyAllocatedLootList; +}; + +//----------------------------------------------------------------------------- +// Purpose: Maintains a handle to an CEconItem. If the item gets deleted, this +// handle will return NULL when dereferenced +//----------------------------------------------------------------------------- +class CEconItemHandle : GCSDK::ISharedObjectListener +{ +public: + CEconItemHandle() + : m_pItem( NULL ) + , m_iItemID( INVALID_ITEM_ID ) + {} + + CEconItemHandle( CEconItem* pItem ) + : m_pItem( pItem ) + { + SetItem( pItem ); + } + + virtual ~CEconItemHandle(); + + void SetItem( CEconItem* pItem ); + + operator CEconItem *( void ) const + { + return m_pItem; + } + + CEconItem* operator->( void ) const + { + return m_pItem; + } + + CEconItem* operator=( CEconItem* pRhs ) + { + SetItem( pRhs ); + return m_pItem; + } + + virtual void SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + + virtual void SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + virtual void SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + virtual void SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + + virtual void PreSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE{} + virtual void PostSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE{} + virtual void SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE{} + +private: + + void UnsubscribeFromSOEvents(); + + CEconItem* m_pItem; // The item + itemid_t m_iItemID; // The stored itemID + CSteamID m_OwnerSteamID; // Steam ID of the item owner. Used for registering/unregistering from SOCache +}; + +#endif // ECONITEM_H diff --git a/game/shared/econ/econ_item_constants.cpp b/game/shared/econ/econ_item_constants.cpp new file mode 100644 index 0000000..ffb7784 --- /dev/null +++ b/game/shared/econ/econ_item_constants.cpp @@ -0,0 +1,970 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Holds constants for the econ item system +// +//============================================================================= + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *g_szQualityStrings[] = +{ + "Normal", + "rarity1", // Genuine + "rarity2", // Customized + "vintage", // Vintage has to stay at 3 for backwards compatibility + "rarity3", // Well-Designed + "rarity4", // Unusual + "Unique", + "community", + "developer", + "selfmade", + "customized", + "strange", + "completed", + "haunted", + "collectors", + "paintkitWeapon", + + "default", // AE_RARITY_DEFAULT, + "common", // AE_RARITY_COMMON, + "uncommon", // AE_RARITY_UNCOMMON, + "rare", // AE_RARITY_RARE, + "mythical", // AE_RARITY_MYTHICAL, + "legendary", // AE_RARITY_LEGENDARY, + "ancient", // AE_RARITY_ANCIENT, +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_szQualityStrings ) == AE_MAX_TYPES ); + +const char *EconQuality_GetQualityString( EEconItemQuality eQuality ) +{ + // This is a runtime check and not an assert because we could theoretically bounce the GC with new + // qualities while the client is running. + if ( eQuality >= 0 && eQuality < AE_MAX_TYPES ) + return g_szQualityStrings[ eQuality ]; + + return NULL; +} + +EEconItemQuality EconQuality_GetQualityFromString( const char* pszQuality ) +{ + // Convert to lowercase + CUtlString strLoweredInput( pszQuality ); + strLoweredInput.ToLower(); + + // Guaranteed with the compile time assert above that AE_MAX_TYPES is + // the size of the string qualities + for( int i = 0; i < AE_MAX_TYPES; ++i ) + { + // Convert to lowercase + CUtlString strLoweredQuality( g_szQualityStrings[i] ); + strLoweredQuality.ToLower(); + + if( !Q_stricmp( strLoweredInput.Get(), strLoweredQuality.Get() ) ) + return EEconItemQuality(i); + } + + return AE_UNDEFINED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *g_szQualityColorStrings[] = +{ + "QualityColorNormal", + "QualityColorrarity1", + "QualityColorrarity2", + "QualityColorVintage", + "QualityColorrarity3", + "QualityColorrarity4", // AE_UNUSUAL + "QualityColorUnique", + "QualityColorCommunity", + "QualityColorDeveloper", + "QualityColorSelfMade", + "QualityColorSelfMadeCustomized", + "QualityColorStrange", + "QualityColorCompleted", + "QualityColorHaunted", // AE_HAUNTED + "QualityColorCollectors", // AE_COLLECTORS + "QualityColorPaintkitWeapon", // AE_PAINTKITWEAPON + + "ItemRarityDefault" , // AE_RARITY_DEFAULT, + "ItemRarityCommon" , // AE_RARITY_COMMON, + "ItemRarityUncommon" , // AE_RARITY_UNCOMMON, + "ItemRarityRare" , // AE_RARITY_RARE, + "ItemRarityMythical" , // AE_RARITY_MYTHICAL, + "ItemRarityLegendary" , // AE_RARITY_LEGENDARY, + "ItemRarityAncient" , // AE_RARITY_ANCIENT, +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_szQualityColorStrings ) == AE_MAX_TYPES ); + +const char *EconQuality_GetColorString( EEconItemQuality eQuality ) +{ + if ( eQuality >= 0 && eQuality < AE_MAX_TYPES ) + return g_szQualityColorStrings[ eQuality ]; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *g_szQualityLocalizationStrings[] = +{ + "#Normal", + "#rarity1", // Genuine + "#rarity2", + "#vintage", + "#rarity3", // Artisan + "#rarity4", // Unusual + "#unique", + "#community", + "#developer", + "#selfmade", + "#customized", + "#strange", + "#completed", + "#haunted", + "#collectors", + "#paintkitWeapon", + + "#Rarity_Default", + "#Rarity_Common", + "#Rarity_Uncommon", + "#Rarity_Rare", + "#Rarity_Mythical", + "#Rarity_Legendary", + "#Rarity_Ancient" +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_szQualityLocalizationStrings ) == AE_MAX_TYPES ); + +const char *EconQuality_GetLocalizationString( EEconItemQuality eQuality ) +{ + if ( eQuality >= 0 && eQuality < AE_MAX_TYPES ) + return g_szQualityLocalizationStrings[ eQuality ]; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Sort order for rarities +// Small Numbers sort to front +//----------------------------------------------------------------------------- +int g_nRarityScores[] = +{ + 15, // AE_NORMAL, + 10, // AE_RARITY1, // Geniune + 102, // AE_RARITY2, // Customized (unused) + 11, // AE_VINTAGE, + 101, // AE_RARITY3, // Artisan (unused) + 0, // AE_UNUSUAL, + 14, // AE_UNIQUE, + -1, // AE_COMMUNITY, + -3, // AE_DEVELOPER, + -2, // AE_SELFMADE, + 100, // AE_CUSTOMIZED, // Unused + 9, // AE_STRANGE, + 103, // AE_COMPLETED, // Unused + 13, // AE_HAUNTED + 12, // AE_COLLECTORS + 8, // AE_PAINTKITWEAPON + 7, // AE_RARITY_DEFAULT, + 6, // AE_RARITY_COMMON, + 5, // AE_RARITY_UNCOMMON, + 4, // AE_RARITY_RARE, + 3, // AE_RARITY_MYTHICAL, + 2, // AE_RARITY_LEGENDARY, + 1, // AE_RARITY_ANCIENT, +}; +COMPILE_TIME_ASSERT( ARRAYSIZE( g_nRarityScores ) == AE_MAX_TYPES ); + +//----------------------------------------------------------------------------- +int EconQuality_GetRarityScore( EEconItemQuality eQuality ) +{ + if ( eQuality >= 0 && eQuality < AE_MAX_TYPES ) + return g_nRarityScores[ eQuality ]; + + return 0; +} + +//----------------------------------------------------------------------------- +const char *g_pchWearAmountStrings[] = +{ + "#TFUI_InvTooltip_None", + "#TFUI_InvTooltip_FactoryNew", + "#TFUI_InvTooltip_MinimalWear", + "#TFUI_InvTooltip_FieldTested", + "#TFUI_InvTooltip_WellWorn", + "#TFUI_InvTooltip_BattleScared" +}; + +//----------------------------------------------------------------------------- +int EconWear_ToIntCategory( float flWear ) +{ + if ( flWear <= 0.2f ) + { + return 1; + } + else if ( flWear <= 0.4f ) + { + return 2; + } + else if ( flWear <= 0.6f ) + { + return 3; + } + else if ( flWear <= 0.8f ) + { + return 4; + } + else if ( flWear <= 1.0f ) + { + return 5; + } + + return 3; // default wear +} + +// ------------------------------------------------------------------------- +// Shim to return a value for buckets. For strange we bucket all of them in to 1 non-instance data group +int EconStrange_ToStrangeBucket( float value ) +{ + return 0; +} +float EconStrange_FromStrangeBucket( int value ) +{ + return 0; +} + +//----------------------------------------------------------------------------- +const char *GetWearLocalizationString( float flWear ) +{ + int nIndex = EconWear_ToIntCategory( flWear ); + return g_pchWearAmountStrings[ nIndex ]; +} +//----------------------------------------------------------------------------- +bool EconWear_IsValidValue( int nWear ) +{ + return nWear > 0 && nWear <= 5; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CSchemaColorDefHandle g_AttribColorDefs[] = +{ + CSchemaColorDefHandle( "desc_level" ), // ATTRIB_COL_LEVEL + CSchemaColorDefHandle( "desc_attrib_neutral" ), // ATTRIB_COL_NEUTRAL + CSchemaColorDefHandle( "desc_attrib_positive" ), // ATTRIB_COL_POSITIVE + CSchemaColorDefHandle( "desc_attrib_negative" ), // ATTRIB_COL_NEGATIVE + CSchemaColorDefHandle( "desc_itemset_name" ), // ATTRIB_COL_ITEMSET_NAME + CSchemaColorDefHandle( "desc_itemset_equipped" ), // ATTRIB_COL_ITEMSET_EQUIPPED + CSchemaColorDefHandle( "desc_itemset_missing" ), // ATTRIB_COL_ITEMSET_MISSING + CSchemaColorDefHandle( "desc_bundle" ), // ATTRIB_COL_BUNDLE_ITEM + CSchemaColorDefHandle( "desc_limited_use" ), // ATTRIB_COL_LIMITED_USE + CSchemaColorDefHandle( "desc_flags" ), // ATTRIB_COL_component_flags + CSchemaColorDefHandle( "desc_limited_quantity" ), // ATTRIB_COL_LIMITED_QUANTITY + + CSchemaColorDefHandle( "desc_default" ), // ATTRIB_COL_RARITY_DEFAULT + CSchemaColorDefHandle( "desc_common" ), // ATTRIB_COL_RARITY_COMMON + CSchemaColorDefHandle( "desc_uncommon" ), // ATTRIB_COL_RARITY_UNCOMMON + CSchemaColorDefHandle( "desc_rare" ), // ATTRIB_COL_RARITY_RARE + CSchemaColorDefHandle( "desc_mythical" ), // ATTRIB_COL_RARITY_MYTHICAL + CSchemaColorDefHandle( "desc_legendary" ), // ATTRIB_COL_RARITY_LEGENDARY + CSchemaColorDefHandle( "desc_ancient" ), // ATTRIB_COL_RARITY_ANCIENT + CSchemaColorDefHandle( "desc_immortal" ), // ATTRIB_COL_RARITY_IMMORTAL + CSchemaColorDefHandle( "desc_arcana" ), // ATTRIB_COL_RARITY_ARCANA + + CSchemaColorDefHandle( "desc_strange" ), // ATTRIB_COL_STRANGE + CSchemaColorDefHandle( "desc_unusual" ), // ATTRIB_COL_UNUSUAL +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_AttribColorDefs ) == NUM_ATTRIB_COLORS ); + +attrib_colors_t GetAttribColorIndexForName( const char* pszName ) +{ + for ( int i = 0; i < NUM_ATTRIB_COLORS; ++i ) + { + if ( !Q_strcmp( g_AttribColorDefs[i].GetName(), pszName ) ) + return (attrib_colors_t)i; + } + + return (attrib_colors_t)0; +} + +const char *GetColorNameForAttribColor( attrib_colors_t unAttribColor ) +{ + Assert( unAttribColor >= 0 ); + Assert( unAttribColor < NUM_ATTRIB_COLORS ); + + return g_AttribColorDefs[unAttribColor] + ? g_AttribColorDefs[unAttribColor]->GetColorName() + : "ItemAttribNeutral"; +} + +const char *GetHexColorForAttribColor( attrib_colors_t unAttribColor ) +{ + Assert( unAttribColor >= 0 ); + Assert( unAttribColor < NUM_ATTRIB_COLORS ); + + return g_AttribColorDefs[unAttribColor] + ? g_AttribColorDefs[unAttribColor]->GetHexColor() + : "#ebe2ca"; +} + +entityquality_t GetItemQualityFromString( const char *sQuality ) +{ + for ( int i = 0; i < AE_MAX_TYPES; i++ ) + { + if ( !Q_strnicmp( sQuality, g_szQualityStrings[i], 16 ) ) + return (entityquality_t)i; + } + + return AE_NORMAL; +} + +const char *g_szRecipeCategoryStrings[] = +{ + "crafting", // RECIPE_CATEGORY_CRAFTINGITEMS = 0, + "commonitem", // RECIPE_CATEGORY_COMMONITEMS, + "rareitem", // RECIPE_CATEGORY_RAREITEMS, + "special", // RECIPE_CATEGORY_SPECIAL, +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_szRecipeCategoryStrings ) == NUM_RECIPE_CATEGORIES ); + +//----------------------------------------------------------------------------- +// Item acquisition. +//----------------------------------------------------------------------------- +// Strings shown to the local player in the pickup dialog +const char *g_pszItemPickupMethodStrings[] = +{ + "#NewItemMethod_Dropped", // UNACK_ITEM_DROPPED = 1, + "#NewItemMethod_Crafted", // UNACK_ITEM_CRAFTED, + "#NewItemMethod_Traded", // UNACK_ITEM_TRADED, + "#NewItemMethod_Purchased", // UNACK_ITEM_PURCHASED, + "#NewItemMethod_FoundInCrate", // UNACK_ITEM_FOUND_IN_CRATE, + "#NewItemMethod_Gifted", // UNACK_ITEM_GIFTED, + "#NewItemMethod_Support", // UNACK_ITEM_SUPPORT, + "#NewItemMethod_Promotion", // UNACK_ITEM_PROMOTION, + "#NewItemMethod_Earned", // UNACK_ITEM_EARNED, + "#NewItemMethod_Refunded", // UNACK_ITEM_REFUNDED, + "#NewItemMethod_GiftWrapped", // UNACK_ITEM_GIFT_WRAPPED, + "#NewItemMethod_Foreign", // UNACK_ITEM_FOREIGN, + "#NewItemMethod_CollectionReward", // UNACK_ITEM_COLLECTION_REWARD + "#NewItemMethod_PreviewItem", // UNACK_ITEM_PREVIEW_ITEM + "#NewItemMethod_PreviewItemPurchased", // UNACK_ITEM_PREVIEW_ITEM_PURCHASED + "#NewItemMethod_PeriodicScoreReward",// UNACK_ITEM_PERIODIC_SCORE_REWARD + "#NewItemMethod_MvMBadgeCompletionReward",// UNACK_ITEM_MVM_MISSION_COMPLETION_REWARD + "#NewItemMethod_MvMSquadSurplusReward",// UNACK_ITEM_MVM_SQUAD_SURPLUS_REWARD + "#NewItemMethod_HolidayGift", // UNACK_ITEM_FOUND_HOLIDAY_GIFT + "#NewItemMethod_CommunityMarketPurchase", // UNACK_ITEM_COMMUNITY_MARKET_PURCHASE + "#NewItemMethod_RecipeOutput", // UNACK_ITEM_RECIPE_OUTPUT + NULL, // UNACK_ITEM_HIDDEN_QUEST_ITEM + "#NewItemMethod_QuestOutput", // UNACK_ITEM_QUEST_OUTPUT + "#NewItemMethod_QuestLoaner", // UNACK_ITEM_QUEST_LOANER + "#NewItemMethod_TradeUp", // UNACK_ITEM_TRADE_UP + "#NewItemMethod_QuestMerasmissionOutput", //UNACK_ITEM_QUEST_MERASMISSION_OUTPUT + "#NewItemMethod_ViralCompetitiveBetaPassSpread", //UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD +#ifdef ENABLE_STORE_RENTAL_BACKEND + "#NewItemMethod_RentalPurchase", // UNACK_ITEM_RENTAL_PURCHASE +#endif +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_pszItemPickupMethodStrings ) == (UNACK_NUM_METHODS - 1) ); // -1 because UNACK_ITEM_DROPPED is index 1, not 0 + +const char *g_pszItemPickupMethodStringsUnloc[] = +{ + "dropped", // UNACK_ITEM_DROPPED = 1, + "crafted", // UNACK_ITEM_CRAFTED, + "traded", // UNACK_ITEM_TRADED, + "purchased", // UNACK_ITEM_PURCHASED, + "found_in_crate", // UNACK_ITEM_FOUND_IN_CRATE, + "gifted", // UNACK_ITEM_GIFTED, + "support", // UNACK_ITEM_SUPPORT, + "promotion", // UNACK_ITEM_PROMOTION, + "earned", // UNACK_ITEM_EARNED, + "refunded", // UNACK_ITEM_REFUNDED, + "gift_wrapped", // UNACK_ITEM_GIFT_WRAPPED + "foreign", // UNACK_ITEM_FOREIGN + "collection_reward",// UNACK_ITEM_COLLECTION_REWARD + "preview_item", // UNACK_ITEM_PREVIEW_ITEM + "preview_item_purchased", // UNACK_ITEM_PREVIEW_ITEM_PURCHASED + "periodic_score_reward", // UNACK_ITEM_PERIODIC_SCORE_REWARD + "mvm_badge_completion_reward", // UNACK_ITEM_MVM_MISSION_COMPLETION_REWARD + "mvm_squad_surplus_reward", // UNACK_ITEM_MVM_SQUAD_SURPLUS_REWARD + "holiday_gift", // UNACK_ITEM_FOUND_HOLIDAY_GIFT + "market_purchase", // UNACK_ITEM_COMMUNITY_MARKET_PURCHASE + "recipe_output", // UNACK_ITEM_RECIPE_OUTPUT + "hidden_quest", // UNACK_ITEM_HIDDEN_QUEST_ITEM + "quest_output", // UNACK_ITEM_QUEST_OUTPUT + "trade_up", // UNACK_ITEM_TRADE_UP + "quest_output", // UNACK_ITEM_QUEST_MERASMISSION_OUTPUT + "viral_competitive_beta_pass", //UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD +#ifdef ENABLE_STORE_RENTAL_BACKEND + "rental_purchase", // UNACK_ITEM_RENTAL_PURCHASE +#endif +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_pszItemPickupMethodStringsUnloc ) == (UNACK_NUM_METHODS - 1) ); + +// Strings shown to other players in the chat dialog +const char *g_pszItemFoundMethodStrings[] = +{ + "#Item_Found", // UNACK_ITEM_DROPPED = 1, + "#Item_Crafted", // UNACK_ITEM_CRAFTED, + "#Item_Traded", // UNACK_ITEM_TRADED, + NULL, // UNACK_ITEM_PURCHASED, + "#Item_FoundInCrate", // UNACK_ITEM_FOUND_IN_CRATE, + "#Item_Gifted", // UNACK_ITEM_GIFTED, + NULL, // UNACK_ITEM_SUPPORT, + NULL, // UNACK_ITEM_PROMOTION + "#Item_Earned", // UNACK_ITEM_EARNED + "#Item_Refunded", // UNACK_ITEM_REFUNDED + "#Item_GiftWrapped", // UNACK_ITEM_GIFT_WRAPPED + "#Item_Foreign", // UNACK_ITEM_FOREIGN + "#Item_CollectionReward", // UNACK_ITEM_COLLECTION_REWARD + "#Item_PreviewItem", // UNACK_ITEM_PREVIEW_ITEM + "#Item_PreviewItemPurchased",// UNACK_ITEM_PREVIEW_ITEM_PURCHASED + "#Item_PeriodicScoreReward",// UNACK_ITEM_PERIODIC_SCORE_REWARD + "#Item_MvMBadgeCompletionReward",// UNACK_ITEM_MVM_MISSION_COMPLETION_REWARD + "#Item_MvMSquadSurplusReward",// UNACK_ITEM_MVM_SQUAD_SURPLUS_REWARD + "#Item_HolidayGift", // UNACK_ITEM_FOUND_HOLIDAY_GIFT + NULL, // UNACK_ITEM_COMMUNITY_MARKET_PURCHASE + "#Item_RecipeOutput", // UNACK_ITEM_RECIPE_OUTPUT + NULL, // UNACK_ITEM_HIDDEN_QUEST_ITEM + "#Item_QuestOutput", // UNACK_ITEM_QUEST_OUTPUT + NULL, // UNACK_ITEM_QUEST_LOANER + "#Item_TradeUp", // UNACK_ITEM_TRADE_UP + "#Item_QuestMerasmissionOutput", // UNACK_ITEM_QUEST_MERASMISSION_OUTPUT + "#Item_ViralCompetitiveBetaPassSpread", //UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD +#ifdef ENABLE_STORE_RENTAL_BACKEND + NULL, // UNACK_ITEM_RENTAL_PURCHASE +#endif +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_pszItemFoundMethodStrings ) == (UNACK_NUM_METHODS - 1) ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct strange_attr_set_t +{ + strange_attr_set_t( const char *pScoreAttrName, const char *pTypeAttrName, const char *pRestrictionAttrName, const char *pRestrictionValueAttrName, bool bIsUserCustomizable ) + : m_attrScore( pScoreAttrName ) + , m_attrType( pTypeAttrName ) + , m_attrRestriction( pRestrictionAttrName ) + , m_attrRestrictionValue( pRestrictionValueAttrName ) + , m_bIsUserCustomizable( bIsUserCustomizable ) + { + // + } + + CSchemaAttributeDefHandle m_attrScore; + CSchemaAttributeDefHandle m_attrType; + CSchemaAttributeDefHandle m_attrRestriction; + CSchemaAttributeDefHandle m_attrRestrictionValue; + bool m_bIsUserCustomizable; +}; + +strange_attr_set_t g_KillEaterAttr[] = +{ + strange_attr_set_t( "kill eater", "kill eater score type", "strange restriction type 1", "strange restriction value 1", false ), + strange_attr_set_t( "kill eater 2", "kill eater score type 2", "strange restriction type 2", "strange restriction value 2", false ), + strange_attr_set_t( "kill eater 3", "kill eater score type 3", "strange restriction type 3", "strange restriction value 3", false ), + + // assumption: all of the user-customizable attributes will follow all of the schema-specified attributes + strange_attr_set_t( "kill eater user 1", "kill eater user score type 1", "strange restriction user type 1", "strange restriction user value 1", true ), + strange_attr_set_t( "kill eater user 2", "kill eater user score type 2", "strange restriction user type 2", "strange restriction user value 2", true ), + strange_attr_set_t( "kill eater user 3", "kill eater user score type 3", "strange restriction user type 3", "strange restriction user value 3", true ), +}; + +int GetKillEaterAttrCount() +{ +#ifdef DBGFLAG_ASSERT + // Verify our commented assumption that all of the non-user-customizable attributes will be followed by + // all of the user-customizable attributes. + bool bInUserCustomizableBlock = false; + + for ( int i = 0; i < ARRAYSIZE( g_KillEaterAttr ); i++ ) + { + if ( bInUserCustomizableBlock ) + { + AssertMsg( g_KillEaterAttr[i].m_bIsUserCustomizable, "Ordering assumption for g_KillEaterAttr violated! User-customizable attributes should all be at the end of the list!" ); + } + + bInUserCustomizableBlock |= g_KillEaterAttr[i].m_bIsUserCustomizable; + } +#endif + + return ARRAYSIZE( g_KillEaterAttr ); +} + +int GetKillEaterAttrCount_UserCustomizable() +{ + int iCount = 0; + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( GetKillEaterAttr_IsUserCustomizable( i ) ) + { + iCount++; + } + } + + return iCount; +} + +const CEconItemAttributeDefinition *GetKillEaterAttr_Score( int i ) +{ + Assert( i >= 0 ); + Assert( i < GetKillEaterAttrCount() ); + + const CEconItemAttributeDefinition *pAttrRes = g_KillEaterAttr[i].m_attrScore; + AssertMsg1( pAttrRes, "Missing Killeater attr score %s", g_KillEaterAttr[ i ].m_attrScore.GetName() ); + + return pAttrRes; +} + +const CEconItemAttributeDefinition *GetKillEaterAttr_Type( int i ) +{ + Assert( i >= 0 ); + Assert( i < GetKillEaterAttrCount() ); + + const CEconItemAttributeDefinition *pAttrRes = g_KillEaterAttr[i].m_attrType; + AssertMsg1( pAttrRes, "Missing Killeater attr type %s", g_KillEaterAttr[ i ].m_attrType.GetName() ); + + return pAttrRes; +} + +const CEconItemAttributeDefinition *GetKillEaterAttr_Restriction( int i ) +{ + Assert( i >= 0 ); + Assert( i < GetKillEaterAttrCount() ); + + const CEconItemAttributeDefinition *pAttrRes = g_KillEaterAttr[i].m_attrRestriction; + AssertMsg1( pAttrRes, "Missing Killeater attr restriction %s", g_KillEaterAttr[ i ].m_attrRestriction.GetName() ); + + return pAttrRes; +} + +const CEconItemAttributeDefinition *GetKillEaterAttr_RestrictionValue( int i ) +{ + Assert( i >= 0 ); + Assert( i < GetKillEaterAttrCount() ); + + const CEconItemAttributeDefinition *pAttrRes = g_KillEaterAttr[i].m_attrRestrictionValue; + AssertMsg1( pAttrRes, "Missing Killeater attr restriction value %s", g_KillEaterAttr[ i ].m_attrRestrictionValue.GetName() ); + + return pAttrRes; +} + +bool GetKillEaterAttr_IsUserCustomizable( int i ) +{ + Assert( i >= 0 ); + Assert( i < GetKillEaterAttrCount() ); + + return g_KillEaterAttr[i].m_bIsUserCustomizable; +} + + +bool GetKilleaterValueByEvent( const IEconItemInterface* pItem, const kill_eater_event_t& EEventType, uint32& value ) +{ + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + const CEconItemAttributeDefinition *pAttribKillEater = GetKillEaterAttr_Score( i ); + const CEconItemAttributeDefinition *pAttribKillEaterScoreType = GetKillEaterAttr_Type( i ); + + Assert( pAttribKillEater && pAttribKillEaterScoreType ); + if ( !pAttribKillEater || !pAttribKillEaterScoreType ) + return false; + + // make sure this item even has a kill count attribute we're looking for + uint32 unKillEaterAttrValue; + if ( !pItem->FindAttribute( pAttribKillEater, &unKillEaterAttrValue ) ) + continue; + + uint32 unKillEaterScoreTypeAttrValue = kKillEaterEvent_PlayerKill; + + float fKillEaterScoreTypeAttrValue; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttribKillEaterScoreType, &fKillEaterScoreTypeAttrValue ) ) + { + unKillEaterScoreTypeAttrValue = (uint32)fKillEaterScoreTypeAttrValue; + } + + // this isn't the attribute we're trying to find + if ( EEventType != (kill_eater_event_t)unKillEaterScoreTypeAttrValue ) + continue; + + value = unKillEaterAttrValue; + return true; + } + + return false; +} + +// Does this thing have kill eater +bool BIsItemStrange( const IEconItemInterface *pItem ) +{ + // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply + uint32 unKillEaterAttr; + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( pItem->FindAttribute( GetKillEaterAttr_Score( i ), &unKillEaterAttr ) ) + { + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Get a localization token that describes why an item is not usable +// in the trade-up crafting. Returns NULL if no reason. Can pass in +// another item to compare against, which causes extra consistency checks +//----------------------------------------------------------------------------- +const char* GetCollectionCraftingInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) +{ + if ( !pTestItem ) + { + return "#TF_CollectionCrafting_NoItem"; + } + + // Needs to have a collection + const CEconItemCollectionDefinition* pTestCollection = pTestItem->GetItemDefinition()->GetItemCollectionDefinition(); + if ( !pTestCollection ) + { + return "#TF_CollectionCrafting_NoCollection"; + } + + // Make sure this item is a part of the collection it claims to be in + { + item_definition_index_t nThisDefIndex = pTestItem->GetItemDefIndex(); + bool bFound = false; + for( int i=0; i < pTestCollection->m_iItemDefs.Count() && !bFound; ++i ) + { + bFound |= pTestCollection->m_iItemDefs[i] == nThisDefIndex; + } + + if ( !bFound ) + { + return "#TF_CollectionCrafting_NoCollection"; + } + } + + // Needs rarity + uint8 nRarity = pTestItem->GetItemDefinition()->GetRarity(); + if( nRarity == k_unItemRarity_Any ) + { + return "#TF_CollectionCrafting_NoRarity"; + } + + // Can't use items with rarity at the "top" of a collection (what would they craft into?) + if ( nRarity == pTestCollection->GetMaxRarity() ) + { + return "#TF_CollectionCrafting_MaxRarity"; + } + + // No self mades or community items + uint32 eQuality = pTestItem->GetQuality(); + if ( eQuality == AE_SELFMADE || eQuality == AE_COMMUNITY ) + { + return "#TF_CollectionCrafting_NoUnusual"; + } + + // This is how we test for unusuals. Don't let unusuals be crafted + static CSchemaAttributeDefHandle pAttrDef_ParticleEffect( "attach particle effect" ); + if ( pTestItem->FindAttribute( pAttrDef_ParticleEffect ) ) + { + return "#TF_CollectionCrafting_NoUnusual"; + } + + static CSchemaAttributeDefHandle pAttrDef_TauntUnusualAttr( "on taunt attach particle index" ); + if ( pTestItem->FindAttribute( pAttrDef_TauntUnusualAttr ) ) + { + return "#TF_CollectionCrafting_NoUnusual"; + } + + // Not allowed to be crafted? + if ( !pTestItem->IsUsableInCrafting() ) + { + return "#TF_CollectionCrafting_NotCraftable"; + } + + // If another item was passed in, we have a few consistency checks to make + if ( pSourceItem ) + { + // Need to have the same rarity + if ( nRarity != pSourceItem->GetItemDefinition()->GetRarity() ) + { + return "#TF_CollectionCrafting_MismatchRarity"; + } + + // Need to have the same strangeness + if ( BIsItemStrange( pSourceItem ) != BIsItemStrange( pTestItem ) ) + { + return "#TF_CollectionCrafting_MismatchStrange"; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Get a localization token that describes why an item is not usable +// in the Halloween Offering. Returns NULL if no reason. Can pass in +// another item to compare against, which causes extra consistency checks +//----------------------------------------------------------------------------- +const char* GetHalloweenOfferingInvalidReason( const IEconItemInterface *pTestItem, const IEconItemInterface *pSourceItem ) +{ + // Must either be a Cosmetic + // Taunt + // Allowable Tool (Strange part, Paint, name tag, killstreak). Not crates, keys + // Marketable Weapon ie Strange, Genuine, Vintage, paintkit + + // Cannot be Unusual + + if ( !pTestItem ) + { + return "#TF_CollectionCrafting_NoItem"; + } + + // No self mades or community items + uint32 eQuality = pTestItem->GetQuality(); + if ( eQuality == AE_SELFMADE || eQuality == AE_COMMUNITY ) + { + return "#TF_CollectionCrafting_NoUnusual"; + } + + // This is how we test for unusuals. Don't let unusuals be crafted + static CSchemaAttributeDefHandle pAttrDef_ParticleEffect( "attach particle effect" ); + if ( pTestItem->FindAttribute( pAttrDef_ParticleEffect ) ) + { + return "#TF_CollectionCrafting_NoUnusual"; + } + + static CSchemaAttributeDefHandle pAttrDef_TauntUnusualAttr( "on taunt attach particle index" ); + if ( pTestItem->FindAttribute( pAttrDef_TauntUnusualAttr ) ) + { + return "#TF_CollectionCrafting_NoUnusual"; + } + + // Invalid Items + static CSchemaAttributeDefHandle pAttrDef_CannotTransmute( "cannot_transmute" ); + if ( pTestItem->FindAttribute( pAttrDef_CannotTransmute ) ) + { + return "#TF_HalloweenOffering_Invalid"; + } + + static CSchemaAttributeDefHandle pAttrDef_CannotDelete( "cannot delete" ); + if ( pTestItem->FindAttribute( pAttrDef_CannotDelete ) ) + { + return "#TF_HalloweenOffering_Invalid"; + } + + const CEconItemDefinition *pItemDef = pTestItem->GetItemDefinition(); + if ( pItemDef == NULL ) + { + return "#TF_CollectionCrafting_NoItem"; + } + + if ( pTestItem->IsTemporaryItem() ) + { + return "#TF_CollectionCrafting_NoItem"; + } + + // If you are a taunt or a cosmetic you are allowed + if ( pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_MISC || pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_TAUNT ) + { + // do not 'medal' equip region items + if ( pTestItem->GetItemDefinition()->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "medal" ) ) + { + return "#TF_HalloweenOffering_Invalid"; + } + + return NULL; + } + + // Do not allow Crates + if ( ( pItemDef->GetCapabilities() & ITEM_CAP_DECODABLE ) != 0 ) + { + return "#TF_HalloweenOffering_Invalid"; + } + + // Cause of weird legacy items lets be explicit about what we allow + if ( pItemDef->IsTool() ) + { + // ignore everything that is not a paint can tool + const IEconTool *pEconTool = pItemDef->GetEconTool(); + if ( !pEconTool ) + return "#TF_HalloweenOffering_Invalid"; + + const char *pToolType = pEconTool->GetTypeName(); + + if ( !V_strcmp( pToolType, "paint_can" ) ) + return NULL; + else if ( !V_strcmp( pToolType, "strange_part" ) ) + return NULL; + else if ( !V_strcmp( pToolType, "name" ) ) + return NULL; + else if ( !V_strcmp( pToolType, "desc" ) ) + return NULL; + else if ( !V_strcmp( pToolType, "killstreakifier" ) ) + return NULL; + else if ( !V_strcmp( pToolType, "strangifier" ) ) + return NULL; + + // Not a tool we are allowing + return "#TF_HalloweenOffering_Invalid"; + } + + // Otherwise you must be a weapon or we won't allow + if ( pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_PRIMARY + || pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_SECONDARY + || pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_MELEE + || pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_BUILDING + || pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_PDA + || pTestItem->GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_PDA2 + ) { + // Must be strange, genuine, vintage, haunted or paintkit (ie a marketable weapon) + eQuality = pTestItem->GetQuality(); + if ( eQuality == AE_RARITY1 + || eQuality == AE_VINTAGE + || eQuality == AE_HAUNTED + || eQuality == AE_COLLECTORS + || eQuality == AE_PAINTKITWEAPON + ) { + return NULL; + } + + // Weapons with rarity are allowed + uint8 nRarity = pTestItem->GetItemDefinition()->GetRarity(); + if ( nRarity != k_unItemRarity_Any ) + { + return NULL; + } + + // Strange items. Dont just check for strange quality, actually check for a strange attribute. + // See if we've got any strange attributes. + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( pTestItem->FindAttribute( GetKillEaterAttr_Score( i ) ) ) + { + return NULL; + } + } + } + + return "#TF_HalloweenOffering_Invalid"; +} + +const char* GetCraftCommonStatClockInvalidReason( const class IEconItemInterface *pTestItem, const class IEconItemInterface *pSourceItem ) +{ + if ( !pTestItem ) + { + return "#TF_CollectionCrafting_NoItem"; + } + + // Not allowed to be crafted? + if ( !pTestItem->IsUsableInCrafting() ) + { + return "#TF_CollectionCrafting_NotCraftable"; + } + + // No self mades or community items + uint32 eQuality = pTestItem->GetQuality(); + if ( eQuality == AE_SELFMADE || eQuality == AE_COMMUNITY ) + return "#TF_CollectionCrafting_NoUnusual"; + + // This is how we test for unusuals. Don't let unusuals be crafted + static CSchemaAttributeDefHandle pAttrDef_ParticleEffect( "attach particle effect" ); + if ( pTestItem->FindAttribute( pAttrDef_ParticleEffect ) ) + return "#TF_CollectionCrafting_NoUnusual"; + + static CSchemaAttributeDefHandle pAttrDef_TauntUnusualAttr( "on taunt attach particle index" ); + if ( pTestItem->FindAttribute( pAttrDef_TauntUnusualAttr ) ) + return "#TF_CollectionCrafting_NoUnusual"; + + const CEconItemDefinition *pItemDef = pTestItem->GetItemDefinition(); + if ( pItemDef == NULL ) + return "#TF_CollectionCrafting_NoItem"; + + if ( pTestItem->IsTemporaryItem() ) + return "#TF_CollectionCrafting_NoItem"; + + // Strange items. Dont just check for strange quality, actually check for a strange attribute. + // See if we've got any strange attributes. + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( pTestItem->FindAttribute( GetKillEaterAttr_Score( i ) ) ) + { + return NULL; + } + } + + // Needs Rarity + uint8 nRarity = pTestItem->GetItemDefinition()->GetRarity(); + if ( nRarity != k_unItemRarity_Any && nRarity > 1 ) // do not allow default nor common rarity + { + return NULL; + } + + return "#TF_MannCoTrade_ItemInvalid"; +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +enum { kMaxCardUpgradesPerItem = 2 }; + +int GetMaxCardUpgradesPerItem() +{ + return kMaxCardUpgradesPerItem; +} + +const CEconItemAttributeDefinition *GetCardUpgradeForIndex( const IEconItemInterface *pItem, int i ) +{ + Assert( pItem ); + Assert( i >= 0 ); + Assert( i < kMaxCardUpgradesPerItem ); + + class CGetNthUserGeneratedAttributeIterator : public IEconItemUntypedAttributeIterator + { + public: + CGetNthUserGeneratedAttributeIterator( int iTargetIndex ) + : m_iCount( iTargetIndex ) + , m_pAttrDef( NULL ) + { + } + + virtual bool OnIterateAttributeValueUntyped( const CEconItemAttributeDefinition *pAttrDef ) OVERRIDE + { + if ( pAttrDef->GetUserGenerationType() != 0 && m_iCount-- == 0 ) + { + m_pAttrDef = pAttrDef; + return false; + } + + return true; + } + + const CEconItemAttributeDefinition *GetAttrDef() const { return m_pAttrDef; } + + private: + int m_iCount; + const CEconItemAttributeDefinition *m_pAttrDef; + }; + + CGetNthUserGeneratedAttributeIterator findNthAttrIterator( i ); + pItem->IterateAttributes( &findNthAttrIterator ); + + return findNthAttrIterator.GetAttrDef(); +} diff --git a/game/shared/econ/econ_item_constants.h b/game/shared/econ/econ_item_constants.h new file mode 100644 index 0000000..80518c7 --- /dev/null +++ b/game/shared/econ/econ_item_constants.h @@ -0,0 +1,965 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef ACTUAL_ECON_ITEM_CONSTANTS_H // ECON_ITEM_CONSTANTS_H is used by src/common/econ_item_view.h +#define ACTUAL_ECON_ITEM_CONSTANTS_H +#ifdef _WIN32 +#pragma once +#endif + +//============================================================================= +// To avoid #include dependency chains, this file should +// contain only constants that do not depend on other +// header files. +// This file is #included in cbase.h to allow schema compiles +// to use these constants to ensure correlation between +// code data structures and database entries +//============================================================================= + +typedef uint32 item_price_t; // this is the type that is used to hold currency values for transactions! don't change this without changing the relevant code/databases/etc. +typedef uint8 item_transaction_quantity_t; + +class CLocalizationProvider; + +enum { kLocalizedPriceSizeInChararacters = 64 }; + +//----------------------------------------------------------------------------- +// Econ Item testing +//----------------------------------------------------------------------------- +enum testitem_itemtypes_t +{ + TI_TYPE_UNKNOWN = -1, + + TI_TYPE_WEAPON = 0, + TI_TYPE_HEADGEAR, + TI_TYPE_MISC1, + TI_TYPE_MISC2, + + TI_TYPE_COUNT, +}; +#define TESTITEM_DEFINITIONS_BEGIN_AT 40000 + +//----------------------------------------------------------------------------- +// Type IDs for economy classes. These are part of the client-GC protocol and +// should not change if it can be helped +//----------------------------------------------------------------------------- +enum EEconTypeID +{ + k_EEconTypeItem =1, + k_EEconTypePlayerInfo =2, + k_EEconTypeClaimCode =3, + k_EEconTypeRecipe =5, + k_EEconTypeGameAccountClient =7, + k_EEconTypeGameAccount =8, + k_EEconTypeDuelSummary =19, + k_EEconTypeExperiment =20, + k_EEconTypeMapContribution =28, + k_EEconTypeGameServerAccount =29, + k_EEconTypeCoachRating =30, +// k_EEconTypeEquipInstance =31, // DEPRECATED + k_EEconTypeSelectedItemPreset =35, + k_EEconTypeItemPresetInstance =36, + k_EEconTypeGameAccountForGameServers =37, + k_EEConTypeWarData =38, + k_EEConTypeLadderData =39, + k_EEConTypeMatchResultPlayerInfo =40, + k_EEconTypeXPSource =41, + k_EEconTypeNotification =42, +}; + +//----------------------------------------------------------------------------- +// Actions for the ItemAudit table +//----------------------------------------------------------------------------- +// WARNING!!! Values stored in DB. Do not renumber! +enum EItemAction +{ + k_EItemActionInvalid = -1, + k_EItemActionGSCreate = 0, + k_EItemActionUnpurchase = 1, + k_EItemActionDelete = 2, + k_EItemActionAwardAchievement = 3, + k_EItemActionBanned = 4, + k_EItemActionQuantityChanged = 5, + k_EItemActionRestored = 6, + k_EItemActionAwardTime = 7, + k_EItemActionManualCreate = 8, + k_EItemActionDrop = 9, + k_EItemActionPickUp = 10, + k_EItemActionCraftDestroy = 11, + k_EItemActionCraftCreate = 12, + k_EItemActionLimitExceeded = 13, + k_EItemActionPurchase = 14, + k_EItemActionNameChanged_Add = 15, + k_EItemActionUnlockCrate_Add = 16, + k_EItemActionPaintItem_Add = 17, + k_EItemActionAutoGrantItem = 18, + k_EItemActionCrossGameAchievement = 19, + k_EItemActionAddItemToSocket_Add = 20, + k_EItemActionAddSocketToItem_Add = 21, + k_EItemActionRemoveSocketItem_Add = 22, + k_EItemActionCustomizeItemTexture_Add = 23, + k_EItemActionItemTraded_Add = 24, + k_EItemActionUseItem = 25, + k_EItemActionAwardGift_Receiver = 26, + k_EItemActionNameChanged_Remove = 27, + k_EItemActionUnlockCrate_Remove = 28, + k_EItemActionPaintItem_Remove = 29, + k_EItemActionAddItemToSocket_Remove = 30, + k_EItemActionAddSocketToItem_Remove = 31, + k_EItemActionRemoveSocketItem_Remove = 32, + k_EItemActionCustomizeItemTexture_Remove = 33, + k_EItemActionItemTraded_Remove = 34, + k_EItemActionUnpackItemBundle = 35, + k_EItemActionCreateItemFromBundle = 36, + k_EItemActionAwardStorePromotionItem = 37, + k_EItemActionConvertItem = 38, + k_EItemActionEarnedItem = 39, + k_EItemActionAwardGift_Giver = 40, + k_EItemActionRefundedItem = 41, + k_EItemActionAwardThirdPartyPromo = 42, + k_EItemActionRemoveItemName_Remove = 43, + k_EItemActionRemoveItemName_Add = 44, + k_EItemActionRemoveItemPaint_Remove = 45, + k_EItemActionRemoveItemPaint_Add = 46, + k_EItemActionHalloweenDrop = 47, + k_EItemActionSteamWorkshopContributor = 48, + k_EItemActionManualOwnershipChange = 49, // when we have bad bugs that corrupt item data and have to fix up rows in the DB by hand + k_EItemActionSupportDelete = 50, + k_EItemActionSupportCreatedByUndo = 51, + k_EItemActionSupportDeletedByUndo = 52, + k_EItemActionSupportQuantityChangedByUndo = 53, + k_EItemActionSupportRename_Add = 54, + k_EItemActionSupportRename_Remove = 55, + k_EItemActionSupportDescribe_Add = 56, + k_EItemActionSupportDescribe_Remove = 57, + + k_EItemActionStrangePartApply_Add = 58, + k_EItemActionStrangePartApply_Remove = 59, + k_EItemActionStrangeScoreReset_Add = 60, + k_EItemActionStrangeScoreReset_Remove = 61, + k_EItemActionStrangePartRemove_Add = 62, + k_EItemActionStrangePartRemove_Remove = 63, + + k_EItemActionSupportStrangify_Add = 64, + k_EItemActionSupportStrangify_Remove = 65, + + k_EItemActionUpgradeCardApply_Add = 66, + k_EItemActionUpgradeCardApply_Remove = 67, + k_EItemActionUpgradeCardRemove_Add = 68, + k_EItemActionUpgradeCardRemove_Remove = 69, + + k_EItemActionStrangeRestrictionApply_Add = 70, + k_EItemActionStrangeRestrictionApply_Remove = 71, + k_EItemActionTransmogrify_Add = 72, + k_EItemActionTransmogrify_Remove = 73, + k_EItemActionHalloweenSpellPageAdd_Add = 74, + k_EItemActionHalloweenSpellPageAdd_Remove = 75, + + k_EItemActionDev_ClientLootListRoll = 90, + + k_EItemActionGiftWrap_Add = 100, + k_EItemActionGiftWrap_Remove = 101, + k_EItemActionGiftDelivery_Add = 102, + k_EItemActionGiftDelivery_Remove = 103, + k_EItemActionGiftUnwrap_Add = 104, + k_EItemActionGiftUnwrap_Remove = 105, + k_EItemActionPackageItem = 106, + k_EItemActionPackageItem_Revoked = 107, + k_EItemActionHandleMapToken = 108, + k_EItemActionCafeOrSchoolItem_Remove = 109, + k_EItemActionVACBanned_Remove = 110, + k_EItemActionUpgradeThirdPartyPromo = 111, + k_EItemActionExpired = 112, + k_EItemActionTradeRollback_Add = 113, + k_EItemActionTradeRollback_Remove = 114, + k_EItemActionCDKeyGrant = 115, + k_EItemActionCDKeyRevoke = 116, + k_EItemActionWeddingRing_Add = 117, + k_EItemActionWeddingRing_Remove = 118, + k_EItemActionWeddingRing_AddPartner = 119, + k_EItemActionEconSetUnowned = 120, + k_EItemActionEconSetOwned = 121, + k_EItemActionStrangifyItem_Add = 122, + k_EItemActionStrangifyItem_Remove = 123, + k_EItemActionConsumeItem_Consume_ToolRemove = 124, + k_EItemActionConsumeItem_Consume_ToolAdd = 125, + k_EItemActionConsumeItem_Consume_InputRemove = 126, + k_EItemActionConsumeItem_Complete_OutputAdd = 127, + k_EItemActionConsumeItem_Complete_ToolRemove = 128, + k_EItemActionItemEaterRecharge_Add = 129, + k_EItemActionItemEaterRecharge_Remove = 130, + + k_EItemActionRemoveItemCraftIndex_Remove = 150, + k_EItemActionRemoveItemCraftIndex_Add = 151, + k_EItemActionRemoveItemMakersMark_Remove = 152, // early versions of this will be in the database as 150 + k_EItemActionRemoveItemMakersMark_Add = 153, // early versions of this will be in the database as 151 because I am a terrible person + + k_EItemActionCollectItem_CollectedItem = 154, + k_EItemActionCollectItem_UpdateCollection = 155, + k_EItemActionCollectItem_RemoveCollection = 156, + k_EItemActionCollectItem_RedeemCollectionReward = 157, + + k_EItemActionPreviewItem_BeginPreviewPeriod = 158, + k_EItemActionPreviewItem_EndPreviewPeriodExpired = 159, + k_EItemActionPreviewItem_EndPreviewPeriodItemBought = 160, + + k_EItemActionPeriodicScoreReward_Add = 170, + k_EItemActionPeriodicScoreReward_Remove = 171, + + k_EItemActionMvM_ChallengeCompleted_RemoveTicket = 180, // we completed a challenge and consumed this ticket as the cost + k_EItemActionMvM_ChallengeCompleted_GrantBadge = 181, // we completed a challenge and granted the player a badge because they didn't have one + k_EItemActionMvM_ChallengeCompleted_UpdateBadgeStamps_Remove = 182, // we completed a challenge and we're crossing an entry off our badge checklist (this may also reset the badge back down to empty if this was the last line item) + k_EItemActionMvM_ChallengeCompleted_UpdateBadgeStamps_Add = 183, // (other half of the above) + k_EItemActionMvM_ChallengeCompleted_GrantMissionCompletionLoot = 184, // we completed a mission in MvM + k_EItemActionMvM_RemoveSquadSurplusVoucher = 185, + k_EItemActionMvM_AwardSquadSurplus_Receiver = 186, + k_EItemActionMvM_AwardSquadSurplus_Giver = 187, + k_EItemActionMvM_ChallengeCompleted_GrantTourCompletionLoot = 188, // we completed a full tour in MvM + k_EItemActionMvM_AwardHelpANoobBonus_Helper = 189, + + k_EItemActionHalloween_UpdateMerasmusLootLevel_Add = 200, // set the level of the merasmus loot + k_EItemActionHalloween_UpdateMerasmusLootLevel_Remove = 201, + + k_EItemActionRemoveItemKillStreak_Remove = 202, + k_EItemActionRemoveItemKillStreak_Add = 203, + + k_EItemActionSupportAddOrModifyAttribute_Remove = 204, + k_EItemActionSupportAddOrModifyAttribute_Add = 205, + + k_EItemActionSpyVsEngyWar_JoinedWar = 206, + + k_EItemAction_UpdateDuckBadgeLevel_Add = 207, + k_EItemAction_UpdateDuckBadgeLevel_Remove = 208, + + k_EItemAction_QuestDrop = 209, + + k_EItemAction_OperationPass_Add = 210, + + k_EItemActionMarket_Add = 211, + k_EItemActionMarket_Remove = 212, + + k_EItemAction_QuestComplete_Reward = 213, + k_EItemAction_QuestComplete_Remove = 214, + + k_EItemAction_QuestLoaner_Add = 215, + k_EItemActionStrangeCountTransfer_Add = 216, + k_EItemActionStrangeCountTransfer_Remove = 217, + + k_EItemActionCraftCollectionUpgrade_Add = 218, + k_EItemActionCraftCollectionUpgrade_Remove = 219, + + k_EItemActionCraftHalloweenOffering_Add = 220, + k_EItemActionCraftHalloweenOffering_Remove = 221, + + k_EItemActionRemoveItemGiftedBy_Remove = 222, + k_EItemActionRemoveItemGiftedBy_Add = 223, + + k_EItemActionAddParticleVerticalAttr_Remove = 224, + k_EItemActionAddParticleVerticalAttr_Add = 225, + + k_EItemActionAddParticleUseHeadOriginAttr_Remove = 226, + k_EItemActionAddParticleUseHeadOriginAttr_Add = 227, + + k_EItemActionRemoveItemDynamicAttr_Add = 228, + k_EItemActionRemoveItemDynamicAttr_Remove = 229, + + k_EItemActionCraftStatClockTradeUp_Add = 230, + k_EItemActionCraftStatClockTradeUp_Remove = 231, + + k_EItemActionViralCompetitiveBetaPass_Drop = 232, + + k_EItemActionSupportDeleteAttribute_Remove = 233, + k_EItemActionSupportDeleteAttribute_Add = 234, + + // Let's be consistent with the underscores please. + // k_EItemActionYourNewAction, not k_EItemAction_YourNewAction + // Yes, it matters. See PchLocalizedNameFromEItemAction for why. +}; +extern const char *PchNameFromEItemAction( EItemAction eAction ); +extern const char *PchNameFromEItemActionUnsafe( EItemAction eAction ); + +extern bool BIsActionCreative( EItemAction ); +extern bool BIsActionDestructive( EItemAction ); + +enum EItemActionMissingBehavior { kEItemAction_FriendlyNameLookup_ReturnNULLIfMissing, kEItemAction_FriendlyNameLookup_ReturnDummyStringIfMissing }; +extern const char *PchFriendlyNameFromEItemAction( EItemAction eAction, EItemActionMissingBehavior eMissingBehavior ); +extern const char *PchLocalizedNameFromEItemAction( EItemAction eAction, CLocalizationProvider &localizationProvider ); + +//----------------------------------------------------------------------------- +// Purpose: Used to pass audit actions to asset servers for SetUnowned and +// SetOwned methods. +//----------------------------------------------------------------------------- +enum EEconOwnershipAction +{ + k_EEconOwnershipAction_Invalid = 0, + + k_EEconOwnershipAction_TradeBase = 100, + k_EEconOwnershipAction_TradeCommit = 101, // precommit and docommit step of a trade. Reference is trade ID + k_EEconOwnershipAction_TradeRollback = 102, // cancelcommit and rollbackcommit step of a trade. Reference is trade ID +}; + +// old +enum eEconItemFlags_Deprecated +{ + kDeprecated_EconItemFlag_AchievementGrantedItem = 1 << 0, + kDeprecated_EconItemFlag_CannotTrade = 1 << 1, + kDeprecated_EconItemFlag_Purchased = 1 << 2, + kDeprecated_EconItemFlag_CannotBeUsedInCrafting = 1 << 3, + kDeprecated_EconItemFlag_Promotion = 1 << 4, +}; + +//----------------------------------------------------------------------------- +// Periodic score events +//----------------------------------------------------------------------------- +enum eEconPeriodicScoreEvents +{ + kPeriodicScoreEvent_GiftsDistributed = 0, + kPeriodicScoreEvent_DuelsWon = 1, + kPeriodicScoreEvent_MapStampsPurchased = 2, +}; + +//----------------------------------------------------------------------------- +// Flags for CEconItem +//----------------------------------------------------------------------------- +// WARNING!!! Values stored in DB. DO NOT CHANGE EXISTING VALUES. Add values to the end. +enum eEconItemFlags +{ + kEconItemFlag_CannotTrade = 1 << 0, + kEconItemFlag_CannotBeUsedInCrafting = 1 << 1, + kEconItemFlag_CanBeTradedByFreeAccounts = 1 << 2, + kEconItemFlag_NonEconomy = 1 << 3, // used for items that are meant to not interact in the economy -- these can't be traded, gift-wrapped, crafted, etc. + kEconItemFlag_PurchasedAfterStoreCraftabilityChanges2012 = 1 << 4, // cosmetic items coming from the store are now usable in crafting; this flag is set on all items purchased from the store after this change was made + +#ifdef CLIENT_DLL +#ifdef TF_CLIENT_DLL + kEconItemFlagClient_ForceBlueTeam = 1 << 5, +#endif // TF_CLIENT_DLL + kEconItemFlagClient_StoreItem = 1 << 6, + kEconItemFlagClient_Preview = 1 << 7, // only set on the client; means "this item is being previewed" +#endif // CLIENT_DLL + + // combination of the above flags used in code + kEconItemFlags_CheckFlags_AllGCFlags = kEconItemFlag_CannotTrade | kEconItemFlag_CannotBeUsedInCrafting | kEconItemFlag_CanBeTradedByFreeAccounts | kEconItemFlag_NonEconomy | kEconItemFlag_PurchasedAfterStoreCraftabilityChanges2012, +}; + +//----------------------------------------------------------------------------- +// Origin for an item for CEconItem +//----------------------------------------------------------------------------- +// WARNING!!! Values stored in DB. DO NOT CHANGE EXISTING VALUES. Add values to the end. +enum eEconItemOrigin +{ + kEconItemOrigin_Invalid = -1, // should never be stored in the DB! used to indicate "invalid" for in-memory objects only + + kEconItemOrigin_Drop = 0, + kEconItemOrigin_Achievement, + kEconItemOrigin_Purchased, + kEconItemOrigin_Traded, + kEconItemOrigin_Crafted, + kEconItemOrigin_StorePromotion, + kEconItemOrigin_Gifted, + kEconItemOrigin_SupportGranted, + kEconItemOrigin_FoundInCrate, + kEconItemOrigin_Earned, + kEconItemOrigin_ThirdPartyPromotion, + kEconItemOrigin_GiftWrapped, + kEconItemOrigin_HalloweenDrop, + kEconItemOrigin_PackageItem, + kEconItemOrigin_Foreign, + kEconItemOrigin_CDKey, + kEconItemOrigin_CollectionReward, + kEconItemOrigin_PreviewItem, + kEconItemOrigin_SteamWorkshopContribution, + kEconItemOrigin_PeriodicScoreReward, + kEconItemOrigin_MvMMissionCompletionReward, // includes loot from both "mission completed" and "tour completed" events + kEconItemOrigin_MvMSquadSurplusReward, + kEconItemOrigin_RecipeOutput, + kEconItemOrigin_QuestDrop, + kEconItemOrigin_QuestLoanerItem, + kEconItemOrigin_TradeUp, + kEconItemOrigin_ViralCompetitiveBetaPassSpread, + + kEconItemOrigin_Max, +}; +extern const char *PchNameFromeEconItemOrigin( eEconItemOrigin eOrigin ); + +// The Steam backend representation of a unique item index +typedef uint64 itemid_t; +typedef uint16 item_definition_index_t; +typedef uint16 attrib_definition_index_t; +typedef uint32 attrib_value_t; +typedef uint32 operation_definition_index_t; +typedef uint8 war_definition_index_t; +typedef uint8 war_side_t; + +// Misc typedefs for clarity. +typedef uint32 equip_region_mask_t; +typedef uint8 style_index_t; + +const uint64 INVALID_ITEM_ID = (itemid_t)-1; +const item_definition_index_t INVALID_ITEM_DEF_INDEX = ((item_definition_index_t)-1); +const attrib_definition_index_t INVALID_ATTRIB_DEF_INDEX= ((attrib_definition_index_t)-1); +const war_definition_index_t INVALID_WAR_DEF_INDEX = ((war_definition_index_t)-1); +const war_side_t INVALID_WAR_SIDE = ((war_side_t)-1); +// Hard code the pyro/heavy stuff. Must be in sync with the schema. +const war_definition_index_t PYRO_VS_HEAVY_WAR_DEF_INDEX= ((war_definition_index_t)0); +const war_side_t PYRO_VS_HEAVY_WAR_SIDE_HEAVY = ((war_side_t)0); +const war_side_t PYRO_VS_HEAVY_WAR_SIDE_PYRO = ((war_side_t)1); + +//----------------------------------------------------------------------------- + +// Standard/default backpack size +#define DEFAULT_NUM_BACKPACK_SLOTS 300 +#define DEFAULT_NUM_BACKPACK_SLOTS_FREE_TRIAL_ACCOUNT 50 +#define MAX_NUM_BACKPACK_SLOTS 2000 + +// Current item level range +#define MIN_ITEM_LEVEL 0 +#define MAX_ITEM_LEVEL 100 + +// Maximum number of attributes allowed on a single item +#define MAX_ATTRIBUTES_PER_ITEM 15 +// The maximum length of a single attribute's description +// divide by locchar_t, so we can ensure 192 bytes, whether that's 128 wchars on client or 256 utf-8 bytes on gc +#define MAX_ATTRIBUTE_DESCRIPTION_LENGTH ( 256 / sizeof( locchar_t ) ) + +// The maximum length of an item's name +#define MAX_ITEM_NAME_LENGTH 128 +#define MAX_ITEM_DESC_LENGTH 256 +// The maximum length of an item description. (Extra +1 line is for the base item type line) +#define MAX_ITEM_DESCRIPTION_LENGTH ((MAX_ATTRIBUTES_PER_ITEM+1) * MAX_ATTRIBUTE_DESCRIPTION_LENGTH) + +// For custom user-naming of econ items. +#define MAX_ITEM_CUSTOM_NAME_LENGTH 40 +#define MAX_ITEM_CUSTOM_NAME_DATABASE_SIZE ((4 * MAX_ITEM_CUSTOM_NAME_LENGTH) + 1) // Ensures we can store MAX_ITEM_CUSTOM_NAME_LENGTH + // characters worth of obscure unicode characters in UTF8 +#define MAX_ITEM_CUSTOM_DESC_LENGTH 80 +#define MAX_ITEM_CUSTOM_DESC_DATABASE_SIZE ((4 * MAX_ITEM_CUSTOM_DESC_LENGTH) + 1) + +#define MAX_KILLCAM_MESSAGE_LENGTH 40 +#define MAX_KILLCAM_MESSAGE_DATABASE_SIZE ((4 * MAX_KILLCAM_MESSAGE_LENGTH) + 1) + +// max length in the DB for claim codes +#define MAX_CLAIM_CODE_LENGTH 128 + +// The item definition index reserved for the preview item +#define PREVIEW_ITEM_DEFINITION_INDEX (item_definition_index_t)-1 + +// The number of items to work on in a job before checking if a yield is necessary +#define MAX_ITEMS_BEFORE_YIELD 50 + +// TF team-color paints (moved from econ_item_view.h) +#define RGB_INT_RED 12073019 +#define RGB_INT_BLUE 5801378 + +// Custom textures +const int k_nCustomImageSize = 128; +const int k_nMaxCustomImageFileSize = k_nCustomImageSize*k_nCustomImageSize*4 + 4*1024; // Is this about right? + +//----------------------------------------------------------------------------- +// Purpose: Quality types of items +//----------------------------------------------------------------------------- +typedef int32 entityquality_t; +enum EEconItemQuality +{ + AE_UNDEFINED = -1, + + AE_NORMAL = 0, + AE_RARITY1 = 1, // Genuine + AE_RARITY2 = 2, // Customized (unused) + AE_VINTAGE = 3, // Vintage has to stay at 3 for backwards compatibility + AE_RARITY3, // Artisan + AE_UNUSUAL, // Unusual + AE_UNIQUE, + AE_COMMUNITY, + AE_DEVELOPER, + AE_SELFMADE, + AE_CUSTOMIZED, // (unused) + AE_STRANGE, + AE_COMPLETED, + AE_HAUNTED, + AE_COLLECTORS, + AE_PAINTKITWEAPON, + + AE_RARITY_DEFAULT, + AE_RARITY_COMMON, + AE_RARITY_UNCOMMON, + AE_RARITY_RARE, + AE_RARITY_MYTHICAL, + AE_RARITY_LEGENDARY, + AE_RARITY_ANCIENT, + + AE_MAX_TYPES, + AE_DEPRECATED_UNIQUE = 3, +}; + + +//----------------------------------------------------------------------------- +// Purpose: colors used in the display of attributes +//----------------------------------------------------------------------------- +enum attrib_colors_t +{ + ATTRIB_COL_LEVEL = 0, + ATTRIB_COL_NEUTRAL, + ATTRIB_COL_POSITIVE, + ATTRIB_COL_NEGATIVE, + ATTRIB_COL_ITEMSET_NAME, + ATTRIB_COL_ITEMSET_EQUIPPED, + ATTRIB_COL_ITEMSET_MISSING, + ATTRIB_COL_BUNDLE_ITEM, + ATTRIB_COL_LIMITED_USE, + ATTRIB_COL_component_flags, + ATTRIB_COL_LIMITED_QUANTITY, + + ATTRIB_COL_RARITY_DEFAULT, + ATTRIB_COL_RARITY_COMMON, + ATTRIB_COL_RARITY_UNCOMMON, + ATTRIB_COL_RARITY_RARE, + ATTRIB_COL_RARITY_MYTHICAL, + ATTRIB_COL_RARITY_LEGENDARY, + ATTRIB_COL_RARITY_ANCIENT, + ATTRIB_COL_RARITY_IMMORTAL, + ATTRIB_COL_RARITY_ARCANA, + + ATTRIB_COL_STRANGE, + ATTRIB_COL_UNUSUAL, + + NUM_ATTRIB_COLORS, +}; + + +#define AE_USE_SCRIPT_VALUE 9999 // Can't be -1, due to unsigned ints used on the backend + +const char *EconQuality_GetQualityString( EEconItemQuality eQuality ); +const char *EconQuality_GetColorString( EEconItemQuality eQuality ); +const char *EconQuality_GetLocalizationString( EEconItemQuality eQuality ); +EEconItemQuality EconQuality_GetQualityFromString( const char* pszQuality ); + +// Sort order for rarities +int EconQuality_GetRarityScore( EEconItemQuality eQuality ); + +extern attrib_colors_t GetAttribColorIndexForName( const char* pszName ); +extern const char *GetColorNameForAttribColor( attrib_colors_t unAttribColor ); +extern const char *GetHexColorForAttribColor( attrib_colors_t unAttribColor ); + +// Utility function that'll get you an item quality from a string +entityquality_t GetItemQualityFromString( const char *sQuality ); + +enum recipecategories_t +{ + RECIPE_CATEGORY_CRAFTINGITEMS = 0, + RECIPE_CATEGORY_COMMONITEMS, + RECIPE_CATEGORY_RAREITEMS, + RECIPE_CATEGORY_SPECIAL, + + NUM_RECIPE_CATEGORIES +}; +extern const char *g_szRecipeCategoryStrings[NUM_RECIPE_CATEGORIES]; + +//----------------------------------------------------------------------------- +// Kill eater support. +// Strange counters and strange parts +//----------------------------------------------------------------------------- +#if defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) +enum kill_eater_event_t +{ + kKillEaterEvent_PlayerKill = 0, // default; items with no event type specified use this + kKillEaterEvent_UberActivated, + kKillEaterEvent_PlayerKillAssist, + kKillEaterEvent_PlayerKillsBySentry, // your sentry you built with this item killed someone + kKillEaterEvent_PeeVictims, // this game is great + kKillEaterEvent_BackstabAbsorbed, // you're a sniper and you got a spy to stab your Razorback + kKillEaterEvent_HeadsTaken, // this also tracks kills but with different flavor text + kKillEaterEvent_Humiliations, // fish kills! + kKillEaterEvent_GiftsGiven, // number of gifts given + kKillEaterEvent_DeathsFeigned, // number of deaths successfully feigned with the Dead Ringer + kKillEaterEvent_ScoutKill, // (part) + kKillEaterEvent_SniperKill, // (part) + kKillEaterEvent_SoldierKill, // (part) + kKillEaterEvent_DemomanKill, // (part) + kKillEaterEvent_HeavyKill, // (part) + kKillEaterEvent_PyroKill, // (part) + kKillEaterEvent_SpyKill, // (part) + kKillEaterEvent_EngineerKill, // (part) + kKillEaterEvent_MedicKill, // (part) + kKillEaterEvent_BuildingDestroyed, // (part) + kKillEaterEvent_ProjectileReflect, // (part) + kKillEaterEvent_HeadshotKill, // (part) + kKillEaterEvent_AirborneEnemyKill, // (part) (enemy is in the air when they die) + kKillEaterEvent_GibKill, // (part) + kKillEaterEvent_BuildingSapped, // a sapper was doing damage to this building while it was destroyed + kKillEaterEvent_PlayerTickle, // we used our comedy holiday gloves to force someone else to laugh + kKillEaterEvent_PlayerKillByBootStomp, // we killed a player by transferring our falling damage onto them + kKillEaterEvent_PlayerKillDuringFullMoon, // (part) we killed a player during the full moon holiday event (GC-updated) + kKillEaterEvent_PlayerKillStartDomination, // (part) we killed a player and this kill was enough to start our domination of them + kKillEaterEvent_PlayerKillAlreadyDominated, // (part) we killed a player with this weapon that we were already dominating + kKillEaterEvent_PlayerKillRevenge, // (part) we killed a player with this weapon when that player was dominating us + kKillEaterEvent_PlayerKillPosthumous, // (part) we killed a player after we were already dead (afterburn, stray rocket, etc.) + kKillEaterEvent_BurningAllyExtinguished, // (part) we used urine/milk/flamethrower/whatever to put out the fire on an ally that was burning + kKillEaterEvent_PlayerKillCritical, // (part) we killed a player with a shot that was a critical + kKillEaterEvent_PlayerKillWhileExplosiveJumping, // (part) we killed a player while we were rocket/sticky-jumping + kKillEaterEvent_PlayerKillFriend, // (part) we killed a player who is a Steam friend (GC-updated) + kKillEaterEvent_SapperDestroyed, // (part) we destroyed a sapper that was on a friendly building + kKillEaterEvent_InvisibleSpiesKilled, // (part) we killed an invisible spy + kKillEaterEvent_MedicsWithFullUberKilled, // (part) we killed a fully ubered medic + kKillEaterEvent_RobotsDestroyed, // (part) we killed a robot in MvM + kKillEaterEvent_MinibossRobotsDestroyed, // (part) we killed a miniboss robot in MvM + kKillEaterEvent_RobotsDestroyedAfterPenetration, // (part) we killed a robot with a shot that had already penetrated another robot + kKillEaterEvent_RobotHeadshotKills, // (part) like kKillEaterEvent_HeadshotKill, but only for robots + kKillEaterEvent_RobotsSlowed, // (part) we hit some robots with Jarate and now they're slow + kKillEaterEvent_KillWhileLowHealth, // (part) we killed someone while we had <10% max health + kKillEaterEvent_HalloweenKill, // (part) we killed someone during the Halloween holiday + kKillEaterEvent_HalloweenKillRobot, // (part) we killed a robot in MvM during the Halloween holiday + kKillEaterEvent_DefenderKill, // (part) we killed someone carrying the intel, pushing the cart, or capping a point + kKillEaterEvent_UnderwaterKill, // (part) we killed someone who was completely submerged + kKillEaterEvent_KillWhileUbercharged, // (part) we killed someone while we were invulnerable + kKillEaterEvent_FoodEaten, // We ate our food + kKillEaterEvent_BannersDeployed, // We deployed a banner buff + kKillEaterEvent_NEGATIVE_SniperShotsMissed, // (part) we shot our sniper rifle and didnt hit anything + kKillEaterEvent_NEGATIVE_UbersDropped, // (part) we died with a full ubercharge + kKillEaterEvent_NEGATIVE_DeathsWhileCarryingBuilding, // (part) we died while carrying a building + kKillEaterEvent_NEGATIVE_DeathsFromCratering, // (part) we died from cratering + kKillEaterEvent_NEGATIVE_DeathsFromEnvironment, // (part) we died from environmental damage + kKillEaterEvent_NEGATIVE_Deaths, // (part) we died :( + kKillEaterEvent_TimeCloaked, // Time we are cloaked + kKillEaterEvent_HealingProvided, // Health Provided to Allies + kKillEaterEvent_TeleportsProvided, // Teleports Provided to Allies + kKillEaterEvent_TanksDestroyed, // (part) we dealt the killing blow to a tank in MvM + kKillEaterEvent_LongDistanceKill, // (part) we dealt the killing blow (while alive) from far away + kKillEaterEvent_UniqueEvent__KilledAccountWithItem, // (part) (unique event) how many individual accounts have we killed? +// kKillEaterEvent_UniqueEvent__PlayedWithAccountIDWhileWearingItem, // (part) (unique event) how many individual accounts have we played a round with? + kKillEaterEvent_PointsScored, // How many score points we've accumulated + kKillEaterEvent_DoubleDonks, // Double-Donks scored with the loose cannon + kKillEaterEvent_TeammatesWhipped, // Whipped Teammates with the Disciplinary Action + kKillEaterEvent_VictoryTimeKill, // Kills while in Victory / Bonus Time + kKillEaterEvent_RobotScoutKill, // (part) + kKillEaterEvent_RobotSniperKill, // (part) Not yet shipped + kKillEaterEvent_RobotSoldierKill, // (part) Not yet shipped + kKillEaterEvent_RobotDemomanKill, // (part) Not yet shipped + kKillEaterEvent_RobotHeavyKill, // (part) Not yet shipped + kKillEaterEvent_RobotPyroKill, // (part) Not yet shipped + kKillEaterEvent_RobotSpyKill, // (part) + kKillEaterEvent_RobotEngineerKill, // (part) Not yet shipped + kKillEaterEvent_RobotMedicKill, // (part) Not yet shipped + kKillEaterEvent_TauntKill, // Taunt Kills + kKillEaterEvent_PlayersWearingUnusualKill, // (part) we killed someone wearing an unusual hat (!) + kKillEaterEvent_BurningEnemyKill, // (part) we killed someone who was on fire up until they died + kKillEaterEvent_KillstreaksEnded, // (part) we killed someone who was on a killstreak + kKillEaterEvent_KillcamTaunts, // (cosmetic part) we appeared wearing this item in the killcam taunting + kKillEaterEvent_DamageDealt, // (part) we have dealt this much damage to people + kKillEaterEvent_FiresSurvived, // (cosmetic part) we were lit on fire wearing this item and then the fire went out and we were still alive + kKillEaterEvent_AllyHealingDone, // (part) we have healed this much (directly, so doesn't count Mad Milk, etc. because we lose the item pointer at some point); also ignores self heal (ie., Concheror buff, MvM upgrades) + kKillEaterEvent_PointBlankKills, // (part) we killed someone while standing right next to them + kKillEaterEvent_PlayerKillsByManualControlOfSentry, // Kills from wrangled a sentry + kKillEaterEvent_CosmeticKills, // (cosmetic part) kills + kKillEaterEvent_FullHealthKills, // (part) Kills while at fullhealth + kKillEaterEvent_TauntingPlayerKills, // (part) Taunting Player Kills + kKillEaterEvent_Halloween_OverworldKills, + kKillEaterEvent_Halloween_UnderworldKills, + kKillEaterEvent_Halloween_MinigamesWon, + kKillEaterEvent_NonCritKills, // part kills that are not crit or mini crit + kKillEaterEvent_PlayersHit, // part + kKillEaterEvent_CosmeticAssists, // Cosmetic part + kKillEaterEvent_CosmeticOperationContractsCompleted, // Operation Stat Tracker + kKillEaterEvent_CosmeticOperationKills, // Operation Stat Tracker + kKillEaterEvent_CosmeticOperationContractsPoints, + kKillEaterEvent_CosmeticOperationBonusPoints, + kKillEaterEvent_TauntsPerformed, // Strange Taunts + kKillEaterEvent_InvasionKills, // Kills During Invasion Event. Locked after Operation + kKillEaterEvent_InvasionKillsOnMap01, + kKillEaterEvent_InvasionKillsOnMap02, + kKillEaterEvent_InvasionKillsOnMap03, + kKillEaterEvent_InvasionKillsOnMap04, + kKillEaterEvent_HalloweenSouls, // Halloween + kKillEaterEvent_HalloweenContractsCompleted, + kKillEaterEvent_HalloweenOfferings, + kKillEaterEvent_PowerupBottlesUsed, + + // NEW ENTRIES MUST BE ADDED AT THE BOTTOM +}; +#else + // projects that actually want to implement kill-eater functionality will want to put their list somewhere around here, + // but unfortunately the base code relies on this specific definition being entry 0 + static const uint32 kKillEaterEvent_PlayerKill = 0; +#endif // defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) + +enum strange_event_restriction_t +{ + kStrangeEventRestriction_None = 0, // default -- unassigned, all events pass + kStrangeEventRestriction_VictimSteamAccount, // the victim must have a specific Steam ID +#if defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) + kStrangeEventRestriction_Map, // must be playing on a certain map when the event takes place + kStrangeEventRestriction_Competitive, // must be playing in a competitive game +#endif // defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) + kStrangeEventRestrictionCount +}; + +// Ugh -- these are shared between the GC and the client. Maybe #define is slightly better than +// magic string literals? +#define KILL_EATER_RANK_LEVEL_BLOCK_NAME "KillEaterRank" + +#ifdef TF_DLL + class CTFWeaponBase *GetKilleaterWeaponFromDamageInfo( const class CTakeDamageInfo *pInfo ); + // A specific CEconEntity caused a kill eater event to happen. For example, a weapon might cause a + // player kill event so we want to update the stats for that specific weapon. + void EconEntity_OnOwnerKillEaterEvent( class CEconEntity *pEconEntity, class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + void EconItemInterface_OnOwnerKillEaterEvent( class IEconItemInterface *pEconEntity, class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + void EconEntity_OnOwnerKillEaterEventNoPartner( class CEconEntity *pEconEntity, class CTFPlayer *pOwner, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + void EconItemInterface_OnOwnerKillEaterEventNoPartner( class IEconItemInterface *pEconEntity, class CTFPlayer *pOwner, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + + void HatAndMiscEconEntities_OnOwnerKillEaterEvent( class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + void HatAndMiscEconEntities_OnOwnerKillEaterEventNoParter( class CTFPlayer *pOwner, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + + void EconEntity_NonEquippedItemKillTracking_NoPartner( class CTFPlayer *pOwner, item_definition_index_t iDefIndex, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + void EconEntity_NonEquippedItemKillTracking_NoPartnerBatched( class CTFPlayer *pOwner, item_definition_index_t iDefIndex, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + // Batching system for frequent events (ie., damage dealing). The game server will flush all batches + // at specific time intervals and send up one composite message to avoid flooding the GC. Batched + // messages will only work correctly for types that support increment values. Because the game client + // and game server don't know which event types support increment values we can't do any checking + // before we send the message. + void EconEntity_OnOwnerKillEaterEvent_Batched( class CEconEntity *pEconEntity, class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + void EconItemInterface_OnOwnerKillEaterEvent_Batched( class IEconItemInterface *pEconEntity, class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue = 1 ); + void KillEaterEvents_FlushBatches(); +#endif // TF_DLL + +int GetKillEaterAttrCount(); +int GetKillEaterAttrCount_UserCustomizable(); +const class CEconItemAttributeDefinition *GetKillEaterAttr_Score( int i ); +const class CEconItemAttributeDefinition *GetKillEaterAttr_Type( int i ); +const class CEconItemAttributeDefinition *GetKillEaterAttr_Restriction( int i ); +const class CEconItemAttributeDefinition *GetKillEaterAttr_RestrictionValue( int i ); +bool GetKillEaterAttr_IsUserCustomizable( int i ); +bool GetKilleaterValueByEvent( const class IEconItemInterface* pItem, const kill_eater_event_t& EEventType, uint32& value ); +bool BIsItemStrange( const class IEconItemInterface *pItem ); + +const int COLLECTION_CRAFTING_ITEM_COUNT = 10; +const char* GetCollectionCraftingInvalidReason( const class IEconItemInterface *pTestItem, const class IEconItemInterface *pSourceItem ); + +const int HALLOWEEN_OFFERING_ITEM_COUNT = 3; +const char* GetHalloweenOfferingInvalidReason( const class IEconItemInterface *pTestItem, const class IEconItemInterface *pSourceItem ); + +const int CRAFT_COMMON_STATCLOCK_ITEM_COUNT = 5; +const char* GetCraftCommonStatClockInvalidReason( const class IEconItemInterface *pTestItem, const class IEconItemInterface *pSourceItem ); + +int GetMaxCardUpgradesPerItem(); +const class CEconItemAttributeDefinition *GetCardUpgradeForIndex( const class IEconItemInterface *pItem, int i ); + +#define GUARANTEED_OUTPUT (1<<0) +#define GUARANTEED_INPUT (1<<1) + +#define DYNAMIC_RECIPE_FLAG_IS_OUTPUT (1<<0) +#define DYNAMIC_RECIPE_FLAG_IS_UNTRADABLE (1<<1) +#define DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET (1<<2) +#define DYNAMIC_RECIPE_FLAG_PARAM_QUALITY_SET (1<<3) +#define DYNAMIC_RECIPE_FLAG_PARAM_ATTRIBUTE_SET_ALL (1<<4) +#define DYNAMIC_RECIPE_FLAG_PARAM_ATTRIBUTE_SET_ANY (1<<5) + +#define k_ObjectiveTrackerFlag_OwnerClient (1<<0) +#define k_ObjectiveTrackerFlag_Servers (1<<1) +#define k_ObjectiveTrackerFlag_AllClients (1<<2) + +#define k_ObjectiveTrackerFlag_ClientAndServer ( k_ObjectiveTrackerFlag_OwnerClient | k_ObjectiveTrackerFlag_Servers ) + +const float k_MaxElapsedQuestReportTime = 10.f; + +//=============================================================================================================== +// POSITION HANDLING +//=============================================================================================================== +// TF Inventory Position cracking + +// REALLY OLD FORMAT (??): +// We store a bag index in the highbits of the inventory position. +// The lowbit stores the position of the item within the bag. +// +// LESS OLD FORMAT (up through July, 2011): +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Bits 17-26 are a bool for whether the item is equipped in the matching class. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Bit 32 is 1, to note the new format. +// +// CURRENT FORMAT: +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Equipped state is stored elsewhere. +// This is the only format that should exist on clients. +// Note (1/15/2013) For backwards compatibility, if the value is 0 item is considered unacknowledged too + + +enum unacknowledged_item_inventory_positions_t +{ + UNACK_ITEM_UNKNOWN = 0, + UNACK_ITEM_DROPPED = 1, + UNACK_ITEM_CRAFTED, + UNACK_ITEM_TRADED, + UNACK_ITEM_PURCHASED, + UNACK_ITEM_FOUND_IN_CRATE, + UNACK_ITEM_GIFTED, + UNACK_ITEM_SUPPORT, + UNACK_ITEM_PROMOTION, + UNACK_ITEM_EARNED, + UNACK_ITEM_REFUNDED, + UNACK_ITEM_GIFT_WRAPPED, + UNACK_ITEM_FOREIGN, + UNACK_ITEM_COLLECTION_REWARD, + UNACK_ITEM_PREVIEW_ITEM, + UNACK_ITEM_PREVIEW_ITEM_PURCHASED, + UNACK_ITEM_PERIODIC_SCORE_REWARD, + UNACK_ITEM_MVM_MISSION_COMPLETION_REWARD, + UNACK_ITEM_MVM_SQUAD_SURPLUS_REWARD, + UNACK_ITEM_FOUND_HOLIDAY_GIFT, + UNACK_ITEM_COMMUNITY_MARKET_PURCHASE, + UNACK_ITEM_RECIPE_OUTPUT, + UNACK_ITEM_HIDDEN_QUEST_ITEM, + UNACK_ITEM_QUEST_OUTPUT, + UNACK_ITEM_QUEST_LOANER, + UNACK_ITEM_TRADE_UP, + UNACK_ITEM_QUEST_MERASMISSION_OUTPUT, + UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD, +#ifdef ENABLE_STORE_RENTAL_BACKEND + UNACK_ITEM_RENTAL_PURCHASE, +#endif + + UNACK_NUM_METHODS, +}; + +extern const char *g_pszItemPickupMethodStrings[UNACK_NUM_METHODS - 1]; // -1 because UNACK_ITEM_DROPPED is index 1, not 0 +extern const char *g_pszItemPickupMethodStringsUnloc[UNACK_NUM_METHODS - 1]; +extern const char *g_pszItemFoundMethodStrings[UNACK_NUM_METHODS - 1]; + +enum +{ + kGCItemSort_NoSort = 0, // this won't do anything, but can be used as a safe "header" value + + kGCItemSort_SortByName = 1, + kGCItemSort_SortByDefIndex = 2, + kGCItemSort_SortByRarity = 3, + kGCItemSort_SortByType = 4, + kGCItemSort_SortByDate = 5, + + kGCItemSort_GameSpecificBase = 100, +}; + +// FIXME: these should be moved... somewhere; where? +enum +{ + kTFGCItemSort_SortByClass = kGCItemSort_GameSpecificBase + 1, + kTFGCItemSort_SortBySlot = kGCItemSort_GameSpecificBase + 2, +}; + +enum +{ + kBackendPosition_Unacked = 1 << 30, + kBackendPosition_NewFormat = 1 << 31, + + kBackendPositionMask_Position = 0x0000ffff, + kBackendPositionMask_FormatFlags = (kBackendPosition_Unacked | kBackendPosition_NewFormat), +}; + +inline void SetBackpackPosition( uint32 *pPosition, uint32 iPackPosition ) +{ + (*pPosition) = iPackPosition; + + // Remove the unack'd flag + (*pPosition) &= ~kBackendPosition_Unacked; +} + +inline bool IsNewPositionFormat( uint32 iBackendPosition ) +{ + return ( iBackendPosition & kBackendPosition_NewFormat ) != 0; +} + +inline bool IsUnacknowledged( uint32 iBackendPosition ) +{ + // For backwards compatibility, we consider position 0 as unacknowledged too + return (iBackendPosition == 0 || (iBackendPosition & kBackendPosition_Unacked) != 0); +} + +inline int ExtractBackpackPositionFromBackend( uint32 iBackendPosition ) +{ + if ( IsUnacknowledged( iBackendPosition) ) + return 0; + + return iBackendPosition & kBackendPositionMask_Position; +} + +inline unacknowledged_item_inventory_positions_t GetUnacknowledgedReason( uint32 iBackendPosition ) +{ + return (unacknowledged_item_inventory_positions_t)( iBackendPosition &= ~kBackendPositionMask_FormatFlags ); +} + +inline uint32 GetUnacknowledgedPositionFor( unacknowledged_item_inventory_positions_t iMethod ) +{ + return (iMethod | kBackendPosition_Unacked | kBackendPosition_NewFormat); +} + +//----------------------------------------------------------------------------- +// Item Preview event IDs for logging. +//----------------------------------------------------------------------------- +enum EEconItemPreviewEventIDs +{ + k_EEconItemPreview_Start =1, + k_EEconItemPreview_Expired =2, + k_EEconItemPreview_ItemPurchased =3, +}; + +//----------------------------------------------------------------------------- +// List of holidays. These are sorted by priority. Needs to match static IIsHolidayActive *s_HolidayChecks +//----------------------------------------------------------------------------- +enum EHoliday +{ + kHoliday_None = 0, // must stay at zero for backwards compatibility + kHoliday_TFBirthday, + kHoliday_Halloween, + kHoliday_Christmas, + kHoliday_CommunityUpdate, + kHoliday_EOTL, + kHoliday_Valentines, + kHoliday_MeetThePyro, + kHoliday_FullMoon, + kHoliday_HalloweenOrFullMoon, + kHoliday_HalloweenOrFullMoonOrValentines, + kHoliday_AprilFools, + kHolidayCount, +}; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +enum ECartItemType +{ + kCartItem_Purchase, // a normal lifetime purchase (needs to stay as entry 0!) + kCartItem_TryOutUpgrade, // an upgrade from "try-it-out" + kCartItem_Rental_1Day, + kCartItem_Rental_3Day, + kCartItem_Rental_7Day, +}; + +inline bool IsRentalCartItemType( ECartItemType eCartType ) +{ + return eCartType == kCartItem_Rental_1Day + || eCartType == kCartItem_Rental_3Day + || eCartType == kCartItem_Rental_7Day; +} + +const uint8 k_unItemRarity_Any = 0xFF; +const uint8 k_unItemQuality_Any = 0xFF; + +typedef int econ_tag_handle_t; + +enum EItemUntradability +{ + k_Untradability_Temporary = 1<<1, + k_Untradability_Permanent = 1<<2, +}; + +#define INVALID_ECON_TAG_HANDLE ((econ_tag_handle_t)-1) + +#endif // ACTUAL_ECON_ITEM_CONSTANTS_H diff --git a/game/shared/econ/econ_item_description.cpp b/game/shared/econ/econ_item_description.cpp new file mode 100644 index 0000000..b74a86f --- /dev/null +++ b/game/shared/econ/econ_item_description.cpp @@ -0,0 +1,4146 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#include "cbase.h" +#include "econ_item_description.h" +#include "econ_item_interface.h" +#include "econ_item_tools.h" +#include "econ_holidays.h" +#include "econ_store.h" +#include "tier1/ilocalize.h" +#include "localization_provider.h" +#include "rtime.h" +#include "econ_dynamic_recipe.h" + +#ifdef GC + // the GC needs accountdetails to get persona names + #include "gcsdk/accountdetails.h" +#else // !GC + + #ifndef EXTERNALTESTS_DLL + #include "econ_item_inventory.h" + #endif + + #ifdef CLIENT_DLL + #include "gc_clientsystem.h" + #include "client_community_market.h" // for Market data in tooltips + #include "econ_ui.h" // for money-value-to-display-string formatting + #include "store/store_panel.h" // for money-value-to-display-string formatting + #endif // CLIENT_DLL +#endif // GC + + +#ifdef PROJECT_TF + #include "tf_duel_summary.h" + #include "econ_contribution.h" + #include "tf_player_info.h" + #include "tf_wardata.h" + + #ifdef TF_CLIENT_DLL + #include "tf_gamerules.h" + #include "tf_mapinfo.h" + #endif +#endif + +#ifdef VPROF_ENABLED + static const char *g_pszEconDescriptionVprofGroup = _T("Econ Description"); +#endif + +extern const char *GetWearLocalizationString( float flWear ); + +// -------------------------------------------------------------------------- +// Local Helper +// -------------------------------------------------------------------------- +const size_t k_VerboseStringBufferSize = 128; +static char *BuildVerboseStrings( char buf[k_VerboseStringBufferSize], bool bIsVerbose, const char *format, ... ) +{ + if ( !bIsVerbose ) + return NULL; + + va_list argptr; + va_start( argptr, format ); + Q_vsnprintf( buf, k_VerboseStringBufferSize, format, argptr ); + va_end(argptr); + + return buf; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +static bool IsStorePreviewItem( const IEconItemInterface *pEconItem ) +{ + Assert( pEconItem ); + +#ifdef CLIENT_DLL + return pEconItem->GetFlags() & kEconItemFlagClient_StoreItem; +#else + return false; +#endif +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void IEconItemDescription::YieldingFillOutEconItemDescription( IEconItemDescription *out_pDescription, CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + VPROF_BUDGET( "IEconItemDescription::YieldingFillOutEconItemDescription()", g_pszEconDescriptionVprofGroup ); + + Assert( out_pDescription ); + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + out_pDescription->YieldingCacheDescriptionData( pLocalizationProvider, pEconItem ); + out_pDescription->GenerateDescriptionLines( pLocalizationProvider, pEconItem ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const econ_item_description_line_t *IEconItemDescription::GetFirstLineWithMetaType( uint32 unMetaTypeSearchFlags ) const +{ + for ( unsigned int i = 0; i < GetLineCount(); i++ ) + { + const econ_item_description_line_t& pLine = GetLine(i); + if ( (pLine.unMetaType & unMetaTypeSearchFlags) == unMetaTypeSearchFlags ) + return &pLine; + } + + return NULL; +} + +#ifdef BUILD_ITEM_NAME_AND_DESC + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CLocalizedStringArg<CLocalizedRTime32>::CLocalizedStringArg( const CLocalizedRTime32& cTimeIn ) +{ +#if TF_ANTI_IDLEBOT_VERIFICATION + // We expect the client and the GC to display dates and times differently in certain situations (ie., + // they may disagree on whether to display in GMT). Rather than trying to find the specific cases where + // they might agree and let them through, we just early out and feed back the empty string for all + // date/time formatting when doing client verification. + if ( cTimeIn.m_pHashContext ) + return; +#endif + + CRTime cTime( cTimeIn.m_unTime ); + + // The GC will always display time in GMT. "Local time" isn't a useful thing from a client's perspective + // when viewing an item in the Steam Community, etc. +#ifdef GC_DLL + cTime.SetToGMT( true ); +#else + cTime.SetToGMT( cTimeIn.m_bForceGMTOnClient ); +#endif + + const locchar_t *loc_LocalizationFormat = cTimeIn.m_pLocalizationProvider->Find( cTime.BIsGMT() ? "Econ_DateFormat_GMT" : "Econ_DateFormat" ); + + time_t tTime = cTime.GetRTime32(); + struct tm tmStruct; + struct tm *ptm = cTime.BIsGMT() ? Plat_gmtime( &tTime, &tmStruct ) : Plat_localtime( &tTime, &tmStruct ); + + time_t tFinalTime = mktime( ptm ); + + char rgchDateBuf[ 128 ]; + BGetLocalFormattedDate( tFinalTime, rgchDateBuf, sizeof( rgchDateBuf ) ); + + KeyValues *pKeyValues = new KeyValues( "DateTokens" ); + + pKeyValues->SetString( "day", &rgchDateBuf[0] ); + pKeyValues->SetInt( "hour", cTime.GetHour() ); + pKeyValues->SetString( "min", CFmtStr( "%02u", cTime.GetMinute() ).Access() ); + pKeyValues->SetString( "sec", CFmtStr( "%02u", cTime.GetSecond() ).Access() ); + + m_Str = CConstructLocalizedString( loc_LocalizationFormat, pKeyValues ); + + pKeyValues->deleteThis(); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::YieldingFillOutAccountPersonaName( const CLocalizationProvider *pLocalizationProvider, uint32 unAccountID ) +{ + Assert( pLocalizationProvider ); + + // Never cache invalid accounts. + if ( unAccountID == 0 ) + return; + + // Make sure we have a cache entry for this account ID. If we're hashing, we won't fill + // this with real data to avoid discrepancies between the GC view of a persona name and the + // client view, both of which are cached differently. If we're not hashing, we'll do our best + // to find the current name. Either way, by the time this function ends we expect to have a + // value stored for this account ID. + CEconItemDescription::steam_account_persona_name_t& AccountPersona = vecPersonaNames[ vecPersonaNames.AddToTail() ]; + AccountPersona.unAccountID = unAccountID; + +#if TF_ANTI_IDLEBOT_VERIFICATION + // Force persona names to match "verify" between the client and the GC for verification. + if ( m_pHashContext ) + { + AccountPersona.loc_sPersonaName = LOCCHAR("verify"); + } + else +#endif // TF_ANTI_IDLEBOT_VERIFICATION + { + const char *utf8_PersonaName = NULL; + +#ifdef GC + CSteamID steamID( unAccountID, GCSDK::GGCHost()->GetUniverse(), k_EAccountTypeIndividual ); + utf8_PersonaName = GGCGameBase()->YieldingGetPersonaName( steamID ); +#else // if defined( CLIENT_DLL ) + utf8_PersonaName = InventoryManager()->PersonaName_Get( unAccountID ); +#endif + +#if defined( CLIENT_DLL ) + m_bUnknownPlayer = Q_strncmp( utf8_PersonaName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) == 0; +#endif + + // We should have filled this in with something by now, even if that something is "we couldn't + // find useful information". + Assert( utf8_PersonaName ); + + // Convert our UTF8 to whatever we're using for localized display, and done. + pLocalizationProvider->ConvertUTF8ToLocchar( utf8_PersonaName, &AccountPersona.loc_sPersonaName ); + } + + Assert( !AccountPersona.loc_sPersonaName.IsEmpty() ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const locchar_t *CEconItemDescription::FindAccountPersonaName( uint32 unAccountID ) const +{ + FOR_EACH_VEC( vecPersonaNames, i ) + { + if ( vecPersonaNames[i].unAccountID == unAccountID ) + return vecPersonaNames[i].loc_sPersonaName.Get(); + } + + // FIXME: add localization token + return LOCCHAR("Unknown User"); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::YieldingFillOutAccountTypeCache( uint32 unAccountID, int nClassID ) +{ + if( !unAccountID ) + return; + +#ifdef GC_DLL + CEconSharedObjectCache *pSOCache = GGCEcon()->YieldingFindOrLoadEconSOCache( CSteamID( unAccountID, GCSDK::GGCHost()->GetUniverse(), k_EAccountTypeIndividual ) ); +#else // if defined( CLIENT_DLL ) + EUniverse eUniverse = GetUniverse(); + + if ( eUniverse == k_EUniverseInvalid ) + return; + + GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( CSteamID( unAccountID, eUniverse, k_EAccountTypeIndividual ) ); +#endif + if ( !pSOCache ) + return; + + GCSDK::CSharedObjectTypeCache *pTypeCache = pSOCache->FindTypeCache( nClassID ); + if ( !pTypeCache ) + return; + + CEconItemDescription::steam_account_type_cache_t& AccountTypeCache = vecTypeCaches[ vecTypeCaches.AddToTail() ]; + + AccountTypeCache.unAccountID = unAccountID; + AccountTypeCache.nClassID = nClassID; + AccountTypeCache.pTypeCache = pTypeCache; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +GCSDK::CSharedObjectTypeCache *CEconItemDescription::FindAccountTypeCache( uint32 unAccountID, int nClassID ) const +{ + FOR_EACH_VEC( vecTypeCaches, i ) + { + if ( vecTypeCaches[i].unAccountID == unAccountID && + vecTypeCaches[i].nClassID == nClassID ) + { + return vecTypeCaches[i].pTypeCache; + } + } + + return NULL; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +template < typename T > +const T *CEconItemDescription::FindAccountTypeCacheSingleton( uint32 unAccountID, int nClassID ) const +{ + GCSDK::CSharedObjectTypeCache *pSOTypeCache = FindAccountTypeCache( unAccountID, nClassID ); + if ( !pSOTypeCache ) + return NULL; + + if ( pSOTypeCache->GetCount() != 1 ) + return NULL; + + return dynamic_cast<T *>( pSOTypeCache->GetObject( 0 ) ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::YieldingCacheDescriptionData( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + VPROF_BUDGET( "CEconItemDescription::YieldingCacheDescriptionData()", g_pszEconDescriptionVprofGroup ); + + vecPersonaNames.Purge(); + vecTypeCaches.Purge(); + + // For each attribute that is set to display as an account ID, load the persona name for that account + // ID in advance so that we don't yield somewhere crazy down below. + + // Walk our attribute list and accumulate IDs. + CSteamAccountIDAttributeCollector AccountIDCollector; + pEconItem->IterateAttributes( &AccountIDCollector ); + const CUtlVector<uint32>& vecSteamAccountIDs = AccountIDCollector.GetAccountIDs(); + + // Look up the persona names for each account referenced by an attribute directly. + FOR_EACH_VEC( vecSteamAccountIDs, i ) + { + YieldingFillOutAccountPersonaName( pLocalizationProvider, vecSteamAccountIDs[i] ); + } + + // Look up the persona names for each account referencing an attribute indirectly (ie., just stuffed + // into 32 bits). + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + uint32 unRestrictionType; + if ( pEconItem->FindAttribute( GetKillEaterAttr_Restriction(i), &unRestrictionType ) && + unRestrictionType == kStrangeEventRestriction_VictimSteamAccount ) + { + uint32 unAccountID; + DbgVerify( pEconItem->FindAttribute( GetKillEaterAttr_RestrictionValue(i), &unAccountID ) ); + YieldingFillOutAccountPersonaName( pLocalizationProvider, unAccountID ); + } + } + +#ifdef PROJECT_TF + uint32 unAccountID = pEconItem->GetAccountID(); + + // Duel summary. + { + // We'll need to access other information about our duel stats later, but we also need to precache + // the account name of whoever our last kill was beforehand. + YieldingFillOutAccountTypeCache( unAccountID, CTFDuelSummary::k_nTypeID ); + + // In TF, we also store information about our previous duel target, stored way way down inside some + // other structures. + const CTFDuelSummary *pDuelSummary = FindAccountTypeCacheSingleton<CTFDuelSummary>( unAccountID, CTFDuelSummary::k_nTypeID ); + + if ( pDuelSummary ) + { + YieldingFillOutAccountPersonaName( pLocalizationProvider, pDuelSummary->Obj().last_duel_account_id() ); + } + } + + // Map contributions. + YieldingFillOutAccountTypeCache( unAccountID, CTFMapContribution::k_nTypeID ); + + // New users helped. + YieldingFillOutAccountTypeCache( unAccountID, CTFPlayerInfo::k_nTypeID ); + + // War data + YieldingFillOutAccountTypeCache( unAccountID, CWarData::k_nTypeID ); + +#ifdef CLIENT_DLL + // Duck LeaderBoards + { + static CSchemaAttributeDefHandle pAttrDef_DisplayDuckLeaderboard( "display duck leaderboard" ); + if ( pEconItem->FindAttribute( pAttrDef_DisplayDuckLeaderboard ) ) + { + CUtlVector< AccountID_t > accountIds; + Leaderboards_GetDuckLeaderboardSteamIDs( accountIds ); + + FOR_EACH_VEC( accountIds, i ) + { + // Look up the persona names for each account referenced in the leaderboard + YieldingFillOutAccountPersonaName( pLocalizationProvider, accountIds[i] ); + } + } + } +#endif // CLIENT_DLL + +#if TF_ANTI_IDLEBOT_VERIFICATION + if ( m_pHashContext ) + { + // Feed in the account IDs of each person we care about. We don't feed in the actual persona name + // string because this could theoretically differ between the GC and the client if one is out of sync. + FOR_EACH_VEC( vecPersonaNames, i ) + { + char verboseStringBuf[ k_VerboseStringBufferSize ]; + TFDescription_HashDataMunge( m_pHashContext, vecPersonaNames[i].unAccountID, m_bIsVerbose, BuildVerboseStrings( verboseStringBuf, m_bIsVerbose, "%d", vecPersonaNames[i].unAccountID ) ); + } + + // Are we in text mode or not? We'll use this to generate two different hashes to compare against. + unsigned int unRunningTextMode = +#ifdef GC_DLL + m_bTextModeEnabled +#else + *((bool *)g_pClientPurchaseInterface - 156) +#endif + ? 0x73aaff8e + : 0x12800c0a; + + char verboseStringBuf[ k_VerboseStringBufferSize ]; + TFDescription_HashDataMunge( m_pHashContext, unRunningTextMode, m_bIsVerbose, BuildVerboseStrings( verboseStringBuf, m_bIsVerbose, "%d", unRunningTextMode ) ); + } +#endif // TF_ANTI_IBLEBOT_VERIFICATION + +#endif // PROJECT_TF +} + +void CEconItemDescription::GenerateDescriptionLines( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + VPROF_BUDGET( "CEconItemDescription::GenerateDescriptionLines()", g_pszEconDescriptionVprofGroup ); + + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + m_vecDescLines.Purge(); + + Generate_ItemName( pLocalizationProvider, pEconItem ); + Generate_ItemRarityDesc( pLocalizationProvider, pEconItem ); + Generate_ItemLevelDesc( pLocalizationProvider, pEconItem ); + //Generate_WearAmountDesc( pLocalizationProvider, pEconItem ); +#if defined( STAGING_ONLY ) && defined( CLIENT_DLL ) + Generate_DebugInformation( pLocalizationProvider, pEconItem ); +#endif + + // If we decide that for performance reasons some descriptions only want the name/description + // information and not all the details, this is the block to skip over. + { + Generate_CraftTag( pLocalizationProvider, pEconItem ); + Generate_StyleDesc( pLocalizationProvider, pEconItem ); + Generate_Painted( pLocalizationProvider, pEconItem ); + + Generate_HolidayRestriction( pLocalizationProvider, pEconItem ); +#ifdef PROJECT_TF + Generate_SaxxyAwardDesc( pLocalizationProvider, pEconItem ); +#endif // PROJECT_TF + Generate_VisibleAttributes( pLocalizationProvider, pEconItem ); + + Generate_QualityDesc( pLocalizationProvider, pEconItem ); + Generate_ItemDesc( pLocalizationProvider, pEconItem ); + Generate_Bundle( pLocalizationProvider, pEconItem ); + Generate_GiftedBy( pLocalizationProvider, pEconItem ); +#ifdef PROJECT_TF + Generate_DuelingMedal( pLocalizationProvider, pEconItem ); + Generate_MapContributor( pLocalizationProvider, pEconItem ); + Generate_FriendlyHat( pLocalizationProvider, pEconItem ); + Generate_SquadSurplusClaimedBy( pLocalizationProvider, pEconItem ); + Generate_MvmChallenges( pLocalizationProvider, pEconItem ); + Generate_DynamicRecipe( pLocalizationProvider, pEconItem ); + Generate_Leaderboard( pLocalizationProvider, pEconItem ); +#endif // PROJECT_TF + Generate_XifierToolTargetItem( pLocalizationProvider, pEconItem ); + Generate_LootListDesc( pLocalizationProvider, pEconItem ); + Generate_EventDetail( pLocalizationProvider, pEconItem ); + Generate_ItemSetDesc( pLocalizationProvider, pEconItem ); + Generate_CollectionDesc( pLocalizationProvider, pEconItem ); + Generate_ExpirationDesc( pLocalizationProvider, pEconItem ); + Generate_DropPeriodDesc( pLocalizationProvider, pEconItem ); + + Generate_MarketInformation( pLocalizationProvider, pEconItem ); + Generate_DirectX8Warning( pLocalizationProvider, pEconItem ); + } + + // Certain information (tradeability, etc.) used to only get displayed if we were the owning player, or + // if we were looking at an unowned item (ie., a store preview) and want to show what it will look like + // when it *is* owned. Unfortunately this led to problems where you wouldn't know if the item you were + // about to be traded (currently not owned by you) would be craftable, etc. + Generate_FlagsAttributes( pLocalizationProvider, pEconItem ); +} + +// -------------------------------------------------------------------------- +// Purpose: Code to build up the item display name, including any relevant quality +// strings, custom renaming, craft numbers, and anything else we decide +// to throw at it. +// -------------------------------------------------------------------------- + +/*static*/ uint32 GetScoreTypeForKillEaterAttr( const IEconItemInterface *pEconItem, const CEconItemAttributeDefinition *pAttribDef ) +{ + // What sort of event are we tracking? If we don't have an attribute at all we're one of the + // old kill-eater weapons that didn't specify what it was tracking. + uint32 unKillEaterEventType = 0; + + // This will overwrite our default 0 value if we have a value set but leave it if not. + { + float fKillEaterEventType; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttribDef, &fKillEaterEventType ) ) + { + unKillEaterEventType = fKillEaterEventType; + } + } + + return unKillEaterEventType; +} + +// The item backend may add craft numbers well past what we want to display in the game. This +// function determines whether a given number should be visible rather than always showing +// whatever the GC shows. +bool ShouldDisplayCraftCounterValue( int iValue ) +{ + return iValue > 0 && iValue <= 100; +} + +// This function will return the localized string (ie., "Face-Melting") for a specific item based +// on the score it has accumulated. +#if defined( CLIENT_DLL ) && defined( STAGING_ONLY ) + ConVar staging_force_strange_score_selector_value( "staging_force_strange_score_selector_value", "-1" ); +#endif // defined( CLIENT_DLL ) && defined( STAGING_ONLY ) + +class CStrangeRankLocalizationGenerator +{ +public: + CStrangeRankLocalizationGenerator( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, bool bHashContextOff ); + + bool IsValid() const { return m_bValid; } + + const locchar_t *GetRankLocalized() const { Assert( m_bValid ); return m_loc_Rank; } + const locchar_t *GetRankSecondaryLocalized() const { Assert( m_bValid ); return m_loc_SecondaryRank; } + + uint32 GetStrangeType() const { Assert( m_bValid ); return m_unType; } + uint32 GetStrangeScore() const { Assert( m_bValid ); return m_unScore; } + uint32 GetUsedStrangeSlot() const { Assert( m_bValid ); return m_unUsedStrangeSlot; } + +private: + bool m_bValid; + + const locchar_t *m_loc_Rank; + const locchar_t *m_loc_SecondaryRank; + + uint32 m_unType; + uint32 m_unScore; + uint32 m_unUsedStrangeSlot; +}; + +CStrangeRankLocalizationGenerator::CStrangeRankLocalizationGenerator( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, bool bHashContextOff ) + : m_bValid( false ) + , m_loc_Rank( NULL ) + , m_loc_SecondaryRank( NULL ) + , m_unType( kKillEaterEvent_PlayerKill ) + , m_unScore( 0 ) + , m_unUsedStrangeSlot( 0 ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttrDef_StrangeScoreSelector( "strange score selector" ); + + // Do we have a strange score selector attribute? If so, the value of this attribute will tell us which strange + // attribute we're actually going to use to generate a name. Leaving this value as 0 will fall back to the + // default behavior of looking at the base "kill eater" attribute. + if ( pEconItem->FindAttribute( pAttrDef_StrangeScoreSelector, &m_unUsedStrangeSlot ) ) + { + // Make sure the value we pulled from the database is within range. + m_unUsedStrangeSlot = MIN( m_unUsedStrangeSlot, static_cast<uint32>( GetKillEaterAttrCount() ) ); + } + +#if defined( CLIENT_DLL ) && defined( STAGING_ONLY ) + if ( staging_force_strange_score_selector_value.GetInt() > 0 ) + { + m_unUsedStrangeSlot = staging_force_strange_score_selector_value.GetInt(); + } +#endif // defined( CLIENT_DLL ) && defined( STAGING_ONLY ) + + // Use the strange prefix if the weapon has one. + if ( !pEconItem->FindAttribute( GetKillEaterAttr_Score( m_unUsedStrangeSlot ), &m_unScore ) ) + return; + + // What type of event are we tracking and how does it describe itself? + m_unType = GetScoreTypeForKillEaterAttr( pEconItem, GetKillEaterAttr_Type( m_unUsedStrangeSlot ) ); + + const char *pszLevelingDataName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( m_unType ); + if ( !pszLevelingDataName ) + { + pszLevelingDataName = KILL_EATER_RANK_LEVEL_BLOCK_NAME; + } + + uint32 uUsedScore = m_unScore; +#ifdef TF_ANTI_IDLEBOT_VERIFICATION + // TF2 Anti-Idle hack. It totally needs to be fixed + if ( !bHashContextOff ) + { + uUsedScore = 0; + } +#endif //TF_ANTI_IDLEBOT_VERIFICATION + + // For TF - Strange Scores reset on Trade, sharing that information is actually misleading so we'll always display base strange name +#ifdef TF_GC_DLL + uUsedScore = 0; +#endif // TF_GC_DLL + + const CItemLevelingDefinition *pLevelDef = GetItemSchema()->GetItemLevelForScore( pszLevelingDataName, uUsedScore ); + if ( !pLevelDef ) + return; + + // Primary rank established! + m_loc_Rank = pLocalizationProvider->Find( pLevelDef->GetNameLocalizationKey() ); + m_bValid = true; + + // Does this score slot have a restriction that adds additional text somewhere in the localization token? + uint32 unFilterType; + uint32 unFilterValue; + if ( pEconItem->FindAttribute( GetKillEaterAttr_Restriction( m_unUsedStrangeSlot ), &unFilterType ) && + pEconItem->FindAttribute( GetKillEaterAttr_RestrictionValue( m_unUsedStrangeSlot ), &unFilterValue ) ) + { + // Game-specific code doesn't belong here. "We're shipping soon" hack fun! +#ifdef PROJECT_TF + if ( unFilterType == kStrangeEventRestriction_Map ) + { + const MapDef_t *pMap = GetItemSchema()->GetMasterMapDefByIndex( unFilterValue ); + if ( pMap && pMap->pszStrangePrefixLocKey ) + { + m_loc_SecondaryRank = pLocalizationProvider->Find( pMap->pszStrangePrefixLocKey ); + } + } + else if (unFilterType == kStrangeEventRestriction_Competitive) + { + m_loc_SecondaryRank = pLocalizationProvider->Find( "TF_StrangeFilter_Prefix_Competitive" ); + } +#endif // PROJECT_TF + } +} + +// --------------------------------------------------------------------------------------------------------------------------- +void Econ_SetNameAsPaintkit( locchar_t( &out_pItemName )[MAX_ITEM_NAME_LENGTH], const CLocalizationProvider *pLocalizationProvider, CEconItemPaintKitDefinition *pPaintKit ) +{ + if ( !pPaintKit ) + return; + + // Generate Paint kitted name + // IE Purple Rain Sniper Rifle + locchar_t tempName[MAX_ITEM_NAME_LENGTH]; + loc_scpy_safe( tempName, out_pItemName ); +#ifndef GC + //bool bAppendWeapon = wcsstr( pPaintKitStr, out_pItemName ) == NULL; + g_pVGuiLocalize->ConstructString_safe( out_pItemName, + LOCCHAR( "%s1" ), + 1, + pLocalizationProvider->Find( pPaintKit->GetLocalizeName() ) ); +#else + // GC doesn't have g_pVGuiLocalize so we construct the painted gun string like this + locchar_t *pPaintKitStr = pLocalizationProvider->Find( pPaintKit->GetLocalizeName() ); + loc_scpy_safe( out_pItemName, pPaintKitStr ? pPaintKitStr : LOCCHAR( "" ) ); +#endif +} + +// --------------------------------------------------------------------------------------------------------------------------- +void Econ_ConcatPaintKitName( locchar_t( &out_pItemName )[MAX_ITEM_NAME_LENGTH], const CLocalizationProvider *pLocalizationProvider, CEconItemPaintKitDefinition *pPaintKit ) +{ + if ( !pPaintKit ) + return; + + // Check to see if the paintkit localized name already has the weapon name, if so do not add (Weapon) + locchar_t *pPaintKitStr = pLocalizationProvider->FindSafe( pPaintKit->GetLocalizeName() ); + + locchar_t tempName[MAX_ITEM_NAME_LENGTH]; + loc_scpy_safe( tempName, out_pItemName ); + +#ifndef GC + bool bAppendWeapon = wcsstr( pPaintKitStr, out_pItemName ) == NULL; + // Generate Paint kitted name + // IE Purple Rain Sniper Rifle + + if ( bAppendWeapon ) + { + g_pVGuiLocalize->ConstructString_safe( out_pItemName, + LOCCHAR("%s1 (%s2)"), + 2, + pPaintKitStr, + tempName ); + } + else + { + g_pVGuiLocalize->ConstructString_safe( out_pItemName, + LOCCHAR("%s1"), + 1, + pPaintKitStr + ); + } +#else + // GC doesn't have g_pVGuiLocalize so we construct the painted gun string like this + bool bAppendWeapon = V_strstr( pPaintKitStr, out_pItemName ) == NULL; + // Generate Paint kitted name + // IE Purple Rain Sniper Rifle + loc_scpy_safe( out_pItemName, pPaintKitStr ? pPaintKitStr : LOCCHAR( "" ) ); + if ( bAppendWeapon ) + { + loc_scat_safe( out_pItemName, LOCCHAR( " " ) ); + loc_scat_safe( out_pItemName, tempName ); + } +#endif +} + +// --------------------------------------------------------------------------------------------------------------------------- +void Econ_ConcatPaintKitWear( locchar_t( &out_pItemName )[MAX_ITEM_NAME_LENGTH], const CLocalizationProvider *pLocalizationProvider, float flWear ) +{ + if ( flWear <= 0.0 ) + return; + +#ifndef GC + locchar_t tempName[MAX_ITEM_NAME_LENGTH]; + loc_scpy_safe( tempName, out_pItemName ); + + g_pVGuiLocalize->ConstructString_safe( out_pItemName, + LOCCHAR( "%s1 (%s2)" ), + 2, + tempName, + pLocalizationProvider->Find( GetWearLocalizationString( flWear ) ) + ); +#else + // GC doesn't have g_pVGuiLocalize so we construct the painted gun string like this + locchar_t *pWearStr = pLocalizationProvider->Find( GetWearLocalizationString( flWear ) ); + loc_scat_safe( out_pItemName, LOCCHAR( " (" ) ); + loc_scat_safe( out_pItemName, pWearStr ? pWearStr : LOCCHAR( "" ) ); + loc_scat_safe( out_pItemName, LOCCHAR( ")" ) ); +#endif +} +// --------------------------------------------------------------------------------------------------------------------------- +static bool GetLocalizedBaseItemName( locchar_t (&szItemName)[MAX_ITEM_NAME_LENGTH], const CLocalizationProvider *pLocalizationProvider, const CEconItemDefinition *pEconItemDefinition ) +{ + if ( pEconItemDefinition->GetItemBaseName() ) + { + const locchar_t *pLocalizedItemName = pLocalizationProvider->Find( pEconItemDefinition->GetItemBaseName() ); + if ( !pLocalizedItemName || !pLocalizedItemName[0] ) + { + // Couldn't localize it, just use it raw + pLocalizationProvider->ConvertUTF8ToLocchar( pEconItemDefinition->GetItemBaseName(), szItemName, ARRAYSIZE( szItemName ) ); + } + else + { + loc_scpy_safe( szItemName, pLocalizedItemName ); + } + + return true; + } + + return false; +} + +// Given the item in pEconItem and the localization provider passed in, stuff the correct *localized* +// string into out_pItemName. +static void GenerateLocalizedFullItemName +( + locchar_t (&out_pItemName)[MAX_ITEM_NAME_LENGTH], + const CLocalizationProvider *pLocalizationProvider, + const IEconItemInterface *pEconItem, + EGenerateLocalizedFullItemNameFlag_t eFlagsMask, + bool bHashContextOff +) +{ + bool bUseProperName = bHashContextOff; + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static const locchar_t *s_pUnknownItemName = LOCCHAR("Unknown Item"); + + const CEconItemDefinition *pEconItemDefinition = pEconItem->GetItemDefinition(); + if ( !pEconItemDefinition ) + { + out_pItemName[0] = (locchar_t)0; + return; + } + + bool bIgnoreQualityAndWear = false; + bool bIgnoreNameWithPaintkit = false; + bool bHasCustomName = false; + if ( eFlagsMask == k_EGenerateLocalizedFullItemName_Default ) + { + bIgnoreQualityAndWear = pEconItem->GetCustomPainkKitDefinition() ? true : false; + } + + if ( eFlagsMask == k_EGenerateLocalizedFullItemName_WithPaintkitNoItem ) + { + bIgnoreNameWithPaintkit = pEconItem->GetCustomPainkKitDefinition() ? true : false; + bIgnoreQualityAndWear = pEconItem->GetCustomPainkKitDefinition() ? true : false; + } + + // Figure out which localization pattern we're using. By default we assume we're using the common "[Quality] [Item Name]" + // format, but if we're a unique item with an article we'll change this later on. + const char *pszLocalizationPattern = "ItemNameFormat"; + + // Start with the base name. + locchar_t szItemName[ MAX_ITEM_NAME_LENGTH ]; + + static CSchemaAttributeDefHandle pAttrDef_ItemNameTextOverride( "item name text override" ); + CAttribute_String attrItemNameTextOverride; + // Check if we ahve a item name override + if ( pEconItem->FindAttribute( pAttrDef_ItemNameTextOverride, &attrItemNameTextOverride ) ) + { + const locchar_t *pNameOverrideString = pLocalizationProvider->Find( attrItemNameTextOverride.value().c_str() ); + if ( pNameOverrideString ) + { + loc_scpy_safe( szItemName, pNameOverrideString ); + bHasCustomName = true; + } + } + else if( !GetLocalizedBaseItemName( szItemName, pLocalizationProvider, pEconItemDefinition ) ) + { + loc_scpy_safe( szItemName, s_pUnknownItemName ); + } + + // Check for killstreak attribute + enum { kKillStreakLength = 64, }; + locchar_t szKillStreak[ kKillStreakLength ] = LOCCHAR(""); + static CSchemaAttributeDefHandle pAttrDef_KillStreak( "killstreak tier" ); + uint32 nKillStreakValue; + + if ( pEconItem->FindAttribute( pAttrDef_KillStreak, &nKillStreakValue ) && !bIgnoreQualityAndWear ) + { + nKillStreakValue = (float&)(nKillStreakValue); + + // if you have the eyeballs you are automatically higher tier + static CSchemaAttributeDefHandle pAttrDef_KillStreakEyes( "killstreak effect" ); + static CSchemaAttributeDefHandle pAttrDef_KillStreakSheen( "killstreak idleeffect" ); + if ( pEconItem->FindAttribute( pAttrDef_KillStreakEyes ) ) + { + nKillStreakValue = 3; // professional + } + else if ( pEconItem->FindAttribute( pAttrDef_KillStreakSheen ) ) + { + nKillStreakValue = 2; // specialized + } + + const locchar_t *pKillStreakLocalizedString = NULL; + + // All tier-1 killstreaks have idle effect 1 + if ( nKillStreakValue == 1 ) + { + pKillStreakLocalizedString = pLocalizationProvider->Find( "ItemNameKillStreakv0" ); + } + else if ( nKillStreakValue == 2 ) + { + pKillStreakLocalizedString = pLocalizationProvider->Find( "ItemNameKillStreakv1" ); + } + else // Tier-2's are things above 1 + { + pKillStreakLocalizedString = pLocalizationProvider->Find( "ItemNameKillStreakv2" ); + } + + if ( pKillStreakLocalizedString ) + { + loc_scpy_safe( szKillStreak, pKillStreakLocalizedString ); + // If we're appending some sort of killstreak identifier, dont use the proper name + bUseProperName = false; + } + } + + // Check to see if we have a quality text override attribute. We can get this when a temporary item + // comes in from a crafting recipe that needs to get its name generated, and wants to specify that it + // takes in any quality + static CSchemaAttributeDefHandle pAttrDef_QualityTextOverride( "quality text override" ); + CAttribute_String attrQualityTextOverride; + pEconItem->FindAttribute( pAttrDef_QualityTextOverride, &attrQualityTextOverride ); + + // Generate our quality string. + enum { kQualityLength = 128, }; + locchar_t szQuality[ kQualityLength ] = LOCCHAR(""); + + // Unique names may have a prefix or not, and so use a different format. (This is less to deal + // with the space after "The" and more to deal with foreign languages that want to display unique + // and non-unique items differently. + const uint8 unQuality = pEconItem->GetQuality(); + if ( unQuality == AE_SELFMADE || ( !bIgnoreQualityAndWear ) ) + { + // It's possible to get in here with a quality of -1 if we're dealing with an item view that has no + // associated item. In that case we're probably doing something like browsing the armory, and in any + // event don't have an item and so don't have a quality and so we just don't show a quality string. + // If we have a quality text override, use that. + const char *pszQualityLocalizationString = attrQualityTextOverride.has_value() + ? attrQualityTextOverride.value().c_str() + : EconQuality_GetLocalizationString( (EEconItemQuality)unQuality ); + + if ( unQuality > 0 && pszQualityLocalizationString && unQuality != AE_PAINTKITWEAPON ) + { + // Unique items use proper names, but not if we have a quality text override + if ( unQuality == AE_UNIQUE && !attrQualityTextOverride.has_value() ) + { + const locchar_t *pszArticleContent = NULL; + if ( bUseProperName && pEconItemDefinition->HasProperName() ) + { + pszArticleContent = pLocalizationProvider->Find( "TF_Unique_Prepend_Proper" ); + } + + // If the language isn't supposed to have articles or we just haven't provided one yet, fall + // back to the empty string. + if ( !pszArticleContent ) + { + pszArticleContent = LOCCHAR(""); + } + + loc_scpy_safe( szQuality, pszArticleContent ); + } + // Any quality besides unique ignores "proper name" articles. + else + { + const locchar_t *pQualityLocalizedString = pLocalizationProvider->Find( pszQualityLocalizationString ); + if ( pQualityLocalizedString ) + { + loc_scpy_safe( szQuality, pQualityLocalizedString ); + loc_scat_safe( szQuality, LOCCHAR(" ") ); + } + } + } + + { + static CSchemaAttributeDefHandle pAttrDef_HideStrangePrefix( "hide_strange_prefix" ); + if ( !pAttrDef_HideStrangePrefix || !pEconItem->FindAttribute( pAttrDef_HideStrangePrefix ) ) + { + // + CStrangeRankLocalizationGenerator RankGenerator( pLocalizationProvider, pEconItem, bHashContextOff ); + if ( RankGenerator.IsValid() ) + { + // If the quality of this item is special (not just strange) persist and append that value + // Otherwise the ranker will replace the 'strange' quality tag with a strange rank + if ( unQuality == AE_STRANGE ) + { + loc_scpy_safe( szQuality, + CConstructLocalizedString( LOCCHAR("%s1%s2 "), + RankGenerator.GetRankLocalized(), + RankGenerator.GetRankSecondaryLocalized() ? RankGenerator.GetRankSecondaryLocalized() : LOCCHAR("") ) ); + } + else // Strange Unusual Something + { + loc_scpy_safe( szQuality, + CConstructLocalizedString( LOCCHAR("%s1%s2 %s3"), + RankGenerator.GetRankLocalized(), + RankGenerator.GetRankSecondaryLocalized() ? RankGenerator.GetRankSecondaryLocalized() : LOCCHAR(""), + szQuality) ); + } + } + } + } + } + + static CSchemaAttributeDefHandle pAttrDef_IsAustralium( "is australium item" ); + enum { kAustraliumLength = 64, }; + locchar_t szAustraliumSkin[ kAustraliumLength ] = LOCCHAR(""); + if ( pAttrDef_IsAustralium && pEconItem->FindAttribute( pAttrDef_IsAustralium ) ) + { + const locchar_t *pAustraliumLocalizedString = pLocalizationProvider->Find( "ItemNameAustralium" ); + if ( pAustraliumLocalizedString ) + { + loc_scpy_safe( szAustraliumSkin, pAustraliumLocalizedString ); + } + } + + static CSchemaAttributeDefHandle pAttrDef_IsFestivized( "is_festivized" ); + enum { kFestiveLength = 64, }; + locchar_t szIsFestivized[kFestiveLength] = LOCCHAR( "" ); + if ( pAttrDef_IsFestivized && pEconItem->FindAttribute( pAttrDef_IsFestivized ) ) + { + // TODO : update ItemNameFestive in tf_english to Festivized later to differentiate Festive vs Festivized + const locchar_t *pFestivizedLocalizedString = pLocalizationProvider->Find( "ItemNameFestive" ); + if ( pFestivizedLocalizedString ) + { + loc_scpy_safe( szIsFestivized, pFestivizedLocalizedString ); + } + } + + const char* pszQualityFormat = ( !attrQualityTextOverride.has_value() && ( unQuality == AE_NORMAL || unQuality == AE_UNIQUE || unQuality == AE_PAINTKITWEAPON || bIgnoreQualityAndWear ) && unQuality != AE_SELFMADE ) + ? "ItemNameNormalOrUniqueQualityFormat" + : "ItemNameQualityFormat"; + + // TODO : Make Generic + // Journal Leveling + uint32 unDuckBadgeLevel; + static CSchemaAttributeDefHandle pAttrDef_DuckBadgeLevel( "duck rating" ); + enum { kDuckBadgeLength = 64, }; + locchar_t szDuckBadge[kDuckBadgeLength] = LOCCHAR(""); + { //if ( pItem && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttr_DuckLevelBadge, &iDuckBadgeLevel ) ) + if ( pAttrDef_DuckBadgeLevel && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttrDef_DuckBadgeLevel, &unDuckBadgeLevel ) && unDuckBadgeLevel != 0 ) + { + const CItemLevelingDefinition *pLevelDef = GetItemSchema()->GetItemLevelForScore( "Journal_DuckBadge", unDuckBadgeLevel ); + if ( pLevelDef ) + { + loc_scpy_safe( szDuckBadge, pLocalizationProvider->Find( pLevelDef->GetNameLocalizationKey() ) ); + loc_scat_safe( szDuckBadge, LOCCHAR(" ") ); + } + } + } + + // Strange Unusual Festive Killstreak Australium ducks + loc_scpy_safe( szQuality, CConstructLocalizedString( pLocalizationProvider->Find( pszQualityFormat ), szQuality, szIsFestivized, szKillStreak, szAustraliumSkin, szDuckBadge ) ); + + enum { kLocalizedCrateSeriesLength = 128, }; + locchar_t szLocalizedCrateSeries[ kLocalizedCrateSeriesLength ] = LOCCHAR(""); + +#ifdef PROJECT_TF + static CSchemaAttributeDefHandle pAttrDef_SupplyCrateSeries( "set supply crate series" ); + // do not display series number for crates that have a collection reference + if ( pAttrDef_SupplyCrateSeries && pEconItemDefinition->GetItemClass() && !Q_stricmp( pEconItemDefinition->GetItemClass(), "supply_crate" ) && !pEconItemDefinition->GetCollectionReference() ) + { + // It's a crate, find a series # + uint32 unSupplyCrateSeries; + float fSupplyCrateSeries; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttrDef_SupplyCrateSeries, &fSupplyCrateSeries ) && fSupplyCrateSeries != 0.0f ) + { + unSupplyCrateSeries = fSupplyCrateSeries; + + loc_scpy_safe( szLocalizedCrateSeries, + CConstructLocalizedString( pLocalizationProvider->Find( "ItemNameCraftSeries" ), + unSupplyCrateSeries ) ); + } + } + + // This is not "crate series number"; this is "release series number", ie., "a series 3 chemistry kit". + static CSchemaAttributeDefHandle pAttrDef_SeriesNumber( "series number" ); + if ( pAttrDef_SeriesNumber ) + { + float flSeriesNumber = 0.0f; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttrDef_SeriesNumber, &flSeriesNumber ) && flSeriesNumber != 0.0f ) + { + uint32 unSeriesNumber = flSeriesNumber; + + loc_scpy_safe( szLocalizedCrateSeries, + CConstructLocalizedString( pLocalizationProvider->Find( "ItemNameCraftSeries" ), + unSeriesNumber ) ); + } + } +#endif + + // Were we one of the first couple that were crafted? If so, output our craft number as well. + locchar_t *pCraftNumberLocFormat = pLocalizationProvider->Find( "ItemNameCraftNumberFormat" ); + + enum { kLocalizedCraftIndexLength = 128, }; + locchar_t szLocalizedCraftIndex[ kLocalizedCraftIndexLength ] = LOCCHAR(""); + + if ( pCraftNumberLocFormat ) + { + static CSchemaAttributeDefHandle pAttrDef_UniqueCraftIndex( "unique craft index" ); + + uint32 unCraftIndex; + if ( pEconItem->FindAttribute( pAttrDef_UniqueCraftIndex, &unCraftIndex ) && + ShouldDisplayCraftCounterValue( unCraftIndex ) ) + { + locchar_t szCraftNumber[ kLocalizedCraftIndexLength ]; + loc_sprintf_safe( szCraftNumber, LOCCHAR( "%i" ), unCraftIndex ); + + ILocalize::ConstructString_safe( szLocalizedCraftIndex, + pCraftNumberLocFormat, + 1, + szCraftNumber ); + } + } + + // Generate tool application string + enum { kToolApplicationNameLength = 128, }; + locchar_t szToolTargetNameName[ kToolApplicationNameLength ] = LOCCHAR(""); + locchar_t szDynamicRecipeOutputName[ kToolApplicationNameLength ] = LOCCHAR(""); + + static CSchemaAttributeDefHandle pAttribDef_ToolTarget( "tool target item" ); + if( pAttribDef_ToolTarget && pEconItem->GetItemDefinition()->GetItemClass() && !Q_stricmp( pEconItem->GetItemDefinition()->GetItemClass(), "tool" ) ) + { + // It's a tool, see if it has a tool target item attribute + float flItemDef; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttribDef_ToolTarget, &flItemDef ) ) + { + const item_definition_index_t unItemDef = flItemDef; + + locchar_t szTargetItemName[ MAX_ITEM_NAME_LENGTH ] = LOCCHAR("Unknown Item"); + + // Get base name of target item + const CEconItemDefinition *pEconTargetDef = GetItemSchema()->GetItemDefinition( unItemDef ); + if ( pEconTargetDef ) + { + GetLocalizedBaseItemName( szTargetItemName, pLocalizationProvider, pEconTargetDef ); + } + + loc_scpy_safe( szToolTargetNameName, + CConstructLocalizedString( pLocalizationProvider->Find( "ItemNameToolTargetNameFormat" ), + szTargetItemName ) ); + } + else + { + CRecipeComponentMatchingIterator componentIterator( pEconItem, NULL ); + pEconItem->IterateAttributes( &componentIterator ); + + // It only makes sense to mention the output if there's only 1 output + if( componentIterator.GetMatchingComponentOutputs().Count() == 1 ) + { + CAttribute_DynamicRecipeComponent attribValue; + pEconItem->FindAttribute( componentIterator.GetMatchingComponentOutputs().Head(), &attribValue ); + + CEconItem tempItem; + if ( !DecodeItemFromEncodedAttributeString( attribValue, &tempItem ) ) + { + AssertMsg2( 0, "%s: Unable to decode dynamic recipe output attribute on item %llu.", __FUNCTION__, pEconItem->GetID() ); + } + else + { + locchar_t loc_ItemName[MAX_ITEM_NAME_LENGTH]; + GenerateLocalizedFullItemName( loc_ItemName, pLocalizationProvider, &tempItem, k_EGenerateLocalizedFullItemName_Default, false ); + + loc_scpy_safe( szDynamicRecipeOutputName, + CConstructLocalizedString( pLocalizationProvider->Find( "ItemNameDynamicRecipeTargetNameFormat" ), + loc_ItemName ) ); + } + } + } + } + + + // PaintKit and Wear + if ( !bHasCustomName ) + { + CEconItemPaintKitDefinition *pPaintKit = pEconItem->GetCustomPainkKitDefinition(); + if ( pPaintKit ) + { + if ( bIgnoreNameWithPaintkit ) + { + Econ_SetNameAsPaintkit( szItemName, pLocalizationProvider, pPaintKit ); + } + else + { + Econ_ConcatPaintKitName( szItemName, pLocalizationProvider, pPaintKit ); + if ( !bIgnoreQualityAndWear ) + { + float flWear = 0; + if ( pEconItem->GetCustomPaintKitWear( flWear ) ) + { + Econ_ConcatPaintKitWear( szItemName, pLocalizationProvider, flWear ); + } + } + } + } + } + + locchar_t *pNameLocalizationFormat = pLocalizationProvider->Find( pszLocalizationPattern ); + + if ( pNameLocalizationFormat ) + { + ILocalize::ConstructString_safe( out_pItemName, + pNameLocalizationFormat, + 6, + szQuality, + szItemName, + szLocalizedCraftIndex, + szLocalizedCrateSeries, + szToolTargetNameName, + szDynamicRecipeOutputName); + } + else + { + loc_scpy_safe( out_pItemName, s_pUnknownItemName ); + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_ItemName( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + // If this item has a custom name, use it instead of doing our crazy name compositing based on quality, + // type, etc. + const char *utf8_CustomName = pEconItem->GetCustomName(); + + if ( utf8_CustomName && utf8_CustomName[0] ) + { + locchar_t loc_CustomName[ MAX_ITEM_NAME_LENGTH ]; + pLocalizationProvider->ConvertUTF8ToLocchar( utf8_CustomName, loc_CustomName, sizeof( loc_CustomName ) ); + + // Store it in the item name, wrapped in quotes to prevent item name spoofing + // We use two single quotes, because the double quote isn't very visible in the TF2 font + locchar_t loc_CustomNameWithQuotes[ MAX_ITEM_NAME_LENGTH ]; + loc_scpy_safe( loc_CustomNameWithQuotes, LOCCHAR("''") ); + loc_scat_safe( loc_CustomNameWithQuotes, loc_CustomName ); + loc_scat_safe( loc_CustomNameWithQuotes, LOCCHAR("''") ); + + AddDescLine( loc_CustomNameWithQuotes, /* this will be ignored: */ ATTRIB_COL_LEVEL, kDescLineFlag_Name ); + } + else + { + locchar_t loc_ItemName[MAX_ITEM_NAME_LENGTH]; + GenerateLocalizedFullItemName( loc_ItemName, pLocalizationProvider, pEconItem, k_EGenerateLocalizedFullItemName_WithPaintkitNoItem, m_pHashContext == NULL ); + + AddDescLine( loc_ItemName, /* this will be ignored: */ ATTRIB_COL_LEVEL, kDescLineFlag_Name ); + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +const locchar_t *GetLocalizedStringForKillEaterTypeAttr( const CLocalizationProvider *pLocalizationProvider, uint32 unKillEaterEventType ) +{ + Assert( pLocalizationProvider ); + + // Generate localized string. + const char *pszLocString = GetItemSchema()->GetKillEaterScoreTypeLocString( unKillEaterEventType ); + + return pszLocString != NULL + ? pLocalizationProvider->Find( pszLocString ) + : LOCCHAR(""); +} + +class CStrangeRestrictionAttrWrapper +{ +public: + CStrangeRestrictionAttrWrapper( const CLocalizationProvider *pLocalizationProvider, const locchar_t *loc_In ) + : m_str( loc_In ? pLocalizationProvider->Find( "ItemTypeDescStrangeFilterSubStr" ) : LOCCHAR(""), loc_In ? loc_In : LOCCHAR("") ) + { + // + } + + const locchar_t *operator *() const + { + return static_cast<const locchar_t *>( m_str ); + } + +private: + CConstructLocalizedString m_str; +}; + +const locchar_t *CEconItemDescription::GetLocalizedStringForStrangeRestrictionAttr( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, int iAttrIndex ) const +{ + uint32 unRestrictionType; + uint32 unRestrictionValue; + if ( !pEconItem->FindAttribute( GetKillEaterAttr_Restriction( iAttrIndex ), &unRestrictionType ) || + !pEconItem->FindAttribute( GetKillEaterAttr_RestrictionValue( iAttrIndex ), &unRestrictionValue ) || + unRestrictionType == kStrangeEventRestriction_None ) + { + return NULL; + } + + switch ( unRestrictionType ) + { +#ifdef PROJECT_TF + case kStrangeEventRestriction_Map: + { + const MapDef_t *pMap = GetItemSchema()->GetMasterMapDefByIndex( unRestrictionValue ); + if ( pMap ) + return pLocalizationProvider->Find( pMap->pszMapNameLocKey ); + } + case kStrangeEventRestriction_Competitive: + { + return pLocalizationProvider->Find( "ItemTypeDescStrangeFilterCompetitive" ); + } +#endif // PROJECT_TF + + case kStrangeEventRestriction_VictimSteamAccount: + return FindAccountPersonaName( unRestrictionValue ); + } + + return NULL; +} + +bool CEconItemDescription::BGenerate_ItemLevelDesc_StrangeNameAndStats( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, const locchar_t *locTypename ) +{ + CStrangeRankLocalizationGenerator RankGenerator( pLocalizationProvider, pEconItem, m_pHashContext == NULL ); + if ( !RankGenerator.IsValid() ) + return false; + + // For Collection Items + if ( pEconItem->GetCustomPainkKitDefinition() ) + { + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "Attrib_stattrakmodule" ), RankGenerator.GetRankLocalized() ), + ATTRIB_COL_STRANGE, + kDescLineFlag_Misc + ); + + // Are we tracking alternate stats as well? + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + const CEconItemAttributeDefinition *pKillEaterAltAttrDef = GetKillEaterAttr_Score( i ), + *pKillEaterAltScoreTypeAttrDef = GetKillEaterAttr_Type( i ); + if ( !pKillEaterAltAttrDef || !pKillEaterAltScoreTypeAttrDef ) + continue; + + uint32 unKillEaterAltScore; + if ( !pEconItem->FindAttribute( pKillEaterAltAttrDef, &unKillEaterAltScore ) ) + continue; + + // Older items can optionally not specify a type attribute at all and have an implicit "I'm tracking + // kills" zeroth attribute. We require a score type for any slot besides that. + if ( i != 0 && !pEconItem->FindAttribute( pKillEaterAltScoreTypeAttrDef ) ) + continue; + + const uint32 unKillEaterAltType = GetScoreTypeForKillEaterAttr( pEconItem, pKillEaterAltScoreTypeAttrDef ); + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "ItemTypeDescKillEaterAltv2" ), + unKillEaterAltScore, + GetLocalizedStringForKillEaterTypeAttr( pLocalizationProvider, unKillEaterAltType ), + *CStrangeRestrictionAttrWrapper( pLocalizationProvider, GetLocalizedStringForStrangeRestrictionAttr( pLocalizationProvider, pEconItem, i ) ) ), + ATTRIB_COL_LEVEL, + kDescLineFlag_Misc ); // strange item scores past the first are not considered part of the type + } + + return true; + } // End Collection Items + + // Normal old way + + // Look for Limited Item Attr + bool bLimitedQuantity = false; + static CSchemaAttributeDefHandle pAttrDef_LimitedQuantityItem( "limited quantity item" ); + bLimitedQuantity = pEconItem->FindAttribute( pAttrDef_LimitedQuantityItem ); + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "ItemTypeDescKillEater" ), + RankGenerator.GetRankLocalized(), + locTypename ? locTypename : LOCCHAR(""), + RankGenerator.GetStrangeScore(), + GetLocalizedStringForKillEaterTypeAttr( pLocalizationProvider, RankGenerator.GetStrangeType() ), + *CStrangeRestrictionAttrWrapper( pLocalizationProvider, GetLocalizedStringForStrangeRestrictionAttr( pLocalizationProvider, pEconItem, RankGenerator.GetUsedStrangeSlot() ) ), + RankGenerator.GetRankSecondaryLocalized() ? RankGenerator.GetRankSecondaryLocalized() : LOCCHAR(""), + bLimitedQuantity ? pLocalizationProvider->Find( "LimitedQualityDesc" ) : LOCCHAR("") + ), + bLimitedQuantity ? ATTRIB_COL_LIMITED_QUANTITY : ATTRIB_COL_LEVEL, + kDescLineFlag_Type ); + + // Are we tracking alternate stats as well? + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + const CEconItemAttributeDefinition *pKillEaterAltAttrDef = GetKillEaterAttr_Score(i), + *pKillEaterAltScoreTypeAttrDef = GetKillEaterAttr_Type(i); + if ( !pKillEaterAltAttrDef || !pKillEaterAltScoreTypeAttrDef ) + continue; + + uint32 unKillEaterAltScore; + if ( !pEconItem->FindAttribute( pKillEaterAltAttrDef, &unKillEaterAltScore ) ) + continue; + + // Older items can optionally not specify a type attribute at all and have an implicit "I'm tracking + // kills" zeroth attribute. We require a score type for any slot besides that. + if ( i != 0 && !pEconItem->FindAttribute( pKillEaterAltScoreTypeAttrDef ) ) + continue; + + const uint32 unKillEaterAltType = GetScoreTypeForKillEaterAttr( pEconItem, pKillEaterAltScoreTypeAttrDef ); + + // Skip if this is our primary stat and we already output it above. + if ( unKillEaterAltType == RankGenerator.GetStrangeType() ) + continue; + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "ItemTypeDescKillEaterAlt" ), + unKillEaterAltScore, + GetLocalizedStringForKillEaterTypeAttr( pLocalizationProvider, unKillEaterAltType ), + *CStrangeRestrictionAttrWrapper( pLocalizationProvider, GetLocalizedStringForStrangeRestrictionAttr( pLocalizationProvider, pEconItem, i ) ) ), + ATTRIB_COL_LEVEL, + kDescLineFlag_Misc ); // strange item scores past the first are not considered part of the type + } + + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +uint32 GetItemDescriptionDisplayLevel( const IEconItemInterface *pEconItem ) +{ + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttrDef_WideItemLevel( "wide item level" ); + + uint32 unWideLevelValue; + if ( pEconItem->FindAttribute( pAttrDef_WideItemLevel, &unWideLevelValue ) ) + return unWideLevelValue; + + return pEconItem->GetItemLevel(); +} + +void CEconItemDescription::Generate_ItemLevelDesc_Default( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, const locchar_t *locTypename ) +{ + // By default, items will only show the level if there is an item type to go along with it. + // Combined, these will build a string like "Level 10 Shotgun". We allow a custom attribute + // to force the level to be displayed by itself even if there is no item class ("Level 10"). + static CSchemaAttributeDefHandle pAttrDef_ForceLevelDisplay( "force_level_display" ); + + item_definition_index_t usDefIndex = pEconItem->GetItemDefIndex(); + + +#ifdef CLIENT_DLL + const bool bIsStoreItem = IsStorePreviewItem( pEconItem ); + const bool bIsPreviewItem = pEconItem->GetFlags() & kEconItemFlagClient_Preview; + + // If the item doesn't have a valid itemID, we'll just use the locTypename for the item level description. + // We don't want to display "Level 0 Hat" in places like the Mann Co. Store and Armory. We'll just display "Hat". + if ( bIsStoreItem || bIsPreviewItem || pEconItem->GetItemDefinition()->GetRarity() != k_unItemRarity_Any ) + { + if ( locTypename && *locTypename ) + { + AddDescLine( locTypename, ATTRIB_COL_LEVEL, kDescLineFlag_Type, NULL, usDefIndex ); + } + return; + } +#endif + + float fForceLevelDisplayValue; + bool bForceLevelDisplay = FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttrDef_ForceLevelDisplay, &fForceLevelDisplayValue ) + && fForceLevelDisplayValue > 0.0f; + + if ( ( locTypename && *locTypename ) || bForceLevelDisplay ) + { + if ( locTypename ) + { + // How are we going to format our level number and base type string? + const locchar_t *pszFormatString = NULL; + +#ifdef PROJECT_TF + static CSchemaAttributeDefHandle pAttrDef_OverrideItemLevelDescString( CTFItemSchema::k_rchOverrideItemLevelDescStringAttribName ); + static const char *s_pszCustomItemLevelDescLocalizationTokens[] = + { + "ItemTypeDescCustomLevelString_MvMTour", + }; + + // ...are we going to use a custom format string specified in an attribute? + uint32 unOverrideItemLevelDescString = 0; + if ( pEconItem->FindAttribute( pAttrDef_OverrideItemLevelDescString, &unOverrideItemLevelDescString ) + && unOverrideItemLevelDescString != 0 + && unOverrideItemLevelDescString <= ARRAYSIZE( s_pszCustomItemLevelDescLocalizationTokens ) ) + { + const char *pszLevelLocalizationToken = s_pszCustomItemLevelDescLocalizationTokens[ unOverrideItemLevelDescString - 1 ]; + Assert( pszLevelLocalizationToken ); + + pszFormatString = pLocalizationProvider->Find( pszLevelLocalizationToken ); + } +#endif // PROJECT_TF + + // Either we didn't have a custom override attribute, or we did and it had an invalid value, or it had a valid + // value but the localization system failed to find something for that key. In any event, we fall back to our default + // format string here. + if ( pszFormatString == NULL ) + { + bool bLimitedQuantity = false; + static CSchemaAttributeDefHandle pAttrDef_LimitedQuantityItem( "limited quantity item" ); + bLimitedQuantity = pEconItem->FindAttribute( pAttrDef_LimitedQuantityItem ); + +#if defined( TF_CLIENT_DLL ) + if ( pEconItem->GetItemDefinition()->GetItemClass() && V_strcmp( pEconItem->GetItemDefinition()->GetItemClass(), "map_token" ) == 0 ) + { + // For map stamps on the client we can show how many hours they've played each map + // And how many times they've donated to it instead of the generic "level" + for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ ) + { + const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i ); + + if ( pMap->mapStampDef != pEconItem->GetItemDefinition() ) + continue; + + int nItemLevel = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMap->pszMapName ); + + MapStats_t &mapStats = GetMapStats( pMap->GetStatsIdentifier() ); + int nNumHours = ( mapStats.accumulated.m_iStat[TFMAPSTAT_PLAYTIME] ) / ( 60 /*sec*/ * 60 /*min*/ ); + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "ItemTypeDescCustomLevelString_MapStamp" ), (uint32)nItemLevel, (uint32)nNumHours ), ATTRIB_COL_LEVEL, kDescLineFlag_Type ); + return; + } + } + else +#endif + { + if ( bLimitedQuantity ) + { + // Limited Item Description + pszFormatString = pLocalizationProvider->Find( "ItemTypeDescLimited" ); + AddDescLine( CConstructLocalizedString( + pszFormatString, + GetItemDescriptionDisplayLevel( pEconItem ), + locTypename, + pLocalizationProvider->Find( "LimitedQualityDesc" ) ), + ATTRIB_COL_LIMITED_QUANTITY, + kDescLineFlag_Type, + NULL, + usDefIndex + ); + return; + } + pszFormatString = pLocalizationProvider->Find( "ItemTypeDesc" ); + } + } + + // If we still don't have a format string here, it means our default also failed, but CConstructLocalizedString will + // handle that safely. + AddDescLine( CConstructLocalizedString( pszFormatString, GetItemDescriptionDisplayLevel( pEconItem ), locTypename ), ATTRIB_COL_LEVEL, kDescLineFlag_Type, NULL, usDefIndex ); + } + else + { + Assert( bForceLevelDisplay ); + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "ItemTypeDescNoLevel" ), GetItemDescriptionDisplayLevel( pEconItem ) ), ATTRIB_COL_LEVEL, kDescLineFlag_Type, NULL, usDefIndex ); + } + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_ItemLevelDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const locchar_t *locTypename = pLocalizationProvider->Find( pItemDef->GetItemTypeName() ); + + // Kill-eating weapons replace the standard weapon name/level line with a label + // describing the current class of the item instead of the level. This overrides + // even "force_level_display". + if ( BGenerate_ItemLevelDesc_StrangeNameAndStats( pLocalizationProvider, pEconItem, locTypename ) ) + return; + + // Not strange, but if you are paint kitted or have a collection reference dont create this + if ( pEconItem->GetCustomPainkKitDefinition() || pItemDef->GetCollectionReference() ) + return; + + // If we didn't generate a fancy strange name, we fall back to our default behavior. + Generate_ItemLevelDesc_Default( pLocalizationProvider, pEconItem, locTypename ); +} + +#if defined( STAGING_ONLY ) && defined( CLIENT_DLL ) +ConVar econ_include_debug_item_description( "econ_include_debug_item_description","0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Controls display of the additional debug fields in CEconItemDescription (definition index, etc.)." ); + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_DebugInformation( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + if ( !econ_include_debug_item_description.GetBool() ) + return; + +#if TF_ANTI_IDLEBOT_VERIFICATION + // Adding these extra description lines would mess with our GC/client sync. + if ( m_pHashContext ) + return; +#endif // TF_ANTI_IDLEBOT_VERIFICATION + + AddDescLine( CConstructLocalizedString( LOCCHAR("([ Item ID: %s1 ])"), pEconItem->GetID() ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + AddDescLine( CConstructLocalizedString( LOCCHAR("([ Item Definition Index: %s1 ])"), (uint32)pEconItem->GetItemDefIndex() ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + AddDescLine( CConstructLocalizedString( LOCCHAR("([ In Use?: %s1 ])"), pEconItem->GetInUse() ? LOCCHAR("true") : LOCCHAR("false") ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + AddEmptyDescLine(); + + class CDebugAttributeDisplayer : public IEconItemAttributeIterator + { + public: + CDebugAttributeDisplayer( CEconItemDescription *pOut_Desc ) : m_pDesc( pOut_Desc ) { Assert( m_pDesc ); } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) OVERRIDE + { + // Ugh. + wchar_t wszAttrDef[ 256 ]; + ILocalize::ConvertANSIToUnicode( pAttrDef->GetDefinitionName(), &wszAttrDef[0], sizeof( wszAttrDef ) ); + + m_pDesc->AddDescLine( CConstructLocalizedString( LOCCHAR("([ Attribute [%s1]: '%s2' ( %s3 | %s4 ) ])"), + (uint32)pAttrDef->GetDefinitionIndex(), + wszAttrDef, + *(uint32 *)&value, + *(float *)&value ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) OVERRIDE + { + // Ugh. + wchar_t wszAttrDef[ 256 ]; + ILocalize::ConvertANSIToUnicode( pAttrDef->GetDefinitionName(), &wszAttrDef[0], sizeof( wszAttrDef ) ); + + m_pDesc->AddDescLine( CConstructLocalizedString( LOCCHAR("([ Attribute [%s1]: '%s2' ( %f ) ])"), + (uint32)pAttrDef->GetDefinitionIndex(), + wszAttrDef, + value ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64& value ) OVERRIDE + { + // Ugh. + wchar_t wszAttrDef[ 256 ]; + ILocalize::ConvertANSIToUnicode( pAttrDef->GetDefinitionName(), &wszAttrDef[0], sizeof( wszAttrDef ) ); + + m_pDesc->AddDescLine( CConstructLocalizedString( LOCCHAR("([ Attribute [%s1]: '%s2' ( %llu ) ])"), + (uint32)pAttrDef->GetDefinitionIndex(), + wszAttrDef, + value ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& value ) OVERRIDE + { + // Ugh. + wchar_t wszAttrDef[ 256 ]; + ILocalize::ConvertANSIToUnicode( pAttrDef->GetDefinitionName(), &wszAttrDef[0], sizeof( wszAttrDef ) ); + + wchar_t wszAttrContents[ 1024 ]; + ILocalize::ConvertANSIToUnicode( value.value().c_str(), &wszAttrContents[0], sizeof( wszAttrContents ) ); + + m_pDesc->AddDescLine( CConstructLocalizedString( LOCCHAR("([ Attribute [%s1]: '%s2' ( \"%s3\" ) ])"), + (uint32)pAttrDef->GetDefinitionIndex(), + wszAttrDef, + wszAttrContents ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) OVERRIDE + { + const char* pszItemName = GetItemSchema()->GetItemDefinition( value.def_index() )->GetItemBaseName(); + + wchar_t wszItemQuality[ 256 ]; + ILocalize::ConvertANSIToUnicode( EconQuality_GetQualityString(EEconItemQuality(value.item_quality())), &wszItemQuality[0], sizeof( wszItemQuality ) ); + + wchar_t wszAttrString[ 256 ]; + ILocalize::ConvertANSIToUnicode( value.attributes_string().c_str(), &wszAttrString[0], sizeof( wszAttrString ) ); + + wchar_t wszCount[ 64 ]; + ILocalize::ConvertANSIToUnicode( CFmtStr( "%d/%d", value.num_fulfilled(), value.num_required() ), &wszCount[0], sizeof( wszCount ) ); + + locchar_t wszLocalizedItemName[ 128 ]; + const locchar_t *pLocalizedItemName = GLocalizationProvider()->Find( pszItemName ); + if ( pLocalizedItemName ) + { + V_wcscpy_safe( wszLocalizedItemName, pLocalizedItemName ); + } + else + { + // name wasn't found by Find(), so just convert the pszItemName + ILocalize::ConvertANSIToUnicode( pszItemName, &wszLocalizedItemName[0], sizeof( wszLocalizedItemName ) ); + } + + m_pDesc->AddDescLine( CConstructLocalizedString( LOCCHAR( "\nItem: \"%s1\"\nQuality: %s2\nAttribs:%s3\nCount: %s4" ), + wszLocalizedItemName, + wszItemQuality, + wszAttrString, + wszCount ), + ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria& value ) OVERRIDE + { + // Ugh. + wchar_t wszAttrDef[ 256 ]; + ILocalize::ConvertANSIToUnicode( pAttrDef->GetDefinitionName(), &wszAttrDef[0], sizeof( wszAttrDef ) ); + + wchar_t wszAttrTags[ 256 ]; + ILocalize::ConvertANSIToUnicode( value.tags().c_str(), &wszAttrTags[0], sizeof( wszAttrTags ) ); + + m_pDesc->AddDescLine( CConstructLocalizedString( LOCCHAR("([ Attribute [%s1]: '%s2'])\nTags: \"%s3\""), + (uint32)pAttrDef->GetDefinitionIndex(), + wszAttrDef, + wszAttrTags ), + ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement& value ) OVERRIDE + { + wchar_t wszAttrDef[ 256 ]; + ILocalize::ConvertANSIToUnicode( pAttrDef->GetDefinitionName(), &wszAttrDef[0], sizeof( wszAttrDef ) ); + + m_pDesc->AddDescLine( CConstructLocalizedString( LOCCHAR( "([ Attribute [%s1]: '%s2'])\n" ), + (uint32)pAttrDef->GetDefinitionIndex(), + wszAttrDef ), + ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + return true; + } + + private: + CEconItemDescription *m_pDesc; + }; + + CDebugAttributeDisplayer DebugAttributeDisplayer( this ); + pEconItem->IterateAttributes( &DebugAttributeDisplayer ); +} +#endif // defined( STAGING_ONLY ) && defined( CLIENT_DLL ) + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_CraftTag( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttribDef_MakersMarkId( "makers mark id" ); + + attrib_value_t value; + if ( !pEconItem->FindAttribute( pAttribDef_MakersMarkId, &value ) ) + return; + + AddAttributeDescription( pLocalizationProvider, pAttribDef_MakersMarkId, value ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_StyleDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const CEconStyleInfo *pStyle = pItemDef->GetStyleInfo( pEconItem->GetStyle() ); + if ( !pStyle ) + return; + + const locchar_t *loc_StyleName = pLocalizationProvider->Find( pStyle->GetName() ); + if ( !loc_StyleName ) + return; + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "#Econ_Style_Desc" ), loc_StyleName ), ATTRIB_COL_LEVEL, kDescLineFlag_Misc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_HolidayRestriction( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const char *pszHolidayRestriction = pItemDef->GetHolidayRestriction(); + if ( !pszHolidayRestriction ) + return; + + // Report any special restrictions. We'll output in a different color depending on whether or not + // the restriction currently prevents the item from showing up. + LocalizedAddDescLine( pLocalizationProvider, + CFmtStr( "Econ_holiday_restriction_%s", pszHolidayRestriction ).Access(), + EconHolidays_IsHolidayActive( EconHolidays_GetHolidayForString( pszHolidayRestriction ), CRTime::RTime32TimeCur() ) ? ATTRIB_COL_LEVEL : ATTRIB_COL_NEGATIVE, + kDescLineFlag_Misc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_QualityDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + // Does this item quality have additional description information that goes along with + // besides the usual name/coloration changes? + const char *pszQualityDescLocalizationKey = NULL; + + switch( pEconItem->GetQuality() ) + { + case AE_SELFMADE: + pszQualityDescLocalizationKey = "Attrib_Selfmade_Description"; + break; + case AE_COMMUNITY: + pszQualityDescLocalizationKey = "Attrib_Community_Description"; + break; + } + + // We don't need to do anything special. + if ( !pszQualityDescLocalizationKey ) + return; + + // If this item has a particle system attached but doesn't have the attribute that we usually use + // to attach particles, we hack it and dump out an extra line to show the particle system description + // as well. + static CSchemaAttributeDefHandle pAttrDef_ParticleEffect( "attach particle effect" ); + static attachedparticlesystem_t *pSparkleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( "community_sparkle" ); + + // If the schema understands these properties... + if ( pAttrDef_ParticleEffect && pSparkleSystem ) + { + // ...and we don't have a real particle effect attribute attribute... + if ( !pEconItem->FindAttribute( pAttrDef_ParticleEffect ) ) + { + // check for Unusual Cap def index (1173) + // We manually assign unusual effect to content author. No community sparkle + if ( pEconItem->GetItemDefIndex() != 1173 ) + { + // ...then manually add the description as if we did. + float flSystemID = pSparkleSystem->nSystemID; + AddAttributeDescription( pLocalizationProvider, pAttrDef_ParticleEffect, *(uint32*)&flSystemID ); + } + } + } + + LocalizedAddDescLine( pLocalizationProvider, pszQualityDescLocalizationKey, ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); +} + +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_ItemRarityDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pEconItem->GetItemDefinition()->GetRarity() ); + if ( !pItemRarity ) + return; + + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const char *pszTooltip = "TFUI_InvTooltip_Rarity"; + + attrib_colors_t colorRarity = pItemRarity->GetAttribColor(); + + const locchar_t *loc_RarityText = pLocalizationProvider->Find( pItemRarity->GetLocKey() ); + const locchar_t *locTypename = pLocalizationProvider->Find( pItemDef->GetItemTypeName() ); + const locchar_t *loc_WearText = LOCCHAR(""); + + float flWear = 0; + if ( pEconItem->GetCustomPaintKitWear( flWear ) ) + { + loc_WearText = pLocalizationProvider->Find( GetWearLocalizationString( flWear ) ); + } + else + { + pszTooltip = "TFUI_InvTooltip_RarityNoWear"; + } + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( pszTooltip ), loc_RarityText, locTypename, loc_WearText ), colorRarity, kDescLineFlag_Misc ); +} + + +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_WearAmountDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + if ( pEconItem->GetCustomPainkKitDefinition() == 0 ) + return; + + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + float flWear = 0; + if ( pEconItem->GetCustomPaintKitWear( flWear ) ) + { + locchar_t loc_WearText[MAX_ATTRIBUTE_DESCRIPTION_LENGTH]; + + loc_scpy_safe( loc_WearText, pLocalizationProvider->Find( "#TFUI_InvTooltip_Wear" ) ); + loc_scat_safe( loc_WearText, LOCCHAR( " " ) ); + loc_scat_safe( loc_WearText, pLocalizationProvider->Find( GetWearLocalizationString( flWear ) ) ); + + AddDescLine( loc_WearText, ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_ItemDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + // Show the custom description if it has one. + const char *utf8_CustomDesc = pEconItem->GetCustomDesc(); + if ( utf8_CustomDesc && utf8_CustomDesc[0] ) + { + locchar_t loc_CustomDesc[ MAX_ITEM_DESC_LENGTH ]; + pLocalizationProvider->ConvertUTF8ToLocchar( utf8_CustomDesc, loc_CustomDesc, sizeof( loc_CustomDesc ) ); + + locchar_t loc_CustomDescWithQuotes[ MAX_ITEM_DESC_LENGTH ]; + loc_scpy_safe( loc_CustomDescWithQuotes, LOCCHAR("''") ); + loc_scat_safe( loc_CustomDescWithQuotes, loc_CustomDesc ); + loc_scat_safe( loc_CustomDescWithQuotes, LOCCHAR("''") ); + + AddDescLine( loc_CustomDescWithQuotes, ATTRIB_COL_NEUTRAL, kDescLineFlag_Desc ); + return; + } + + // No custom description -- see if the item has a default description as part of the definition. + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + // Add any additional item description + if ( pItemDef->GetItemDesc() ) + { + LocalizedAddDescLine( pLocalizationProvider, pItemDef->GetItemDesc(), ATTRIB_COL_NEUTRAL, kDescLineFlag_Desc ); + } + + // If we're a store preview item, show the available styles in the tooltip so potential buyers + // have more information. + if ( IsStorePreviewItem( pEconItem ) ) + { + if ( pItemDef && pItemDef->GetNumStyles() > 0 ) + { + AddEmptyDescLine(); + LocalizedAddDescLine( pLocalizationProvider, "#Store_AvailableStyles_Header", ATTRIB_COL_LEVEL, kDescLineFlag_Misc ); + + for ( int i = 0; i < pItemDef->GetNumStyles(); i++ ) + { + LocalizedAddDescLine( pLocalizationProvider, pItemDef->GetStyleInfo( i )->GetName(), ATTRIB_COL_LEVEL, kDescLineFlag_Misc ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +// If we have at least this many items in our bundle then display multiple entries +// per line. Otherwise display one item per line for clarity. +enum { kDescription_CompositeBundleEntriesCount = 15 }; + +void CEconItemDescription::Generate_Bundle( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const bundleinfo_t *pBundleInfo = pItemDef->GetBundleInfo(); + if ( !pBundleInfo ) + return; + + enum EBundleEntryDisplayStyle + { + kBundleDisplay_SingleEntry, // one entry per line + kBundleDisplay_PairEntry, // "Some Item, Some Other Item," (with ending comma) + kBundleDisplay_PairEntryFinal, // "Some Item, Some Other Item" (with no ending comma) + }; + +#ifdef GC_DLL + AddEmptyDescLine(); +#endif // GC_DLL + + CUtlVector< item_definition_index_t > vecPackBundlesAdded; + + FOR_EACH_VEC( pBundleInfo->vecItemDefs, i ) + { + // Sanity check. + const CEconItemDefinition *pBundleItemDef = pBundleInfo->vecItemDefs[i]; + if ( !pBundleItemDef ) + continue; + + // If the current item is part of a pack bundle, add the pack bundle to the description, rather than the individual item +#ifdef DOTA + if ( pBundleItemDef->IsPackItem() ) + { + const CUtlVector< CEconItemDefinition * > &vecPackBundleItemDefs = pBundleItemDef->GetOwningPackBundles(); + + item_definition_index_t usPackBundleItemDefIndex = vecPackBundleItemDefs[i]->GetDefinitionIndex(); + if ( vecPackBundlesAdded.HasElement( usPackBundleItemDefIndex ) ) + continue; + + // Remember the def index so we don't add the reference to the pack bundle more than once + vecPackBundlesAdded.AddToTail( usPackBundleItemDefIndex ); + + // Now, point pBundleItemDef at the pack bundle itself and carry on + pBundleItemDef = pPackBundleItemDef; + } +#endif + + // Figure out which display style to use for this item. By default we put one item one each line... + EBundleEntryDisplayStyle eDisplayStyle = kBundleDisplay_SingleEntry; + + // ...but if we have a whole bunch of items in a single bundle, we lump them together two per line to + // save space. Only do this on the client. On the GC, use single lines so that link meta data can be passed + // along per-line to the store bundle pages. +#if defined( CLIENT_DLL ) && !defined( TF_CLIENT_DLL ) + if ( pBundleInfo->vecItemDefs.Count() >= kDescription_CompositeBundleEntriesCount ) + { + const int iRemainingItems = pBundleInfo->vecItemDefs.Count() - i; + + // We distinguish between "there are at least three entries left", which means we'll end the line + // with a comma, etc. + if ( iRemainingItems > 2 ) + { + eDisplayStyle = kBundleDisplay_PairEntry; + } + // ...or if these are our very last two items, we list our last two items and that's it. + else if ( iRemainingItems == 2 ) + { + eDisplayStyle = kBundleDisplay_PairEntryFinal; + } + } +#endif + + if ( eDisplayStyle == kBundleDisplay_SingleEntry ) + { + // pBundleItemDef will point at the pack bundle if pBundleItemDef is a pack item. In DotA, pack bundles *only* include pack items, whereas in TF, there are bundles which include some items where are individually for sale and others that are not. For example, the Scout Starter Bundle, etc. +#ifdef DOTA + LocalizedAddDescLine( pLocalizationProvider, pBundleItemDef->GetItemBaseName(), ATTRIB_COL_BUNDLE_ITEM, kDescLineFlag_Misc, NULL, pBundleItemDef->GetDefinitionIndex() ); +#else + LocalizedAddDescLine( pLocalizationProvider, pBundleItemDef->GetItemBaseName(), pBundleItemDef->IsPackItem() ? ATTRIB_COL_NEUTRAL : ATTRIB_COL_BUNDLE_ITEM, kDescLineFlag_Misc, NULL, pBundleItemDef->IsPackItem() ? INVALID_ITEM_DEF_INDEX : pBundleItemDef->GetDefinitionIndex(), !pBundleItemDef->IsPackItem() ); +#endif + } + else + { + Assert( eDisplayStyle == kBundleDisplay_PairEntry || eDisplayStyle == kBundleDisplay_PairEntryFinal ); + + const CEconItemDefinition *pOtherBundleItem = pBundleInfo->vecItemDefs[i + 1]; + const char *pOtherBundleItemBaseName = pOtherBundleItem ? pOtherBundleItem->GetItemBaseName() : ""; + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( eDisplayStyle == kBundleDisplay_PairEntryFinal ? "Econ_Bundle_Double" : "Econ_Bundle_DoubleContinued" ), + pLocalizationProvider->Find( pBundleItemDef->GetItemBaseName() ), + pLocalizationProvider->Find( pOtherBundleItemBaseName ) ), + ATTRIB_COL_BUNDLE_ITEM, + kDescLineFlag_Misc, + NULL, + pBundleItemDef->GetDefinitionIndex() ); + + // We consumed a second element as well. + i++; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_GiftedBy( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttrDef_GiftedBy( "gifter account id" ); + static CSchemaAttributeDefHandle pAttrDef_EventDate( "event date" ); + + attrib_value_t val_GifterId; + if ( pAttrDef_GiftedBy && pEconItem->FindAttribute( pAttrDef_GiftedBy, &val_GifterId ) ) + { + // Who gifted us this present? + AddAttributeDescription( pLocalizationProvider, pAttrDef_GiftedBy, val_GifterId ); + + // Do we also have (optional) information about when it happened? + attrib_value_t val_EventData; + if ( pAttrDef_EventDate && pEconItem->FindAttribute( pAttrDef_EventDate, &val_EventData ) ) + { + AddAttributeDescription( pLocalizationProvider, pAttrDef_EventDate, val_EventData ); + } + } +} + +#ifdef PROJECT_TF +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static bool IsDuelingMedal( const GameItemDefinition_t *pItemDef ) +{ + static CSchemaItemDefHandle pAttrDef_DuelingMedals[] = + { + CSchemaItemDefHandle( "Duel Medal Bronze" ), + CSchemaItemDefHandle( "Duel Medal Silver" ), + CSchemaItemDefHandle( "Duel Medal Gold" ), + CSchemaItemDefHandle( "Duel Medal Plat" ), + }; + + Assert( pItemDef ); + + for ( int i = 0; i < ARRAYSIZE( pAttrDef_DuelingMedals ); i++ ) + if ( pItemDef == pAttrDef_DuelingMedals[i] ) + return true; + + return false; +} + +void CEconItemDescription::Generate_DuelingMedal( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttrDef_EventDate( "event date" ); + + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + if ( !IsDuelingMedal( pItemDef ) ) + return; + + const CTFDuelSummary *pDuelSummary = FindAccountTypeCacheSingleton<CTFDuelSummary>( pEconItem->GetAccountID(), CTFDuelSummary::k_nTypeID ); + if ( !pDuelSummary ) + return; + + // Add the date received first. + attrib_value_t value; + if ( !pEconItem->FindAttribute( pAttrDef_EventDate, &value ) ) + return; + + // We feed our format-string parameters in via KeyValues. + KeyValues *pKeyValues = new KeyValues( "DuelStrings" ); + + CLocalizedRTime32 time = { pDuelSummary->Obj().last_duel_timestamp(), false, pLocalizationProvider TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( m_pHashContext ) }; + + TypedKeyValuesStringHelper<locchar_t>::Write( pKeyValues, "last_date", CLocalizedStringArg<CLocalizedRTime32>( time ).GetLocArg() ); + TypedKeyValuesStringHelper<locchar_t>::Write( pKeyValues, "wins", CLocalizedStringArg<uint32>( pDuelSummary->Obj().duel_wins() ).GetLocArg() ); + TypedKeyValuesStringHelper<locchar_t>::Write( pKeyValues, "last_target", FindAccountPersonaName( pDuelSummary->Obj().last_duel_account_id() ) ); + + // What happened in our last duel? This will be used as a format string to wrap the above data. + const char *pszTextFormat; + switch ( pDuelSummary->Obj().last_duel_status() ) + { + case kDuelStatus_Loss: + pszTextFormat = "#TF_Duel_Desc_Lost"; + break; + case kDuelStatus_Tie: + pszTextFormat = "#TF_Duel_Desc_Tied"; + break; + case kDuelStatus_Win: + default: + pszTextFormat = "#TF_Duel_Desc_Won"; + break; + } + + // Output our whole description. + AddEmptyDescLine(); + AddAttributeDescription( pLocalizationProvider, pAttrDef_EventDate, value ); + AddEmptyDescLine(); + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( pszTextFormat ), pKeyValues ), ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + + pKeyValues->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_MapContributor( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaItemDefHandle pItemDef_WorldTraveler( "World Traveler" ); + if ( !pItemDef_WorldTraveler || pEconItem->GetItemDefinition() != pItemDef_WorldTraveler ) + return; + + GCSDK::CSharedObjectTypeCache *pTypeCache = FindAccountTypeCache( pEconItem->GetAccountID(), CTFMapContribution::k_nTypeID ); + if ( !pTypeCache ) + return; + + static const char *kDonationLevels[] = + { + "#TF_MapDonationLevel_Bronze", + "#TF_MapDonationLevel_Silver", + "#TF_MapDonationLevel_Gold", + "#TF_MapDonationLevel_Platinum", + "#TF_MapDonationLevel_Diamond", + "#TF_MapDonationLevel_Australium1", + "#TF_MapDonationLevel_Australium2", + "#TF_MapDonationLevel_Australium3", + "#TF_MapDonationLevel_Unobtainium" + }; + const int kNumDonationLevels = ARRAYSIZE( kDonationLevels ); + const int kNumDonationsPerLevel = 25; + + CUtlVector<const CTFMapContribution *> vecContributionsPerLevel[ kNumDonationLevels ]; + + for ( uint32 i = 0; i < pTypeCache->GetCount(); ++i ) + { + CTFMapContribution *pMapContribution = (CTFMapContribution*)( pTypeCache->GetObject( i ) ); + const CEconItemDefinition *pMapItemDef = GetItemSchema()->GetItemDefinition( pMapContribution->Obj().def_index() ); + if ( pMapItemDef ) + { + int iLevel = MIN( pMapContribution->Obj().contribution_level() / kNumDonationsPerLevel, kNumDonationLevels - 1 ); + vecContributionsPerLevel[iLevel].AddToTail( pMapContribution ); + } + } + for ( int i = 0; i < kNumDonationLevels; ++i ) + { + const CUtlVector<const CTFMapContribution *>& vecContributions = vecContributionsPerLevel[i]; + if ( vecContributions.Count() > 0 ) + { + // Add header like "Silver:" to show the level of contribution for each of the maps following. + LocalizedAddDescLine( pLocalizationProvider, kDonationLevels[i], ATTRIB_COL_ITEMSET_NAME, kDescLineFlag_Misc ); + + // Add a label showing the map names and number of contributions for each map. + locchar_t tempDescription[MAX_ITEM_DESCRIPTION_LENGTH] = { 0 }; + FOR_EACH_VEC( vecContributions, j ) + { + const CTFMapContribution *pMapContribution = vecContributions[j]; + const CEconItemDefinition *pMapItemDef = GetItemSchema()->GetItemDefinition( pMapContribution->Obj().def_index() ); + Assert( pMapItemDef ); + + const char *pszMapNameLocalizationToken = pMapItemDef->GetDefinitionString( "map_name", NULL ); + if ( pszMapNameLocalizationToken ) + { + loc_sncat( tempDescription, + CConstructLocalizedString( pLocalizationProvider->Find( "#Attrib_MapDonation" ), + pLocalizationProvider->Find( pszMapNameLocalizationToken ), + (uint32)pMapContribution->Obj().contribution_level() ), + MAX_ITEM_DESCRIPTION_LENGTH ); + + if ( j < ( vecContributions.Count() - 1 ) ) + { + loc_sncat( tempDescription, LOCCHAR( ", " ), MAX_ITEM_DESCRIPTION_LENGTH ); + } + } + } + + if ( tempDescription[0] ) + { + AddDescLine( tempDescription, ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_FriendlyHat( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaItemDefHandle pItemDef_FriendlyHat( "Friendly Item" ); + if ( !pItemDef_FriendlyHat || pEconItem->GetItemDefinition() != pItemDef_FriendlyHat ) + return; + + const CTFPlayerInfo *pPlayerInfo = FindAccountTypeCacheSingleton<CTFPlayerInfo>( pEconItem->GetAccountID(), CTFPlayerInfo::k_nTypeID ); + if ( !pPlayerInfo ) + return; + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "#Attrib_NewUsersHelped" ), (uint32)pPlayerInfo->Obj().num_new_users_helped() ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_SaxxyAwardDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + // Don't display anything for items besides the Saxxy itself. + static CSchemaItemDefHandle pItemDef_Saxxy( "Saxxy" ); + static CSchemaItemDefHandle pItemDef_MemoryMaker( "The Memory Maker" ); + if ( ( !pItemDef_Saxxy || pEconItem->GetItemDefinition() != pItemDef_Saxxy ) && + ( !pItemDef_MemoryMaker || pEconItem->GetItemDefinition() != pItemDef_MemoryMaker ) ) + { + return; + } + + // Output our award category if present, or abort if absent. + static CSchemaAttributeDefHandle pAttrDef_SaxxyAwardCategory( "saxxy award category" ); + static CSchemaAttributeDefHandle pAttrDef_EventDate( "event date" ); + + uint32 unAwardCategory, + unEventDate; + if ( !pEconItem->FindAttribute( pAttrDef_SaxxyAwardCategory, &unAwardCategory ) || + !pEconItem->FindAttribute( pAttrDef_EventDate, &unEventDate ) ) + { + return; + } + + CRTime cTime( unEventDate ); + cTime.SetToGMT( false ); + + const char *pszFormatString = "#Attrib_SaxxyAward"; + if ( pEconItem->GetItemDefinition() == pItemDef_MemoryMaker ) + { + pszFormatString = "#Attrib_MemoryMakerAward"; + } + + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( pszFormatString ), + pLocalizationProvider->Find( CFmtStr( "Replay_Contest_Category%d", unAwardCategory ).Access() ), + (uint32)cTime.GetYear() ), + ATTRIB_COL_POSITIVE, + kDescLineFlag_Misc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_MvmChallenges( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + // Look for our "challenges completed" attribute. If we have this, we assume we're a badge + // of some kind. If we don't, we don't display MvM information. This would be a little weird + // for level 0 badges that have no completed challenges, but those are something that currently + // exist. + static CSchemaAttributeDefHandle pAttrDef_ChallengesCompleted( CTFItemSchema::k_rchMvMChallengeCompletedMaskAttribName ); + + uint32 unMask = 0; + if ( !pEconItem->FindAttribute( pAttrDef_ChallengesCompleted, &unMask ) ) + return; + + // Look through our list of MvM tours to figure out which badge this came from. The badge itself + // doesn't know and we need this information to figure out which completion bits map to which + // missions. + const MvMTour_t *pTour = NULL; + + FOR_EACH_VEC( GetItemSchema()->GetMvmTours(), i ) + { + const MvMTour_t& tour = GetItemSchema()->GetMvmTours()[i]; + + if ( tour.m_pBadgeItemDef == pEconItem->GetItemDefinition() ) + { + pTour = &tour; + break; + } + } + + // Couldn't find a tour matching this badge? (This can happen if a client has a busted schema or if + // we remove a tour for some reason.) + if ( !pTour ) + return; + + const CUtlVector<MvMMission_t>& vecAllMissions = GetItemSchema()->GetMvmMissions(); + CUtlVector<int> vecCompletedMissions; + + FOR_EACH_VEC( pTour->m_vecMissions, i ) + { + // Make sure our mission index is valid based on our current schema. If we're a client playing a + // game during a GC roll, we could wind up looking at someone else's badge where they have a + // mission that we don't understand. + const int iMissionIndex = pTour->m_vecMissions[i].m_iMissionIndex; + if ( !vecAllMissions.IsValidIndex( iMissionIndex ) ) + continue; + + const int iBadgeSlot = pTour->m_vecMissions[i].m_iBadgeSlot; + if ( iBadgeSlot >= 0 && ((unMask & (1U << iBadgeSlot)) != 0) ) + { + vecCompletedMissions.AddToTail( iMissionIndex ); + } + } + + // Add a summary line for the number they have completed + AddDescLine( + CConstructLocalizedString( + pLocalizationProvider->Find( "#Attrib_MvMChallengesCompletedSummary" ), + uint32( vecCompletedMissions.Count() ) + ), + ATTRIB_COL_POSITIVE, + kDescLineFlag_Misc + ); + + // Detail lines for each completed challenge + FOR_EACH_VEC( vecCompletedMissions, i ) + { + const MvMMission_t& mission = vecAllMissions[ vecCompletedMissions[i] ]; + const MvMMap_t& map = GetItemSchema()->GetMvmMaps()[ mission.m_iDisplayMapIndex ]; + const locchar_t *pszLocFmt = pLocalizationProvider->Find( "#Attrib_MvMChallengeCompletedDetail" ); + const locchar_t *pszLocMap = pLocalizationProvider->Find( map.m_sDisplayName.Get() ); + const locchar_t *pszLocChal = pLocalizationProvider->Find( mission.m_sDisplayName.Get() ); + if ( pszLocFmt && pszLocMap && pszLocChal ) + { + CConstructLocalizedString locLine( + pszLocFmt, + pszLocMap, + pszLocChal + ); + AddDescLine( + locLine, + ATTRIB_COL_POSITIVE, + kDescLineFlag_Misc + ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_SquadSurplusClaimedBy( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttrDef_SquadSurplusClaimer( "squad surplus claimer id" ); + static CSchemaAttributeDefHandle pAttrDef_EventDate( "event date" ); + + attrib_value_t val_GifterId; + if ( pAttrDef_SquadSurplusClaimer&& pEconItem->FindAttribute( pAttrDef_SquadSurplusClaimer, &val_GifterId ) ) + { + // Who gifted us this present? + AddAttributeDescription( pLocalizationProvider, pAttrDef_SquadSurplusClaimer, val_GifterId ); + + // Do we also have (optional) information about when it happened? + attrib_value_t val_EventData; + if ( pAttrDef_EventDate && pEconItem->FindAttribute( pAttrDef_EventDate, &val_EventData ) ) + { + AddAttributeDescription( pLocalizationProvider, pAttrDef_EventDate, val_EventData ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_DynamicRecipe( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + // Gather our attributes we care about + CRecipeComponentMatchingIterator componentIterator( pEconItem, NULL ); + pEconItem->IterateAttributes( &componentIterator ); + + // Nothing to say, bail! + if( !componentIterator.GetMatchingComponentInputs().Count() && + !componentIterator.GetMatchingComponentOutputs().Count() ) + { + return; + } + + // Add the no partial complete tag if the attribute exists + static CSchemaAttributeDefHandle pAttrib_NoPartialComplete( "recipe no partial complete" ); + if ( pEconItem->FindAttribute( pAttrib_NoPartialComplete ) ) + { + LocalizedAddDescLine( pLocalizationProvider, "TF_ItemDynamic_Recipe_No_Partial_Completion", ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + } + + AddEmptyDescLine(); + + if ( componentIterator.GetMatchingComponentInputs().Count() ) + { + // Print out item input header + LocalizedAddDescLine( pLocalizationProvider, "TF_ItemDynamic_Recipe_Inputs", ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + // Print out inputs + FOR_EACH_VEC( componentIterator.GetMatchingComponentInputs(), i ) + { + CAttribute_DynamicRecipeComponent attribValue; + pEconItem->FindAttribute( componentIterator.GetMatchingComponentInputs()[i], &attribValue ); + + const GameItemDefinition_t *pItemDef = dynamic_cast<GameItemDefinition_t *>( GetItemSchema()->GetItemDefinition( attribValue.def_index() ) ); + if ( !pItemDef ) + continue; + + int nCount = attribValue.num_required() - attribValue.num_fulfilled(); + + // This is a completed component. We don't want to show it (for now) + if( nCount == 0 ) + continue; + + CEconItem tempItem; + if ( !DecodeItemFromEncodedAttributeString( attribValue, &tempItem ) ) + { + AssertMsg2( 0, "%s: Unable to decode dynamic recipe input attribute on item %llu.", __FUNCTION__, pEconItem->GetID() ); + continue; + } + + locchar_t lineItem[256]; + locchar_t loc_ItemName[MAX_ITEM_NAME_LENGTH]; + GenerateLocalizedFullItemName( loc_ItemName, pLocalizationProvider, &tempItem, k_EGenerateLocalizedFullItemName_Default, m_pHashContext == NULL ); + + loc_sprintf_safe( lineItem, + LOCCHAR("%s x %d"), + loc_ItemName, + nCount + ); + + AddDescLine( lineItem, ATTRIB_COL_ITEMSET_MISSING, kDescLineFlag_Misc ); + } + + AddEmptyDescLine(); + } + + // Print out outputs + LocalizedAddDescLine( pLocalizationProvider, "TF_ItemDynamic_Recipe_Outputs", ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + FOR_EACH_VEC( componentIterator.GetMatchingComponentOutputs(), i ) + { + CAttribute_DynamicRecipeComponent attribValue; + pEconItem->FindAttribute( componentIterator.GetMatchingComponentOutputs()[i], &attribValue ); + + CEconItem tempItem; + if ( !DecodeItemFromEncodedAttributeString( attribValue, &tempItem ) ) + { + AssertMsg2( 0, "%s: Unable to decode dynamic recipe output attribute on item %llu.", __FUNCTION__, pEconItem->GetID() ); + continue; + } + + locchar_t loc_ItemName[MAX_ITEM_NAME_LENGTH]; + GenerateLocalizedFullItemName( loc_ItemName, pLocalizationProvider, &tempItem, k_EGenerateLocalizedFullItemName_Default, m_pHashContext == NULL ); + + AddDescLine( loc_ItemName, /* this will be ignored: */ ATTRIB_COL_ITEMSET_MISSING, kDescLineFlag_Misc ); + + // Iterate through the attributes on this temp item and have it store the attributes that should affect + // this component's name. Once we have that, have it fill out a temporary CEconItemDescription. + CRecipeNameAttributeDisplayer recipeAttributeIterator; + tempItem.IterateAttributes( &recipeAttributeIterator ); + recipeAttributeIterator.SortAttributes(); + CEconItemDescription tempDescription; + recipeAttributeIterator.Finalize( &tempItem, &tempDescription, pLocalizationProvider ); + + // Check if that temp CEconItemDescription has any attributes we want. If so, steal them. + if ( tempDescription.m_vecDescLines.Count() > 0 ) + { + locchar_t loc_Attribs[MAX_ITEM_NAME_LENGTH] = LOCCHAR(""); + + // Put the attributes on the next line in parenthesis + loc_scat_safe( loc_Attribs, LOCCHAR("(") ); + + // Put in each attribute + FOR_EACH_VEC( tempDescription.m_vecDescLines, j ) + { + // Comma separated + if ( j > 0 ) + { + loc_scat_safe( loc_Attribs, LOCCHAR(", ") ); + } + + loc_sprintf_safe( loc_Attribs, + LOCCHAR("%s%s"), + loc_Attribs, + tempDescription.m_vecDescLines[j].sText.Get() ); + } + + loc_scat_safe( loc_Attribs, LOCCHAR(")") ); + + // Print out in the same color as the item name above + AddDescLine( loc_Attribs, /* this will be ignored: */ ATTRIB_COL_ITEMSET_MISSING, kDescLineFlag_Misc ); + } + } +} + +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_Leaderboard( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ +#ifdef GC_DLL + return; +#endif + +#ifdef TF_CLIENT_DLL + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttrDef_DisplayDuckLeaderboard( "display duck leaderboard" ); + + if ( pEconItem->FindAttribute( pAttrDef_DisplayDuckLeaderboard ) ) + { + // Friend Board + //locchar_t lineItem[256]; + // + //AddDescLine( pLocalizationProvider->Find( "#TF_DuckLeaderboard_Friends" ), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + // + //CUtlVector< LeaderboardEntry_t > scores; + //Leaderboards_GetDuckLeaderboard( scores, g_szDuckLeaderboardNames[0] ); + + //// Show max of top 10 + //for ( int i = 0; i < scores.Count() && i < 10; i++ ) + //{ + // const locchar_t *pName = FindAccountPersonaName( scores[i].m_steamIDUser.GetAccountID() ); + // uint32 iRank = scores[i].m_nGlobalRank; + // uint32 iScore = scores[i].m_nScore; + // + // AddDescLine( + // CConstructLocalizedString( + // pLocalizationProvider->Find( "#TF_DuckLeaderboard_Entry" ), + // iRank, pName, iScore + // ), + // ATTRIB_COL_POSITIVE, + // kDescLineFlag_Misc + // ); + //} + } +#endif // TF_CLIENT_DLL +} + +#endif // PROJECT_TF + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconItemDefinition *GetPaintItemDefinitionForPaintedItem( const IEconItemInterface *pEconItem ) +{ + static CSchemaAttributeDefHandle pAttribDef_Paint( "set item tint RGB" ); + + attrib_value_t unPaintRGBAttrBits; + if ( !pAttribDef_Paint || !pEconItem->FindAttribute( pAttribDef_Paint, &unPaintRGBAttrBits ) ) + return NULL; + + const CEconItemSchema::ToolsItemDefinitionMap_t &toolDefs = GetItemSchema()->GetToolsItemDefinitionMap(); + + FOR_EACH_MAP_FAST( toolDefs, i ) + { + const CEconItemDefinition *pItemDef = toolDefs[i]; + + // ignore everything that is not a paint can tool + const IEconTool *pEconTool = pItemDef->GetEconTool(); + if ( pEconTool && !V_strcmp( pEconTool->GetTypeName(), "paint_can" ) ) + { + attrib_value_t unPaintRGBAttrCompareBits; + if ( FindAttribute( pItemDef, pAttribDef_Paint, &unPaintRGBAttrCompareBits ) && unPaintRGBAttrCompareBits == unPaintRGBAttrBits ) + return pItemDef; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Specify target (strangifiers, etc that can only be applied to specific items) +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_XifierToolTargetItem( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + // Make sure it's a tool of the appropriate type + const CEconTool_Xifier *pTool = pEconItem->GetItemDefinition()->GetTypedEconTool<CEconTool_Xifier>(); + if ( pTool == NULL ) + return; + + // Make sure there is a specific target item + static CSchemaAttributeDefHandle pAttribDef_ToolTargetItem( "tool target item" ); + float flItemDef; + if( pAttribDef_ToolTargetItem && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttribDef_ToolTargetItem, &flItemDef ) ) + { + locchar_t szTargetItemName[ MAX_ITEM_NAME_LENGTH ] = LOCCHAR("Unknown Item"); + + // It's a tool, see if it has a tool target item attribute + const item_definition_index_t unItemDef = flItemDef; + const CEconItemDefinition *pEconTargetDef = GetItemSchema()->GetItemDefinition( unItemDef ); + + // Start with the base name. + if ( pEconTargetDef ) + { + GetLocalizedBaseItemName( szTargetItemName, pLocalizationProvider, pEconTargetDef ); + } + + const char *pszDesc = pTool->GetItemDescToolTargetLocToken(); + AssertMsg( pszDesc && *pszDesc, "%s: missing 'item_desc_tool_target' key", pTool->GetTypeName() ); + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( pszDesc ), + szTargetItemName ), + ATTRIB_COL_NEUTRAL, + kDescLineFlag_Desc ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_Painted( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttrDef_PaintEffect( "Paint Effect" ); + + float fPaintEffectType; + if ( pAttrDef_PaintEffect && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconItem, pAttrDef_PaintEffect, &fPaintEffectType ) ) + { + if ( fPaintEffectType == 1 ) + { + LocalizedAddDescLine( pLocalizationProvider, "Econ_Paint_Effect_Oscillating", ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + } + else if ( fPaintEffectType == 2 ) + { + LocalizedAddDescLine( pLocalizationProvider, "Econ_Paint_Effect_Position", ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + } + else if ( fPaintEffectType == 3 ) + { + LocalizedAddDescLine( pLocalizationProvider, "Econ_Paint_Effect_LowHealthWarning", ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + } + } + + // Find the name of the paint we have applied in the least efficient way imaginable! + const CEconItemDefinition *pItemDef = pEconItem->GetItemDefinition(); + static CSchemaAttributeDefHandle pAttrDef_ShowPaint( "show paint description" ); + if ( pItemDef && ( !pItemDef->IsTool() || FindAttribute( pEconItem, pAttrDef_ShowPaint ) ) ) + { + const CEconItemDefinition *pTempDef = GetPaintItemDefinitionForPaintedItem( pEconItem ); + if ( pTempDef ) + { + const locchar_t *locLocalizedPaintName = pLocalizationProvider->Find( pTempDef->GetItemBaseName() ); + + if ( locLocalizedPaintName ) + { + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "Econ_Paint_Name" ), + locLocalizedPaintName ), + ATTRIB_COL_LEVEL, + kDescLineFlag_Misc ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDescription::Generate_Uses( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + // don't display a quantity if we have the unlimited quantity attribute + static CSchemaAttributeDefHandle unlimitedQuantityAttribute( "unlimited quantity" ); + if ( pEconItem->FindAttribute( unlimitedQuantityAttribute ) ) + return; + + // Collection tools don't display this. + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const IEconTool *pEconTool = pItemDef->GetEconTool(); + if ( !pEconTool->ShouldDisplayQuantity( pEconItem ) ) + return; + + int iQuantity = pEconItem->GetQuantity(); + bool bIsTool = pItemDef->GetItemClass() && !Q_strcmp( pItemDef->GetItemClass(), "tool" ); + bool bIsConsumable = ( pItemDef->GetCapabilities() & ITEM_CAP_USABLE_GC ) != 0 && iQuantity != 0; + + if ( bIsTool || bIsConsumable ) + { + locchar_t wszQuantity[10]; + loc_sprintf_safe( wszQuantity, LOCCHAR( "%d" ), iQuantity ); + + // Add an empty line before the usage display. + AddEmptyDescLine(); + + // Display our usage count. + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "#Attrib_LimitedUse" ), &wszQuantity[0] ), ATTRIB_COL_LIMITED_USE, kDescLineFlag_Misc ); + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_LootListDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const CEconItemDefinition *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + // Don't add this description if the item is a special crate type. + const IEconTool *pEconTool = pItemDef->GetEconTool(); + const bool bIsRestrictedCrate = pEconTool && pEconTool->GetUsageRestriction() + ? !V_stricmp( pEconTool->GetUsageRestriction(), "winter" ) || !V_stricmp( pEconTool->GetUsageRestriction(), "summer" ) + : false; + + if ( bIsRestrictedCrate ) + return; + + // Do we have a generation code we want to make public? We do this regardless of whether we're describing our + // loot list contents in detail. + { + static CSchemaAttributeDefHandle pAttrDef_CrateGenerationCode( "crate generation code" ); + CAttribute_String sCrateGenerationCode; + + const locchar_t *pszCrateGenerationCodeLoc = pLocalizationProvider->Find( "Attrib_CrateGenerationCode" ); + + if ( pEconItem->FindAttribute( pAttrDef_CrateGenerationCode, &sCrateGenerationCode ) && sCrateGenerationCode.value().length() > 0 ) + { + CUtlConstStringBase<locchar_t> loc_sAttrValue; + pLocalizationProvider->ConvertUTF8ToLocchar( sCrateGenerationCode.value().c_str(), &loc_sAttrValue ); + + AddDescLine( CConstructLocalizedString( pszCrateGenerationCodeLoc, loc_sAttrValue.Get() ), + ATTRIB_COL_POSITIVE, + kDescLineFlag_Misc ); + } + } + + // Grab the actual contents of our loot list. + CCrateLootListWrapper LootListWrapper( pEconItem ); + const IEconLootList *pLootList = LootListWrapper.GetEconLootList(); + + if ( pLootList == nullptr ) + return; + + // If our base loot list is set not to list contents, skip the header/footer as well and don't display anything. + if ( !pLootList->BPublicListContents() ) + return; + + AddEmptyDescLine(); + + if ( !pLootList->GetLootListCollectionReference() ) + { + LocalizedAddDescLine( pLocalizationProvider, pLootList->GetLootListHeaderLocalizationKey(), ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + } + else + { + + int iCollectionIndex = GetItemSchema()->GetItemCollections().Find( pLootList->GetLootListCollectionReference() ); + if ( GetItemSchema()->GetItemCollections().IsValidIndex( iCollectionIndex ) ) + { + LocalizedAddDescLine( pLocalizationProvider, (GetItemSchema()->GetItemCollections()[iCollectionIndex])->m_pszLocalizedDesc, ATTRIB_COL_NEUTRAL, kDescLineFlag_Misc ); + } + } + + class CDescriptionLootListIterator : public IEconLootList::IEconLootListIterator + { + public: + CDescriptionLootListIterator( CEconItemDescription *pThis, const CLocalizationProvider *pLocalizationProvider, bool bUseProperName ) + : m_pThis( pThis ) + , m_pLocalizationProvider( pLocalizationProvider ) + , m_bUseProperName( bUseProperName ) + { + } + + virtual void OnIterate( item_definition_index_t unItemDefIndex ) OVERRIDE + { + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( unItemDefIndex ); + if ( pItemDef ) + { + // Check if this item is already owned + bool bOwned = false; + bool bUnusual = false; +#ifdef CLIENT_DLL + CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); + if ( pLocalInv ) + { + for ( int i = 0; i < pLocalInv->GetItemCount(); ++i ) + { + CEconItemView *pItem = pLocalInv->GetItem( i ); + if ( pItem->GetItemDefinition() == pItemDef ) + { + bOwned = true; + // Check Quality + if ( pItem->GetQuality() == AE_UNUSUAL ) + { + bUnusual = true; + break; + } + } + } + } +#endif + const locchar_t * pCheckmark = bOwned ? m_pLocalizationProvider->Find( "TF_Checkmark" ) : m_pLocalizationProvider->Find( "TF_LackOfCheckmark" ); + if ( bOwned && bUnusual ) + { + pCheckmark = m_pLocalizationProvider->Find( "TF_Checkmark_Unusual" ); + } + + attrib_colors_t colorRarity = ATTRIB_COL_RARITY_DEFAULT; + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItemDef->GetRarity() ); + if ( pItemRarity ) + { + colorRarity = pItemRarity->GetAttribColor(); + } + + m_pThis->AddDescLine( + CConstructLocalizedString( LOCCHAR( "%s1%s2" ), pCheckmark, + CEconItemLocalizedFullNameGenerator( + m_pLocalizationProvider, + pItemDef, + m_bUseProperName + ).GetFullName() ), + colorRarity, + kDescLineFlag_Misc, + NULL, + pItemDef->GetDefinitionIndex() + ); + } + } + + private: + CEconItemDescription *m_pThis; // look at me I'm a lambda! + const CLocalizationProvider *m_pLocalizationProvider; + bool m_bUseProperName; + }; + + CDescriptionLootListIterator it( this, pLocalizationProvider, m_pHashContext == NULL ); + pLootList->EnumerateUserFacingPotentialDrops( &it ); + + if ( pLootList->GetLootListFooterLocalizationKey() ) + { + LocalizedAddDescLine( pLocalizationProvider, pLootList->GetLootListFooterLocalizationKey(), ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + } + else + { + const char *pszRareLootListFooterLocalizationKey = pItemDef->GetDefinitionString( "loot_list_rare_item_footer", "#Econ_Revolving_Loot_List_Rare_Item" ); + LocalizedAddDescLine( pLocalizationProvider, pszRareLootListFooterLocalizationKey, ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + } +} + +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_EventDetail( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const CEconItemDefinition *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + // Check to see if we should append any extra description information + const char *pszEventLocalizationKey = pItemDef->GetDefinitionString( "event_desc_footer", NULL ); + if ( pszEventLocalizationKey ) + { + AddEmptyDescLine(); + LocalizedAddDescLine( pLocalizationProvider, pszEventLocalizationKey, ATTRIB_COL_POSITIVE, kDescLineFlag_Misc ); + } +} +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +#ifdef CLIENT_DLL + +static bool IsItemEquipped( uint32 unAccountID, const CEconItemDefinition *pSearchItemDef, const GameItemDefinition_t **ppFoundSetItemDef ) +{ + Assert( pSearchItemDef ); + Assert( ppFoundSetItemDef ); + + CPlayerInventory *pInv = InventoryManager()->GetInventoryForAccount( unAccountID ); + if ( !pInv ) + return false; + + for ( int i = 0; i < pInv->GetItemCount(); i++ ) + { + const CEconItemView *pInvItem = pInv->GetItem( i ); + if ( !pInvItem ) + continue; + + // This code is client-only so we expect to always get back an item definition pointer. + const GameItemDefinition_t *pInvItemDef = pInvItem->GetItemDefinition(); + Assert( pInvItemDef ); + + if ( pInvItemDef->GetSetItemRemap() != pSearchItemDef->GetDefinitionIndex() ) + continue; + + if ( !pInvItem->IsEquipped() ) + continue; + + *ppFoundSetItemDef = pInvItemDef; + return true; + } + + return false; +} + +#endif // CLIENT_DLL + +void CEconItemDescription::Generate_ItemSetDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const GameItemDefinition_t *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const CEconItemSetDefinition *pItemSetDef = pItemDef->GetItemSetDefinition(); + if ( !pItemSetDef ) + return; + + bool bAllItemsEquipped = true; // filled in below when iterating over items + + // Some item sets are tagged to only appear on items at all if the entire set is visible. Rather than + // walk the whole set multiple times checking for item equipped state, we build up a set of description lines + // that we *will* display if we display anything at all. Later, we either submit all those lines for real or + // return before adding any of them. + { + CUtlVector<econ_item_description_line_t> vecPotentialDescLines; + + AddEmptyDescLine( &vecPotentialDescLines ); + LocalizedAddDescLine( pLocalizationProvider, pItemSetDef->m_pszLocalizedName, ATTRIB_COL_ITEMSET_NAME, kDescLineFlag_Set | kDescLineFlag_SetName, &vecPotentialDescLines, pItemSetDef->m_iBundleItemDef ); + + // Kyle says: Jon wants different formatting on the GC for sets. +#if defined( GC_DLL ) +#if TF_ANTI_IDLEBOT_VERIFICATION + if ( !m_pHashContext ) +#endif // TF_ANTI_IDLEBOT_VERIFICATION + { + AddEmptyDescLine( &vecPotentialDescLines ); + } +#endif // defined( GC_DLL ) && + + // Iterate over the items in the set. We'll output a line in different colors to show + // the current state of this item. For normal item sets, the color is based on whether + // the owner has the item in question equipped (except on the GC, where we always say + // "it's not equipped" to avoid confusion). For collections, the color is based on whether + // the item has been collected. + FOR_EACH_VEC( pItemSetDef->m_iItemDefs, i ) + { + const GameItemDefinition_t *pOtherSetItem = dynamic_cast<const GameItemDefinition_t *>( GetItemSchema()->GetItemDefinition( pItemSetDef->m_iItemDefs[i] ) ); + if ( !pOtherSetItem ) + continue; + + item_definition_index_t usLinkItemDefIndex = pOtherSetItem->GetDefinitionIndex(); + +#ifdef DOTA + // If the current item is part of a pack bundle, add the pack bundle to the description, rather than the individual item + if ( pOtherSetItem->IsPackItem() ) + { + // Link to the pack bundle, not the individual pack item + usLinkItemDefIndex = pOtherSetItem->GetOwningPackBundle()->GetDefinitionIndex(); + } +#endif + + // Only used on non-GC in case we have an item misrepresenting itself intentionally for set + // grouping purposes. NULL elsewhere. + const GameItemDefinition_t *pFoundSetItemDef = NULL; + + const bool bItemPresent = +#ifdef GC_DLL + false; // the GC always treats set items as unequipped for clarity in trading +#else +#if TF_ANTI_IDLEBOT_VERIFICATION + m_pHashContext + ? false // when generating descriptions for GC verification we treat item sets as unequipped to match the GC +#endif + : IsItemEquipped( pEconItem->GetAccountID(), pOtherSetItem, &pFoundSetItemDef ); // non-GC display will find out whether the player in question has this item actively equipped +#endif + + AddDescLine( CEconItemLocalizedFullNameGenerator( + pLocalizationProvider, + pFoundSetItemDef ? pFoundSetItemDef : pOtherSetItem, + m_pHashContext == NULL + ).GetFullName(), bItemPresent ? ATTRIB_COL_ITEMSET_EQUIPPED : ATTRIB_COL_ITEMSET_MISSING, kDescLineFlag_Set, &vecPotentialDescLines, usLinkItemDefIndex ); + + bAllItemsEquipped &= bItemPresent; + } + + // If the item set is set to be only displayed when the full set is equipped, give up here and + // toss out our potential lines. Otherwise submit them for real. + if ( pItemSetDef->m_bIsHiddenSet && !bAllItemsEquipped ) + return; + + FOR_EACH_VEC( vecPotentialDescLines, i ) + { + AddDescLine( vecPotentialDescLines[i].sText.Get(), vecPotentialDescLines[i].eColor, vecPotentialDescLines[i].unMetaType, NULL, vecPotentialDescLines[i].unDefIndex ); + } + } + + // Show the set only attributes if we have the entire set and we have bonus attributes to display. + bool bHasVisible = false; + FOR_EACH_VEC( pItemSetDef->m_iAttributes, i ) + { + const CEconItemSetDefinition::itemset_attrib_t& attrib = pItemSetDef->m_iAttributes[i]; + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( attrib.m_iAttribDefIndex ); + + if ( !pAttrDef->IsHidden() ) + { + bHasVisible = true; + break; + } + } + + if ( !bHasVisible ) + return; + + AddEmptyDescLine(); + LocalizedAddDescLine( pLocalizationProvider, "#Econ_Set_Bonus", ATTRIB_COL_ITEMSET_NAME, kDescLineFlag_Set ); + + FOR_EACH_VEC( pItemSetDef->m_iAttributes, i ) + { + const CEconItemSetDefinition::itemset_attrib_t& attrib = pItemSetDef->m_iAttributes[i]; + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( attrib.m_iAttribDefIndex ); + + if ( pAttrDef ) + { + // Add the attribute description. Override the color to be grayed out if we don't have the + // full set equipped. + AddAttributeDescription( pLocalizationProvider, + pAttrDef, + *(attrib_value_t *)&attrib.m_flValue, + bAllItemsEquipped ? /* "do not override": */ NUM_ATTRIB_COLORS : ATTRIB_COL_ITEMSET_MISSING ); + } + } +} + +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_CollectionDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const CEconItemDefinition *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + const CEconItemCollectionDefinition *pCollection = pItemDef->GetItemCollectionDefinition(); + if ( !pCollection ) + return; + + // Collection Header .. + const locchar_t *loc_name = pLocalizationProvider->Find( pCollection->m_pszLocalizedName ); + + // Add a bit of spacing, this is only for the market + // Add empty line + AddDescLine( LOCCHAR( " " ), ATTRIB_COL_NEUTRAL, kDescLineFlag_CollectionName | kDescLineFlag_Empty ); + AddDescLine( LOCCHAR( " " ), ATTRIB_COL_NEUTRAL, kDescLineFlag_CollectionName | kDescLineFlag_Empty ); + + AddDescLine( + CConstructLocalizedString( LOCCHAR( "%s1" ), loc_name ), + ATTRIB_COL_NEUTRAL, + kDescLineFlag_CollectionName + ); + + FOR_EACH_VEC( pCollection->m_iItemDefs, index ) + { + int eFlag = kDescLineFlag_Collection; + const CEconItemDefinition *pTempItemDef = GetItemSchema()->GetItemDefinition( pCollection->m_iItemDefs[index] ); + if ( pTempItemDef ) + { + // Check if this item is already owned + bool bOwned = false; + bool bUnusual = false; + if ( pTempItemDef == pItemDef ) + { + bOwned = true; + eFlag |= kDescLineFlag_CollectionCurrentItem; + if ( pEconItem->GetQuality() == AE_UNUSUAL ) + { + bUnusual = true; + } + } +#ifdef CLIENT_DLL + else + { + CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); + if ( pLocalInv ) + { + const CEconItemCollectionDefinition *pRefCollection = GetItemSchema()->GetCollectionByName( pTempItemDef->GetCollectionReference() ); + // if item has a collection reference, we are looking for all those items and this item + if ( pRefCollection ) + { + bOwned = true; + FOR_EACH_VEC( pRefCollection->m_iItemDefs, iRefCollectionItem ) + { + const CEconItemView *pRefItem = pLocalInv->FindFirstItembyItemDef( pRefCollection->m_iItemDefs[ iRefCollectionItem ] ); + if ( !pRefItem ) + { + bOwned = false; + break; + } + } + } + else + { + // Normal Backpack scan + for ( int i = 0; i < pLocalInv->GetItemCount(); ++i ) + { + CEconItemView *pItem = pLocalInv->GetItem( i ); + if ( pItem->GetItemDefinition() == pTempItemDef ) + { + bOwned = true; + // Check Quality + if ( pItem->GetQuality() == AE_UNUSUAL ) + { + bUnusual = true; + break; + } + } + } + } + } + } +#endif + + const locchar_t * pCheckmark = bOwned ? pLocalizationProvider->Find( "TF_Checkmark" ) : pLocalizationProvider->Find( "TF_LackOfCheckmark" ); + if ( bOwned && bUnusual ) + { + pCheckmark = pLocalizationProvider->Find( "TF_Checkmark_Unusual" ); + } + + attrib_colors_t colorRarity = ATTRIB_COL_RARITY_DEFAULT; + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pTempItemDef->GetRarity() ); + if ( pItemRarity ) + { + colorRarity = pItemRarity->GetAttribColor(); + } + + AddDescLine( + CConstructLocalizedString( LOCCHAR("%s1%s2"), pCheckmark, + CEconItemLocalizedFullNameGenerator( + pLocalizationProvider, + pTempItemDef, + m_pHashContext == NULL + ).GetFullName() ), + colorRarity, + eFlag, + NULL, + pTempItemDef->GetDefinitionIndex() + ); + } + } +} +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_ExpirationDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const CEconItemDefinition *pItemDef = pEconItem->GetItemDefinition(); + if ( !pItemDef ) + return; + + // Look for schema-specified static expiration date. + RTime32 timeSchemaExpiration = pItemDef->GetExpirationDate(); + + // Look also for a dynamic attribute -- this could come from item tryouts. + static CSchemaAttributeDefHandle pAttrDef_ExpirationDate( "expiration date" ); + + RTime32 timeAttrExpiration = 0; + pEconItem->FindAttribute( pAttrDef_ExpirationDate, &timeAttrExpiration ); // if we don't have the attribute we'll use our starting value of 0 + + // Which will have us expire first? + RTime32 timeExpiration = MAX( timeSchemaExpiration, timeAttrExpiration ); + + // If we still don't have an expiration date we don't display anything. + if ( !timeExpiration ) + return; + + AddEmptyDescLine(); + +#ifdef TF_CLIENT_DLL + // is this a loaner item? + if ( GetAssociatedQuestItemID( pEconItem ) != INVALID_ITEM_ID ) + { + AddDescLine( pLocalizationProvider->Find( "#Attrib_LoanerItemExpirationDate" ), + ATTRIB_COL_NEGATIVE, + kDescLineFlag_Misc ); + } + else +#endif // TF_CLIENT_DLL + { + CLocalizedRTime32 time = { timeExpiration, false, pLocalizationProvider TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( m_pHashContext ) }; + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "#Attrib_ExpirationDate" ), + time ), + ATTRIB_COL_NEGATIVE, + kDescLineFlag_Misc ); + } +} + + +void CEconItemDescription::Generate_DropPeriodDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + static CSchemaAttributeDefHandle pAttr_EndDropDate( "end drop date" ); + + // See if we have the drop date period end attribute + CAttribute_String value; + if ( !FindAttribute( pEconItem, pAttr_EndDropDate, &value ) ) + return; + + AddEmptyDescLine(); + + // Convert the string value to an RTime32 + RTime32 endDate = CRTime::RTime32FromString( value.value().c_str() ); + // Is the time before or after now? Use different strings for each + const char* pszDropString = endDate > CRTime::RTime32TimeCur() + ? "#Attrib_DropPeriodComing" + : "#Attrib_DropPeriodPast"; + + CLocalizedRTime32 time = { endDate, false, pLocalizationProvider TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( m_pHashContext ) }; + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( pszDropString ), + time ), + ATTRIB_COL_LEVEL, + kDescLineFlag_Misc ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +#ifdef TF_CLIENT_DLL + extern ConVar cl_showbackpackrarities; + extern ConVar cl_show_market_data_on_items; +#endif + +void CEconItemDescription::Generate_MarketInformation( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + // Deprecated; + // We now have right click go to market + return; +// +// Assert( pLocalizationProvider ); +// Assert( pEconItem ); +// +// // Early-out to avoid doing more expensive name lookups for items that can't possibly have +// // entries. +// if ( !pEconItem->IsMarketable() ) +// return; +// +// // For now, Market information is only shown on clients, and even then only sometimes. +//#ifdef CLIENT_DLL +//#if TF_ANTI_IDLEBOT_VERIFICATION +// // Don't generate this client-only information when we're trying to match GC output. +// if ( m_pHashContext ) +// return; +//#endif // TF_ANTI_IDLEBOT_VERIFICATION +// +//#ifdef TF_CLIENT_DLL +// if ( cl_show_market_data_on_items.GetInt() == 0 ) +// return; +// +// if ( cl_show_market_data_on_items.GetInt() == 1 && cl_showbackpackrarities.GetInt() != 2 ) +// return; +//#endif // TF_CLIENT_DLL +// +// steam_market_gc_identifier_t ident; +// ident.m_unDefIndex = pEconItem->GetItemDefIndex(); +// ident.m_unQuality = pEconItem->GetQuality(); +// +// const client_market_data_t *pClientMarketData = GetClientMarketData( ident ); +// if ( !pClientMarketData ) +// return; +// +// locchar_t loc_Price[ kLocalizedPriceSizeInChararacters ]; +// MakeMoneyString( loc_Price, ARRAYSIZE( loc_Price ), pClientMarketData->m_unLowestPrice, EconUI()->GetStorePanel()->GetCurrency() ); +// +// AddEmptyDescLine(); +// AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "#Econ_MarketTooltipFormat" ), +// pClientMarketData->m_unQuantityAvailable, +// loc_Price ), +// ATTRIB_COL_POSITIVE, +// kDescLineFlag_Misc ); +//#endif // CLIENT_DLL +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +struct localized_localplayer_line_t +{ + localized_localplayer_line_t( const char *pLocalizationKey, attrib_colors_t eAttribColor, const char *pLocalizationSubKey = NULL ) + : m_pLocalizationKey( pLocalizationKey ) + , m_pLocalizationSubKey( pLocalizationSubKey ) + , m_eAttribColor( eAttribColor ) + { + // + } + + const char *m_pLocalizationKey; + const char *m_pLocalizationSubKey; + attrib_colors_t m_eAttribColor; +}; + +void CEconItemDescription::Generate_FlagsAttributes( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + const CEconItemDefinition *pItemDef = pEconItem->GetItemDefinition(); + if ( pItemDef && pItemDef->GetEconTool() && pItemDef->GetEconTool()->ShouldDisplayQuantity( pEconItem ) ) + { + AddEmptyDescLine(); + AddDescLine( CConstructLocalizedString( pLocalizationProvider->Find( "#Attrib_LimitedUse" ), (uint32)pEconItem->GetQuantity() ), + ATTRIB_COL_LIMITED_USE, + kDescLineFlag_LimitedUse ); + } + + CUtlVector<localized_localplayer_line_t> vecLines; + + // Is this item in use? (ie., being used as part of a cross-game trade) + if ( pEconItem->GetInUse() ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_InUse", ATTRIB_COL_NEUTRAL ) ); + } + + static CSchemaAttributeDefHandle pAttrDef_TradableAfter( "tradable after date" ); + static CSchemaAttributeDefHandle pAttrDef_ToolEscrowUntil( "tool escrow until date" ); + static CSchemaAttributeDefHandle pAttrDef_AlwaysTradableAndUsableInCrafting( "always tradable" ); + + uint32 unTradeTime = 0, + unEscrowTime = 0; + const bool bHasTradableAfterDate = pEconItem->FindAttribute( pAttrDef_TradableAfter, &unTradeTime ); + const bool bHasToolEscrowUntilDate = pEconItem->FindAttribute( pAttrDef_ToolEscrowUntil, &unEscrowTime ); + const bool bHasExpiringTimer = bHasTradableAfterDate || bHasToolEscrowUntilDate; + +#ifdef CLIENT_DLL + const bool bIsStoreItem = IsStorePreviewItem( pEconItem ); + const bool bIsPreviewItem = pEconItem->GetFlags() & kEconItemFlagClient_Preview; + + if ( bIsStoreItem || bIsPreviewItem ) + { +#if TF_ANTI_IDLEBOT_VERIFICATION + if ( !m_pHashContext ) +#endif // TF_ANTI_IDLEBOT_VERIFICATION + { + // Does this item come with other packages on Steam? + const econ_store_entry_t *pStoreEntry = GetEconPriceSheet() ? GetEconPriceSheet()->GetEntry( pItemDef->GetDefinitionIndex() ) : NULL; + if ( pStoreEntry && pStoreEntry->GetGiftSteamPackageID() != 0 ) + { + const char *pszSteamPackageLocalizationToken = GetItemSchema()->GetSteamPackageLocalizationToken( pStoreEntry->GetGiftSteamPackageID() ); + if ( pszSteamPackageLocalizationToken ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_Store_IncludesSteamGiftPackage", ATTRIB_COL_POSITIVE, pszSteamPackageLocalizationToken ) ); + vecLines.AddToTail( localized_localplayer_line_t( NULL, ATTRIB_COL_POSITIVE ) ); + } + } + + // While the above apply to store *and* preview items, the below only apply to store items. + if ( bIsStoreItem ) + { + // Don't display this line for map stamps because they can't be traded. + if ( pItemDef && pItemDef->GetItemClass() && !FStrEq( pItemDef->GetItemClass(), "map_token" ) ) + { + static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" ); + Assert( pAttrib_CannotTrade ); + + // Some items cannot ever be traded, so don't indicate to the users that they'll be tradeable after a few days. + if ( !FindAttribute( pItemDef, pAttrib_CannotTrade ) ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_Store_TradableAfterDate", ATTRIB_COL_NEGATIVE ) ); + } + + if ( pItemDef->GetEconTool() && pItemDef->GetEconTool()->RequiresToolEscrowPeriod() ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_Store_ToolEscrowUntilDate", ATTRIB_COL_NEGATIVE ) ); + } + } + + if ( !pItemDef || (pItemDef->GetCapabilities() & ITEM_CAP_CAN_BE_CRAFTED_IF_PURCHASED) == 0 ) + { + if ( pItemDef->IsBundle() ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_CannotCraftWeapons", ATTRIB_COL_NEGATIVE ) ); + } + else + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_CannotCraft", ATTRIB_COL_NEGATIVE ) ); + } + } + } + } + } + else +#endif // CLIENT_DLL + if ( bHasExpiringTimer ) + { + if ( unTradeTime > CRTime::RTime32TimeCur() ) + { + AddAttributeDescription( pLocalizationProvider, pAttrDef_TradableAfter, unTradeTime ); + } + + if ( unEscrowTime > CRTime::RTime32TimeCur() ) + { + AddAttributeDescription( pLocalizationProvider, pAttrDef_ToolEscrowUntil, unEscrowTime ); + } + + if ( !pEconItem->IsUsableInCrafting() ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_CannotCraft", ATTRIB_COL_NEUTRAL ) ); + } + } + else if ( pEconItem->FindAttribute( pAttrDef_AlwaysTradableAndUsableInCrafting ) && pEconItem->IsTradable() ) + { + // do nothing if we are always tradable or usable in crafting + // + // some items are marked as "always_tradable" in their itemDef but the specific item may have the + // "non_economy" flag, so we need to also check that this specific item is tradable before doing nothing + } + else + { + const int32 iQuality = pEconItem->GetQuality(); + const eEconItemOrigin eOrigin = pEconItem->GetOrigin(); + + if ( iQuality >= AE_COMMUNITY && iQuality <= AE_SELFMADE ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_SpecialItem", ATTRIB_COL_NEUTRAL ) ); + } + else if ( eOrigin == kEconItemOrigin_Achievement ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_AchievementItem", ATTRIB_COL_NEUTRAL ) ); + } + else if ( eOrigin == kEconItemOrigin_CollectionReward ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_CollectionReward", ATTRIB_COL_NEUTRAL ) ); + } + else if ( eOrigin == kEconItemOrigin_PreviewItem ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_PreviewItem", ATTRIB_COL_NEUTRAL ) ); + } + else if ( eOrigin == kEconItemOrigin_QuestLoanerItem ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_LoanerItem", ATTRIB_COL_NEUTRAL ) ); + } + else if ( eOrigin == kEconItemOrigin_Invalid ) + { + // do nothing, but skip the below "cannot trade/cannot craft" block below" + } + else if ( (pEconItem->GetFlags() & kEconItemFlag_NonEconomy) != 0 ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_NonEconomyItem", ATTRIB_COL_NEUTRAL ) ); + } + else + { + const bool bIsTradable = pEconItem->IsTradable(), + bIsCraftable = pEconItem->IsUsableInCrafting(); + + if ( !bIsTradable && !bIsCraftable ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_CannotTradeOrCraft", ATTRIB_COL_NEUTRAL ) ); + } + else + { + if ( !bIsTradable ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_CannotTrade", ATTRIB_COL_NEUTRAL ) ); + } + + if ( !bIsCraftable ) + { + vecLines.AddToTail( localized_localplayer_line_t( "#Attrib_CannotCraft", ATTRIB_COL_NEUTRAL ) ); + } + } + } + } + + if ( vecLines.Count() > 0 ) + { + const locchar_t *loc_AttribFormat_AdditionalNode = pLocalizationProvider->Find( "#AttribFormat_AdditionalNote" ); + if ( loc_AttribFormat_AdditionalNode ) + { + AddEmptyDescLine(); + + FOR_EACH_VEC( vecLines, i ) + { + const char *pszLocalizationKey = vecLines[i].m_pLocalizationKey; + const char *pszLocalizationSubKey = vecLines[i].m_pLocalizationSubKey; + + if ( pszLocalizationKey ) + { + AddDescLine( pszLocalizationSubKey ? + CConstructLocalizedString( pLocalizationProvider->Find( pszLocalizationKey ), pLocalizationProvider->Find( pszLocalizationSubKey ) ): // has subtoken, doesn't use additional note format + CConstructLocalizedString( loc_AttribFormat_AdditionalNode, pLocalizationProvider->Find( pszLocalizationKey ) ), // no subtoken, uses base additional note format + vecLines[i].m_eAttribColor, + kDescLineFlag_Misc ); + } + else + { + AddEmptyDescLine(); + } + } + } + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- + +bool CEconItemDescription::CVisibleAttributeDisplayer::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) +{ + if ( !pAttrDef->IsHidden() ) + { + attrib_iterator_value_t attrVal = { pAttrDef, value }; + m_vecAttributes.AddToTail( attrVal ); + } + + return true; +} + +void CEconItemDescription::CVisibleAttributeDisplayer::SortAttributes() +{ + // We need to make sure we process attributes in the same order when iterating on the GC and the client + // when looking for agreement. We take advantage of this to also sort our attributes into a coherent + // order for display -- first come neutral attributes, then positive, then negative. In the event of a + // tie, we sort by attribute index, which is arbitrary but consistent across the client/GC. + struct AttributeValueSorter + { + static int sSort( const attrib_iterator_value_t *pA, const attrib_iterator_value_t *pB ) + { + const int iEffectTypeDelta = pA->m_pAttrDef->GetEffectType() - pB->m_pAttrDef->GetEffectType(); + if ( iEffectTypeDelta != 0 ) + return iEffectTypeDelta; + + return pA->m_pAttrDef->GetDefinitionIndex() - pB->m_pAttrDef->GetDefinitionIndex(); + } + }; + + m_vecAttributes.Sort( &AttributeValueSorter::sSort ); +} + +void CEconItemDescription::CVisibleAttributeDisplayer::Finalize( const IEconItemInterface *pEconItem, CEconItemDescription *pEconItemDescription, const CLocalizationProvider *pLocalizationProvider ) +{ + // HACK so we dont show series number on select crates since they are self describing (Event Crates, Collection Crates) + static CSchemaAttributeDefHandle pAttrDef_SupplyCrateSeries( "set supply crate series" ); + static CSchemaAttributeDefHandle pAttrDef_HideSeries( "hide crate series number" ); + + FOR_EACH_VEC( m_vecAttributes, i ) + { + if ( pEconItem && m_vecAttributes[i].m_pAttrDef == pAttrDef_SupplyCrateSeries && pEconItem->FindAttribute( pAttrDef_HideSeries ) ) + continue; + + pEconItemDescription->AddAttributeDescription( pLocalizationProvider, m_vecAttributes[i].m_pAttrDef, m_vecAttributes[i].m_value ); + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_VisibleAttributes( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ + Assert( pLocalizationProvider ); + Assert( pEconItem ); + + CVisibleAttributeDisplayer AttributeDisplayer; + pEconItem->IterateAttributes( &AttributeDisplayer ); + AttributeDisplayer.SortAttributes(); + AttributeDisplayer.Finalize( pEconItem, this, pLocalizationProvider ); +} + +// -------------------------------------------------------------------------- +void CEconItemDescription::Generate_DirectX8Warning( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) +{ +#ifdef CLIENT_DLL + static ConVarRef mat_dxlevel( "mat_dxlevel" ); + const CEconItemDefinition *pEconItemDefinition = pEconItem->GetItemDefinition(); + // If less than 90, we�re in DX8 mode. + // Display warning if you are looking at a painthit item or case + if ( mat_dxlevel.GetInt() < 90 && pEconItemDefinition && ( pEconItemDefinition->GetItemCollectionDefinition() || pEconItemDefinition->GetCollectionReference() ) ) + { + AddEmptyDescLine(); + AddDescLine( pLocalizationProvider->Find( "#Attrib_DirectX8Warning" ), + ATTRIB_COL_NEGATIVE, + kDescLineFlag_Misc ); + } + +#endif +} + + +bool CEconItemDescription::CRecipeNameAttributeDisplayer::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) +{ + if ( pAttrDef->CanAffectRecipeComponentName() ) + { + return CVisibleAttributeDisplayer::OnIterateAttributeValue( pAttrDef, value ); + } + + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +static attrib_colors_t GetAttributeDefaultColor( const CEconItemAttributeDefinition *pAttribDef ) +{ + // positive attribute? + switch ( pAttribDef->GetEffectType() ) + { + case ATTRIB_EFFECT_NEUTRAL: return ATTRIB_COL_NEUTRAL; + case ATTRIB_EFFECT_POSITIVE: return ATTRIB_COL_POSITIVE; + case ATTRIB_EFFECT_NEGATIVE: return ATTRIB_COL_NEGATIVE; + case ATTRIB_EFFECT_STRANGE: return ATTRIB_COL_STRANGE; + case ATTRIB_EFFECT_UNUSUAL: return ATTRIB_COL_UNUSUAL; + } + + // hell if we know + return ATTRIB_COL_NEUTRAL; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconAttributeDescription::InternalConstruct +( + const CLocalizationProvider *pLocalizationProvider, + const CEconItemAttributeDefinition *pAttribDef, + attrib_value_t value, + TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( MD5Context_t *pHashContext ) TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA + IAccountPersonaLocalizer *pOptionalAccountPersonaLocalizer +) +{ + Assert( pAttribDef != NULL ); + + const float& value_as_float = (float&)value; + const uint32& value_as_uint32 = (uint32&)value; + + // Calculate our color first -- if we don't know what to do, we'll wind up as neutral. + m_eDefaultColor = GetAttributeDefaultColor( pAttribDef ); + + // Early out abort if we don't have a localization string for this attribute. + locchar_t *loc_String = pAttribDef->GetDescriptionString() && pLocalizationProvider + ? pLocalizationProvider->Find( pAttribDef->GetDescriptionString() ) + : NULL; + + if ( !loc_String ) + return; + + char szAttrShortDescToken[MAX_PATH]; + V_sprintf_safe( szAttrShortDescToken, "%s%s", pAttribDef->GetDescriptionString(), "_shortdesc" ); + + locchar_t *loc_ShortString = pLocalizationProvider + ? pLocalizationProvider->Find( szAttrShortDescToken ) + : NULL; + + // How do we format an attribute value of this type? + switch ( pAttribDef->GetDescriptionFormat() ) + { + case ATTDESCFORM_VALUE_IS_ADDITIVE_PERCENTAGE: + m_loc_sValue = CLocalizedStringArg<float>( value_as_float * 100.0f ).GetLocArg(); + break; + + case ATTDESCFORM_VALUE_IS_ACCOUNT_ID: +#ifdef CLIENT_DLL + // If this assert fires, it means that the client fed in an attribute that should be localized + // as a Steam persona name but didn't feed it any way to get that information. The GC won't + // assert, but also won't generate anything for the attribute text. + // + // It's still totally fine to pass in NULL for the persona localizer as long as you don't + // expect to have any attributes that have account IDs. + Assert( pOptionalAccountPersonaLocalizer ); +#endif + if ( pOptionalAccountPersonaLocalizer ) + { + m_loc_sValue = pOptionalAccountPersonaLocalizer->FindAccountPersonaName( value_as_uint32 ); + } + break; + + case ATTDESCFORM_VALUE_IS_ADDITIVE: + m_loc_sValue = pAttribDef->IsStoredAsFloat() + ? CLocalizedStringArg<float>( value_as_float ).GetLocArg() + : CLocalizedStringArg<uint32>( value_as_uint32 ).GetLocArg(); + break; + + case ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE: + if ( value_as_float < 1.0 ) + { + m_loc_sValue = CLocalizedStringArg<float>( (1.0 - value_as_float) * 100.0f ).GetLocArg(); + break; + } + + // We intentionally fall through when value_as_float >= 1.0f to treat it the same as "value as + // percentage". + case ATTDESCFORM_VALUE_IS_PERCENTAGE: + m_loc_sValue = CLocalizedStringArg<float>( (value_as_float * 100.0f) - 100.0f ).GetLocArg(); + break; + + case ATTDESCFORM_VALUE_IS_DATE: + { + bool bUseGMT = false; + +#ifdef PROJECT_TF + static CSchemaAttributeDefHandle pAttribDef_SetEmployeeNumber( "custom employee number" ); + + // only use GMT for custom employee number -- not doing this generated a bunch of support + // tickets because items were granted based on GC time but would display local time, causing + // people on the border to think they deserved a better badge, etc. + bUseGMT = (pAttribDef == pAttribDef_SetEmployeeNumber); +#endif // PROJECT_TF + + CLocalizedRTime32 time = { value_as_uint32, bUseGMT, pLocalizationProvider TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( pHashContext ) }; + m_loc_sValue = CLocalizedStringArg<CLocalizedRTime32>( time ).GetLocArg(); + break; + } + + case ATTDESCFORM_VALUE_IS_PARTICLE_INDEX: + { + // This is a horrible, horrible line of code. It exists because old particle references are + // ints stored as floats as float bit patterns and new particle references are ints stored + // as ints all the way through. + CUtlConstString utf8_ParticleKeyName( CFmtStr( "#Attrib_Particle%i", pAttribDef->IsStoredAsInteger() ? value_as_uint32 : (int)value_as_float ).Access() ); // this value is stored as a float but interpreted as an int (1.0 -> 1) + if ( utf8_ParticleKeyName.IsEmpty() ) + return; + + m_loc_sValue = pLocalizationProvider->Find( utf8_ParticleKeyName.Get() ); + break; + } + + case ATTDESCFORM_VALUE_IS_KILLSTREAKEFFECT_INDEX: + { + CUtlConstString utf8_KeyName( CFmtStr( "#Attrib_KillStreakEffect%i", (int)value_as_float ).Access() ); // this value is stored as a float but interpreted as an int (1.0 -> 1) + if ( utf8_KeyName.IsEmpty() ) + return; + + m_loc_sValue = pLocalizationProvider->Find( utf8_KeyName.Get() ); + break; + } + + case ATTDESCFORM_VALUE_IS_KILLSTREAK_IDLEEFFECT_INDEX: + { + CUtlConstString utf8_KeyName( CFmtStr( "#Attrib_KillStreakIdleEffect%i", (int)value_as_float ).Access() ); // this value is stored as a float but interpreted as an int (1.0 -> 1) + if ( utf8_KeyName.IsEmpty() ) + return; + + m_loc_sValue = pLocalizationProvider->Find( utf8_KeyName.Get() ); + break; + } + // Don't output any value for bitmasks, but let the attribute text display. + case ATTDESCFORM_VALUE_IS_OR: + break; + + default: +#ifdef CLIENT_DLL + // Only assert on the client -- the GC will just silently fail rather than crash if we ever run into + // this case, but if we are adding a new display type this will help us catch a reason why it isn't + // showing up. + Assert( !"Unhandled attribute value display type in CEconAttributeDescription." ); + + // Anywhere besides the client, we intentionally fall through to return immediately. +#endif + case ATTDESCFORM_VALUE_IS_ITEM_DEF: // referencing definitions is handled per-attribute + return; + + case ATTDESCFORM_VALUE_IS_FROM_LOOKUP_TABLE: + { + const char *pszLocalizationToken = GetItemSchema()->FindStringTableEntry( pAttribDef->GetDefinitionName(), (int)value_as_float ); + if ( !pszLocalizationToken ) + return; + + const locchar_t *loc_Entry = pLocalizationProvider->Find( pszLocalizationToken ); + if ( !loc_Entry ) + return; + + m_loc_sValue = loc_Entry; + break; + } + } + + // Some attributes have a short description for the upgrade + if ( loc_ShortString ) + { + m_loc_sShortValue = CConstructLocalizedString( loc_ShortString, m_loc_sValue.Get() ); + } + + // Combine the value string we just generated with the localized display for that value. (ie., the value + // might be "10" and the display would be "health is increased by 10%".) + m_loc_sValue = CConstructLocalizedString( loc_String, m_loc_sValue.Get() ); + + // Is this an attribute that needs a custom wrapper around the default attribute text? (ie., + // if our string was "Damage +10%" we want that to be "(only on Hightower: Damage +10%)") + if ( pAttribDef->GetUserGenerationType() ) + { + const locchar_t *locUGTLocalizationKey = pLocalizationProvider->Find( CFmtStr( "#Econ_Attrib_UserGeneratedWrapper_%i", pAttribDef->GetUserGenerationType() ).Get() ); + + if ( locUGTLocalizationKey ) + { + m_loc_sValue = CConstructLocalizedString( locUGTLocalizationKey, m_loc_sValue.Get() ); + } + } + + // If there's no short description, just copy the normal one + if ( !loc_ShortString ) + { + m_loc_sShortValue = m_loc_sValue; + } +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::AddAttributeDescription( const CLocalizationProvider *pLocalizationProvider, const CEconItemAttributeDefinition *pAttribDef, attrib_value_t value, attrib_colors_t eOverrideDisplayColor /* = NUM_ATTRIB_COLORS */ ) +{ + Assert( pLocalizationProvider ); + Assert( pAttribDef ); + + CEconAttributeDescription AttrDesc( pLocalizationProvider, + pAttribDef, + value, + TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( m_pHashContext ) TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA + this ); + + if ( AttrDesc.GetDescription().IsEmpty() ) + return; + + // Is this an attribute that needs a custom wrapper around the default attribute text? (ie., + // if our string was "Damage +10%" we want that to be "(only on Hightower: Damage +10%)") + attrib_colors_t eDefaultAttribColor = GetAttributeDefaultColor( pAttribDef ); + +#ifdef TF_CLIENT_DLL + enum + { + kUserGeneratedAttributeType_None = 0, + kUserGeneratedAttributeType_MVMEngineering = 1, + kUserGeneratedAttributeType_HalloweenSpell = 2 + }; + + // On TF, these user-generated attributes can be from upgrade cards which only apply in MvM. + // We then colorize them based on whether they'll be active, with the caveat that out-of-game + // views always say yes (GC, loadout when not on a server, etc.). + if ( pAttribDef->GetUserGenerationType() == kUserGeneratedAttributeType_MVMEngineering && TFGameRules() && !TFGameRules()->IsMannVsMachineMode() ) + { + eDefaultAttribColor = ATTRIB_COL_ITEMSET_MISSING; + } + // They can also be from Halloween spells. These are intended to expire after Halloween in any + // event, but for display purposes they'll appear in grey unless the holiday is active. + else if ( pAttribDef->GetUserGenerationType() == kUserGeneratedAttributeType_HalloweenSpell && !EconHolidays_IsHolidayActive( kHoliday_Halloween, CRTime::RTime32TimeCur() ) ) + { + eDefaultAttribColor = ATTRIB_COL_ITEMSET_MISSING; + } +#endif // TF_CLIENT_DLL + + AddDescLine( AttrDesc.GetDescription().Get(), + eOverrideDisplayColor != NUM_ATTRIB_COLORS ? // are we overriding the output color? + eOverrideDisplayColor : // we are + eDefaultAttribColor, // fall back to normal attribute color + kDescLineFlag_Attribute ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::AddDescLine( const locchar_t *pString, attrib_colors_t eColor, uint32 unMetaType, CUtlVector<econ_item_description_line_t> *out_pOptionalDescLineDest /* = NULL */, item_definition_index_t unDefIndex /* = INVALID_ITEM_DEF_INDEX */, bool bIsItemForSale /*= true*/ ) +{ + CUtlVector<econ_item_description_line_t>& vecTargetDescLines = out_pOptionalDescLineDest ? *out_pOptionalDescLineDest : m_vecDescLines; + + econ_item_description_line_t& line = vecTargetDescLines[ vecTargetDescLines.AddToTail() ]; + + line.eColor = eColor; + line.unMetaType = unMetaType; + line.sText = pString; + line.unDefIndex = unDefIndex; + line.bIsItemForSale = bIsItemForSale; + +#if TF_ANTI_IDLEBOT_VERIFICATION + if ( m_pHashContext ) + { + const int iLineCount = vecTargetDescLines.Count() + 1; + + char verboseStringBuf[ k_VerboseStringBufferSize ]; + TFDescription_HashDataMunge( m_pHashContext, iLineCount, m_bIsVerbose, BuildVerboseStrings( verboseStringBuf, m_bIsVerbose, "%d", iLineCount ) ); // which line did we just add? + TFDescription_HashDataMunge( m_pHashContext, line.eColor, m_bIsVerbose, BuildVerboseStrings( verboseStringBuf, m_bIsVerbose,"%d", line.eColor ) ); + TFDescription_HashDataMunge( m_pHashContext, line.unMetaType, m_bIsVerbose, BuildVerboseStrings( verboseStringBuf, m_bIsVerbose, "%d", line.unMetaType ) ); + +#ifdef GC_DLL + COMPILE_TIME_ASSERT( sizeof( locchar_t ) == sizeof( char ) ); + TFDescription_HashDataMungeContents( m_pHashContext, pString, StringFuncs<locchar_t>::Length( pString ) * sizeof( locchar_t ), m_bIsVerbose, pString ); +#else + COMPILE_TIME_ASSERT( sizeof( locchar_t ) == sizeof( wchar_t ) ); + + CUtlConstString ansiString; + GLocalizationProvider()->ConvertLoccharToANSI( pString, &ansiString ); + TFDescription_HashDataMungeContents( m_pHashContext, ansiString.Get(), StringFuncs<char>::Length( ansiString.Get() ) * sizeof( char ), m_bIsVerbose, ansiString.Get() ); +#endif + } +#endif // TF_ANTI_IDLEBOT_VERIFICATION +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::AddEmptyDescLine( CUtlVector<econ_item_description_line_t> *out_pOptionalDescLineDest ) +{ + AddDescLine( LOCCHAR(" "), ATTRIB_COL_NEUTRAL, kDescLineFlag_Empty, out_pOptionalDescLineDest ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +void CEconItemDescription::LocalizedAddDescLine( const CLocalizationProvider *pLocalizationProvider, const char *pLocalizationToken, attrib_colors_t eColor, uint32 unMetaType, CUtlVector<econ_item_description_line_t> *out_pOptionalDescLineDest /* = NULL */, item_definition_index_t unDefIndex /* = INVALID_ITEM_DEF_INDEX */, bool bIsItemForSale /* = true */ ) +{ + Assert( pLocalizationToken ); + + const locchar_t *pTextToAdd = pLocalizationProvider->Find( pLocalizationToken ); + + if ( pTextToAdd ) + { + AddDescLine( pTextToAdd, eColor, unMetaType, out_pOptionalDescLineDest, unDefIndex, bIsItemForSale ); + } + else if ( pLocalizationToken && (pLocalizationToken[0] != '#') ) + { + // If we couldn't localize correctly, we might be a string literal like "My temp item desc.". In + // this case, we use that string as-is. + CUtlConstStringBase<locchar_t> loc_sText; + pLocalizationProvider->ConvertUTF8ToLocchar( pLocalizationToken, &loc_sText ); + AddDescLine( loc_sText.Get(), eColor, unMetaType, out_pOptionalDescLineDest, unDefIndex, bIsItemForSale ); + } + else + { + // We couldn't localize this token, but also don't think it was a string meant to be user-facing so + // just silently fail. + } + +#if TF_ANTI_IDLEBOT_VERIFICATION + if ( m_pHashContext ) + { + TFDescription_HashDataMungeContents( m_pHashContext, pLocalizationToken, V_strlen( pLocalizationToken ), m_bIsVerbose, pLocalizationToken ); + } +#endif // TF_ANTI_IDLEBOT_VERIFICATION +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +class CGameItemDefinition_EconItemInterfaceWrapper : public CMaterialOverrideContainer< IEconItemInterface > +{ +public: + CGameItemDefinition_EconItemInterfaceWrapper( const CEconItemDefinition *pEconItemDefinition, entityquality_t eQuality ) + : m_pEconItemDefinition( pEconItemDefinition ) + , m_eQuality( eQuality ) + { + Assert( m_pEconItemDefinition ); + } + + virtual const GameItemDefinition_t *GetItemDefinition() const { return assert_cast<const GameItemDefinition_t *>( m_pEconItemDefinition ); } + + virtual itemid_t GetID() const { return INVALID_ITEM_ID; } + virtual uint32 GetAccountID() const { return 0; } + virtual int32 GetQuality() const { return m_eQuality; } + virtual style_index_t GetStyle() const { return INVALID_STYLE_INDEX; } + virtual uint8 GetFlags() const { return 0; } + virtual eEconItemOrigin GetOrigin() const { return kEconItemOrigin_Invalid; } + virtual int GetQuantity() const { return 1; } + virtual uint32 GetItemLevel() const { return 0; } + virtual bool GetInUse() const { return false; } + + virtual const char *GetCustomName() const { return NULL; } + virtual const char *GetCustomDesc() const { return NULL; } + + virtual CEconItemPaintKitDefinition *GetCustomPainkKitDefinition( void ) const { return GetItemDefinition()->GetCustomPainkKitDefinition(); } + + // IEconItemInterface attribute iteration interface. This is not meant to be used for + // attribute lookup! This is meant for anything that requires iterating over the full + // attribute list. + virtual void IterateAttributes( class IEconItemAttributeIterator *pIterator ) const OVERRIDE + { + Assert( pIterator ); + + m_pEconItemDefinition->IterateAttributes( pIterator ); + } + +private: + const CEconItemDefinition *m_pEconItemDefinition; + entityquality_t m_eQuality; +}; + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItemLocalizedFullNameGenerator::CEconItemLocalizedFullNameGenerator( const CLocalizationProvider *pLocalizationProvider, const CEconItemDefinition *pItemDef, bool bUseProperName, entityquality_t eQuality ) +{ + Assert( pItemDef ); + + CGameItemDefinition_EconItemInterfaceWrapper EconItemDefinitionWrapper( pItemDef, eQuality ); + GenerateLocalizedFullItemName( m_loc_LocalizedItemName, pLocalizationProvider, &EconItemDefinitionWrapper, k_EGenerateLocalizedFullItemName_Default, bUseProperName ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +class CMarketNameGenerator_EconItemInterfaceWrapper : public IEconItemInterface +{ +public: + CMarketNameGenerator_EconItemInterfaceWrapper( CEconItem *pItem ) + : m_pItem( pItem ) + { + Assert( m_pItem ); + } + + virtual const GameItemDefinition_t *GetItemDefinition() const { return m_pItem->GetItemDefinition(); } + + virtual itemid_t GetID() const { return m_pItem->GetID(); } + virtual uint32 GetAccountID() const { return 0; } + virtual int32 GetQuality() const { return m_pItem->GetQuality(); } + virtual style_index_t GetStyle() const { return INVALID_STYLE_INDEX; } + virtual uint8 GetFlags() const { return 0; } + virtual eEconItemOrigin GetOrigin() const { return kEconItemOrigin_Invalid; } + virtual int GetQuantity() const { return 1; } + virtual uint32 GetItemLevel() const { return 0; } + virtual bool GetInUse() const { return false; } + + virtual const char *GetCustomName() const { return NULL; } + virtual const char *GetCustomDesc() const { return NULL; } + + virtual CEconItemPaintKitDefinition *GetCustomPainkKitDefinition( void ) const { return m_pItem->GetCustomPainkKitDefinition(); } + virtual bool GetCustomPaintKitWear( float &flWear ) const { return m_pItem->GetCustomPaintKitWear( flWear ); } + + virtual IMaterial *GetMaterialOverride( int iTeam ) OVERRIDE { return m_pItem->GetMaterialOverride( iTeam ); } + + // IEconItemInterface attribute iteration interface. This is not meant to be used for + // attribute lookup! This is meant for anything that requires iterating over the full + // attribute list. + virtual void IterateAttributes( class IEconItemAttributeIterator *pIterator ) const OVERRIDE + { + Assert( pIterator ); + + // Wrap their iterator in our iterator that will selectively let specific attributes + // get iterated on by the wrapped iterator. + CMarketNameGenerator_SelectiveAttributeIterator iteratorWrapper( pIterator ); + m_pItem->IterateAttributes( &iteratorWrapper ); + } + +private: + + CEconItem *m_pItem; + + // Iterator class that wraps another iterator and selectively allows specific attributes to be + // iterated by the passed in iterator + class CMarketNameGenerator_SelectiveAttributeIterator : public IEconItemAttributeIterator + { + public: + CMarketNameGenerator_SelectiveAttributeIterator( IEconItemAttributeIterator* pIterator ) + : m_pIterator( pIterator ) + {} + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) OVERRIDE + { + if( pAttrDef->CanAffectMarketName() ) + { + m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) OVERRIDE + { + if( pAttrDef->CanAffectMarketName() ) + { + m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64& value ) OVERRIDE + { + if( pAttrDef->CanAffectMarketName() ) + { + m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& value ) OVERRIDE + { + if( pAttrDef->CanAffectMarketName() ) + { + m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) OVERRIDE + { + if( pAttrDef->CanAffectMarketName() ) + { + m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria& value ) OVERRIDE + { + if( pAttrDef->CanAffectMarketName() ) + { + m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement& value ) OVERRIDE + { + if ( pAttrDef->CanAffectMarketName() ) + { + m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + return true; + } + + private: + + IEconItemAttributeIterator *m_pIterator; + }; +}; + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItemLocalizedMarketNameGenerator::CEconItemLocalizedMarketNameGenerator( const CLocalizationProvider *pLocalizationProvider, CEconItem *pItem, bool bUseProperName ) +{ + Assert( pItem ); + + CMarketNameGenerator_EconItemInterfaceWrapper EconItemWrapper( pItem ); + GenerateLocalizedFullItemName( m_loc_LocalizedItemName, pLocalizationProvider, &EconItemWrapper, k_EGenerateLocalizedFullItemName_WithPaintWear, bUseProperName ); +} + +#endif // BUILD_ITEM_NAME_AND_DESC diff --git a/game/shared/econ/econ_item_description.h b/game/shared/econ/econ_item_description.h new file mode 100644 index 0000000..f900974 --- /dev/null +++ b/game/shared/econ/econ_item_description.h @@ -0,0 +1,536 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#ifndef ECONITEMDESCRIPTION_H +#define ECONITEMDESCRIPTION_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "localization_provider.h" // needed for locchar_t type + +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) || defined( TF_GC_DLL ) + #define PROJECT_TF +#endif + +#define TF_ANTI_IDLEBOT_VERIFICATION defined( PROJECT_TF ) + +#if TF_ANTI_IDLEBOT_VERIFICATION + #define TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA , + #define TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( arg ) arg +#else + #define TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA + #define TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( arg ) +#endif + +#if TF_ANTI_IDLEBOT_VERIFICATION + #include "checksum_md5.h" + #include "tf_gcmessages.pb.h" + #include "tf_gcmessages.h" +#ifdef CLIENT_DLL + #include "gc_clientsystem.h" +#endif // CLIENT_DLL + +#endif // TF_ANTI_IDLEBOT_VERIFICATION + +#ifdef GC_DLL +#include "gcsdk/gclogger.h" +using namespace GCSDK; +#endif + +class IEconItemInterface; +namespace GCSDK +{ + class CSharedObjectTypeCache; +} + +//----------------------------------------------------------------------------- +// Purpose: Generate a description block for an IEconItemInterface. What the +// client does with the description is anyone's guess, but this will +// generate a block of UTF16 lines of text with meta/color data that +// can be used for whatever. +//----------------------------------------------------------------------------- + +enum EDescriptionLineMetaFlags +{ + kDescLineFlag_Name = 0x001, // the item name (can be renamed by user) + kDescLineFlag_Type = 0x002, // the item type (ie., "Level 5 Rocket Launcher") + kDescLineFlag_Desc = 0x004, // base item description (description from the item definition, level, etc.) + kDescLineFlag_Attribute = 0x008, // some sort of gameplay-affecting attribute + kDescLineFlag_Misc = 0x010, // not an attribute, not name/level + kDescLineFlag_Empty = 0x020, // line with no content that needs to be displayed; meant for spacing + kDescLineFlag_Set = 0x040, // this line is associated with item sets somehow + kDescLineFlag_LimitedUse= 0x080, // this is a limited use item + kDescLineFlag_SetName = 0x100, // this line is the title for an item set + kDescLineFlag_Collection = 0x200, // this line is associated with item collections + kDescLineFlag_CollectionCurrentItem = 0x400, // this line is the current item being describe + kDescLineFlag_CollectionName = 0x800, // this line is the collection name + + kDescLineFlagSet_DisplayInAttributeBlock = ~(kDescLineFlag_Name | kDescLineFlag_Type), +}; + +struct econ_item_description_line_t +{ + attrib_colors_t eColor; // desired color type for this line -- will likely be looked up either in VGUI or in the schema + uint32 unMetaType; // type information for this line -- "item name"? "item level"?; etc.; can be game-specific + CUtlConstStringBase<locchar_t> sText; // actual text for this, post-localization + item_definition_index_t unDefIndex; // item def index for description lines which represent names of other items (used by bundles) + bool bIsItemForSale; // if this line is an item (eg in the case where a bundle description lists its contained items) - is the given item for sale? +}; + +class IEconItemDescription +{ +public: + // This may yield on the GC and should never yield on the client. + static void YieldingFillOutEconItemDescription( IEconItemDescription *out_pDescription, CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + +public: + IEconItemDescription() { } + virtual ~IEconItemDescription() { } + + uint32 GetLineCount() const { return m_vecDescLines.Count(); } + const econ_item_description_line_t& GetLine( int i ) const { return m_vecDescLines[i]; } + + // Finds and returns the first line with *all* of the passed in search flags. Will return NULL if a line + // will all of the flags cannot be found. + const econ_item_description_line_t *GetFirstLineWithMetaType( uint32 unMetaTypeSearchFlags ) const; + +private: + // When generating an item description, this is guaranteed to be called once and only once before GenerateDescription() + // is called. Any data that may yield but will be needed somewhere deep inside GenerateDescription() should be determined + // and cached off here. + virtual void YieldingCacheDescriptionData( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) { } + + // Take the properties off our pEconItem, and anything that we calculated in YieldingCacheDescriptionData() above and + // fill out all of our description lines. + virtual void GenerateDescriptionLines( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ) = 0; + +protected: + CUtlVector<econ_item_description_line_t> m_vecDescLines; +}; + +// This will be defined as either 1 or 0 depending on which project we're in. We test its value explicitly +// rather than just checking defined() because otherwise failing to include this header file will silently +// result in it appearing to be undefined. +#define BUILD_ITEM_NAME_AND_DESC (defined( CLIENT_DLL ) || defined( GC_DLL )) + +#if BUILD_ITEM_NAME_AND_DESC +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class IAccountPersonaLocalizer +{ +public: + virtual const locchar_t *FindAccountPersonaName( uint32 unAccountID ) const = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconItemDescription : public IEconItemDescription, public IAccountPersonaLocalizer +{ +public: + // Instances should be filled out via YieldingFillOutEconItemDescription(). + CEconItemDescription() +#if TF_ANTI_IDLEBOT_VERIFICATION + : m_pHashContext( NULL ) + , m_bIsVerbose( false ) +#ifdef GC_DLL + , m_bTextModeEnabled( false ) +#else // if defined( CLIENT_DLL ) + , m_bUnknownPlayer( false ) +#endif // GC_DLL +#endif // TF_ANTI_IDLEBOT_VERIFICATION + { + // + } + + // External helper interface, also used internally. This should only be used to add lines + // that are not a part of the properties of the item, but are instead a part of the environment + // around the item (ie., "this item cannot be equipped in this slot because another item is + // equipped that has conflicting regions"). + // + // The final argument is an optional target array to use for the description lines instead of + // our internal storage. We can use this to queue up and then batch-submit/-discard lines. Passing + // in NULL means "use the internal array". + virtual void AddDescLine( const locchar_t *pString, attrib_colors_t eColor, uint32 unMetaType, CUtlVector<econ_item_description_line_t> *out_pOptionalDescLineDest = NULL, item_definition_index_t unDefIndex = INVALID_ITEM_DEF_INDEX, bool bIsItemForSale = true ); + virtual void AddEmptyDescLine( CUtlVector<econ_item_description_line_t> *out_pOptionalDescLineDest = NULL ); + virtual void LocalizedAddDescLine( const CLocalizationProvider *pLocalizationProvider, const char *pLocalizationToken, attrib_colors_t eColor, uint32 unMetaType, CUtlVector<econ_item_description_line_t> *out_pOptionalDescLineDest = NULL, item_definition_index_t unDefIndex = INVALID_ITEM_DEF_INDEX, bool bIsItemForSale = true ); + + // A helper class to iterate all attributes that we expect to appear on an item description. This + // is useable from outside CEconItemDescription. Attributes can be accessed in iteration order or + // manually sorted to be grouped by positive/negative status, etc. + class CVisibleAttributeDisplayer : public IEconItemAttributeIterator + { + public: + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) OVERRIDE; + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) OVERRIDE + { + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64& value ) OVERRIDE + { + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& value ) OVERRIDE + { + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) OVERRIDE + { + // Don't show these + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria& value ) OVERRIDE + { + // Don't show these + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement& value ) OVERRIDE + { + // Don't show these + return true; + } + + void SortAttributes(); + void Finalize( const IEconItemInterface *pEconItem, CEconItemDescription *pEconItemDescription, const CLocalizationProvider *pLocalizationProvider ); + + private: + struct attrib_iterator_value_t + { + const CEconItemAttributeDefinition *m_pAttrDef; + attrib_value_t m_value; + }; + + CUtlVector<attrib_iterator_value_t> m_vecAttributes; + }; + + class CRecipeNameAttributeDisplayer : public CVisibleAttributeDisplayer + { + public: + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) OVERRIDE; + }; + +#if TF_ANTI_IDLEBOT_VERIFICATION + void SetHashContext( MD5Context_t *pHashContext ) + { + AssertMsg( pHashContext == NULL || m_pHashContext == NULL, "Only one hash context allowed per item description!" ); + + m_pHashContext = pHashContext; + } + + void SetVerbose( bool bIsVerbose ) + { + m_bIsVerbose = bIsVerbose; + } + +#ifdef GC_DLL + void SetHashGCTextModeEnabled( bool bTextModeEnabled ) + { + m_bTextModeEnabled = bTextModeEnabled; + } +#endif // GC_DLL +#endif // TF_ANTI_IDLEBOT_VERIFICATION + +#ifdef GC_DLL + bool HasUnknownPlayer( ) const + { + return false; + } +#else // if defined( CLIENT_DLL ) + bool HasUnknownPlayer( ) const + { + return m_bUnknownPlayer; + } +#endif + +private: + // IEconItemDescription interface. + virtual void YieldingCacheDescriptionData( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void GenerateDescriptionLines( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + +private: + // Internal. + virtual void AddAttributeDescription( const CLocalizationProvider *pLocalizationProvider, const CEconItemAttributeDefinition *pAttribDef, attrib_value_t value, attrib_colors_t eOverrideDisplayColor = NUM_ATTRIB_COLORS ); + + virtual void Generate_ItemName( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_ItemLevelDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); +#if defined( STAGING_ONLY ) && defined( CLIENT_DLL ) + virtual void Generate_DebugInformation( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); +#endif // defined( DEBUG ) && defined( CLIENT_DLL ) + virtual void Generate_CraftTag( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_StyleDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_HolidayRestriction( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_QualityDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_ItemRarityDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_WearAmountDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_ItemDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_Bundle( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_GiftedBy( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); +#ifdef PROJECT_TF + virtual void Generate_DuelingMedal( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_MapContributor( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_FriendlyHat( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_SaxxyAwardDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_MvmChallenges( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_SquadSurplusClaimedBy( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_DynamicRecipe( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_Leaderboard( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); +#endif // PROJECT_TF + virtual void Generate_XifierToolTargetItem( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_Painted( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_Uses( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_LootListDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_EventDetail( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_ItemSetDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_CollectionDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_ExpirationDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_MarketInformation( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_FlagsAttributes( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_DropPeriodDesc( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + + virtual void Generate_VisibleAttributes( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + virtual void Generate_DirectX8Warning( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem ); + + // Helpers for the above. + virtual void Generate_ItemLevelDesc_Default( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, const locchar_t *locTypename ); + virtual bool BGenerate_ItemLevelDesc_StrangeNameAndStats( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, const locchar_t *locTypename ); // returns true if generated a level/desc based on strange stats or false if nothing was generated + const locchar_t *GetLocalizedStringForStrangeRestrictionAttr( const CLocalizationProvider *pLocalizationProvider, const IEconItemInterface *pEconItem, int iAttrIndex ) const; + + // Internal data. + void YieldingFillOutAccountPersonaName( const CLocalizationProvider *pLocalizationProvider, uint32 unAccountID ); + const locchar_t *FindAccountPersonaName( uint32 unAccountID ) const; + + void YieldingFillOutAccountTypeCache( uint32 unAccountID, int nClassID ); + GCSDK::CSharedObjectTypeCache *FindAccountTypeCache( uint32 unAccountID, int nClassID ) const; + + // Defined in source file -- not meant for external access. + template < typename T > + const T *FindAccountTypeCacheSingleton( uint32 unAccountID, int nClassID ) const; + + // Precache data. + struct steam_account_persona_name_t + { + uint32 unAccountID; + CUtlConstStringBase<locchar_t> loc_sPersonaName; + }; + + CUtlVector<steam_account_persona_name_t> vecPersonaNames; + + struct steam_account_type_cache_t + { + uint32 unAccountID; + int nClassID; + GCSDK::CSharedObjectTypeCache *pTypeCache; + }; + + CUtlVector<steam_account_type_cache_t> vecTypeCaches; + +#if TF_ANTI_IDLEBOT_VERIFICATION + MD5Context_t *m_pHashContext; + bool m_bIsVerbose; +#ifdef GC_DLL + bool m_bTextModeEnabled; +#else // if defined( CLIENT_DLL ) + bool m_bUnknownPlayer; +#endif // GC_DLL +#endif // TF_ANTI_IDLEBOT_VERIFICATION +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconAttributeDescription +{ +private: + // Internal constructor. + void InternalConstruct + ( + const CLocalizationProvider *pLocalizationProvider, + const CEconItemAttributeDefinition *pAttribDef, + attrib_value_t value, + TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( MD5Context_t *pHashContext ) TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA + IAccountPersonaLocalizer *pOptionalAccountPersonaLocalizer + ); + +public: + // Outward-facing constructor. Pass in whatever you want for "value" and we'll + // use the raw bits for their value interpreted however the attribute says. + template < typename T > + CEconAttributeDescription + ( + const CLocalizationProvider *pLocalizationProvider, + const CEconItemAttributeDefinition *pAttribDef, + T value, + TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( MD5Context_t *pHashContext = NULL ) TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA + IAccountPersonaLocalizer *pOptionalAccountPersonaLocalizer = NULL + ) + { + COMPILE_TIME_ASSERT( sizeof( T ) == sizeof( attrib_value_t ) ); + + InternalConstruct( pLocalizationProvider, pAttribDef, *(attrib_value_t *)&value, TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( pHashContext ) TF_ANTI_IDLEBOT_VERIFICATION_ONLY_COMMA pOptionalAccountPersonaLocalizer ); + } + + const CUtlConstStringBase<locchar_t>& GetDescription() const { return m_loc_sValue; } + const CUtlConstStringBase<locchar_t>& GetShortDescription() const { return m_loc_sShortValue; } + attrib_colors_t GetDefaultColor() const { return m_eDefaultColor; } + +private: + CUtlConstStringBase<locchar_t> m_loc_sValue; + CUtlConstStringBase<locchar_t> m_loc_sShortValue; + attrib_colors_t m_eDefaultColor; +}; + +//----------------------------------------------------------------------------- +// Purpose: control how item name is generated +//----------------------------------------------------------------------------- +enum EGenerateLocalizedFullItemNameFlag_t +{ + k_EGenerateLocalizedFullItemName_Default = 0, + k_EGenerateLocalizedFullItemName_WithPaintWear = ( 1 << 0 ), + k_EGenerateLocalizedFullItemName_WithoutCustomName = ( 1 << 1 ), + k_EGenerateLocalizedFullItemName_WithoutQuality = ( 1 << 2 ), + k_EGenerateLocalizedFullItemName_WithPaintkitNoItem = ( 1 << 3 ), +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconItemLocalizedFullNameGenerator +{ +public: + CEconItemLocalizedFullNameGenerator( const CLocalizationProvider *pLocalizationProvider, const CEconItemDefinition *pItemDef, bool bUseingHashContext = true, entityquality_t eQuality = AE_UNIQUE ); + + const locchar_t *GetFullName() const { return m_loc_LocalizedItemName; } + +private: + locchar_t m_loc_LocalizedItemName[ MAX_ITEM_NAME_LENGTH ]; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconItemLocalizedMarketNameGenerator +{ +public: + CEconItemLocalizedMarketNameGenerator( const CLocalizationProvider *pLocalizationProvider, CEconItem *pItem, bool bUseingHashContext = true ); + + const locchar_t *GetFullName() const { return m_loc_LocalizedItemName; } + +private: + locchar_t m_loc_LocalizedItemName[ MAX_ITEM_NAME_LENGTH ]; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSteamAccountIDAttributeCollector : public CEconItemSpecificAttributeIterator +{ +public: + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) OVERRIDE + { + if ( pAttrDef->GetDescriptionFormat() == ATTDESCFORM_VALUE_IS_ACCOUNT_ID ) + { + m_vecSteamAccountIDs.AddToTail( value ); + } + + return true; + } + + // Data access. + const CUtlVector<uint32>& GetAccountIDs() + { + return m_vecSteamAccountIDs; + } + +private: + CUtlVector<uint32> m_vecSteamAccountIDs; +}; + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +#if TF_ANTI_IDLEBOT_VERIFICATION + +#include "checksum_md5.h" + +enum +{ + kTFDescriptionHash_TextmodeArbitraryKey = 0x19a04480, + kTFDescriptionHash_ValidArbitraryKey = 0xa0939180, + kTFDescriptionHash_MultiRunArbitraryKey = 0x5790a31d, + kTFDescriptionHash_ChallengeXorShenanigans = 0x1870f0d2, +}; + +// Global function/variable names show up in Mac binaries so we give them names that will stand out less +// here and then #define them back so the code is readable. +#define TF_Description_HashDataMungeContents CompressFragments +inline void TFDescription_HashDataMungeContents( MD5Context_t *out_pContext, const void *pContents, size_t unContentLength, bool bIsVerbose, const char* pszInfo ) +{ + Assert( out_pContext ); + Assert( pContents ); + + MD5Update( out_pContext, static_cast<const uint8 *>( pContents ), unContentLength ); + + // if Verbose, report the contents to the GC + if ( bIsVerbose ) + { + MD5Context_t md5ContextEx = *out_pContext; + MD5Value_t md5ResultEx; + MD5Final( &md5ResultEx.bits[0], &md5ContextEx ); + +#ifdef GC_DLL + EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Verbose Verification GC : [ %s ] - [ %s ] \n", MD5_Print( md5ResultEx.bits, MD5_DIGEST_LENGTH ), pszInfo ); +#else + // Client reports this to the GC + GCSDK::CProtoBufMsg<CGCMsgTFSyncEx> msgResponse( k_EMsgGC_ClientVerificationVerboseResponse ); + msgResponse.Body().set_version_checksum( pszInfo ); // before + msgResponse.Body().set_version_checksum_ex( &md5ResultEx.bits[0], MD5_DIGEST_LENGTH ); // after + GCClientSystem()->BSendMessage( msgResponse ); +#endif + //delete [] pArr; + } +} + +// Okay, this one is actually just a helper macro. +#define TFDescription_HashDataMunge( context, field, bIsVerbose, pszInfo ) \ + { \ + TFDescription_HashDataMungeContents( context, (void *)&field, sizeof( field ), bIsVerbose, pszInfo ); \ + } + +#endif // TF_ANTI_IDLEBOT_VERIFICATION + +#endif // BUILD_ITEM_NAME_AND_DESC + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +struct CLocalizedRTime32 +{ + RTime32 m_unTime; + bool m_bForceGMTOnClient; // display this time in GMT on the client? by default, clients show local time; the GC will ignore this flag and always display GMT + const CLocalizationProvider *m_pLocalizationProvider; + TF_ANTI_IDLEBOT_VERIFICATION_ONLY_ARG( MD5Context_t *m_pHashContext; ) +}; + +template < > +class CLocalizedStringArg<CLocalizedRTime32> +{ +public: + enum { kIsValid = true }; + + CLocalizedStringArg( const CLocalizedRTime32& cTimeIn ); + + const locchar_t *GetLocArg() const { return m_Str.Get(); } + +private: + CUtlConstStringBase<locchar_t> m_Str; +}; + +#endif // ECONITEMDESCRIPTION_H diff --git a/game/shared/econ/econ_item_factory.cpp b/game/shared/econ/econ_item_factory.cpp new file mode 100644 index 0000000..426ce85 --- /dev/null +++ b/game/shared/econ/econ_item_factory.cpp @@ -0,0 +1,331 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: EconItemFactory: Manages rolling for items requested by the game server +// +//============================================================================= + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +#include "econ/econ_assetapi_context.h" + + +using namespace GCSDK; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CEconItemFactory::CEconItemFactory( ) + : m_ulNextObjID( 0 ) + , m_bIsInitialized( false ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Initializes the item factory and schema. Return false if init failed +//----------------------------------------------------------------------------- +bool CEconItemFactory::BYieldingInit() +{ + CUtlVector< CUtlString > vecErrors; + bool bRet = m_schema.BInit( "scripts/items/unencrypted/items_master.txt", "GAME", &vecErrors ); + + FOR_EACH_VEC( vecErrors, nError ) + { + EmitError( SPEW_GC, "%s\n", vecErrors[nError].Get() ); + } + + static const char *pchMaxIDQuery = "SELECT MAX( ID ) FROM " + "( select max(ID) AS ID FROM Item UNION SELECT MAX(ID) AS ID FROM ForeignItem ) as tbl"; + + CSQLAccess sqlAccess; + if( !sqlAccess.BYieldingExecuteSingleResult<uint64, uint64>( NULL, pchMaxIDQuery, k_EGCSQLType_int64, &m_ulNextObjID, NULL ) ) + { + EmitError( SPEW_GC, "Failed to read max item ID" ); + return false; + } + m_ulNextObjID++; // our next ID is one past the current max ID + + m_bIsInitialized = bRet; + return bRet; +} + +static const CEconItemQualityDefinition *GetQualityDefinitionForItemCreation( const CItemSelectionCriteria *pOptionalCriteria, const CEconItemDefinition *pItemDef ) +{ + Assert( pItemDef ); + + // Do we have a quality specified? If so, is it a valid quality? If not, we fall back to the + // quality specified by the item definition, the schema, etc. + uint8 unQuality = k_unItemQuality_Any; + + // Quality specified in generation request via criteria? + if ( pOptionalCriteria && pOptionalCriteria->BQualitySet() ) + { + unQuality = pOptionalCriteria->GetQuality(); + } + + // If not: quality specified in item definition? + if ( unQuality == k_unItemQuality_Any ) + { + unQuality = pItemDef->GetQuality(); + } + + // Final fallback: default quality in schema. + if ( unQuality == k_unItemQuality_Any ) + { + unQuality = GetItemSchema()->GetDefaultQuality(); + } + + AssertMsg( unQuality != k_unItemQuality_Any, "Unable to locate valid quality!" ); + + return GetItemSchema()->GetQualityDefinition( unQuality ); +} + +//----------------------------------------------------------------------------- +// Purpose: Creates an item matching the incoming item selection criteria +// Input: pItem - Pointer to the item to fill in +// criteria - The criteria that the generated item must match +// Output: True if a matching item could be generated, false otherwise +//----------------------------------------------------------------------------- +CEconItem *CEconItemFactory::CreateRandomItem( const CEconGameAccount *pGameAccount, const CItemSelectionCriteria &criteria ) +{ + // Find a matching item definition. + const CEconItemDefinition *pItemDef = RollItemDefinition( criteria ); + if ( NULL == pItemDef ) + { + EmitWarning( SPEW_GC, 2, "CEconItemFactory::CreateRandomItem(): Item creation request with no matching definition\n" ); + return NULL; + } + + const CEconItemQualityDefinition *pQualityDef = GetQualityDefinitionForItemCreation( &criteria, pItemDef ); + if ( NULL == pQualityDef ) + { + EmitWarning( SPEW_GC, 2, "CEconItemFactory::CreateRandomItem(): Item creation request with unknown quality\n" ); + return NULL; + } + + // At this point we have everything that can fail will already have failed, so we can safely + // create an item and just move properties over to it. + CEconItem *pItem = new CEconItem(); + pItem->SetItemID( GetNextID() ); + pItem->SetDefinitionIndex( pItemDef->GetDefinitionIndex() ); + pItem->SetItemLevel( criteria.BItemLevelSet() ? criteria.GetItemLevel() : pItemDef->RollItemLevel() ); + pItem->SetQuality( pQualityDef->GetDBValue() ); + pItem->SetInventoryToken( criteria.GetInitialInventory() ); + pItem->SetQuantity( criteria.BInitialQuantitySet() ? criteria.GetInitialQuantity() : pItemDef->GetDefaultDropQuantity() ); + // don't set account ID + + // Add any custom attributes we need + if( !BAddGCGeneratedAttributesToItem( pGameAccount, pItem ) ) + { + delete pItem; + EmitWarning( SPEW_GC, 2, "CEconItemFactory::CreateSpecificItem(): Failed to generate attributes\n" ); + return NULL; + } + + return pItem; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates an item based on a specific item definition index +// Input: pItem - Pointer to the item to fill in +// unDefinitionIndex - The definition index of the item to create +// Output: True if a matching item could be generated, false otherwise +//----------------------------------------------------------------------------- +CEconItem *CEconItemFactory::CreateSpecificItem( const CEconGameAccount *pGameAccount, item_definition_index_t unDefinitionIndex ) +{ + // Find the matching index + const CEconItemDefinition *pItemDef = m_schema.GetItemDefinition( unDefinitionIndex ); + if ( NULL == pItemDef ) + { + EmitWarning( SPEW_GC, 2, "CEconItemFactory::CreateSpecificItem(): Item creation request with no matching definition (def index %u)\n", unDefinitionIndex ); + return NULL; + } + + const CEconItemQualityDefinition *pQualityDef = GetQualityDefinitionForItemCreation( NULL, pItemDef ); + if ( NULL == pQualityDef ) + { + EmitWarning( SPEW_GC, 2, "CEconItemFactory::CreateSpecificItem(): Item creation request with unknown quality\n" ); + return NULL; + } + + CEconItem *pItem = new CEconItem(); + if ( pGameAccount != NULL ) + pItem->SetItemID( GetNextID() ); + pItem->SetDefinitionIndex( unDefinitionIndex ); + pItem->SetItemLevel( pItemDef->RollItemLevel() ); + pItem->SetQuality( pQualityDef->GetDBValue() ); + // don't set inventory token + pItem->SetQuantity( MAX( 1, pItemDef->GetDefaultDropQuantity() ) ); + + // Startup test code calls this with a null pGameAccount. + if ( pGameAccount != NULL ) + { + pItem->SetAccountID( pGameAccount->Obj().m_unAccountID ); + + // Add any custom attributes we need + if( !BAddGCGeneratedAttributesToItem( pGameAccount, pItem ) ) + { + delete pItem; + EmitWarning( SPEW_GC, 2, "CEconItemFactory::CreateSpecificItem(): Failed to generate attributes\n" ); + return NULL; + } + } + + return pItem; +} + + +//----------------------------------------------------------------------------- +// Purpose: Randomly chooses an item definition that matches the criteria +// Input: sCriteria - The criteria that the generated item must match +// Output: The chosen item definition, or NULL if no item could be selected +//----------------------------------------------------------------------------- +const CEconItemDefinition *CEconItemFactory::RollItemDefinition( const CItemSelectionCriteria &criteria ) const +{ + // Determine which item templates match the criteria + CUtlVector<item_definition_index_t> vecMatches; + const CEconItemSchema::ItemDefinitionMap_t &mapDefs = m_schema.GetItemDefinitionMap(); + + FOR_EACH_MAP_FAST( mapDefs, i ) + { + if ( criteria.BEvaluate( mapDefs[i] ) ) + { + vecMatches.AddToTail( mapDefs.Key( i ) ); + } + } + + if ( 0 == vecMatches.Count() ) + return NULL; + + // Choose a random match + int iIndex = RandomInt( 0, vecMatches.Count() - 1 ); + return m_schema.GetItemDefinition( vecMatches[iIndex] ); +} + +//----------------------------------------------------------------------------- +// Purpose: Generates attributes that the item definition insists it always has, but must be generated by the GC +// Input: +//----------------------------------------------------------------------------- +bool CEconItemFactory::BAddGCGeneratedAttributesToItem( const CEconGameAccount *pGameAccount, CEconItem *pItem ) const +{ + const CEconItemDefinition *pDef = m_schema.GetItemDefinition( pItem->GetDefinitionIndex() ); + if ( !pDef ) + return false; + + const CUtlVector<static_attrib_t> &vecStaticAttribs = pDef->GetStaticAttributes(); + + // Only generate attributes that force the GC to generate them (so they vary per item created) + FOR_EACH_VEC( vecStaticAttribs, i ) + { + if ( vecStaticAttribs[i].bForceGCToGenerate ) + { + ApplyStaticAttributeToItem( pItem, vecStaticAttribs[i], pGameAccount ); + } + } + + const IEconTool* pTool = pDef->GetEconTool(); + if( pTool ) + { + if( !pTool->BGenerateDynamicAttributes( pItem, pGameAccount ) ) + return false; + } + + if ( !pDef->BApplyPropertyGenerators( pItem ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemFactory::ApplyStaticAttributeToItem( CEconItem *pItem, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount ) const +{ + static CSchemaAttributeDefHandle pAttr_ElevateQuality( "elevate quality" ); + static CSchemaAttributeDefHandle pAttr_ElevateToUnusual( "elevate to unusual if applicable" ); + + static CSchemaAttributeDefHandle pAttr_Particle( "attach particle effect" ); + static CSchemaAttributeDefHandle pAttr_HatUnusual( "hat only unusual effect" ); + + static CSchemaAttributeDefHandle pAttrDef_TauntUnusual( "taunt only unusual effect" ); + static CSchemaAttributeDefHandle pAttrDef_TauntUnusualAttr( "on taunt attach particle index" ); + + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( staticAttrib.iDefIndex ); + Assert( pAttrDef ); + + // Special-case the elevate-quality attribute. + if ( pAttrDef == pAttr_ElevateQuality ) + { + //AssertMsg( CEconItem::GetTypedAttributeType<CSchemaAttributeType_Default>( pAttrDef ), "Elevate quality attribute doesn't have the right type!" ); + int iQuality = (int)staticAttrib.m_value.asFloat; + + // Do not change the quality of an item to Strange if it is not basic + if ( iQuality == AE_STRANGE ) + { + if ( pItem->GetQuality() == AE_UNIQUE || pItem->GetQuality() == AE_PAINTKITWEAPON || pItem->GetQuality() == AE_NORMAL ) + { + pItem->SetQuality( iQuality ); + } + // If the quality is strange, strangify this item + StrangifyItemInPlace( pItem ); + } + else + { + pItem->SetQuality( iQuality ); + } + + return; + } + // Special-case to elevate-quality only if item has particles. This 'attr' needs to be added LAST in a lootlist + // Or rather after particles may have been granted + else if ( pAttrDef == pAttr_ElevateToUnusual ) + { + // Scan all attributes. + if ( pItem->FindAttribute( pAttr_Particle ) || pItem->FindAttribute( pAttrDef_TauntUnusualAttr ) ) + { + pItem->SetQuality( AE_UNUSUAL ); + } + return; + } + else if ( pAttrDef == pAttr_HatUnusual ) + { + // Ensure the target item is a hat, if it is not bail, if it is setup a particle effect attr (Whole head items are considered 'hats' for purposes of unusuals ) + + if ( !(pItem->GetItemDefinition()->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "hat" ) ) + && !(pItem->GetItemDefinition()->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "whole_head" ) ) + ) { + // does not match, bail + return; + } + + // create a new static attrib + static_attrib_t unusualAttr( staticAttrib ); + + // load the normal attach effect instead + pAttr_Particle->GetAttributeType()->LoadOrGenerateEconAttributeValue( pItem, pAttr_Particle, unusualAttr, pGameAccount ); + return; + } + else if ( pAttrDef == pAttrDef_TauntUnusual ) + { + // Ensure the target item is a taunt, if it is not bail + if ( pItem->GetItemDefinition()->GetLoadoutSlot( 0 ) != LOADOUT_POSITION_TAUNT ) + { + // does not match, bail + CFmtStr fmtStr( "Attempted to put an unusual taunt effect onto item %s, but it's not a taunt! Check which lootlists it appears in and remove it from any that are trying to unusualize it!", pItem->GetItemDefinition()->GetItemDefinitionName() ); + EmitError( SPEW_GC, "%s\n", fmtStr.Get() ); + return; + } + + // create a new static attrib + static_attrib_t unusualAttr( staticAttrib ); + + // load the normal attach effect instead + pAttrDef_TauntUnusualAttr->GetAttributeType()->LoadOrGenerateEconAttributeValue( pItem, pAttrDef_TauntUnusualAttr, unusualAttr, pGameAccount ); + return; + } + + // Custom attribute initialization code? + pAttrDef->GetAttributeType()->LoadOrGenerateEconAttributeValue( pItem, pAttrDef, staticAttrib, pGameAccount ); +} diff --git a/game/shared/econ/econ_item_factory.h b/game/shared/econ/econ_item_factory.h new file mode 100644 index 0000000..5673f28 --- /dev/null +++ b/game/shared/econ/econ_item_factory.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: EconItemFactory: Manages rolling for items requested by the game server +// +//============================================================================= + +#ifndef ECONITEMFACTORY_H +#define ECONITEMFACTORY_H +#ifdef _WIN32 +#pragma once +#endif + +class CEconItem; +class CEconGameAccount; + +namespace GCSDK +{ + class CGCSharedObjectCache; +} + +#include "game_item_schema.h" + +//----------------------------------------------------------------------------- +// CEconItemFactory +// Factory responsible for rolling random items +//----------------------------------------------------------------------------- +class CEconItemFactory +{ +public: + CEconItemFactory( ); + + // Gets a pointer to the underlying item schema the factory is using + GameItemSchema_t &GetSchema() { return m_schema; } + + // Create a random item based on the incoming item selection criteria + CEconItem *CreateRandomItem( const CEconGameAccount *pGameAccount, const CItemSelectionCriteria &criteria ); + + // Create an item from a specific definition index + CEconItem *CreateSpecificItem( const CEconGameAccount *pGameAccount, item_definition_index_t unDefinitionIndex ); + + CEconItem *CreateSpecificItem( GCSDK::CGCSharedObjectCache *pUserSOCache, item_definition_index_t unDefinitionIndex ) + { + return CreateSpecificItem( pUserSOCache->GetSingleton<CEconGameAccount>(), unDefinitionIndex ); + } + + uint64 GetNextID() { Assert( m_bIsInitialized ); return m_ulNextObjID++; } + + bool BYieldingInit(); + bool BIsInitialized() { return m_bIsInitialized; } + +#ifdef DBGFLAG_VALIDATE + virtual void Validate( CValidator &validator, const char *pchName ) + { + VALIDATE_SCOPE(); + ValidateObj( m_schema ); + } +#endif // DBGFLAG_VALIDATE + + void ApplyStaticAttributeToItem( CEconItem *pItem, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount ) const; + const CEconItemDefinition *RollItemDefinition( const CItemSelectionCriteria &criteria ) const; + +private: + bool BAddGCGeneratedAttributesToItem( const CEconGameAccount *pGameAccount, CEconItem *pItem ) const; + +private: + + // The schema this factory uses to create items + GameItemSchema_t m_schema; + + // the next item ID to give out + itemid_t m_ulNextObjID; + bool m_bIsInitialized; +}; + +#endif //ECONITEMFACTORY_H diff --git a/game/shared/econ/econ_item_interface.cpp b/game/shared/econ/econ_item_interface.cpp new file mode 100644 index 0000000..f915846 --- /dev/null +++ b/game/shared/econ/econ_item_interface.cpp @@ -0,0 +1,412 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#include "cbase.h" +#include "econ_item_interface.h" +#include "econ_item_tools.h" // needed for CEconTool_WrappedGift definition for IsMarketable() +#include "rtime.h" + +#ifdef STAGING_ONLY +ConVar tf_paint_kit_force_wear( "tf_paint_kit_force_wear", "0", FCVAR_REPLICATED, "Set to force the wear level of paink kit weapons and ignore the GC dynamic attribute value." ); +#endif + +// -------------------------------------------------------------------------- +bool IEconItemInterface::GetCustomPaintKitWear( float &flWear ) const +{ + +#ifdef STAGING_ONLY + // don't assert in staging if this ConVar is set + if ( tf_paint_kit_force_wear.GetInt() > 0 ) + { + flWear = tf_paint_kit_force_wear.GetFloat(); + return true; + } +#endif // STAGING_ONLY + + static CSchemaAttributeDefHandle pAttrDef_PaintKitWear( "set_item_texture_wear" ); + float flPaintKitWear = 0; + if ( pAttrDef_PaintKitWear && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, pAttrDef_PaintKitWear, &flPaintKitWear ) ) + { + flWear = flPaintKitWear; + return true; + } + + static CSchemaAttributeDefHandle pAttrDef_DefaultWear( "texture_wear_default" ); + if ( pAttrDef_DefaultWear && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, pAttrDef_DefaultWear, &flPaintKitWear ) ) + { + flWear = flPaintKitWear; + return true; + } + // If you have no wear, you also should not have a paint kit + AssertMsg( !GetCustomPainkKitDefinition(), "No Wear Found on Item [%llu - %s] that has a Paintkit!", GetID(), GetItemDefinition()->GetDefinitionName() ); + + return false; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool IEconItemInterface::IsTemporaryItem() const +{ + // store preview items are also temporary + if ( GetOrigin() == kEconItemOrigin_PreviewItem ) + return true; + + RTime32 rtTime = GetExpirationDate(); + if ( rtTime > 0 ) + return true; + + return false; +} + +// -------------------------------------------------------------------------- +RTime32 IEconItemInterface::GetExpirationDate() const +{ + COMPILE_TIME_ASSERT( sizeof( float ) == sizeof( RTime32 ) ); + + // dynamic attributes, if present, will override any static expiration timer + static CSchemaAttributeDefHandle pAttrib_ExpirationDate( "expiration date" ); + + attrib_value_t unAttribExpirationTimeBits; + COMPILE_TIME_ASSERT( sizeof( unAttribExpirationTimeBits ) == sizeof( RTime32 ) ); + + if ( pAttrib_ExpirationDate && FindAttribute( pAttrib_ExpirationDate, &unAttribExpirationTimeBits ) ) + return *(RTime32 *)&unAttribExpirationTimeBits; + + // do we have a static timer set in the schema for all instances to expire? + return GetItemDefinition() + ? GetItemDefinition()->GetExpirationDate() + : RTime32( 0 ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +RTime32 IEconItemInterface::GetTradableAfterDateTime() const +{ + static CSchemaAttributeDefHandle pAttrib_TradableAfter( "tradable after date" ); + Assert( pAttrib_TradableAfter ); + + if ( !pAttrib_TradableAfter ) + return 0; + + RTime32 rtTimestamp; + if ( !FindAttribute( pAttrib_TradableAfter, &rtTimestamp ) ) + return 0; + + return rtTimestamp; +} + +// -------------------------------------------------------------------------- +// Purpose: Return true if this item can never be traded +// -------------------------------------------------------------------------- +bool IEconItemInterface::IsPermanentlyUntradable() const +{ + if ( GetItemDefinition() == NULL ) + return true; + + // tagged to not be a part of the economy? + if ( ( kEconItemFlag_NonEconomy & GetFlags() ) != 0 ) + return true; + + // check attributes + + static CSchemaAttributeDefHandle pAttrib_AlwaysTradable( "always tradable" ); + static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" ); + static CSchemaAttributeDefHandle pAttrib_NonEconomy( "non economy" ); + + Assert( pAttrib_AlwaysTradable != NULL ); + Assert( pAttrib_CannotTrade != NULL ); + + if ( pAttrib_AlwaysTradable == NULL || pAttrib_CannotTrade == NULL || pAttrib_NonEconomy == NULL ) + return true; + + // Order matters, check for nonecon first. Always tradable overrides cannot trade. + if ( FindAttribute( pAttrib_NonEconomy ) ) + return true; + + if ( FindAttribute( pAttrib_AlwaysTradable ) ) // *sigh* + return false; + + if ( FindAttribute( pAttrib_CannotTrade ) ) + return true; + + // items gained in this way are not tradable + switch ( GetOrigin() ) + { + case kEconItemOrigin_Invalid: + case kEconItemOrigin_Achievement: + case kEconItemOrigin_Foreign: + case kEconItemOrigin_PreviewItem: + case kEconItemOrigin_SteamWorkshopContribution: + return true; + } + + // temporary items (items that will expire for any reason) cannot be traded + if ( IsTemporaryItem() ) + return true; + + // certain quality levels are not tradable + if ( GetQuality() >= AE_COMMUNITY && GetQuality() <= AE_SELFMADE ) + return true; + + // explicitly marked cannot trade? + if ( ( kEconItemFlag_CannotTrade & GetFlags() ) != 0 ) + return true; + + return false; +} + +// -------------------------------------------------------------------------- +// Purpose: Return true if this item is a commodity on the Market (can place buy orders) +// -------------------------------------------------------------------------- +bool IEconItemInterface::IsCommodity() const +{ + if ( GetItemDefinition() == NULL ) + return false; + + static CSchemaAttributeDefHandle pAttrib_IsCommodity( "is commodity" ); + if ( FindAttribute( pAttrib_IsCommodity ) ) + return true; + + return false; +} + +// -------------------------------------------------------------------------- +// Purpose: Return true if temporarily untradable +// -------------------------------------------------------------------------- +bool IEconItemInterface::IsTemporarilyUntradable() const +{ + // Temporary untradability does NOT take "always tradable" into account + if ( GetTradableAfterDateTime() >= CRTime::RTime32TimeCur() ) + return true; + + return false; +} + +// -------------------------------------------------------------------------- +// Purpose: Return true if this item is untradable +// -------------------------------------------------------------------------- +bool IEconItemInterface::IsTradable() const +{ + // Items that are expired are never listable, regardless of other rules. + //RTime32 timeExpirationDate = GetExpirationDate(); + //if ( timeExpirationDate > 0 && timeExpirationDate < CRTime::RTime32TimeCur() ) + // return false; + + return GetUntradabilityFlags() == 0; +} + +// -------------------------------------------------------------------------- +// Purpose: Return untradability flags +// -------------------------------------------------------------------------- +int IEconItemInterface::GetUntradabilityFlags() const +{ + int nFlags = 0; + if ( IsTemporarilyUntradable() ) + { + nFlags |= k_Untradability_Temporary; + } + + if ( IsPermanentlyUntradable() ) + { + nFlags |= k_Untradability_Permanent; + } + + return nFlags; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool IEconItemInterface::IsUsableInCrafting() const +{ + if ( GetItemDefinition() == NULL ) + return false; + + // tagged to not be a part of the economy? + if ( ( kEconItemFlag_NonEconomy & GetFlags() ) != 0 ) + return false; + + // always craftable? + static CSchemaAttributeDefHandle pAttrib_AlwaysUsableInCraft( "always tradable" ); + Assert( pAttrib_AlwaysUsableInCraft ); + + if ( FindAttribute( pAttrib_AlwaysUsableInCraft ) ) + return true; + + // never craftable? + static CSchemaAttributeDefHandle pAttrib_NeverCraftable( "never craftable" ); + Assert( pAttrib_NeverCraftable ); + + if ( FindAttribute( pAttrib_NeverCraftable ) ) + return false; + + // temporary items (items that will expire for any reason) cannot be turned into + // permanent items + if ( IsTemporaryItem() ) + return false; + + // explicitly marked not usable in crafting? + if ( ( kEconItemFlag_CannotBeUsedInCrafting & GetFlags() ) != 0 ) + return false; + + // items gained in this way are not craftable + switch ( GetOrigin() ) + { + case kEconItemOrigin_Invalid: + case kEconItemOrigin_Foreign: + case kEconItemOrigin_StorePromotion: + case kEconItemOrigin_SteamWorkshopContribution: + return false; + + // purchased items can be used in crafting if explicitly tagged, but not by default + case kEconItemOrigin_Purchased: + // deny items the GC didn't flag at purchase time + if ( (GetFlags() & kEconItemFlag_PurchasedAfterStoreCraftabilityChanges2012) == 0 ) + return false; + + // deny items that can never be used + if ( (GetItemDefinition()->GetCapabilities() & ITEM_CAP_CAN_BE_CRAFTED_IF_PURCHASED) == 0 ) + return false; + + break; + } + + // certain quality levels are not craftable + if ( GetQuality() >= AE_COMMUNITY && GetQuality() <= AE_SELFMADE ) + return false; + + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool IEconItemInterface::IsMarketable() const +{ + const CEconItemDefinition *pItemDef = GetItemDefinition(); + if ( pItemDef == NULL ) + return false; + + // Untradeable items can never be marketed, regardless of other rules. + // Temporarily untradable items can be marketed, only permanent untradable items cannot be marketed + if ( IsPermanentlyUntradable() ) + return false; + + // Items that are expired are never listable, regardless of other rules. + RTime32 timeExpirationDate = GetExpirationDate(); + if ( timeExpirationDate > 0 && timeExpirationDate < CRTime::RTime32TimeCur() ) + return false; + + // Initially, only TF2 supports listing items in the Marketplace. +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) || defined( TF_GC_DLL ) + { + // User-created wrapped gifts are untradeable for the moment. This would provide a backdoor + // for users to sell anything they wanted, which is interesting but not what we want in + // the initial launch. + if ( pItemDef->GetTypedEconTool<CEconTool_WrappedGift>() ) + return false; + + // All other tools are listable. This includes keys, paints, backpack expanders, strange + // parts, Halloween spells, wedding rings, etc. It does not includes gifts (see above), + // noisemakers, or crates (see below). + if ( pItemDef->IsTool() ) + return true; + + // All crates are listable. Anything with the "decodable" flag is considered a crate. + if ( (pItemDef->GetCapabilities() & ITEM_CAP_DECODABLE) != 0 ) + return true; + + // Genuine-quality items come from time-limited purchase promos and are listable. Vintage + // items are from one-time transitions and are all finite quality. Haunted quality items are + // TF-Halloween-event specific. Some of the older haunted items didn't generate revenue, but + // the content is all old and there seems to be little harm in letting it be listed. The + // haunted items from 2013 all come from crates, which means they all generated revenue. + // Collectors items are created from a finite set of recipes. + // Paintkit Weapons are from cases or operations + if ( GetQuality() == AE_RARITY1 || GetQuality() == AE_VINTAGE || GetQuality() == AE_HAUNTED + || GetQuality() == AE_COLLECTORS || GetQuality() == AE_PAINTKITWEAPON ) + return true; + + // All festive items are from time-limited holiday crates and are listable. This code seems + // safe. (...) (This code is in fact so safe that if we just do a substring match we'll also + // allow "A Rather Festive Tree".) + if ( !V_strncmp( pItemDef->GetDefinitionName(), "Festive", 7 ) ) + return true; + + // All botkiller items come from MvM rewards and are listable. This does a substring search + // to find all varieties (gold, silver, rust, etc.), etc. + if ( V_strstr( pItemDef->GetDefinitionName(), " Botkiller " ) ) + return true; + + // Mvm V2 Robit Parts + if ( V_strstr( pItemDef->GetDefinitionName(), "Robits " ) ) + return true; + + // MvM Killstreak Weapons + static CSchemaAttributeDefHandle pAttr_killstreak( "killstreak tier" ); + if ( FindAttribute( pAttr_killstreak ) ) + return true; + + // Australium Items + static CSchemaAttributeDefHandle pAttrDef_IsAustralium( "is australium item" ); + if ( FindAttribute( pAttrDef_IsAustralium ) ) + return true; + + // Glitch GateHat Replacement Item + static CSchemaItemDefHandle pItemDef_GlitchedCircuit( "Glitched Circuit Board" ); + if ( pItemDef == pItemDef_GlitchedCircuit ) + return true; + + // Anything that says it wants to be marketable. + static CSchemaAttributeDefHandle pAttrDef_IsMarketable( "is marketable" ); + if ( FindAttribute( pAttrDef_IsMarketable ) ) + return true; + + // Anything that is of limited quantity (ie limited promos) + static CSchemaAttributeDefHandle pAttrDef_IsLimited( "limited quantity item" ); + if ( FindAttribute( pAttrDef_IsLimited ) ) + return true; + + // Allow the Giving items (not a wrapped_gift but a gift, ie Secret Saxton, Pile O Gifts, Pallet of Keys) + const CEconTool_Gift *pEconToolGift = pItemDef->GetTypedEconTool<CEconTool_Gift>(); + if ( pEconToolGift ) + return true; + + // Unusual Cosmetics and Taunts + if ( GetQuality() == AE_UNUSUAL && ( GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_MISC || GetItemDefinition()->GetLoadoutSlot( 0 ) == LOADOUT_POSITION_TAUNT ) ) + return true; + + // Strange items. Dont just check for strange quality, actually check for a strange attribute. + // See if we've got any strange attributes. + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( FindAttribute( GetKillEaterAttr_Score( i ) ) ) + { + return true; + } + } + } +#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL ) || defined( TF_GC_DLL ) + + // By default, items aren't listable. + return false; +} + +// -------------------------------------------------------------------------- +const char *IEconItemInterface::GetDefinitionString( const char *pszKeyName, const char *pszDefaultValue ) const +{ + const GameItemDefinition_t *pDef = GetItemDefinition(); + if ( pDef ) + return pDef->GetDefinitionString( pszKeyName, pszDefaultValue ); + return pszDefaultValue; +} + +// -------------------------------------------------------------------------- +KeyValues *IEconItemInterface::GetDefinitionKey( const char *pszKeyName ) const +{ + const GameItemDefinition_t *pDef = GetItemDefinition(); + if ( pDef ) + return pDef->GetDefinitionKey( pszKeyName ); + return NULL; +} diff --git a/game/shared/econ/econ_item_interface.h b/game/shared/econ/econ_item_interface.h new file mode 100644 index 0000000..287ab0a --- /dev/null +++ b/game/shared/econ/econ_item_interface.h @@ -0,0 +1,546 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: CEconItem, a shared object for econ items +// +//============================================================================= + +#ifndef ECONITEMINTERFACE_H +#define ECONITEMINTERFACE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "game_item_schema.h" // needed for GameItemDefinition_t + +class CAttribute_String; +class CAttribute_DynamicRecipeComponent; +class CAttribute_ItemSlotCriteria; +class CAttribute_WorldItemPlacement; +class IMaterial; +//----------------------------------------------------------------------------- +// Purpose: Template helper classes for dealing with types. +//----------------------------------------------------------------------------- + +// StripConstIfPresent<T> will take an input type T and "return" via ResultType: +// +// - T: T +// - const T: T +// +// This is used to prevent having to have different specializations for "T" versus +// "const T" when checking for equivalent template type arguments, etc. +template < typename T > +struct StripConstIfPresent { typedef T ResultType; }; + +template < typename T > struct StripConstIfPresent<const T> { typedef T ResultType; }; + +// AreTypesIdentical<T, U> takes two input types and "returns" via kValue whether the +// types are exactly equal. This is intended for checking type equivalence at compile-time +// in ways that template specializations for functions/classes may not be ideal for. +// +// We use it in the attribute code to guarantee that we're only doing The Old, Scary Path +// when dealing with attributes of The Old, Scary Type. +template < typename T, typename U > +struct AreTypesIdentical { enum { kValue = false }; }; + +template < typename T > struct AreTypesIdentical<T, T> { enum { kValue = true }; }; + +// IsPointerType<T> takes one input and "returns" via kValue whether the type is a pointer +// type in any way, const, volatile, whatever. +template < typename T > +struct IsPointerType { enum { kValue = false }; }; + +template < typename T > struct IsPointerType<T *> { enum { kValue = true }; }; + +// IsValidAttributeValueTypeImpl<T> is a hand-made specialization for what types we want +// to consider valid attribute data types. This is used as a sanity check to make sure we +// don't pass in completely arbitrary types to things like FindAttribute(). (Doing so +// would cause an assert at runtime, but it seems like getting compile-time asserts is +// advantageous, and probably worth paying the small cost of adding to this list whenever +// a new attribute type is added.) +template < typename T> +struct IsValidAttributeValueTypeImpl { enum { kValue = false }; }; + +template < > struct IsValidAttributeValueTypeImpl<attrib_value_t> { enum { kValue = true }; }; +template < > struct IsValidAttributeValueTypeImpl<float> { enum { kValue = true }; }; +template < > struct IsValidAttributeValueTypeImpl<uint64> { enum { kValue = true }; }; +template < > struct IsValidAttributeValueTypeImpl<CAttribute_String> { enum { kValue = true }; }; +template < > struct IsValidAttributeValueTypeImpl<CAttribute_DynamicRecipeComponent> { enum { kValue = true }; }; +template < > struct IsValidAttributeValueTypeImpl < CAttribute_ItemSlotCriteria > { enum { kValue = true }; }; +template < > struct IsValidAttributeValueTypeImpl < CAttribute_WorldItemPlacement > { enum { kValue = true }; }; + +template < typename T > +struct IsValidAttributeValueType : public IsValidAttributeValueTypeImpl< typename StripConstIfPresent<T>::ResultType > { }; + +//----------------------------------------------------------------------------- +// Purpose: Interface for callback functions per-attribute-data-type. When adding +// a new attribute data type that can't be converted to any existing type, +// you'll need to add a new virtual function here or the code will fail +// to compile. +//----------------------------------------------------------------------------- +class IEconItemAttributeIterator +{ +public: + virtual ~IEconItemAttributeIterator ( ) { } + + // Returns whether to continue iteration after this element. + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) = 0; + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) = 0; + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64& value ) = 0; + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& value ) = 0; + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) = 0; + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria& value ) = 0; + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement& value ) = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Iterator where each callback is default implemented, but the value +// is ignored. Derive from this iterator when you only care about certain +// attribute types. +// +//----------------------------------------------------------------------------- +class CEconItemSpecificAttributeIterator : public IEconItemAttributeIterator +{ + // By default, always return true + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) { return true; } + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) { return true; } + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64& value ) { return true; } + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& value ) { return true; } + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) { return true; } + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria& value ) { return true; } + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement& value ) { return true; } +}; + +//----------------------------------------------------------------------------- +// Purpose: Interface for a single callback function per-attribute, regardless of +// what type of data it stores and what the value is. This can be used +// to count attributes, display generic information about definitions, etc. +// but can't be used to pull data. +// +// To implement a subclass, override the OnIterateAttributeValueUntyped() +// method. +//----------------------------------------------------------------------------- +class IEconItemUntypedAttributeIterator : public IEconItemAttributeIterator +{ +public: + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t ) OVERRIDE + { + return OnIterateAttributeValueUntyped( pAttrDef ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float ) OVERRIDE + { + return OnIterateAttributeValueUntyped( pAttrDef ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64& ) OVERRIDE + { + return OnIterateAttributeValueUntyped( pAttrDef ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& ) OVERRIDE + { + return OnIterateAttributeValueUntyped( pAttrDef ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& ) OVERRIDE + { + return OnIterateAttributeValueUntyped( pAttrDef ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria& ) OVERRIDE + { + return OnIterateAttributeValueUntyped( pAttrDef ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement& ) OVERRIDE + { + return OnIterateAttributeValueUntyped( pAttrDef ); + } + + +private: + virtual bool OnIterateAttributeValueUntyped( const CEconItemAttributeDefinition *pAttrDef ) = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Simple class to answer the question "does this attribute exist" without +// regards to what value it might have. Intended to be used by FindAttribute() +// but made global because why not. +//----------------------------------------------------------------------------- +class CAttributeIterator_HasAttribute : public IEconItemUntypedAttributeIterator +{ +public: + CAttributeIterator_HasAttribute( const CEconItemAttributeDefinition *pAttrDef ) + : m_pAttrDef( pAttrDef ) + , m_bFound( false ) + { + Assert( m_pAttrDef ); + } + + bool WasFound() const + { + return m_bFound; + } + +private: + bool OnIterateAttributeValueUntyped( const CEconItemAttributeDefinition *pAttrDef ) OVERRIDE + { + // We don't assert because we might be reusing the same iterator between calls. + // Assert( !m_bFound ); + + if ( m_pAttrDef == pAttrDef ) + { + m_bFound = true; + } + + return !m_bFound; + } + +private: + const CEconItemAttributeDefinition *m_pAttrDef; + bool m_bFound; +}; + +//----------------------------------------------------------------------------- +// Purpose: Helper class to answer the question "does this attribute exist? and if +// so what is its value?". There are some template shenanigans that happen +// to make things as safe as possible, and to catch errors as early as +// possible. +// +// TActualTypeInMemory: the in-memory type of the data we're going to try +// to read out (ie., "attrib_value_t", "CAttribute_String", +// etc. +// +// TTreatAsThisType: if TActualTypeInMemory is "attrib_value_t", then we're +// dealing with the old attribute system and so maybe we +// want to treat these bits as a float, or a bitmask, or +// who knows! Specifying this type for non-attrib_value_t +// in-memory types is invalid and will fail to compile. +// +// This class isn't intended to be used directly but instead called from +// either FindAttribute() or FindAttribute_UnsafeBitwiseCast(). It's +// global because C++ doesn't support template member functions on a +// template class inside a standalone template function. Weird. +//----------------------------------------------------------------------------- +template < typename TActualTypeInMemory, typename TTreatAsThisType = TActualTypeInMemory > +class CAttributeIterator_GetTypedAttributeValue : public IEconItemAttributeIterator +{ +public: + CAttributeIterator_GetTypedAttributeValue( const CEconItemAttributeDefinition *pAttrDef, TTreatAsThisType *outpValue ) + : m_pAttrDef( pAttrDef ) + , m_outpValue( outpValue ) + , m_bFound( false ) + { + // If this fails, it means that the type TActualTypeInMemory isn't something the attribute + // system is prepared to recognize as a valid attribute storage type. The list of valid types + // are IsValidAttributeValueTypeImpl<> specializations. + // + // If you added a new type and didn't make a specialization for it, this will fail. If you + // *didn't* add a new type, it probably means you're passing a pointer of an incorrect type + // in to FindAttribute(). + COMPILE_TIME_ASSERT( IsValidAttributeValueType<TActualTypeInMemory>::kValue ); + + // The only reason we allow callers to specify a different TTreatAsThisType (versus having + // it always match TActualTypeInMemory) is to deal with the old attribute system, which sometimes + // had attributes have int/float types and sometimes had attribute data values that were 32 + // arbitrary bits. We test here to make sure that we're only using the "treat these bits as + // a different type" behavior code when dealing with attributes using the old storage system + // (attrib_value_t) or when we're trying to get the pointer to buffer contents for a string. + COMPILE_TIME_ASSERT( ((AreTypesIdentical<TActualTypeInMemory, attrib_value_t>::kValue && AreTypesIdentical<TTreatAsThisType, float>::kValue) || + (AreTypesIdentical<TActualTypeInMemory, CAttribute_String>::kValue && AreTypesIdentical<TTreatAsThisType, const char *>::kValue) || + AreTypesIdentical<TActualTypeInMemory, TTreatAsThisType>::kValue) ); + + Assert( m_pAttrDef ); + Assert( outpValue ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) OVERRIDE + { + return OnIterateAttributeValueTyped( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) OVERRIDE + { + return OnIterateAttributeValueTyped( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64 & value ) OVERRIDE + { + return OnIterateAttributeValueTyped( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String & value ) OVERRIDE + { + return OnIterateAttributeValueTyped( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent & value ) OVERRIDE + { + return OnIterateAttributeValueTyped( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria & value ) OVERRIDE + { + return OnIterateAttributeValueTyped( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement & value ) OVERRIDE + { + return OnIterateAttributeValueTyped( pAttrDef, value ); + } + + bool WasFound() const + { + return m_bFound; + } + +private: + // Generic template function for handling any attribute value of any type besides the one that we're looking + // for. For example, if we say "we're looking for attribute 'damage multiplier' and give me back a float", then + // all other attribute value types (strings, structures, etc.) will go through this code, which does nothing + // except look for caller errors. + // + // If you call FindAttribute() and specify the wrong type for an attribute (ie., using the above example, looking + // for "damage multiplier" but feeding in a string), it will get found in this function, which will assert and + // tell you you've got the wrong type. (FindAttribute() in that case will return false because it's impossible + // for us to safely copy the value out.) + template < typename TAnyOtherType > + bool OnIterateAttributeValueTyped( const CEconItemAttributeDefinition *pAttrDef, const TAnyOtherType& value ) + { + COMPILE_TIME_ASSERT( IsValidAttributeValueType<TAnyOtherType>::kValue ); + + // We don't assert because we might be reusing the same iterator between calls. + // Assert( !m_bFound ); + AssertMsg( m_pAttrDef != pAttrDef, "Incorrect type found for attribute during iteration." ); + + return true; + } + + // Overload for attributes of the data type we're looking for. ie., if we say "we're looking for attribute + // 'damage multiplier' and give me back a float", this will be the <float> specialization. We assume that + // we're only going to find at most one attribute per definition and stop looking after we've found the first. + // + // Note that this is just a normal member function, but is *not* a template member function, which would compile + // under VC but otherwise be illegal. + bool OnIterateAttributeValueTyped( const CEconItemAttributeDefinition *pAttrDef, const TActualTypeInMemory& value ) + { + // We don't assert because we might be reusing the same iterator between calls. + // Assert( !m_bFound ); + + if ( m_pAttrDef == pAttrDef ) + { + m_bFound = true; + CopyAttributeValueToOutput( &value, reinterpret_cast<TTreatAsThisType *>( m_outpValue ) ); + } + + return !m_bFound; + } + +private: + static void CopyAttributeValueToOutput( const TActualTypeInMemory *pValue, TTreatAsThisType *out_pValue ) + { + // Even if we are using the old attribute type system, we need to guarantee that the type + // in memory (ie., uint32) and the type we're considering it as (ie., float) are the same size + // because we're going to be doing bitwise casts. + COMPILE_TIME_ASSERT( sizeof( TActualTypeInMemory ) == sizeof( TTreatAsThisType ) ); + + Assert( pValue ); + Assert( out_pValue ); + + *out_pValue = *reinterpret_cast<const TTreatAsThisType *>( pValue ); + } + +private: + const CEconItemAttributeDefinition *m_pAttrDef; + TTreatAsThisType *m_outpValue; + bool m_bFound; +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom code path to support getting the char * result from an +// attribute of type CAttribute_String. +// +// We can't specify the implementation here because we may or may not +// have the definition of CAttribute_String in scope. We also can't +// declare the template specialization here and define it later because +// that would violate the standard, so instead we have the template +// function call a declared-but-not-defined non-template function that +// we can define later. +//----------------------------------------------------------------------------- +void CopyStringAttributeValueToCharPointerOutput( const CAttribute_String *pValue, const char **out_pValue ); + +template < > +inline void CAttributeIterator_GetTypedAttributeValue<CAttribute_String, const char *>::CopyAttributeValueToOutput( const CAttribute_String *pValue, const char **out_pValue ) +{ + CopyStringAttributeValueToCharPointerOutput( pValue, out_pValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Look for the existence/nonexistence of an attribute with the +// definition [pAttrDef]. Can be called on anything with an IterateAttributes() +// member functions (IEconItemInterface, CEconItemDefinition). +//----------------------------------------------------------------------------- +template < typename TAttributeContainerType > +bool FindAttribute( const TAttributeContainerType *pSomethingThatHasAnIterateAttributesFunction, const CEconItemAttributeDefinition *pAttrDef ) +{ +#ifdef CLIENT_DLL + VPROF_BUDGET( "IEconItemInterface::FindAttribute", VPROF_BUDGETGROUP_FINDATTRIBUTE ); +#endif + if ( !pAttrDef ) + return false; + + CAttributeIterator_HasAttribute it( pAttrDef ); + pSomethingThatHasAnIterateAttributesFunction->IterateAttributes( &it ); + return it.WasFound(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < typename TActualTypeInMemory, typename TTreatAsThisType, typename TAttributeContainerType > +bool FindAttribute_UnsafeBitwiseCast( const TAttributeContainerType *pSomethingThatHasAnIterateAttributesFunction, const CEconItemAttributeDefinition *pAttrDef, TTreatAsThisType *out_pValue ) +{ +#ifdef CLIENT_DLL + VPROF_BUDGET( "IEconItemInterface::FindAttribute_UnsafeBitwiseCast", VPROF_BUDGETGROUP_FINDATTRIBUTEUNSAFE ); +#endif + if ( !pAttrDef ) + return false; + + CAttributeIterator_GetTypedAttributeValue<TActualTypeInMemory, TTreatAsThisType> it( pAttrDef, out_pValue ); + pSomethingThatHasAnIterateAttributesFunction->IterateAttributes( &it ); + return it.WasFound(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < typename TAttributeContainerType, typename T > +bool FindAttribute( const TAttributeContainerType *pSomethingThatHasAnIterateAttributesFunction, const CEconItemAttributeDefinition *pAttrDef, T *out_pValue ) +{ + return FindAttribute_UnsafeBitwiseCast<T, T, TAttributeContainerType>( pSomethingThatHasAnIterateAttributesFunction, pAttrDef, out_pValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class IEconItemInterface +{ +public: + virtual ~IEconItemInterface() { } + + // Is an attribute present? We neither know nor care anything about the attribute + // value stored. + bool FindAttribute( const CEconItemAttributeDefinition *pAttrDef ) const + { + return ::FindAttribute( this, pAttrDef ); + } + + // If an attribute is present, it will copy the value into out_pValue and return true. + // If the attribute is not present, it will return false and not touch the value in + // out_pValue. If a T is passed in that is not a type the attribute system understands, + // this function will fail to compile. + template < typename T > + bool FindAttribute( const CEconItemAttributeDefinition *pAttrDef, T *out_pValue ) const + { + return ::FindAttribute( this, pAttrDef, out_pValue ); + } + + // Helpers to look for specific attribute values + virtual CEconItemPaintKitDefinition *GetCustomPainkKitDefinition( void ) const { return GetItemDefinition() ? GetItemDefinition()->GetCustomPainkKitDefinition() : NULL; } + virtual bool GetCustomPaintKitWear( float &flWear ) const; + + // IEconItemInterface common implementation. + virtual bool IsTradable() const; + virtual int GetUntradabilityFlags() const; + virtual bool IsCommodity() const; + virtual bool IsUsableInCrafting() const; + virtual bool IsMarketable() const; // can this item be listed on the Marketplace? + + bool IsTemporaryItem() const; // returns whether this item is a temporary instance of an item that is not by nature temporary (ie., a preview item, an item with an attribute expiration timer) + RTime32 GetExpirationDate() const; // will return RTime32( 0 ) if this item will not expire, otherwise the time that it will auto-delete itself; this looks at both static and dynamic ways of expiring timers + + // IEconItemInterface interface. + virtual const GameItemDefinition_t *GetItemDefinition() const = 0; + + virtual itemid_t GetID() const = 0; // intentionally not called GetItemID to avoid stomping non-virtual GetItemID() on CEconItem + virtual uint32 GetAccountID() const = 0; + virtual int32 GetQuality() const = 0; + virtual style_index_t GetStyle() const = 0; + virtual uint8 GetFlags() const = 0; + virtual eEconItemOrigin GetOrigin() const = 0; + virtual int GetQuantity() const = 0; + virtual uint32 GetItemLevel() const = 0; + virtual bool GetInUse() const = 0; // is this item in use somewhere in the backend? (ie., cross-game trading) + + virtual const char *GetCustomName() const = 0; // get a user-generated name, if present, otherwise NULL; return value is UTF8 + virtual const char *GetCustomDesc() const = 0; // get a user-generated flavor text, if present, otherwise NULL; return value is UTF8 + + // IEconItemInterface attribute iteration interface. This is not meant to be used for + // attribute lookup! This is meant for anything that requires iterating over the full + // attribute list. + virtual void IterateAttributes( IEconItemAttributeIterator *pIterator ) const = 0; + + // Fetch values from the definition + const char *GetDefinitionString( const char *pszKeyName, const char *pszDefaultValue = "" ) const; + KeyValues *GetDefinitionKey( const char *pszKeyName ) const; + + RTime32 GetTradableAfterDateTime() const; + + virtual item_definition_index_t GetItemDefIndex() const { return GetItemDefinition() ? GetItemDefinition()->GetDefinitionIndex() : INVALID_ITEM_DEF_INDEX; } + + virtual IMaterial* GetMaterialOverride( int iTeam ) = 0; + +protected: + bool IsPermanentlyUntradable() const; + bool IsTemporarilyUntradable() const; +}; + +//----------------------------------------------------------------------------- +// Purpose: Classes that want default behavior for GetMaterialOverride, which +// currently derive from IEconItemInterface can instead derive from +// CMaterialOverrideContainer< IEconItemInterface > and have the details +// of material overrides hidden from them. +//----------------------------------------------------------------------------- +template <typename TBaseClass> +class CMaterialOverrideContainer : public TBaseClass +{ +public: + virtual IMaterial* GetMaterialOverride( int iTeam ) OVERRIDE + { + #ifdef CLIENT_DLL + Assert( iTeam >= 0 && iTeam < ARRAYSIZE( m_materialOverrides ) ); + + if ( m_materialOverrides[ iTeam ].IsValid() ) + return m_materialOverrides[ iTeam ]; + + if ( !this->GetItemDefinition() ) + return NULL; + + const char* pName = this->GetItemDefinition()->GetMaterialOverride( iTeam ); + if ( pName == NULL ) + return NULL; + + m_materialOverrides[ iTeam ].Init( pName, TEXTURE_GROUP_CLIENT_EFFECTS ); + return m_materialOverrides[ iTeam ]; + #else + return NULL; + #endif + } + +protected: + void ResetMaterialOverrides() + { + #ifdef CLIENT_DLL + for ( int i = 0; i < TF_TEAM_COUNT; ++i ) + m_materialOverrides[ i ].Shutdown(); + #endif + } + +private: +#ifdef CLIENT_DLL + CMaterialReference m_materialOverrides[ TF_TEAM_COUNT ]; +#endif +}; + +#endif // ECONITEMINTERFACE_H diff --git a/game/shared/econ/econ_item_inventory.cpp b/game/shared/econ/econ_item_inventory.cpp new file mode 100644 index 0000000..77082ec --- /dev/null +++ b/game/shared/econ/econ_item_inventory.cpp @@ -0,0 +1,2340 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "econ_item_inventory.h" +#include "vgui/ILocalize.h" +#include "tier3/tier3.h" +#include "econ_item_system.h" +#include "econ_item.h" +#include "econ_gcmessages.h" +#include "shareddefs.h" +#include "filesystem.h" +#include "econ_item_description.h" // only for CSteamAccountIDAttributeCollector + +#ifdef CLIENT_DLL +#include <igameevents.h> +#include "econ_game_account_client.h" +#include "ienginevgui.h" +#include "econ_ui.h" +#include "item_pickup_panel.h" +#include "econ/econ_item_preset.h" +#include "econ/confirm_dialog.h" +#include "tf_xp_source.h" +#include "tf_notification.h" +#else +#include "props_shared.h" +#include "basemultiplayerplayer.h" +#endif + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) +#include "tf_gcmessages.h" +#include "tf_duel_summary.h" +#include "econ_contribution.h" +#include "tf_player_info.h" +#include "econ/econ_claimcode.h" +#include "tf_wardata.h" +#include "tf_ladder_data.h" +#include "tf_rating_data.h" +#endif + +#if defined(TF_DLL) && defined(GAME_DLL) +#include "tf_gc_api.h" +#include "econ/econ_game_account_server.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace GCSDK; + +#ifdef _DEBUG +ConVar item_inventory_debug( "item_inventory_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); +#endif + +#ifdef USE_DYNAMIC_ASSET_LOADING +//extern ConVar item_dynamicload; +#endif + +#define ITEM_CLIENTACK_FILE "item_clientacks.txt" + +#ifdef _DEBUG +#ifdef CLIENT_DLL +ConVar item_debug_clientacks( "item_debug_clientacks", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); +#endif +#endif // _DEBUG + +// Result codes strings for GC results. +const char* GCResultString[8] = +{ + "k_EGCMsgResponseOK", // Request succeeded + "k_EGCMsgResponseDenied", // Request denied + "k_EGCMsgResponseServerError", // Request failed due to a temporary server error + "k_EGCMsgResponseTimeout", // Request timed out + "k_EGCMsgResponseInvalid", // Request was corrupt + "k_EGCMsgResponseNoMatch", // No item definition matched the request + "k_EGCMsgResponseUnknownError", // Request failed with an unknown error + "k_EGCMsgResponseNotLoggedOn", // Client not logged on to steam +}; + +CBasePlayer *GetPlayerBySteamID( const CSteamID &steamID ) +{ + CSteamID steamIDPlayer; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer == NULL ) + continue; + + if ( pPlayer->GetSteamID( &steamIDPlayer ) == false ) + continue; + + if ( steamIDPlayer == steamID ) + return pPlayer; + } + return NULL; +} + +// Inventory Less function. +// Used to sort the inventory items into their positions. +bool CInventoryListLess::Less( const CEconItemView &src1, const CEconItemView &src2, void *pCtx ) +{ + int iPos1 = src1.GetInventoryPosition(); + int iPos2 = src2.GetInventoryPosition(); + + // Context can be specified to point to a func that extracts the position from the backend position. + // Necessary if your inventory packs a bunch of info into the position instead of using it just as a position. + if ( pCtx ) + { + CPlayerInventory *pInv = (CPlayerInventory*)pCtx; + iPos1 = pInv->ExtractInventorySortPosition( iPos1 ); + iPos2 = pInv->ExtractInventorySortPosition( iPos2 ); + } + + if ( iPos1 < iPos2 ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CInventoryManager::CInventoryManager( void ) +#ifdef CLIENT_DLL + : m_mapPersonaNamesCache( DefLessFunc( uint32 ) ) + , m_sPersonaStateChangedCallback( this, &CInventoryManager::OnPersonaStateChanged ) + , m_personaNameRequests( DefLessFunc( uint64 ) ) +#endif +{ +#ifdef CLIENT_DLL + m_pkvItemClientAckFile = NULL; + m_bClientAckDirty = false; + m_iPredictedDiscards = 0; + m_flNextLoadPresetChange = 0.0f; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::SteamRequestInventory( CPlayerInventory *pInventory, CSteamID pSteamID, IInventoryUpdateListener *pListener ) +{ + // SteamID must be valid + if ( !pSteamID.IsValid() || !pSteamID.BIndividualAccount() ) + { + if ( !HushAsserts() ) + { + Assert( pSteamID.IsValid() ); + Assert( pSteamID.BIndividualAccount() ); + } + return; + } + + // If we haven't seen this inventory before, register it + bool bFound = false; + for ( int i = 0; i < m_pInventories.Count(); i++ ) + { + if ( m_pInventories[i].pInventory == pInventory ) + { + bFound = true; + break; + } + } + if ( !bFound ) + { + int iIdx = m_pInventories.AddToTail(); + m_pInventories[iIdx].pInventory = pInventory; + m_pInventories[iIdx].pListener = pListener; + } + + // Add the request to our list of pending requests + int iIdx = m_hPendingInventoryRequests.AddToTail(); + m_hPendingInventoryRequests[iIdx].pID = pSteamID; + m_hPendingInventoryRequests[iIdx].pInventory = pInventory; + + pInventory->RequestInventory( pSteamID ); + + if( pListener ) + { + pInventory->AddListener( pListener ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when a gameserver connects to steam. +//----------------------------------------------------------------------------- +void CInventoryManager::GameServerSteamAPIActivated() +{ +#if defined(TF_DLL) && defined(GAME_DLL) + GameCoordinator_NotifyGameState(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerInventory *CInventoryManager::GetInventoryForAccount( uint32 iAccountID ) +{ + FOR_EACH_VEC( m_pInventories, i ) + { + if ( m_pInventories[i].pInventory->GetOwner().GetAccountID() == iAccountID ) + return m_pInventories[i].pInventory; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::DeregisterInventory( CPlayerInventory *pInventory ) +{ + int iCount = m_pInventories.Count(); + for ( int i = iCount-1; i >= 0; i-- ) + { + if ( m_pInventories[i].pInventory == pInventory ) + { + m_pInventories.Remove(i); + } + } +} + + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInventoryManager::IsPresetIndexValid( equipped_preset_t unPreset ) +{ + const bool bResult = GetItemSchema()->IsValidPreset( unPreset ); + AssertMsg( bResult, "Invalid preset index!" ); + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInventoryManager::LoadPreset( equipped_class_t unClass, equipped_preset_t unPreset ) +{ + if ( !IsValidPlayerClass( unClass ) ) + return false; + + if ( !IsPresetIndexValid( unPreset ) ) + return false; + + if ( !GetLocalInventory()->GetSOC() ) + return false; + + if ( m_flNextLoadPresetChange > gpGlobals->realtime ) + { + Msg( "Loadout change denied. Changing presets too quickly.\n" ); + return false; + } + + m_flNextLoadPresetChange = gpGlobals->realtime + 0.5f; + + GCSDK::CProtoBufMsg<CMsgSelectPresetForClass> msg( k_EMsgGCPresets_SelectPresetForClass ); + msg.Body().set_class_id( unClass ); + msg.Body().set_preset_id( unPreset ); + GCClientSystem()->BSendMessage( msg ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::UpdateLocalInventory( void ) +{ + if ( steamapicontext->SteamUser() && GetLocalInventory() ) + { + CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); + if ( steamID.IsValid() ) // make sure we're logged in and we know who we are + { + SteamRequestInventory( GetLocalInventory(), steamID ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::OnPersonaStateChanged( PersonaStateChange_t *info ) +{ + if ( ( info->m_nChangeFlags & k_EPersonaChangeName ) != 0 ) + m_personaNameRequests.InsertOrReplace( info->m_ulSteamID, true ); +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInventoryManager::Init( void ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::PostInit( void ) +{ + // Initialize the item system. + ItemSystem()->Init(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::PreInitGC() +{ + REG_SHARED_OBJECT_SUBCLASS( CEconItem ); + +#if defined (CLIENT_DLL) + REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountClient ); + REG_SHARED_OBJECT_SUBCLASS( CEconItemPerClassPresetData ); + REG_SHARED_OBJECT_SUBCLASS( CSOTFMatchResultPlayerInfo ); + REG_SHARED_OBJECT_SUBCLASS( CXPSource ); + REG_SHARED_OBJECT_SUBCLASS( CTFNotification ); +#endif + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) + + REG_SHARED_OBJECT_SUBCLASS( CWarData ); + REG_SHARED_OBJECT_SUBCLASS( CTFDuelSummary ); + REG_SHARED_OBJECT_SUBCLASS( CTFMapContribution ); + REG_SHARED_OBJECT_SUBCLASS( CTFPlayerInfo ); + REG_SHARED_OBJECT_SUBCLASS( CEconClaimCode ); + REG_SHARED_OBJECT_SUBCLASS( CSOTFLadderData ); +#endif + +#ifdef TF_DLL + REG_SHARED_OBJECT_SUBCLASS( CEconGameAccountForGameServers ); +#endif // TF_DLL +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::PostInitGC() +{ +#ifdef CLIENT_DLL + // The client immediately loads the local player's inventory + UpdateLocalInventory(); +#endif +} + + +//----------------------------------------------------------------------------- +void CInventoryManager::Shutdown() +{ + int nInventoryCount = m_pInventories.Count(); + for ( int iInventory = 0; iInventory < nInventoryCount; ++iInventory ) + { + CPlayerInventory *pInventory = m_pInventories[iInventory].pInventory; + if ( pInventory ) + { + pInventory->Clear(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::LevelInitPreEntity( void ) +{ + // Throw out any testitem definitions + for ( int i = 0; i < TI_TYPE_COUNT; i++ ) + { + int iNewDef = TESTITEM_DEFINITIONS_BEGIN_AT + i; + ItemSystem()->GetItemSchema()->ItemTesting_DiscardTestDefinition( iNewDef ); + } + + // Precache all item models we've got +#ifdef GAME_DLL + CUtlVector<const char *> vecPrecacheModelStrings; +#endif // GAME_DLL + const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetItemDefinitionMap(); + FOR_EACH_MAP_FAST( mapItemDefs, i ) + { + CEconItemDefinition *pData = mapItemDefs[i]; + + pData->SetHasBeenLoaded( true ); + +#ifdef GAME_DLL + bool bDynamicLoad = false; +#ifdef USE_DYNAMIC_ASSET_LOADING + bDynamicLoad = true;//item_dynamicload.GetBool(); +#endif // USE_DYNAMIC_ASSET_LOADING + pData->GeneratePrecacheModelStrings( bDynamicLoad, &vecPrecacheModelStrings ); + + // Precache the models and the gibs for everything the definition requested. + FOR_EACH_VEC( vecPrecacheModelStrings, i ) + { + // Ignore any objects which requested an empty precache string for whatever reason. + if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] ) + { + int iModelIndex = CBaseEntity::PrecacheModel( vecPrecacheModelStrings[i] ); + PrecacheGibsForModel( iModelIndex ); + } + } + + vecPrecacheModelStrings.RemoveAll(); + + pData->GeneratePrecacheSoundStrings( bDynamicLoad, &vecPrecacheModelStrings ); + + // Precache the sounds for everything + FOR_EACH_VEC( vecPrecacheModelStrings, i ) + { + // Ignore any objects which requested an empty precache string for whatever reason. + if ( vecPrecacheModelStrings[i] && vecPrecacheModelStrings[i][0] ) + { + CBaseEntity::PrecacheScriptSound( vecPrecacheModelStrings[i] ); + } + } + + vecPrecacheModelStrings.RemoveAll(); +#endif + } + + // We reset the cached attribute class strings, since it's invalidated by level changes + ItemSystem()->ResetAttribStringCache(); + +#ifdef GAME_DLL + ItemSystem()->ReloadWhitelist(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::LevelShutdownPostEntity( void ) +{ + // We reset the cached attribute class strings, since it's invalidated by level changes + ItemSystem()->ResetAttribStringCache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Lets the client know that we're now connected to the GC +//----------------------------------------------------------------------------- +#ifdef CLIENT_DLL +void CInventoryManager::SendGCConnectedEvent( void ) +{ + IGameEvent *event = gameeventmanager->CreateEvent( "gc_connected" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } +} +#endif + + +#if !defined(NO_STEAM) +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the dev "new item" response +//----------------------------------------------------------------------------- +class CGCDev_NewItemRequestResponse : public GCSDK::CGCClientJob +{ +public: + CGCDev_NewItemRequestResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + if ( msg.Body().m_eResponse == k_EGCMsgResponseOK ) + { + Msg("Received new item acknowledgement: %s\n", GCResultString[msg.Body().m_eResponse] ); + } + else + { + Warning("Failed to generate new item: %s\n", GCResultString[msg.Body().m_eResponse] ); + } + return true; + } + +}; +GC_REG_JOB( GCSDK::CGCClient, CGCDev_NewItemRequestResponse, "CGCDev_NewItemRequestResponse", k_EMsgGCDev_NewItemRequestResponse, GCSDK::k_EServerTypeGCClient ); + +#endif // NO_STEAM + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::RemovePendingRequest( CSteamID *pSteamID ) +{ +#ifdef CLIENT_DLL + // Only the client, all requests are for the local player. Clear them all. + m_hPendingInventoryRequests.Purge(); + return; +#endif + + // On the server, remove all requests for the specified steam id + int iCount = m_hPendingInventoryRequests.Count(); + for ( int i = iCount-1; i >= 0; i-- ) + { + if ( m_hPendingInventoryRequests[i].pID == *pSteamID ) + { + m_hPendingInventoryRequests.Remove(i); + } + } +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::DropItem( itemid_t iItemID ) +{ + static CSchemaAttributeDefHandle pAttrDef_NoDelete( "cannot delete" ); + + // Double check that this item can be delete + CEconItemView *pItem = GetLocalInventory()->GetInventoryItemByItemID( iItemID ); + if ( !pItem || !pAttrDef_NoDelete || pItem->FindAttribute( pAttrDef_NoDelete ) ) + { + return; + } + + GCSDK::CGCMsg<MsgGCDelete_t> msg( k_EMsgGCDelete ); + msg.Body().m_unItemID = iItemID; + GCClientSystem()->BSendMessage( msg ); + + // Keep track of how many items we've discarded, but haven't received responses for. + m_iPredictedDiscards++; +} + +//----------------------------------------------------------------------------- +// Purpose: Delete any items we can't find static data for. This can happen when we're testing +// internally, and then remove an item. Shouldn't ever happen in the wild. +//----------------------------------------------------------------------------- +int CInventoryManager::DeleteUnknowns( CPlayerInventory *pInventory ) +{ + // We need to manually walk the main inventory's SOC, because unknown items won't be in the inventory + GCSDK::CGCClientSharedObjectCache *pSOC = pInventory->GetSOC(); + if ( pSOC ) + { + int iBadItems = 0; + CGCClientSharedObjectTypeCache *pTypeCache = pSOC->FindTypeCache( CEconItem::k_nTypeID ); + if( pTypeCache ) + { + for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ ) + { + CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem ); + if ( pItem ) + { + CEconItemDefinition *pData = ItemSystem()->GetStaticDataForItemByDefIndex( pItem->GetDefinitionIndex() ); + if ( !pData ) + { + DropItem( pItem->GetItemID() ); + iBadItems++; + } + } + } + } + return iBadItems; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Tries to move the specified item into the player's backpack. +// FAILS if the backpack is full. Returns false in that case. +//----------------------------------------------------------------------------- +bool CInventoryManager::SetItemBackpackPosition( CEconItemView *pItem, uint32 iPosition, bool bForceUnequip, bool bAllowOverflow ) +{ + CPlayerInventory *pInventory = GetLocalInventory(); + if ( !pInventory ) + return false; + + const int iMaxItems = pInventory->GetMaxItemCount(); + if ( !iPosition ) + { + // Build a list of empty slots. We track extra slots beyond the backpack for overflow. + CUtlVector< bool > bFilledSlots; + bFilledSlots.SetSize( iMaxItems * 2 ); + for ( int i = 0; i < bFilledSlots.Count(); ++i ) + { + bFilledSlots[i] = false; + } + for ( int i = 0; i < pInventory->GetItemCount(); i++ ) + { + CEconItemView *pTmpItem = pInventory->GetItem(i); + // Ignore the item we're moving. + if ( pTmpItem == pItem ) + continue; + + int iBackpackPos = GetBackpackPositionFromBackend( pTmpItem->GetInventoryPosition() ); + if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() ) + { + bFilledSlots[iBackpackPos] = true; + } + } + + // Add predicted filled slots + for ( int i = 0; i < m_PredictedFilledSlots.Count(); i++ ) + { + int iBackpackPos = m_PredictedFilledSlots[i]; + if ( iBackpackPos >= 0 && iBackpackPos < bFilledSlots.Count() ) + { + bFilledSlots[iBackpackPos] = true; + } + } + + // Now find an empty slot + for ( int i = 1; i < bFilledSlots.Count(); i++ ) + { + if ( !bFilledSlots[i] ) + { + iPosition = i; + break; + } + } + + if ( !iPosition ) + return false; + } + + if ( !bAllowOverflow && iPosition > (uint32)iMaxItems ) + return false; + + //Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iPosition ); + + uint32 iBackendPosition = bForceUnequip ? 0 : pItem->GetInventoryPosition(); + SetBackpackPosition( &iBackendPosition, iPosition ); + UpdateInventoryPosition( pInventory, pItem->GetItemID(), iBackendPosition ); + + m_PredictedFilledSlots.AddToTail( iPosition ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::MoveItemToBackpackPosition( CEconItemView *pItem, int iBackpackPosition ) +{ + CEconItemView *pOldItem = GetItemByBackpackPosition( iBackpackPosition ); + if ( pOldItem ) + { + // Move the item in the new spot to our current spot + SetItemBackpackPosition( pOldItem, GetBackpackPositionFromBackend(pItem->GetInventoryPosition()) ); + + //Warning("Moved OLD item %llu to backpack slot: %d\n", pOldItem->GetItemID(), GetBackpackPositionFromBackend(iBackendPosition) ); + } + + // Move the item to the new spot + SetItemBackpackPosition( pItem, iBackpackPosition ); + + //Warning("Moved item %llu to backpack slot: %d\n", pItem->GetItemID(), iBackpackPosition ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CWaitForBackpackSortFinishDialog : public CGenericWaitingDialog +{ +public: + CWaitForBackpackSortFinishDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent ) + { + } + +protected: + virtual void OnTimeout() + { + InventoryManager()->SortBackpackFinished(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::SortBackpackBy( uint32 iSortType ) +{ + GCSDK::CProtoBufMsg<CMsgSortItems> msg( k_EMsgGCSortItems ); + msg.Body().set_sort_type( iSortType ); + GCClientSystem()->BSendMessage( msg ); + + ShowWaitingDialog( new CWaitForBackpackSortFinishDialog( NULL ), "#BackpackSortExplanation_Title", true, false, 3.0f ); + m_bInBackpackSort = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::SortBackpackFinished( void ) +{ + m_bInBackpackSort = false; + + GetLocalInventory()->SendInventoryUpdateEvent(); +} + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the sort finished message +//----------------------------------------------------------------------------- +class CGBackpackSortFinished : public GCSDK::CGCClientJob +{ +public: + CGBackpackSortFinished( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CloseWaitingDialog(); + InventoryManager()->SortBackpackFinished(); + return true; + } + +}; +GC_REG_JOB( GCSDK::CGCClient, CGBackpackSortFinished, "CGBackpackSortFinished", k_EMsgGCBackpackSortFinished, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::UpdateInventoryPosition( CPlayerInventory *pInventory, uint64 ulItemID, uint32 unNewInventoryPos ) +{ + if ( !pInventory->GetInventoryItemByItemID( ulItemID ) ) + { + Warning("Attempt to update inventory position failure: %s.\n", "could not find matching item ID"); + return; + } + if ( !pInventory->GetSOCDataForItem( ulItemID ) ) + { + Warning("Attempt to update inventory position failure: %s\n", "could not find SOC data for item"); + return; + } + + // In the incredibly rare case where the GC crashed while sorting our backpack, we won't have gotten + // a k_EMsgGCBackpackSortFinished message. Assume that if we're requesting a manual move of an item, we're not sorting anymore. + m_bInBackpackSort = false; + + // TF has multiple ways of using the inventory position bits. For all inventory positions moving forward, assume + // they're in the new format. +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) + if ( unNewInventoryPos != 0 ) + { + unNewInventoryPos |= kBackendPosition_NewFormat; + } +#endif // defined(TF_CLIENT_DLL) || defined(TF_DLL) + + // Queue a message to be sent to the GC + CMsgSetItemPositions_ItemPosition *pMsg = m_msgPendingSetItemPositions.add_item_positions(); + pMsg->set_item_id( ulItemID ); + pMsg->set_position( unNewInventoryPos ); +} + +void CInventoryManager::Update( float frametime ) +{ + + // Check if we have any pending item position changes that we need to flush out + if ( m_msgPendingSetItemPositions.item_positions_size() > 0 ) + { + // !KLUDGE! It would be nice if we could just send this in one line instead of making a copy + CProtoBufMsg<CMsgSetItemPositions> msg( k_EMsgGCSetItemPositions ); + msg.Body() = m_msgPendingSetItemPositions; + GCClientSystem()->BSendMessage( msg ); + + m_msgPendingSetItemPositions.Clear(); + } + + // Check if we have any pending account lookups to batch up + if ( m_msgPendingLookupAccountNames.accountids_size() > 0 ) + { + // !KLUDGE! It would be nice if we could just send this in one line instead of making a copy + CProtoBufMsg< CMsgLookupMultipleAccountNames > msg( k_EMsgGCLookupMultipleAccountNames ); + msg.Body() = m_msgPendingLookupAccountNames; + GCClientSystem()->BSendMessage( msg ); + + m_msgPendingLookupAccountNames.Clear(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::UpdateInventoryEquippedState( CPlayerInventory *pInventory, uint64 ulItemID, equipped_class_t unClass, equipped_slot_t unSlot ) +{ + // passing in INVALID_ITEM_ID means "unequip from this slot" + if ( ulItemID != INVALID_ITEM_ID ) + { + if ( !pInventory->GetInventoryItemByItemID( ulItemID ) ) + { + //Warning("Attempt to update equipped state failure: %s.\n", "could not find matching item ID"); + return; + } + if ( !pInventory->GetSOCDataForItem( ulItemID ) ) + { + //Warning("Attempt to update equipped state failure: %s\n", "could not find SOC data for item"); + return; + } + } + + CProtoBufMsg<CMsgAdjustItemEquippedState> msg( k_EMsgGCAdjustItemEquippedState ); + msg.Body().set_item_id( ulItemID ); + msg.Body().set_new_class( unClass ); + msg.Body().set_new_slot( unSlot ); + GCClientSystem()->BSendMessage( msg ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInventoryManager::ShowItemsPickedUp( bool bForce, bool bReturnToGame, bool bNoPanel ) +{ + CPlayerInventory *pLocalInv = GetLocalInventory(); + if ( !pLocalInv ) + return false; + + // Don't bring it up if we're already browsing something in the gameUI + vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL ); + if ( !bForce && vgui::ipanel()->IsVisible( gameuiPanel ) ) + return false; + + CUtlVector<CEconItemView*> aItemsFound; + + // Go through the root inventory and find any items that are in the "found" position + int iCount = pLocalInv->GetItemCount(); + for ( int i = 0; i < iCount; i++ ) + { + CEconItemView *pTmp = pLocalInv->GetItem(i); + if ( !pTmp ) + continue; + + if ( pTmp->GetStaticData()->IsHidden() ) + continue; + + uint32 iPosition = pTmp->GetInventoryPosition(); + if ( IsUnacknowledged(iPosition) == false ) + continue; + if ( GetBackpackPositionFromBackend(iPosition) != 0 ) + continue; + + // Now make sure we haven't got a clientside saved ack for this item. + // This makes sure we don't show multiple pickups for items that we've found, + // but haven't been able to move out of unack'd position due to the GC being unavailable. + if ( HasBeenAckedByClient( pTmp ) ) + continue; + + aItemsFound.AddToTail( pTmp ); + } + + if ( !aItemsFound.Count() ) + return CheckForRoomAndForceDiscard(); + + // We're not forcing the player to make room yet. Just show the pickup panel. + CItemPickupPanel *pItemPanel = bNoPanel ? NULL : EconUI()->OpenItemPickupPanel(); + + if ( pItemPanel ) + { + pItemPanel->SetReturnToGame( bReturnToGame ); + } + + for ( int i = 0; i < aItemsFound.Count(); i++ ) + { + if ( pItemPanel ) + { + pItemPanel->AddItem( aItemsFound[i] ); + } + else + { + AcknowledgeItem( aItemsFound[i] ); + } + } + + if ( pItemPanel ) + { + pItemPanel->MoveToFront(); + } + else + { + SaveAckFile(); + } + + aItemsFound.Purge(); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInventoryManager::CheckForRoomAndForceDiscard( void ) +{ + CPlayerInventory *pLocalInv = GetLocalInventory(); + if ( !pLocalInv ) + return false; + + // Go through the inventory and attempt to move any items outside the backpack into valid positions. + // Remember the first item that we failed to move, so we can force a discard later. + CEconItemView *pItem = NULL; + const int iMaxItems = pLocalInv->GetMaxItemCount(); + int iCount = pLocalInv->GetItemCount(); + for ( int i = 0; i < iCount; i++ ) + { + CEconItemView *pTmp = pLocalInv->GetItem(i); + if ( !pTmp ) + continue; + + if ( pTmp->GetStaticData()->IsHidden() ) + continue; + + uint32 iPosition = pTmp->GetInventoryPosition(); + if ( IsUnacknowledged(iPosition) || GetBackpackPositionFromBackend(iPosition) > iMaxItems ) + { + if ( !SetItemBackpackPosition( pTmp, 0, false, false ) ) + { + pItem = pTmp; + break; + } + } + } + + // If we're not over the limit, we're done. + if ( ( iCount - m_iPredictedDiscards ) <= iMaxItems ) + return false; + + if ( !pItem ) + return false; + + // We're forcing the player to make room for items he's found. Bring up that panel with the first item over the limit. + CItemDiscardPanel *pDiscardPanel = EconUI()->OpenItemDiscardPanel(); + pDiscardPanel->SetItem( pItem ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Client Acknowledges an item and moves it in to the backpack +//----------------------------------------------------------------------------- +void CInventoryManager::AcknowledgeItem ( CEconItemView *pItem, bool bMoveToBackpack /* = true */ ) +{ + SetAckedByClient( pItem ); + + int iMethod = GetUnacknowledgedReason( pItem->GetInventoryPosition() ) - 1; + if ( iMethod >= ARRAYSIZE( g_pszItemPickupMethodStringsUnloc ) || iMethod < 0 ) + iMethod = 0; + EconUI()->Gamestats_ItemTransaction( IE_ITEM_RECEIVED, pItem, g_pszItemPickupMethodStringsUnloc[iMethod] ); + + // Then move it to the first empty backpack position + if ( bMoveToBackpack ) + { + SetItemBackpackPosition( pItem, 0, false, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemView *CInventoryManager::GetItemByBackpackPosition( int iBackpackPosition ) +{ + CPlayerInventory *pInventory = GetLocalInventory(); + if ( !pInventory ) + return NULL; + + // Backpack positions start from 1 + Assert( iBackpackPosition > 0 && iBackpackPosition <= pInventory->GetMaxItemCount() ); + for ( int i = 0; i < pInventory->GetItemCount(); i++ ) + { + CEconItemView *pItem = pInventory->GetItem(i); + if ( GetBackpackPositionFromBackend( pItem->GetInventoryPosition() ) == iBackpackPosition ) + return pItem; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInventoryManager::HasBeenAckedByClient( CEconItemView *pItem ) +{ + return ( GetAckKeyForItem( pItem ) != NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::SetAckedByClient( CEconItemView *pItem ) +{ + VerifyAckFileLoaded(); + + static char szTmp[128]; + Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() ); + m_pkvItemClientAckFile->SetInt( szTmp, 1 ); + + m_bClientAckDirty = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::SetAckedByGC( CEconItemView *pItem, bool bSave ) +{ + KeyValues *pkvItem = GetAckKeyForItem( pItem ); + if ( pkvItem ) + { + m_pkvItemClientAckFile->RemoveSubKey( pkvItem ); + pkvItem->deleteThis(); + + m_bClientAckDirty = true; + + if ( bSave ) + { + SaveAckFile(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *CInventoryManager::GetAckKeyForItem( CEconItemView *pItem ) +{ + VerifyAckFileLoaded(); + + static char szTmp[128]; + Q_snprintf( szTmp, sizeof(szTmp), "%llu", pItem->GetItemID() ); + return m_pkvItemClientAckFile->FindKey( szTmp ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::VerifyAckFileLoaded( void ) +{ + if ( m_pkvItemClientAckFile ) + return; + + m_pkvItemClientAckFile = new KeyValues( ITEM_CLIENTACK_FILE ); + + ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface( + SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL; + + if ( pRemoteStorage ) + { + if ( pRemoteStorage->FileExists(ITEM_CLIENTACK_FILE) ) + { + int32 nFileSize = pRemoteStorage->GetFileSize( ITEM_CLIENTACK_FILE ); + + if ( nFileSize > 0 ) + { + CUtlBuffer buf( 0, nFileSize ); + if ( pRemoteStorage->FileRead( ITEM_CLIENTACK_FILE, buf.Base(), nFileSize ) == nFileSize ) + { + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSize ); + m_pkvItemClientAckFile->ReadAsBinary( buf ); + +#ifdef _DEBUG + if ( item_debug_clientacks.GetBool() ) + { + m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_readack.txt", "MOD" ); + } +#endif + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Clean up any item references that we no longer have items for. +// This ensures that if we delete an item on the backend, we remove it from the ack file. +//----------------------------------------------------------------------------- +void CInventoryManager::CleanAckFile( void ) +{ + CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); + if ( !pInventory ) + return; + + if ( !pInventory->RetrievedInventoryFromSteam() ) + return; + + if ( m_pkvItemClientAckFile ) + { + KeyValues *pKVItem = m_pkvItemClientAckFile->GetFirstSubKey(); + while ( pKVItem != NULL ) + { + itemid_t ulID = (itemid_t)Q_atoi64( pKVItem->GetName() ); + if ( pInventory->GetInventoryItemByItemID(ulID) == NULL ) + { + KeyValues *pTmp = pKVItem->GetNextKey(); + m_pkvItemClientAckFile->RemoveSubKey( pKVItem ); + pKVItem->deleteThis(); + + m_bClientAckDirty = true; + + pKVItem = pTmp; + } + else + { + pKVItem = pKVItem->GetNextKey(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInventoryManager::SaveAckFile( void ) +{ + if ( !m_bClientAckDirty ) + return; + m_bClientAckDirty = false; + + ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface( + SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL; + + if ( pRemoteStorage ) + { + CUtlBuffer buf; + m_pkvItemClientAckFile->WriteAsBinary( buf ); + pRemoteStorage->FileWrite( ITEM_CLIENTACK_FILE, buf.Base(), buf.TellPut() ); + +#ifdef _DEBUG + if ( item_debug_clientacks.GetBool() ) + { + m_pkvItemClientAckFile->SaveToFile( g_pFullFileSystem, "cfg/tmp_saveack.txt", "MOD" ); + } +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: GC sent name of account down +//----------------------------------------------------------------------------- +class CGCLookupAccountNameResponse : public GCSDK::CGCClientJob +{ +public: + CGCLookupAccountNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCLookupAccountNameResponse_t> msg( pNetPacket ); + + CUtlString playerName; + if ( msg.BReadStr( &playerName ) ) + { + InventoryManager()->PersonaName_Store( msg.Body().m_unAccountID, playerName.Get() ); + } + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCLookupAccountNameResponse, "CGCLookupAccountNameResponse", k_EMsgGCLookupAccountNameResponse, GCSDK::k_EServerTypeGCClient ); + +class CGCLookupMultipleAccountsNameResponse : public GCSDK::CGCClientJob +{ +public: + CGCLookupMultipleAccountsNameResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CProtoBufMsg<CMsgLookupMultipleAccountNamesResponse> msg( pNetPacket ); + for ( int i = 0 ; i < msg.Body().accounts_size() ; ++i ) + { + const CMsgLookupMultipleAccountNamesResponse_Account &account = msg.Body().accounts( i ); + InventoryManager()->PersonaName_Store( account.accountid(), account.persona().c_str() ); + } + + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCLookupMultipleAccountsNameResponse, "CGCLookupMultipleAccountsNameResponse", k_EMsgGCLookupMultipleAccountNamesResponse, GCSDK::k_EServerTypeGCClient ); + +void CInventoryManager::PersonaName_Precache( uint32 unAccountID ) +{ + const char *pszName = PersonaName_Get( unAccountID ); + if ( pszName == NULL ) + { + // Queue request name from GC + m_msgPendingLookupAccountNames.add_accountids( unAccountID ); + + // insert empty string so we don't ask again + m_mapPersonaNamesCache.Insert( unAccountID, "" ); + } +} + +const char *CInventoryManager::PersonaName_Get( uint32 unAccountID ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + // First ask Steam if this is one of friends -- if so we can get an up-to-date persona name. + { + const char *pszName = NULL; + + if ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() ) + { + CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); + steamID.SetAccountID( unAccountID ); + uint64 u64AccountId = steamID.ConvertToUint64(); + + // We're covering three states here: + // 1. We've never asked before. We need to queue up a RequestUserInformation. + // 2. We've asked before, and we haven't heard back yet + // 3. We've asked before, we heard back. Don't re-request user information. + auto index = m_personaNameRequests.Find( u64AccountId ); + if ( !m_personaNameRequests.IsValidIndex( index ) ) + { + // This is case 1--we've never asked before. + + // If RequestUserInformation returns false, the information is already available. + // Otherwise, it will arrive later and we need to rebuild the description at that time. + if ( !steamapicontext->SteamFriends()->RequestUserInformation( steamID, true ) ) + { + pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); + Assert( pszName ); // Guaranteed by the steam api + + if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 ) + { + m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName ); + return pszName; + } + } + else + { + // This is case 2, we've asked above. + m_personaNameRequests.Insert( u64AccountId, false ); + } + } + else + { + if ( m_personaNameRequests[ index ] ) + { + // This is case 3. + pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ); + Assert( pszName ); // Guaranteed by the steam api + + if ( Q_strncmp( pszName, "[unknown]", ARRAYSIZE( "[unknown]" ) ) != 0 ) + { + m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszName ); + return pszName; + } + } + } + } + } + + // If that didn't work, ask the server we're playing on if they know this account ID. + CBasePlayer *pPlayer = GetPlayerByAccountID( unAccountID ); + if ( pPlayer ) + { + const char *pszPlayerName = pPlayer->GetPlayerName(); + if ( pszPlayerName ) + { + m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pszPlayerName ); + return pszPlayerName; + } + } + + // If *that* didn't work, look in our cache populated by the GC (or the above paths). This + // might be out of date but it's better than nothing. + int idx = m_mapPersonaNamesCache.Find( unAccountID ); + if ( m_mapPersonaNamesCache.IsValidIndex( idx ) ) + { + return m_mapPersonaNamesCache[idx].Get(); + } + + return "[unknown]"; +} + +void CInventoryManager::PersonaName_Store( uint32 unAccountID, const char *pPersonaName ) +{ + m_mapPersonaNamesCache.InsertOrReplace( unAccountID, pPersonaName ); +} + +#endif // CLIENT_DLL + + +//======================================================================================================================= +// PLAYER INVENTORY +//======================================================================================================================= +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerInventory::CPlayerInventory( void ) +{ + m_bGotItemsFromSteam = false; + m_iPendingRequests = 0; + m_aInventoryItems.Purge(); + m_pSOCache = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPlayerInventory::~CPlayerInventory() +{ + FOR_EACH_VEC( m_vecItemHandles, i ) + { + m_vecItemHandles[ i ]->InventoryIsBeingDeleted(); + } + m_vecItemHandles.Purge(); + + if ( m_iPendingRequests ) + { + InventoryManager()->RemovePendingRequest( &m_OwnerID ); + m_iPendingRequests = 0; + } + + SOClear(); + + InventoryManager()->DeregisterInventory( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::SOClear() +{ + if ( m_OwnerID.IsValid() ) + { + CGCClientSystem *pClientSystem = GCClientSystem(); + Assert ( pClientSystem != NULL ); + if ( pClientSystem != NULL ) + { + CGCClient *pClient = pClientSystem->GetGCClient(); + Assert ( pClient != NULL ); + pClient->RemoveSOCacheListener( m_OwnerID, this ); + } + } + + // Somebody registered as a listener through us, but now our Steam ID + // is changing? This is bad news. + Assert( m_vecListeners.Count() == 0 ); + while ( m_vecListeners.Count() > 0 ) + { + RemoveListener( m_vecListeners[0] ); + } + + // If we were subscribed, we should have gotten our unsubscribe message, + // and that should have cleared the pointer + Assert( m_pSOCache == NULL); + m_pSOCache = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::AddItemHandle( CEconItemViewHandle* pHandle ) +{ + FOR_EACH_VEC( m_vecItemHandles, i ) + { + if ( m_vecItemHandles[ i ] == pHandle ) + { + Assert( !"Item handle already in list to track!" ); + return; + } + } + + m_vecItemHandles.AddToTail( pHandle ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::RemoveItemHandle( CEconItemViewHandle* pHandle ) +{ + FOR_EACH_VEC( m_vecItemHandles, i ) + { + if ( m_vecItemHandles[ i ] == pHandle ) + { + m_vecItemHandles.Remove( i ); + return; + } + } + + Assert( !"Could not find item handle to remove!" ); +} + + +void CPlayerInventory::Clear() +{ + SOClear(); + m_OwnerID = CSteamID(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::RequestInventory( CSteamID pSteamID ) +{ + // Make sure we don't already have somebody else's stuff + // on hand + if ( m_OwnerID != pSteamID ) + SOClear(); + + // Remember whose inventory we're looking at + m_OwnerID = pSteamID; + + // SteamID must be valid + if ( !m_OwnerID.IsValid() || !m_OwnerID.BIndividualAccount() ) + { + Assert( m_OwnerID.IsValid() ); + Assert( m_OwnerID.BIndividualAccount() ); + return; + } + + // If we don't already have an SO cache, then ask the GC for one, + // and start listening to it. We will receive our "subscribed" message + // when the data is valid + GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, this ); +} + +void CPlayerInventory::AddListener( GCSDK::ISharedObjectListener *pListener ) +{ + Assert( m_OwnerID.IsValid() ); + if ( m_vecListeners.Find( pListener ) < 0 ) + { + m_vecListeners.AddToTail( pListener ); + GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerID, pListener ); + } +} + + +void CPlayerInventory::RemoveListener( GCSDK::ISharedObjectListener *pListener ) +{ + if ( m_OwnerID.IsValid() ) + { + m_vecListeners.FindAndFastRemove( pListener ); + GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerID, pListener ); + } + else + { + Assert( m_vecListeners.Count() == 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Helper function to add a new item for a econ item +//----------------------------------------------------------------------------- +bool CPlayerInventory::AddEconItem( CEconItem * pItem, bool bUpdateAckFile, bool bWriteAckFile, bool bCheckForNewItems ) +{ + CEconItemView newItem; + if( !FilloutItemFromEconItem( &newItem, pItem ) ) + { + return false; + } + + int iIdx = m_aInventoryItems.Insert( newItem ); + + DirtyItemHandles(); + + ItemHasBeenUpdated( &m_aInventoryItems[iIdx], bUpdateAckFile, bWriteAckFile ); + +#ifdef CLIENT_DLL + if ( bCheckForNewItems && InventoryManager()->GetLocalInventory() == this ) + { + bool bNotify = IsUnacknowledged( pItem->GetInventoryToken() ); + // ignore Halloween drops + bNotify &= pItem->GetOrigin() != kEconItemOrigin_HalloweenDrop; + // only notify for specific reasons + unacknowledged_item_inventory_positions_t reason = GetUnacknowledgedReason( pItem->GetInventoryToken() ); + switch ( reason ) + { + case UNACK_ITEM_UNKNOWN: + case UNACK_ITEM_DROPPED: + case UNACK_ITEM_SUPPORT: + case UNACK_ITEM_EARNED: + case UNACK_ITEM_REFUNDED: + case UNACK_ITEM_COLLECTION_REWARD: + case UNACK_ITEM_TRADED: + case UNACK_ITEM_GIFTED: + case UNACK_ITEM_QUEST_LOANER: + case UNACK_ITEM_VIRAL_COMPETITIVE_BETA_PASS_SPREAD: + break; + default: + bNotify = false; + break; + } + + if ( bNotify && !pItem->GetItemDefinition()->IsHidden() ) + { + OnHasNewItems(); + } + } +#endif + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Creates a script item and associates it with this econ item +//----------------------------------------------------------------------------- +void CPlayerInventory::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + if( pObject->GetTypeID() != CEconItem::k_nTypeID ) + return; + Assert( steamIDOwner == m_OwnerID ); + if ( steamIDOwner != m_OwnerID ) + return; + + // We shouldn't get these notifications unless we're subscribed, right? + if ( m_pSOCache == NULL) + { + Assert( m_pSOCache ); + return; + } + + // Don't bother unless it's an incremental notification. + // For mass updates, we'll do everything more efficiently in one place + if ( eEvent != GCSDK::eSOCacheEvent_Incremental ) + { + Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed || eEvent == GCSDK::eSOCacheEvent_ListenerAdded ); + return; + } + + CEconItem *pItem = (CEconItem *)pObject; + AddEconItem( pItem, true, true, true ); + SendInventoryUpdateEvent(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates the script item associated with this econ item +//----------------------------------------------------------------------------- +void CPlayerInventory::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) +{ + if( pObject->GetTypeID() != CEconItem::k_nTypeID ) + return; + Assert( steamIDOwner == m_OwnerID ); + if ( steamIDOwner != m_OwnerID ) + return; + + // We shouldn't get these notifications unless we're subscribed, right? + if ( m_pSOCache == NULL) + { + Assert( m_pSOCache ); + return; + } + + // Don't bother unless it's an incremental notification. + // For mass updates, we'll do everything more efficiently in one place + if ( eEvent != GCSDK::eSOCacheEvent_Incremental ) + { + Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed ); + return; + } + + CEconItem *pEconItem = (CEconItem *)pObject; + + bool bChanged = false; + CEconItemView *pScriptItem = GetInventoryItemByItemID( pEconItem->GetItemID() ); + if ( pScriptItem ) + { + if ( FilloutItemFromEconItem( pScriptItem, pEconItem ) ) + { + ItemHasBeenUpdated( pScriptItem, false, false ); + } + + bChanged = true; + } + else + { + // The item isn't in this inventory right now. But it may need to be + // after the update, so try adding it and see if the inventory wants it. + bChanged = AddEconItem( pEconItem, false, false, false ); + } + + if ( bChanged ) + { + ResortInventory(); + + DirtyItemHandles(); + +#ifdef CLIENT_DLL + // Client doesn't update inventory while items are moving in a backpack sort. Does it once at the sort end instead. + if ( !InventoryManager()->IsInBackpackSort() ) +#endif + { + SendInventoryUpdateEvent(); + } +#ifdef _DEBUG + if ( item_inventory_debug.GetBool() ) + { + DumpInventoryToConsole( true ); + } +#endif + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Removes the script item associated with this econ item +//----------------------------------------------------------------------------- +void CPlayerInventory::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) +{ + if( pObject->GetTypeID() != CEconItem::k_nTypeID ) + return; + Assert( steamIDOwner == m_OwnerID ); + if ( steamIDOwner != m_OwnerID ) + return; + + // We shouldn't get these notifications unless we're subscribed, right? + if ( m_pSOCache == NULL) + { + Assert( m_pSOCache ); + return; + } + + // Don't bother unless it's an incremental notification. + // For mass updates, we'll do everything more efficiently in one place + if ( eEvent != GCSDK::eSOCacheEvent_Incremental ) + { + Assert( eEvent == GCSDK::eSOCacheEvent_Subscribed || eEvent == GCSDK::eSOCacheEvent_Resubscribed ); + return; + } + + CEconItem *pEconItem = (CEconItem *)pObject; + RemoveItem( pEconItem->GetItemID() ); + +#ifdef CLIENT_DLL + InventoryManager()->OnItemDeleted( this ); +#endif + + SendInventoryUpdateEvent(); +} + + +//----------------------------------------------------------------------------- +// Purpose: This is our initial notification that this cache has been received +// from the server. +//----------------------------------------------------------------------------- +void CPlayerInventory::SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) +{ + // Make sure we expect notifications about this guy + Assert( steamIDOwner == m_OwnerID ); + if ( steamIDOwner != m_OwnerID ) + return; + + #ifdef _DEBUG + Msg("CPlayerInventory::SOCacheSubscribed\n"); + #endif + + // Clear our old inventory + m_aInventoryItems.Purge(); + + DirtyItemHandles(); + + // Locate the cache that was just subscribed to + m_pSOCache = GCClientSystem()->GetSOCache( m_OwnerID ); + if ( m_pSOCache == NULL ) + { + Assert( m_pSOCache != NULL ); + return; + } + + // add all the items already in the inventory + CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindTypeCache( CEconItem::k_nTypeID ); + if( pTypeCache ) + { + for( uint32 unItem = 0; unItem < pTypeCache->GetCount(); unItem++ ) + { + CEconItem *pItem = (CEconItem *)pTypeCache->GetObject( unItem ); + AddEconItem(pItem, true, false, true ); + } + } + + m_bGotItemsFromSteam = true; + +#ifdef CLIENT_DLL + if ( InventoryManager()->GetLocalInventory() == this ) + { + // Only validate the local player inventory + ValidateInventoryPositions(); + + // tell the entire client that we're 'connected' to the GC now + CInventoryManager::SendGCConnectedEvent(); + } +#endif + + ResortInventory(); + +#ifdef CLIENT_DLL + // Now that we've read all the items in, write out the ack file (only if we're the local inventory) + if ( InventoryManager()->GetLocalInventory() == this ) + { + InventoryManager()->CleanAckFile(); + InventoryManager()->SaveAckFile(); + } +#endif +} + +bool CInventoryManager::IsValidPlayerClass( equipped_class_t unClass ) +{ + const bool bResult = ItemSystem()->GetItemSchema()->IsValidClass( unClass ); + AssertMsg( bResult, "Invalid player class!" ); + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Removes the script item associated with this econ item +//----------------------------------------------------------------------------- +void CPlayerInventory::ValidateInventoryPositions( void ) +{ +#ifdef TF2 + if ( engine->GetAppID() == 520 ) + { + TFInventoryManager()->DeleteUnknowns( this ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::ItemHasBeenUpdated( CEconItemView *pItem, bool bUpdateAckFile, bool bWriteAckFile ) +{ +#ifdef CLIENT_DLL + // Handle the clientside ack file + if ( bUpdateAckFile && !IsUnacknowledged(pItem->GetInventoryPosition()) ) + { + if ( InventoryManager()->GetLocalInventory() == this ) + { + InventoryManager()->SetAckedByGC( pItem, bWriteAckFile ); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) +{ + m_pSOCache = NULL; + m_bGotItemsFromSteam = false; + m_aInventoryItems.Purge(); + + DirtyItemHandles(); +} + + +//----------------------------------------------------------------------------- +// Purpose: On the client this sends the "inventory_updated" event. On the server +// it does nothing. +//----------------------------------------------------------------------------- +void CPlayerInventory::SendInventoryUpdateEvent() +{ +#ifdef CLIENT_DLL + if( InventoryManager()->GetLocalInventory() == this ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "inventory_updated" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Fills out all the fields in the script item based on what's in the +// econ item +//----------------------------------------------------------------------------- +bool CPlayerInventory::FilloutItemFromEconItem( CEconItemView *pScriptItem, CEconItem *pEconItem ) +{ + // We need to detect the case where items have been updated & moved bags / positions. + uint32 iOldPos = pScriptItem->GetInventoryPosition(); + bool bWasInThisBag = ItemShouldBeIncluded( iOldPos ); + + // Ignore items that this inventory doesn't care about + if ( !ItemShouldBeIncluded( pEconItem->GetInventoryToken() ) ) + { + // The item has been moved out of this bag. Ensure our derived inventory classes know. + if ( bWasInThisBag ) + { + // We need to update it before it's removed. + ItemHasBeenUpdated( pScriptItem, false, false ); + + RemoveItem( pEconItem->GetItemID() ); + } + + return false; + } + + pScriptItem->Init( pEconItem->GetDefinitionIndex(), pEconItem->GetQuality(), pEconItem->GetItemLevel(), pEconItem->GetAccountID() ); + if ( !pScriptItem->IsValid() ) + return false; + + pScriptItem->SetItemID( pEconItem->GetItemID() ); + + pScriptItem->SetInventoryPosition( pEconItem->GetInventoryToken() ); + OnItemChangedPosition( pScriptItem, iOldPos ); + +#if BUILD_ITEM_NAME_AND_DESC + // Precache account names if we have any. We do this way in advance of any code that might + // use it (ie., description text building) so that by the time we try that we already have + // the data setup. + // + // We don't worry about yielding here because this inventory code only runs on game + // clients/servers, not the GC. + CSteamAccountIDAttributeCollector AccountIDCollector; + pEconItem->IterateAttributes( &AccountIDCollector ); + + FOR_EACH_VEC( AccountIDCollector.GetAccountIDs(), i ) + { + InventoryManager()->PersonaName_Precache( (AccountIDCollector.GetAccountIDs())[i] ); + } +#endif + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::DumpInventoryToConsole( bool bRoot ) +{ + if ( bRoot ) + { +#ifdef CLIENT_DLL + Msg("(CLIENT) Inventory:\n"); +#else + Msg("(SERVER) Inventory for account (%d):\n", m_OwnerID.GetAccountID() ); +#endif + Msg(" Version: %llu:\n", m_pSOCache ? m_pSOCache->GetVersion() : -1 ); + } + + int iCount = m_aInventoryItems.Count(); + Msg(" Num items: %d\n", iCount ); + for ( int i = 0; i < iCount; i++ ) + { + Msg(" %s (ID %llu)\n", m_aInventoryItems[i].GetStaticData()->GetDefinitionName(), m_aInventoryItems[i].GetItemID() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerInventory::RemoveItem( itemid_t iItemID ) +{ + int iIndex; + CEconItemView *pItem = GetInventoryItemByItemID( iItemID, &iIndex ); + if ( pItem ) + { + ItemIsBeingRemoved( pItem ); + + FOR_EACH_VEC( m_vecItemHandles, i ) + { + m_vecItemHandles[ i ]->MarkDirty(); + m_vecItemHandles[ i ]->ItemIsBeingDeleted( pItem ); + } + + m_aInventoryItems.Remove(iIndex); + +#ifdef _DEBUG + if ( item_inventory_debug.GetBool() ) + { + DumpInventoryToConsole( true ); + } +#endif + } + + // Don't need to resort because items will still be in order +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the item in our inventory that matches the specified global index +//----------------------------------------------------------------------------- +CEconItemView *CPlayerInventory::GetInventoryItemByItemID( itemid_t iIndex, int *pIndex ) +{ + int iCount = m_aInventoryItems.Count(); + for ( int i = 0; i < iCount; i++ ) + { + if ( m_aInventoryItems[i].GetItemID() == iIndex ) + { + if ( pIndex ) + { + *pIndex = i; + } + + return &m_aInventoryItems[i]; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Finds the item in our inventory that matches the specified global original id +//----------------------------------------------------------------------------- +CEconItemView *CPlayerInventory::GetInventoryItemByOriginalID( itemid_t iOriginalID, int *pIndex /*= NULL*/ ) +{ + int iCount = m_aInventoryItems.Count(); + for ( int i = 0; i < iCount; i++ ) + { + CEconItem *pItem = m_aInventoryItems[i].GetSOCData(); + if ( pItem && pItem->GetOriginalID() == iOriginalID ) + { + if ( pIndex ) + { + *pIndex = i; + } + + return &m_aInventoryItems[i]; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the item in our inventory in the specified position +//----------------------------------------------------------------------------- +CEconItemView *CPlayerInventory::GetItemByPosition( int iPosition, int *pIndex ) +{ + int iCount = m_aInventoryItems.Count(); + for ( int i = 0; i < iCount; i++ ) + { + if ( m_aInventoryItems[i].GetInventoryPosition() == (unsigned int)iPosition ) + { + if ( pIndex ) + { + *pIndex = i; + } + + return &m_aInventoryItems[i]; + } + } + + return NULL; +} + +// Finds the first item in our backpack with match itemdef +//----------------------------------------------------------------------------- +CEconItemView *CPlayerInventory::FindFirstItembyItemDef( item_definition_index_t iItemDef ) +{ + int iCount = m_aInventoryItems.Count(); + for ( int i = 0; i < iCount; i++ ) + { + //GetItemDefIndex() + if ( m_aInventoryItems[i].GetItemDefIndex() == iItemDef ) + { + return &m_aInventoryItems[i]; + } + } + + return NULL; + +} + +//----------------------------------------------------------------------------- +// Purpose: Get the index for the item in our inventory utlvector +//----------------------------------------------------------------------------- +int CPlayerInventory::GetIndexForItem( CEconItemView *pItem ) +{ + int iCount = m_aInventoryItems.Count(); + for ( int i = 0; i < iCount; i++ ) + { + if ( m_aInventoryItems[i].GetItemID() == pItem->GetItemID() ) + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Dirty all the item handles that are registered with us +//----------------------------------------------------------------------------- +void CPlayerInventory::DirtyItemHandles() +{ + FOR_EACH_VEC( m_vecItemHandles, i ) + { + m_vecItemHandles[ i ]->MarkDirty(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Get the item object cache data for the specified item +//----------------------------------------------------------------------------- +CEconItem *CPlayerInventory::GetSOCDataForItem( itemid_t iItemID ) +{ + if ( !m_pSOCache ) + return NULL; + + CEconItem soIndex; + soIndex.SetItemID( iItemID ); + return (CEconItem *)m_pSOCache->FindSharedObject( soIndex ); +} + +#if defined (_DEBUG) && defined(CLIENT_DLL) +CON_COMMAND_F( item_deleteall, "WARNING: Removes all of the items in your inventory.", FCVAR_CHEAT ) +{ + CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); + if ( !pInventory ) + return; + + int iCount = pInventory->GetItemCount(); + for ( int i = 0; i < iCount; i++ ) + { + CEconItemView *pItem = pInventory->GetItem(i); + if ( pItem ) + { + InventoryManager()->DropItem( pItem->GetItemID() ); + } + } + + InventoryManager()->UpdateLocalInventory(); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CPlayerInventory::GetRecipeCount() const +{ + const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap(); + + return mapRecipes.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDef( int iIndex ) +{ + if ( !m_pSOCache ) + return NULL; + + if ( iIndex < 0 || iIndex >= GetRecipeCount() ) + return NULL; + + const CEconItemSchema::RecipeDefinitionMap_t& mapRecipes = GetItemSchema()->GetRecipeDefinitionMap(); + + // Store off separate index for "number of items iterated over" in case something + // deletes from the recipes map out from under us. + int j = 0; + FOR_EACH_MAP_FAST( mapRecipes, i ) + { + if ( j == iIndex ) + return mapRecipes[i]; + + j++; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconCraftingRecipeDefinition *CPlayerInventory::GetRecipeDefByDefIndex( uint16 iDefIndex ) +{ + if ( !m_pSOCache ) + return NULL; + + // check always-known recipes + const CUtlMap<int, CEconCraftingRecipeDefinition *, int>& mapRecipes = ItemSystem()->GetItemSchema()->GetRecipeDefinitionMap(); + int i = mapRecipes.Find( iDefIndex ); + if ( i != mapRecipes.InvalidIndex() ) + return mapRecipes[i]; + + // there are no more SO recipes + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemViewHandle::SetItem( CEconItemView* pItem ) +{ + m_pItem = pItem; + + if ( pItem ) + { + // Cache the item_id for lookup when our pointer gets dirtied + m_nItemID = pItem->GetItemID(); + auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() ); + Assert( pInv ); + if ( m_pInv != pInv ) + { + // If this is a different inventory, unsubscribe. This can happen if the + // handle gets reused + if ( m_pInv ) + { + m_pInv->RemoveItemHandle( this ); + } + + m_pInv = pInv; + + // Subscribe to the new inventory + m_pInv->AddItemHandle( this ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return a pointer to a CEconItemView +//----------------------------------------------------------------------------- +CEconItemView* CEconItemViewHandle::Get() const +{ + // If our pointer is dirty, we need to go get a new pointer + if ( m_bPointerDirty ) + { + if ( m_pInv ) + { + m_pItem = m_pInv->GetInventoryItemByItemID( m_nItemID ); + m_bPointerDirty = false; + } + } + + return m_pItem; +} + + +//----------------------------------------------------------------------------- +// Purpose: Unsubscribe us from future updates +//----------------------------------------------------------------------------- +CEconItemHandle::~CEconItemHandle() +{ + UnsubscribeFromSOEvents(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Save a pointer to the item and register us for SOCache events +//----------------------------------------------------------------------------- +void CEconItemHandle::SetItem( CEconItem* pItem ) +{ + UnsubscribeFromSOEvents(); + + m_pItem = NULL; + m_iItemID = INVALID_ITEM_ID; + + if ( pItem ) + { + auto* pInv = InventoryManager()->GetInventoryForAccount( pItem->GetAccountID() ); + if ( pInv ) + { + m_OwnerSteamID.SetFromUint64( pInv->GetOwner().ConvertToUint64() ); + GCClientSystem()->GetGCClient()->AddSOCacheListener( m_OwnerSteamID, this ); + } + + m_pItem = pItem; + m_iItemID = pItem->GetID(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Check if out item got deleted. If it did, mark our pointer as NULL +// so future dereferences will get NULL instead of a stale pointer. +//----------------------------------------------------------------------------- +void CEconItemHandle::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) +{ + if( pObject->GetTypeID() != CEconItem::k_nTypeID || m_pItem == NULL ) + return; + + const CEconItem *pItem = (CEconItem *)pObject; + + if ( m_iItemID == pItem->GetID() ) + { + UnsubscribeFromSOEvents(); + m_pItem = NULL; + m_iItemID = INVALID_ITEM_ID; + } +} + +void CEconItemHandle::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) +{ + if( pObject->GetTypeID() != CEconItem::k_nTypeID ) + return; + + CEconItem *pItem = (CEconItem *)pObject; + + if ( m_iItemID == pItem->GetID() ) + { + SetItem( pItem ); + } +} + +void CEconItemHandle::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) +{ + if ( pObject->GetTypeID() != CEconItem::k_nTypeID ) + return; + + CEconItem *pItem = (CEconItem *)pObject; + + if ( m_iItemID == pItem->GetID() ) + { + SetItem( pItem ); + } +} + +void CEconItemHandle::SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) +{ + UnsubscribeFromSOEvents(); +} + +void CEconItemHandle::UnsubscribeFromSOEvents() +{ + if ( m_OwnerSteamID.GetAccountID() != 0 ) + { + GCClientSystem()->GetGCClient()->RemoveSOCacheListener( m_OwnerSteamID, this ); + } +} + + +#if defined( STAGING_ONLY ) || defined( _DEBUG ) +#if defined(CLIENT_DLL) +CON_COMMAND_F( item_dumpinv, "Dumps the contents of a specified client inventory.", FCVAR_CHEAT ) +#else +CON_COMMAND_F( item_dumpinv_sv, "Dumps the contents of a specified server inventory.", FCVAR_CHEAT ) +#endif +{ +#if defined(CLIENT_DLL) + CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); +#else + CSteamID steamID; + CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_GetCommandClient() ); + pPlayer->GetSteamID( &steamID ); + CPlayerInventory *pInventory = InventoryManager()->GetInventoryForAccount( steamID.GetAccountID() ); +#endif + if ( !pInventory ) + { + Msg("No inventory found.\n"); + return; + } + + pInventory->DumpInventoryToConsole( true ); +} + +#if defined (CLIENT_DLL) + +CON_COMMAND_F( item_dumpschema, "Dump the expanded schema for items to a file in sorted order suitable for diffs. Format: item_dumpschema <filename>", FCVAR_CHEAT ) +{ + if ( args.ArgC() != 2 ) + { + Msg("Usage: item_dumpschema <filename>\n"); + return; + } + + if ( GetItemSchema()->DumpItems(args[1]) ) + Msg("Dump complete, saved in game/tf/%s\n", args[1]); + else + Msg("Dump failed (?)\n"); +} + +CON_COMMAND_F( item_giveitem, "Give an item to the local player. Format: item_giveitem <item definition name> or <item def index>", FCVAR_NONE ) +{ + if ( !steamapicontext || !steamapicontext->SteamUser() ) + { + Msg("Not connected to Steam.\n"); + return; + } + CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID(); + if ( !steamIDForPlayer.IsValid() ) + { + Msg("Failed to find a valid steamID for the local player.\n"); + return; + } + + int iItemCount = args.ArgC(); + for ( int i = 1; i < iItemCount; ++i ) + { + // Check to see if args[1] is a number (itemdefid) and if so, translate it to actual itemname + const char *pszItemname = NULL; + if ( V_isdigit( args[i][0] ) ) + { + int iDef = V_atoi( args[i] ); + CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( iDef ); + if ( pItemDef ) + { + pszItemname = pItemDef->GetItemDefinitionName(); + } + } + else + { + pszItemname = args[i]; + } + + Msg("Sending request to generate '%s' for Local Player (%llu)\n", pszItemname, steamIDForPlayer.ConvertToUint64() ); + + CItemSelectionCriteria criteria; + + GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest ); + msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); + + criteria.SetIgnoreEnabledFlag( true ); + if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemname, true ) || + !criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) ) + { + Msg("Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" ); + return; + } + GCClientSystem()->BSendMessage( msg ); + } +} + +CON_COMMAND_F( item_rolllootlist, "Force a loot list rool for the local player. Format: item_rolllootlist <loot list definition name>", FCVAR_NONE ) +{ + if ( !steamapicontext || !steamapicontext->SteamUser() ) + { + Msg("Not connected to Steam.\n"); + return; + } + CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID(); + if ( !steamIDForPlayer.IsValid() ) + { + Msg("Failed to find a valid steamID for the local player.\n"); + return; + } + + Msg("Sending request to roll '%s' for Local Player (%llu)\n", args[1], steamIDForPlayer.ConvertToUint64() ); + + GCSDK::CProtoBufMsg<CMsgDevDebugRollLootRequest> msg( k_EMsgGCDev_DebugRollLootRequest ); + msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); + msg.Body().set_loot_list_name( args[1] ); + GCClientSystem()->BSendMessage( msg ); +} + +#include "econ_item_description.h" +#include "localization_provider.h" + +CON_COMMAND_F( item_generate_all_descriptions, "Generate full item descriptions for every item in your backpack. Meant as a code test.", FCVAR_CHEAT ) +{ + CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory(); + + for ( int i = 0; i < pInventory->GetItemCount(); i++ ) + { + CEconItemDescription desc; + IEconItemDescription::YieldingFillOutEconItemDescription( &desc, GLocalizationProvider(), pInventory->GetItem( i ) ); + } + + Msg("Done.\n"); +} +#endif // CLIENT_DLL + +#endif // STAGING_ONLY || _DEBUG + + diff --git a/game/shared/econ/econ_item_inventory.h b/game/shared/econ/econ_item_inventory.h new file mode 100644 index 0000000..fd6e8b8 --- /dev/null +++ b/game/shared/econ/econ_item_inventory.h @@ -0,0 +1,472 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Container that allows client & server access to data in player inventories & loadouts +// +//============================================================================= + +#ifndef ITEM_INVENTORY_H +#define ITEM_INVENTORY_H +#ifdef _WIN32 +#pragma once +#endif + +#include "igamesystem.h" +#include "econ_entity.h" +#include "gamestringpool.h" +#include "econ_item_view.h" +#include "UtlSortVector.h" +#include "econ_gcmessages.h" +#include "gc_clientsystem.h" + +#if !defined(NO_STEAM) +#include "steam/steam_api.h" +#include "gcsdk/gcclientsdk.h" +#endif // NO_STEAM + + +class CPlayerInventory; +class CEconItem; +struct baseitemcriteria_t; +class CEconItemViewHandle; +#ifdef CLIENT_DLL +class ITexture; +#endif + +// Inventory Less function. +// Used to sort the inventory items into their positions. +class CInventoryListLess +{ +public: + bool Less( const CEconItemView &src1, const CEconItemView &src2, void *pCtx ); +}; + +// A class that wants notifications when an inventory is updated +class IInventoryUpdateListener : public GCSDK::ISharedObjectListener +{ +public: + virtual void InventoryUpdated( CPlayerInventory *pInventory ) = 0; + + virtual void SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { InventoryUpdated( NULL ); } + virtual void PreSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { /* do nothing */ } + virtual void SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { /* do nothing */ } + virtual void PostSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { InventoryUpdated( NULL ); } + virtual void SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { InventoryUpdated( NULL ); } + virtual void SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { InventoryUpdated( NULL ); } + virtual void SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { InventoryUpdated( NULL ); } +}; + +//----------------------------------------------------------------------------- +// Purpose: A single player's inventory. +// On the client, the inventory manager contains an instance of this for the local player. +// On the server, each player contains an instance of this. +//----------------------------------------------------------------------------- +class CPlayerInventory : public GCSDK::ISharedObjectListener +{ + DECLARE_CLASS_NOBASE( CPlayerInventory ); +public: + CPlayerInventory(); + virtual ~CPlayerInventory(); + + void Clear(); + + // Returns true if this inventory has been filled out by Steam. + bool RetrievedInventoryFromSteam( void ) { return m_bGotItemsFromSteam; } + bool IsWaitingForSteam( void ) { return (m_iPendingRequests > 0); } + + // Inventory access + CSteamID &GetOwner( void ) { return m_OwnerID; } + int GetItemCount( void ) const { return m_aInventoryItems.Count(); } + virtual bool CanPurchaseItems( int iItemCount ) const { return GetMaxItemCount() - GetItemCount() >= iItemCount; } + virtual int GetMaxItemCount( void ) const { return DEFAULT_NUM_BACKPACK_SLOTS; } + CEconItemView *GetItem( int i ) { return &m_aInventoryItems[i]; } + + virtual CEconItemView *GetItemInLoadout( int iClass, int iSlot ) { AssertMsg( 0, "Implement me!" ); return NULL; } + + // Get the item object cache data for the specified item + CEconItem *GetSOCDataForItem( itemid_t iItemID ); + GCSDK::CGCClientSharedObjectCache *GetSOC( void ) { return m_pSOCache; } + + // tells the GC systems to forget about this listener + void RemoveListener( GCSDK::ISharedObjectListener *pListener ); + + // Finds the item in our inventory that matches the specified global index + CEconItemView *GetInventoryItemByItemID( itemid_t iIndex, int *pIndex = NULL ); + + // Finds the item in our inventory that matches the specified global original id + CEconItemView *GetInventoryItemByOriginalID( itemid_t iOriginalID, int *pIndex = NULL ); + + // Finds the item in our inventory in the specified position + CEconItemView *GetItemByPosition( int iPosition, int *pIndex = NULL ); + + // Finds the first item in our backpack with match itemdef + CEconItemView *FindFirstItembyItemDef( item_definition_index_t iItemDef ); + + // Used to reject items on the backend for inclusion into this inventory. + // Mostly used for division of bags into different in-game inventories. + virtual bool ItemShouldBeIncluded( int iItemPosition ) { return true; } + + // Debugging + virtual void DumpInventoryToConsole( bool bRoot ); + + // Extracts the position that should be used to sort items in the inventory from the backend position. + // Necessary if your inventory packs a bunch of info into the position instead of using it just as a position. + virtual int ExtractInventorySortPosition( uint32 iBackendPosition ) { return iBackendPosition; } + + // Recipe access + int GetRecipeCount( void ) const; + const CEconCraftingRecipeDefinition *GetRecipeDef( int iIndex ); + const CEconCraftingRecipeDefinition *GetRecipeDefByDefIndex( uint16 iDefIndex ); + + // Item previews + virtual int GetPreviewItemDef( void ) const { return 0; }; + + // Access helpers + virtual void SOClear(); + + virtual void NotifyHasNewItems() {} + + void AddItemHandle( CEconItemViewHandle* pHandle ); + void RemoveItemHandle( CEconItemViewHandle* pHandle ); + +#ifdef CLIENT_DLL + virtual ITexture *GetWeaponSkinBaseLowRes( itemid_t nItemId, int iTeam ) const { return NULL; } +#endif + + +protected: + // Inventory updating, called by the Inventory Manager only. If you want an inventory updated, + // use the SteamRequestX functions in CInventoryManager. + void RequestInventory( CSteamID pSteamID ); + void AddListener( GCSDK::ISharedObjectListener *pListener ); + virtual bool AddEconItem( CEconItem * pItem, bool bUpdateAckFile, bool bWriteAckFile, bool bCheckForNewItems ); + virtual void RemoveItem( itemid_t iItemID ); + bool FilloutItemFromEconItem( CEconItemView *pScriptItem, CEconItem *pEconItem ); + void SendInventoryUpdateEvent(); + virtual void OnHasNewItems() {} + virtual void OnItemChangedPosition( CEconItemView *pItem, uint32 iOldPos ) { return; } + + virtual void SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + virtual void PreSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { /* do nothing */ } + virtual void SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + virtual void PostSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE { /* do nothing */ } + virtual void SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + virtual void SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + virtual void SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE; + + void ResortInventory( void ) { m_aInventoryItems.RedoSort( true ); } + virtual void ValidateInventoryPositions( void ); + + // Derived inventory hooks + virtual void ItemHasBeenUpdated( CEconItemView *pItem, bool bUpdateAckFile, bool bWriteAckFile ); + virtual void ItemIsBeingRemoved( CEconItemView *pItem ) { return; } + + // Get the index for the item in our inventory utlvector + int GetIndexForItem( CEconItemView *pItem ); + + void DirtyItemHandles(); + +protected: + // The Steam Id of the player who owns this inventory + CSteamID m_OwnerID; + + // The items the player has in his inventory, received from steam. + CUtlSortVector<CEconItemView,CInventoryListLess> m_aInventoryItems; + + int m_iPendingRequests; + bool m_bGotItemsFromSteam; + + GCSDK::CGCClientSharedObjectCache *m_pSOCache; + + CUtlVector<GCSDK::ISharedObjectListener *> m_vecListeners; + + CUtlVector< CEconItemViewHandle* > m_vecItemHandles; + + friend class CInventoryManager; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CInventoryManager : public CAutoGameSystemPerFrame +{ + DECLARE_CLASS_GAMEROOT( CInventoryManager, CAutoGameSystem ); +public: + CInventoryManager( void ); + + // Adds the inventory to the list of inventories that should be maintained. + // This causes the game to load the items for the SteamID into this inventory. + // NOTE: This fires off a request to Steam. The data will not be filled out immediately. + void SteamRequestInventory( CPlayerInventory *pInventory, CSteamID pSteamID, IInventoryUpdateListener *pListener = NULL ); + + void PreInitGC(); + void PostInitGC(); + +#ifdef CLIENT_DLL + void DropItem( itemid_t iItemID ); + int DeleteUnknowns( CPlayerInventory *pInventory ); +#endif + +public: + //----------------------------------------------------------------------- + // IAutoServerSystem + //----------------------------------------------------------------------- + virtual bool Init( void ) OVERRIDE; + virtual void PostInit( void ) OVERRIDE; + virtual void Shutdown() OVERRIDE; + virtual void LevelInitPreEntity( void ) OVERRIDE; + virtual void LevelShutdownPostEntity( void ) OVERRIDE; + +#ifdef CLIENT_DLL + // Gets called each frame + virtual void Update( float frametime ) OVERRIDE; +#endif + + void GameServerSteamAPIActivated(); + + virtual CPlayerInventory *GetInventoryForAccount( uint32 iAccountID ); + + // 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. + virtual void AddBaseItemCriteria( baseitemcriteria_t *pCriteria, CItemSelectionCriteria *pSelectionCriteria ) { return; } + +#ifdef CLIENT_DLL + // Must be implemented by derived class + virtual bool EquipItemInLoadout( int iClass, int iSlot, itemid_t iItemID ) = 0; + + virtual CPlayerInventory *GeneratePlayerInventoryObject() const { return new CPlayerInventory; } + + //----------------------------------------------------------------------- + // ITEM PRESETS + //----------------------------------------------------------------------- + + // Is the given preset index valid? + bool IsPresetIndexValid( equipped_preset_t unPreset ); + + // Equip all items for the given class and preset (all the work is done on the GC -- this just + // sends the message up) + bool LoadPreset( equipped_class_t unClass, equipped_preset_t unPreset ); + + //----------------------------------------------------------------------- + // LOCAL INVENTORY + // + // On the client, we have a single inventory for the local player. Stored here, instead of in the + // local player entity, because players need to access it while not being connected to a server. + // Override GetLocalInventory() in your inventory manager and return your custom local inventory. + //----------------------------------------------------------------------- + virtual void UpdateLocalInventory( void ); + virtual CPlayerInventory *GetLocalInventory( void ) { return NULL; } + + // The local inventory is used to track discards & responses to. We need to + // make a decision about inventory space right after sending a delete request, + // so we predict the request will work. + void OnItemDeleted( CPlayerInventory *pInventory ) { if ( pInventory == GetLocalInventory() ) m_iPredictedDiscards--; } + + virtual void PersonaName_Precache( uint32 unAccountID ); + virtual const char *PersonaName_Get( uint32 unAccountID ); + virtual void PersonaName_Store( uint32 unAccountID, const char *pPersonaName ); + + static void SendGCConnectedEvent( void ); + + // Returns the item at the specified backpack position + virtual CEconItemView *GetItemByBackpackPosition( int iBackpackPosition ); + + // Moves the item to the specified backpack position. If there's another item as that spot, it swaps positions with it. + virtual void MoveItemToBackpackPosition( CEconItemView *pItem, int iBackpackPosition ); + + // Tries to set the item to the specified backpack position. Passing in 0 will find the first empty position. + // FAILS if the backpack is full, or if that spot isn't clear. Returns false in that case. + virtual bool SetItemBackpackPosition( CEconItemView *pItem, uint32 iPosition = 0, bool bForceUnequip = false, bool bAllowOverflow = false ); + + // Sort the backpack items by the specified type + virtual void SortBackpackBy( uint32 iSortType ); + void SortBackpackFinished( void ); + bool IsInBackpackSort( void ) { return m_bInBackpackSort; } + + void PredictedBackpackPosFilled( int iBackpackPos ) { m_PredictedFilledSlots.FindAndRemove( iBackpackPos ); } + + // Tell the backend to move an item to a specified backend position + virtual void UpdateInventoryPosition( CPlayerInventory *pInventory, uint64 ulItemID, uint32 unNewInventoryPos ); + + virtual void UpdateInventoryEquippedState( CPlayerInventory *pInventory, uint64 ulItemID, equipped_class_t unClass, equipped_slot_t unSlot ); + + + //----------------------------------------------------------------------- + // CLIENT PICKUP UI HANDLING + //----------------------------------------------------------------------- + + // Get the number of items picked up + virtual int GetNumItemPickedUpItems( void ) { return 0; } + + // Show the player a pickup screen with any items they've collected recently, if any + virtual bool ShowItemsPickedUp( bool bForce = false, bool bReturnToGame = true, bool bNoPanel = false ); + + // Show the player a pickup screen with the items they've crafted + virtual void ShowItemsCrafted( CUtlVector<itemid_t> *vecCraftedIndices ) { return; } + + // Force the player to discard an item to make room for a new item, if they have one. + // Returns true if the discard panel has been brought up, and the player will be forced to discard an item. + virtual bool CheckForRoomAndForceDiscard( void ); + + //----------------------------------------------------------------------- + // CLIENT ITEM PICKUP ACKNOWLEDGEMENT FILES + // + // This system avoids showing 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. We keep a list of + // items we've ack'd in a client file, and don't re-show pickups for them. When a GC item + // update tells us the item has moved out of the unack'd position, we remove it from our file. + //----------------------------------------------------------------------- + + virtual void AcknowledgeItem ( CEconItemView *pItem, bool bMoveToBackpack = true ); // Client Acknowledges an item and moves it in to the backpack + bool HasBeenAckedByClient( CEconItemView *pItem ); // Returns true if it's in our client file + void SetAckedByClient( CEconItemView *pItem ); // Adds it to our client file + void SetAckedByGC( CEconItemView *pItem, bool bSave ); // Removes it from our client file + KeyValues *GetAckKeyForItem( CEconItemView *pItem ); + void CleanAckFile( void ); + void SaveAckFile( void ); + +private: + void VerifyAckFileLoaded( void ); + KeyValues *m_pkvItemClientAckFile; + bool m_bClientAckDirty; + +private: + // As we move items around in batches (on pickups usually) we need to know what slots will be filled + // by items we've moved, and haven't received a response from Steam. + CUtlVector<int> m_PredictedFilledSlots; +#endif + +public: + virtual int GetBackpackPositionFromBackend( uint32 iBackendPosition ) { return ExtractBackpackPositionFromBackend(iBackendPosition); } + +private: + //----------------------------------------------------------------------- + // Pending inventory requests + struct pendingreq_t + { + CPlayerInventory *pInventory; + CSteamID pID; + }; + CUtlVector<pendingreq_t> m_hPendingInventoryRequests; + void RemovePendingRequest( CSteamID *pSteamID ); + +protected: + //----------------------------------------------------------------------- + // Inventory registry + void DeregisterInventory( CPlayerInventory *pInventory ); + struct inventories_t + { + CPlayerInventory *pInventory; + IInventoryUpdateListener *pListener; + }; + CUtlVector<inventories_t> m_pInventories; + + friend class CPlayerInventory; + + inline bool IsValidPlayerClass( equipped_class_t unClass ); + +#ifdef CLIENT_DLL + // Keep track of the number of items we've tried to discard, but haven't recieved responses on + int m_iPredictedDiscards; + + typedef CUtlMap< uint32, CUtlString, int > tPersonaNamesByAccountID; + tPersonaNamesByAccountID m_mapPersonaNamesCache; + + bool m_bInBackpackSort; + + float m_flNextLoadPresetChange; + + CMsgSetItemPositions m_msgPendingSetItemPositions; + CMsgLookupMultipleAccountNames m_msgPendingLookupAccountNames; + + void OnPersonaStateChanged( PersonaStateChange_t *info ); + CCallback< CInventoryManager, PersonaStateChange_t, false > m_sPersonaStateChangedCallback; + CUtlMap< uint64, bool > m_personaNameRequests; + +#endif +}; + +//================================================================================= +// Implement these functions in your game code to create custom derived versions +CInventoryManager *InventoryManager( void ); + +CBasePlayer *GetPlayerBySteamID( const CSteamID &steamID ); + +//----------------------------------------------------------------------------- +// Purpose: Maintains a handle to an CEconItemView within an inventory. When +// the inventory gets updated and shuffles CEconItemViews around, this +// handle automatically updates its pointer to point to the new +// CEconItemView that has the same item_id +//----------------------------------------------------------------------------- +class CEconItemViewHandle +{ +public: + CEconItemViewHandle() + : m_pItem( NULL ) + , m_pInv( NULL ) + , m_bPointerDirty( false ) + {} + + CEconItemViewHandle( CEconItemView* pItem ) + : m_pItem( pItem ) + , m_pInv( NULL ) + , m_bPointerDirty( false ) + { + SetItem( pItem ); + } + + virtual ~CEconItemViewHandle() + { + // Unregister us + if ( m_pInv ) + { + m_pInv->RemoveItemHandle( this ); + } + } + + void SetItem( CEconItemView* pItem ); + + operator CEconItemView *( void ) const + { + return Get(); + } + + CEconItemView* operator->( void ) const + { + return Get(); + } + + void ItemIsBeingDeleted( const CEconItemView* pItem ) + { + m_bPointerDirty = true; + + // Inventory told us the item is going away + if ( m_pItem == pItem ) + { + m_pItem = NULL; + } + } + + void InventoryIsBeingDeleted() + { + m_pInv = NULL; + m_pItem = NULL; + m_bPointerDirty = false; // So we dont keep trying to look up the item + } + + void MarkDirty() + { + m_bPointerDirty = true; + } + +private: + + CEconItemView* Get() const; + + mutable bool m_bPointerDirty; // Used to mark when m_pItem is no longer valid + CPlayerInventory *m_pInv; // Inventory the item belongs to. Used to look up new CEconItemView + mutable CEconItemView* m_pItem; // The item. + uint64 m_nItemID; // ID of the item + CSteamID m_OwnerSteamID; // Steam ID of the item owner +}; + + +#endif // ITEM_INVENTORY_H diff --git a/game/shared/econ/econ_item_preset.cpp b/game/shared/econ/econ_item_preset.cpp new file mode 100644 index 0000000..e778167 --- /dev/null +++ b/game/shared/econ/econ_item_preset.cpp @@ -0,0 +1,338 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=================================================================== + +#include "cbase.h" +#include "econ_item_preset.h" +#include "tier1/generichash.h" + +#ifdef GC_DLL +#include "gcsdk/sqlaccess/sqlaccess.h" +#endif + +using namespace GCSDK; + +#ifdef GC_DLL +IMPLEMENT_CLASS_MEMPOOL( CEconItemPerClassPresetData, 10 * 1000, UTLMEMORYPOOL_GROW_SLOW ); +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +CEconItemPerClassPresetData::CEconItemPerClassPresetData() + : m_unAccountID( 0 ) + , m_unClassID( (equipped_class_t)-1 ) + , m_unActivePreset( INVALID_PRESET_INDEX ) +{ +} + +CEconItemPerClassPresetData::CEconItemPerClassPresetData( uint32 unAccountID, equipped_class_t unClassID ) + : m_unAccountID( unAccountID ) + , m_unClassID( unClassID ) + , m_unActivePreset( 0 ) +{ +} + +void CEconItemPerClassPresetData::SerializeToProtoBufItem( CSOClassPresetClientData& msgPresetData ) const +{ + msgPresetData.set_account_id( m_unAccountID ); + msgPresetData.set_class_id( m_unClassID ); + msgPresetData.set_active_preset_id( m_unActivePreset ); +} + +void CEconItemPerClassPresetData::DeserializeFromProtoBufItem( const CSOClassPresetClientData &msgPresetData ) +{ + m_unAccountID = msgPresetData.account_id(); + m_unClassID = msgPresetData.class_id(); + m_unActivePreset = msgPresetData.active_preset_id(); +} + +bool CEconItemPerClassPresetData::BIsKeyLess( const CSharedObject& soRHS ) const +{ + const CEconItemPerClassPresetData *soPresetData = assert_cast< const CEconItemPerClassPresetData * >( &soRHS ); + + Assert( m_unAccountID == soPresetData->m_unAccountID ); + + return m_unClassID < soPresetData->m_unClassID; +} + +#ifdef GC +static bool BYieldingAddPresetItemRowsForSpecificPreset( GCSDK::CSQLAccess &sqlAccess, CSchItemPresetInstance& schItemPresetInstance, const CUtlVector<PresetSlotItem_t>& vecPresetData ) +{ + FOR_EACH_VEC( vecPresetData, j ) + { + schItemPresetInstance.m_unSlotID = vecPresetData[j].m_unSlotID; + schItemPresetInstance.m_ulItemID = vecPresetData[j].m_ulItemOriginalID; + if ( !sqlAccess.BYieldingInsertRecord( &schItemPresetInstance ) ) + return false; + } + + return true; +} + +bool CEconItemPerClassPresetData::BYieldingAddInsertToTransaction( GCSDK::CSQLAccess &sqlAccess ) +{ + // Write out the preset data for our selected items. + CSchItemPresetInstance schItemPresetInstance; + schItemPresetInstance.m_unAccountID = m_unAccountID; + schItemPresetInstance.m_unClassID = m_unClassID; + + for ( int i = 0; i < ARRAYSIZE( m_PresetData ); i++ ) + { + schItemPresetInstance.m_unPresetID = i; + + if ( !BYieldingAddPresetItemRowsForSpecificPreset( sqlAccess, schItemPresetInstance, m_PresetData[i] ) ) + return false; + } + + // Write out the data for which preset is active for this class. + CSchSelectedItemPreset schSelectedItemPreset; + schSelectedItemPreset.m_unAccountID = m_unAccountID; + schSelectedItemPreset.m_unClassID = m_unClassID; + schSelectedItemPreset.m_unPresetID = m_unActivePreset; + + if ( !sqlAccess.BYieldingInsertRecord( &schSelectedItemPreset ) ) + return false; + + return true; +} + +bool CEconItemPerClassPresetData::BYieldingAddWriteToTransaction( GCSDK::CSQLAccess &sqlAccess, const CUtlVector< int > &fields ) +{ + Assert( sqlAccess.BInTransaction() ); + + FOR_EACH_VEC( fields, i ) + { + const int iField = fields[i]; + + if ( iField == kPerClassPresetDataDirtyField_ActivePreset ) + { + CSchSelectedItemPreset schSelectedItemPreset; + schSelectedItemPreset.m_unAccountID = m_unAccountID; + schSelectedItemPreset.m_unClassID = m_unClassID; + schSelectedItemPreset.m_unPresetID = m_unActivePreset; + + if ( !sqlAccess.BYieldingUpdateRecord( schSelectedItemPreset, CSET_2_COL( CSchSelectedItemPreset, k_iField_unAccountID, k_iField_unClassID ), CSET_1_COL( CSchSelectedItemPreset, k_iField_unPresetID ) ) ) + return false; + } + else if ( iField >= kPerClassPresetDataDirtyField_PresetData_Base ) + { + int iDirtyPreset = iField - kPerClassPresetDataDirtyField_PresetData_Base; + Assert( iDirtyPreset >= 0 ); + Assert( iDirtyPreset < ARRAYSIZE( m_PresetData ) ); + + // First, remove any existing rows for this preset. + CSchItemPresetInstance schItemPresetInstance; + schItemPresetInstance.m_unAccountID = m_unAccountID; + schItemPresetInstance.m_unClassID = m_unClassID; + schItemPresetInstance.m_unPresetID = iDirtyPreset; + + if ( !sqlAccess.BYieldingDeleteRecords( schItemPresetInstance, CSET_3_COL( CSchItemPresetInstance, k_iField_unAccountID, k_iField_unPresetID, k_iField_unClassID ) ) ) + return false; + + // Don't write out data for our currently-equipped items. We'll handle these by + // writing them out as actually equipped. + if ( iDirtyPreset != GetActivePreset() ) + { + // Add our new rows. + if ( !BYieldingAddPresetItemRowsForSpecificPreset( sqlAccess, schItemPresetInstance, m_PresetData[iDirtyPreset] ) ) + return false; + } + } + } + + return true; +} + +bool CEconItemPerClassPresetData::BYieldingAddRemoveToTransaction( GCSDK::CSQLAccess &sqlAccess ) +{ + CSchItemPresetInstance schItemPresetInstance; + schItemPresetInstance.m_unAccountID = m_unAccountID; + schItemPresetInstance.m_unClassID = m_unClassID; + + if ( !sqlAccess.BYieldingDeleteRecords( schItemPresetInstance, CSET_2_COL( CSchItemPresetInstance, k_iField_unAccountID, k_iField_unClassID ) ) ) + return false; + + CSchSelectedItemPreset schSelectedItemPreset; + schSelectedItemPreset.m_unAccountID = m_unAccountID; + schSelectedItemPreset.m_unClassID = m_unClassID; + + if ( !sqlAccess.BYieldingDeleteRecords( schSelectedItemPreset, CSET_2_COL( CSchSelectedItemPreset, k_iField_unAccountID, k_iField_unClassID ) ) ) + return false; + + return true; +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItemPerClassPresetData::BAddToMessage( CUtlBuffer & bufOutput ) const +{ + CSOClassPresetClientData msgClientPresetData; + SerializeToProtoBufItem( msgClientPresetData ); + return CProtoBufSharedObjectBase::SerializeToBuffer( msgClientPresetData, bufOutput ); +} + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +bool CEconItemPerClassPresetData::BAddToMessage( std::string *pBuffer ) const +{ + CSOClassPresetClientData msgClientPresetData; + SerializeToProtoBufItem( msgClientPresetData ); + return msgClientPresetData.SerializeToString( pBuffer ); +} + +//---------------------------------------------------------------------------- +// Purpose: Adds just the item ID to the message so that the client can find +// which item to destroy +//---------------------------------------------------------------------------- +bool CEconItemPerClassPresetData::BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const +{ + CSOClassPresetClientData msgClientPresetData; + msgClientPresetData.set_class_id( m_unClassID ); + return CProtoBufSharedObjectBase::SerializeToBuffer( msgClientPresetData, bufDestroy ); +} + +//---------------------------------------------------------------------------- +// Purpose: Adds just the item ID to the message so that the client can find +// which item to destroy +//---------------------------------------------------------------------------- +bool CEconItemPerClassPresetData::BAddDestroyToMessage( std::string *pBuffer ) const +{ + CSOClassPresetClientData msgClientPresetData; + msgClientPresetData.set_class_id( m_unClassID ); + return msgClientPresetData.SerializeToString( pBuffer ); +} +#endif + +bool CEconItemPerClassPresetData::BParseFromMessage( const CUtlBuffer & buffer ) +{ + CSOClassPresetClientData msgClientPresetData; + if( !msgClientPresetData.ParseFromArray( buffer.Base(), buffer.TellMaxPut() ) ) + return false; + + DeserializeFromProtoBufItem( msgClientPresetData ); + + return true; +} + +bool CEconItemPerClassPresetData::BParseFromMessage( const std::string &buffer ) +{ + CSOClassPresetClientData msgClientPresetData; + if( !msgClientPresetData.ParseFromString( buffer ) ) + return false; + + DeserializeFromProtoBufItem( msgClientPresetData ); + + return true; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +bool CEconItemPerClassPresetData::BUpdateFromNetwork( const CSharedObject & objUpdate ) +{ + Copy( objUpdate ); + return true; +} + +void CEconItemPerClassPresetData::Copy( const CSharedObject & soRHS ) +{ + const CEconItemPerClassPresetData& rhs = static_cast<const CEconItemPerClassPresetData&>( soRHS ); + + m_unAccountID = rhs.m_unAccountID; + m_unClassID = rhs.m_unClassID; + m_unActivePreset = rhs.m_unActivePreset; + + for ( int i = 0; i < ARRAYSIZE( m_PresetData ); i++ ) + { + m_PresetData[i].CopyArray( rhs.m_PresetData[i].Base(), rhs.m_PresetData[i].Count() ); + } +} + +void CEconItemPerClassPresetData::Dump() const +{ +#if 0 + EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "preset id=%d class id=%d slot id=%d item id=%llu\n", + m_unPresetID, m_unClassID, m_unSlotID, m_ulItemID ); +#endif +} + +#ifdef GC_DLL +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +const CUtlVector<PresetSlotItem_t> *CEconItemPerClassPresetData::FindItemsForPresetIndex( equipped_preset_t unPreset ) const +{ + if ( unPreset >= ARRAYSIZE( m_PresetData ) ) + return NULL; + + return &m_PresetData[ unPreset ]; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void CEconItemPerClassPresetData::SetActivePreset( equipped_preset_t unPreset ) +{ + if ( unPreset >= ARRAYSIZE( m_PresetData ) ) + return; + + m_unActivePreset = unPreset; +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void CEconItemPerClassPresetData::EquipItemIntoActivePresetSlot( equipped_slot_t unSlot, itemid_t unOriginalItemID ) +{ + Assert( GetItemSchema()->IsValidItemSlot( unSlot, EQUIP_TYPE_CLASS ) ); + Assert( m_unActivePreset < ARRAYSIZE( m_PresetData ) ); + + auto& PresetData = m_PresetData[m_unActivePreset]; + + FOR_EACH_VEC( PresetData, i ) + { + if ( PresetData[i].m_unSlotID == unSlot ) + { + // If we're unequipping, stop tracking this slot. + if ( unOriginalItemID == INVALID_ITEM_ID ) + { + PresetData.FastRemove( i ); + } + // Otherwise store the current reference. + else + { + PresetData[i].m_ulItemOriginalID = unOriginalItemID; + } + return; + } + } + + // We don't expect to get here without having an item equipped already, but it's possible if + // items get swapped around on the back end, or if we process messages out of order and/or drop + // some. + if ( unOriginalItemID != INVALID_ITEM_ID ) + { + PresetSlotItem_t PresetSlotItem; + PresetSlotItem.m_unSlotID = unSlot; + PresetSlotItem.m_ulItemOriginalID = unOriginalItemID; + PresetData.AddToTail( PresetSlotItem ); + } +} + +//---------------------------------------------------------------------------- +// Purpose: +//---------------------------------------------------------------------------- +void CEconItemPerClassPresetData::RemoveAllItemsFromPresetIndex( equipped_preset_t unPreset ) +{ + if ( unPreset >= ARRAYSIZE( m_PresetData ) ) + return; + + m_PresetData[unPreset].Purge(); +} +#endif // GC_DLL
\ No newline at end of file diff --git a/game/shared/econ/econ_item_preset.h b/game/shared/econ/econ_item_preset.h new file mode 100644 index 0000000..0aa394a --- /dev/null +++ b/game/shared/econ/econ_item_preset.h @@ -0,0 +1,102 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=================================================================== + +#ifndef ECONITEMPRESET_H +#define ECONITEMPRESET_H +#ifdef _WIN32 +#pragma once +#endif + +#include "gcsdk/protobufsharedobject.h" +#include "gcsdk/gcclientsdk.h" +#include "base_gcmessages.pb.h" + +#include "econ/econ_item_constants.h" + +namespace GCSDK +{ + class CSQLAccess; +}; + +class CSOClassPresetClientData; + +typedef uint8 equipped_preset_t; + +struct PresetSlotItem_t +{ +#ifdef GC_DLL + DECLARE_CLASS_MEMPOOL( PresetSlotItem_t ); +#endif + + equipped_slot_t m_unSlotID; + itemid_t m_ulItemOriginalID; // Original ID of the item in this slot. We store this instead of the current ID to avoid breaking presets when items get renamed, etc. +}; + +// -------------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------------- +class CEconItemPerClassPresetData : public GCSDK::CSharedObject +{ +#ifdef GC_DLL + DECLARE_CLASS_MEMPOOL( CEconItemPerClassPresetData ); +#endif + +public: + typedef GCSDK::CSharedObject BaseClass; + + const static int k_nTypeID = k_EEconTypeItemPresetInstance; + virtual int GetTypeID() const OVERRIDE { return k_nTypeID; } + + CEconItemPerClassPresetData(); + CEconItemPerClassPresetData( uint32 unAccountID, equipped_class_t unClassID ); + + virtual bool BIsKeyLess( const CSharedObject& soRHS ) const; + +#ifdef GC + virtual bool BYieldingAddInsertToTransaction( GCSDK::CSQLAccess &sqlAccess ) OVERRIDE; + virtual bool BYieldingAddWriteToTransaction( GCSDK::CSQLAccess &sqlAccess, const CUtlVector< int > &fields ) OVERRIDE; + virtual bool BYieldingAddRemoveToTransaction( GCSDK::CSQLAccess &sqlAccess ) OVERRIDE; + virtual bool BAddToMessage( CUtlBuffer & bufOutput ) const OVERRIDE; + virtual bool BAddToMessage( std::string *pBuffer ) const OVERRIDE; + virtual bool BAddDestroyToMessage( CUtlBuffer & bufDestroy ) const OVERRIDE; + virtual bool BAddDestroyToMessage( std::string *pBuffer ) const OVERRIDE; +#endif + + virtual bool BParseFromMessage( const CUtlBuffer & buffer ) OVERRIDE; + virtual bool BParseFromMessage( const std::string &buffer ) OVERRIDE; + virtual bool BUpdateFromNetwork( const CSharedObject & objUpdate ) OVERRIDE; + virtual void Copy( const CSharedObject & soRHS ); + virtual void Dump() const; + + void SerializeToProtoBufItem( CSOClassPresetClientData &msgPresetInstance ) const; + void DeserializeFromProtoBufItem( const CSOClassPresetClientData &msgPresetIntance ); + + enum + { + kPerClassPresetDataDirtyField_ActivePreset, + kPerClassPresetDataDirtyField_PresetData_Base, + }; + +#ifdef GC_DLL + const CUtlVector<PresetSlotItem_t> *FindItemsForPresetIndex( equipped_preset_t unPreset ) const; + void EquipItemIntoActivePresetSlot( equipped_slot_t unSlot, itemid_t unOriginalItemID ); + void RemoveAllItemsFromPresetIndex( equipped_preset_t unPreset ); + + void SetActivePreset( equipped_preset_t unPreset ); + equipped_class_t GetClass() const { return m_unClassID; } +#endif // GC_DLL + equipped_preset_t GetActivePreset() const { return m_unActivePreset; } + +private: + CEconItemPerClassPresetData( const CEconItemPerClassPresetData& ) = delete; + void operator=( const CEconItemPerClassPresetData& ) = delete; + +private: + uint32 m_unAccountID; + equipped_class_t m_unClassID; + equipped_preset_t m_unActivePreset; + CUtlVector<PresetSlotItem_t> m_PresetData[ CEconItemSchema::kMaxItemPresetCount ]; +}; + +#endif // ECONITEMPRESET_H diff --git a/game/shared/econ/econ_item_schema.cpp b/game/shared/econ/econ_item_schema.cpp new file mode 100644 index 0000000..1093c5c --- /dev/null +++ b/game/shared/econ/econ_item_schema.cpp @@ -0,0 +1,9705 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: EconItemSchema: Defines a schema for econ items +// +//============================================================================= + +#include "cbase.h" +#include "econ_item_schema.h" +#include "tier1/fmtstr.h" +#include "tier1/UtlSortVector.h" +#include "tier2/tier2.h" +#include "filesystem.h" +#include "schemainitutils.h" +#include "gcsdk/gcsdk_auto.h" +#include "rtime.h" +#include "item_selection_criteria.h" +#include "crypto.h" +#include "checksum_sha1.h" + +#include <google/protobuf/text_format.h> +#include <string.h> + +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/itexture.h" +#include "materialsystem/itexturecompositor.h" + +#if ( defined( _MSC_VER ) && _MSC_VER >= 1900 ) +#define timezone _timezone +#define daylight _daylight +#endif + +// For holiday-limited loot lists. +#include "econ_holidays.h" + +// Only used for startup testing. +#include "econ_item_tools.h" + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + #include "econ_item_system.h" + #include "econ_item.h" + #include "activitylist.h" + + #if defined(TF_CLIENT_DLL) || defined(TF_DLL) + #include "tf_gcmessages.h" + #endif +#endif + +#ifdef GC_DLL +#include "gcgamebase.h" +#include <memory> // unique_ptr +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +using namespace GCSDK; + + +CEconItemSchema & GEconItemSchema() +{ +#if defined( EXTERNALTESTS_DLL ) + static CEconItemSchema g_econItemSchema; + return g_econItemSchema; +#elif defined( GC_DLL ) + return *GEconManager()->GetItemSchema(); +#else + return *ItemSystem()->GetItemSchema(); +#endif +} + +const char *g_szDropTypeStrings[] = +{ + "", // Blank and none mean the same thing: stay attached to the body. + "none", + "drop", // The item drops off the body. + "break", // Not implemented, but an example of a type that could be added. +}; + +const char *g_TeamVisualSections[TEAM_VISUAL_SECTIONS] = +{ + "visuals", // TF_TEAM_UNASSIGNED. Visual changes applied to both teams. + NULL, // TF_TEAM_SPECTATOR. Unused. + "visuals_red", // TF_TEAM_RED + "visuals_blu", // TF_TEAM_BLUE + "visuals_mvm_boss", // Hack to override things in MvM at a general level +}; + +int GetTeamVisualsFromString( const char *pszString ) +{ + for ( int i = 0; i < TEAM_VISUAL_SECTIONS; i++ ) + { + // There's a NULL hidden in g_TeamVisualSections + if ( g_TeamVisualSections[i] && !Q_stricmp( pszString, g_TeamVisualSections[i] ) ) + return i; + } + return -1; +} + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +// Used to convert strings to ints for wearable animation types +const char *g_WearableAnimTypeStrings[ NUM_WAP_TYPES ] = +{ + "on_spawn", // WAP_ON_SPAWN, + "start_building", // WAP_START_BUILDING, + "stop_building", // WAP_STOP_BUILDING, + "start_taunting", // WAP_START_TAUNTING, + "stop_taunting", // WAP_STOP_TAUNTING, +}; +#endif + +const char *g_AttributeDescriptionFormats[] = +{ + "value_is_percentage", // ATTDESCFORM_VALUE_IS_PERCENTAGE, + "value_is_inverted_percentage", // ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE + "value_is_additive", // ATTDESCFORM_VALUE_IS_ADDITIVE + "value_is_additive_percentage", // ATTDESCFORM_VALUE_IS_ADDITIVE_PERCENTAGE + "value_is_or", // ATTDESCFORM_VALUE_IS_OR + "value_is_date", // ATTDESCFORM_VALUE_IS_DATE + "value_is_account_id", // ATTDESCFORM_VALUE_IS_ACCOUNT_ID + "value_is_particle_index", // ATTDESCFORM_VALUE_IS_PARTICLE_INDEX -> Could change to "string index" + "value_is_killstreakeffect_index", // ATTDESCFORM_VALUE_IS_KILLSTREAKEFFECT_INDEX -> Could change to "string index" + "value_is_killstreak_idleeffect_index", // ATTDESCFORM_VALUE_IS_KILLSTREAK_IDLEEFFECT_INDEX + "value_is_item_def", // ATTDESCFORM_VALUE_IS_ITEM_DEF + "value_is_from_lookup_table", // ATTDESCFORM_VALUE_IS_FROM_LOOKUP_TABLE +}; + +const char *g_EffectTypes[NUM_EFFECT_TYPES] = +{ + "unusual", // ATTRIB_EFFECT_UNUSUAL, + "strange", // ATTRIB_EFFECT_STRANGE, + "neutral", // ATTRIB_EFFECT_NEUTRAL = 0, + "positive", // ATTRIB_EFFECT_POSITIVE, + "negative", // ATTRIB_EFFECT_NEGATIVE, +}; + +//----------------------------------------------------------------------------- +// Purpose: Set the capabilities bitfield based on whether the entry is true/false. +//----------------------------------------------------------------------------- +const char *g_Capabilities[] = +{ + "paintable", // ITEM_CAP_PAINTABLE + "nameable", // ITEM_CAP_NAMEABLE + "decodable", // ITEM_CAP_DECODABLE + "can_craft_if_purchased", // ITEM_CAP_CAN_BE_CRAFTED_IF_PURCHASED + "can_customize_texture", // ITEM_CAP_CAN_CUSTOMIZE_TEXTURE + "usable", // ITEM_CAP_USABLE + "usable_gc", // ITEM_CAP_USABLE_GC + "can_gift_wrap", // ITEM_CAP_CAN_GIFT_WRAP + "usable_out_of_game", // ITEM_CAP_USABLE_OUT_OF_GAME + "can_collect", // ITEM_CAP_CAN_COLLECT + "can_craft_count", // ITEM_CAP_CAN_CRAFT_COUNT + "can_craft_mark", // ITEM_CAP_CAN_CRAFT_MARK + "paintable_team_colors", // ITEM_CAP_PAINTABLE_TEAM_COLORS + "can_be_restored", // ITEM_CAP_CAN_BE_RESTORED + "strange_parts", // ITEM_CAP_CAN_USE_STRANGE_PARTS + "can_card_upgrade", // ITEM_CAP_CAN_CARD_UPGRADE + "can_strangify", // ITEM_CAP_CAN_STRANGIFY + "can_killstreakify", // ITEM_CAP_CAN_KILLSTREAKIFY + "can_consume", // ITEM_CAP_CAN_CONSUME_ITEMS + "can_spell_page", // ITEM_CAP_CAN_SPELLBOOK_PAGE + "has_slots", // ITEM_CAP_HAS_SLOTS + "duck_upgradable", // ITEM_CAP_DUCK_UPGRADABLE + "can_unusualify", // ITEM_CAP_CAN_UNUSUALIFY +}; +COMPILE_TIME_ASSERT( ARRAYSIZE(g_Capabilities) == NUM_ITEM_CAPS ); + +#define RETURN_ATTRIBUTE_STRING( attrib_name, default_string ) \ + static CSchemaAttributeDefHandle pAttribString( attrib_name ); \ + const char *pchResultAttribString = default_string; \ + FindAttribute_UnsafeBitwiseCast< CAttribute_String >( this, pAttribString, &pchResultAttribString ); \ + return pchResultAttribString; + +#define RETURN_ATTRIBUTE_STRING_F( func_name, attrib_name, default_string ) \ + const char *func_name( void ) const { RETURN_ATTRIBUTE_STRING( attrib_name, default_string ) } + +static void ParseCapability( item_capabilities_t &capsBitfield, KeyValues* pEntry ) +{ + int idx = StringFieldToInt( pEntry->GetName(), g_Capabilities, ARRAYSIZE(g_Capabilities) ); + if ( idx < 0 ) + { + return; + } + int bit = 1 << idx; + if ( pEntry->GetBool() ) + { + (int&)capsBitfield |= bit; + } + else + { + (int&)capsBitfield &= ~bit; + } +} + +#ifdef GC_DLL +static bool BGetPaymentRule( KeyValues *pKVRule, EPaymentRuleType *out_pePaymentRuleType, double *out_pRevenueShare ) +{ + Assert( out_pePaymentRuleType ); + Assert( out_pRevenueShare ); + + struct payment_rule_lookup_t + { + const char *m_pszStr; + EPaymentRuleType m_eRuleType; + }; + + static payment_rule_lookup_t s_Lookup[] = + { + { "workshop_revenue_share", kPaymentRule_SteamWorkshopFileID }, + { "partner_revenue_share", kPaymentRule_PartnerSteamID }, + { "bundle_revenue_share", kPaymentRule_Bundle }, + }; + + for ( int i = 0; i < ARRAYSIZE( s_Lookup ); i++ ) + { + KeyValues *pKVKey = pKVRule->FindKey( s_Lookup[i].m_pszStr ); + if ( !pKVKey ) + continue; + + *out_pePaymentRuleType = s_Lookup[i].m_eRuleType; + *out_pRevenueShare = atof( pKVKey->GetString() ) * 100.0; // KeyValues doesn't support parsing a string as a double-precision value, so we do it by hand + return true; + } + + return false; +} +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Purpose: CEconItemSeriesDefinition +//----------------------------------------------------------------------------- +CEconItemSeriesDefinition::CEconItemSeriesDefinition( void ) + : m_nValue( INT_MAX ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Copy constructor +//----------------------------------------------------------------------------- +CEconItemSeriesDefinition::CEconItemSeriesDefinition( const CEconItemSeriesDefinition &that ) +{ + ( *this ) = that; +} + + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CEconItemSeriesDefinition &CEconItemSeriesDefinition::operator=( const CEconItemSeriesDefinition &rhs ) +{ + m_nValue = rhs.m_nValue; + m_strName = rhs.m_strName; + + return *this; +} + + +//----------------------------------------------------------------------------- +// Purpose: Initialize the quality definition +// Input: pKVQuality - The KeyValues representation of the quality +// schema - The overall item schema for this attribute +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSeriesDefinition::BInitFromKV( KeyValues *pKVSeries, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + + m_nValue = pKVSeries->GetInt( "value", -1 ); + m_strName = pKVSeries->GetName(); + + m_strLockKey = pKVSeries->GetString( "loc_key" ); + m_strUiFile = pKVSeries->GetString( "ui" ); + + // Check for required fields + SCHEMA_INIT_CHECK( + NULL != pKVSeries->FindKey( "value" ), + "Quality definition %s: Missing required field \"value\"", pKVSeries->GetName() ); + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CEconItemQualityDefinition::CEconItemQualityDefinition( void ) +: m_nValue( INT_MAX ) +, m_bCanSupportSet( false ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Copy constructor +//----------------------------------------------------------------------------- +CEconItemQualityDefinition::CEconItemQualityDefinition( const CEconItemQualityDefinition &that ) +{ + (*this) = that; +} + + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CEconItemQualityDefinition &CEconItemQualityDefinition::operator=( const CEconItemQualityDefinition &rhs ) +{ + m_nValue = rhs.m_nValue; + m_strName = rhs.m_strName; + m_bCanSupportSet = rhs.m_bCanSupportSet; + + return *this; +} + + +//----------------------------------------------------------------------------- +// Purpose: Initialize the quality definition +// Input: pKVQuality - The KeyValues representation of the quality +// schema - The overall item schema for this attribute +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemQualityDefinition::BInitFromKV( KeyValues *pKVQuality, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + + m_nValue = pKVQuality->GetInt( "value", -1 ); + m_strName = pKVQuality->GetName(); + m_bCanSupportSet = pKVQuality->GetBool( "canSupportSet" ); +#ifdef GC_DLL + m_strHexColor = pKVQuality->GetString( "hexColor" ); +#endif // GC_DLL + + // Check for required fields + SCHEMA_INIT_CHECK( + NULL != pKVQuality->FindKey( "value" ), + "Quality definition %s: Missing required field \"value\"", pKVQuality->GetName() ); + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + return SCHEMA_INIT_SUCCESS(); +#endif // GC_DLL + + // Check for data consistency + SCHEMA_INIT_CHECK( + 0 != Q_stricmp( GetName(), "any" ), + "Quality definition any: The quality name \"any\" is a reserved keyword and cannot be used." ); + + SCHEMA_INIT_CHECK( + m_nValue != k_unItemQuality_Any, + "Quality definition %s: Invalid value (%d). It is reserved for Any", GetName(), k_unItemQuality_Any ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// CEconItemRarityDefinition +//----------------------------------------------------------------------------- +CEconItemRarityDefinition::CEconItemRarityDefinition( void ) + : m_nValue( INT_MAX ) + , m_nLootlistWeight( 0 ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize the rarity definition +//----------------------------------------------------------------------------- +bool CEconItemRarityDefinition::BInitFromKV( KeyValues *pKVRarity, KeyValues *pKVRarityWeights, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + m_nValue = pKVRarity->GetInt( "value", -1 ); + m_strName = pKVRarity->GetName(); + m_strLocKey = pKVRarity->GetString( "loc_key" ); + m_strWepLocKey = pKVRarity->GetString( "loc_key_weapon" ); + + m_iAttribColor = GetAttribColorIndexForName( pKVRarity->GetString( "color" ) ); + m_strDropSound = pKVRarity->GetString( "drop_sound" ); + m_strNextRarity = pKVRarity->GetString( "next_rarity" ); // Not required. + +#ifdef GC_DLL + if ( pKVRarityWeights ) + { + m_nLootlistWeight = pKVRarityWeights->GetInt( m_strName, 0 ); + } +#endif + // + + // Check for required fields + SCHEMA_INIT_CHECK( + NULL != pKVRarity->FindKey( "value" ), + "Rarity definition %s: Missing required field \"value\"", pKVRarity->GetName() ); + + SCHEMA_INIT_CHECK( + NULL != pKVRarity->FindKey( "loc_key" ), + "Rarity definition %s: Missing required field \"loc_key\"", pKVRarity->GetName() ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconColorDefinition::BInitFromKV( KeyValues *pKVColor, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + m_strName = pKVColor->GetName(); + m_strColorName = pKVColor->GetString( "color_name" ); +#ifdef GC_DLL + m_strHexColor = pKVColor->GetString( "hex_color" ); +#endif // GC_DLL + + SCHEMA_INIT_CHECK( + !m_strColorName.IsEmpty(), + "Quality definition %s: missing \"color_name\"", GetName() ); + +#ifdef GC_DLL + SCHEMA_INIT_CHECK( + !m_strHexColor.IsEmpty(), + "Quality definition %s: missing \"hex_color\"", GetName() ); +#endif // GC_DLL + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CEconItemSetDefinition::CEconItemSetDefinition( void ) + : m_pszName( NULL ) + , m_pszLocalizedName( NULL ) + , m_iBundleItemDef( INVALID_ITEM_DEF_INDEX ) + , m_bIsHiddenSet( false ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Copy constructor +//----------------------------------------------------------------------------- +CEconItemSetDefinition::CEconItemSetDefinition( const CEconItemSetDefinition &that ) +{ + (*this) = that; +} + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CEconItemSetDefinition &CEconItemSetDefinition::operator=( const CEconItemSetDefinition &other ) +{ + m_pszName = other.m_pszName; + m_pszLocalizedName = other.m_pszLocalizedName; + m_iItemDefs = other.m_iItemDefs; + m_iAttributes = other.m_iAttributes; + m_iBundleItemDef = other.m_iBundleItemDef; + m_bIsHiddenSet = other.m_bIsHiddenSet; + + return *this; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CEconItemSetDefinition::BInitFromKV( KeyValues *pKVItemSet, CUtlVector<CUtlString> *pVecErrors ) +{ + m_pszName = pKVItemSet->GetName(); + + m_iBundleItemDef = INVALID_ITEM_DEF_INDEX; + const char *pszBundleName = pKVItemSet->GetString( "store_bundle" ); + if ( pszBundleName && pszBundleName[0] ) + { + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszBundleName ); + if ( pDef ) + { + m_iBundleItemDef = pDef->GetDefinitionIndex(); + } + + SCHEMA_INIT_CHECK( + pDef != NULL, + "Item set %s: Bundle definition \"%s\" was not found", m_pszName, pszBundleName ); + } + + m_pszLocalizedName = pKVItemSet->GetString( "name", NULL ); + m_bIsHiddenSet = pKVItemSet->GetBool( "is_hidden_set", false ); + + KeyValues *pKVItems = pKVItemSet->FindKey( "items" ); + if ( pKVItems ) + { + FOR_EACH_SUBKEY( pKVItems, pKVItem ) + { + const char *pszName = pKVItem->GetName(); + + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); + + SCHEMA_INIT_CHECK( + pDef != NULL, + "Item set %s: Item definition \"%s\" was not found", m_pszName, pszName ); + + const item_definition_index_t unDefIndex = pDef->GetDefinitionIndex(); + + SCHEMA_INIT_CHECK( + !m_iItemDefs.IsValidIndex( m_iItemDefs.Find( unDefIndex ) ), + "Item set %s: item definition \"%s\" appears multiple times", m_pszName, pszName ); + SCHEMA_INIT_CHECK( + !pDef->GetItemSetDefinition(), + "Item set %s: item definition \"%s\" specified in multiple item sets", m_pszName, pszName ); + + m_iItemDefs.AddToTail( unDefIndex ); + pDef->SetItemSetDefinition( this ); + + // FIXME: hack to work around crafting item criteria + pDef->GetRawDefinition()->SetString( "item_set", m_pszName ); + } + } + + KeyValues *pKVAttributes = pKVItemSet->FindKey( "attributes" ); + if ( pKVAttributes ) + { + FOR_EACH_SUBKEY( pKVAttributes, pKVAttribute ) + { + const char *pszName = pKVAttribute->GetName(); + + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( pszName ); + SCHEMA_INIT_CHECK( + pAttrDef != NULL, + "Item set %s: Attribute definition \"%s\" was not found", m_pszName, pszName ); + SCHEMA_INIT_CHECK( + pAttrDef->BIsSetBonusAttribute(), + "Item set %s: Attribute definition \"%s\" is not a set bonus attribute", m_pszName, pszName ); + + int iIndex = m_iAttributes.AddToTail(); + m_iAttributes[iIndex].m_iAttribDefIndex = pAttrDef->GetDefinitionIndex(); + m_iAttributes[iIndex].m_flValue = pKVAttribute->GetFloat( "value" ); + } + } + + // Sanity check. + SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, + "Item set %s: Set contains no localized name", m_pszName ); + SCHEMA_INIT_CHECK( m_iItemDefs.Count() > 0, + "Item set %s: Set contains no items", m_pszName ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CEconItemSetDefinition::IterateAttributes( class IEconItemAttributeIterator *pIterator ) const +{ + FOR_EACH_VEC( m_iAttributes, i ) + { + const itemset_attrib_t& itemsetAttrib = m_iAttributes[i]; + + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( itemsetAttrib.m_iAttribDefIndex ); + if ( !pAttrDef ) + continue; + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + // We know (and assert) that we only need 32 bits of data to store this attribute + // data. We don't know anything about the type but we'll let the type handle it + // below. + attribute_data_union_t value; + value.asFloat = itemsetAttrib.m_flValue; + + if ( !pAttrType->OnIterateAttributeValue( pIterator, pAttrDef, value ) ) + return; + } +} + +//----------------------------------------------------------------------------- +CEconItemCollectionDefinition::CEconItemCollectionDefinition( void ) + : m_pszName( NULL ) + , m_pszLocalizedName( NULL ) + , m_pszLocalizedDesc( NULL ) + , m_iRarityMin( k_unItemRarity_Any ) + , m_iRarityMax( k_unItemRarity_Any ) +{ +} + +//----------------------------------------------------------------------------- +// + +//----------------------------------------------------------------------------- +static int SortCollectionByRarity( item_definition_index_t const *a, item_definition_index_t const *b ) +{ + Assert( a ); + Assert( *a ); + Assert( b ); + Assert( *b ); + + CEconItemDefinition *pItemA = GetItemSchema()->GetItemDefinition( *a ); + CEconItemDefinition *pItemB = GetItemSchema()->GetItemDefinition( *b ); + + if ( !pItemA || !pItemB ) + { + AssertMsg( 0, "ItemDef Doesn't exist for sorting" ); + return 1; + } + + // If same Rarity, leave in current position? + if ( pItemA->GetRarity() == pItemB->GetRarity() && pItemA->GetCustomPainkKitDefinition() && pItemB->GetCustomPainkKitDefinition() ) + { +#ifdef CLIENT_DLL + // Sort by localized name + // paintkits sort by paintkit name + auto paintkitA = pItemA->GetCustomPainkKitDefinition(); + auto paintkitB = pItemB->GetCustomPainkKitDefinition(); + auto paintkitALocName = paintkitA->GetLocalizeName(); + auto paintkitBLocName = paintkitB->GetLocalizeName(); + auto pkALocalized = g_pVGuiLocalize->Find( paintkitALocName ); + auto pkBLocalized = g_pVGuiLocalize->Find( paintkitBLocName ); + if ( pkALocalized ) + { + if ( pkBLocalized ) + { + return V_wcscmp( pkALocalized, pkBLocalized ); + } + else + { + return -1; + } + } + else + { + return pkBLocalized ? 1 : -1; + } +#else + return 0; +#endif + } + + return ( pItemA->GetRarity() > pItemB->GetRarity() ) ? -1 : 1; +} + + +//----------------------------------------------------------------------------- +bool CEconItemCollectionDefinition::BInitFromKV( KeyValues *pKVPItemCollection, CUtlVector<CUtlString> *pVecErrors ) +{ + m_pszName = pKVPItemCollection->GetName(); + + m_pszLocalizedName = pKVPItemCollection->GetString( "name", NULL ); + m_pszLocalizedDesc = pKVPItemCollection->GetString( "description", NULL ); + + m_bIsReferenceCollection = pKVPItemCollection->GetBool( "is_reference_collection", false ); + + KeyValues *pKVItems = pKVPItemCollection->FindKey( "items" ); + + // Create a 'lootlist' from this collection + KeyValues *pCollectionLootList = NULL; + bool bIsLootList = false; + if ( !m_bIsReferenceCollection ) + { + pCollectionLootList = new KeyValues( m_pszName ); + } + + if ( pKVItems ) + { + // Traverse rarity items and set rarity + // Create a lootlist if applicable + FOR_EACH_TRUE_SUBKEY( pKVItems, pKVRarity ) + { + bIsLootList = true; + // Get the Rarity Value + const CEconItemRarityDefinition *pRarity = GetItemSchema()->GetRarityDefinitionByName( pKVRarity->GetName() ); + SCHEMA_INIT_CHECK( pRarity != NULL, "Item collection %s: Rarity type \"%s\" was not found", m_pszName, pKVRarity->GetName() ); + + // Create a lootlist + if ( !m_bIsReferenceCollection ) + { + CFmtStr lootlistname( "%s_%s", m_pszName, pRarity->GetName() ); + const char *pszName = V_strdup( lootlistname.Get() ); + pKVRarity->SetInt( "rarity", pRarity->GetDBValue() ); + SCHEMA_INIT_CHECK( GetItemSchema()->BInsertLootlist( pszName, pKVRarity, pVecErrors ), "Invalid collection lootlist %s", pszName ); + KeyValues *pTempRarityKey = pKVRarity->FindKey( "rarity" ); + Assert( pTempRarityKey ); + pKVRarity->RemoveSubKey( pTempRarityKey ); + pTempRarityKey->deleteThis(); + pCollectionLootList->SetInt( pszName, pRarity->GetLootlistWeight() ); + } + + // Items in the Rarity + FOR_EACH_VALUE( pKVRarity, pKVItem ) + { + const char *pszName = pKVItem->GetName(); + + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); + + SCHEMA_INIT_CHECK( + pDef != NULL, + "Item set %s: Item definition \"%s\" was not found", m_pszName, pszName ); + + const item_definition_index_t unDefIndex = pDef->GetDefinitionIndex(); + + SCHEMA_INIT_CHECK( + !m_iItemDefs.IsValidIndex( m_iItemDefs.Find( unDefIndex ) ), + "Item Collection %s: item definition \"%s\" appears multiple times", m_pszName, pszName ); + + m_iItemDefs.AddToTail( unDefIndex ); + + // Collection Reference + if ( !m_bIsReferenceCollection ) + { + SCHEMA_INIT_CHECK( + !pDef->GetItemCollectionDefinition(), + "Item Collection %s: item definition \"%s\" specified in multiple item sets", m_pszName, pszName ); + pDef->SetItemCollectionDefinition( this ); + } + + // Item Rarity + pDef->SetRarity( pRarity->GetDBValue() ); + } + } + + // Loose Items + FOR_EACH_VALUE( pKVItems, pKVItem ) + { + const char *pszName = pKVItem->GetName(); + + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); + + SCHEMA_INIT_CHECK( + pDef != NULL, + "Item set %s: Item definition \"%s\" was not found", m_pszName, pszName ); + + const item_definition_index_t unDefIndex = pDef->GetDefinitionIndex(); + + SCHEMA_INIT_CHECK( + !m_iItemDefs.IsValidIndex( m_iItemDefs.Find( unDefIndex ) ), + "Item Collection %s: item definition \"%s\" appears multiple times", m_pszName, pszName ); + + m_iItemDefs.AddToTail( unDefIndex ); + + if ( !m_bIsReferenceCollection ) + { + SCHEMA_INIT_CHECK( + !pDef->GetItemCollectionDefinition(), + "Item Collection %s: item definition \"%s\" specified in multiple item sets", m_pszName, pszName ); + pDef->SetItemCollectionDefinition( this ); + } + } + + // Sort by Rarity + m_iItemDefs.Sort( &SortCollectionByRarity ); + } + + if ( !m_bIsReferenceCollection && bIsLootList ) + { + // Insert collection lootlist + GetItemSchema()->BInsertLootlist( m_pszName, pCollectionLootList, pVecErrors ); + } + + if ( pCollectionLootList ) + { + pCollectionLootList->deleteThis(); + } + + // Sorted high to low + m_iRarityMax = GetItemSchema()->GetItemDefinition( m_iItemDefs[ 0 ] )->GetRarity(); + m_iRarityMin = GetItemSchema()->GetItemDefinition( m_iItemDefs[ m_iItemDefs.Count() - 1] )->GetRarity(); + // Verify that there is no gaps in the Rarity (would cause crafting problems and makes no sense) + + if ( !m_bIsReferenceCollection ) + { + int iRarityVerify = m_iRarityMax; + FOR_EACH_VEC( m_iItemDefs, i ) + { + int iNextRarity = GetItemSchema()->GetItemDefinition( m_iItemDefs[i] )->GetRarity(); + SCHEMA_INIT_CHECK( iRarityVerify - iNextRarity <= 1, "Items in Collection %s: Have a gap in rarity tiers", m_pszName ); + iRarityVerify = iNextRarity; + } + } + + // Sanity check. + SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, + "Item Collection %s: Collection contains no localized name", m_pszName ); + SCHEMA_INIT_CHECK( m_pszLocalizedDesc != NULL, + "Item Collection %s: Collection contains no localized description", m_pszName ); + SCHEMA_INIT_CHECK( m_iItemDefs.Count() > 0, + "Item Collection %s: Collection contains no items", m_pszName ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// CEconItemPaintKitDefinition +//----------------------------------------------------------------------------- +CEconItemPaintKitDefinition::CEconItemPaintKitDefinition( void ) + : m_pszName( NULL ) +{ +} + +//----------------------------------------------------------------------------- +CEconItemPaintKitDefinition::~CEconItemPaintKitDefinition( void ) +{ + FOR_EACH_VEC( m_vecPaintKitWearKVP, i ) + { + if ( m_vecPaintKitWearKVP[i] ) + { + m_vecPaintKitWearKVP[i]->deleteThis(); + } + } + + m_vecPaintKitWearKVP.Purge(); +} + +bool VerifyPaintKitComposite( KeyValues *pKVWearInputItems, const char* pName, int iWearLevel, CUtlVector<CUtlString> *pVecErrors ) +{ + SCHEMA_INIT_CHECK( pKVWearInputItems != NULL, "Paint Kit %s: Does not contain Wear Level %d", pName, iWearLevel ); +#ifdef CLIENT_DLL + int w = 1; + int h = 1; + int seed = 0; + ITextureCompositor* pWeaponSkinBaseCompositor = NULL; + + SafeAssign( &pWeaponSkinBaseCompositor, materials->NewTextureCompositor( w, h, pName, TF_TEAM_RED, seed, pKVWearInputItems, TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) ); + SCHEMA_INIT_CHECK( pWeaponSkinBaseCompositor != NULL, "Could Not Create Weapon Skin Compositor for [%s][Wear %d][Team Red]", pName, iWearLevel); + SafeRelease( &pWeaponSkinBaseCompositor ); + + SafeAssign( &pWeaponSkinBaseCompositor, materials->NewTextureCompositor( w, h, pName, TF_TEAM_BLUE, seed, pKVWearInputItems, TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) ); + SCHEMA_INIT_CHECK( pWeaponSkinBaseCompositor != NULL, "Could Not Create Weapon Skin Compositor for [%s][Wear %d][Team BLUE]", pName, iWearLevel ); + SafeRelease( &pWeaponSkinBaseCompositor ); +#endif // CLIENT_DLL + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +bool CEconItemPaintKitDefinition::BInitFromKV( KeyValues *pKVPItemPaintKit, CUtlVector<CUtlString> *pVecErrors ) +{ + m_pszName = pKVPItemPaintKit->GetName(); + m_pszLocalizedName = m_pszName; // localization key is same as paintkit name for ease of generation + + SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, "Paint Kit %s: PaintKit contains no localized name", m_pszName ); + + KeyValues *pKVWearInputItems = NULL; + + pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_1", false ); + SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 1, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 1 ); + m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); + + pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_2", false ); + SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 2, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 2 ); + m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); + + pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_3", false ); + SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 3, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 3 ); + m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); + + pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_4", false ); + SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 4, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 4 ); + m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); + + pKVWearInputItems = pKVPItemPaintKit->FindKey( "wear_level_5", false ); + SCHEMA_INIT_CHECK( VerifyPaintKitComposite( pKVWearInputItems, m_pszName, 5, pVecErrors ), "Could Not Create Weapon Skin Compositor for [%s][Wear %d]", m_pszName, 5 ); + m_vecPaintKitWearKVP.AddToTail( pKVWearInputItems->MakeCopy() ); + + return SCHEMA_INIT_SUCCESS(); +} + +KeyValues *CEconItemPaintKitDefinition::GetPaintKitWearKV( int nWear ) +{ + // Wear is 1-5 but this vec is 0-4 + int iIndex = nWear - 1; + + if ( !m_vecPaintKitWearKVP.IsValidIndex( iIndex ) ) + { + iIndex = 0; + Assert( m_vecPaintKitWearKVP.IsValidIndex( iIndex ) ); + DevMsg( "Invalid Paint Kit or Paint Kit Wear Entry (%s) Wear (%d).", m_pszName, nWear ); + return NULL; + } + + return m_vecPaintKitWearKVP[iIndex]; +} + +//----------------------------------------------------------------------------- +// CEconOperationDefinition +//----------------------------------------------------------------------------- +CEconOperationDefinition::CEconOperationDefinition( void ) + : m_pszName( NULL ) + , m_unRequiredItemDefIndex( INVALID_ITEM_DEF_INDEX ) + , m_unGatewayItemDefIndex( INVALID_ITEM_DEF_INDEX ) +{ +} + +//----------------------------------------------------------------------------- +CEconOperationDefinition::~CEconOperationDefinition( void ) +{ + if ( m_pKVItem ) + m_pKVItem->deleteThis(); + m_pKVItem = NULL; +} + +//----------------------------------------------------------------------------- +bool CEconOperationDefinition::BInitFromKV( KeyValues *pKVPOperation, CUtlVector<CUtlString> *pVecErrors ) +{ + m_unOperationID = V_atoi( pKVPOperation->GetName() ); + + m_pszName = pKVPOperation->GetString( "name", NULL ); + SCHEMA_INIT_CHECK( m_pszName != NULL, "OperationDefinition %s does not have 'name'", m_pszName ); + + // initialize required item def index if we specified one + const char *pszRequiredName = pKVPOperation->GetString( "required_item_name", NULL ); + if ( pszRequiredName ) + { + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszRequiredName ); + SCHEMA_INIT_CHECK( pDef != NULL, "OperationDefinition couldn't find item def from required name '%s'", pszRequiredName ); + + m_unRequiredItemDefIndex = pDef->GetDefinitionIndex(); + } + + const char *pszGatewayItemName = pKVPOperation->GetString( "gateway_item_name", NULL ); + if ( pszGatewayItemName ) + { + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszGatewayItemName ); + SCHEMA_INIT_CHECK( pDef != NULL, "OperationDefinition couldn't find item def from gateway name '%s'", pszGatewayItemName ); + + m_unGatewayItemDefIndex = pDef->GetDefinitionIndex(); + } + + if ( m_unGatewayItemDefIndex != INVALID_ITEM_DEF_INDEX ) + { + SCHEMA_INIT_CHECK( m_unRequiredItemDefIndex != INVALID_ITEM_DEF_INDEX, "If a gateway item is set, a required item must be set! Mismatch in %d", m_unOperationID ); + } + + const char *pszOperationStartDate = pKVPOperation->GetString( "operation_start_date", NULL ); + SCHEMA_INIT_CHECK( pszOperationStartDate != NULL, "OperationDefinition %s does not have 'operation_start_date'", m_pszName ); + m_OperationStartDate = ( pszOperationStartDate && pszOperationStartDate[0] ) + ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszOperationStartDate ) + : RTime32(0); + + const char *pszDropEndDate = pKVPOperation->GetString( "stop_giving_to_player_date", NULL ); + SCHEMA_INIT_CHECK( pszDropEndDate != NULL, "OperationDefinition %s does not have 'stop_giving_to_player_date'", m_pszName ); + m_StopGivingToPlayerDate = ( pszDropEndDate && pszDropEndDate[0] ) + ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszDropEndDate ) + : RTime32(0); + + const char *pszOperationEndDate = pKVPOperation->GetString( "stop_adding_to_queue_date", NULL ); + SCHEMA_INIT_CHECK( pszOperationEndDate != NULL, "OperationDefinition %s does not have 'stop_adding_to_queue_date'", m_pszName ); + m_StopAddingToQueueDate = ( pszOperationEndDate && pszOperationEndDate[0] ) + ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszOperationEndDate ) + : RTime32(0); + + m_pszQuestLogResFile = pKVPOperation->GetString( "quest_log_res_file", NULL ); + m_pszQuestListResFile = pKVPOperation->GetString( "quest_list_res_file", NULL ); + + m_pszOperationLootList = pKVPOperation->GetString( "operation_lootlist" ); + SCHEMA_INIT_CHECK( m_pszOperationLootList != NULL, "OperationDefinition %s does not have 'operation_lootlist'", m_pszName ); + + m_bIsCampaign = pKVPOperation->GetBool( "is_campaign" ); + m_unMaxDropCount = pKVPOperation->GetInt( "max_drop_count" ); + +#ifdef GC_DLL + + m_rtQueueFreqMin = pKVPOperation->GetFloat( "queue_freq_min" ) * k_nSecondsPerHour; // converts specified hours to seconds + SCHEMA_INIT_CHECK( m_rtQueueFreqMin != 0.f, "OperationDefinition %s does not have 'queue_freq_min'", m_pszName ); + + m_rtQueueFreqMax = pKVPOperation->GetFloat( "queue_freq_max" ) * k_nSecondsPerHour; // converts specified hours to seconds + SCHEMA_INIT_CHECK( m_rtQueueFreqMax != 0.f, "OperationDefinition %s does not have 'queue_freq_max'", m_pszName ); + + SCHEMA_INIT_CHECK( m_rtQueueFreqMin <= m_rtQueueFreqMax, "OperationDefinition %s 'queue_freq_min' must be less than 'queue_freq_max'", m_pszName ); + + m_rtDropFreqMin = pKVPOperation->GetFloat( "drop_freq_min" ) * k_nSecondsPerHour; // converts specified hours to seconds + SCHEMA_INIT_CHECK( m_rtDropFreqMin != 0.f, "OperationDefinition %s does not have 'drop_freq_min'", m_pszName ); + + m_rtDropFreqMax = pKVPOperation->GetFloat( "drop_freq_max" ) * k_nSecondsPerHour; // converts specified hours to seconds + SCHEMA_INIT_CHECK( m_rtDropFreqMax != 0.f, "OperationDefinition %s does not have 'drop_freq_max'", m_pszName ); + + SCHEMA_INIT_CHECK( m_rtDropFreqMin <= m_rtDropFreqMax, "OperationDefinition %s 'drop_freq_min' must be less than 'drop_freq_max'", m_pszName ); + + m_unSeed = pKVPOperation->GetInt( "seed_drops" ); + m_unMaxHeldDrops = pKVPOperation->GetInt( "max_held_drops" ); + m_nMaxQueueCount = pKVPOperation->GetInt( "max_queue_count" ); + m_unMaxDropPerThink = pKVPOperation->GetInt( "max_drop_per_think", 1 ); + + m_pszContractRewardLootlist[ REWARD_CASE ] = pKVPOperation->GetString( "contract_reward_case_lootlist" ); + m_pszContractRewardLootlist[ REWARD_WEAPON ] = pKVPOperation->GetString( "contract_reward_weapon_lootlist" ); + +#endif // GC_DLL + + m_pKVItem = pKVPOperation->MakeCopy(); + + return SCHEMA_INIT_SUCCESS(); +} + +#ifdef GC_DLL +#ifdef STAGING_ONLY + GCConVar gc_quick_operation_drop_name( "gc_quick_operation_drop_name", "" ); + GCConVar gc_quick_operation_drop_rate( "gc_quick_operation_drop_rate", "10" ); +#endif // STAGING_ONLY + +RTime32 CEconOperationDefinition::GetMinQueueFreq() const +{ +#ifdef STAGING_ONLY + if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) + { + return gc_quick_operation_drop_rate.GetInt(); + } +#endif + + return m_rtQueueFreqMin; +} + +RTime32 CEconOperationDefinition::GetMaxQueueFreq() const +{ +#ifdef STAGING_ONLY + if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) + { + return gc_quick_operation_drop_rate.GetInt() + 2; + } +#endif + + return m_rtQueueFreqMax; +} + +RTime32 CEconOperationDefinition::GetMinDropFreq() const +{ +#ifdef STAGING_ONLY + if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) + { + return gc_quick_operation_drop_rate.GetInt(); + } +#endif + + return m_rtDropFreqMin; +} + +RTime32 CEconOperationDefinition::GetMaxDropFreq() const +{ +#ifdef STAGING_ONLY + if ( Q_stricmp( gc_quick_operation_drop_name.GetString(), m_pszName ) == 0 ) + { + return gc_quick_operation_drop_rate.GetInt() + 2; + } +#endif + + return m_rtDropFreqMax; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BCommonInitPropertyGeneratorsFromKV( const char *pszContext, CUtlVector<const IEconItemPropertyGenerator *> *out_pvecGenerators, KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ) +{ + // Forward declaration so factory functions can be wherever. + IEconItemPropertyGenerator *CreateChangeQualityGenerator( KeyValues *, CUtlVector<CUtlString> * ); + IEconItemPropertyGenerator *CreateRandomEvenChanceAttrGenerator( KeyValues *, CUtlVector<CUtlString> * ); + IEconItemPropertyGenerator *CreateUniformLineItemLootListGenerator( KeyValues *, CUtlVector<CUtlString> * ); + IEconItemPropertyGenerator *CreateDynamicAttrsGenerator( KeyValues *, CUtlVector<CUtlString> * ); + + // "Factory". + struct econ_item_property_generator_factory_entry_t + { + const char *m_pszGeneratorName; + IEconItemPropertyGenerator * (* m_funcCreateGeneratorInstance)( KeyValues *, CUtlVector<CUtlString> * ); + }; + + static econ_item_property_generator_factory_entry_t s_Generators[] = + { + { "change_quality", &CreateChangeQualityGenerator }, + { "random_even_chance_attr", &CreateRandomEvenChanceAttrGenerator }, + { "uniform_line_item_loot_list", &CreateUniformLineItemLootListGenerator }, + { "dynamic_attrs", &CreateDynamicAttrsGenerator }, + }; + + Assert( out_pvecGenerators ); + Assert( pVecErrors ); + + // No input data means "we succeeded here". + if ( !pKV ) + return true; + + // Handle each generator one at a time. We'll try to initialize the whole set to get as many + // errors as possible and then return accumulated errors. + FOR_EACH_SUBKEY( pKV, pKVGenerator ) + { + const char *pszGeneratorName = pKVGenerator->GetName(); + + IEconItemPropertyGenerator *pGenerator = NULL; + for ( const auto& gen : s_Generators ) + { + if ( Q_stricmp( gen.m_pszGeneratorName, pszGeneratorName ) != 0 ) + continue; + + pGenerator = (*gen.m_funcCreateGeneratorInstance)( pKVGenerator, pVecErrors ); + SCHEMA_INIT_CHECK( pGenerator != nullptr, "%s: property generator \"%s\" failed to initialize.\n", pszContext, pszGeneratorName ); + + out_pvecGenerators->AddToTail( pGenerator ); + break; + } + + // Make sure we found a way to create this instance. + SCHEMA_INIT_CHECK( pGenerator != nullptr, "%s: unknown type for property generator \"%s\".\n", pszContext, pszGeneratorName ); + } + + return SCHEMA_INIT_SUCCESS(); +} +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Dtor +//----------------------------------------------------------------------------- +CEconLootListDefinition::~CEconLootListDefinition( void ) +{ +#ifdef GC_DLL + m_RandomAttribs.PurgeAndDeleteElements(); + m_PropertyGenerators.PurgeAndDeleteElements(); +#endif +} + +#ifdef GC_DLL +bool CEconLootListDefinition::AddRandomAtrributes( KeyValues *pRandomAttributesKV, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors /*= NULL*/ ) +{ + const char *pszAttrName = pRandomAttributesKV->GetName(); + + // We've found the random attribute block. Parse it. + random_attrib_t *pRandomAttr = pschema.CreateRandomAttribute( m_pszName, pRandomAttributesKV, pVecErrors ); + + SCHEMA_INIT_CHECK( + NULL != pRandomAttr, + CFmtStr( "Loot List %s: Failed to create random_attrib_t '%s'", m_pszName, pszAttrName ) ); + + m_RandomAttribs.AddToTail( pRandomAttr ); + + return true; +} + +bool CEconLootListDefinition::AddRandomAttributesFromTemplates( KeyValues *pRandomAttributesKV, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors /*= NULL*/ ) +{ + const char *pszAttrName = pRandomAttributesKV->GetName(); + + // try to find attr by template name + random_attrib_t *pRandomAttrTemplate = pschema.GetRandomAttributeTemplateByName( pszAttrName ); + + SCHEMA_INIT_CHECK( + NULL != pRandomAttrTemplate, + CFmtStr( "Loot List %s: Couldn't find random_attrib_t '%s' from attribute_templates", m_pszName, pszAttrName ) ); + + // craete a copy of the template and add to the list + random_attrib_t *pRandomAttr = new random_attrib_t; + *pRandomAttr = *pRandomAttrTemplate; + m_RandomAttribs.AddToTail( pRandomAttr ); + + return true; +} +#endif // GC_DLL +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static const char *g_pszDefaultRevolvingLootListHeader = "#Econ_Revolving_Loot_List"; + +bool CEconLootListDefinition::BInitFromKV( KeyValues *pKVLootList, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors ) +{ + m_pszName = pKVLootList->GetName(); + m_pszLootListHeader = g_pszDefaultRevolvingLootListHeader; + m_pszLootListFooter = NULL; + m_pszCollectionReference = NULL; + m_bPublicListContents = true; +#ifdef GC_DLL + m_iNoDupesIterations = -1; // disable no-dupes functionality by default + m_unRarity = pKVLootList->GetInt( "rarity", k_unItemRarity_Any ); +#endif // GC_DLL + bool bCollectionLootList = false; + + FOR_EACH_SUBKEY( pKVLootList, pKVListItem ) + { + const char *pszName = pKVListItem->GetName(); + + if ( !Q_strcmp( pszName, "loot_list_header_desc" ) ) + { + // Make sure we didn't specify multiple entries. + SCHEMA_INIT_CHECK( + g_pszDefaultRevolvingLootListHeader == m_pszLootListHeader, + "Loot list %s: Multiple header descriptions specified", m_pszName ); + + m_pszLootListHeader = pKVListItem->GetString(); + + SCHEMA_INIT_CHECK( + NULL != m_pszLootListHeader, + "Loot list %s: Invalid header description specified", m_pszName ); + + continue; + } + else if ( !Q_strcmp( pszName, "loot_list_footer_desc" ) ) + { + // Make sure we didn't specify multiple entries. + SCHEMA_INIT_CHECK( + NULL == m_pszLootListFooter, + "Loot list %s: Multiple footer descriptions specified", m_pszName ); + + m_pszLootListFooter = pKVListItem->GetString(); + + SCHEMA_INIT_CHECK( + NULL != m_pszLootListFooter, + "Loot list %s: Invalid header description specified", m_pszName ); + + continue; + } + else if ( !Q_strcmp( pszName, "loot_list_collection" ) ) + { + // Set name as the collection lootlist name + pszName = pKVListItem->GetString(); + m_pszCollectionReference = pszName; + bCollectionLootList = true; + } + else if ( !Q_strcmp( pszName, "hide_lootlist" ) ) + { + m_bPublicListContents = !pKVListItem->GetBool( nullptr, true ); + continue; + } + else if ( !Q_strcmp( pszName, "rarity" ) ) + { + // already parsed up top + continue; + } +#ifdef GC_DLL + else if ( !Q_strcmp( pszName, "random_attributes" ) ) + { + AddRandomAtrributes( pKVListItem, pschema, pVecErrors ); + continue; + } + else if ( !Q_strcmp( pszName, "attribute_templates" ) ) + { + FOR_EACH_SUBKEY( pKVListItem, pKVAttributeTemplate ) + { + if ( pKVAttributeTemplate->GetInt() == 0 ) + continue; + + bool bAdded = AddRandomAttributesFromTemplates( pKVAttributeTemplate, pschema, pVecErrors ); + SCHEMA_INIT_CHECK( bAdded, "Loot list %s: Failed to attribute_templates '%s'", m_pszName, pKVAttributeTemplate->GetName() ); + } + + continue; + } + else if ( !Q_strcmp( pszName, "public_list_contents" ) ) + { + m_bPublicListContents = pKVListItem->GetBool( nullptr, true ); + continue; + } + else if ( !Q_stricmp( pszName, "__no_dupes_iter_count" ) ) + { + m_iNoDupesIterations = pKVListItem->GetInt( nullptr, -1 ); + continue; + } + else if ( !Q_strcmp( pszName, "additional_drop" ) ) + { + float fChance = pKVListItem->GetFloat( "chance", 0.0f ); + bool bPremiumOnly = pKVListItem->GetBool( "premium_only", false ); + const char *pszLootList = pKVListItem->GetString( "loot_list", "" ); + const char *pszRequiredHoliday = pKVListItem->GetString( "required_holiday", NULL ); + const char *pszDropPerdiodStartDate = pKVListItem->GetString( "start_date", NULL ); + const char *pszDropPerdiodEndDate = pKVListItem->GetString( "end_date", NULL ); + + int iRequiredHolidayIndex = pszRequiredHoliday + ? EconHolidays_GetHolidayForString( pszRequiredHoliday ) + : kHoliday_None; + + RTime32 dropStartDate = ( pszDropPerdiodStartDate && pszDropPerdiodStartDate[0] ) + ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszDropPerdiodStartDate ) + : RTime32(0); // Default to the start of time + + // Check that if we convert back to a string, we get the same value + char rtimeBuf[k_RTimeRenderBufferSize]; + SCHEMA_INIT_CHECK( + pszDropPerdiodStartDate == NULL || Q_strcmp( CRTime::RTime32ToString( dropStartDate, rtimeBuf ), pszDropPerdiodStartDate ) == 0, + "Malformed start drop date \"%s\" for additional_drop in lootlist %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", pszDropPerdiodStartDate, m_pszName ); + + + RTime32 dropEndDate = ( pszDropPerdiodEndDate && pszDropPerdiodEndDate[0] ) + ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszDropPerdiodEndDate ) + : ~RTime32(0); // Default to the end of time + + // Check that if we convert back to a string, we get the same value + SCHEMA_INIT_CHECK( + pszDropPerdiodEndDate == NULL || Q_strcmp( CRTime::RTime32ToString( dropEndDate, rtimeBuf ), pszDropPerdiodEndDate ) == 0, + "Malformed end drop date \"%s\" for additional_drop in lootlist %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", pszDropPerdiodEndDate, m_pszName ); + + SCHEMA_INIT_CHECK( + fChance > 0.0f && fChance <= 1.0f, + "Loot list %s: Invalid \"additional_drop\" chance %.2f", m_pszName, fChance ); + + SCHEMA_INIT_CHECK( + pszLootList && pszLootList[0], + "Loot list %s: Missing \"additional_drop\" loot list name", m_pszName ); + + SCHEMA_INIT_CHECK( + (pszRequiredHoliday == NULL) == (iRequiredHolidayIndex == kHoliday_None), + "Loot list %s: Unknown or missing holiday \"%s\"", m_pszName, pszRequiredHoliday ? pszRequiredHoliday : "(null)" ); + + if ( pszLootList ) + { + const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pszLootList ); + SCHEMA_INIT_CHECK( + pLootListDef, + "Loot list %s: Invalid \"additional_drop\" loot list \"%s\"", m_pszName, pszLootList ); + + if ( pLootListDef ) + { + drop_period_t dropPeriod = { dropStartDate, dropEndDate }; + loot_list_additional_drop_t additionalDrop = { fChance, bPremiumOnly, pszLootList, iRequiredHolidayIndex, dropPeriod }; + m_AdditionalDrops.AddToTail( additionalDrop ); + } + } + continue; + } + else if ( !Q_strcmp( pszName, "property_generators" ) ) + { + SCHEMA_INIT_SUBSTEP( BCommonInitPropertyGeneratorsFromKV( m_pszName, &m_PropertyGenerators, pKVListItem, pVecErrors ) ); + continue; + } +#endif // GC_DLL + + int iDef = 0; + // First, see if we've got a loot list name, for embedded loot lists + int iIdx = 0; + if ( GetItemSchema()->GetLootListByName( pszName, &iIdx ) ) + { + // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index + iDef = 0 - 1 - iIdx; + } + else + { + // Not a loot list. See if it's an item index. Check the first character. + if ( pszName[0] >= '0' && pszName[0] <= '9' ) + { + iDef = atoi( pszName ); + } + else + { + // Not a number. See if we can find an item def with a matching name. + const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszName ); + if ( pDef ) + { + iDef = pDef->GetDefinitionIndex(); + } + + SCHEMA_INIT_CHECK( + pDef != NULL, + "Loot list %s: Item definition \"%s\" was not found", m_pszName, pszName ); + } + } + + // Default to the start dropping at the start of time and end dropping at the end of time + drop_period_t dropPeriod = { RTime32(0), ~RTime32(0) }; + + // Make sure we never put non-enabled items into loot lists + if ( iDef > 0 ) + { + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( iDef ); + SCHEMA_INIT_CHECK( + pItemDef != NULL, + "Loot list %s: Item definition index \"%s\" (%d) was not found", m_pszName, pszName, iDef ); + + static CSchemaAttributeDefHandle pAttribDef_StartDropDate( "start drop date" ); + static CSchemaAttributeDefHandle pAttribDef_EndDropDate( "end drop date" ); + + CAttribute_String value; + // Check for start drop date attribute on this item + if ( FindAttribute( pItemDef, pAttribDef_StartDropDate, &value ) ) + { + const char* pszStartDate = value.value().c_str(); + dropPeriod.m_DropStartDate = CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszStartDate ); + + // Check that if we convert back to a string, we get the same value + char rtimeBuf[k_RTimeRenderBufferSize]; + SCHEMA_INIT_CHECK( + Q_strcmp( CRTime::RTime32ToString( dropPeriod.m_DropStartDate, rtimeBuf ), pszStartDate ) == 0, + "Malformed start drop date \"%s\" for item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", + pszStartDate, pItemDef->GetDefinitionName() ); + } + + // Check for end drop date attribute on this item + if ( FindAttribute( pItemDef, pAttribDef_EndDropDate, &value ) ) + { + const char* pszEndDate = value.value().c_str(); + dropPeriod.m_DropEndDate = CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszEndDate ); + + // Check that if we convert back to a string, we get the same value + char rtimeBuf[k_RTimeRenderBufferSize]; + SCHEMA_INIT_CHECK( + Q_strcmp( CRTime::RTime32ToString( dropPeriod.m_DropEndDate, rtimeBuf ), pszEndDate ) == 0, + "Malformed end drop date \"%s\" for item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\"", pszEndDate, pItemDef->GetDefinitionName() ); + } + +#ifdef GC_DLL + if ( pItemDef ) + { + SCHEMA_INIT_CHECK( + true == pItemDef->BEnabled(), + "Loot list %s: Item definition \"%s\" (%d) isn't enabled, not allowed in loot lists", m_pszName, pItemDef->GetDefinitionName(), iDef ); + } +#endif // GC_DLL + } + + float fItemWeight = 0.f; +#ifdef GC_DLL + fItemWeight = bCollectionLootList ? 1.0f : pKVListItem->GetFloat(); + SCHEMA_INIT_CHECK( + fItemWeight > 0.0f, + "Loot list %s: Item definition index \"%s\" (%d) has invalid weight %.2f", m_pszName, pszName, iDef, fItemWeight ); +#endif // GC_DLL + + // Add this item + drop_item_t dropItem = { iDef, fItemWeight, dropPeriod }; + m_DropList.AddToTail( dropItem ); + } + +#ifdef GC_DLL + + int nNumTimeLimitedItems = 0; + FOR_EACH_VEC( m_DropList, i ) + { + // If either the start date or the end date is set, tally it up as a time limited item + if( m_DropList[i].m_dropPeriod.m_DropStartDate != RTime32(0) || m_DropList[i].m_dropPeriod.m_DropEndDate != ~RTime32(0) ) + { + ++nNumTimeLimitedItems; + } + } + + // Verify that at least one item in a lootlist does not have a drop period that limits when it can drop. + // This guarantees that we will always drop *something* + SCHEMA_INIT_CHECK( m_DropList.Count() > nNumTimeLimitedItems, "Lootlist \"%s\" is made up entirely of limited-time items! At least one must not be time-limited.", m_pszName ); +#endif + + return SCHEMA_INIT_SUCCESS(); +} + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static bool BContainsDuplicateItemDefs( const CUtlVector<CEconLootListDefinition::rolled_item_defs_t>& vecItemDefsA, const CUtlVector<CEconLootListDefinition::rolled_item_defs_t>& vecItemDefsB ) +{ + CUtlHashtable<const CEconItemDefinition *> hashItemDefs; + + auto BPopulateAndLookForDupes = [&] ( const CUtlVector<CEconLootListDefinition::rolled_item_defs_t>& vecItemDefs ) + { + for ( const auto& rolledItemDef : vecItemDefs ) + { + if ( hashItemDefs.HasElement( rolledItemDef.m_pItemDef ) ) + return true; + + hashItemDefs.Insert( rolledItemDef.m_pItemDef ); + } + + return false; + }; + + return BPopulateAndLookForDupes( vecItemDefsA ) + || BPopulateAndLookForDupes( vecItemDefsB ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconLootListDefinition::BGenerateSingleRollRandomItems( const CEconGameAccount *pGameAccount, bool bFreeAccount, CUtlVector<CEconItem *> *out_pvecItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs /*= NULL*/ ) const +{ + Assert( out_pvecItems ); + + // Where is our source of random numbers coming from? If we're a no-dupe list, + // we want to have reproducible state so we use our account ID as a unique-ish + // seed. + // + // Wrap the whole thing in a smart pointer so we clean up whenever/however we + // leave. + std::unique_ptr<IUniformRandomStream> pRandomStream( [=]() -> IUniformRandomStream * + { + if ( !BIsInternalNoDupesLootList() || !pGameAccount ) + return new CDefaultUniformRandomStream; + + + CUniformRandomStream *pAccountUniformRandomStream = new CUniformRandomStream; + pAccountUniformRandomStream->SetSeed( pGameAccount->Obj().m_unAccountID ); + + return pAccountUniformRandomStream; + }() ); + + // Make however many passes through our loot list code until we've generated the + // right number of passing sets. (Most loot lists let anything pass. Some specify + // no duplicate definitions allowed.) + CUtlVector<rolled_item_defs_t> vecCumulativeItemDefs; // total list of everything we've generated so far in any number of result sets + CUtlVector<rolled_item_defs_t> vecItemDefs; // current list under evaluation + int iNoDupesIterations = 0; + + // This actually isn't guaranteed to converge, and is guaranteed not to converge if + // we set up a broken lootlist. We set a really high bar here to catch broken/pathologically + // bad cases here without grinding the whole GC to a halt. + enum { kUpperBoundIterationSanityCheck = 2000 }; + int iTotalIterations = 0; + + while ( true ) + { + // Don't runaway. + iTotalIterations++; + if ( iTotalIterations >= kUpperBoundIterationSanityCheck ) + return false; + + // Generate all of our item defs and their lootlists + vecItemDefs.Purge(); + if ( !RollRandomItemsAndAdditionalItems( pRandomStream.get(), bFreeAccount, &vecItemDefs, pVecAvoidItemDefs ) ) + return false; + + // If we don't care about dupes and we got any results at all we're done. + if ( !BIsInternalNoDupesLootList() ) + break; + + // If we do care about dupes and we have some, ignore this set of items. + if ( BContainsDuplicateItemDefs( vecItemDefs, vecCumulativeItemDefs ) ) + continue; + + // Did we get to the right result set? + iNoDupesIterations++; + if ( iNoDupesIterations > m_iNoDupesIterations ) + break; + + // Store off the list of definition indices we've already used them so they don't get reused + // in a later set. + vecCumulativeItemDefs.AddVectorToTail( vecItemDefs ); + } + + // If we get down to here, we expect that we've rolled at least one item def + Assert( vecItemDefs.Count() > 0 ); + FOR_EACH_VEC( vecItemDefs, i ) + { + const rolled_item_defs_t& rolledDef = vecItemDefs[i]; + Assert( rolledDef.m_pItemDef ); + Assert( rolledDef.m_vecAffectingLootLists.Count() > 0 ); + + // Create the items + CEconItem *pItem = GEconManager()->GetItemFactory().CreateSpecificItem( pGameAccount, rolledDef.m_pItemDef->GetDefinitionIndex() ); + out_pvecItems->AddToTail( pItem ); + + // Go through and let all the affecting lootlists attach their attributes to the item + FOR_EACH_VEC( rolledDef.m_vecAffectingLootLists, j ) + { + const CEconLootListDefinition *pLootList = rolledDef.m_vecAffectingLootLists[j]; + Assert( pLootList ); + if ( !pLootList->BAttachLootListAttributes( pGameAccount, pItem ) ) + return false; + } + } + + return ( out_pvecItems->Count() > 0 ); +} + +class CRollSimulator +{ +public: + + CRollSimulator() + : m_mapRarityCounts( StringLessThan ) + , m_mapCounts( DefLessFunc( item_definition_index_t ) ) + , m_mapUnusualHatEffectsCount( DefLessFunc( uint32 ) ) + , m_mapUnusualTauntEffectsCount( DefLessFunc( uint32 ) ) + , m_nNumIters( 0 ) + {} + + void RollLootlist( const char* pszLootListName, int nRolls, bool bWipePreviousResults = false ) + { + const CEconLootListDefinition* pLootlist = GetItemSchema()->GetLootListByName( pszLootListName ); + + if ( !pLootlist ) + { + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Invalid lootlist \"%s\".\n", pszLootListName ); + return; + } + + // Clear out results first? + if ( bWipePreviousResults ) + { + m_mapCounts.Purge(); + m_mapRarityCounts.Purge(); + m_DropsMaxes.Clear(); + m_DropsTotals.Clear(); + } + + while( nRolls-- ) + { + AutoYield(); + CUtlVector<CEconItem *> vecItems; + pLootlist->BGenerateSingleRollRandomItems( NULL, false, &vecItems ); + + // Tally up what we got. + for( auto pItem : vecItems ) + { + AutoYield(); + // Insert item def if we need + item_definition_index_t nDefIndex = pItem->GetDefinitionIndex(); + auto idx = m_mapCounts.Find( nDefIndex ); + if ( m_mapCounts.InvalidIndex() == idx ) + { + idx = m_mapCounts.Insert( nDefIndex ); + } + + // Insert rarity if needed + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItem->GetItemDefinition()->GetRarity() ); + if ( pItemRarity ) + { + const char* pszRarity = GGCGameBase()->LocalizeToken( pItemRarity->GetLocKey() , k_Lang_English ); + auto rarityIdx = m_mapRarityCounts.Find( pszRarity ); + if ( m_mapRarityCounts.InvalidIndex() == rarityIdx ) + { + rarityIdx = m_mapRarityCounts.Insert( pszRarity, 0 ); + } + + m_mapRarityCounts[ rarityIdx ] += 1; + } + + // Count + DropResult_t& result = m_mapCounts[ idx ]; + ++result.m_nRollCount; + ++m_DropsTotals.m_nRollCount; + m_DropsMaxes.m_nRollCount = Max( m_DropsMaxes.m_nRollCount, result.m_nRollCount ); + + // Strange count + if ( BIsItemStrange( pItem ) ) + { + ++result.m_nStrangeCount; + ++m_DropsTotals.m_nStrangeCount; + m_DropsMaxes.m_nStrangeCount = Max( m_DropsMaxes.m_nStrangeCount, result.m_nStrangeCount ); + } + + // Unusual count + static CSchemaAttributeDefHandle pAttrDef_ParticleEffect( "attach particle effect" ); + static CSchemaAttributeDefHandle pAttrDef_TauntUnusualAttr( "on taunt attach particle index" ); + if ( pAttrDef_ParticleEffect && pAttrDef_TauntUnusualAttr ) + { + uint32 nUnusualHatValue = 0; + uint32 nUnusualTauntValue = 0; + pItem->FindAttribute( pAttrDef_ParticleEffect, &nUnusualHatValue ); + pItem->FindAttribute( pAttrDef_TauntUnusualAttr, &nUnusualTauntValue ); + // Cant use quality cause of old legacy items. Quality is just a quick test + if ( nUnusualHatValue != 0 || nUnusualTauntValue != 0 ) + { + ++result.m_nUnusualCount; + ++m_DropsTotals.m_nUnusualCount; + m_DropsMaxes.m_nUnusualCount = Max( m_DropsMaxes.m_nUnusualCount, result.m_nUnusualCount ); + } + + if ( nUnusualHatValue ) + { + nUnusualHatValue = (uint32)((float&)nUnusualHatValue); + auto idx = m_mapUnusualHatEffectsCount.Find( nUnusualHatValue ); + if ( idx == m_mapUnusualHatEffectsCount.InvalidIndex() ) + { + idx = m_mapUnusualHatEffectsCount.Insert( nUnusualHatValue, 0 ); + } + + ++m_mapUnusualHatEffectsCount[ idx ]; + } + + if ( nUnusualTauntValue ) + { + nUnusualTauntValue = (uint32)((float&)nUnusualTauntValue); + auto idx = m_mapUnusualTauntEffectsCount.Find( nUnusualTauntValue ); + if ( idx == m_mapUnusualTauntEffectsCount.InvalidIndex() ) + { + idx = m_mapUnusualTauntEffectsCount.Insert( nUnusualTauntValue, 0 ); + } + + ++m_mapUnusualTauntEffectsCount[ idx ]; + } + } + } + + // Delete what we got + vecItems.PurgeAndDeleteElements(); + } + } + + void PrintRarityBreakdwn() + { + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Rarity breakdown:\n" ); + int nMaxDigits = 0; + while( m_mapRarityCounts.Count() ) + { + // Go through and print the most rolled to least rolled rarities + auto maxIdx = m_mapRarityCounts.InvalidIndex(); + + // Find the most hit + FOR_EACH_MAP_FAST( m_mapRarityCounts, i ) + { + AutoYield(); + if ( maxIdx == m_mapRarityCounts.InvalidIndex() || m_mapRarityCounts[ i ] > m_mapRarityCounts[ maxIdx ] ) + { + maxIdx = i; + } + + nMaxDigits = Max( nMaxDigits, NumDigits( m_mapRarityCounts[ maxIdx ] ) ); + } + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%9s %*d %.3f%%\n", + m_mapRarityCounts.Key( maxIdx ), nMaxDigits, m_mapRarityCounts[ maxIdx ], + 100.f * (float)m_mapRarityCounts[ maxIdx ] / m_DropsTotals.m_nRollCount ); + m_mapRarityCounts.RemoveAt( maxIdx ); + } + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%-*s %*s %*s\n", 9 + 1 + NumDigits( m_DropsMaxes.m_nRollCount ) + 6, + "-- Item breakdown", NumDigits( m_DropsMaxes.m_nStrangeCount ) + 1, "S", NumDigits( m_DropsMaxes.m_nUnusualCount ), "U" ); + + } + + void PrintUnusualCounts() + { + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Unusual Hats breakdown:\n" ); + int nTotal = PrintUnusualsForType( m_mapUnusualHatEffectsCount ); + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- %d total unusual hats\n", nTotal ); + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Unusual Taunts breakdown:\n" ); + nTotal = PrintUnusualsForType( m_mapUnusualTauntEffectsCount ); + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- %d total unusual taunts\n", nTotal ); + } + + void PrintTotals() + { + while( m_mapCounts.Count() ) + { + // Go through and print the most rolled to least rolled + auto maxIdx = m_mapCounts.InvalidIndex(); + + // Find the most hit + FOR_EACH_MAP_FAST( m_mapCounts, i ) + { + AutoYield(); + if ( maxIdx == m_mapCounts.InvalidIndex() || m_mapCounts[ i ].m_nRollCount > m_mapCounts[ maxIdx ].m_nRollCount ) + { + maxIdx = i; + } + } + + DropResult_t& result = m_mapCounts[ maxIdx ]; + CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( m_mapCounts.Key( maxIdx ) ); + + const CEconItemRarityDefinition* pItemRarity = GetItemSchema()->GetRarityDefinition( pItemDef->GetRarity() ); + const char* pszRarity = pItemRarity ? GGCGameBase()->LocalizeToken( pItemRarity->GetLocKey() , k_Lang_English ) : ""; + CFmtStr rollString( "%*d %2.3f%%", NumDigits( m_DropsMaxes.m_nRollCount ), result.m_nRollCount, + 100.f * (float)result.m_nRollCount / m_DropsTotals.m_nRollCount ); + CFmtStr strangeString( "%*d", NumDigits( m_DropsMaxes.m_nStrangeCount ), result.m_nStrangeCount ); + CFmtStr unusualString( "%*d", NumDigits( m_DropsMaxes.m_nUnusualCount ), result.m_nUnusualCount ); + const char* pszItemname = GGCGameBase()->LocalizeToken( pItemDef->GetCustomPainkKitDefinition() ? pItemDef->GetCustomPainkKitDefinition()->GetName() : pItemDef->GetItemBaseName(), k_Lang_English ); + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%9s %s %s %s %s\n", pszRarity, rollString.Get(), strangeString.Get(), unusualString.Get(), pszItemname ); + + m_mapCounts.RemoveAt( maxIdx ); + } + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "-- Total Items: %d Strange: %d (%.3f%%) Unusual: %d (%.3f%%)\n" + , m_DropsTotals.m_nRollCount + , m_DropsTotals.m_nStrangeCount + , ( 100.f * (float)m_DropsTotals.m_nStrangeCount / m_DropsTotals.m_nRollCount ) + , m_DropsTotals.m_nUnusualCount + , ( 100.f * (float)m_DropsTotals.m_nUnusualCount / m_DropsTotals.m_nRollCount ) ); + } + + struct DropResult_t + { + DropResult_t() { Clear(); } + + void Clear() + { + m_nStrangeCount = 0; + m_nUnusualCount = 0; + m_nRollCount = 0; + } + + int m_nStrangeCount; + int m_nUnusualCount; + int m_nRollCount; + }; + + const DropResult_t& GetTotalDrops() const { return m_DropsTotals; } + const DropResult_t& GetMaxesDrops() const { return m_DropsMaxes; } + +private: + + int PrintUnusualsForType( CUtlMap< uint32, int >& mapUnusuals ) + { + int nMaxDigits = 0; + int nTotal = 0; + FOR_EACH_MAP_FAST( mapUnusuals, i ) + { + nTotal += mapUnusuals[ i ]; + } + + while( mapUnusuals.Count() ) + { + // Go through and print the most rolled to least rolled unusual effects + auto maxIdx = mapUnusuals.InvalidIndex(); + + // Find the most hit + FOR_EACH_MAP_FAST( mapUnusuals, i ) + { + AutoYield(); + if ( maxIdx == mapUnusuals.InvalidIndex() || mapUnusuals[ i ] > mapUnusuals[ maxIdx ] ) + { + maxIdx = i; + } + + nMaxDigits = Max( nMaxDigits, NumDigits( mapUnusuals[ maxIdx ] ) ); + } + + char particleNameEntry[128]; + Q_snprintf( particleNameEntry, ARRAYSIZE( particleNameEntry ), "#Attrib_Particle%d", mapUnusuals.Key( maxIdx ) ); + const char* pszParticleName = GGCGameBase()->LocalizeToken( particleNameEntry, k_Lang_English ); + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%*d %.3f%% %s (%d)\n", + nMaxDigits, + mapUnusuals[ maxIdx ], + 100.f * (float)mapUnusuals[ maxIdx ] / nTotal, + pszParticleName, + mapUnusuals.Key( maxIdx ) ); + + mapUnusuals.RemoveAt( maxIdx ); + } + + return nTotal; + } + + void AutoYield() + { + if ( ++m_nNumIters % 100 == 0 ) + { + if ( GJobCur().BYieldIfNeeded() ) + { + // If we re-entered logon surge we should go away for a while + while ( GGCGameBase()->BIsInLogonSurge() ) + { + GJobCur().BYieldingWaitOneFrame(); + } + } + } + }; + + int NumDigits( int nNumber ) + { + int digits = 0; + + if ( nNumber <= 0) + { + digits = 1; + } + + while ( nNumber ) + { + nNumber /= 10; + ++digits; + } + + return digits; + } + + + + CUtlMap< item_definition_index_t, DropResult_t > m_mapCounts; + CUtlMap< uint32, int > m_mapUnusualHatEffectsCount; + CUtlMap< uint32, int > m_mapUnusualTauntEffectsCount; + // Rarity name is the key + CUtlMap< const char*, int > m_mapRarityCounts; + DropResult_t m_DropsTotals; + DropResult_t m_DropsMaxes; + size_t m_nNumIters; +}; + +GC_CON_COMMAND( simulate_lootlist_contents, "<lootlist> <iterations> Check item distribution from a given lootlist n times" ) +{ + if ( !BCheckArgs( 2, args, simulate_lootlist_contents_command ) ) + return; + + const char* pszLootListName = args[1]; + int nRolls = args.ArgC() == 3 ? atoi( args[2] ) : 1000; + + CRollSimulator simulator; + simulator.RollLootlist( pszLootListName, nRolls ); + simulator.PrintRarityBreakdwn(); + simulator.PrintUnusualCounts(); + simulator.PrintTotals(); +} + + +#ifdef GC_DLL +class CItemSourceFinder +{ +public: + CItemSourceFinder( const char* pszItemName ) + : m_pItemDef( GetItemSchema()->GetItemDefinitionByName( pszItemName ) ) + { + Assert( m_pItemDef ); + if ( !m_pItemDef ) + { + EG_ERROR( SPEW_CONSOLE, "%s is not a valid item", pszItemName ); + return; + } + + // Find out what series this crate belongs to. + static CSchemaAttributeDefHandle pAttr_CrateSeries( "set supply crate series" ); + if ( !pAttr_CrateSeries ) + return; + + auto& mapItemDefs = GetItemSchema()->GetItemDefinitionMap(); + auto& mapRevolvingLootlists = GetItemSchema()->GetRevolvingLootLists(); + + CUtlDict< int > dictSeenLootlists; + + // Look through all the item defs and see if any of them statically specify a lootlist that they want to open + FOR_EACH_MAP_FAST( mapItemDefs, i ) + { + const CEconItemDefinition* pSourceItemDef = mapItemDefs[ i ]; + const CEconLootListDefinition* pLootlist = NULL; + + const CEconTool_Gift* pGift = pSourceItemDef->GetTypedEconTool< CEconTool_Gift >(); + // Self-opening crate? + if ( pGift ) + { + pLootlist = GetItemSchema()->GetLootListByName( pGift->GetLootListName() ); + } + else // Crate with an item series? + { + int iCrateSeries; + { + float fCrateSeries; // crate series ID is stored as a float internally because we hate ourselves + if ( !FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pSourceItemDef, pAttr_CrateSeries, &fCrateSeries ) || fCrateSeries == 0.0f ) + continue; + + iCrateSeries = fCrateSeries; + } + + auto idx = mapRevolvingLootlists.Find( iCrateSeries ); + if ( idx == mapRevolvingLootlists.InvalidIndex() ) + continue; + + pLootlist = GetItemSchema()->GetLootListByName( mapRevolvingLootlists[ idx ] ); + } + + if ( !pLootlist ) + continue; + + // Mark that we've seen this lootlist already + dictSeenLootlists.Insert( pLootlist->GetName() ); + + DropSource_t& source = m_vecSources[ m_vecSources.AddToTail() ]; + source.m_pDroppingItem = pSourceItemDef; + ChanceForItemFromLootlist( m_pItemDef, pLootlist, source.lootlistSource ); + } + + CEconItemDefinition* pCrateItemDef = GetItemSchema()->GetItemDefinitionByName( "Supply Crate" ); + Assert( pCrateItemDef ); + + // Go through all the revolving lootlists and see if they have the item. Assume that + // they're from a "Supply Crate". + FOR_EACH_MAP_FAST( mapRevolvingLootlists, i ) + { + if ( !pCrateItemDef ) + continue; + + if ( mapRevolvingLootlists.Key( i ) <= 0 ) + continue; + + auto pLootlist = GetItemSchema()->GetLootListByName( mapRevolvingLootlists[ i ] ); + if ( !pLootlist ) + continue; + + // This lootlist was on a different crate already + if ( dictSeenLootlists.Find( pLootlist->GetName() ) != dictSeenLootlists.InvalidIndex() ) + continue; + + DropSource_t& source = m_vecSources[ m_vecSources.AddToTail() ]; + source.m_pDroppingItem = pCrateItemDef; + ChanceForItemFromLootlist( m_pItemDef, pLootlist, source.lootlistSource ); + } + + // Not available at all! + if ( !m_vecSources.Count() ) + { + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Not available from any sources!\n" ); + return; + } + + // Sort greatest % chance + auto lambdaSort = [] ( DropSource_t const *pLHS, DropSource_t const *pRHS ) -> int + { + return pLHS->lootlistSource.m_flChance < pRHS->lootlistSource.m_flChance; + }; + m_vecSources.Sort( lambdaSort ); + } + + void PrintSources() + { + FOR_EACH_VEC( m_vecSources, i ) + { + m_vecSources[ i ].PrintSources(); + } + } + + bool BDropsFromLootlist( const CEconLootListDefinition* pLootlist ) + { + FOR_EACH_VEC( m_vecSources, i ) + { + if ( m_vecSources[ i ].lootlistSource.BDropsFromLootlist( pLootlist ) ) + return true; + } + + return false; + } + +private: + + struct DropSource_t + { + void PrintSources() + { + if ( lootlistSource.m_flChance == 0.f ) + return; + + bool bSelfOpening = m_pDroppingItem->GetTypedEconTool< CEconTool_Gift >() != NULL; + + // Print the name of the item, and whether it's a self-opening item, or a crate + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%3.5f%% %s (%d - %s)\n", + lootlistSource.m_flChance * 100.f, + m_pDroppingItem->GetDefinitionName(), + m_pDroppingItem->GetDefinitionIndex(), + bSelfOpening ? "Self-Opening" : "Crate/Case" ); + + // Print all the sources + lootlistSource.PrintSources( 1 ); + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n" ); + } + + struct LootListSource_t + { + LootListSource_t() + : m_flChance( 0.f ) + {} + + bool BDropsFromLootlist( const CEconLootListDefinition* pLootlist ) + { + // Skip no-chance entries + if ( m_flChance == 0.f ) + return false; + + + if ( m_pDroppingLootlist == pLootlist ) + return true; + + FOR_EACH_VEC( m_vecLootlistSources, i ) + { + if ( m_vecLootlistSources[ i ].BDropsFromLootlist( pLootlist ) ) + return true; + } + + return false; + } + + void PrintSources( int nInset ) + { + // Skip no-chance entries + if ( m_flChance == 0.f ) + return; + + auto& mapRevolvingLootlists = GetItemSchema()->GetRevolvingLootLists(); + int nRevolvingIdx = mapRevolvingLootlists.InvalidIndex(); + + // Check if our lootlist is one of the revolving lootlists. If so, we want + // to print out its index within revolving_lootlists, so we can map that to + // the attribute value of supply_crate_series (187) + FOR_EACH_MAP_FAST( mapRevolvingLootlists, i ) + { + if ( V_stricmp( mapRevolvingLootlists[ i ], m_pDroppingLootlist->GetName() ) == 0 ) + { + nRevolvingIdx = mapRevolvingLootlists.Key( i ); + break; + } + } + + if ( nRevolvingIdx != mapRevolvingLootlists.InvalidIndex() && nRevolvingIdx > 0 ) + { + // It's in revolving_lootlists. Print its index in there. + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%*.5f%% (%d) %s\n", + 4 * nInset + 5, + m_flChance * 100.f, + nRevolvingIdx, + m_pDroppingLootlist->GetName() ); + } + else + { + // Not in the revolving lootlist + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%*.5f%% %s\n", + 4 * nInset + 5, + m_flChance * 100.f, + m_pDroppingLootlist->GetName() ); + } + + // Sort greatest % chance + auto lambdaSort = [] ( LootListSource_t const *pLHS, LootListSource_t const *pRHS ) -> int + { + return pLHS->m_flChance < pRHS->m_flChance; + }; + + m_vecLootlistSources.Sort( lambdaSort ); + + // Print out children indented a lil bit + FOR_EACH_VEC( m_vecLootlistSources, i ) + { + m_vecLootlistSources[i].PrintSources( nInset + 1 ); + } + } + + float m_flChance; + const CEconLootListDefinition* m_pDroppingLootlist; + + CCopyableUtlVector< LootListSource_t > m_vecLootlistSources; + }; + + const CEconItemDefinition* m_pDroppingItem; + LootListSource_t lootlistSource; + }; + + float ChanceForItemFromLootlist( const CEconItemDefinition* pItemDef, const CEconLootListDefinition* pLootlist, DropSource_t::LootListSource_t& lootlistSource ) + { + auto& vecContents = pLootlist->GetLootListContents(); + // Accumulate our chance of dropping the specified item + lootlistSource.m_flChance = 0.f; + lootlistSource.m_pDroppingLootlist = pLootlist; + + // Gather the items in this lootlist that we're able to roll for at this time + float flTotalWeight = 0.f; + CUtlVector<CEconLootListDefinition::drop_item_t> vecValidContents; + FOR_EACH_VEC( vecContents, i ) + { + if( !vecContents[ i ].m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) + continue; + + flTotalWeight += vecContents[ i ].m_flWeight; + vecValidContents.AddToTail( vecContents[ i ] ); + } + + // Go through valid contents, and see if the specified item is in there + FOR_EACH_VEC( vecValidContents, i ) + { + const int iItemDef = vecValidContents[ i ].m_iItemOrLootlistDef; + const float flChance = vecValidContents[ i ].m_flWeight / flTotalWeight; + + if ( iItemDef < 0 ) // Lootlist + { + int iLLIndex = (iItemDef * -1) - 1; + auto pSubLootlist = GetItemSchema()->GetLootListByIndex( iLLIndex ); + + // One of our sub-lootlists might drop it. Add in it's chance within + // the sub-lootlist scaled by the chance to roll that sub-lootlist. + auto& subSource = lootlistSource.m_vecLootlistSources[ lootlistSource.m_vecLootlistSources.AddToTail() ]; + lootlistSource.m_flChance += ChanceForItemFromLootlist( pItemDef, pSubLootlist, subSource ) * flChance; + } + else if ( pItemDef->GetDefinitionIndex() == iItemDef ) + { + // We drop it! Add the chance + lootlistSource.m_flChance += flChance; + } + } + + // Treat additional drops just the same as nested lootlists. + auto& vecAdditionalDrops = pLootlist->GetAdditionalDrops(); + FOR_EACH_VEC( vecAdditionalDrops, i ) + { + auto& additionalDrop = vecAdditionalDrops[ i ]; + if ( !additionalDrop.m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) + continue; + + auto pSubLootlist = GetItemSchema()->GetLootListByName( additionalDrop.m_pszLootListDefName ); + auto& subSource = lootlistSource.m_vecLootlistSources[ lootlistSource.m_vecLootlistSources.AddToTail() ]; + lootlistSource.m_flChance += ChanceForItemFromLootlist( pItemDef, pSubLootlist, subSource ) * additionalDrop.m_fChance; + } + + // Return the total chance + return lootlistSource.m_flChance; + } + + CUtlVector< DropSource_t > m_vecSources; + const CEconItemDefinition* m_pItemDef; +}; + +GC_CON_COMMAND( item_sources, "Lists the sources for obtaining a list of specific items" ) +{ + if ( !BCheckArgs( 1, args, item_sources_command ) ) + return; + + for ( int i=1; i < args.ArgC(); ++i ) + { + const char* pszItemName = args[i]; + const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinitionByName( pszItemName ); + if ( pItemDef ) + { + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\nChecking items for lootlists containing (%d) %s...\n", pItemDef->GetDefinitionIndex(), pItemDef->GetDefinitionName() ); + CItemSourceFinder source( pszItemName ); + source.PrintSources(); + } + else + { + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "\n\"%s\" is not a valid item name\n", pszItemName ); + } + } +} + +GC_CON_COMMAND( list_keys, "Lists all the keys in the schema" ) +{ + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Spewing keys...\n" ); + + auto& mapItemDefs = GetItemSchema()->GetItemDefinitionMap(); + FOR_EACH_MAP_FAST( mapItemDefs, i ) + { + const CEconItemDefinition* pItemDef = mapItemDefs[ i ]; + if ( !pItemDef || !pItemDef->GetEconTool() || ( Q_strcmp( pItemDef->GetEconTool()->GetTypeName(), "decoder_ring" ) != 0 ) ) + { + continue; + } + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "%llu - %s\n", pItemDef->GetDefinitionIndex(), pItemDef->GetItemDefinitionName() ); + } +} + +ConVar case_behavior_rolls_per_lootlist( "case_behavior_rolls_per_lootlist", "1000000", FCVAR_REPLICATED, "How many times to roll a lootlist" ); +class CJobCaseBehaviorCheck : public CGCGameBaseJob +{ +public: + CJobCaseBehaviorCheck() + : CGCGameBaseJob( GGCGameBase() ) + {} + + virtual bool BYieldingRunGCJob() + { + // Wait until logon surge ends to get going + while ( GGCGameBase()->BIsInLogonSurge() ) + { + GJobCur().BYieldingWaitOneFrame(); + } + + CRollSimulator simulator; + + // Convert the hash to something readable + char pchSHAHex[41]; + memset( pchSHAHex, 0, sizeof( pchSHAHex ) ); + V_binarytohex( GetItemSchema()->GetSchemaSHA().m_shaDigest, 20, pchSHAHex, 41 ); + + RTime32 now = CRTime::RTime32TimeCur(); + int nNewRecords = 0; + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Beginning CJobCaseBehaviorCheck\n" ); + + auto& lootlists = GetItemSchema()->GetRevolvingLootLists(); + + // Go through all of the lootlists + FOR_EACH_MAP( lootlists, i ) + { + const CEconLootListDefinition* pEconLootlist = GetItemSchema()->GetLootListByName( lootlists[ i ] ); + if ( pEconLootlist ) + { + // Check if we have data for this lootlist on this hash already + { + CSQLAccess sqlReadAccess; + CUtlVector< CSchCaseBehavior > vecExistingResult; + sqlReadAccess.AddBindParam( pchSHAHex ); + sqlReadAccess.AddBindParam( i ); + if ( sqlReadAccess.BYieldingReadRecordsWithWhereClause( &vecExistingResult, "SchemaSHA = ? and Series = ?", CSET_FULL( CSchCaseBehavior ) ) ) + { + // Already got it? Skip the work + if ( vecExistingResult.Count() ) + { + continue; + } + } + } + + // Roll 'em up + simulator.RollLootlist( pEconLootlist->GetName(), case_behavior_rolls_per_lootlist.GetInt(), true ); + + // Insert record + CSchCaseBehavior behavior; + behavior.SetVarCharField( behavior.m_VarCharSchemaSHA, pchSHAHex, true, CSchCaseBehavior::k_iField_VarCharSchemaSHA ); + behavior.m_RTime32Date = now; + behavior.m_unSeries = i; + behavior.SetVarCharField( behavior.m_VarCharLootListName, pEconLootlist->GetName(), true, CSchCaseBehavior::k_iField_VarCharLootListName ); + behavior.m_fStrangeChance = 100.f * (float)simulator.GetTotalDrops().m_nStrangeCount / simulator.GetTotalDrops().m_nRollCount; + behavior.m_fUnusualChance = 100.f * (float)simulator.GetTotalDrops().m_nUnusualCount / simulator.GetTotalDrops().m_nRollCount; + + CSQLAccess sqlAccess; + sqlAccess.BBeginTransaction( "CJobCaseBehaviorCheck" ); + sqlAccess.BYieldingInsertOrUpdateOnPK( &behavior ); + sqlAccess.BCommitTransaction( true ); + ++nNewRecords; + } + + BYieldIfNeeded(); + } + + EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Completed CJobUnusualChecker. %d updated records.\n", nNewRecords ); + + return true; + } +}; + +void CEconItemSchema::PerformCaseBehaviorCheck() +{ + CJob* pJob = new CJobCaseBehaviorCheck(); + pJob->StartJobDelayed( NULL ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconLootListDefinition::RollRandomItemsAndAdditionalItems( IUniformRandomStream *pRandomStream, bool bFreeAccount, CUtlVector<rolled_item_defs_t> *out_pVecRolledItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs ) const +{ + Assert( out_pVecRolledItems ); + + // Roll to see what items we get from this loot list. + bool bCreatedItems = RollRandomItemDef( pRandomStream, bFreeAccount, out_pVecRolledItems, pVecAvoidItemDefs ); + + // Do we have additional drops? + FOR_EACH_VEC( m_AdditionalDrops, i ) + { + if ( !bCreatedItems ) + break; + + // Is this within the period this is allowed to drop? + if ( !m_AdditionalDrops[i].m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) + continue; + + // Does this only apply to premium accounts? + if ( m_AdditionalDrops[i].m_bPremiumOnly && bFreeAccount ) + continue; + + // Does this only apply on certain holidays? + if ( m_AdditionalDrops[i].m_iRequiredHolidayIndex != kHoliday_None && !EconHolidays_IsHolidayActive( m_AdditionalDrops[i].m_iRequiredHolidayIndex, CRTime::RTime32TimeCur() ) ) + continue; + + // Random chance is in the range 0-1 so generate a value in that range to "roll". + if ( pRandomStream->RandomFloat( 0.0f, 1.0f ) > m_AdditionalDrops[i].m_fChance ) + continue; + + // Roll! + const char *pszAdditionalDropLootList = m_AdditionalDrops[i].m_pszLootListDefName; + const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pszAdditionalDropLootList ); + if ( pLootListDef == NULL ) + { + AssertMsg2( false, "Loot list '%s' specifies unknown additional drop '%s'", GetName(), pszAdditionalDropLootList ); + return false; + } + + bCreatedItems &= pLootListDef->RollRandomItemsAndAdditionalItems( pRandomStream, bFreeAccount, out_pVecRolledItems ); + } + + // If we failed to create some items, we might still have chosen some item defs before those failures. In that case, clear + // out any choices we've made so far + if ( !bCreatedItems ) + { + out_pVecRolledItems->Purge(); + } + + Assert( bCreatedItems == (out_pVecRolledItems->Count() > 0) ); + + return bCreatedItems; +} + + +bool CEconLootListDefinition::RollRandomItemDef( IUniformRandomStream *pRandomStream, bool bFreeAccount, CUtlVector<rolled_item_defs_t> *out_pVecRolledItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs ) const +{ + Assert( out_pVecRolledItems ); + + CUtlVector< rolled_item_defs_t > vecScratchDefs; + CUtlVector<const drop_item_t*> vecValidDrops; + + // Gather the items in this lootlist that we're able to roll for at this time + float flTotalWeight = 0.f; + FOR_EACH_VEC( m_DropList, i ) + { + if( !m_DropList[i].m_dropPeriod.IsValidForTime( CRTime::RTime32TimeCur() ) ) + continue; + + // Skip any item defs that are in our avoid list (if we have one) + if ( pVecAvoidItemDefs ) + { + item_definition_index_t defIndex = m_DropList[i].m_iItemOrLootlistDef; + if ( pVecAvoidItemDefs->Find( defIndex ) != pVecAvoidItemDefs->InvalidIndex() ) + continue; + } + + // If this is valid, add it to the list and add its weight to the total + vecValidDrops.AddToTail( &m_DropList[i] ); + flTotalWeight += m_DropList[i].m_flWeight; + } + + // Roll to see what item drops. + float flRand = pRandomStream->RandomFloat(0.0f, 1.0f) * flTotalWeight; + + float flAccum = 0.0f; + FOR_EACH_VEC( vecValidDrops, i ) + { + flAccum += vecValidDrops[i]->m_flWeight; + if ( flRand <= flAccum ) + { + const int iItemDef = vecValidDrops[i]->m_iItemOrLootlistDef; // not item_definition_index because it might also be a negative value to indicate a sub lootlist + + if ( iItemDef >= 0 ) + { + const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( iItemDef ); + if( !pItemDef ) + return false; + + // Add the item def and the lootlist + rolled_item_defs_t& rolledDef = vecScratchDefs[ vecScratchDefs.AddToTail() ]; + rolledDef.m_pItemDef = pItemDef; + } + else + { + // In the case where iItemDef is negative, it's a nested loot list. Ask that list to choose an item. + // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index + int iLLIndex = (iItemDef * -1) - 1; + const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); + if ( !pNestedLootList ) + return false; + + if( !pNestedLootList->RollRandomItemsAndAdditionalItems( pRandomStream, bFreeAccount, &vecScratchDefs, pVecAvoidItemDefs ) ) + return false; + } + + // Add ourselves to the list of affecting lootlists, so that we and all nested loot lists will affect this item + // We intentionally don't do this in our calling function because we don't want to include additional drops. + FOR_EACH_VEC( vecScratchDefs, j ) + { + vecScratchDefs[j].m_vecAffectingLootLists.AddToTail( this ); + } + + // We want to exit the loop here regardless of whether items were successfully so that we only perform a single + // item-generating roll on this list. + break; + } + } + + // Feed item defs, if they exist, back to our caller + out_pVecRolledItems->AddVectorToTail( vecScratchDefs ); + + // Did we successfully create any items? + return ( vecScratchDefs.Count() > 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: find a list of lootlists with rarity from this lootlist +//----------------------------------------------------------------------------- +void CEconLootListDefinition::GetRarityLootLists( CUtlVector< const CEconLootListDefinition* > *out_pVecRarityLootList ) const +{ + Assert( out_pVecRarityLootList ); + if ( m_unRarity != k_unItemRarity_Any ) + { + out_pVecRarityLootList->AddToTail( this ); + } + + FOR_EACH_VEC( m_DropList, i ) + { + const int iItemDef = m_DropList[i].m_iItemOrLootlistDef; // not item_definition_index because it might also be a negative value to indicate a sub lootlist + if ( iItemDef < 0 ) + { + // In the case where iItemDef is negative, it's a nested loot list. Ask that list to choose an item. + // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index + int iLLIndex = (iItemDef * -1) - 1; + const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); + if ( !pNestedLootList ) + return; + + pNestedLootList->GetRarityLootLists( out_pVecRarityLootList ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: get all item defs from this lootlist ( not lootlist item def ) +//----------------------------------------------------------------------------- +void CEconLootListDefinition::GetItemDefs( CUtlVector< item_definition_index_t > *out_pVecItemDefs ) const +{ + Assert( out_pVecItemDefs ); + + FOR_EACH_VEC( m_DropList, i ) + { + const int iItemDef = m_DropList[i].m_iItemOrLootlistDef; // not item_definition_index because it might also be a negative value to indicate a sub lootlist + if ( iItemDef >= 0 ) + { + out_pVecItemDefs->AddToTail( (item_definition_index_t)iItemDef ); + } + else + { + // In the case where iItemDef is negative, it's a nested loot list. Ask that list to choose an item. + // HACKY: Store loot list indices as negatives, starting from -1, because 0 is a valid item index + int iLLIndex = (iItemDef * -1) - 1; + const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); + if ( !pNestedLootList ) + return; + + pNestedLootList->GetItemDefs( out_pVecItemDefs ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Given a vector of possible attributes, roll to see which ones are +// chosen. We allocate memory for these new attributes, so it's the +// responsibility of the caller to free these attributes when they're +// done with them! +//----------------------------------------------------------------------------- +void CEconLootListDefinition::RollRandomAttributes( CUtlVector< static_attrib_t >& vecAttributes, const CEconGameAccount *pGameAccount ) const +{ + for ( int i=0; i<m_RandomAttribs.Count(); ++i ) + { + const random_attrib_t* rattr = m_RandomAttribs[i]; + rattr->RollRandomAttributes( vecAttributes, pGameAccount ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconLootListDefinition::drop_period_t::IsValidForTime( const RTime32& time ) const +{ + if( time >= m_DropStartDate && time < m_DropEndDate ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconLootListDefinition::BAttachLootListAttributes( const CEconGameAccount *pGameAccount, CEconItem *pItem ) const +{ + //static CSchemaAttributeDefHandle pAttr_ElevateQuality( "elevate quality" ); + + // Gather and apply old-style random attributes. + CUtlVector< static_attrib_t > vecAttributes; + RollRandomAttributes( vecAttributes, pGameAccount ); + + FOR_EACH_VEC( vecAttributes, i ) + { + GEconManager()->GetItemFactory().ApplyStaticAttributeToItem( pItem, vecAttributes[i], pGameAccount ); + vecAttributes[i].GetAttributeDefinition()->GetAttributeType()->UnloadEconAttributeValue( &vecAttributes[i].m_value ); + } + + // Apply all relevant property generators. + for ( auto pGenerator : m_PropertyGenerators ) + { + if ( !pGenerator->BGenerateProperties( pItem ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool lootlist_attrib_t::BInitFromKV( const char *pszContext, KeyValues *pKVKey, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors ) +{ + SCHEMA_INIT_SUBSTEP( m_staticAttrib.BInitFromKV_MultiLine( pszContext, pKVKey, pVecErrors ) ); + + SCHEMA_INIT_CHECK( + pKVKey->FindKey( "weight" ), + "Context '%s': Attribute \"%s\" missing required 'weight' field", pszContext, pKVKey->GetName() ); + + m_flWeight = pKVKey->GetFloat( "weight" ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if we should stop rolling from this random_attrib_t +//----------------------------------------------------------------------------- +bool random_attrib_t::RollRandomAttributes( CUtlVector< static_attrib_t >& vecAttributes, const CEconGameAccount *pGameAccount ) const +{ + if ( m_flChanceOfRandomAttribute && RandomFloat() <= m_flChanceOfRandomAttribute ) + { + // We're attaching a random attribute. Determine which attribute. + float flRand = 0.0f; + if ( !m_bPickAllAttributes ) + { + // Pick one attribute to add + // Otherwise we'll pick them all + flRand = RandomFloat( 0.f, 1.f ) * m_flTotalAttributeWeight; + } + + float flAccum = 0.f; + for ( int iAttrib = 0; iAttrib < m_RandomAttributes.Count(); ++iAttrib ) + { + const lootlist_attrib_t& randomAttrib = m_RandomAttributes[iAttrib]; + + flAccum += randomAttrib.m_flWeight; + if ( flRand <= flAccum ) + { + // Add the attribute + static_attrib_t &staticAttrib = vecAttributes[ vecAttributes.AddToTail( randomAttrib.m_staticAttrib ) ]; + const CEconItemAttributeDefinition *pAttrDef = staticAttrib.GetAttributeDefinition(); + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + // Generate a special value? + pAttrType->InitializeNewEconAttributeValue( &staticAttrib.m_value ); + pAttrDef->GetAttributeType()->GenerateEconAttributeValue( pAttrDef, staticAttrib, pGameAccount, &staticAttrib.m_value ); + + if ( !m_bPickAllAttributes ) + { + // We're only picking one attribute from the list + return true; + } + } + } + } + + return false; +} + +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconLootListDefinition::EnumerateUserFacingPotentialDrops( IEconLootListIterator *pIt ) const +{ + Assert( pIt ); + + // Loot lists have the option of specifying that their contents should not be publicly + // listed. This is used on the GC for things like the "rare item drop list" to prevent + // every single potentially-unusual hat from showing up. + if ( !BPublicListContents() ) + return; + + FOR_EACH_VEC( GetLootListContents(), i ) + { + const int iID = GetLootListContents()[i].m_iItemOrLootlistDef; + + // Nested loot lists are stored as negative indices. + if ( iID < 0 ) + { + const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByIndex( (-iID) - 1 ); + if ( !pLootListDef ) + continue; + + pLootListDef->EnumerateUserFacingPotentialDrops( pIt ); + } + else + { + pIt->OnIterate( iID ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +/*static*/ CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItems[] = +{ + CSchemaAttributeDefHandle( "random drop line item 0" ), + CSchemaAttributeDefHandle( "random drop line item 1" ), + CSchemaAttributeDefHandle( "random drop line item 2" ), + CSchemaAttributeDefHandle( "random drop line item 3" ), +}; + +#ifdef GC_DLL +/*static*/ CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItemUnusualChance( "random drop line item unusual chance" ); // "one out of this many" +/*static*/ CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItemUnusualList( "random drop line item unusual list" ); +#endif // GC_DLL +CSchemaAttributeDefHandle CAttributeLineItemLootList::s_pAttrDef_RandomDropLineItemFooterDesc( "random drop line item footer desc" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeLineItemLootList::EnumerateUserFacingPotentialDrops( IEconLootListIterator *pIt ) const +{ + Assert( pIt ); + + for ( int i = 0; i < ARRAYSIZE( s_pAttrDef_RandomDropLineItems ); i++ ) + { + uint32 unItemDef; + COMPILE_TIME_ASSERT( sizeof( unItemDef ) >= sizeof( item_definition_index_t ) ); + + // If we run out of attributes we have set we're done. + if ( !m_pEconItem->FindAttribute( s_pAttrDef_RandomDropLineItems[i], &unItemDef ) ) + break; + + pIt->OnIterate( unItemDef ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CAttributeLineItemLootList::GetLootListHeaderLocalizationKey() const +{ + return g_pszDefaultRevolvingLootListHeader; +} + +//----------------------------------------------------------------------------- +const char *CAttributeLineItemLootList::GetLootListFooterLocalizationKey() const +{ + CAttribute_String sFooter; + const char* pszFooter = NULL; + if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( m_pEconItem, s_pAttrDef_RandomDropLineItemFooterDesc, &pszFooter ) ) + { + return pszFooter; + } + return NULL; +} +//----------------------------------------------------------------------------- +const char *CAttributeLineItemLootList::GetLootListCollectionReference() const +{ + // TODO : Implement me! + return NULL; +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconLootListDefinition* CEconItemSchema::GetLootListByName( const char* pListName, int *out_piIndex ) const +{ + auto idx = m_mapLootLists.Find( pListName ); + if ( !m_mapLootLists.IsValidIndex( idx ) ) + return NULL; + + if ( out_piIndex ) + { + *out_piIndex = idx; + } + + return m_mapLootLists[idx]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CQuestObjectiveDefinition* CEconItemSchema::GetQuestObjectiveByDefIndex( int iIdx ) const +{ + auto nMapIndex = m_mapQuestObjectives.Find( iIdx ); + if ( nMapIndex != m_mapQuestObjectives.InvalidIndex() ) + { + return m_mapQuestObjectives[ nMapIndex ]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CEconCraftingRecipeDefinition::CEconCraftingRecipeDefinition( void ) + : m_nDefIndex( 0 ) +#ifdef GC_DLL + , m_bIsCraftableByUnverifiedClient( false ) +#endif // GC_DLL +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize the attribute definition +// Input: pKVAttribute - The KeyValues representation of the attribute +// schema - The overall item schema for this attribute +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconCraftingRecipeDefinition::BInitFromKV( KeyValues *pKVRecipe, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + m_nDefIndex = Q_atoi( pKVRecipe->GetName() ); + + // Check for required fields + SCHEMA_INIT_CHECK( + NULL != pKVRecipe->FindKey( "input_items" ), + "Recipe definition %d: Missing required field \"input_items\"", m_nDefIndex ); + + SCHEMA_INIT_CHECK( + NULL != pKVRecipe->FindKey( "output_items" ), + "Recipe definition %d: Missing required field \"output_items\"", m_nDefIndex ); + + m_bDisabled = pKVRecipe->GetBool( "disabled" ); + m_strName = pKVRecipe->GetString( "name" ); + m_strN_A = pKVRecipe->GetString( "n_A" ); + m_strDescInputs = pKVRecipe->GetString( "desc_inputs" ); + m_strDescOutputs = pKVRecipe->GetString( "desc_outputs" ); + m_strDI_A = pKVRecipe->GetString( "di_A" ); + m_strDI_B = pKVRecipe->GetString( "di_B" ); + m_strDI_C = pKVRecipe->GetString( "di_C" ); + m_strDO_A = pKVRecipe->GetString( "do_A" ); + m_strDO_B = pKVRecipe->GetString( "do_B" ); + m_strDO_C = pKVRecipe->GetString( "do_C" ); + +#ifdef GC_DLL + m_bIsCraftableByUnverifiedClient = pKVRecipe->GetBool( "is_craftable_by_unverified_clients", false ); +#endif // GC_DLL + m_bRequiresAllSameClass = pKVRecipe->GetBool( "all_same_class" ); + m_bRequiresAllSameSlot = pKVRecipe->GetBool( "all_same_slot" ); + m_iCacheClassUsageForOutputFromItem = pKVRecipe->GetInt( "add_class_usage_to_output", -1 ); + m_iCacheSlotUsageForOutputFromItem = pKVRecipe->GetInt( "add_slot_usage_to_output", -1 ); + m_iCacheSetForOutputFromItem = pKVRecipe->GetInt( "add_set_to_output", -1 ); + m_bPremiumAccountOnly = pKVRecipe->GetBool( "premium_only", false ); + m_iCategory = (recipecategories_t)StringFieldToInt( pKVRecipe->GetString("category"), g_szRecipeCategoryStrings, ARRAYSIZE(g_szRecipeCategoryStrings) ); + + // Read in all the input items + KeyValues *pKVInputItems = pKVRecipe->FindKey( "input_items" ); + if ( NULL != pKVInputItems ) + { + FOR_EACH_TRUE_SUBKEY( pKVInputItems, pKVInputItem ) + { + int index = m_InputItemsCriteria.AddToTail(); + SCHEMA_INIT_SUBSTEP( m_InputItemsCriteria[index].BInitFromKV( pKVInputItem ) ); + + // Recipes ignore the enabled flag when generating items + m_InputItemsCriteria[index].SetIgnoreEnabledFlag( true ); + + index = m_InputItemDupeCounts.AddToTail(); + m_InputItemDupeCounts[index] = atoi( pKVInputItem->GetName() ); + } + } + + // Read in all the output items + KeyValues *pKVOutputItems = pKVRecipe->FindKey( "output_items" ); + if ( NULL != pKVOutputItems ) + { + FOR_EACH_TRUE_SUBKEY( pKVOutputItems, pKVOutputItem ) + { + int index = m_OutputItemsCriteria.AddToTail(); + SCHEMA_INIT_SUBSTEP( m_OutputItemsCriteria[index].BInitFromKV( pKVOutputItem ) ); + + // Recipes ignore the enabled flag when generating items + m_OutputItemsCriteria[index].SetIgnoreEnabledFlag( true ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Serializes the criteria to and from messages +//----------------------------------------------------------------------------- +bool CEconCraftingRecipeDefinition::BSerializeToMsg( CSOItemRecipe & msg ) const +{ + msg.set_def_index( m_nDefIndex ); + msg.set_name( m_strName ); + msg.set_n_a( m_strN_A ); + msg.set_desc_inputs( m_strDescInputs ); + msg.set_desc_outputs( m_strDescOutputs ); + msg.set_di_a( m_strDI_A ); + msg.set_di_b( m_strDI_B ); + msg.set_di_c( m_strDI_C ); + msg.set_do_a( m_strDO_A ); + msg.set_do_b( m_strDO_B ); + msg.set_do_c( m_strDO_C ); + msg.set_requires_all_same_class( m_bRequiresAllSameClass ); + msg.set_requires_all_same_slot( m_bRequiresAllSameSlot ); + msg.set_class_usage_for_output( m_iCacheClassUsageForOutputFromItem ); + msg.set_slot_usage_for_output( m_iCacheSlotUsageForOutputFromItem ); + msg.set_set_for_output( m_iCacheSetForOutputFromItem ); + + FOR_EACH_VEC( m_InputItemsCriteria, i ) + { + CSOItemCriteria *pCrit = msg.add_input_items_criteria(); + if ( !m_InputItemsCriteria[i].BSerializeToMsg( *pCrit ) ) + return false; + } + + FOR_EACH_VEC( m_InputItemDupeCounts, i ) + { + msg.add_input_item_dupe_counts( m_InputItemDupeCounts[i] ); + } + + FOR_EACH_VEC( m_OutputItemsCriteria, i ) + { + CSOItemCriteria *pCrit = msg.add_output_items_criteria(); + if ( !m_OutputItemsCriteria[i].BSerializeToMsg( *pCrit ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Serializes the criteria to and from messages +//----------------------------------------------------------------------------- +bool CEconCraftingRecipeDefinition::BDeserializeFromMsg( const CSOItemRecipe & msg ) +{ + m_nDefIndex = msg.def_index(); + m_strName = msg.name().c_str(); + m_strN_A = msg.n_a().c_str(); + m_strDescInputs = msg.desc_inputs().c_str(); + m_strDescOutputs = msg.desc_outputs().c_str(); + m_strDI_A = msg.di_a().c_str(); + m_strDI_B = msg.di_b().c_str(); + m_strDI_C = msg.di_c().c_str(); + m_strDO_A = msg.do_a().c_str(); + m_strDO_B = msg.do_b().c_str(); + m_strDO_C = msg.do_c().c_str(); + + m_bRequiresAllSameClass = msg.requires_all_same_class(); + m_bRequiresAllSameSlot = msg.requires_all_same_slot(); + m_iCacheClassUsageForOutputFromItem = msg.class_usage_for_output(); + m_iCacheSlotUsageForOutputFromItem = msg.slot_usage_for_output(); + m_iCacheSetForOutputFromItem = msg.set_for_output(); + + // Read how many input items there are + uint32 unCount = msg.input_items_criteria_size(); + m_InputItemsCriteria.SetSize( unCount ); + for ( uint32 i = 0; i < unCount; i++ ) + { + if ( !m_InputItemsCriteria[i].BDeserializeFromMsg( msg.input_items_criteria( i ) ) ) + return false; + } + + // Read how many input item dupe counts there are + unCount = msg.input_item_dupe_counts_size(); + m_InputItemDupeCounts.SetSize( unCount ); + for ( uint32 i = 0; i < unCount; i++ ) + { + m_InputItemDupeCounts[i] = msg.input_item_dupe_counts( i ); + } + + // Read how many output items there are + unCount = msg.output_items_criteria_size(); + m_OutputItemsCriteria.SetSize( unCount ); + for ( uint32 i = 0; i < unCount; i++ ) + { + if ( !m_OutputItemsCriteria[i].BDeserializeFromMsg( msg.output_items_criteria( i ) ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the vector contains a set of items that matches the inputs for this recipe +// Note it will fail if the vector contains extra items that aren't needed. +// +//----------------------------------------------------------------------------- +bool CEconCraftingRecipeDefinition::ItemListMatchesInputs( CUtlVector<CEconItem*> *vecCraftingItems, KeyValues *out_pkvCraftParams, bool bIgnoreSlop, CUtlVector<uint64> *vecChosenItems ) const +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +int CEconCraftingRecipeDefinition::GetTotalInputItemsRequired( void ) const +{ + int iCount = 0; + FOR_EACH_VEC( m_InputItemsCriteria, i ) + { + if ( m_InputItemDupeCounts[i] ) + { + iCount += m_InputItemDupeCounts[i]; + } + else + { + iCount++; + } + } + return iCount; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef GC_DLL + #define GC_SCH_REFERENCE( TAttribSchType ) \ + TAttribSchType, +#else + #define GC_SCH_REFERENCE( TAttribSchType ) +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +unsigned int Internal_GetAttributeTypeUniqueIdentifierNextValue() +{ + static unsigned int s_unUniqueCounter = 0; + + unsigned int unCounter = s_unUniqueCounter; + s_unUniqueCounter++; + return unCounter; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef GC_DLL +template < typename TAttribSchType, typename TRecordBaseType > +static TAttribSchType *GetTypedSch( TRecordBaseType *pRecordBase ) +{ + Assert( pRecordBase->GetITable() == TAttribSchType::k_iTable ); + +#if ENABLE_TYPED_ATTRIBUTE_PARANOIA + TAttribSchType *pTypedSch = dynamic_cast<TAttribSchType *>( pRecordBase ); + Assert( pTypedSch ); + return pTypedSch; +#else + return static_cast<TAttribSchType *>( pRecordBase ); +#endif +} +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < GC_SCH_REFERENCE( typename TAttribSchType ) typename TAttribInMemoryType > +class CSchemaAttributeTypeBase : public ISchemaAttributeTypeBase<TAttribInMemoryType> +{ +public: +#ifdef GC_DLL + virtual CColumnSet& GetFullColumnSet() const OVERRIDE + { + static CColumnSet sFullColumnSet( CColumnSet::Full<TAttribSchType>() ); + + return sFullColumnSet; + } + + virtual CRecordBase *CreateTypedSchRecord() const OVERRIDE + { + return new TAttribSchType; + } +#endif // GC_DLL +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < GC_SCH_REFERENCE( typename TAttribSchType ) typename TProtobufValueType > +class CSchemaAttributeTypeProtobufBase : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( TAttribSchType ) TProtobufValueType > +{ +public: + virtual void ConvertTypedValueToByteStream( const TProtobufValueType& typedValue, ::std::string *out_psBytes ) const OVERRIDE + { + DbgVerify( typedValue.SerializeToString( out_psBytes ) ); + } + + virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, TProtobufValueType *out_pTypedValue ) const OVERRIDE + { + DbgVerify( out_pTypedValue->ParseFromString( sBytes ) ); + } + + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + std::string sValue( pszValue ); + TProtobufValueType typedValue; + if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedValue ) ) + return false; + + this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); + return true; + } + + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_ps ); + + google::protobuf::TextFormat::PrintToString( this->GetTypedValueContentsFromEconAttributeValue( value ), out_ps ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSchemaAttributeType_String : public CSchemaAttributeTypeProtobufBase< GC_SCH_REFERENCE( CSchItemAttributeString ) CAttribute_String > +{ +public: +#ifdef GC_DLL + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE + { + Assert( out_pSchRecord ); + Assert( pAttrDef ); + Assert( pAttrDef->GetAttributeType() == this ); + + CSchItemAttributeString *out_psch = GetTypedSch<CSchItemAttributeString>( out_pSchRecord ); + + CAttribute_String typedValue; + this->ConvertEconAttributeValueToTypedValue( value, &typedValue ); + + // const CAttribute_String& typedValue = GetTypedValueContentsFromEconAttributeValue( value ); + + out_psch->m_ulItemID = unItemId; + out_psch->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); + WRITE_VAR_CHAR_FIELD( (*out_psch), VarCharAttrStrValue, typedValue.value().c_str() ); + } + + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + Assert( pSchRecord ); + Assert( pAttrDef->GetAttributeType() == this ); + + const CSchItemAttributeString *psch = GetTypedSch<const CSchItemAttributeString>( pSchRecord ); + + CAttribute_String typedValue; + typedValue.set_value( READ_VAR_CHAR_FIELD( (*psch), m_VarCharAttrStrValue ) ); + + pTargetItem->SetDynamicAttributeValue( pAttrDef, typedValue ); + } +#endif // GC_DLL + + // We intentionally override the convert-to-/convert-from-string functions for strings so that string literals can be + // specified in the schema, etc. without worrying about the protobuf text format. + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + CAttribute_String typedValue; + typedValue.set_value( pszValue ); + + this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); + + return true; + } + + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_ps ); + + *out_ps = this->GetTypedValueContentsFromEconAttributeValue( value ).value().c_str(); + } +}; + +void CopyStringAttributeValueToCharPointerOutput( const CAttribute_String *pValue, const char **out_pValue ) +{ + Assert( pValue ); + Assert( out_pValue ); + + *out_pValue = pValue->value().c_str(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSchemaAttributeType_DynamicRecipeComponentDefinedItem : public CSchemaAttributeTypeProtobufBase< GC_SCH_REFERENCE( CSchItemAttributeDynamicRecipeComponentDefinedItem ) CAttribute_DynamicRecipeComponent > +{ +public: +#ifdef GC_DLL + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE + { + Assert( out_pSchRecord ); + Assert( pAttrDef ); + Assert( pAttrDef->GetAttributeType() == this ); + + CSchItemAttributeDynamicRecipeComponentDefinedItem *out_psch = GetTypedSch<CSchItemAttributeDynamicRecipeComponentDefinedItem>( out_pSchRecord ); + + CAttribute_DynamicRecipeComponent typedValue; + ConvertEconAttributeValueToTypedValue( value, &typedValue ); + + out_psch->m_ulItemID = unItemId; + out_psch->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); + out_psch->m_unItemDef = typedValue.def_index(); + out_psch->m_unItemQuality = typedValue.item_quality(); + out_psch->m_unFlags = typedValue.component_flags(); + out_psch->m_unItemCount = typedValue.num_required(); + out_psch->m_unItemsFulfilled = typedValue.num_fulfilled(); + WRITE_VAR_CHAR_FIELD( (*out_psch), VarCharAttrStr, typedValue.attributes_string().c_str() ); + } + + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + Assert( pSchRecord ); + Assert( pAttrDef->GetAttributeType() == this ); + + const CSchItemAttributeDynamicRecipeComponentDefinedItem *psch = GetTypedSch<const CSchItemAttributeDynamicRecipeComponentDefinedItem>( pSchRecord ); + + CAttribute_DynamicRecipeComponent typedValue; + typedValue.set_def_index( psch->m_unItemDef ); + typedValue.set_item_quality( psch->m_unItemQuality ); + typedValue.set_component_flags( psch->m_unFlags ); + typedValue.set_attributes_string( READ_VAR_CHAR_FIELD( (*psch), m_VarCharAttrStr ) ); + typedValue.set_num_required( psch->m_unItemCount ); + typedValue.set_num_fulfilled( psch->m_unItemsFulfilled ); + + pTargetItem->SetDynamicAttributeValue( pAttrDef, typedValue ); + } + + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + std::string sValue( pszValue ); + // What's happened here is we've renamed some fields within CAttribute_DynamicRecipeComponent, + // but steam contains the strings of the old format serialized, and keeps sending them to us. + // Rather than updating steam, we're going to made a protobuff class that can accept the new + // and old formats, and put the corret values into the correct members of the new format. + + + CAttribute_DynamicRecipeComponent_COMPAT_NEVER_SERIALIZE_THIS_OUT typedCompatValue; + CAttribute_DynamicRecipeComponent typedActualValue; + +#ifdef STAGING_ONLY + auto *pActualFields = typedActualValue.descriptor(); + auto *pCompatFields = typedCompatValue.descriptor(); + for ( int i=0; i < pActualFields->field_count(); ++i ) + { + const bool bFoundField = pCompatFields->FindFieldByName( pActualFields->field( i )->name() ) != NULL; + Assert( bFoundField ); + if ( !bFoundField ) + { + EmitError( SPEW_GC, "Missing field '%s' in CAttribute_DynamicRecipeComponent_COMPAT_NEVER_SERIALIZE_THIS_OUT\n", pActualFields->field( i )->name() ); + return false; + } + } +#endif // STAGING_ONLY + + if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedCompatValue ) ) + { + EmitError( SPEW_GC, "Failed to parse recipe component into compatible protobuf\n" ); + return false; + } + + if ( typedCompatValue.has_component_flags() ) + typedActualValue.set_component_flags( typedCompatValue.component_flags() ); + else if ( typedCompatValue.has_item_flags() ) + typedActualValue.set_component_flags( typedCompatValue.item_flags() ); + else + { + EmitError( SPEW_GC, "Failed to parse component_flags. component_flags: %d, item_flags: %d\n", typedCompatValue.component_flags(), typedCompatValue.item_flags() ); + return false; + } + + if ( typedCompatValue.has_def_index() ) + typedActualValue.set_def_index( typedCompatValue.def_index() ); + else if ( typedCompatValue.has_item_def() ) + typedActualValue.set_def_index( typedCompatValue.item_def() ); + else if ( typedActualValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET ) + { + EmitError( SPEW_GC, "Failed to parse item_def. def_index: %d, item_def: %d\n", typedCompatValue.def_index(), typedCompatValue.item_def() ); + return false; + } + + typedActualValue.set_item_quality( typedCompatValue.item_quality() ); + + + + typedActualValue.set_attributes_string( typedCompatValue.attributes_string() ); + + if( typedCompatValue.has_num_required() ) + typedActualValue.set_num_required( typedCompatValue.num_required() ); + else if ( typedCompatValue.has_item_count() ) + typedActualValue.set_num_required( typedCompatValue.item_count() ); + else + { + EmitError( SPEW_GC, "Failed to parse component_flags. num_required: %d, item_count: %d\n", typedCompatValue.num_required(), typedCompatValue.item_count() ); + return false; + } + + if ( typedCompatValue.has_items_fulfilled() ) + typedActualValue.set_num_fulfilled( typedCompatValue.items_fulfilled() ); + else if ( typedCompatValue.has_num_fulfilled() ) + typedActualValue.set_num_fulfilled( typedCompatValue.num_fulfilled() ); + else + { + EmitError( SPEW_GC, "Failed to parse num_fulfilled. items_fulfilled: %d, num_fulfilled: %d\n", typedCompatValue.items_fulfilled(), typedCompatValue.num_fulfilled() ); + return false; + } + + this->ConvertTypedValueToEconAttributeValue( typedActualValue, out_pValue ); + return true; + } + +#endif // GC_DLL +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSchemaAttributeType_ItemSlotCriteria : public CSchemaAttributeTypeProtobufBase< GC_SCH_REFERENCE( CSchItemAttributeItemSlotCriteria ) CAttribute_ItemSlotCriteria > +{ +public: +#ifdef GC_DLL + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE + { + Assert( out_pSchRecord ); + Assert( pAttrDef ); + Assert( pAttrDef->GetAttributeType() == this ); + + AssertMsg( 0, "Implement this when we want this attribute to be dynamic" ); + } + + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + Assert( pSchRecord ); + Assert( pAttrDef->GetAttributeType() == this ); + + AssertMsg( 0, "Implement this when we want this attribute to be dynamic" ); + } +#endif // GC_DLL + + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + std::string sValue( pszValue ); + CAttribute_ItemSlotCriteria typedValue; + if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedValue ) ) + return false; + + this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); + + return true; + } + + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_ps ); + + this->ConvertEconAttributeValueToString( pAttrDef, value, out_ps ); + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSchemaAttributeType_WorldItemPlacement : public CSchemaAttributeTypeProtobufBase < GC_SCH_REFERENCE( CSchItemAttributeWorldItemPlacement ) CAttribute_WorldItemPlacement > +{ +public: +#ifdef GC_DLL + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE + { + Assert( out_pSchRecord ); + Assert( pAttrDef ); + Assert( pAttrDef->GetAttributeType() == this ); + + CSchItemAttributeWorldItemPlacement *out_psch = GetTypedSch< CSchItemAttributeWorldItemPlacement >( out_pSchRecord ); + + CAttribute_WorldItemPlacement typedValue; + ConvertEconAttributeValueToTypedValue( value, &typedValue ); + + out_psch->m_ulItemID = unItemId; + out_psch->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); + out_psch->m_ulOriginalItemID = typedValue.original_item_id(); + out_psch->m_fPosX = typedValue.pos_x(); + out_psch->m_fPosY = typedValue.pos_y(); + out_psch->m_fPosZ = typedValue.pos_z(); + out_psch->m_fAngX = typedValue.ang_x(); + out_psch->m_fAngY = typedValue.ang_y(); + out_psch->m_fAngZ = typedValue.ang_z(); + } + + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + Assert( pSchRecord ); + Assert( pAttrDef->GetAttributeType() == this ); + + const CSchItemAttributeWorldItemPlacement *psch = GetTypedSch< const CSchItemAttributeWorldItemPlacement >( pSchRecord ); + + CAttribute_WorldItemPlacement typedValue; + typedValue.set_original_item_id( psch->m_ulOriginalItemID ); + typedValue.set_pos_x( psch->m_fPosX ); + typedValue.set_pos_y( psch->m_fPosY ); + typedValue.set_pos_x( psch->m_fPosZ ); + typedValue.set_ang_x( psch->m_fAngX ); + typedValue.set_ang_y( psch->m_fAngY ); + typedValue.set_ang_z( psch->m_fAngZ ); + + pTargetItem->SetDynamicAttributeValue( pAttrDef, typedValue ); + } +#endif // GC_DLL + + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + CAttribute_WorldItemPlacement typedValue; + + uint32 unValue = ( pszValue ) ? atoi( pszValue ) : 0; + + // Item forcing us to create the attribute (via force_gc_to_generate) + if ( unValue == 0 ) + { + typedValue.set_original_item_id( INVALID_ITEM_ID ); + typedValue.set_pos_x( 0.f ); + typedValue.set_pos_y( 0.f ); + typedValue.set_pos_z( 0.f ); + typedValue.set_ang_x( 0.f ); + typedValue.set_ang_y( 0.f ); + typedValue.set_ang_z( 0.f ); + } + else + { + std::string sValue( pszValue ); + if ( !google::protobuf::TextFormat::ParseFromString( sValue, &typedValue ) ) + return false; + } + + this->ConvertTypedValueToEconAttributeValue( typedValue, out_pValue ); + return true; + } + + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_ps ); + + this->ConvertEconAttributeValueToString( pAttrDef, value, out_ps ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSchemaAttributeType_Float : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttributeFloat ) float > +{ +public: +#ifdef GC_DLL + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE + { + Assert( out_pSchRecord ); + Assert( pAttrDef ); + Assert( pAttrDef->GetAttributeType() == this ); + + CSchItemAttributeFloat *out_pschItemAttribute = GetTypedSch<CSchItemAttributeFloat>( out_pSchRecord ); + + // @note Tom Bui: we store the value as an unsigned integer in the DB, so just treat the field as a bunch of bits + out_pschItemAttribute->m_ulItemID = unItemId; + out_pschItemAttribute->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); + out_pschItemAttribute->m_fValue = value.asFloat; + } + + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + Assert( pSchRecord ); + Assert( pAttrDef->GetAttributeType() == this ); + + const CSchItemAttributeFloat *pschItemAttribute = GetTypedSch<const CSchItemAttributeFloat>( pSchRecord ); + + pTargetItem->SetDynamicAttributeValue( pAttrDef, pschItemAttribute->m_fValue ); + } +#endif // GC_DLL + + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + out_pValue->asFloat = Q_atof( pszValue ); + return true; + } + + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_ps ); + + *out_ps = CFmtStr( "%f", value.asFloat ).Get(); + } + + virtual void ConvertTypedValueToByteStream( const float& typedValue, ::std::string *out_psBytes ) const OVERRIDE + { + Assert( out_psBytes ); + Assert( out_psBytes->size() == 0 ); + + out_psBytes->resize( sizeof( float ) ); + *reinterpret_cast<float *>( &((*out_psBytes)[0]) ) = typedValue; // overwrite string contents (sizeof( float ) bytes) + } + + virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, float *out_pTypedValue ) const OVERRIDE + { + Assert( out_pTypedValue ); + Assert( sBytes.size() == sizeof( float ) ); + + *out_pTypedValue = *reinterpret_cast<const float *>( &sBytes[0] ); + } + + virtual bool BSupportsGameplayModificationAndNetworking() const OVERRIDE + { + return true; + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSchemaAttributeType_UInt64 : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttributeUInt64 ) uint64 > +{ +public: +#ifdef GC_DLL + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE + { + Assert( out_pSchRecord ); + Assert( pAttrDef ); + Assert( pAttrDef->GetAttributeType() == this ); + + uint64 ulValue; + ConvertEconAttributeValueToTypedValue( value, &ulValue ); + + CSchItemAttributeUInt64 *out_pschItemAttribute = GetTypedSch<CSchItemAttributeUInt64>( out_pSchRecord ); + + // @note Tom Bui: we store the value as an unsigned integer in the DB, so just treat the field as a bunch of bits + out_pschItemAttribute->m_ulItemID = unItemId; + out_pschItemAttribute->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); + out_pschItemAttribute->m_ulValue = ulValue; + } + + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + Assert( pSchRecord ); + Assert( pAttrDef->GetAttributeType() == this ); + + const CSchItemAttributeUInt64 *pschItemAttribute = GetTypedSch<const CSchItemAttributeUInt64>( pSchRecord ); + + pTargetItem->SetDynamicAttributeValue( pAttrDef, pschItemAttribute->m_ulValue ); + } +#endif // GC_DLL + + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + out_pValue->asUint32 = V_atoui64( pszValue ); + return true; + } + + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_ps ); + + uint64 ulValue; + ConvertEconAttributeValueToTypedValue( value, &ulValue ); + + *out_ps = CFmtStr( "%llu", ulValue ).Get(); + } + + virtual void ConvertTypedValueToByteStream( const uint64& typedValue, ::std::string *out_psBytes ) const OVERRIDE + { + Assert( out_psBytes ); + Assert( out_psBytes->size() == 0 ); + + out_psBytes->resize( sizeof( uint64 ) ); + *reinterpret_cast<uint64 *>( &((*out_psBytes)[0]) ) = typedValue; // overwrite string contents (sizeof( uint64 ) bytes) + } + + virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, uint64 *out_pTypedValue ) const OVERRIDE + { + Assert( out_pTypedValue ); + Assert( sBytes.size() == sizeof( uint64 ) ); + + *out_pTypedValue = *reinterpret_cast<const uint64 *>( &sBytes[0] ); + } +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CSchemaAttributeType_Default : public CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttribute ) attrib_value_t > +{ +public: +#ifdef GC_DLL + virtual bool BAssetClassExportedAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value ) const OVERRIDE + { + Assert( pAttrDef ); + + static CSchemaAttributeDefHandle pAttrib_TradableAfter( "tradable after date" ); + + // Don't include "tradable after date" if it's in the past + // See IEconItemInterface::IsTradable for the specific logic on how this affects tradability + if ( pAttrDef == pAttrib_TradableAfter && CRTime::RTime32TimeCur() > value.asUint32 ) + return false; + + return CSchemaAttributeTypeBase< GC_SCH_REFERENCE( CSchItemAttribute ) attrib_value_t >::BAssetClassExportedAttributeValue( pAttrDef, value ); + } + + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE + { + Assert( out_pSchRecord ); + Assert( pAttrDef ); + Assert( pAttrDef->GetAttributeType() == this ); + + CSchItemAttribute *out_pschItemAttribute = GetTypedSch<CSchItemAttribute>( out_pSchRecord ); + + // @note Tom Bui: we store the value as an unsigned integer in the DB, so just treat the field as a bunch of bits + out_pschItemAttribute->m_ulItemID = unItemId; + out_pschItemAttribute->m_unAttrDefIndex = pAttrDef->GetDefinitionIndex(); + out_pschItemAttribute->m_unValue = value.asUint32; + } + + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pAttrDef ); + Assert( pSchRecord ); + Assert( pAttrDef->GetAttributeType() == this ); + + const CSchItemAttribute *pschItemAttribute = GetTypedSch<const CSchItemAttribute>( pSchRecord ); + + pTargetItem->SetDynamicAttributeValue( pAttrDef, pschItemAttribute->m_unValue ); + } + + virtual void LoadOrGenerateEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount ) const OVERRIDE + { + Assert( pTargetItem ); + Assert( pTargetItem->GetItemDefinition() ); + Assert( pAttrDef ); + + // Wear is reassigned by attributes but has a default value from itemdef prefab + static CSchemaAttributeDefHandle pAttrDef_PaintkitWear( "set_item_texture_wear" ); + + // do not apply an attribute if it already exists. If the new and the old attribute value is different then we assert (and use the latest value) + attrib_value_t unValue = 0; + if ( pTargetItem->FindAttribute( pAttrDef, &unValue ) && pAttrDef != pAttrDef_PaintkitWear ) + { + AssertMsg4( unValue == staticAttrib.m_value.asUint32, + "Item id %llu (%s) attempting to generate dynamic attribute value for '%s' (%d) when attribute already exists with a different Value! This probably indicates some sort of code flow error calling LoadOrGenerateEconAttributeValue() late.", + pTargetItem->GetItemID(), pTargetItem->GetItemDefinition()->GetDefinitionName(), pAttrDef->GetDefinitionName(), pAttrDef->GetDefinitionIndex() ); + + if ( unValue == staticAttrib.m_value.asUint32 ) + return; + } + + // Could be raw integer bits or raw floating-point bits depending on where in the union we stored the value. We copy the + // bit pattern indiscriminately. + attribute_data_union_t ResultValue; + ResultValue = staticAttrib.m_value; + GenerateEconAttributeValue( pAttrDef, staticAttrib, pGameAccount, &ResultValue ); + LoadEconAttributeValue( pTargetItem, pAttrDef, ResultValue ); + } + + virtual void GenerateEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount, attribute_data_union_t* out_pValue ) const + { + Assert( pAttrDef ); + AssertMsg( pGameAccount || !staticAttrib.m_pKVCustomData, "Cannot run custom logic with no game account object! Passing in NULL for pGameAccount is only supported when we know we won't be running custom value generation code!" ); + Assert( out_pValue ); + + if( staticAttrib.m_pKVCustomData ) + { + Internal_RunCustomAttributeValueLogic( staticAttrib, pGameAccount, out_pValue ); + } + } +#endif // GC_DLL + + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_pValue ); + + if ( bEnableTerribleBackwardsCompatibilitySchemaParsingCode ) + { + // Not having any value specified is valid -- we interpret this as "default", or 0 as both an in int and a float. + out_pValue->asFloat = pszValue + ? atof( pszValue ) + : 0.0f; + } + // This is terrible backwards-compatibility code to support the pulling of values from econ asset classes. + else + { + if ( pAttrDef->IsStoredAsInteger() ) + { + out_pValue->asUint32 = (uint32)Q_atoui64( pszValue ); + } + else if ( pAttrDef->IsStoredAsFloat() ) + { + out_pValue->asFloat = Q_atof( pszValue ); + } + else + { + Assert( !"Unknown storage type for CSchemaAttributeType_Default::BConvertStringToEconAttributeValue()!" ); + return false; + } + } + + return true; + } + + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const OVERRIDE + { + Assert( pAttrDef ); + Assert( out_ps ); + + if( pAttrDef->IsStoredAsFloat() ) + { + *out_ps = CFmtStr( "%f", value.asFloat ).Get(); + } + else if( pAttrDef->IsStoredAsInteger() ) + { + *out_ps = CFmtStr( "%u", value.asUint32 ).Get(); + } + else + { + Assert( !"Unknown storage type for CSchemaAttributeType_Default::ConvertEconAttributeValueToString()!" ); + } + } + + virtual void ConvertTypedValueToByteStream( const attrib_value_t& typedValue, ::std::string *out_psBytes ) const OVERRIDE + { + Assert( out_psBytes ); + Assert( out_psBytes->size() == 0 ); + + out_psBytes->resize( sizeof( attrib_value_t ) ); + *reinterpret_cast<attrib_value_t *>( &((*out_psBytes)[0]) ) = typedValue; // overwrite string contents (sizeof( attrib_value_t ) bytes) + } + + virtual void ConvertByteStreamToTypedValue( const ::std::string& sBytes, attrib_value_t *out_pTypedValue ) const OVERRIDE + { + Assert( out_pTypedValue ); +#ifdef GC_DLL + // The GC is expected to always have internally-consistent information. + Assert( sBytes.size() == sizeof( attrib_value_t ) ); +#else + // Game clients and servers may have partially out-of-date information, or may have downloaded a new schema + // but not know how to parse an attribute of a certain type, etc. In these cases, because we know we + // aren't on the GC, temporarily failing to load these values until the client shuts down and updates + // is about the best we can hope for. + if ( sBytes.size() < sizeof( attrib_value_t ) ) + { + *out_pTypedValue = attrib_value_t(); + return; + } +#endif + + *out_pTypedValue = *reinterpret_cast<const attrib_value_t *>( &sBytes[0] ); + } + + virtual bool BSupportsGameplayModificationAndNetworking() const OVERRIDE + { + return true; + } + +private: +#ifdef GC_DLL + void Internal_RunCustomAttributeValueLogic( const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount, attribute_data_union_t* out_pValue ) const + { + AssertMsg( pGameAccount, "No game account when running custom attribute value logic!" ); + + float flValue = 0; + + const char *pszMethod = staticAttrib.m_pKVCustomData->GetString( "method", NULL ); + + if ( Q_stricmp( pszMethod, "employee_number" ) == 0 ) + { + flValue = pGameAccount->Obj().m_rtime32FirstPlayed; + } + else if ( Q_stricmp( pszMethod, "date" ) == 0 ) // Not used? + { + flValue = CRTime::RTime32TimeCur(); + } + else if ( Q_stricmp( pszMethod, "year" ) == 0 ) + { + flValue = CRTime( CRTime::RTime32TimeCur() ).GetYear(); + } + else if ( Q_stricmp( pszMethod, "gifts_given_out" ) == 0 ) + { + flValue = pGameAccount->Obj().m_unNumGiftsGiven; + } + else if ( Q_stricmp( pszMethod, "expiration_period_hours_from_now" ) == 0 ) + { + flValue = CRTime::RTime32DateAdd( CRTime::RTime32TimeCur(), staticAttrib.m_value.asFloat, k_ETimeUnitHour ); + } + else if ( Q_stricmp( pszMethod, "def index from lootlist" ) == 0 ) + { + const char* pszLootlistName = staticAttrib.m_pKVCustomData->GetString( "lootlist" ); + // Custom data stores the lootlist + const CEconLootListDefinition* pLootList = GEconItemSchema().GetLootListByName( pszLootlistName ); + + if( !pLootList ) + { + AssertMsg1( 0, "Lootlist '%s' not found when performing custom attribute logic", pszLootlistName ); + return; + } + + CUtlVector<CEconLootListDefinition::rolled_item_defs_t> vecRolledItems; + // Roll our item def + CDefaultUniformRandomStream RandomStream; + if( !pLootList->RollRandomItemsAndAdditionalItems( &RandomStream, false, &vecRolledItems ) ) + { + AssertMsg1( 0, "Error generating item defs from lootlist '%s'", pszLootlistName ); + return; + } + + // Just take the first one's def index + flValue = vecRolledItems.Head().m_pItemDef->GetDefinitionIndex(); + } + else + { + AssertMsg1( false, "Unknown value for 'method': '%s'", pszMethod ); + } + + // Put the value into the right part of the union + if ( staticAttrib.GetAttributeDefinition()->IsStoredAsFloat() ) + { + (*out_pValue).asFloat = flValue; + } + else if ( staticAttrib.GetAttributeDefinition()->IsStoredAsInteger() ) + { + (*out_pValue).asUint32 = (uint32)flValue; + } + else + { + AssertMsg1( 0, "Unknown storage type for CSchemaAttributeType_Default::Internal_RunCustomAttributeValueLogic() for attribute %s", staticAttrib.GetAttributeDefinition()->GetDefinitionName() ); + } + } +#endif +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CEconItemAttributeDefinition::CEconItemAttributeDefinition( void ) +: m_pKVAttribute( NULL ), + m_pAttrType( NULL ), + m_bHidden( false ), + m_bWebSchemaOutputForced( false ), + m_bStoredAsInteger( false ), + m_bInstanceData( false ), + m_bIsSetBonus( false ), + m_iUserGenerationType( 0 ), + m_iEffectType( ATTRIB_EFFECT_NEUTRAL ), + m_iDescriptionFormat( 0 ), + m_pszDescriptionString( NULL ), + m_pszArmoryDesc( NULL ), + m_pszDefinitionName( NULL ), + m_pszAttributeClass( NULL ), + m_ItemDefinitionTag( INVALID_ECON_TAG_HANDLE ), + m_bCanAffectMarketName( false ), + m_bCanAffectRecipeComponentName( false ) +#ifndef GC_DLL + , m_iszAttributeClass( NULL_STRING ) +#endif +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Copy constructor +//----------------------------------------------------------------------------- +CEconItemAttributeDefinition::CEconItemAttributeDefinition( const CEconItemAttributeDefinition &that ) +{ + (*this) = that; +} + + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CEconItemAttributeDefinition &CEconItemAttributeDefinition::operator=( const CEconItemAttributeDefinition &rhs ) +{ + m_nDefIndex = rhs.m_nDefIndex; + m_pAttrType = rhs.m_pAttrType; + m_bHidden = rhs.m_bHidden; + m_bWebSchemaOutputForced = rhs.m_bWebSchemaOutputForced; + m_bStoredAsInteger = rhs.m_bStoredAsInteger; + m_iUserGenerationType = rhs.m_iUserGenerationType; + m_bInstanceData = rhs.m_bInstanceData; + m_bIsSetBonus = rhs.m_bIsSetBonus; + m_iEffectType = rhs.m_iEffectType; + m_iDescriptionFormat = rhs.m_iDescriptionFormat; + m_pszDescriptionString = rhs.m_pszDescriptionString; + m_pszArmoryDesc = rhs.m_pszArmoryDesc; + m_pszDefinitionName = rhs.m_pszDefinitionName; + m_pszAttributeClass = rhs.m_pszAttributeClass; + m_ItemDefinitionTag = rhs.m_ItemDefinitionTag; + m_bCanAffectMarketName = rhs.m_bCanAffectMarketName; + m_bCanAffectRecipeComponentName = rhs.m_bCanAffectRecipeComponentName; +#ifndef GC_DLL + m_iszAttributeClass = rhs.m_iszAttributeClass; +#endif + + m_pKVAttribute = NULL; + if ( NULL != rhs.m_pKVAttribute ) + { + m_pKVAttribute = rhs.m_pKVAttribute->MakeCopy(); + + // Re-assign string pointers + m_pszDefinitionName = m_pKVAttribute->GetString("name"); + m_pszDescriptionString = m_pKVAttribute->GetString( "description_string", NULL ); + m_pszArmoryDesc = m_pKVAttribute->GetString( "armory_desc", NULL ); + m_pszAttributeClass = m_pKVAttribute->GetString( "attribute_class", NULL ); + + Assert( V_strcmp( m_pszDefinitionName, rhs.m_pszDefinitionName ) == 0 ); + Assert( V_strcmp( m_pszDescriptionString, rhs.m_pszDescriptionString ) == 0 ); + Assert( V_strcmp( m_pszArmoryDesc, rhs.m_pszArmoryDesc ) == 0 ); + Assert( V_strcmp( m_pszAttributeClass, rhs.m_pszAttributeClass ) == 0 ); + } + else + { + Assert( m_pszDefinitionName == NULL ); + Assert( m_pszDescriptionString == NULL ); + Assert( m_pszArmoryDesc == NULL ); + Assert( m_pszAttributeClass == NULL ); + } + return *this; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CEconItemAttributeDefinition::~CEconItemAttributeDefinition( void ) +{ + if ( m_pKVAttribute ) + m_pKVAttribute->deleteThis(); + m_pKVAttribute = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Initialize the attribute definition +// Input: pKVAttribute - The KeyValues representation of the attribute +// schema - The overall item schema for this attribute +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemAttributeDefinition::BInitFromKV( KeyValues *pKVAttribute, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + m_pKVAttribute = pKVAttribute->MakeCopy(); + m_nDefIndex = Q_atoi( m_pKVAttribute->GetName() ); + + m_pszDefinitionName = m_pKVAttribute->GetString("name", "(unnamed)"); + m_bHidden = m_pKVAttribute->GetInt( "hidden", 0 ) != 0; + m_bWebSchemaOutputForced = m_pKVAttribute->GetInt( "force_output_description", 0 ) != 0; + m_bStoredAsInteger = m_pKVAttribute->GetInt( "stored_as_integer", 0 ) != 0; + m_bIsSetBonus = m_pKVAttribute->GetBool( "is_set_bonus", false ); + m_bCanAffectMarketName = m_pKVAttribute->GetBool( "can_affect_market_name", false ); + m_bCanAffectRecipeComponentName = m_pKVAttribute->GetBool( "can_affect_recipe_component_name", false ); + m_iUserGenerationType = m_pKVAttribute->GetInt( "is_user_generated", 0 ); + m_iEffectType = (attrib_effect_types_t)StringFieldToInt( m_pKVAttribute->GetString("effect_type"), g_EffectTypes, ARRAYSIZE(g_EffectTypes) ); + m_iDescriptionFormat = StringFieldToInt( m_pKVAttribute->GetString("description_format"), g_AttributeDescriptionFormats, ARRAYSIZE(g_AttributeDescriptionFormats) ); + m_pszDescriptionString = m_pKVAttribute->GetString( "description_string", NULL ); + m_pszArmoryDesc = m_pKVAttribute->GetString( "armory_desc", NULL ); + m_pszAttributeClass = m_pKVAttribute->GetString( "attribute_class", NULL ); + m_bInstanceData = pKVAttribute->GetBool( "instance_data", false ); + + const char *pszTag = m_pKVAttribute->GetString( "apply_tag_to_item_definition", NULL ); + m_ItemDefinitionTag = pszTag ? GetItemSchema()->GetHandleForTag( pszTag ) : INVALID_ECON_TAG_HANDLE; + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + m_iszAttributeClass = NULL_STRING; +#endif + const char *pszAttrType = m_pKVAttribute->GetString( "attribute_type", NULL ); // NULL implies "default type" for backwards compatibility + m_pAttrType = GetItemSchema()->GetAttributeType( pszAttrType ); + + SCHEMA_INIT_CHECK( + NULL != m_pKVAttribute->FindKey( "name" ), + "Attribute definition %s: Missing required field \"name\"", m_pKVAttribute->GetName() ); + + SCHEMA_INIT_CHECK( + NULL != m_pAttrType, + "Attribute definition %s: Unable to find attribute data type '%s'", m_pszDefinitionName, pszAttrType ? pszAttrType : "(default)" ); + + if ( m_bIsSetBonus ) + { + SCHEMA_INIT_CHECK( + m_pAttrType->BSupportsGameplayModificationAndNetworking(), + "Attribute definition %s: set as set bonus attribute but does not support gameplay modification/networking!", m_pszDefinitionName ); + } + + m_unAssetClassBucket = pKVAttribute->GetInt( "asset_class_bucket", 0 ); + m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Default; + if ( char const *szRule = pKVAttribute->GetString( "asset_class_export", NULL ) ) + { + if ( !V_stricmp( szRule, "skip" ) ) + { + m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Skip; + } + else if ( !V_stricmp( szRule, "gconly" ) ) + { + m_eAssetClassAttrExportRule = EAssetClassAttrExportRule_t( k_EAssetClassAttrExportRule_GCOnly | k_EAssetClassAttrExportRule_Skip ); + } + else if ( !V_stricmp( szRule, "bucketed" ) ) + { + SCHEMA_INIT_CHECK( m_unAssetClassBucket, "Attribute definition %s: Asset class export rule '%s' is incompatible", m_pszDefinitionName, szRule ); + m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Bucketed; + } + else if ( !V_stricmp( szRule, "default" ) ) + { + m_eAssetClassAttrExportRule = k_EAssetClassAttrExportRule_Default; + } + else + { + SCHEMA_INIT_CHECK( false, "Attribute definition %s: Invalid asset class export rule '%s'", m_pszDefinitionName, szRule ); + } + } + + // Check for misuse of asset class bucket + SCHEMA_INIT_CHECK( ( !m_unAssetClassBucket || m_bInstanceData ), "Attribute definition %s: Cannot use \"asset_class_bucket\" on class-level attributes", m_pKVAttribute->GetName() ); + + + return SCHEMA_INIT_SUCCESS(); +} + +CQuestObjectiveDefinition::CQuestObjectiveDefinition( void ) + : m_pszDescriptionToken( NULL ) + , m_nDefIndex( 0 ) + , m_nPoints( 0 ) +{} + +CQuestObjectiveDefinition::~CQuestObjectiveDefinition() +{} + +bool CQuestObjectiveDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + m_nDefIndex = pKVItem->GetInt( "defindex", -1 ); + m_pszDescriptionToken = pKVItem->GetString( "description_string" ); + m_bOptional = pKVItem->GetBool( "optional", false ); + m_bAdvanced = pKVItem->GetBool( "advanced", false ); + m_nPoints = pKVItem->GetInt( "points", 0 ); + + SCHEMA_INIT_CHECK( m_nDefIndex != -1, "Quest objective missing def index" ); + SCHEMA_INIT_CHECK( m_pszDescriptionToken != NULL, "Quest objective is missing a description" ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CEconItemDefinition::CEconItemDefinition( void ) +: m_pKVItem( NULL ), +m_bEnabled( false ), +m_unMinItemLevel( 1 ), +m_unMaxItemLevel( 1 ), +m_iArmoryRemap( 0 ), +m_iStoreRemap( 0 ), +m_nItemQuality( k_unItemQuality_Any ), +m_nForcedItemQuality( k_unItemQuality_Any ), +m_nDefaultDropQuantity( 1 ), +m_bLoadOnDemand( false ), +m_pTool( NULL ), +m_rtExpiration( 0 ), +m_BundleInfo( NULL ), +#ifdef TF_CLIENT_DLL +m_unNumConcreteItems( 0 ), +#endif // TF_CLIENT_DLL +m_nPopularitySeed( 0 ), +m_pszDefinitionName( NULL ), +m_pszItemClassname( NULL ), +m_pszClassToken( NULL ), +m_pszSlotToken( NULL ), +m_pszItemBaseName( NULL ), +m_pszItemTypeName( NULL ), +m_pszItemDesc( NULL ), +m_pszArmoryDesc( NULL ), +m_pszInventoryModel( NULL ), +m_pszInventoryImage( NULL ), +m_pszHolidayRestriction( NULL ), +m_iSubType( 0 ), +m_pszBaseDisplayModel( NULL ), +m_iDefaultSkin( -1 ), +m_pszWorldDisplayModel( NULL ), +m_pszWorldExtraWearableModel( NULL ), +m_pszWorldExtraWearableViewModel( NULL ), +m_pszVisionFilteredDisplayModel( NULL ), +m_pszBrassModelOverride( NULL ), +m_bHideBodyGroupsDeployedOnly( false ), +m_bAttachToHands( false ), +m_bAttachToHandsVMOnly( false ), +m_bProperName( false ), +m_bFlipViewModel( false ), +m_bActAsWearable( false ), +m_bActAsWeapon( false ), +m_iDropType( 1 ), +m_bHidden( false ), +m_bShouldShowInArmory( false ), +m_bIsPackBundle( false ), +m_pOwningPackBundle( NULL ), +m_bIsPackItem( false ), +m_bBaseItem( false ), +m_pszItemLogClassname( NULL ), +m_pszItemIconClassname( NULL ), +m_pszDatabaseAuditTable( NULL ), +m_bImported( false ), +m_pItemSetDef( NULL ), +m_pItemCollectionDef( NULL ), +m_pItemPaintKitDef( NULL ), +m_pszArmoryRemap( NULL ), +m_pszStoreRemap( NULL ), +m_unSetItemRemapDefIndex( INVALID_ITEM_DEF_INDEX ), +m_pszXifierRemapClass( NULL ), +m_pszBaseFunctionalItemName( NULL ), +m_pszParticleSuffix( NULL ), +m_pszCollectionReference( NULL ), +m_nItemRarity( k_unItemRarity_Any ), +m_unItemSeries( 0 ), +m_bValidForShuffle( false ), +m_bValidForSelfMade( true ) +{ + for ( int team = 0; team < TEAM_VISUAL_SECTIONS; team++ ) + { + m_PerTeamVisuals[team] = NULL; + } + + m_pDictIcons = new CUtlDict< CUtlString >; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CEconItemDefinition::~CEconItemDefinition( void ) +{ + for ( int i = 0; i < ARRAYSIZE( m_PerTeamVisuals ); i++ ) + delete m_PerTeamVisuals[i]; + +#ifdef GC_DLL + m_vecPropertyGenerators.PurgeAndDeleteElements(); +#endif // GC_DLL + + if ( m_pKVItem ) + m_pKVItem->deleteThis(); + m_pKVItem = NULL; + delete m_pTool; + delete m_BundleInfo; + delete m_pDictIcons; +} + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: Stomp our base data with extra testing data specified by the player +//----------------------------------------------------------------------------- +bool CEconItemDefinition::BInitFromTestItemKVs( int iNewDefIndex, KeyValues *pKVItem, CUtlVector<CUtlString>* pVecErrors ) +{ + // The KeyValues are stored in the player entity, so we can cache our name there + + m_nDefIndex = iNewDefIndex; + m_unSetItemRemapDefIndex = m_nDefIndex; + + bool bTestingExistingItem = pKVItem->GetBool( "test_existing_item", false ); + if ( !bTestingExistingItem ) + { + m_pszDefinitionName = pKVItem->GetString( "name", NULL ); + m_pszItemBaseName = pKVItem->GetString( "name", NULL ); + +#ifdef CLIENT_DLL + pKVItem->SetString( "name", VarArgs("Test Item %d", iNewDefIndex) ); +#else + pKVItem->SetString( "name", UTIL_VarArgs("Test Item %d", iNewDefIndex) ); +#endif + + m_pszBaseDisplayModel = pKVItem->GetString( "model_player", NULL ); + m_pszVisionFilteredDisplayModel = pKVItem->GetString( "model_vision_filtered", NULL ); + m_bAttachToHands = pKVItem->GetInt( "attach_to_hands", 0 ) != 0; + + BInitVisualBlockFromKV( pKVItem ); + } + + // Handle attributes + m_vecStaticAttributes.Purge(); + int iPaintCanIndex = pKVItem->GetInt("paintcan_index", 0); + if ( iPaintCanIndex ) + { + static CSchemaAttributeDefHandle pAttrDef_PaintRGB( "set item tint RGB" ); + + const CEconItemDefinition *pCanDef = GetItemSchema()->GetItemDefinition(iPaintCanIndex); + + float flRGBVal; + if ( pCanDef && pAttrDef_PaintRGB && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pCanDef, pAttrDef_PaintRGB, &flRGBVal ) ) + { + static_attrib_t& StaticAttrib = m_vecStaticAttributes[ m_vecStaticAttributes.AddToTail() ]; + + StaticAttrib.iDefIndex = pAttrDef_PaintRGB->GetDefinitionIndex(); + StaticAttrib.m_value.asFloat = flRGBVal; // this is bad! but we're in crazy hack code for UI customization of item definitions that don't exist so + } + } + + int iUnusualEffectIndex = pKVItem->GetInt( "unusual_index", 0 ); + if ( iUnusualEffectIndex ) + { + static CSchemaAttributeDefHandle pAttrDef_AttachParticleStatic( "attach particle effect static" ); + + const attachedparticlesystem_t *pSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iUnusualEffectIndex ); + + if ( pAttrDef_AttachParticleStatic && pSystem ) + { + static_attrib_t& StaticAttrib = m_vecStaticAttributes[ m_vecStaticAttributes.AddToTail() ]; + + StaticAttrib.iDefIndex = pAttrDef_AttachParticleStatic->GetDefinitionIndex(); + StaticAttrib.m_value.asFloat = iUnusualEffectIndex; // this is bad! but we're in crazy hack code for UI customization of item definitions that don't exist so + } + } + + return true; +} + +animation_on_wearable_t *GetOrCreateAnimationActivity( perteamvisuals_t *pVisData, const char *pszActivityName ) +{ + FOR_EACH_VEC( pVisData->m_Animations, i ) + { + if ( Q_stricmp(pVisData->m_Animations[i].pszActivity, pszActivityName) == 0 ) + return &pVisData->m_Animations[i]; + } + + animation_on_wearable_t *pEntry = &pVisData->m_Animations[pVisData->m_Animations.AddToTail()]; + + pEntry->iActivity = kActivityLookup_Unknown; // We can't look it up yet, the activity list hasn't been populated. + pEntry->pszActivity = pszActivityName; + pEntry->iReplacement = kActivityLookup_Unknown; + pEntry->pszReplacement = NULL; + pEntry->pszSequence = NULL; + pEntry->pszScene = NULL; + pEntry->pszRequiredItem = NULL; + + return pEntry; +} + +activity_on_wearable_t *GetOrCreatePlaybackActivity( perteamvisuals_t *pVisData, wearableanimplayback_t iPlayback ) +{ + FOR_EACH_VEC( pVisData->m_Animations, i ) + { + if ( pVisData->m_Activities[i].iPlayback == iPlayback ) + return &pVisData->m_Activities[i]; + } + + activity_on_wearable_t *pEntry = &pVisData->m_Activities[pVisData->m_Activities.AddToTail()]; + + pEntry->iPlayback = iPlayback; + pEntry->iActivity = kActivityLookup_Unknown; // We can't look it up yet, the activity list hasn't been populated. + pEntry->pszActivity = NULL; + + return pEntry; +} + +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + +//----------------------------------------------------------------------------- +// Purpose: Handle parsing the per-team visual block from the keyvalues +//----------------------------------------------------------------------------- +void CEconItemDefinition::BInitVisualBlockFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors ) +{ + // Visuals + for ( int team = 0; team < TEAM_VISUAL_SECTIONS; team++ ) + { + m_PerTeamVisuals[team] = NULL; + + if ( !g_TeamVisualSections[team] ) + continue; + + KeyValues *pVisualsKV = pKVItem->FindKey( g_TeamVisualSections[team] ); + if ( pVisualsKV ) + { + perteamvisuals_t *pVisData = new perteamvisuals_t(); +#if defined(CLIENT_DLL) || defined(GAME_DLL) + KeyValues *pKVEntry = pVisualsKV->GetFirstSubKey(); + while ( pKVEntry ) + { + const char *pszEntry = pKVEntry->GetName(); + + if ( !Q_stricmp( pszEntry, "use_visualsblock_as_base" ) ) + { + // Start with a copy of an existing PerTeamVisuals + const char *pszString = pKVEntry->GetString(); + int nOverrideTeam = GetTeamVisualsFromString( pszString ); + if ( nOverrideTeam != -1 ) + { + *pVisData = *m_PerTeamVisuals[nOverrideTeam]; + } + else + { + pVecErrors->AddToTail( CFmtStr( "Unknown visuals block: %s", pszString ).Access() ); + } + } + else if ( !Q_stricmp( pszEntry, "attached_models" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVAttachedModelData ) + { + int iAtt = pVisData->m_AttachedModels.AddToTail(); + pVisData->m_AttachedModels[iAtt].m_iModelDisplayFlags = pKVAttachedModelData->GetInt( "model_display_flags", kAttachedModelDisplayFlag_MaskAll ); + pVisData->m_AttachedModels[iAtt].m_pszModelName = pKVAttachedModelData->GetString( "model", NULL ); + } + } + else if ( !Q_stricmp( pszEntry, "attached_models_festive" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVAttachedModelData ) + { + int iAtt = pVisData->m_AttachedModelsFestive.AddToTail(); + pVisData->m_AttachedModelsFestive[iAtt].m_iModelDisplayFlags = pKVAttachedModelData->GetInt( "model_display_flags", kAttachedModelDisplayFlag_MaskAll ); + pVisData->m_AttachedModelsFestive[iAtt].m_pszModelName = pKVAttachedModelData->GetString( "model", NULL ); + } + } + else if ( !Q_stricmp( pszEntry, "attached_particlesystems" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVAttachedParticleSystemData ) + { + int iAtt = pVisData->m_AttachedParticles.AddToTail(); + pVisData->m_AttachedParticles[iAtt].pszSystemName = pKVAttachedParticleSystemData->GetString( "system", NULL ); + pVisData->m_AttachedParticles[iAtt].pszControlPoints[0] = pKVAttachedParticleSystemData->GetString( "attachment", NULL ); + pVisData->m_AttachedParticles[iAtt].bFollowRootBone = pKVAttachedParticleSystemData->GetBool( "attach_to_rootbone" ); + pVisData->m_AttachedParticles[iAtt].iCustomType = 0; + } + } + else if ( !Q_stricmp( pszEntry, "custom_particlesystem2" ) ) + { + int iAtt = pVisData->m_AttachedParticles.AddToTail(); + pVisData->m_AttachedParticles[iAtt].pszSystemName = pKVEntry->GetString( "system", NULL ); + pVisData->m_AttachedParticles[iAtt].iCustomType = 2; + } + else if ( !Q_stricmp( pszEntry, "custom_particlesystem" ) ) + { + int iAtt = pVisData->m_AttachedParticles.AddToTail(); + pVisData->m_AttachedParticles[iAtt].pszSystemName = pKVEntry->GetString( "system", NULL ); + pVisData->m_AttachedParticles[iAtt].iCustomType = 1; + } + else if ( !Q_stricmp( pszEntry, "playback_activity" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) + { + int iPlaybackInt = StringFieldToInt( pKVSubKey->GetName(), g_WearableAnimTypeStrings, ARRAYSIZE(g_WearableAnimTypeStrings) ); + if ( iPlaybackInt >= 0 ) + { + activity_on_wearable_t *pEntry = GetOrCreatePlaybackActivity( pVisData, (wearableanimplayback_t)iPlaybackInt ); + pEntry->pszActivity = pKVSubKey->GetString(); + } + } + } + else if ( !Q_stricmp( pszEntry, "animation_replacement" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) + { + animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); + pEntry->pszReplacement = pKVSubKey->GetString(); + } + } + else if ( !Q_stricmp( pszEntry, "animation_sequence" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) + { + animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); + pEntry->pszSequence = pKVSubKey->GetString(); + } + } + else if ( !Q_stricmp( pszEntry, "animation_scene" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) + { + animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); + pEntry->pszScene = pKVSubKey->GetString(); + } + } + else if ( !Q_stricmp( pszEntry, "animation_required_item" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVSubKey ) + { + animation_on_wearable_t *pEntry = GetOrCreateAnimationActivity( pVisData, pKVSubKey->GetName() ); + pEntry->pszRequiredItem = pKVSubKey->GetString(); + } + } + else if ( !Q_stricmp( pszEntry, "player_bodygroups" ) ) + { + FOR_EACH_SUBKEY( pKVEntry, pKVBodygroupKey ) + { + const char *pszBodygroupName = pKVBodygroupKey->GetName(); + int iValue = pKVBodygroupKey->GetInt(); + + // Track bodygroup information for this item in particular. + pVisData->m_Maps.m_ModifiedBodyGroupNames.Insert( pszBodygroupName, iValue ); + + // Track global schema state. + GetItemSchema()->AssignDefaultBodygroupState( pszBodygroupName, iValue ); + } + } + else if ( !Q_stricmp( pszEntry, "skin" ) ) + { + pVisData->iSkin = pKVEntry->GetInt(); + } + else if ( !Q_stricmp( pszEntry, "use_per_class_bodygroups" ) ) + { + pVisData->bUsePerClassBodygroups = pKVEntry->GetBool(); + } + else if ( !Q_stricmp( pszEntry, "muzzle_flash" ) ) + { + pVisData->pszMuzzleFlash = pKVEntry->GetString(); + } + else if ( !Q_stricmp( pszEntry, "tracer_effect" ) ) + { + pVisData->pszTracerEffect = pKVEntry->GetString(); + } + else if ( !Q_stricmp( pszEntry, "particle_effect" ) ) + { + pVisData->pszParticleEffect = pKVEntry->GetString(); + } + else if ( !Q_strnicmp( pszEntry, "custom_sound", 12 ) ) // intentionally comparing prefixes + { + int iIndex = 0; + if ( pszEntry[12] ) + { + iIndex = clamp( atoi( &pszEntry[12] ), 0, MAX_VISUALS_CUSTOM_SOUNDS-1 ); + } + pVisData->pszCustomSounds[iIndex] = pKVEntry->GetString(); + } + else if ( !Q_stricmp( pszEntry, "material_override" ) ) + { + pVisData->pszMaterialOverride = pKVEntry->GetString(); + } + else if ( !Q_strnicmp( pszEntry, "sound_", 6 ) ) // intentionally comparing prefixes + { + int iIndex = GetWeaponSoundFromString( &pszEntry[6] ); + if ( iIndex != -1 ) + { + pVisData->pszWeaponSoundReplacements[iIndex] = pKVEntry->GetString(); + } + } + else if ( !Q_stricmp( pszEntry, "code_controlled_bodygroup" ) ) + { + const char *pBodyGroupName = pKVEntry->GetString( "bodygroup", NULL ); + const char *pFuncName = pKVEntry->GetString( "function", NULL ); + if ( pBodyGroupName && pFuncName ) + { + codecontrolledbodygroupdata_t ccbgd = { pFuncName, NULL }; + pVisData->m_Maps.m_CodeControlledBodyGroupNames.Insert( pBodyGroupName, ccbgd ); + } + } + else if ( !Q_stricmp( pszEntry, "vm_bodygroup_override" ) ) + { + pVisData->m_iViewModelBodyGroupOverride = pKVEntry->GetInt(); + } + else if ( !Q_stricmp( pszEntry, "vm_bodygroup_state_override" ) ) + { + pVisData->m_iViewModelBodyGroupStateOverride = pKVEntry->GetInt(); + } + else if ( !Q_stricmp( pszEntry, "wm_bodygroup_override" ) ) + { + pVisData->m_iWorldModelBodyGroupOverride = pKVEntry->GetInt(); + } + else if ( !Q_stricmp( pszEntry, "wm_bodygroup_state_override" ) ) + { + pVisData->m_iWorldModelBodyGroupStateOverride = pKVEntry->GetInt(); + } + + pKVEntry = pKVEntry->GetNextKey(); + } +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + KeyValues *pStylesDataKV = pVisualsKV->FindKey( "styles" ); + if ( pStylesDataKV ) + { + // Styles are only valid in the base "visuals" section. + if ( team == 0 ) + { + BInitStylesBlockFromKV( pStylesDataKV, pVisData, pVecErrors ); + } + // ...but they used to be valid everywhere, so spit out a warning if people are trying to use + // the old style of per-team styles. + else + { + pVecErrors->AddToTail( "Per-team styles blocks are no longer valid. Use \"skin_red\" and \"skin_blu\" in a style entry instead." ); + } + } + + m_PerTeamVisuals[team] = pVisData; + } + } +} + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template < typename T > +static void NthPermutation ( T *pData, unsigned int unDataCount, unsigned int unIdx ) +{ + for ( unsigned int i = 1; i < unDataCount; i++ ) + { + std::swap( pData[ unIdx % (i + 1) ], pData[ i ] ); + unIdx = unIdx / (i + 1); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemDefinition::BApplyPropertyGenerators( CEconItem *pItem ) const +{ + Assert( pItem ); + + for ( const IEconItemPropertyGenerator *pPropertyGenerator : m_vecPropertyGenerators ) + { + if ( !pPropertyGenerator->BGenerateProperties( pItem ) ) + return false; + } + + return true; +} + +#endif // GC_DLL + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDefinition::GeneratePrecacheModelStrings( bool bDynamicLoad, CUtlVector<const char *> *out_pVecModelStrings ) const +{ + Assert( out_pVecModelStrings ); + + // Add base model. + out_pVecModelStrings->AddToTail( GetBasePlayerDisplayModel() ); + + // Add styles. + if ( GetNumStyles() ) + { + for ( style_index_t i=0; i<GetNumStyles(); ++i ) + { + const CEconStyleInfo *pStyle = GetStyleInfo( i ); + Assert( pStyle ); + + pStyle->GeneratePrecacheModelStringsForStyle( out_pVecModelStrings ); + } + } + + // Precache all the attached models + for ( int team = 0; team < TEAM_VISUAL_SECTIONS; team++ ) + { + perteamvisuals_t *pPerTeamVisuals = GetPerTeamVisual( team ); + if ( !pPerTeamVisuals ) + continue; + + for ( int model = 0; model < pPerTeamVisuals->m_AttachedModels.Count(); model++ ) + { + out_pVecModelStrings->AddToTail( pPerTeamVisuals->m_AttachedModels[model].m_pszModelName ); + } + + // Festive + for ( int model = 0; model < pPerTeamVisuals->m_AttachedModelsFestive.Count(); model++ ) + { + out_pVecModelStrings->AddToTail( pPerTeamVisuals->m_AttachedModelsFestive[model].m_pszModelName ); + } + } + + if ( GetExtraWearableModel() ) + { + out_pVecModelStrings->AddToTail( GetExtraWearableModel() ); + } + + if ( GetExtraWearableViewModel() ) + { + out_pVecModelStrings->AddToTail( GetExtraWearableViewModel() ); + } + + if ( GetVisionFilteredDisplayModel() ) + { + out_pVecModelStrings->AddToTail( GetVisionFilteredDisplayModel() ); + } + + // We don't need to cache the inventory model, because it's never loaded by the game +} + +void CEconItemDefinition::GeneratePrecacheSoundStrings( bool bDynamicLoad, CUtlVector<const char *> *out_pVecSoundStrings ) const +{ + Assert( out_pVecSoundStrings ); + + for ( int iTeam = 0; iTeam < TEAM_VISUAL_SECTIONS; ++iTeam ) + { + for ( int iSound = 0; iSound < MAX_VISUALS_CUSTOM_SOUNDS; ++iSound ) + { + const char *pSoundName = GetCustomSound( iTeam, iSound ); + if ( pSoundName && pSoundName[ 0 ] != '\0' ) + { + out_pVecSoundStrings->AddToTail( pSoundName ); + } + } + } +} +#endif // #if defined(CLIENT_DLL) || defined(GAME_DLL) + +//----------------------------------------------------------------------------- +const char *CEconItemDefinition::GetDefinitionString( const char *pszKeyName, const char *pszDefaultValue ) const +{ + // !FIXME! Here we could do a dynamic lookup to apply the prefab overlay logic. + // This could save a lot of duplicated data + if ( m_pKVItem ) + return m_pKVItem->GetString( pszKeyName, pszDefaultValue ); + return pszDefaultValue; +} + +//----------------------------------------------------------------------------- +KeyValues *CEconItemDefinition::GetDefinitionKey( const char *pszKeyName ) const +{ + // !FIXME! Here we could do a dynamic lookup to apply the prefab overlay logic. + // This could save a lot of duplicated data + if ( m_pKVItem ) + return m_pKVItem->FindKey( pszKeyName ); + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Parse the styles sub-section of the visuals block. +//----------------------------------------------------------------------------- +void CEconItemDefinition::BInitStylesBlockFromKV( KeyValues *pKVStyles, perteamvisuals_t *pVisData, CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_SUBKEY( pKVStyles, pKVStyle ) + { + CEconStyleInfo *pStyleInfo = GetItemSchema()->CreateEconStyleInfo(); + Assert( pStyleInfo ); + + pStyleInfo->BInitFromKV( pKVStyle, pVecErrors ); + + pVisData->m_Styles.AddToTail( pStyleInfo ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Parse one style from the styles block. +//----------------------------------------------------------------------------- +void CEconStyleInfo::BInitFromKV( KeyValues *pKVStyle, CUtlVector<CUtlString> *pVecErrors ) +{ + enum { kInvalidSkinKey = -1, }; + + Assert( pKVStyle ); + + // A "skin" entry means "use this index for all of our teams, no matter how many we have". + int iCommonSkin = pKVStyle->GetInt( "skin", kInvalidSkinKey ); + if ( iCommonSkin != kInvalidSkinKey ) + { + for ( int i = 0; i < TEAM_VISUAL_SECTIONS; i++ ) + { + m_iSkins[i] = iCommonSkin; + } + } + + int iCommonViewmodelSkin = pKVStyle->GetInt( "v_skin", kInvalidSkinKey ); + if ( iCommonViewmodelSkin != kInvalidSkinKey ) + { + for ( int i=0; i<TEAM_VISUAL_SECTIONS; i++ ) + { + m_iViewmodelSkins[i] = iCommonViewmodelSkin; + } + } + + // If we don't have a base entry, we look for a unique entry for each team. This will be + // handled in a subclass if necessary. + + // Are we hiding additional bodygroups when this style is active? + KeyValues *pKVHideBodygroups = pKVStyle->FindKey( "additional_hidden_bodygroups" ); + if ( pKVHideBodygroups ) + { + FOR_EACH_SUBKEY( pKVHideBodygroups, pKVBodygroup ) + { + m_vecAdditionalHideBodygroups.AddToTail( pKVBodygroup->GetName() ); + } + } + + // Remaining common properties. + m_pszName = pKVStyle->GetString( "name", "#TF_UnknownStyle" ); + m_pszBasePlayerModel = pKVStyle->GetString( "model_player", NULL ); + m_bIsSelectable = pKVStyle->GetBool( "selectable", true ); + m_pszInventoryImage = pKVStyle->GetString( "image_inventory", NULL ); + + KeyValues *pKVBodygroup = pKVStyle->FindKey( "bodygroup" ); + if ( pKVBodygroup ) + { + m_pszBodygroupName = pKVBodygroup->GetString( "name", NULL ); + Assert( m_pszBodygroupName ); + m_iBodygroupSubmodelIndex = pKVBodygroup->GetInt( "submodel_index", -1 ); + Assert( m_iBodygroupSubmodelIndex != -1 ); + } +} + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconStyleInfo::GeneratePrecacheModelStringsForStyle( CUtlVector<const char *> *out_pVecModelStrings ) const +{ + Assert( out_pVecModelStrings ); + + if ( GetBasePlayerDisplayModel() != NULL ) + { + out_pVecModelStrings->AddToTail( GetBasePlayerDisplayModel() ); + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Item definition initialization helpers. +//----------------------------------------------------------------------------- +static void RecursiveInheritKeyValues( KeyValues *out_pValues, KeyValues *pInstance ) +{ + KeyValues *pPrevSubKey = NULL; + for ( KeyValues * pSubKey = pInstance->GetFirstSubKey(); pSubKey != NULL; pPrevSubKey = pSubKey, pSubKey = pSubKey->GetNextKey() ) + { + // If this assert triggers, you have an item that uses a prefab but has multiple keys with the same name + AssertMsg2 ( !pPrevSubKey || pPrevSubKey->GetNameSymbol() != pSubKey->GetNameSymbol(), + "Item definition \"%s\" has multiple attributes of the same name (%s) can't use prefabs", pInstance->GetName(), pSubKey->GetName() ); + + KeyValues::types_t eType = pSubKey->GetDataType(); + switch ( eType ) + { + case KeyValues::TYPE_STRING: out_pValues->SetString( pSubKey->GetName(), pSubKey->GetString() ); break; + case KeyValues::TYPE_INT: out_pValues->SetInt( pSubKey->GetName(), pSubKey->GetInt() ); break; + case KeyValues::TYPE_FLOAT: out_pValues->SetFloat( pSubKey->GetName(), pSubKey->GetFloat() ); break; + case KeyValues::TYPE_WSTRING: out_pValues->SetWString( pSubKey->GetName(), pSubKey->GetWString() ); break; + case KeyValues::TYPE_COLOR: out_pValues->SetColor( pSubKey->GetName(), pSubKey->GetColor() ) ; break; + case KeyValues::TYPE_UINT64: out_pValues->SetUint64( pSubKey->GetName(), pSubKey->GetUint64() ) ; break; + + // "NONE" means "KeyValues" + case KeyValues::TYPE_NONE: + { + // We may already have this part of the tree to stuff data into/overwrite, or we + // may have to make a new block. + KeyValues *pNewChild = out_pValues->FindKey( pSubKey->GetName() ); + if ( !pNewChild ) + { + pNewChild = out_pValues->CreateNewKey(); + pNewChild->SetName( pSubKey->GetName() ); + } + + RecursiveInheritKeyValues( pNewChild, pSubKey ); + break; + } + + case KeyValues::TYPE_PTR: + default: + Assert( !"Unhandled data type for KeyValues inheritance!" ); + break; + } + } +} + +void MergeDefinitionPrefab( KeyValues *pKVWriteItem, KeyValues *pKVSourceItem ) +{ + Assert( pKVWriteItem ); + Assert( pKVSourceItem ); + + const char *svPrefabName = pKVSourceItem->GetString( "prefab", NULL ); + + if ( svPrefabName ) + { + CUtlStringList vecPrefabs; + + Q_SplitString( svPrefabName, " ", vecPrefabs ); + + // Iterate backwards so adjectives get applied over the noun prefab + // e.g. wet scared cat would apply cat first, then scared and wet. + FOR_EACH_VEC_BACK( vecPrefabs, i ) + { + KeyValues *pKVPrefab = GetItemSchema()->FindDefinitionPrefabByName( vecPrefabs[i] ); + AssertMsg1( pKVPrefab, "Unable to find prefab \"%s\".", vecPrefabs[i] ); + + if ( pKVPrefab ) + { + MergeDefinitionPrefab( pKVWriteItem, pKVPrefab ); + } + } + } + + RecursiveInheritKeyValues( pKVWriteItem, pKVSourceItem ); +} + +KeyValues *CEconItemSchema::FindDefinitionPrefabByName( const char *pszPrefabName ) const +{ + int iIndex = m_mapDefinitionPrefabs.Find( pszPrefabName ); + if ( m_mapDefinitionPrefabs.IsValidIndex( iIndex ) ) + return m_mapDefinitionPrefabs[iIndex]; + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemSchema::FindStringTableEntry( const char *pszTableName, int iIndex ) const +{ + SchemaStringTableDict_t::IndexType_t i = m_dictStringTable.Find( pszTableName ); + if ( !m_dictStringTable.IsValidIndex( i ) ) + return NULL; + + const CUtlVector< schema_string_table_entry_t >& vec = *m_dictStringTable[i]; + FOR_EACH_VEC( vec, j ) + { + if ( vec[j].m_iIndex == iIndex ) + return vec[j].m_pszStr; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize the item definition +// Input: pKVItem - The KeyValues representation of the item +// schema - The overall item schema for this item +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +#ifdef GC_DLL + GCConVar gc_steam_payment_rules_kv_key( "gc_steam_payment_rules_kv_key", "payment_rules" ); +#endif // GC_DLL + + +#if defined( WITH_STREAMABLE_WEAPONS ) +#if defined( CLIENT_DLL ) + ConVar tf_loadondemand_default("cl_loadondemand_default", "1", FCVAR_ARCHIVE | FCVAR_CLIENTDLL, "The default value for whether items should be delay loaded (1) or loaded now (0)."); +#elif defined( GAME_DLL ) + // The server doesn't load on demand by default because it can crash sometimes when this is set. We need to run that down, but in the meantime + // we just have it load on demand. + ConVar tf_loadondemand_default("sv_loadondemand_default", "0", FCVAR_ARCHIVE | FCVAR_GAMEDLL, "The default value for whether items should be delay loaded (1) or loaded now (0)."); +#elif defined( GC_DLL ) + GCConVar tf_loadondemand_default("gc_loadondemand_default", "1", FCVAR_ARCHIVE, "The default value for whether items should be delay loaded (1) or loaded now (0)."); +#else +#error "Need to add support for streamable weapons to this configuration, or disable streamable weapons here." +#endif +#endif // WITH_STREAMABLE_WEAPONS + +bool CEconItemDefinition::BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + // Set standard members + m_pKVItem = new KeyValues( pKVItem->GetName() ); + MergeDefinitionPrefab( m_pKVItem, pKVItem ); + m_bEnabled = m_pKVItem->GetBool( "enabled" ); + + // initializing this one first so that it will be available for all the errors below + m_pszDefinitionName = m_pKVItem->GetString( "name", NULL ); + +#if defined( WITH_STREAMABLE_WEAPONS ) + bool bGotDefault = false; + m_bLoadOnDemand = m_pKVItem->GetBool( "loadondemand", tf_loadondemand_default.GetBool(), &bGotDefault ); + + // This logging is useful for tracking down bugs that crop up because we've (possibly) swapped the default value for loadondemand. + // But it can be removed once we're satisfied there aren't any bugs as a result of the change (when we cleanup WITH_STREAMABLE_WEAPONS). + if (bGotDefault) + { + DevMsg(10, "Item %s received default value for loadondemand\n", m_pszDefinitionName); + } +#else + // Keep the old behavior, which is that loadondemand is defaulted to false. + m_bLoadOnDemand = m_pKVItem->GetBool("loadondemand"); +#endif + + m_nDefIndex = Q_atoi( m_pKVItem->GetName() ); + m_unMinItemLevel = (uint32)m_pKVItem->GetInt( "min_ilevel", GetItemSchema()->GetMinLevel() ); + m_unMaxItemLevel = (uint32)m_pKVItem->GetInt( "max_ilevel", GetItemSchema()->GetMaxLevel() ); + m_nDefaultDropQuantity = m_pKVItem->GetInt( "default_drop_quantity", 1 ); + + m_nPopularitySeed = m_pKVItem->GetInt( "popularity_seed", 0 ); + + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + // We read this manually here in the game dlls. The GC reads it below while checking the global schema. + GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "item_quality" ), &m_nItemQuality ); + GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "forced_item_quality" ), &m_nForcedItemQuality ); +#endif + + // Check for required fields + SCHEMA_INIT_CHECK( + NULL != m_pKVItem->FindKey( "name" ), + "Item definition %s: Missing required field \"name\"", m_pKVItem->GetName() ); + + SCHEMA_INIT_CHECK( + NULL != m_pKVItem->FindKey( "item_class" ), + "Item definition %s: Missing required field \"item_class\"", m_pKVItem->GetName() ); + + // Check value ranges + SCHEMA_INIT_CHECK( + m_pKVItem->GetInt( "min_ilevel" ) >= 0, + "Item definition %s: \"min_ilevel\" must be greater than or equal to 0", GetDefinitionName() ); + + SCHEMA_INIT_CHECK( + m_pKVItem->GetInt( "max_ilevel" ) >= 0, + "Item definition %s: \"max_ilevel\" must be greater than or equal to 0", GetDefinitionName() ); + + // Check for consistency +#ifdef GC_DLL + // We don't do these consistency checks in the game, because it doesn't have the data to do them + SCHEMA_INIT_CHECK( + m_unMinItemLevel >= GetItemSchema()->GetMinLevel(), + "Item definition %s: min_ilevel (%d) must be greater or equal to Minimum Item Level (%d)", GetDefinitionName(), m_unMinItemLevel, GetItemSchema()->GetMinLevel() ); + + SCHEMA_INIT_CHECK( + m_unMinItemLevel <= m_unMaxItemLevel, + "Item definition %s: min_ilevel (%d) must be greater or equal to min_ilevel (%d)", GetDefinitionName(), m_unMaxItemLevel, m_unMinItemLevel ); + + SCHEMA_INIT_CHECK( + m_unMaxItemLevel <= GetItemSchema()->GetMaxLevel(), + "Item definition %s: max_ilevel (%d) must be less than or equal to Maximum Item Level (%d)", GetDefinitionName(), m_unMaxItemLevel, GetItemSchema()->GetMaxLevel() ); + + // Read the item quality + if ( m_pKVItem->FindKey( "item_quality" ) ) + { + SCHEMA_INIT_CHECK( + GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "item_quality" ), &m_nItemQuality ), + "Item definition %s: Undefined item_quality \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "item_quality" ) ); + } + if ( m_pKVItem->FindKey( "forced_item_quality" ) ) + { + SCHEMA_INIT_CHECK( + GetItemSchema()->BGetItemQualityFromName( m_pKVItem->GetString( "forced_item_quality" ), &m_nForcedItemQuality ), + "Item definition %s: Undefined item_quality \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "forced_item_quality" ) ); + } +#endif + + // Rarity + // Get Index from this string and save the index + if ( m_pKVItem->FindKey( "item_rarity" ) ) + { + SCHEMA_INIT_CHECK( + GetItemSchema()->BGetItemRarityFromName( m_pKVItem->GetString( "item_rarity" ), &m_nItemRarity ), + "Item definition %s: Undefined item_rarity \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "item_rarity" ) ); + } + + if ( m_pKVItem->FindKey( "item_series" ) ) + { + // Make sure this is a valid series + SCHEMA_INIT_CHECK( + GetItemSchema()->BGetItemSeries( m_pKVItem->GetString( "item_series" ), &m_unItemSeries ), + "Item definition %s: Undefined item_series \"%s\"", GetDefinitionName(), m_pKVItem->GetString( "item_series" ) ); + } + + // Get the item class + m_pszItemClassname = m_pKVItem->GetString( "item_class", NULL ); + + m_pszClassToken = m_pKVItem->GetString( "class_token_id", NULL ); + m_pszSlotToken = m_pKVItem->GetString( "slot_token_id", NULL ); + + // expiration data + const char *pchExpiration = m_pKVItem->GetString( "expiration_date", NULL ); + if( pchExpiration && pchExpiration[0] ) + { + if ( pchExpiration[0] == '!' ) + { + m_rtExpiration = GetItemSchema()->GetCustomExpirationDate( &pchExpiration[1] ); + SCHEMA_INIT_CHECK( + m_rtExpiration != k_RTime32Nil, + "Unknown/malformed expiration_date string \"%s\" in item %s.", pchExpiration, m_pszDefinitionName ); + } + else + { + m_rtExpiration = CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pchExpiration ); + +#ifdef GC_DLL + // Check that if we convert back to a string, we get the same value + char rtimeBuf[k_RTimeRenderBufferSize]; + SCHEMA_INIT_CHECK( + Q_strcmp( CRTime::RTime32ToString( m_rtExpiration, rtimeBuf ), pchExpiration ) == 0, + "Malformed expiration_date \"%s\" for expiration_date in item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\". Input: %s Output: %s InputTime: %u LocalTime: %u Timezone: %lu", pchExpiration, m_pszDefinitionName, pchExpiration, rtimeBuf, m_rtExpiration, CRTime::RTime32TimeCur(), timezone ); +#else + // Check that if we convert back to a string, we get the same value. Emit an error, but don't fail in the game code + char rtimeBuf[k_RTimeRenderBufferSize]; + if ( Q_strcmp( CRTime::RTime32ToString( m_rtExpiration, rtimeBuf ), pchExpiration ) != 0 ) + { +#if ( defined( _MSC_VER ) && _MSC_VER >= 1900 ) +#define timezone _timezone +#define daylight _daylight +#endif + Assert( false ); + Warning( "Malformed expiration_date \"%s\" for expiration_date in item %s. Must be of the form \"YYYY-MM-DD hh:mm:ss\". Input: %s Output: %s InputTime: %u LocalTime: %u Timezone: %lu\n", pchExpiration, m_pszDefinitionName, pchExpiration, rtimeBuf, m_rtExpiration, CRTime::RTime32TimeCur(), timezone ); + } +#endif + } + } + + // Display data + m_pszItemBaseName = m_pKVItem->GetString( "item_name", "" ); // non-NULL to ensure we can sort + m_pszItemTypeName = m_pKVItem->GetString( "item_type_name", "" ); // non-NULL to ensure we can sort + m_pszItemDesc = m_pKVItem->GetString( "item_description", NULL ); + m_pszArmoryDesc = m_pKVItem->GetString( "armory_desc", NULL ); + m_pszInventoryModel = m_pKVItem->GetString( "model_inventory", NULL ); + m_pszInventoryImage = m_pKVItem->GetString( "image_inventory", NULL ); + + const char* pOverlay = m_pKVItem->GetString( "image_inventory_overlay", NULL ); + if ( pOverlay ) + { + m_pszInventoryOverlayImages.AddToTail( pOverlay ); + } + pOverlay = m_pKVItem->GetString( "image_inventory_overlay2", NULL ); + if ( pOverlay ) + { + m_pszInventoryOverlayImages.AddToTail( pOverlay ); + } + + m_iInventoryImagePosition[0] = atoi( m_pKVItem->GetString( "image_inventory_pos_x", "0" ) ); + m_iInventoryImagePosition[1] = atoi( m_pKVItem->GetString( "image_inventory_pos_y", "0" ) ); + m_iInventoryImageSize[0] = atoi( m_pKVItem->GetString( "image_inventory_size_w", "128" ) ); + m_iInventoryImageSize[1] = atoi( m_pKVItem->GetString( "image_inventory_size_h", "82" ) ); + m_iInspectPanelDistance = m_pKVItem->GetInt( "inspect_panel_dist", 70 ); + m_pszHolidayRestriction = m_pKVItem->GetString( "holiday_restriction", NULL ); + m_nVisionFilterFlags = m_pKVItem->GetInt( "vision_filter_flags", 0 ); + m_iSubType = atoi( m_pKVItem->GetString( "subtype", "0" ) ); + m_pszBaseDisplayModel = m_pKVItem->GetString( "model_player", NULL ); + m_iDefaultSkin = m_pKVItem->GetInt( "default_skin", -1 ); + m_pszWorldDisplayModel = m_pKVItem->GetString( "model_world", NULL ); // Not the ideal method. c_models are better, but this is to solve a retrofit problem with the sticky launcher. + m_pszWorldExtraWearableModel = m_pKVItem->GetString( "extra_wearable", NULL ); + m_pszWorldExtraWearableViewModel = m_pKVItem->GetString( "extra_wearable_vm", NULL ); + m_pszVisionFilteredDisplayModel = pKVItem->GetString( "model_vision_filtered", NULL ); + m_pszBrassModelOverride = m_pKVItem->GetString( "brass_eject_model", NULL ); + m_bHideBodyGroupsDeployedOnly = m_pKVItem->GetBool( "hide_bodygroups_deployed_only" ); + m_bAttachToHands = m_pKVItem->GetInt( "attach_to_hands", 0 ) != 0; + m_bAttachToHandsVMOnly = m_pKVItem->GetInt( "attach_to_hands_vm_only", 0 ) != 0; + m_bProperName = m_pKVItem->GetInt( "propername", 0 ) != 0; + m_bFlipViewModel = m_pKVItem->GetInt( "flip_viewmodel", 0 ) != 0; + m_bActAsWearable = m_pKVItem->GetInt( "act_as_wearable", 0 ) != 0; + m_bActAsWeapon = m_pKVItem->GetInt( "act_as_weapon", 0 ) != 0; + m_bIsTool = m_pKVItem->GetBool( "is_tool", 0 ) || ( GetItemClass() && !V_stricmp( GetItemClass(), "tool" ) ); + m_iDropType = StringFieldToInt( m_pKVItem->GetString("drop_type"), g_szDropTypeStrings, ARRAYSIZE(g_szDropTypeStrings) ); + m_pszCollectionReference = m_pKVItem->GetString( "collection_reference", NULL ); + + // Creation data + m_bHidden = m_pKVItem->GetInt( "hidden", 0 ) != 0; + m_bShouldShowInArmory = m_pKVItem->GetInt( "show_in_armory", 0 ) != 0; + m_bBaseItem = m_pKVItem->GetInt( "baseitem", 0 ) != 0; + m_pszItemLogClassname = m_pKVItem->GetString( "item_logname", NULL ); + m_pszItemIconClassname = m_pKVItem->GetString( "item_iconname", NULL ); + m_pszDatabaseAuditTable = m_pKVItem->GetString( "database_audit_table", NULL ); + m_bImported = m_pKVItem->FindKey( "import_from" ) != NULL; + + // Tool data + m_pTool = NULL; + KeyValues *pToolDataKV = m_pKVItem->FindKey( "tool" ); + if ( pToolDataKV ) + { + const char *pszType = pToolDataKV->GetString( "type", NULL ); + SCHEMA_INIT_CHECK( pszType != NULL, "Tool '%s' missing required type.", m_pKVItem->GetName() ); + + // Common-to-all-tools settings. + const char *pszUseString = pToolDataKV->GetString( "use_string", NULL ); + const char *pszUsageRestriction = pToolDataKV->GetString( "restriction", NULL ); + KeyValues *pToolUsageKV = pToolDataKV->FindKey( "usage" ); + + // Common-to-all-tools usage capability flags. + item_capabilities_t usageCapabilities = (item_capabilities_t)ITEM_CAP_TOOL_DEFAULT; + KeyValues *pToolUsageCapsKV = pToolDataKV->FindKey( "usage_capabilities" ); + if ( pToolUsageCapsKV ) + { + KeyValues *pEntry = pToolUsageCapsKV->GetFirstSubKey(); + while ( pEntry ) + { + ParseCapability( usageCapabilities, pEntry ); + pEntry = pEntry->GetNextKey(); + } + } + + m_pTool = GetItemSchema()->CreateEconToolImpl( pszType, pszUseString, pszUsageRestriction, usageCapabilities, pToolUsageKV ); + SCHEMA_INIT_CHECK( m_pTool != NULL, "Unable to create tool implementation for '%s', of type '%s'.", m_pKVItem->GetName(), pszType ); + } + + // Bundle + KeyValues *pBundleDataKV = m_pKVItem->FindKey( "bundle" ); + if ( pBundleDataKV ) + { + m_BundleInfo = new bundleinfo_t(); + FOR_EACH_SUBKEY( pBundleDataKV, pKVCurItem ) + { + CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pKVCurItem->GetName() ); + SCHEMA_INIT_CHECK( pItemDef != NULL, "Unable to find item definition '%s' for bundle '%s'.", pKVCurItem->GetName(), m_pszDefinitionName ); + + m_BundleInfo->vecItemDefs.AddToTail( pItemDef ); + } + + // Only check for pack bundle if the item is actually a bundle - note that we could do this programatically by checking that all items in the bundle are flagged as a "pack item" - but for now the bundle needs to be explicitly flagged as a pack bundle. + m_bIsPackBundle = m_pKVItem->GetInt( "is_pack_bundle", 0 ) != 0; + } + + // capabilities + m_iCapabilities = (item_capabilities_t)ITEM_CAP_DEFAULT; + KeyValues *pCapsKV = m_pKVItem->FindKey( "capabilities" ); + if ( pCapsKV ) + { + KeyValues *pEntry = pCapsKV->GetFirstSubKey(); + while ( pEntry ) + { + ParseCapability( m_iCapabilities, pEntry ); + pEntry = pEntry->GetNextKey(); + } + } + + // item_set + SCHEMA_INIT_CHECK( (!m_pKVItem->GetString( "item_set", NULL )), "Item definition '%s' specifies deprecated \"item_set\" field. Items sets are now specified only in the set itself, not on the definition.", GetDefinitionName() ); + + const char *pszSetItemRemapDefIndexName = m_pKVItem->GetString( "set_item_remap", NULL ); + if ( pszSetItemRemapDefIndexName ) + { + const CEconItemDefinition *pRemapItemDef = GetItemSchema()->GetItemDefinitionByName( pszSetItemRemapDefIndexName ); + m_unSetItemRemapDefIndex = pRemapItemDef ? pRemapItemDef->GetDefinitionIndex() : INVALID_ITEM_DEF_INDEX; + + SCHEMA_INIT_CHECK( m_unSetItemRemapDefIndex != INVALID_ITEM_DEF_INDEX, "Unable to find set item remap definition '%s' for '%s'.", pszSetItemRemapDefIndexName, GetDefinitionName() ); + SCHEMA_INIT_CHECK( m_unSetItemRemapDefIndex != GetDefinitionIndex(), "Unable to set set item remap for definition '%s' to itself.", GetDefinitionName() ); + } + else + { + m_unSetItemRemapDefIndex = GetDefinitionIndex(); + } + + // cache item map names + m_pszArmoryRemap = m_pKVItem->GetString( "armory_remap", NULL ); + m_pszStoreRemap = m_pKVItem->GetString( "store_remap", NULL ); + + m_pszXifierRemapClass = m_pKVItem->GetString( "xifier_class_remap", NULL ); + m_pszBaseFunctionalItemName = m_pKVItem->GetString( "base_item_name", "" ); + m_pszParticleSuffix = m_pKVItem->GetString( "particle_suffix", NULL ); + + m_bValidForShuffle = m_pKVItem->GetBool( "valid_for_shuffle", false ); + m_bValidForSelfMade = m_pKVItem->GetBool( "valid_for_self_made", true ); + + // Init our visuals blocks. + BInitVisualBlockFromKV( m_pKVItem, pVecErrors ); + + // Calculate our equip region mask. + { + m_unEquipRegionMask = 0; + m_unEquipRegionConflictMask = 0; + + // Our equip region will come from one of two places -- either we have an "equip_regions" (plural) section, + // in which case we have any number of regions specified; or we have an "equip_region" (singular) section + // which will have one and exactly one region. If we have "equip_regions" (plural), we ignore whatever is + // in "equip_region" (singular). + // + // Yes, this is sort of dumb. + CUtlVector<const char *> vecEquipRegionNames; + + KeyValues *pKVMultiEquipRegions = m_pKVItem->FindKey( "equip_regions" ), + *pKVSingleEquipRegion = m_pKVItem->FindKey( "equip_region" ); + + // Maybe we have multiple entries? + if ( pKVMultiEquipRegions ) + { + for ( KeyValues *pKVRegion = pKVMultiEquipRegions->GetFirstSubKey(); pKVRegion; pKVRegion = pKVRegion->GetNextKey() ) + { + vecEquipRegionNames.AddToTail( pKVRegion->GetName() ); + } + } + // This is our one-and-only-one equip region. + else if ( pKVSingleEquipRegion ) + { + const char *pEquipRegionName = pKVSingleEquipRegion->GetString( (const char *)NULL, NULL ); + if ( pEquipRegionName ) + { + vecEquipRegionNames.AddToTail( pEquipRegionName ); + } + } + + // For each of our regions, add to our conflict mask both ourself and all the regions + // that we conflict with. + FOR_EACH_VEC( vecEquipRegionNames, i ) + { + const char *pszEquipRegionName = vecEquipRegionNames[i]; + equip_region_mask_t unThisRegionMask = GetItemSchema()->GetEquipRegionMaskByName( pszEquipRegionName ); + + SCHEMA_INIT_CHECK( + unThisRegionMask != 0, + "Item definition %s: Unable to find equip region mask for region named \"%s\"", GetDefinitionName(), vecEquipRegionNames[i] ); + + m_unEquipRegionMask |= GetItemSchema()->GetEquipRegionBitMaskByName( pszEquipRegionName ); + m_unEquipRegionConflictMask |= unThisRegionMask; + } + } + + // Single-line static attribute parsing. + { + KeyValues *pKVStaticAttrsKey = m_pKVItem->FindKey( "static_attrs" ); + if ( pKVStaticAttrsKey ) + { + FOR_EACH_SUBKEY( pKVStaticAttrsKey, pKVKey ) + { + static_attrib_t staticAttrib; + + SCHEMA_INIT_SUBSTEP( staticAttrib.BInitFromKV_SingleLine( GetDefinitionName(), pKVKey, pVecErrors, false ) ); + m_vecStaticAttributes.AddToTail( staticAttrib ); + + // Does this attribute specify a tag to apply to this item definition? + Assert( staticAttrib.GetAttributeDefinition() ); + } + } + } + + // Old style attribute parsing. Really only useful now for GC-generated attributes. + KeyValues *pKVAttribKey = m_pKVItem->FindKey( "attributes" ); + if ( pKVAttribKey ) + { + FOR_EACH_SUBKEY( pKVAttribKey, pKVKey ) + { + static_attrib_t staticAttrib; + + SCHEMA_INIT_SUBSTEP( staticAttrib.BInitFromKV_MultiLine( GetDefinitionName(), pKVKey, pVecErrors ) ); + m_vecStaticAttributes.AddToTail( staticAttrib ); + + // Does this attribute specify a tag to apply to this item definition? + Assert( staticAttrib.GetAttributeDefinition() ); + } + } + + // Initialize tags based on all static attributes for this item. + for ( const static_attrib_t& attr : m_vecStaticAttributes ) + { + const econ_tag_handle_t tag = attr.GetAttributeDefinition()->GetItemDefinitionTag(); + if ( tag != INVALID_ECON_TAG_HANDLE ) + { + m_vecTags.AddToTail( tag ); + } + } + + // Auto-generate tags based on capabilities. + for ( int i = 0; i < NUM_ITEM_CAPS; i++ ) + { + if ( m_iCapabilities & (1 << i) ) + { + m_vecTags.AddToTail( GetItemSchema()->GetHandleForTag( CFmtStr( "auto__cap_%s", g_Capabilities[i] ).Get() ) ); + } + } + + // Initialize used-specified tags for this item if present. + KeyValues *pKVTags = m_pKVItem->FindKey( "tags" ); + if ( pKVTags ) + { + FOR_EACH_SUBKEY( pKVTags, pKVTag ) + { + m_vecTags.AddToTail( GetItemSchema()->GetHandleForTag( pKVTag->GetName() ) ); + } + } + +#ifdef GC_DLL + SCHEMA_INIT_SUBSTEP( BCommonInitPropertyGeneratorsFromKV( GetDefinitionName(), &m_vecPropertyGenerators, m_pKVItem->FindKey( "property_generators" ), pVecErrors ) ); + + // Parse payment rules on the GC if any exist. + KeyValues *pKVPaymentRules = m_pKVItem->FindKey( gc_steam_payment_rules_kv_key.GetString() ); + if ( pKVPaymentRules ) + { + FOR_EACH_TRUE_SUBKEY( pKVPaymentRules, pKVRule ) + { + econ_item_payment_rule_t rule; + + const bool bFoundPaymentRule = BGetPaymentRule( pKVRule, &rule.m_eRuleType, &rule.m_RevenueShare ); + SCHEMA_INIT_CHECK( bFoundPaymentRule, "Item definition '%s': payment rule %s didn't specify a known payment rule type", GetDefinitionName(), pKVRule->GetName() ); + + // Allow us to override some of our checks if we want to claim we really know what we're doing. + if ( !pKVRule->FindKey( "sanity_check_override" ) ) + { + SCHEMA_INIT_CHECK( rule.m_RevenueShare > 0.0, "Item definition '%s': payment rule %s has invalid revenue share %0.2f", GetDefinitionName(), pKVRule->GetName(), rule.m_RevenueShare ); + + // Ordinarily, bundles can only specify the "bundle_revenue_share" payment rule type. However, for backwards compatability + // with previous payment rules that pre-date the Workshop and had manual percentages set offline, we allow people who know + // what they're doing to specify the "sanity_check_override" key and then use custom rules. + SCHEMA_INIT_CHECK( (rule.m_eRuleType == kPaymentRule_Bundle) == IsBundle(), "Item definition '%s': payment rule %s has invalid bundle rules", GetDefinitionName(), pKVRule->GetName() ); + } + + KeyValues *pPaymentRuleForItemdef = pKVRule->FindKey( "payment_rule_for_itemdef" ); + SCHEMA_INIT_CHECK( pPaymentRuleForItemdef && ( pPaymentRuleForItemdef->GetInt() == m_nDefIndex ), "Item definition '%s': payment rule %s has invalid payment_rule_for_itemdef", GetDefinitionName(), pKVRule->GetName() ); + + KeyValues *pKVMultiTargets = pKVRule->FindKey( "targets" ); + KeyValues *pKVSingleTarget = pKVRule->FindKey( "target" ); + SCHEMA_INIT_CHECK( pKVMultiTargets == NULL || pKVSingleTarget == NULL, "Item definition '%s': payment rule %s specifies both single- and multi-targets", GetDefinitionName(), pKVRule->GetName() ); + + if ( pKVMultiTargets ) + { + FOR_EACH_SUBKEY( pKVMultiTargets, pKVTarget ) + { + rule.m_vecValues.AddToTail( (uint64)Q_atoi64( pKVTarget->GetName() ) ); + } + } + + if ( pKVSingleTarget ) + { + rule.m_vecValues.AddToTail( pKVSingleTarget->GetUint64() ); + } + + // We expect bundles to have no associated account data at all -- their payment processing + // is done by splitting the total value of the bundle between each of the items in it. All + // non-bundle payment rules require at least one data entry. + SCHEMA_INIT_CHECK( (rule.m_eRuleType == kPaymentRule_Bundle) == (rule.m_vecValues.Count() == 0), "Item definition '%s': payment rule %s has invalid number of target entries %i", GetDefinitionName(), pKVRule->GetName(), rule.m_vecValues.Count() ); + + // It doesn't make any sense to have multiple entries for a bundle. We expect their to be one + // rule, and that's "process this like a bundle". + if ( rule.m_eRuleType == kPaymentRule_Bundle ) + { + SCHEMA_INIT_CHECK( m_vecPaymentRules.Count() == 0, "Item definition '%s': only the first payment rule can be specified as 'bundle'", GetDefinitionName() ); + + // We only allow bundle payment rules to be applied to actual bundles with contents + // specified. Without doing this, we would have nowhere to pull the metadata about + // which items are contained. + SCHEMA_INIT_CHECK( GetBundleInfo() && GetBundleInfo()->vecItemDefs.Count() > 0, "Item definition '%s': payment rule %s is specified as a bundle but outer item definition has no bundle contents.\n", GetDefinitionName(), pKVRule->GetName() ); + + // Bundles rely on sub-items for figuring out payment so the revenue share for the + // bundle itself is expected to be 100%. + SCHEMA_INIT_CHECK( rule.m_RevenueShare == 100.0, "Item definition '%s': payment rule %s has invalid bundle revenue share %0.2f", GetDefinitionName(), pKVRule->GetName(), rule.m_RevenueShare ); + } + + const bool bWasNumberedCorrectly = (AddPaymentRule( rule ) == atoi( pKVRule->GetName() )); + SCHEMA_INIT_CHECK( bWasNumberedCorrectly, "Item definition '%s': misnumbered payment rule %s", GetDefinitionName(), pKVRule->GetName() ); + } + } +#endif // GC_DLL + + return SCHEMA_INIT_SUCCESS(); +} + +#ifdef GC_DLL +int CEconItemDefinition::AddPaymentRule( const econ_item_payment_rule_t& newRule ) +{ + return m_vecPaymentRules.AddToTail( newRule ); +} +#endif // GC_DLL + +bool static_attrib_t::BInitFromKV_MultiLine( const char *pszContext, KeyValues *pKVAttribute, CUtlVector<CUtlString> *pVecErrors ) +{ + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( pKVAttribute->GetName() ); + + SCHEMA_INIT_CHECK( + NULL != pAttrDef, + "Context '%s': Attribute \"%s\" in \"attributes\" did not match any attribute definitions", pszContext, pKVAttribute->GetName() ); + + if ( pAttrDef ) + { + iDefIndex = pAttrDef->GetDefinitionIndex(); + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + pAttrType->InitializeNewEconAttributeValue( &m_value ); + + const char *pszValue = pKVAttribute->GetString( "value", NULL ); + const bool bSuccessfullyLoadedValue = pAttrType->BConvertStringToEconAttributeValue( pAttrDef, pszValue, &m_value, true ); + + SCHEMA_INIT_CHECK( + bSuccessfullyLoadedValue, + "Context '%s': Attribute \"%s\" could not parse value \"%s\"!", pszContext, pKVAttribute->GetName(), pszValue ? pszValue : "(null)" ); + + SCHEMA_INIT_CHECK( + !pAttrDef->BIsSetBonusAttribute(), + "Context '%s': Attribute \"%s\" is a set bonus attribute and not supported here", pszContext, pKVAttribute->GetName() ); +#ifdef GC_DLL + bForceGCToGenerate = pKVAttribute->GetBool( "force_gc_to_generate" ); + + KeyValues *pKVLogicData = pKVAttribute->FindKey( "custom_value_logic" ); + if ( pKVLogicData ) + { + m_pKVCustomData = pKVLogicData->MakeCopy(); + } + + SCHEMA_INIT_CHECK( + m_pKVCustomData == NULL || bForceGCToGenerate, + "Context '%s': Attribute \"%s\" is set to have custom logic but is not GC-generated so that logic will never get used!", pszContext, pKVAttribute->GetName() ); + + SCHEMA_INIT_CHECK( + m_pKVCustomData != NULL || pKVAttribute->FindKey( "value" ), + "Context '%s': Attribute \"%s\" has no value set", pszContext, pKVAttribute->GetName() ); + + SCHEMA_INIT_CHECK( + m_pKVCustomData == NULL || m_pKVCustomData->FindKey( "method" ) != NULL, + "Context '%s': Attribute \"%s\" custom logic data is set, but custom logic method is not set!", pszContext, pKVAttribute->GetName() ); +#endif // GC_DLL + } + + return SCHEMA_INIT_SUCCESS(); +} + +bool static_attrib_t::BInitFromKV_SingleLine( const char *pszContext, KeyValues *pKVAttribute, CUtlVector<CUtlString> *pVecErrors, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode /* = true */ ) +{ + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( pKVAttribute->GetName() ); + + SCHEMA_INIT_CHECK( + NULL != pAttrDef, + "Context '%s': Attribute \"%s\" in \"attributes\" did not match any attribute definitions", pszContext, pKVAttribute->GetName() ); + + if ( pAttrDef ) + { + iDefIndex = pAttrDef->GetDefinitionIndex(); + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + pAttrType->InitializeNewEconAttributeValue( &m_value ); + + const char *pszValue = pKVAttribute->GetString(); + const bool bSuccessfullyLoadedValue = pAttrType->BConvertStringToEconAttributeValue( pAttrDef, pszValue, &m_value, bEnableTerribleBackwardsCompatibilitySchemaParsingCode ); + + SCHEMA_INIT_CHECK( + bSuccessfullyLoadedValue, + "Context '%s': Attribute \"%s\" could not parse value \"%s\"!", pszContext, pKVAttribute->GetName(), pszValue ? pszValue : "(null)" ); + + SCHEMA_INIT_CHECK( + !pAttrDef->BIsSetBonusAttribute(), + "Context '%s': Attribute \"%s\" is a set bonus attribute and not supported here", pszContext, pKVAttribute->GetName() ); + +#ifdef GC_DLL + bForceGCToGenerate = false; + m_pKVCustomData = NULL; +#endif // GC_DLL + } + + return SCHEMA_INIT_SUCCESS(); +} + + + +bool CEconItemDefinition::BInitItemMappings( CUtlVector<CUtlString> *pVecErrors ) +{ + // Armory remapping + if ( m_pszArmoryRemap && m_pszArmoryRemap[0] ) + { + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( m_pszArmoryRemap ); + if ( pDef ) + { + m_iArmoryRemap = pDef->GetDefinitionIndex(); + } + + SCHEMA_INIT_CHECK( + pDef != NULL, + "Item %s: Armory remap definition \"%s\" was not found", m_pszItemBaseName, m_pszArmoryRemap ); + } + + // Store remapping + if ( m_pszStoreRemap && m_pszStoreRemap[0] ) + { + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( m_pszStoreRemap ); + if ( pDef ) + { + m_iStoreRemap = pDef->GetDefinitionIndex(); + } + + SCHEMA_INIT_CHECK( + pDef != NULL, + "Item %s: Store remap definition \"%s\" was not found", m_pszItemBaseName, m_pszStoreRemap ); + } + + return SCHEMA_INIT_SUCCESS(); +} + +const char* CEconItemDefinition::GetIconURL( const char* pszKey ) const +{ + auto idx = m_pDictIcons->Find( pszKey ); + if ( idx == m_pDictIcons->InvalidIndex() ) + { + return NULL; + } + + return (*m_pDictIcons)[ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: Generate and return a random level according to whatever leveling +// curve this definition uses. +//----------------------------------------------------------------------------- +uint32 CEconItemDefinition::RollItemLevel( void ) const +{ + return RandomInt( GetMinLevel(), GetMaxLevel() ); +} + +const char *CEconItemDefinition::GetFirstSaleDate() const +{ + return GetDefinitionString( "first_sale_date", "1960/00/00" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemDefinition::IterateAttributes( IEconItemAttributeIterator *pIterator ) const +{ + FOR_EACH_VEC( GetStaticAttributes(), i ) + { + const static_attrib_t& staticAttrib = GetStaticAttributes()[i]; + +#ifdef GC_DLL + // we skip over static attributes that the GC will turn into dynamic attributes because otherwise we'll have + // the appearance of iterating over them twice; for clients these attributes won't even make it into the + // list + if ( staticAttrib.bForceGCToGenerate ) + continue; +#endif // GC_DLL + + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( staticAttrib.iDefIndex ); + if ( !pAttrDef ) + continue; + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + if ( !pAttrType->OnIterateAttributeValue( pIterator, pAttrDef, staticAttrib.m_value ) ) + return; + } +} + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CEconItemDefinition::GetActivityOverride( int iTeam, Activity baseAct ) const +{ + int iAnims = GetNumAnimations( iTeam ); + for ( int i = 0; i < iAnims; i++ ) + { + animation_on_wearable_t *pData = GetAnimationData( iTeam, i ); + if ( !pData ) + continue; + if ( pData->iActivity == kActivityLookup_Unknown ) + { + pData->iActivity = ActivityList_IndexForName( pData->pszActivity ); + } + + if ( pData->iActivity == baseAct ) + { + if ( pData->iReplacement == kActivityLookup_Unknown ) + { + pData->iReplacement = ActivityList_IndexForName( pData->pszReplacement ); + } + + if ( pData->iReplacement > 0 ) + { + return (Activity) pData->iReplacement; + } + } + } + + return baseAct; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemDefinition::GetActivityOverride( int iTeam, const char *pszActivity ) const +{ + int iAnims = GetNumAnimations( iTeam ); + for ( int i = 0; i < iAnims; i++ ) + { + animation_on_wearable_t *pData = GetAnimationData( iTeam, i ); + if ( Q_stricmp( pszActivity, pData->pszActivity ) == 0 ) + return pData->pszReplacement; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemDefinition::GetReplacementForActivityOverride( int iTeam, Activity baseAct ) const +{ + int iAnims = GetNumAnimations( iTeam ); + for ( int i = 0; i < iAnims; i++ ) + { + animation_on_wearable_t *pData = GetAnimationData( iTeam, i ); + if ( pData->iActivity == kActivityLookup_Unknown ) + { + pData->iActivity = ActivityList_IndexForName( pData->pszActivity ); + } + if ( pData && pData->iActivity == baseAct ) + return pData->pszReplacement; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the content for this item view should be streamed. If false, +// it should be preloaded. +//----------------------------------------------------------------------------- + +// DO NOT MERGE THIS CONSOLE VARIABLE TO REL WE SHOULD NOT SHIP THIS OH GOD +#ifdef STAGING_ONLY +ConVar item_enable_dynamic_loading( "item_enable_dynamic_loading", "1", FCVAR_REPLICATED, "Enable/disable dynamic streaming of econ content." ); +#endif // STAGING_ONLY + +bool CEconItemDefinition::IsContentStreamable() const +{ + if ( !BLoadOnDemand() ) + return false; + +#ifdef STAGING_ONLY + return item_enable_dynamic_loading.GetBool(); +#else + return true; +#endif +} +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + +RETURN_ATTRIBUTE_STRING_F( CEconItemDefinition::GetIconDisplayModel, "icon display model", m_pszWorldDisplayModel ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTimedItemRewardDefinition::CTimedItemRewardDefinition( void ) +: m_unMinFreq( 0 ), + m_unMaxFreq( UINT_MAX ), + m_flChance( 0.0f ), + m_pLootList( NULL ), + m_iRequiredItemDef(INVALID_ITEM_DEF_INDEX) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Copy constructor +//----------------------------------------------------------------------------- +CTimedItemRewardDefinition::CTimedItemRewardDefinition( const CTimedItemRewardDefinition &that ) +{ + (*this) = that; +} + + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CTimedItemRewardDefinition &CTimedItemRewardDefinition::operator=( const CTimedItemRewardDefinition &rhs ) +{ + m_unMinFreq = rhs.m_unMinFreq; + m_unMaxFreq = rhs.m_unMaxFreq; + m_flChance = rhs.m_flChance; + m_criteria = rhs.m_criteria; + m_pLootList = rhs.m_pLootList; + m_iRequiredItemDef = rhs.m_iRequiredItemDef; + + return *this; +} + + +//----------------------------------------------------------------------------- +// Purpose: Initialize the attribute definition +// Input: pKVTimedReward - The KeyValues representation of the timed reward +// schema - The overall item schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CTimedItemRewardDefinition::BInitFromKV( KeyValues *pKVTimedReward, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + // Parse the basic values + m_flChance = pKVTimedReward->GetFloat( "pctChance" ); + m_unMinFreq = pKVTimedReward->GetInt( "value_min", 0 ); + m_unMaxFreq = pKVTimedReward->GetInt( "value_max", UINT_MAX ); + m_iRequiredItemDef = INVALID_ITEM_DEF_INDEX; + + const char *pszRequiredItem = pKVTimedReward->GetString( "required_item", NULL ); + if ( pszRequiredItem ) + { + // Find the ItemDef + CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszRequiredItem ); + SCHEMA_INIT_CHECK( pDef != NULL, "Invalid Item Def Required for a for TimedReward Definition"); + m_iRequiredItemDef = pDef->GetDefinitionIndex(); + } + + // Check required fields + SCHEMA_INIT_CHECK( + NULL != pKVTimedReward->FindKey( "value_min" ), + "Time reward %s: Missing required field \"value_min\"", pKVTimedReward->GetName() ); + SCHEMA_INIT_CHECK( + NULL != pKVTimedReward->FindKey( "value_max" ), + "Time reward %s: Missing required field \"value_max\"", pKVTimedReward->GetName() ); + + SCHEMA_INIT_CHECK( + NULL != pKVTimedReward->FindKey( "pctChance" ), + "Time reward %s: Missing required field \"pctChance\"", pKVTimedReward->GetName() ); + + SCHEMA_INIT_CHECK( + NULL == pKVTimedReward->FindKey( "criteria" ), + "Time reward %s: \"criteria\" is no longer supported. Restructure as \"loot_list\"?", pKVTimedReward->GetName() ); + + SCHEMA_INIT_CHECK( + NULL != pKVTimedReward->FindKey( "loot_list" ), + "Time reward %s: Missing required field \"loot_list\" ", pKVTimedReward->GetName() ); + + // Parse the loot list + const char *pszLootList = pKVTimedReward->GetString("loot_list", NULL); + if ( pszLootList && pszLootList[0] ) + { + m_pLootList = GetItemSchema()->GetLootListByName( pszLootList ); + + // Make sure the item index is correct because we use this index as a reference + SCHEMA_INIT_CHECK( + NULL != m_pLootList, + "Time Reward %s: loot_list (%s) does not exist", pKVTimedReward->GetName(), pszLootList ); + } + + // Other integrity checks + SCHEMA_INIT_CHECK( + m_flChance >= 0.0f, + "Time Reward %s: pctChance (%f) must be greater or equal to 0.0", pKVTimedReward->GetName(), m_flChance ); + + SCHEMA_INIT_CHECK( + m_flChance <= 1.0f, + "Time Reward %s: pctChance (%f) must be less than or equal to 1.0", pKVTimedReward->GetName(), m_flChance ); + + SCHEMA_INIT_CHECK( + pKVTimedReward->GetInt( "value_min" ) > 0, + "Time Reward %s: value_min (%d) must be greater than 0", pKVTimedReward->GetName(), m_unMinFreq ); + SCHEMA_INIT_CHECK( + pKVTimedReward->GetInt( "value_max" ) > 0, + "Time Reward %s: value_max (%d) must be greater than 0", pKVTimedReward->GetName(), m_unMaxFreq ); + SCHEMA_INIT_CHECK( + (m_unMaxFreq >= m_unMinFreq), + "Time Reward %s: value_max (%d) must be greater than or equal to value_min (%d)", pKVTimedReward->GetName(), m_unMaxFreq, m_unMinFreq ); + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a foreign item definition to local definition mapping for a +// foreign app +//----------------------------------------------------------------------------- +void CForeignAppImports::AddMapping( uint16 unForeignDefIndex, const CEconItemDefinition *pDefn ) +{ + m_mapDefinitions.InsertOrReplace( unForeignDefIndex, pDefn ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a foreign item definition to local definition mapping for a +// foreign app +//----------------------------------------------------------------------------- +const CEconItemDefinition *CForeignAppImports::FindMapping( uint16 unForeignDefIndex ) const +{ + int i = m_mapDefinitions.Find( unForeignDefIndex ); + if( m_mapDefinitions.IsValidIndex( i ) ) + return m_mapDefinitions[i]; + else + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CEconItemSchema::CEconItemSchema( ) +: m_unResetCount( 0 ) +, m_pKVRawDefinition( NULL ) +, m_mapItemSeries( DefLessFunc(int) ) +, m_mapRarities( DefLessFunc(int) ) +, m_mapQualities( DefLessFunc(int) ) +, m_mapAttributes( DefLessFunc(int) ) +, m_mapRecipes( DefLessFunc(int) ) +, m_mapQuestObjectives( DefLessFunc(int) ) +, m_mapItemsSorted( DefLessFunc(int) ) +, m_mapToolsItems( DefLessFunc(int) ) +, m_mapBaseItems( DefLessFunc(int) ) +, m_unVersion( 0 ) +#if defined(CLIENT_DLL) || defined(GAME_DLL) +, m_pDefaultItemDefinition( NULL ) +#endif +, m_mapItemSets( CaselessStringLessThan ) +, m_mapItemCollections( CaselessStringLessThan ) +, m_mapItemPaintKits( CaselessStringLessThan ) +, m_mapOperationDefinitions( CaselessStringLessThan ) +, m_mapLootLists( CaselessStringLessThan ) +, m_mapRevolvingLootLists( DefLessFunc(int) ) +, m_mapDefinitionPrefabs( CaselessStringLessThan ) +, m_mapAchievementRewardsByData( DefLessFunc( uint32 ) ) +, m_mapAttributeControlledParticleSystems( DefLessFunc(int) ) +, m_mapDefaultBodygroupState( CaselessStringLessThan ) +#ifdef GC_DLL +, m_mapForeignImports( DefLessFunc(AppId_t) ) +#elif defined(CLIENT_DLL) || defined(GAME_DLL) +, m_pDelayedSchemaData( NULL ) +#endif +, m_mapKillEaterScoreTypes( DefLessFunc( unsigned int ) ) +, m_mapCommunityMarketDefinitionIndexRemap( DefLessFunc( item_definition_index_t ) ) +#ifdef CLIENT_DLL +, m_mapSteamPackageLocalizationTokens( DefLessFunc( uint32 ) ) +#endif +{ + Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +IEconTool *CEconItemSchema::CreateEconToolImpl( const char *pszToolType, const char *pszUseString, const char *pszUsageRestriction, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) +{ + if ( pszToolType ) + { + if ( !V_stricmp( pszToolType, "duel_minigame" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( unCapabilities != ITEM_CAP_NONE ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_DuelingMinigame( pszToolType, pszUseString ); + } + + if ( !V_stricmp( pszToolType, "noise_maker" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( unCapabilities != ITEM_CAP_NONE ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_Noisemaker( pszToolType, pszUseString ); + } + + if ( !V_stricmp( pszToolType, "wrapped_gift" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_WrappedGift( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "backpack_expander" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( unCapabilities != ITEM_CAP_NONE ) return NULL; + + return new CEconTool_BackpackExpander( pszToolType, pszUseString, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "account_upgrade_to_premium" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( unCapabilities != ITEM_CAP_NONE ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_AccountUpgradeToPremium( pszToolType, pszUseString ); + } + + if ( !V_stricmp( pszToolType, "claimcode" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( unCapabilities != ITEM_CAP_NONE ) return NULL; + + return new CEconTool_ClaimCode( pszToolType, pszUseString, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "gift" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( unCapabilities != ITEM_CAP_NONE ) return NULL; + + return new CEconTool_Gift( pszToolType, pszUseString, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "paint_can" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_PaintCan( pszToolType, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "name" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_NameTag( pszToolType, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "desc" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_DescTag( pszToolType, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "decoder_ring" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pUsageKV ) return NULL; + + return new CEconTool_CrateKey( pszToolType, pszUsageRestriction, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "customize_texture_item" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_CustomizeTexture( pszToolType, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "gift_wrap" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_GiftWrap( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "wedding_ring" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_WeddingRing( pszToolType, pszUseString, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "strange_part" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_StrangePart( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "strange_part_restriction" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( !pUsageKV ) return NULL; // required + + return new CEconTool_StrangePartRestriction( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "apply_custom_attrib" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_UpgradeCard( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "strangifier" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_Strangifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "killstreakifier" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_KillStreakifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if( !V_stricmp( pszToolType, "dynamic_recipe" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_ItemDynamicRecipe( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "item_eater_recharger" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_ItemEaterRecharger( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "class_transmogrifier" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_ClassTransmogrifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "duck_token" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_DuckToken( pszToolType, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "grant_operation_pass" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + + return new CEconTool_GrantOperationPass( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "strange_count_transfer" ) ) + { + // Error checking -- make sure we aren't setting properties in the schema that we don't support. + if ( pszUsageRestriction ) return NULL; + if ( pUsageKV ) return NULL; + + return new CEconTool_StrangeCountTransfer( pszToolType, unCapabilities ); + } + + if ( !V_stricmp( pszToolType, "paintkit_weapon_festivizer" ) ) + { + return new CEconTool_Festivizer( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + + if ( !V_stricmp( pszToolType, "unusualifier" ) ) + { + return new CEconTool_Unusualifier( pszToolType, pszUseString, unCapabilities, pUsageKV ); + } + } + + // Default behavior. + return new CEconTool_Default( pszToolType, pszUseString, pszUsageRestriction, unCapabilities ); +} + +#ifdef GC_DLL +random_attrib_t *CEconItemSchema::CreateRandomAttribute( const char *pszContext, KeyValues *pRandomAttributesKV, CUtlVector<CUtlString> *pVecErrors /*= NULL*/ ) +{ + // We've found the random attribute block. Parse it. + if ( pRandomAttributesKV->FindKey( "chance" ) == NULL ) + { + CUtlString msg; \ + msg.Format( CFmtStr( "Missing required field \"chance\" in the \"random_attributes\" block." ) ); + if ( pVecErrors ) + { + pVecErrors->AddToTail( msg ); + } + else + { + AssertMsg( pRandomAttributesKV->FindKey( "chance" ) != NULL, msg.String() ); + } + + return NULL; + } + + random_attrib_t randomAttrib; + + randomAttrib.m_flChanceOfRandomAttribute = pRandomAttributesKV->GetFloat( "chance" ); + randomAttrib.m_bPickAllAttributes = ( pRandomAttributesKV->GetFloat( "pick_all_attributes" ) != 0 ); + randomAttrib.m_flTotalAttributeWeight = 0; + + FOR_EACH_TRUE_SUBKEY( pRandomAttributesKV, pKVAttribute ) + { + const char *pszName = pKVAttribute->GetName(); + + if ( !Q_strcmp( pszName, "chance" ) ) + continue; + + // Quick block list of attrs that have equal weight + if ( !Q_strcmp( pszName, "is_even_chance_attr" ) ) + { + FOR_EACH_VALUE( pKVAttribute, pKVListItem ) + { + const CEconItemAttributeDefinition *pDef = GetAttributeDefinitionByName( pKVListItem->GetName() ); + if ( pDef == NULL ) + { + CUtlString msg; \ + msg.Format( CFmtStr( "Attribute definition \"%s\" was not found", pszName ) ); + if ( pVecErrors ) + { + pVecErrors->AddToTail( msg ); + } + else + { + AssertMsg( pDef != NULL, msg.String() ); + } + + return NULL; + } + + lootlist_attrib_t lootListAttrib; + if ( !lootListAttrib.m_staticAttrib.BInitFromKV_SingleLine( __FUNCTION__, pKVListItem, pVecErrors, false ) ) + { + if ( pVecErrors ) + { + pVecErrors->AddToTail( __FUNCTION__ ": error initializing line-item attribute from lootlist definition (possible attr template).\n" ); + } + return NULL; + } + // Weight is set to 1 for even chance attr + lootListAttrib.m_flWeight = 1.0f; + randomAttrib.m_flTotalAttributeWeight += 1.0f; + randomAttrib.m_RandomAttributes.AddToTail( lootListAttrib ); + } + } + else + { + const CEconItemAttributeDefinition *pDef = GetAttributeDefinitionByName( pszName ); + if ( pDef == NULL ) + { + CUtlString msg; \ + msg.Format( CFmtStr( "Attribute definition \"%s\" was not found", pszName ) ); + if ( pVecErrors ) + { + pVecErrors->AddToTail( msg ); + } + else + { + AssertMsg( pDef != NULL, msg.String() ); + } + + return NULL; + } + + lootlist_attrib_t lootListAttrib; + if ( !lootListAttrib.BInitFromKV( pszContext, pKVAttribute, *this, pVecErrors ) ) + { + return NULL; + } + + randomAttrib.m_flTotalAttributeWeight += lootListAttrib.m_flWeight; + randomAttrib.m_RandomAttributes.AddToTail( lootListAttrib ); + } + } + + random_attrib_t *pRandomAttr = new random_attrib_t; + *pRandomAttr = randomAttrib; + return pRandomAttr; +} +#endif // GC_DLL + + +//----------------------------------------------------------------------------- +// Purpose: Resets the schema to before BInit was called +//----------------------------------------------------------------------------- +void CEconItemSchema::Reset( void ) +{ + ++m_unResetCount; + + m_unFirstValidClass = 0; + m_unLastValidClass = 0; + m_unAccoutClassIndex = 0; + m_unFirstValidClassItemSlot = 0; + m_unLastValidClassItemSlot = 0; + m_unFirstValidAccountItemSlot = 0; + m_unLastValidAccountItemSlot = 0; + m_unNumItemPresets = 0; + m_unMinLevel = 0; + m_unMaxLevel = 0; + m_unVersion = 0; + m_unSumQualityWeights = 0; + FOR_EACH_VEC( m_vecAttributeTypes, i ) + { + delete m_vecAttributeTypes[i].m_pAttrType; + } + m_vecAttributeTypes.Purge(); + m_mapItems.PurgeAndDeleteElements(); + m_mapItems.Purge(); + m_mapRarities.Purge(); + m_mapQualities.Purge(); + m_mapItemsSorted.Purge(); + m_mapToolsItems.Purge(); + m_mapBaseItems.Purge(); + m_mapRecipes.PurgeAndDeleteElements(); + m_vecTimedRewards.Purge(); + m_mapItemSets.PurgeAndDeleteElements(); + m_mapLootLists.PurgeAndDeleteElements(); +#ifdef GC_DLL + m_dictRandomAttributeTemplates.PurgeAndDeleteElements(); +#endif // GC_DLL + m_mapAttributeControlledParticleSystems.Purge(); + m_vecAttributeControlledParticleSystemsCosmetics.Purge(); + m_vecAttributeControlledParticleSystemsWeapons.Purge(); + m_vecAttributeControlledParticleSystemsTaunts.Purge(); + + m_mapAttributes.Purge(); + if ( m_pKVRawDefinition ) + { + m_pKVRawDefinition->deleteThis(); + m_pKVRawDefinition = NULL; + } + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + delete m_pDefaultItemDefinition; + m_pDefaultItemDefinition = NULL; +#endif + + FOR_EACH_MAP_FAST( m_mapRecipes, i ) + { + delete m_mapRecipes[i]; + } + + FOR_EACH_MAP_FAST( m_mapDefinitionPrefabs, i ) + { + m_mapDefinitionPrefabs[i]->deleteThis(); + } + m_mapDefinitionPrefabs.Purge(); + + m_vecEquipRegionsList.Purge(); + m_vecItemLevelingData.PurgeAndDeleteElements(); + + m_dictStringTable.PurgeAndDeleteElements(); + m_mapCommunityMarketDefinitionIndexRemap.Purge(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CEconItemSchema &CEconItemSchema::operator=( CEconItemSchema &rhs ) +{ + Reset(); + BInitSchema( rhs.m_pKVRawDefinition ); + return *this; +} + +bool g_bLastSignatureCheck; + +bool CheckValveSignature( const void *data, uint32 nDataSize, const void *signature, uint32 nSignatureSize ) +{ + // Must match the PUBLIC KEY in src\devtools\valve_source_officialcontent.privatekey.vdf + static const unsigned char valvePublicKey[] = + "\x30\x81\x9D\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01" + "\x05\x00\x03\x81\x8B\x00\x30\x81\x87\x02\x81\x81\x00\xB1\xC0\xF1" + "\x1C\xB2\x98\x2F\x29\x25\x95\x07\xA7\x74\xD4\x83\x43\x77\xC5\xB7" + "\xA3\x8D\x9A\x4B\x38\x92\xB5\x98\x00\x9F\x16\xAA\x10\x95\x65\xCB" + "\x09\xAD\x25\xDE\x0D\x3D\x1A\x08\x9C\x3C\xB6\x8E\x49\x19\x21\xCC" + "\x14\x2F\x38\x33\x83\x20\x1D\xE9\x82\x62\xA7\x6E\xD8\xA6\xCC\x78" + "\xBC\x51\x68\x5A\x0A\x64\xA6\x17\x2C\x67\x12\x7A\xF2\x3E\x78\x73" + "\x1F\x4A\x82\xC2\x01\xD6\x4C\x9A\xB8\x09\x37\x32\x21\x84\xB6\x42" + "\x72\x7F\xE1\x42\xD1\x5C\xC0\x45\xF3\x58\x3E\x19\xE3\xE3\xE1\xA9" + "\xC5\x0C\x0F\xC8\x41\x13\x57\x3A\x52\x0A\x8F\x73\x23\x02\x01\x11"; + + // Put into a global var. Could help with VAC detection, if this + // code gets detoured + g_bLastSignatureCheck = CCrypto::RSAVerifySignatureSHA256( + (const uint8 *)data, nDataSize, + (const uint8 *)signature, nSignatureSize, + valvePublicKey, sizeof(valvePublicKey) + ); + + return g_bLastSignatureCheck; +} + +//----------------------------------------------------------------------------- +// Initializes the schema, given KV filename +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInit( const char *fileName, const char *pathID, CUtlVector<CUtlString> *pVecErrors /* = NULL */) +{ + Reset(); + + // Read the raw data + CUtlBuffer bufRawData; + bool bReadFileOK = g_pFullFileSystem->ReadFile( fileName, pathID, bufRawData ); + SCHEMA_INIT_CHECK( bReadFileOK, "Cannot load file '%s'", fileName ); + + // Do we need to check the signature? + #if defined(TF_DLL) || defined(TF_CLIENT_DLL) + { + + // Load up the signature + CUtlString sSignatureFilename( fileName ); sSignatureFilename.Append( ".sig" ); + CUtlBuffer bufSignatureBinary; + bool bReadSignatureOK = g_pFullFileSystem->ReadFile( sSignatureFilename.String(), pathID, bufSignatureBinary ); + SCHEMA_INIT_CHECK( bReadSignatureOK, "Cannot load file '%s'", sSignatureFilename.String() ); + + // Check it with the Valve public key + bool bSignatureValid = CheckValveSignature( + bufRawData.Base(), bufRawData.TellPut(), + bufSignatureBinary.Base(), bufSignatureBinary.TellPut() + ); + + // If they have a signature for a zero-byte file, that's OK, too. + // That's the secret code that is checked into P4 internally that + // let's us run with any items_game file + if ( !bSignatureValid ) + { + bSignatureValid = CheckValveSignature( + "", 0, + bufSignatureBinary.Base(), bufSignatureBinary.TellPut() + ); + } + + SCHEMA_INIT_CHECK( bSignatureValid, "'%s' is corrupt. Please verify your local game files. (https://support.steampowered.com/kb_article.php?ref=2037-QEUH-3335)", fileName ); + } + #endif + + // Compute version hash + CSHA1 sha1; + sha1.Update( (unsigned char *)bufRawData.Base(), bufRawData.Size() ); + sha1.Final(); + sha1.GetHash( m_schemaSHA.m_shaDigest ); + + // Wrap it with a text buffer reader + CUtlBuffer bufText( bufRawData.Base(), bufRawData.TellPut(), CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER ); + + // Use the standard init path + return BInitTextBuffer( bufText, pVecErrors ); +} + +//----------------------------------------------------------------------------- +// Initializes the schema, given KV in binary form +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitBinaryBuffer( CUtlBuffer &buffer, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + Reset(); + m_pKVRawDefinition = new KeyValues( "CEconItemSchema" ); + if ( m_pKVRawDefinition->ReadAsBinary( buffer ) ) + { + return BInitSchema( m_pKVRawDefinition, pVecErrors ) + && BPostSchemaInit( pVecErrors ); + } + if ( pVecErrors ) + { + pVecErrors->AddToTail( "Error parsing keyvalues" ); + } + return false; +} + +unsigned char g_sha1ItemSchemaText[ k_cubHash ]; + +//----------------------------------------------------------------------------- +// Initializes the schema, given KV in text form +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitTextBuffer( CUtlBuffer &buffer, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + // Save off the hash into a global variable, so VAC can check it + // later + GenerateHash( g_sha1ItemSchemaText, buffer.Base(), buffer.TellPut() ); + + Reset(); + m_pKVRawDefinition = new KeyValues( "CEconItemSchema" ); + if ( m_pKVRawDefinition->LoadFromBuffer( NULL, buffer ) ) + { + return BInitSchema( m_pKVRawDefinition, pVecErrors ) + && BPostSchemaInit( pVecErrors ); + } + if ( pVecErrors ) + { + pVecErrors->AddToTail( "Error parsing keyvalues" ); + } + return false; +} + +bool CEconItemSchema::DumpItems ( const char *fileName, const char *pathID ) +{ + // create a write file + FileHandle_t f = g_pFullFileSystem->Open(fileName, "wb", pathID); + + if ( f == FILESYSTEM_INVALID_HANDLE ) + { + DevMsg(1, "CEconItemSchema::DumpItems: couldn't open file \"%s\" in path \"%s\".\n", + fileName?fileName:"NULL", pathID?pathID:"NULL" ); + return false; + } + + CUtlSortVector< KeyValues*, CUtlSortVectorKeyValuesByName > vecSortedItems; + + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + vecSortedItems.InsertNoSort( m_mapItems[ i ]->GetRawDefinition() ); + } + vecSortedItems.RedoSort(); + + CUtlBuffer buf; + FOR_EACH_VEC( vecSortedItems, i ) + { + vecSortedItems[i]->RecursiveSaveToFile( buf, 0, true ); + } + + int iBufSize = buf.GetBytesRemaining(); + bool bSuccess = false; + if ( g_pFullFileSystem->Write(buf.PeekGet(), iBufSize, f) == iBufSize ) + bSuccess = true; + + g_pFullFileSystem->Close(f); + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Called once the price sheet's been loaded +//----------------------------------------------------------------------------- +#ifdef GC_DLL +GCConVar econ_orphaned_sold_items_owned_by_account_id( "econ_orphaned_sold_items_owned_by_account_id", "121416792" ); + +bool CEconItemSchema::DoPostPriceSheetLoadInit( CEconStorePriceSheet *pPriceSheet ) +{ + FOR_EACH_MAP_FAST( m_mapItems, iItem ) + { + CEconItemDefinition *pItemDef = m_mapItems[ iItem ]; + + // Is this item being sold? + const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( pItemDef->GetDefinitionIndex() ); + if ( pStoreEntry ) + { + // Cache off whether this item is a pack item + pItemDef->SetIsPackItem( pStoreEntry->m_bIsPackItem ); + + // If an item is being sold and it has no payment rules set up, we can optionally force-create + // a dummy payment rule that will redirect that item revenue to a "hey, these are orphan items!" + // account. + if ( pItemDef->GetPaymentRules().Count() == 0 && econ_orphaned_sold_items_owned_by_account_id.GetInt() ) + { + econ_item_payment_rule_t rule; + rule.m_RevenueShare = 1.0; + rule.m_eRuleType = kPaymentRule_PartnerSteamID; + rule.m_vecValues.AddToTail( econ_orphaned_sold_items_owned_by_account_id.GetInt() ); + + DbgVerify( pItemDef->AddPaymentRule( rule ) == 0 ); + } + } + + // Go through the cache of all bundles... + FOR_EACH_VEC( m_vecBundles, iBundle ) + { + const CEconItemDefinition *pBundleItemDef = m_vecBundles[ iBundle ]; + const bundleinfo_t *pBundle = pBundleItemDef->GetBundleInfo(); + const bool bBundleItemIsForSale = pPriceSheet->BItemExistsInPriceSheet( pBundleItemDef->GetDefinitionIndex() ) != NULL; // Only add bundles that are actually for sale + + bool bAddToContainingBundleItemDefs = false; + if ( pItemDef->IsPackBundle() ) + { + // If the current item is a pack bundle, look for the first pack item in the current bundle (pBundle). We can safely assume that all pack items will be + // in pBundle if the first pack item is, since the GC won't startup otherwise. Don't add self as a containing bundle. + bAddToContainingBundleItemDefs = pItemDef->GetDefinitionIndex() != pBundleItemDef->GetDefinitionIndex() + && pBundle->vecItemDefs.HasElement( pItemDef->GetBundleInfo()->vecItemDefs[0] ); + } + else + { + bAddToContainingBundleItemDefs = pBundle->vecItemDefs.HasElement( pItemDef ); + } + + // Does the current bundle contain the given item? + if ( bBundleItemIsForSale && bAddToContainingBundleItemDefs ) + { + pItemDef->m_vecContainingBundleItemDefs.AddToTail( pBundleItemDef ); + } + } + } + + return true; +} +#endif + + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Set up the buffer to use to reinitialize our schema next time we can do so safely. +//----------------------------------------------------------------------------- +bool CEconItemSchema::MaybeInitFromBuffer( IDelayedSchemaData *pDelayedSchemaData ) +{ + bool bDidInit = false; + + // Use whatever our most current data block is. + if ( m_pDelayedSchemaData ) + { + delete m_pDelayedSchemaData; + } + + m_pDelayedSchemaData = pDelayedSchemaData; + +#ifdef CLIENT_DLL + // If we aren't in a game we can parse immediately now. + if ( !engine->IsInGame() ) + { + BInitFromDelayedBuffer(); + bDidInit = true; + } +#endif // CLIENT_DLL + + return bDidInit; +} + +//----------------------------------------------------------------------------- +// We're in a safe place to change the contents of the schema, so do so and clean +// up whatever memory we were using. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitFromDelayedBuffer() +{ + if ( !m_pDelayedSchemaData ) + return true; + + bool bSuccess = m_pDelayedSchemaData->InitializeSchema( this ); + delete m_pDelayedSchemaData; + m_pDelayedSchemaData = NULL; + + return bSuccess; +} +#endif // !GC_DLL + +static void CalculateKeyValuesCRCRecursive( KeyValues *pKV, CRC32_t *crc, bool bIgnoreName = false ) +{ + // Hash in the key name in LOWERCASE. Keyvalues files are not deterministic due + // to the case insensitivity of the keys and the dependence on the existing + // state of the name table upon entry. + if ( !bIgnoreName ) + { + const char *s = pKV->GetName(); + for (;;) + { + unsigned char x = tolower(*s); + CRC32_ProcessBuffer( crc, &x, 1 ); // !SPEED! This is slow, but it works. + if (*s == '\0') break; + ++s; + } + } + + // Now hash in value, depending on type + // !FIXME! This is not byte-order independent! + switch ( pKV->GetDataType() ) + { + case KeyValues::TYPE_NONE: + { + FOR_EACH_SUBKEY( pKV, pChild ) + { + CalculateKeyValuesCRCRecursive( pChild, crc ); + } + break; + } + case KeyValues::TYPE_STRING: + { + const char *val = pKV->GetString(); + CRC32_ProcessBuffer( crc, val, strlen(val)+1 ); + break; + } + + case KeyValues::TYPE_INT: + { + int val = pKV->GetInt(); + CRC32_ProcessBuffer( crc, &val, sizeof(val) ); + break; + } + + case KeyValues::TYPE_UINT64: + { + uint64 val = pKV->GetUint64(); + CRC32_ProcessBuffer( crc, &val, sizeof(val) ); + break; + } + + case KeyValues::TYPE_FLOAT: + { + float val = pKV->GetFloat(); + CRC32_ProcessBuffer( crc, &val, sizeof(val) ); + break; + } + case KeyValues::TYPE_COLOR: + { + int val = pKV->GetColor().GetRawColor(); + CRC32_ProcessBuffer( crc, &val, sizeof(val) ); + break; + } + + default: + case KeyValues::TYPE_PTR: + case KeyValues::TYPE_WSTRING: + { + Assert( !"Unsupport data type!" ); + break; + } + } +} + +uint32 CEconItemSchema::CalculateKeyValuesVersion( KeyValues *pKV ) +{ + CRC32_t crc; + CRC32_Init( &crc ); + + // Calc CRC recursively. Ignore the very top-most + // key name, which isn't set consistently + CalculateKeyValuesCRCRecursive( pKV, &crc, true ); + CRC32_Final( &crc ); + return crc; +} + +EEquipType_t CEconItemSchema::GetEquipTypeFromClassIndex( int iClass ) const +{ + if ( iClass == GetAccountIndex() ) + return EEquipType_t::EQUIP_TYPE_ACCOUNT; + + if ( iClass >= GetFirstValidClass() && iClass <= GetLastValidClass() ) + return EEquipType_t::EQUIP_TYPE_CLASS; + + return EEquipType_t::EQUIP_TYPE_INVALID; +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the schema +// Input: pKVRawDefinition - The raw KeyValues representation of the schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitSchema( KeyValues *pKVRawDefinition, CUtlVector<CUtlString> *pVecErrors /* = NULL */ ) +{ + m_unMinLevel = pKVRawDefinition->GetInt( "item_level_min", 0 ); + m_unMaxLevel = pKVRawDefinition->GetInt( "item_level_max", 0 ); + +#if !defined( GC_DLL ) + m_unVersion = CalculateKeyValuesVersion( pKVRawDefinition ); +#endif + + + +#ifdef GC_DLL + // Validate the integrity of the base data. + SCHEMA_INIT_CHECK( 0 <= m_unMinLevel, "Minimum Item Level must be at least 0" ); + SCHEMA_INIT_CHECK( m_unMinLevel <= m_unMaxLevel, "Minimum Item Level must be less than or equal to Maximum Item Level" ); +#endif // GC_DLL + + // Parse the prefabs block first so the prefabs will be populated in case anything else wants + // to use them later. + KeyValues *pKVPrefabs = pKVRawDefinition->FindKey( "prefabs" ); + if ( NULL != pKVPrefabs ) + { + SCHEMA_INIT_SUBSTEP( BInitDefinitionPrefabs( pKVPrefabs, pVecErrors ) ); + } + + // Initialize the game info block + KeyValues *pKVGameInfo = pKVRawDefinition->FindKey( "game_info" ); + SCHEMA_INIT_CHECK( NULL != pKVGameInfo, "Required key \"game_info\" missing.\n" ); + + if ( NULL != pKVGameInfo ) + { + SCHEMA_INIT_SUBSTEP( BInitGameInfo( pKVGameInfo, pVecErrors ) ); + } + + // Initialize our attribute types. We don't actually pull this data from the schema right now but it + // still makes sense to initialize it at this point. + SCHEMA_INIT_SUBSTEP( BInitAttributeTypes( pVecErrors ) ); + + // Initialize the item series block + KeyValues *pKVItemSeries = pKVRawDefinition->FindKey( "item_series_types" ); + SCHEMA_INIT_CHECK( NULL != pKVItemSeries, "Required key \"item_series_types\" missing.\n" ); + if ( NULL != pKVItemSeries ) + { + SCHEMA_INIT_SUBSTEP( BInitItemSeries( pKVItemSeries, pVecErrors ) ); + } + + // Initialize the rarity block + KeyValues *pKVRarities = pKVRawDefinition->FindKey( "rarities" ); + KeyValues *pKVRarityWeights = pKVRawDefinition->FindKey( "rarities_lootlist_weights" ); + SCHEMA_INIT_CHECK( NULL != pKVRarities, "Required key \"rarities\" missing.\n" ); + if ( NULL != pKVRarities ) + { + SCHEMA_INIT_SUBSTEP( BInitRarities( pKVRarities, pKVRarityWeights, pVecErrors ) ); + } + + // Initialize the qualities block + KeyValues *pKVQualities = pKVRawDefinition->FindKey( "qualities" ); + SCHEMA_INIT_CHECK( NULL != pKVQualities, "Required key \"qualities\" missing.\n" ); + + if ( NULL != pKVQualities ) + { + SCHEMA_INIT_SUBSTEP( BInitQualities( pKVQualities, pVecErrors ) ); + } + + // Initialize the colors block + KeyValues *pKVColors = pKVRawDefinition->FindKey( "colors" ); + SCHEMA_INIT_CHECK( NULL != pKVColors, "Required key \"colors\" missing.\n" ); + + if ( NULL != pKVColors ) + { + SCHEMA_INIT_SUBSTEP( BInitColors( pKVColors, pVecErrors ) ); + } + + // Initialize the attributes block + KeyValues *pKVAttributes = pKVRawDefinition->FindKey( "attributes" ); + SCHEMA_INIT_CHECK( NULL != pKVAttributes, "Required key \"attributes\" missing.\n" ); + + if ( NULL != pKVAttributes ) + { + SCHEMA_INIT_SUBSTEP( BInitAttributes( pKVAttributes, pVecErrors ) ); + } + +#ifdef GC + // Initialize the motd block + KeyValues *pKVMOTD = pKVRawDefinition->FindKey( "motd_entries" ); + SCHEMA_INIT_CHECK( NULL != pKVMOTD, "Required key \"motd_entries\" missing.\n" ); + + if ( NULL != pKVMOTD ) + { + SCHEMA_INIT_SUBSTEP( GGCGameBase()->GetMOTDManager().BInitMOTDEntries( pKVMOTD, pVecErrors ) ); + } +#endif + + // Initialize the "equip_regions_list" block -- this is an optional block + KeyValues *pKVEquipRegions = pKVRawDefinition->FindKey( "equip_regions_list" ); + if ( NULL != pKVEquipRegions ) + { + SCHEMA_INIT_SUBSTEP( BInitEquipRegions( pKVEquipRegions, pVecErrors ) ); + } + + // Initialize the "equip_conflicts" block -- this is an optional block, though it doesn't + // make any sense and will probably fail internally if there is no corresponding "equip_regions" + // block as well + KeyValues *pKVEquipRegionConflicts = pKVRawDefinition->FindKey( "equip_conflicts" ); + if ( NULL != pKVEquipRegionConflicts ) + { + SCHEMA_INIT_SUBSTEP( BInitEquipRegionConflicts( pKVEquipRegionConflicts, pVecErrors ) ); + } + + // TF2 Paint Kits + // No included in schema file (Too Big). Loaded Seperately + // Load the KV and add it to the pKVRawDefinition + KeyValues *pPaintkitKV = new KeyValues( "item_paintkit_definitions" ); + SCHEMA_INIT_CHECK( pPaintkitKV->LoadFromFile( g_pFullFileSystem, "scripts/items/paintkits_master.txt", "GAME" ), "Unable to Load paintkits_master.txt KV File!" ); + pKVRawDefinition->AddSubKey( pPaintkitKV ); + + // Init Item Paint Kits + // Must be BEFORE Item defs + KeyValues *pKVItemPaintKits = pKVRawDefinition->FindKey( "item_paintkit_definitions" ); + if ( NULL != pKVItemPaintKits ) + { + SCHEMA_INIT_SUBSTEP( BInitItemPaintKitDefinitions( pKVItemPaintKits, pVecErrors ) ); + } + +#ifdef GC_DLL + // Parse the loot lists block (on the GC) + // Must be BEFORE Item defs + KeyValues *pKVRandomAttributeTemplates = pKVRawDefinition->FindKey( "random_attribute_templates" ); + SCHEMA_INIT_SUBSTEP( BInitRandomAttributeTemplates( pKVRandomAttributeTemplates, pVecErrors ) ); +#endif // GC_DLL + + // Initialize the items block + KeyValues *pKVItems = pKVRawDefinition->FindKey( "items" ); + SCHEMA_INIT_CHECK( NULL != pKVItems, "Required key \"items\" missing.\n" ); + + if ( NULL != pKVItems ) + { + SCHEMA_INIT_SUBSTEP( BInitItems( pKVItems, pVecErrors ) ); + } + + + // Verify base item names are proper in item schema + SCHEMA_INIT_SUBSTEP( BVerifyBaseItemNames( pVecErrors ) ); + + // Parse the item_sets block. + KeyValues *pKVItemSets = pKVRawDefinition->FindKey( "item_sets" ); + SCHEMA_INIT_SUBSTEP( BInitItemSets( pKVItemSets, pVecErrors ) ); + + // Particles + KeyValues *pKVParticleSystems = pKVRawDefinition->FindKey( "attribute_controlled_attached_particles" ); + SCHEMA_INIT_SUBSTEP( BInitAttributeControlledParticleSystems( pKVParticleSystems, pVecErrors ) ); + + // Parse any recipes block + KeyValues *pKVRecipes = pKVRawDefinition->FindKey( "recipes" ); + SCHEMA_INIT_SUBSTEP( BInitRecipes( pKVRecipes, pVecErrors ) ); + + // Reset our loot lists. + m_mapLootLists.RemoveAll(); + + // Init Item Collections - Must be before lootlists since collections are lootlists themselves and are referenced by lootlists + KeyValues *pKVItemCollections = pKVRawDefinition->FindKey( "item_collections" ); + if ( NULL != pKVItemCollections ) + { + SCHEMA_INIT_SUBSTEP( BInitItemCollections( pKVItemCollections, pVecErrors ) ); + } + +#ifdef GC_DLL + // Parse the loot lists block (on the GC) + KeyValues *pKVLootLists = pKVRawDefinition->FindKey( "loot_lists" ); + SCHEMA_INIT_SUBSTEP( BInitLootLists( pKVLootLists, pVecErrors ) ); + + // Initialize the periodic score accumulation block (this needs to take place after items) + KeyValues *pKVPeriodicScoring = pKVRawDefinition->FindKey( "periodic_score_accumulation" ); + if ( NULL != pKVPeriodicScoring ) + { + SCHEMA_INIT_SUBSTEP( BInitPeriodicScoring( pKVPeriodicScoring, pVecErrors ) ); + } +#endif // GC_DLL + + // Parse the client loot lists block (everywhere) + KeyValues *pKVClientLootLists = pKVRawDefinition->FindKey( "client_loot_lists" ); + SCHEMA_INIT_SUBSTEP( BInitLootLists( pKVClientLootLists, pVecErrors ) ); + + // Parse the revolving loot lists block + KeyValues *pKVRevolvingLootLists = pKVRawDefinition->FindKey( "revolving_loot_lists" ); + SCHEMA_INIT_SUBSTEP( BInitRevolvingLootLists( pKVRevolvingLootLists, pVecErrors ) ); + + // Init Items that may reference Collections + SCHEMA_INIT_SUBSTEP( BInitCollectionReferences( pVecErrors ) ); + + // Validate Operation Pass + KeyValues *pKVOperationDefinitions = pKVRawDefinition->FindKey( "operations" ); + if ( NULL != pKVOperationDefinitions ) + { + SCHEMA_INIT_SUBSTEP( BInitOperationDefinitions( pKVGameInfo, pKVOperationDefinitions, pVecErrors ) ); + } + +#if defined( GC_DLL ) + // Parse any time-based rewards + KeyValues *pKVTimeRewards = pKVRawDefinition->FindKey( "time_rewards" ); + SCHEMA_INIT_SUBSTEP( BInitTimedRewards( pKVTimeRewards, pVecErrors ) ); + + KeyValues *pKVExperiments = pKVRawDefinition->FindKey( "experiments" ); + SCHEMA_INIT_SUBSTEP( BInitExperiements( pKVExperiments, pVecErrors ) ); + + SCHEMA_INIT_SUBSTEP( BInitForeignImports( pVecErrors ) ); +#elif defined( CLIENT_DLL ) || defined( GAME_DLL ) + KeyValues *pKVArmoryData = pKVRawDefinition->FindKey( "armory_data" ); + SCHEMA_INIT_SUBSTEP( BInitArmoryData( pKVArmoryData, pVecErrors ) ); +#endif // GC_DLL + + // Parse any achievement rewards + KeyValues *pKVAchievementRewards = pKVRawDefinition->FindKey( "achievement_rewards" ); + SCHEMA_INIT_SUBSTEP( BInitAchievementRewards( pKVAchievementRewards, pVecErrors ) ); + +#ifdef TF_CLIENT_DLL + // Compute the number of concrete items, for each item, and cache for quick access + SCHEMA_INIT_SUBSTEP( BInitConcreteItemCounts( pVecErrors ) ); + + // We don't have access to Steam's full library of app data on the client so initialize whichever packages + // we want to reference. + KeyValues *pKVSteamPackages = pKVRawDefinition->FindKey( "steam_packages" ); + SCHEMA_INIT_SUBSTEP( BInitSteamPackageLocalizationToken( pKVSteamPackages, pVecErrors ) ); +#endif // TF_CLIENT_DLL + + // Parse the item levels block + KeyValues *pKVItemLevels = pKVRawDefinition->FindKey( "item_levels" ); + SCHEMA_INIT_SUBSTEP( BInitItemLevels( pKVItemLevels, pVecErrors ) ); + + // Parse the kill eater score types + KeyValues *pKVKillEaterScoreTypes = pKVRawDefinition->FindKey( "kill_eater_score_types" ); + SCHEMA_INIT_SUBSTEP( BInitKillEaterScoreTypes( pKVKillEaterScoreTypes, pVecErrors ) ); + + // Initialize the string tables, if present + KeyValues *pKVStringTables = pKVRawDefinition->FindKey( "string_lookups" ); + SCHEMA_INIT_SUBSTEP( BInitStringTables( pKVStringTables, pVecErrors ) ); + + // Initialize the community Market remaps, if present + KeyValues *pKVCommunityMarketRemaps = pKVRawDefinition->FindKey( "community_market_item_remaps" ); + SCHEMA_INIT_SUBSTEP( BInitCommunityMarketRemaps( pKVCommunityMarketRemaps, pVecErrors ) ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the "game_info" section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitGameInfo( KeyValues *pKVGameInfo, CUtlVector<CUtlString> *pVecErrors ) +{ + m_unFirstValidClass = pKVGameInfo->GetInt( "first_valid_class", 0 ); + m_unLastValidClass = pKVGameInfo->GetInt( "last_valid_class", 0 ); + SCHEMA_INIT_CHECK( 0 < m_unFirstValidClass, "First valid class must be greater than 0." ); + SCHEMA_INIT_CHECK( m_unFirstValidClass <= m_unLastValidClass, "First valid class must be less than or equal to last valid class." ); + m_unAccoutClassIndex = pKVGameInfo->GetInt( "account_class_index", 0 ); + SCHEMA_INIT_CHECK( m_unAccoutClassIndex > m_unLastValidClass, "Account class index must be greater than 'last_valid_class'" ); + + m_unFirstValidClassItemSlot = pKVGameInfo->GetInt( "first_valid_item_slot", INVALID_EQUIPPED_SLOT ); + m_unLastValidClassItemSlot = pKVGameInfo->GetInt( "last_valid_item_slot", INVALID_EQUIPPED_SLOT ); + SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unFirstValidClassItemSlot, "first_valid_item_slot not set!" ); + SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unFirstValidClassItemSlot, "last_valid_item_slot not set!" ); + SCHEMA_INIT_CHECK( m_unFirstValidClassItemSlot <= m_unLastValidClassItemSlot, "First valid item slot must be less than or equal to last valid item slot." ); + + m_unFirstValidAccountItemSlot = pKVGameInfo->GetInt( "account_first_valid_item_slot", INVALID_EQUIPPED_SLOT ); + m_unLastValidAccountItemSlot = pKVGameInfo->GetInt( "account_last_valid_item_slot", INVALID_EQUIPPED_SLOT ); + SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unFirstValidAccountItemSlot, "account_first_valid_item_slot not set!" ); + SCHEMA_INIT_CHECK( INVALID_EQUIPPED_SLOT != m_unLastValidAccountItemSlot, "account_last_valid_item_slot not set!" ); + SCHEMA_INIT_CHECK( m_unFirstValidAccountItemSlot <= m_unLastValidAccountItemSlot, "First vlid account item slot must be less than or equal to the last valid account item slot." ); + + m_unNumItemPresets = pKVGameInfo->GetInt( "num_item_presets", -1 ); + SCHEMA_INIT_CHECK( (uint32)-1 != m_unNumItemPresets, "num_item_presets not set!" ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitAttributeTypes( CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_VEC( m_vecAttributeTypes, i ) + { + delete m_vecAttributeTypes[i].m_pAttrType; + } + m_vecAttributeTypes.Purge(); + + m_vecAttributeTypes.AddToTail( attr_type_t( NULL, new CSchemaAttributeType_Default ) ); + m_vecAttributeTypes.AddToTail( attr_type_t( "float", new CSchemaAttributeType_Float ) ); + m_vecAttributeTypes.AddToTail( attr_type_t( "uint64", new CSchemaAttributeType_UInt64 ) ); + m_vecAttributeTypes.AddToTail( attr_type_t( "string", new CSchemaAttributeType_String ) ); + m_vecAttributeTypes.AddToTail( attr_type_t( "dynamic_recipe_component_defined_item", new CSchemaAttributeType_DynamicRecipeComponentDefinedItem ) ); + m_vecAttributeTypes.AddToTail( attr_type_t( "item_slot_criteria", new CSchemaAttributeType_ItemSlotCriteria ) ); + m_vecAttributeTypes.AddToTail( attr_type_t( "item_placement", new CSchemaAttributeType_WorldItemPlacement ) ); + + // Make sure that all attribute types specified have the item ID in the 0th column. We use this + // when loading items to map between item IDs and the attributes they own. + FOR_EACH_VEC( m_vecAttributeTypes, i ) + { +#ifdef GC_DLL + const CColumnSet& cs = m_vecAttributeTypes[i].m_pAttrType->GetFullColumnSet(); + + SCHEMA_INIT_CHECK( cs.GetColumnCount() >= 2, "BInitAttributeTypes(): '%s' has invalid column count.\n", cs.GetRecordInfo()->GetName() ); + + const CColumnInfo& Column0 = cs.GetColumnInfo( 0 ); + SCHEMA_INIT_CHECK( Column0.GetType() == k_EGCSQLType_int64, "BInitAttributeTypes(): '%s' column 0 has invalid data type %u.\n", cs.GetRecordInfo()->GetName(), Column0.GetType() ); + SCHEMA_INIT_CHECK( Column0.GetName() && !V_stricmp( Column0.GetName(), "ItemID" ), "BInitAttributeTypes(): '%s' has invalid name '%s'.\n", cs.GetRecordInfo()->GetName(), Column0.GetName() ? Column0.GetName() : "[null]" ); + SCHEMA_INIT_CHECK( Column0.BIsPrimaryKey(), "BInitAttributeTypes(): '%s' has an item ID column that isn't in the PK.\n", cs.GetRecordInfo()->GetName() ); + + const CColumnInfo& Column1 = cs.GetColumnInfo( 1 ); + SCHEMA_INIT_CHECK( Column1.GetType() == k_EGCSQLType_int16, "BInitAttributeTypes(): '%s' column 1 has invalid data type %u.\n", cs.GetRecordInfo()->GetName(), Column0.GetType() ); + SCHEMA_INIT_CHECK( Column1.GetName() && !V_stricmp( Column1.GetName(), "AttrDefIndex" ), "BInitAttributeTypes(): '%s' has invalid name '%s'.\n", cs.GetRecordInfo()->GetName(), Column1.GetName() ? Column1.GetName() : "[null]" ); + + // Make sure two different attribute types don't point to the same DB table. There's nothing + // technically that would prevent this from working, but right now the way we load from the + // DB would make this super-inefficient so we'd want to fix that code if we are rolling content + // that would hit this error. + for ( int j = i + 1; j < m_vecAttributeTypes.Count(); j++ ) + { + SCHEMA_INIT_CHECK( cs.GetRecordInfo() != m_vecAttributeTypes[j].m_pAttrType->GetFullColumnSet().GetRecordInfo(), + "BInitAttributeTypes(): multiple attribute types reference the same table '%s'.\n", cs.GetRecordInfo()->GetName() ); + } +#endif // GC_DLL + } + + return SCHEMA_INIT_SUCCESS(); +} + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: Initializes the "periodic_score_accumulation" section of the schema +//----------------------------------------------------------------------------- +struct periodic_score_event_lookup_entry_t { const char *m_pszName; eEconPeriodicScoreEvents m_eValue; bool m_bGCUpdateOnly; }; +static const periodic_score_event_lookup_entry_t sPeriodicScoreEvents[] = +{ + { "gifts_distributed", kPeriodicScoreEvent_GiftsDistributed, true }, + { "duels_won", kPeriodicScoreEvent_DuelsWon, true }, + { "map_stamps_purchased", kPeriodicScoreEvent_MapStampsPurchased, true }, +}; + +struct periodic_score_duration_lookup_entry_t { const char *m_pszName; uint32 m_unValue; }; +static const periodic_score_duration_lookup_entry_t sPeriodicScoreDurations[] = +{ + { "disabled", 0 }, + { "hourly", 60 * 60 }, + { "daily", 60 * 60 * 24 }, + { "weekly", 60 * 60 * 24 * 7 }, + { "monthly", 60 * 60 * 24 * 7 * 4 }, // four weeks, not necessarily a month boundary +}; + +template < typename search_entry_type, int search_entry_array_size > +static bool LookupValueFromString( const search_entry_type(&searchArray)[search_entry_array_size], const char *pszSearch, search_entry_type *out_pResult ) +{ + Assert( out_pResult ); + + for ( int i = 0; i < search_entry_array_size; i++ ) + { + if ( !V_stricmp( pszSearch, searchArray[i].m_pszName ) ) + { + *out_pResult = searchArray[i]; + return true; + } + } + + return false; +} + +bool CEconItemSchema::BInitPeriodicScoring( KeyValues *pKVPeriodicScoring, CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_TRUE_SUBKEY( pKVPeriodicScoring, pKVScoreType ) + { + int index = Q_atoi( pKVScoreType->GetName() ); + SCHEMA_INIT_CHECK( index == m_vecPeriodicScoreTypes.Count(), "Invalid or out-of-order periodic score type '%s'", pKVScoreType->GetName() ); + + periodic_score_t PeriodicScore; + + // Reward item definition. + const char *pszRewardItemDefName = pKVScoreType->GetString( "reward_item_def_name", NULL ); + PeriodicScore.m_pRewardItemDefinition = pszRewardItemDefName ? GetItemDefinitionByName( pszRewardItemDefName ) : NULL; + SCHEMA_INIT_CHECK( PeriodicScore.m_pRewardItemDefinition, "Periodic score type '%s' missing reward item definition name", pKVScoreType->GetName() ); + + // Event type via string lookup. + const char *pszEventName = pKVScoreType->GetString( "event", "" ); + { + periodic_score_event_lookup_entry_t EventEntry; + SCHEMA_INIT_CHECK( LookupValueFromString( sPeriodicScoreEvents, pszEventName, &EventEntry ), + "Periodic score type '%s' could not find event name '%s'", pKVScoreType->GetName(), pszEventName ); + PeriodicScore.m_eEventType = EventEntry.m_eValue; + + // Note: other parts of the code assume that the event type is associated with the GC-only updatability flag.) + PeriodicScore.m_bGCUpdateOnly = EventEntry.m_bGCUpdateOnly; + } + + // Time period via string lookup. + { + const char *pszTimePeriodName = pKVScoreType->GetString( "time_period", "" ); + periodic_score_duration_lookup_entry_t DurationEntry; + SCHEMA_INIT_CHECK( LookupValueFromString( sPeriodicScoreDurations, pszTimePeriodName, &DurationEntry ), + "Periodic score type '%s' could not find time period name '%s'", pKVScoreType->GetName(), pszEventName ); + PeriodicScore.m_unTimePeriodLengthInSeconds = DurationEntry.m_unValue; + } + + // Alternate time period specified for use in internal Steam? + if ( GGCHost()->GetUniverse() != k_EUniversePublic ) + { + const char *pszInternalTimePeriodName = pKVScoreType->GetString( "time_period_internal", NULL ); + if ( pszInternalTimePeriodName ) + { + periodic_score_duration_lookup_entry_t InternalDurationEntry; + SCHEMA_INIT_CHECK( LookupValueFromString( sPeriodicScoreDurations, pszInternalTimePeriodName, &InternalDurationEntry ), + "Periodic score type '%s' could not find internal time period name '%s'", pKVScoreType->GetName(), pszEventName ); + PeriodicScore.m_unTimePeriodLengthInSeconds = InternalDurationEntry.m_unValue; + } + } + + m_vecPeriodicScoreTypes.AddToTail( PeriodicScore ); + } + + return SCHEMA_INIT_SUCCESS(); +} +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Purpose: Initializes the "prefabs" section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitDefinitionPrefabs( KeyValues *pKVPrefabs, CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_TRUE_SUBKEY( pKVPrefabs, pKVPrefab ) + { + const char *pszPrefabName = pKVPrefab->GetName(); + + int nMapIndex = m_mapDefinitionPrefabs.Find( pszPrefabName ); + + // Make sure the item index is correct because we use this index as a reference + SCHEMA_INIT_CHECK( + !m_mapDefinitionPrefabs.IsValidIndex( nMapIndex ), + "Duplicate prefab name (%s)", pszPrefabName ); + + m_mapDefinitionPrefabs.Insert( pszPrefabName, pKVPrefab->MakeCopy() ); + } + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initializes the Item Series section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitItemSeries( KeyValues *pKVSeries, CUtlVector<CUtlString> *pVecErrors ) +{ + // initialize the item definitions + if ( NULL != pKVSeries) + { + FOR_EACH_TRUE_SUBKEY( pKVSeries, pKVItem ) + { + int nSeriesIndex = pKVItem->GetInt( "value" ); + int nMapIndex = m_mapItemSeries.Find( nSeriesIndex ); + + // Make sure the item index is correct because we use this index as a reference + SCHEMA_INIT_CHECK( + !m_mapItemSeries.IsValidIndex( nMapIndex ), + "Duplicate item series value (%d)", nSeriesIndex ); + + nMapIndex = m_mapItemSeries.Insert( nMapIndex ); + SCHEMA_INIT_SUBSTEP( m_mapItemSeries[nMapIndex].BInitFromKV( pKVItem, pVecErrors ) ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the rarity section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitRarities( KeyValues *pKVRarities, KeyValues *pKVRarityWeights, CUtlVector<CUtlString> *pVecErrors ) +{ + // initialize the item definitions + if ( NULL != pKVRarities ) + { + FOR_EACH_TRUE_SUBKEY( pKVRarities, pKVRarity ) + { + int nRarityIndex = pKVRarity->GetInt( "value" ); + int nMapIndex = m_mapRarities.Find( nRarityIndex ); + + // Make sure the item index is correct because we use this index as a reference + SCHEMA_INIT_CHECK( + !m_mapRarities.IsValidIndex( nMapIndex ), + "Duplicate rarity value (%d)", nRarityIndex ); + + nMapIndex = m_mapRarities.Insert( nRarityIndex ); + SCHEMA_INIT_SUBSTEP( m_mapRarities[nMapIndex].BInitFromKV( pKVRarity, pKVRarityWeights, *this, pVecErrors ) ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the qualities section of the schema +// Input: pKVQualities - The qualities section of the KeyValues +// representation of the schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitQualities( KeyValues *pKVQualities, CUtlVector<CUtlString> *pVecErrors ) +{ + // initialize the item definitions + if ( NULL != pKVQualities ) + { + FOR_EACH_TRUE_SUBKEY( pKVQualities, pKVQuality ) + { + int nQualityIndex = pKVQuality->GetInt( "value" ); + int nMapIndex = m_mapQualities.Find( nQualityIndex ); + + // Make sure the item index is correct because we use this index as a reference + SCHEMA_INIT_CHECK( + !m_mapQualities.IsValidIndex( nMapIndex ), + "Duplicate quality value (%d)", nQualityIndex ); + + nMapIndex = m_mapQualities.Insert( nQualityIndex ); + SCHEMA_INIT_SUBSTEP( m_mapQualities[nMapIndex].BInitFromKV( pKVQuality, pVecErrors ) ); + } + } + + // Check the integrity of the quality definitions + + // Check for duplicate quality names + CUtlRBTree<const char *> rbQualityNames( CaselessStringLessThan ); + rbQualityNames.EnsureCapacity( m_mapQualities.Count() ); + FOR_EACH_MAP_FAST( m_mapQualities, i ) + { + int iIndex = rbQualityNames.Find( m_mapQualities[i].GetName() ); + SCHEMA_INIT_CHECK( + !rbQualityNames.IsValidIndex( iIndex ), + "Quality definition %d: Duplicate quality name %s", m_mapQualities[i].GetDBValue(), m_mapQualities[i].GetName() ); + + if( !rbQualityNames.IsValidIndex( iIndex ) ) + rbQualityNames.Insert( m_mapQualities[i].GetName() ); + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitColors( KeyValues *pKVColors, CUtlVector<CUtlString> *pVecErrors ) +{ + // initialize the color definitions + if ( NULL != pKVColors ) + { + FOR_EACH_TRUE_SUBKEY( pKVColors, pKVColor ) + { + CEconColorDefinition *pNewColorDef = new CEconColorDefinition; + + SCHEMA_INIT_SUBSTEP( pNewColorDef->BInitFromKV( pKVColor, pVecErrors ) ); + m_vecColorDefs.AddToTail( pNewColorDef ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconItemSchema::GetEquipRegionIndexByName( const char *pRegionName ) const +{ + FOR_EACH_VEC( m_vecEquipRegionsList, i ) + { + const char *szEntryRegionName = m_vecEquipRegionsList[i].m_sName.Get(); + if ( !V_stricmp( szEntryRegionName, pRegionName ) ) + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +equip_region_mask_t CEconItemSchema::GetEquipRegionBitMaskByName( const char *pRegionName ) const +{ + int iRegionIndex = GetEquipRegionIndexByName( pRegionName ); + if ( !m_vecEquipRegionsList.IsValidIndex( iRegionIndex ) ) + return 0; + + equip_region_mask_t unRegionMask = 1 << m_vecEquipRegionsList[iRegionIndex].m_unBitIndex; + Assert( unRegionMask > 0 ); + + return unRegionMask; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemSchema::SetEquipRegionConflict( int iRegion, unsigned int unBit ) +{ + Assert( m_vecEquipRegionsList.IsValidIndex( iRegion ) ); + + equip_region_mask_t unRegionMask = 1 << unBit; + Assert( unRegionMask > 0 ); + + m_vecEquipRegionsList[iRegion].m_unMask |= unRegionMask; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +equip_region_mask_t CEconItemSchema::GetEquipRegionMaskByName( const char *pRegionName ) const +{ + int iRegionIdx = GetEquipRegionIndexByName( pRegionName ); + if ( iRegionIdx < 0 ) + return 0; + + return m_vecEquipRegionsList[iRegionIdx].m_unMask; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemSchema::AssignDefaultBodygroupState( const char *pszBodygroupName, int iValue ) +{ + // Flip the value passed in -- if we specify in the schema that a region should be off, we assume that it's + // on by default. + // actually the schemas are all authored assuming that the default is 0, so let's use that + int iDefaultValue = 0; //iValue == 0 ? 1 : 0; + + // Make sure that we're constantly reinitializing our default value to the same default value. This is sort + // of dumb but it works for everything we've got now. In the event that conflicts start cropping up it would + // be easy enough to make a new schema section. + int iIndex = m_mapDefaultBodygroupState.Find( pszBodygroupName ); + if ( (m_mapDefaultBodygroupState.IsValidIndex( iIndex ) && m_mapDefaultBodygroupState[iIndex] != iDefaultValue) || + (iValue < 0 || iValue > 1) ) + { + EmitWarning( SPEW_GC, 4, "Unable to get accurate read on whether bodygroup '%s' is enabled or disabled by default. (The schema is fine, but the code is confused and could stand to be made smarter.)\n", pszBodygroupName ); + } + + if ( !m_mapDefaultBodygroupState.IsValidIndex( iIndex ) ) + { + m_mapDefaultBodygroupState.Insert( pszBodygroupName, iDefaultValue ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitEquipRegions( KeyValues *pKVEquipRegions, CUtlVector<CUtlString> *pVecErrors ) +{ + CUtlVector<const char *> vecNames; + + FOR_EACH_SUBKEY( pKVEquipRegions, pKVRegion ) + { + const char *pRegionKeyName = pKVRegion->GetName(); + + vecNames.Purge(); + + // The "shared" name is special for equip regions -- it means that all of the sub-regions specified + // will use the same bit to store equipped-or-not data, but that one bit can be accessed by a whole + // bunch of different names. This is useful in TF where different classes have different regions, but + // those regions cannot possibly conflict with each other. For example, "scout_backpack" cannot possibly + // overlap with "pyro_shoulder" because they can't even be equipped on the same character. + if ( pRegionKeyName && !Q_stricmp( pRegionKeyName, "shared" ) ) + { + FOR_EACH_SUBKEY( pKVRegion, pKVSharedRegionName ) + { + vecNames.AddToTail( pKVSharedRegionName->GetName() ); + } + } + // We have a standard name -- this one entry is its own equip region. + else + { + vecNames.AddToTail( pRegionKeyName ); + } + + // What bit will this equip region use to mask against conflicts? If we don't have any equip regions + // at all, we'll use the base bit, otherwise we just grab one higher than whatever we used last. + unsigned int unNewBitIndex = m_vecEquipRegionsList.Count() <= 0 ? 0 : m_vecEquipRegionsList.Tail().m_unBitIndex + 1; + + FOR_EACH_VEC( vecNames, i ) + { + const char *pRegionName = vecNames[i]; + + // Make sure this name is unique. + if ( GetEquipRegionIndexByName( pRegionName ) >= 0 ) + { + pVecErrors->AddToTail( CFmtStr( "Duplicate equip region named \"%s\".", pRegionName ).Access() ); + continue; + } + + // Make a new region. + EquipRegion newEquipRegion; + newEquipRegion.m_sName = pRegionName; + newEquipRegion.m_unMask = 0; // we'll update this mask later + newEquipRegion.m_unBitIndex = unNewBitIndex; + + int iIdx = m_vecEquipRegionsList.AddToTail( newEquipRegion ); + + // Tag this region to conflict with itself so that if nothing else two items in the same + // region can't equip over each other. + SetEquipRegionConflict( iIdx, unNewBitIndex ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitEquipRegionConflicts( KeyValues *pKVEquipRegionConflicts, CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_TRUE_SUBKEY( pKVEquipRegionConflicts, pKVConflict ) + { + // What region is the base of this conflict? + const char *pRegionName = pKVConflict->GetName(); + int iRegionIdx = GetEquipRegionIndexByName( pRegionName ); + if ( iRegionIdx < 0 ) + { + pVecErrors->AddToTail( CFmtStr( "Unable to find base equip region named \"%s\" for conflicts.", pRegionName ).Access() ); + continue; + } + + FOR_EACH_SUBKEY( pKVConflict, pKVConflictOther ) + { + const char *pOtherRegionName = pKVConflictOther->GetName(); + int iOtherRegionIdx = GetEquipRegionIndexByName( pOtherRegionName ); + if ( iOtherRegionIdx < 0 ) + { + pVecErrors->AddToTail( CFmtStr( "Unable to find other equip region named \"%s\" for conflicts.", pOtherRegionName ).Access() ); + continue; + } + + SetEquipRegionConflict( iRegionIdx, m_vecEquipRegionsList[iOtherRegionIdx].m_unBitIndex ); + SetEquipRegionConflict( iOtherRegionIdx, m_vecEquipRegionsList[iRegionIdx].m_unBitIndex ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the attributes section of the schema +// Input: pKVAttributes - The attributes section of the KeyValues +// representation of the schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitAttributes( KeyValues *pKVAttributes, CUtlVector<CUtlString> *pVecErrors ) +{ + // Initialize the attribute definitions + FOR_EACH_TRUE_SUBKEY( pKVAttributes, pKVAttribute ) + { + int nAttrIndex = Q_atoi( pKVAttribute->GetName() ); + int nMapIndex = m_mapAttributes.Find( nAttrIndex ); + + // Make sure the index is positive + SCHEMA_INIT_CHECK( + nAttrIndex >= 0, + "Attribute definition index %d must be greater than or equal to zero", nAttrIndex ); + + // Make sure the attribute index is not repeated + SCHEMA_INIT_CHECK( + !m_mapAttributes.IsValidIndex( nMapIndex ), + "Duplicate attribute definition index (%d)", nAttrIndex ); + + nMapIndex = m_mapAttributes.Insert( nAttrIndex ); + + SCHEMA_INIT_SUBSTEP( m_mapAttributes[nMapIndex].BInitFromKV( pKVAttribute, pVecErrors ) ); + } + + // Check the integrity of the attribute definitions + + // Check for duplicate attribute definition names + CUtlRBTree<const char *> rbAttributeNames( CaselessStringLessThan ); + rbAttributeNames.EnsureCapacity( m_mapAttributes.Count() ); + FOR_EACH_MAP_FAST( m_mapAttributes, i ) + { + int iIndex = rbAttributeNames.Find( m_mapAttributes[i].GetDefinitionName() ); + SCHEMA_INIT_CHECK( + !rbAttributeNames.IsValidIndex( iIndex ), + "Attribute definition %d: Duplicate name \"%s\"", m_mapAttributes.Key( i ), m_mapAttributes[i].GetDefinitionName() ); + if( !rbAttributeNames.IsValidIndex( iIndex ) ) + rbAttributeNames.Insert( m_mapAttributes[i].GetDefinitionName() ); + } + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initializes the items section of the schema +// Input: pKVItems - The items section of the KeyValues +// representation of the schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitItems( KeyValues *pKVItems, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapItems.PurgeAndDeleteElements(); + m_mapItemsSorted.Purge(); + m_mapToolsItems.Purge(); + m_mapBaseItems.Purge(); + m_vecBundles.Purge(); + m_mapQuestObjectives.PurgeAndDeleteElements(); + m_vecItemCollectionCrates.Purge(); + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + if ( m_pDefaultItemDefinition ) + { + delete m_pDefaultItemDefinition; + m_pDefaultItemDefinition = NULL; + } +#endif + + // initialize the item definitions + if ( NULL != pKVItems ) + { + FOR_EACH_TRUE_SUBKEY( pKVItems, pKVItem ) + { + if ( Q_stricmp( pKVItem->GetName(), "default" ) == 0 ) + { +#if defined(CLIENT_DLL) || defined(GAME_DLL) + SCHEMA_INIT_CHECK( + m_pDefaultItemDefinition == NULL, + "Duplicate 'default' item definition." ); + + m_pDefaultItemDefinition = CreateEconItemDefinition(); + SCHEMA_INIT_SUBSTEP( m_pDefaultItemDefinition->BInitFromKV( pKVItem, pVecErrors ) ); +#endif + } + else + { + int nItemIndex = Q_atoi( pKVItem->GetName() ); + int nMapIndex = m_mapItems.Find( nItemIndex ); + + // Make sure the item index is correct because we use this index as a reference + SCHEMA_INIT_CHECK( + !m_mapItems.IsValidIndex( nMapIndex ), + "Duplicate item definition (%d)", nItemIndex ); + + // Check to make sure the index is positive + SCHEMA_INIT_CHECK( + nItemIndex >= 0, + "Item definition index %d must be greater than or equal to zero", nItemIndex ); + + CEconItemDefinition *pItemDef = CreateEconItemDefinition(); + nMapIndex = m_mapItems.Insert( nItemIndex, pItemDef ); + m_mapItemsSorted.Insert( nItemIndex, pItemDef ); + SCHEMA_INIT_SUBSTEP( m_mapItems[nMapIndex]->BInitFromKV( pKVItem, pVecErrors ) ); + + // Cache off Tools references + if ( pItemDef->IsTool() ) + { + m_mapToolsItems.Insert( nItemIndex, pItemDef ); + } + + if ( pItemDef->IsBaseItem() ) + { + m_mapBaseItems.Insert( nItemIndex, pItemDef ); + } + + // Cache off bundles for the link phase below. + if ( pItemDef->IsBundle() ) + { + // Cache off the item def for the bundle, since we'll need both the bundle info and the item def index later. + m_vecBundles.AddToTail( pItemDef ); + + // If the bundle is a pack bundle, mark all the contained items as pack items / link to the owning pack bundle + if ( pItemDef->IsPackBundle() ) + { + const bundleinfo_t *pBundleInfo = pItemDef->GetBundleInfo(); + FOR_EACH_VEC( pBundleInfo->vecItemDefs, iCurItem ) + { + CEconItemDefinition *pCurItemDef = pBundleInfo->vecItemDefs[ iCurItem ]; + SCHEMA_INIT_CHECK( NULL == pCurItemDef->m_pOwningPackBundle, "Pack item \"%s\" included in more than one pack bundle - not allowed!", pCurItemDef->GetDefinitionName() ); + pCurItemDef->m_pOwningPackBundle = pItemDef; + } + } + } + + static CSchemaAttributeDefHandle pAttrDef_ContainsCollection( "contains collection" ); + if ( pAttrDef_ContainsCollection ) + { + FOR_EACH_VEC( pItemDef->GetStaticAttributes(), i ) + { + const static_attrib_t& staticAttrib = pItemDef->GetStaticAttributes()[i]; + if ( staticAttrib.iDefIndex == pAttrDef_ContainsCollection->GetDefinitionIndex() ) + { + // Add to collection crate list + m_vecItemCollectionCrates.AddToTail( pItemDef->GetDefinitionIndex() ); + } + } + } + } + } + } + + // Check the integrity of the item definitions + CUtlRBTree<const char *> rbItemNames( CaselessStringLessThan ); + rbItemNames.EnsureCapacity( m_mapItems.Count() ); + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + CEconItemDefinition *pItemDef = m_mapItems[ i ]; + + // Check for duplicate item definition names + int iIndex = rbItemNames.Find( pItemDef->GetDefinitionName() ); + SCHEMA_INIT_CHECK( + !rbItemNames.IsValidIndex( iIndex ), + "Item definition %s: Duplicate name on index %d", pItemDef->GetDefinitionName(), m_mapItems.Key( i ) ); + if( !rbItemNames.IsValidIndex( iIndex ) ) + rbItemNames.Insert( m_mapItems[i]->GetDefinitionName() ); + + // Link up armory and store mappings for the item + SCHEMA_INIT_SUBSTEP( pItemDef->BInitItemMappings( pVecErrors ) ); + } + +#ifdef DOTA + // Go through all regular (ie non-pack) bundles and ensure that if any pack items are included, *all* pack items in the owning pack bundle are included + FOR_EACH_VEC( m_vecBundles, iBundle ) + { + const CEconItemDefinition *pBundleItemDef = m_vecBundles[ iBundle ]; + if ( pBundleItemDef->IsPackBundle() ) + continue; + + // Go through all items in the bundle and look for pack items + const bundleinfo_t *pBundle = pBundleItemDef->GetBundleInfo(); + if ( pBundle ) + { + FOR_EACH_VEC( pBundle->vecItemDefs, iContainedBundleItem ) + { + // Get the associated pack bundle + const CEconItemDefinition *pContainedBundleItemDef = pBundle->vecItemDefs[ iContainedBundleItem ]; + + // Ignore non-pack items + if ( !pContainedBundleItemDef || !pContainedBundleItemDef->IsPackItem() ) + continue; + + // Get the pack bundle that contains this particular pack item + const CEconItemDefinition *pOwningPackBundleItemDef = pContainedBundleItemDef->GetOwningPackBundle(); + + // Make sure all items in the owning pack bundle are in pBundleItemDef's bundle info (pBundle) + const bundleinfo_t *pOwningPackBundle = pOwningPackBundleItemDef->GetBundleInfo(); + FOR_EACH_VEC( pOwningPackBundle->vecItemDefs, iCurPackBundleItem ) + { + CEconItemDefinition *pCurPackBundleItem = pOwningPackBundle->vecItemDefs[ iCurPackBundleItem ]; + if ( !pBundle->vecItemDefs.HasElement( pCurPackBundleItem ) ) + { + SCHEMA_INIT_CHECK( + false, + "The bundle \"%s\" contains some, but not all pack items required specified by pack bundle \"%s.\"", + pBundleItemDef->GetDefinitionName(), + pOwningPackBundleItemDef->GetDefinitionName() + ); + } + } + } + } + } +#endif + + return SCHEMA_INIT_SUCCESS(); +} + +#if 0 // Compiled out until some DotA changes from the item editor are brought over +//----------------------------------------------------------------------------- +// Purpose: Delete an item definition. Moderately dangerous as cached references will become bad. +// Intended for use by the item editor. +//----------------------------------------------------------------------------- +bool CEconItemSchema::DeleteItemDefinition( int iDefIndex ) +{ + m_mapItemsSorted.Remove( iDefIndex ); + + int nMapIndex = m_mapItems.Find( iDefIndex ); + if ( m_mapItems.IsValidIndex( nMapIndex ) ) + { + CEconItemDefinition* pItemDef = m_mapItems[nMapIndex]; + if ( pItemDef ) + { + m_mapItems.RemoveAt( nMapIndex ); + delete pItemDef; + return true; + } + } + return false; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Parses the Item Sets section. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitItemSets( KeyValues *pKVItemSets, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapItemSets.RemoveAll(); + + if ( NULL != pKVItemSets ) + { + FOR_EACH_TRUE_SUBKEY( pKVItemSets, pKVItemSet ) + { + const char* setName = pKVItemSet->GetName(); + + SCHEMA_INIT_CHECK( setName != NULL, "All itemsets must have names." ); + SCHEMA_INIT_CHECK( m_mapItemSets.Find( setName ) == m_mapItemSets.InvalidIndex(), "Duplicate itemset name (%s) found!", setName ); + + int idx = m_mapItemSets.Insert( setName, new CEconItemSetDefinition ); + SCHEMA_INIT_SUBSTEP( m_mapItemSets[idx]->BInitFromKV( pKVItemSet, pVecErrors ) ); + } + + // Once we've initialized all of our item sets, loop through all of our item definitions looking + // for pseudo set items. For example, the Festive Holy Mackerel is a different item definition from + // the regular Holy Mackerel, but for set completion and set listing purposes, we want it to show + // as part of the base set. + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + CEconItemDefinition *pItemDef = m_mapItems[i]; + Assert( pItemDef ); + + // Items that point to themselves are the base set items and got initialized as part of the + // set initialization above. + if ( pItemDef->GetSetItemRemap() == pItemDef->GetDefinitionIndex() ) + continue; + + // Which item are we stealing set information from? + const CEconItemDefinition *pRemappedSetItemDef = GetItemDefinition( pItemDef->GetSetItemRemap() ); + AssertMsg( pRemappedSetItemDef, "Somehow got through item and set initialization but have a broken set remap item!" ); + + pItemDef->SetItemSetDefinition( pRemappedSetItemDef->GetItemSetDefinition() ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +bool CEconItemSchema::BVerifyBaseItemNames( CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + CEconItemDefinition *pItemDef = m_mapItems[i]; + + // get base item name + const char* pBaseName = pItemDef->GetBaseFunctionalItemName(); + + if ( !pBaseName || pBaseName[0] == '\0' ) + { + continue; + } + + // look up base item name + SCHEMA_INIT_CHECK( GetItemDefinitionByName( pBaseName ) != NULL, "Base item name not found %s.", pBaseName ); + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitItemCollections( KeyValues *pKVItemCollections, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapItemCollections.Purge(); + + if ( NULL != pKVItemCollections ) + { + FOR_EACH_TRUE_SUBKEY( pKVItemCollections, pKVItemCollection ) + { + const char* setName = pKVItemCollection->GetName(); + + SCHEMA_INIT_CHECK( setName != NULL, "All item collections must have names." ); + SCHEMA_INIT_CHECK( m_mapItemCollections.Find( setName ) == m_mapItemCollections.InvalidIndex(), "Duplicate item collection name (%s) found!", setName ); + + int idx = m_mapItemCollections.Insert( setName, new CEconItemCollectionDefinition ); + SCHEMA_INIT_SUBSTEP( m_mapItemCollections[idx]->BInitFromKV( pKVItemCollection, pVecErrors ) ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitCollectionReferences( CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + CEconItemDefinition *pItemDef = m_mapItems[i]; + const char *pszCollectionName = pItemDef->GetCollectionReference(); + if ( pszCollectionName ) + { + // Find the collection + bool bFound = false; + FOR_EACH_MAP_FAST( m_mapItemCollections, iCollectionIndex ) + { + const char * pszTemp = m_mapItemCollections[iCollectionIndex]->m_pszName; + + if ( !V_strcmp( pszTemp, pszCollectionName) ) + { + bFound = true; + pItemDef->SetItemCollectionDefinition( m_mapItemCollections[iCollectionIndex] ); + break; + } + } + SCHEMA_INIT_CHECK( bFound == true, "Collection %s referenced by item %s not found", pszCollectionName, pItemDef->GetDefinitionName() ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} +//----------------------------------------------------------------------------- +const CEconItemCollectionDefinition *CEconItemSchema::GetCollectionByName( const char* pCollectionName ) +{ + if ( !pCollectionName ) + return NULL; + + FOR_EACH_MAP_FAST( m_mapItemCollections, iCollectionIndex ) + { + const char * pszTemp = m_mapItemCollections[iCollectionIndex]->m_pszName; + if ( !V_strcmp( pszTemp, pCollectionName ) ) + { + return m_mapItemCollections[iCollectionIndex]; + } + } + return NULL; +} + + + +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitItemPaintKitDefinitions( KeyValues *pKVItemPaintKits, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapItemPaintKits.Purge(); + + const char* cWhitespace = " \r\n\t"; // space, end of line, tab. + cWhitespace; // Compiler happiness for GC build + + if ( NULL != pKVItemPaintKits ) + { +#ifdef CLIENT_DLL + FOR_EACH_TRUE_SUBKEY( pKVItemPaintKits, pKVPaintKit ) + { + const char* keyField = pKVPaintKit->GetName(); + SCHEMA_INIT_CHECK( keyField != NULL, "All item collections must have names." ); + + if ( V_stristr( keyField, "paintkit_template" ) != NULL ) + { + static const int cSkipLen = strlen( "paintkit_template" ); + keyField += cSkipLen; + keyField += strspn( keyField, cWhitespace ); + + bool createTmplResult = materials->AddTextureCompositorTemplate( keyField, pKVPaintKit ); + SCHEMA_INIT_CHECK( createTmplResult, "Could Not Create paintkit_template '%s'", keyField ); + } + } + + // Do post-load validation before moving on to paintkits. + SCHEMA_INIT_CHECK( materials->VerifyTextureCompositorTemplates(), "Paintkit template post-init validation failed." ); +#endif + + // Now do all the paintkits + FOR_EACH_TRUE_SUBKEY( pKVItemPaintKits, pKVPaintKit ) + { + // We know the keyField is valid, it was checked above. + const char* keyField = pKVPaintKit->GetName(); + + if ( V_stristr( keyField, "paintkit_template" ) == NULL ) + { + SCHEMA_INIT_CHECK( m_mapItemPaintKits.Find( keyField ) == m_mapItemPaintKits.InvalidIndex(), "Duplicate paint kit definition name (%s) found!", keyField ); + + int idx = m_mapItemPaintKits.Insert( keyField, new CEconItemPaintKitDefinition ); + SCHEMA_INIT_SUBSTEP( m_mapItemPaintKits[ idx ]->BInitFromKV( pKVPaintKit, pVecErrors ) ); + } + } + } + + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitOperationDefinitions( KeyValues *pKVGameInfo, KeyValues *pKVOperationDefinitions, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapOperationDefinitions.Purge(); + + if ( NULL != pKVOperationDefinitions ) + { + FOR_EACH_TRUE_SUBKEY( pKVOperationDefinitions, pKVOperation ) + { + const char* setName = pKVOperation->GetName(); + SCHEMA_INIT_CHECK( setName != NULL, "All operations must have names." ); + SCHEMA_INIT_CHECK( m_mapOperationDefinitions.Find( setName ) == m_mapOperationDefinitions.InvalidIndex(), "Duplicate operation definition name (%s) found!", setName ); + + CEconOperationDefinition *pNewOperation = new CEconOperationDefinition(); + SCHEMA_INIT_SUBSTEP( pNewOperation->BInitFromKV( pKVOperation, pVecErrors ) ); + + // don't add expired operation to list + if ( pNewOperation->IsExpired() ) + { + delete pNewOperation; + continue; + } + + m_mapOperationDefinitions.Insert( setName, pNewOperation ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initializes the timed rewards section of the schema +// Input: pKVTimedRewards - The timed_rewards section of the KeyValues +// representation of the schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitTimedRewards( KeyValues *pKVTimedRewards, CUtlVector<CUtlString> *pVecErrors ) +{ + m_vecTimedRewards.RemoveAll(); + + // initialize the rewards sections + if ( NULL != pKVTimedRewards ) + { + FOR_EACH_TRUE_SUBKEY( pKVTimedRewards, pKVTimedReward ) + { + int index = m_vecTimedRewards.AddToTail(); + SCHEMA_INIT_SUBSTEP( m_vecTimedRewards[index].BInitFromKV( pKVTimedReward, pVecErrors ) ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CTimedItemRewardDefinition* CEconItemSchema::GetTimedReward( eTimedRewardType type ) const +{ + if ( (int)type < m_vecTimedRewards.Count() ) + { + return &m_vecTimedRewards[type]; + } + return NULL; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Initializes the loot lists section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitLootLists( KeyValues *pKVLootLists, CUtlVector<CUtlString> *pVecErrors ) +{ + if ( NULL != pKVLootLists ) + { + FOR_EACH_TRUE_SUBKEY( pKVLootLists, pKVLootList ) + { + const char* pListName = pKVLootList->GetName(); + SCHEMA_INIT_SUBSTEP( BInsertLootlist( pListName, pKVLootList, pVecErrors ) ); + } + } + + FOR_EACH_MAP_FAST( m_mapLootLists, i ) + { + const CEconLootListDefinition *pLootList = m_mapLootLists[i]; + BVerifyLootListItemDropDates( pLootList, pVecErrors ); + } + + return SCHEMA_INIT_SUCCESS(); +} +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInsertLootlist( const char *pListName, KeyValues *pKVLootList, CUtlVector<CUtlString> *pVecErrors ) +{ + SCHEMA_INIT_CHECK( pListName != NULL, "All lootlists must have names." ); + + if ( m_mapLootLists.Count() > 0 ) + { + SCHEMA_INIT_CHECK( GetLootListByName( pListName ) == NULL, "Duplicate lootlist name (%s) found!", pListName ); + } + + CEconLootListDefinition *pLootList = new CEconLootListDefinition; + SCHEMA_INIT_SUBSTEP( pLootList->BInitFromKV( pKVLootList, *this, pVecErrors ) ); + m_mapLootLists.Insert( pListName, pLootList ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the revolving loot lists section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitRevolvingLootLists( KeyValues *pKVLootLists, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapRevolvingLootLists.RemoveAll(); + + if ( NULL != pKVLootLists ) + { + FOR_EACH_SUBKEY( pKVLootLists, pKVList ) + { + int iListIdx = pKVList->GetInt(); + const char* strListName = pKVList->GetName(); + m_mapRevolvingLootLists.Insert( iListIdx, strListName ); + } + } + + FOR_EACH_MAP_FAST( m_mapRevolvingLootLists, i ) + { + const CEconLootListDefinition* pLootList = GetLootListByName(m_mapRevolvingLootLists[i]); + BVerifyLootListItemDropDates( pLootList, pVecErrors ); + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Create and return a new quest objective definition. Verify that +// a definition with the same name doesnt alreay exist. +//----------------------------------------------------------------------------- +bool CEconItemSchema::AddQuestObjective( const CQuestObjectiveDefinition **ppQuestObjective, KeyValues *pKVObjective, CUtlVector<CUtlString> *pVecErrors ) +{ + // These need to be unique + int nDefIndex = pKVObjective->GetInt( "defindex", -1 ); + SCHEMA_INIT_CHECK( nDefIndex != -1, "Missing defindex for quest objective" ); + // Verify defindex is unique + auto nMapIndex = m_mapQuestObjectives.Find( nDefIndex ); + SCHEMA_INIT_CHECK( nMapIndex == m_mapQuestObjectives.InvalidIndex(), "Multiple quest objectives with defindex: %d", nDefIndex ); + // Create the quest def + nMapIndex = m_mapQuestObjectives.Insert( nDefIndex ); + m_mapQuestObjectives[ nMapIndex ] = CreateQuestDefinition(); + // Init + SCHEMA_INIT_SUBSTEP( m_mapQuestObjectives[nMapIndex]->BInitFromKV( pKVObjective, pVecErrors ) ); + + if ( ppQuestObjective ) + { + (*ppQuestObjective) = m_mapQuestObjectives[nMapIndex]; + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Verify that the contents of visible lootlist do not have drop dates +// associated with them. The thinking being that we dont want to have +// items listed that could potentially not drop, or items disappear/appear +// in from a list. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BVerifyLootListItemDropDates( const CEconLootListDefinition* pLootList, CUtlVector<CUtlString> *pVecErrors ) const +{ + if ( pLootList && pLootList->BPublicListContents() ) + { + BRecurseiveVerifyLootListItemDropDates( pLootList, pLootList, pVecErrors ); + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively dig through all entries in the passed in lootlist to see +// if any of the containted items have drop dates. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BRecurseiveVerifyLootListItemDropDates( const CEconLootListDefinition* pLootList, const CEconLootListDefinition* pRootLootList, CUtlVector<CUtlString> *pVecErrors ) const +{ + FOR_EACH_VEC( pLootList->GetLootListContents(), j ) + { + const CEconLootListDefinition::drop_item_t& item = pLootList->GetLootListContents()[j]; + // 0 and greater means item. Less than 0 means nested lootlist + if( item.m_iItemOrLootlistDef >= 0 ) + { + const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( item.m_iItemOrLootlistDef ); + if( pItemDef ) + { + static CSchemaAttributeDefHandle pAttribDef_StartDropDate( "start drop date" ); + static CSchemaAttributeDefHandle pAttribDef_EndDropDate( "end drop date" ); + + CAttribute_String value; + + // Check for start drop date attribute on this item + SCHEMA_INIT_CHECK( !FindAttribute( pItemDef, pAttribDef_StartDropDate, &value ), + "Lootlist \"%s\" contains lootlist \"%s\", which contains item \"%s\", which has start drop date.", pRootLootList->GetName(), pLootList->GetName(), pItemDef->GetDefinitionName() ); + // Check for end drop date attribute on this item + SCHEMA_INIT_CHECK( !FindAttribute( pItemDef, pAttribDef_EndDropDate, &value ), + "Lootlist \"%s\" contains lootlist \"%s\", which contains item \"%s\", which has end drop date.", pRootLootList->GetName(), pLootList->GetName(), pItemDef->GetDefinitionName() ); + } + } + else + { + // Get the nested lootlist + int iLLIndex = (item.m_iItemOrLootlistDef * -1) - 1; + const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); + if ( !pNestedLootList ) + return SCHEMA_INIT_SUCCESS(); + + // Dig through all of this lootlist's entries + BRecurseiveVerifyLootListItemDropDates( pNestedLootList, pRootLootList, pVecErrors ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initializes the recipes section of the schema +// Input: pKVRecipes - The recipes section of the KeyValues +// representation of the schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitRecipes( KeyValues *pKVRecipes, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapRecipes.RemoveAll(); + + // initialize the rewards sections + if ( NULL != pKVRecipes ) + { + FOR_EACH_TRUE_SUBKEY( pKVRecipes, pKVRecipe ) + { + int nRecipeIndex = Q_atoi( pKVRecipe->GetName() ); + int nMapIndex = m_mapRecipes.Find( nRecipeIndex ); + + // Make sure the recipe index is correct because we use this index as a reference + SCHEMA_INIT_CHECK( + !m_mapRecipes.IsValidIndex( nMapIndex ), + "Duplicate recipe definition (%d)", nRecipeIndex ); + + // Check to make sure the index is positive + SCHEMA_INIT_CHECK( + nRecipeIndex >= 0, + "Recipe definition index %d must be greater than or equal to zero", nRecipeIndex ); + + CEconCraftingRecipeDefinition *recipeDef = CreateCraftingRecipeDefinition(); + SCHEMA_INIT_SUBSTEP( recipeDef->BInitFromKV( pKVRecipe, pVecErrors ) ); + +#ifdef _DEBUG + // Sanity check in debug builds so that we know we aren't putting the same recipe in + // multiple times. + FOR_EACH_MAP_FAST( m_mapRecipes, i ) + { + Assert( i != nRecipeIndex ); + Assert( m_mapRecipes[i] != recipeDef ); + } +#endif // _DEBUG + + // Store this recipe. + m_mapRecipes.Insert( nRecipeIndex, recipeDef ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds the name of a achievement in the form App<ID>.<AchName> +// Input: unAppID - native app ID +// pchNativeAchievementName - name of the achievement in its native app +// Returns: The combined achievement name +//----------------------------------------------------------------------------- +CUtlString CEconItemSchema::ComputeAchievementName( AppId_t unAppID, const char *pchNativeAchievementName ) +{ + return CFmtStr1024( "App%u.%s", unAppID, pchNativeAchievementName ).Access(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initializes the achievement rewards section of the schema +// Input: pKVAchievementRewards - The achievement_rewards section of the KeyValues +// representation of the schema +// pVecErrors - An optional vector that will contain error messages if +// the init fails. +// Output: True if initialization succeeded, false otherwise +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitAchievementRewards( KeyValues *pKVAchievementRewards, CUtlVector<CUtlString> *pVecErrors ) +{ + m_dictAchievementRewards.RemoveAll(); + m_mapAchievementRewardsByData.PurgeAndDeleteElements(); + + // initialize the rewards sections + if ( NULL != pKVAchievementRewards ) + { + FOR_EACH_SUBKEY( pKVAchievementRewards, pKVReward ) + { + AchievementAward_t award; + if( pKVReward->GetDataType() == KeyValues::TYPE_NONE ) + { + int32 nItemIndex = pKVReward->GetInt( "DefIndex", -1 ); + if( nItemIndex != -1 ) + { + award.m_vecDefIndex.AddToTail( (uint16)nItemIndex ); + } + else + { + KeyValues *pkvItems = pKVReward->FindKey( "Items" ); + SCHEMA_INIT_CHECK( + pkvItems != NULL, + "Complex achievement %s must have an Items key or a DefIndex field", pKVReward->GetName() ); + if( !pkvItems ) + { + continue; + } + + FOR_EACH_VALUE( pkvItems, pkvItem ) + { + award.m_vecDefIndex.AddToTail( (uint16)Q_atoi( pkvItem->GetName() ) ); + } + } + + } + else + { + award.m_vecDefIndex.AddToTail( (uint16)pKVReward->GetInt("", -1 ) ); + } + + // make sure all the item types are valid + bool bFoundAllItems = true; + FOR_EACH_VEC( award.m_vecDefIndex, nItem ) + { + const CEconItemDefinition *pDefn = GetItemDefinition( award.m_vecDefIndex[nItem] ); + SCHEMA_INIT_CHECK( + pDefn != NULL, + "Item definition index %d in achievement reward %s was not found", award.m_vecDefIndex[nItem], pKVReward->GetName() ); + if( !pDefn ) + { + bFoundAllItems = false; + } + } + if( !bFoundAllItems ) + continue; + + SCHEMA_INIT_CHECK( + award.m_vecDefIndex.Count() > 0, + "Achievement reward %s has no items!", pKVReward->GetName() ); + if( award.m_vecDefIndex.Count() == 0 ) + continue; + +#ifdef GC_DLL + award.m_unSourceAppId = GGCBase()->GetAppID(); +#else + award.m_unSourceAppId = k_uAppIdInvalid; +#endif + if( pKVReward->GetDataType() == KeyValues::TYPE_NONE ) + { + // cross game achievement + award.m_sNativeName = pKVReward->GetName(); + award.m_unAuditData = pKVReward->GetInt( "AuditData", 0 ); + award.m_unSourceAppId = pKVReward->GetInt( "SourceAppID", award.m_unSourceAppId ); + } + else + { + award.m_sNativeName = pKVReward->GetName(); + award.m_unAuditData = 0; + } + + +#ifdef GC_DLL + // Check to make sure the audit data is valid + SCHEMA_INIT_CHECK( + award.m_unSourceAppId >= 0, + "Source App ID %d in achievement reward %s must be valid", award.m_unSourceAppId, pKVReward->GetName() ); + if( award.m_unSourceAppId == k_uAppIdInvalid ) + continue; + + if( !GGCGameBase()->BYieldingLoadStats( award.m_unSourceAppId ) ) + { + // this will often fail in a dev universe + if( GGCHost()->GetUniverse() != k_EUniverseDev ) + { + SCHEMA_INIT_CHECK( + false, + "Unable to load stats schema for cross-game achievement %s for app %d", pKVReward->GetName(), award.m_unSourceAppId ); + } + continue; + } + + const CGCStatsSchema *pStatsSchema = GGCGameBase()->GetStatsSchema( award.m_unSourceAppId ); + if( !pStatsSchema ) + { + SCHEMA_INIT_CHECK( + false, + "Unable to retrieve stats schema for cross-game achievement %s for app %d", pKVReward->GetName(), award.m_unSourceAppId ); + continue; + } + + if( award.m_unAuditData == 0 ) + { + uint16 usStatID, usBitID; + if( !pStatsSchema->BGetAchievementBit( award.m_sNativeName, &usStatID, &usBitID ) ) + { + SCHEMA_INIT_CHECK( + false, + "Unable to find achievement %s for app %d", award.m_sNativeName.Get(), award.m_unSourceAppId ); + continue; + } + + award.m_unAuditData = ( usStatID <<16 ) | usBitID; + } +#endif // GC_DLL + + + AchievementAward_t *pAward = new AchievementAward_t; + *pAward = award; + + m_dictAchievementRewards.Insert( ComputeAchievementName( pAward->m_unSourceAppId, pAward->m_sNativeName ), pAward ); + m_mapAchievementRewardsByData.Insert( pAward->m_unAuditData, pAward ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + + +#ifdef GC_DLL +bool CEconItemSchema::BInitRandomAttributeTemplates( KeyValues *pKVRandomAttributeTemplates, CUtlVector<CUtlString> *pVecErrors ) +{ + m_dictRandomAttributeTemplates.PurgeAndDeleteElements(); + + FOR_EACH_TRUE_SUBKEY( pKVRandomAttributeTemplates, pKVAttributeTemplate ) + { + const char *pszAttrName = pKVAttributeTemplate->GetName(); + + // try to create random attrib from template + random_attrib_t *pRandomAttr = CreateRandomAttribute( __FUNCTION__, pKVAttributeTemplate, pVecErrors ); + SCHEMA_INIT_CHECK( + NULL != pRandomAttr, + CFmtStr( "%s: Failed to create random_attrib_t '%s'", __FUNCTION__, pszAttrName ) ); + + m_dictRandomAttributeTemplates.Insert( pszAttrName, pRandomAttr ); + } + + return true; +} +#endif // GC_DLL + + +#ifdef TF_CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: Go through all items and cache the number of concrete items in each. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitConcreteItemCounts( CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + CEconItemDefinition *pItemDef = m_mapItems[ i ]; + pItemDef->m_unNumConcreteItems = CalculateNumberOfConcreteItems( pItemDef ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the number of actual "real" items referenced by the item definition +// (i.e. items that would take up space in the inventory) +//----------------------------------------------------------------------------- +int CEconItemSchema::CalculateNumberOfConcreteItems( const CEconItemDefinition *pItemDef ) +{ + AssertMsg( pItemDef, "NULL item definition! This should not happen!" ); + if ( !pItemDef ) + return 0; + + if ( pItemDef->IsBundle() ) + { + uint32 unNumConcreteItems = 0; + + const bundleinfo_t *pBundle = pItemDef->GetBundleInfo(); + Assert( pBundle ); + + FOR_EACH_VEC( pBundle->vecItemDefs, i ) + { + unNumConcreteItems += CalculateNumberOfConcreteItems( pBundle->vecItemDefs[i] ); + } + + return unNumConcreteItems; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitSteamPackageLocalizationToken( KeyValues *pKVSteamPackages, CUtlVector<CUtlString> *pVecErrors ) +{ + if ( NULL != pKVSteamPackages ) + { + FOR_EACH_TRUE_SUBKEY( pKVSteamPackages, pKVEntry ) + { + // Check to make sure the index is positive + int iRawPackageId = atoi( pKVEntry->GetName() ); + SCHEMA_INIT_CHECK( + iRawPackageId > 0, + "Invalid package ID %i for localization", iRawPackageId ); + + // Store off our data. + uint32 unPackageId = (uint32)iRawPackageId; + const char *pszLocalizationToken = pKVEntry->GetString( "localization_key" ); + + m_mapSteamPackageLocalizationTokens.InsertOrReplace( unPackageId, pszLocalizationToken ); + + } + } + return SCHEMA_INIT_SUCCESS(); +} +#endif // TF_CLIENT_DLL + +static const char *s_particle_controlpoint_names[] = +{ + "attachment", + "control_point_1", + "control_point_2", + "control_point_3", + "control_point_4", + "control_point_5", + "control_point_6", +}; + +//----------------------------------------------------------------------------- +// Purpose: Initializes the attribute-controlled-particle-systems section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitAttributeControlledParticleSystems( KeyValues *pKVParticleSystems, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapAttributeControlledParticleSystems.RemoveAll(); + m_vecAttributeControlledParticleSystemsCosmetics.RemoveAll(); + m_vecAttributeControlledParticleSystemsWeapons.RemoveAll(); + m_vecAttributeControlledParticleSystemsTaunts.RemoveAll(); + + CUtlVector< int > *pVec = NULL; + + // Addictional groups we are tracking for. + // "cosmetic_unusual_effects" + // "weapon_unusual_effects" + // "taunt_unusual_effects" + + if ( NULL != pKVParticleSystems ) + { + FOR_EACH_TRUE_SUBKEY( pKVParticleSystems, pKVCategory ) + { + // There is 3 Categories we want to track with additional info + if ( !V_strcmp( pKVCategory->GetName(), "cosmetic_unusual_effects" ) ) + { + pVec = &m_vecAttributeControlledParticleSystemsCosmetics; + } + else if ( !V_strcmp( pKVCategory->GetName(), "weapon_unusual_effects" ) ) + { + pVec = &m_vecAttributeControlledParticleSystemsWeapons; + } + else if ( !V_strcmp( pKVCategory->GetName(), "taunt_unusual_effects" ) ) + { + pVec = &m_vecAttributeControlledParticleSystemsTaunts; + } + else + { + pVec = NULL; // reset + } + + FOR_EACH_TRUE_SUBKEY( pKVCategory, pKVEntry ) + { + int32 nItemIndex = atoi( pKVEntry->GetName() ); + // Check to make sure the index is positive + SCHEMA_INIT_CHECK( + nItemIndex > 0, + "Particle system index %d greater than zero", nItemIndex ); + if ( nItemIndex <= 0 ) + continue; + int iIndex = m_mapAttributeControlledParticleSystems.Insert( nItemIndex ); + attachedparticlesystem_t &system = m_mapAttributeControlledParticleSystems[iIndex]; + system.pszSystemName = pKVEntry->GetString( "system", NULL ); + system.bFollowRootBone = pKVEntry->GetInt( "attach_to_rootbone", 0 ) != 0; + system.iCustomType = 0; + system.nSystemID = nItemIndex; + system.fRefireTime = pKVEntry->GetFloat( "refire_time", 0.0f ); + system.bDrawInViewModel = pKVEntry->GetBool( "draw_in_viewmodel", false ); + system.bUseSuffixName = pKVEntry->GetBool( "use_suffix_name", false ); + system.bHasViewModelSpecificEffect = pKVEntry->GetBool( "has_viewmodel_specific_effect", false ); + + COMPILE_TIME_ASSERT( ARRAYSIZE( system.pszControlPoints ) == ARRAYSIZE( s_particle_controlpoint_names ) ); + for ( int i=0; i<ARRAYSIZE( system.pszControlPoints ); ++i ) + { + system.pszControlPoints[i] = pKVEntry->GetString( s_particle_controlpoint_names[i], NULL ); + } + + if ( pVec ) + { + pVec->AddToTail( nItemIndex ); + } + } + } + } + return SCHEMA_INIT_SUCCESS(); +} + +#ifdef CLIENT_DLL +locchar_t *CEconItemSchema::GetParticleSystemLocalizedName( int index ) const +{ + const attachedparticlesystem_t *pSystem = GetItemSchema()->GetAttributeControlledParticleSystem( index ); + if ( !pSystem ) + return NULL; + + char particleNameEntry[128]; + Q_snprintf( particleNameEntry, ARRAYSIZE( particleNameEntry ), "#Attrib_Particle%d", pSystem->nSystemID ); + + return g_pVGuiLocalize->Find( particleNameEntry ); +} + +#endif +//----------------------------------------------------------------------------- +// Purpose: Inits data for items that can level up through kills, etc. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitItemLevels( KeyValues *pKVItemLevels, CUtlVector<CUtlString> *pVecErrors ) +{ + m_vecItemLevelingData.RemoveAll(); + + // initialize the rewards sections + if ( NULL != pKVItemLevels ) + { + FOR_EACH_TRUE_SUBKEY( pKVItemLevels, pKVItemLevelBlock ) + { + const char *pszLevelBlockName = pKVItemLevelBlock->GetName(); + SCHEMA_INIT_CHECK( GetItemLevelingData( pszLevelBlockName ) == NULL, + "Duplicate leveling data block named \"%s\".", pszLevelBlockName ); + + // Allocate a new structure for this block and assign it. We'll fill in the contents later. + CUtlVector<CItemLevelingDefinition> *pLevelingData = new CUtlVector<CItemLevelingDefinition>; + m_vecItemLevelingData.Insert( pszLevelBlockName, pLevelingData ); + + FOR_EACH_TRUE_SUBKEY( pKVItemLevelBlock, pKVItemLevel ) + { + int index = pLevelingData->AddToTail(); + SCHEMA_INIT_SUBSTEP( (*pLevelingData)[index].BInitFromKV( pKVItemLevel, pszLevelBlockName, pVecErrors ) ); + } + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Inits data for kill eater types. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitKillEaterScoreTypes( KeyValues *pKVKillEaterScoreTypes, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapKillEaterScoreTypes.RemoveAll(); + + // initialize the rewards sections + if ( NULL != pKVKillEaterScoreTypes ) + { + FOR_EACH_TRUE_SUBKEY( pKVKillEaterScoreTypes, pKVScoreType ) + { + unsigned int unIndex = (unsigned int)atoi( pKVScoreType->GetName() ); + SCHEMA_INIT_CHECK( m_mapKillEaterScoreTypes.Find( unIndex ) == KillEaterScoreMap_t::InvalidIndex(), + "Duplicate kill eater score type index %u.", unIndex ); + + kill_eater_score_type_t ScoreType; + ScoreType.m_pszTypeString = pKVScoreType->GetString( "type_name" ); + ScoreType.m_bAllowBotVictims = pKVScoreType->GetBool( "allow_bot_victims", false ); +#ifdef GC_DLL + ScoreType.m_bGCUpdateOnly = pKVScoreType->GetBool( "gc_update_only", false ); + ScoreType.m_AllowIncrementValues = pKVScoreType->GetBool( "gc_allow_increment_values", false ); + ScoreType.m_bIsBaseKillType = pKVScoreType->GetBool( "gc_is_base_kill_type", false ); +#endif + + const char *pszLevelBlockName = pKVScoreType->GetString( "level_data", "KillEaterRank" ); + SCHEMA_INIT_CHECK( GetItemLevelingData( pszLevelBlockName ) != NULL, + "Unable to find leveling data block named \"%s\" for kill eater score type %u.", pszLevelBlockName, unIndex ); + + ScoreType.m_pszLevelBlockName = pszLevelBlockName; + + m_mapKillEaterScoreTypes.Insert( unIndex, ScoreType ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitStringTables( KeyValues *pKVStringTables, CUtlVector<CUtlString> *pVecErrors ) +{ + m_dictStringTable.PurgeAndDeleteElements(); + + // initialize the rewards sections + if ( NULL != pKVStringTables ) + { + FOR_EACH_SUBKEY( pKVStringTables, pKVTable ) + { + SCHEMA_INIT_CHECK( !m_dictStringTable.IsValidIndex( m_dictStringTable.Find( pKVTable->GetName() ) ), + "Duplicate string table name '%s'.", pKVTable->GetName() ); + + SchemaStringTableDict_t::IndexType_t i = m_dictStringTable.Insert( pKVTable->GetName(), new CUtlVector< schema_string_table_entry_t > ); + FOR_EACH_SUBKEY( pKVTable, pKVEntry ) + { + schema_string_table_entry_t s = { atoi( pKVEntry->GetName() ), pKVEntry->GetString() }; + m_dictStringTable[i]->AddToTail( s ); + } + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitCommunityMarketRemaps( KeyValues *pKVCommunityMarketRemaps, CUtlVector<CUtlString> *pVecErrors ) +{ + m_mapCommunityMarketDefinitionIndexRemap.Purge(); + + if ( NULL != pKVCommunityMarketRemaps ) + { + FOR_EACH_SUBKEY( pKVCommunityMarketRemaps, pKVRemapBase ) + { + const char *pszBaseDefName = pKVRemapBase->GetName(); + const CEconItemDefinition *pBaseItemDef = GetItemSchema()->GetItemDefinitionByName( pszBaseDefName ); + SCHEMA_INIT_CHECK( pBaseItemDef != NULL, "Unknown Market remap base definition '%s'.", pszBaseDefName ); + + FOR_EACH_SUBKEY( pKVRemapBase, pKVRemap ) + { + const char *pszDefName = pKVRemap->GetName(); + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pszDefName ); + SCHEMA_INIT_CHECK( pItemDef != NULL, "Unknown Market remap definition '%s' (under '%s').", pszDefName, pszBaseDefName ); + SCHEMA_INIT_CHECK( m_mapCommunityMarketDefinitionIndexRemap.Find( pItemDef->GetDefinitionIndex() ) == m_mapCommunityMarketDefinitionIndexRemap.InvalidIndex(), "Duplicate Market remap definition '%s'.\n", pszDefName ); + + m_mapCommunityMarketDefinitionIndexRemap.Insert( pItemDef->GetDefinitionIndex(), pBaseItemDef->GetDefinitionIndex() ); + } + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +item_definition_index_t CEconItemSchema::GetCommunityMarketRemappedDefinitionIndex( item_definition_index_t unSearchItemDef ) const +{ + CommunityMarketDefinitionRemapMap_t::IndexType_t index = m_mapCommunityMarketDefinitionIndexRemap.Find( unSearchItemDef ); + if ( index == m_mapCommunityMarketDefinitionIndexRemap.InvalidIndex() ) + return unSearchItemDef; + + return m_mapCommunityMarketDefinitionIndexRemap[index]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const ISchemaAttributeType *CEconItemSchema::GetAttributeType( const char *pszAttrTypeName ) const +{ + FOR_EACH_VEC( m_vecAttributeTypes, i ) + { + if ( m_vecAttributeTypes[i].m_sName == pszAttrTypeName ) + return m_vecAttributeTypes[i].m_pAttrType; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// CItemLevelingDefinition Accessor +//----------------------------------------------------------------------------- +const CItemLevelingDefinition *CEconItemSchema::GetItemLevelForScore( const char *pszLevelBlockName, uint32 unScore ) const +{ + const CUtlVector<CItemLevelingDefinition> *pLevelingData = GetItemLevelingData( pszLevelBlockName ); + if ( !pLevelingData ) + return NULL; + + if ( pLevelingData->Count() == 0 ) + return NULL; + + FOR_EACH_VEC( (*pLevelingData), i ) + { + if ( unScore < (*pLevelingData)[i].GetRequiredScore() ) + return &(*pLevelingData)[i]; + } + + return &(*pLevelingData).Tail(); +} + +//----------------------------------------------------------------------------- +// Kill eater score type accessor +//----------------------------------------------------------------------------- +const kill_eater_score_type_t *CEconItemSchema::FindKillEaterScoreType( uint32 unScoreType ) const +{ + KillEaterScoreMap_t::IndexType_t i = m_mapKillEaterScoreTypes.Find( unScoreType ); + if ( i == KillEaterScoreMap_t::InvalidIndex() ) + return NULL; + + return &m_mapKillEaterScoreTypes[i]; +} + +//----------------------------------------------------------------------------- +// Kill eater score type accessor +//----------------------------------------------------------------------------- +const char *CEconItemSchema::GetKillEaterScoreTypeLocString( uint32 unScoreType ) const +{ + const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); + + return pScoreType + ? pScoreType->m_pszTypeString + : NULL; +} + +//----------------------------------------------------------------------------- +// Kill eater score type accessor +//----------------------------------------------------------------------------- +const char *CEconItemSchema::GetKillEaterScoreTypeLevelingDataName( uint32 unScoreType ) const +{ + const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); + + return pScoreType + ? pScoreType->m_pszLevelBlockName + : NULL; +} + +#if defined(STAGING_ONLY) && ( defined(TF_CLIENT_DLL) || defined(TF_DLL) ) + ConVar tf_allow_strange_bot_kills( "tf_allow_strange_bot_kills", "0", FCVAR_REPLICATED ); +#endif +//----------------------------------------------------------------------------- +// Kill eater score type accessor +//----------------------------------------------------------------------------- +bool CEconItemSchema::GetKillEaterScoreTypeAllowsBotVictims( uint32 unScoreType ) const +{ +#if defined(STAGING_ONLY) && ( defined(TF_CLIENT_DLL) || defined(TF_DLL) ) + if ( tf_allow_strange_bot_kills.GetBool() ) + { + return true; + } +#endif + + const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); + + return pScoreType + ? pScoreType->m_bAllowBotVictims + : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +econ_tag_handle_t CEconItemSchema::GetHandleForTag( const char *pszTagName ) +{ + EconTagDict_t::IndexType_t i = m_dictTags.Find( pszTagName ); + if ( m_dictTags.IsValidIndex( i ) ) + return i; + + return m_dictTags.Insert( pszTagName ); +} + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Kill eater score type accessor +//----------------------------------------------------------------------------- +bool CEconItemSchema::GetKillEaterScoreTypeGCOnlyUpdate( uint32 unScoreType ) const +{ + const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); + + return pScoreType + ? pScoreType->m_bGCUpdateOnly + : true; // default to being more restrictive +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::GetKillEaterScoreTypeAllowsIncrementValues( uint32 unScoreType ) const +{ + const kill_eater_score_type_t *pScoreType = FindKillEaterScoreType( unScoreType ); + + return pScoreType + ? pScoreType->m_AllowIncrementValues + : true; // default to being more restrictive +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconItemSchema::periodic_score_t& CEconItemSchema::GetPeriodicScoreInfo( int iPeriodicScoreIndex ) const +{ + Assert( GetPeriodicScoreTypeList().IsValidIndex( iPeriodicScoreIndex ) ); + + return GetPeriodicScoreTypeList()[ iPeriodicScoreIndex ]; +} +#endif + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: Clones the specified item definition, and returns the new item def. +//----------------------------------------------------------------------------- +void CEconItemSchema::ItemTesting_CreateTestDefinition( int iCloneFromItemDef, int iNewDef, KeyValues *pNewKV ) +{ + int nMapIndex = m_mapItems.Find( iNewDef ); + if ( !m_mapItems.IsValidIndex( nMapIndex ) ) + { + nMapIndex = m_mapItems.Insert( iNewDef, CreateEconItemDefinition() ); + m_mapItemsSorted.Insert( iNewDef, m_mapItems[nMapIndex] ); + } + + // Find & copy the clone item def's data in + CEconItemDefinition *pCloneDef = GetItemDefinition( iCloneFromItemDef ); + if ( !pCloneDef ) + return; + m_mapItems[nMapIndex]->CopyPolymorphic( pCloneDef ); + + // Then stomp it with the KV test contents + m_mapItems[nMapIndex]->BInitFromTestItemKVs( iNewDef, pNewKV ); +} + +//----------------------------------------------------------------------------- +// Purpose: Discards the specified item definition +//----------------------------------------------------------------------------- +void CEconItemSchema::ItemTesting_DiscardTestDefinition( int iDef ) +{ + m_mapItems.Remove( iDef ); + m_mapItemsSorted.Remove( iDef ); +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes the armory data section of the schema +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitArmoryData( KeyValues *pKVArmoryData, CUtlVector<CUtlString> *pVecErrors ) +{ + m_dictArmoryItemDataStrings.RemoveAll(); + m_dictArmoryAttributeDataStrings.RemoveAll(); + if ( NULL != pKVArmoryData ) + { + KeyValues *pKVItemTypes = pKVArmoryData->FindKey( "armory_item_types" ); + if ( pKVItemTypes ) + { + FOR_EACH_SUBKEY( pKVItemTypes, pKVEntry ) + { + const char *pszDataKey = pKVEntry->GetName(); + const CUtlConstString sLocString( pKVEntry->GetString() ); + m_dictArmoryItemTypesDataStrings.Insert( pszDataKey, sLocString ); + } + } + + pKVItemTypes = pKVArmoryData->FindKey( "armory_item_classes" ); + if ( pKVItemTypes ) + { + FOR_EACH_SUBKEY( pKVItemTypes, pKVEntry ) + { + const char *pszDataKey = pKVEntry->GetName(); + const CUtlConstString sLocString( pKVEntry->GetString() ); + m_dictArmoryItemClassesDataStrings.Insert( pszDataKey, sLocString ); + } + } + + KeyValues *pKVAttribs = pKVArmoryData->FindKey( "armory_attributes" ); + if ( pKVAttribs ) + { + FOR_EACH_SUBKEY( pKVAttribs, pKVEntry ) + { + const char *pszDataKey = pKVEntry->GetName(); + const CUtlConstString sLocString( pKVEntry->GetString() ); + m_dictArmoryAttributeDataStrings.Insert( pszDataKey, sLocString ); + } + } + + KeyValues *pKVItems = pKVArmoryData->FindKey( "armory_items" ); + if ( pKVItems ) + { + FOR_EACH_SUBKEY( pKVItems, pKVEntry ) + { + const char *pszDataKey = pKVEntry->GetName(); + const CUtlConstString sLocString( pKVEntry->GetString() ); + m_dictArmoryItemDataStrings.Insert( pszDataKey, sLocString ); + } + } + } + return SCHEMA_INIT_SUCCESS(); +} +#endif + + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: Returns the item awarded for an achievement. +// Input: pchAchievementName - The achievement that was awarded. +// Output: The achievement struct for this reward. +//----------------------------------------------------------------------------- +const AchievementAward_t * CEconItemSchema::GetAchievementReward( const char *pchAchievementName, AppId_t unAppID ) const +{ + int nRewardIndex = m_dictAchievementRewards.Find( ComputeAchievementName( unAppID, pchAchievementName ) ); + + if( m_dictAchievementRewards.IsValidIndex( nRewardIndex ) ) + return m_dictAchievementRewards[ nRewardIndex ]; + else + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the achievement award that matches the provided data or NULL +// if there is no such award. +// Input: unData - The data field that would be stored in ItemAudit +//----------------------------------------------------------------------------- +const AchievementAward_t *CEconItemSchema::GetAchievementRewardByData( uint32 unData ) const +{ + uint nIndex = m_mapAchievementRewardsByData.Find( unData ); + if( m_mapAchievementRewardsByData.IsValidIndex( nIndex ) ) + { + return m_mapAchievementRewardsByData[nIndex]; + } + else + { + return NULL; + } +} + + +#endif // GC_DLL + + +//----------------------------------------------------------------------------- +// Purpose: Returns the achievement award that matches the provided defindex or NULL +// if there is no such award. +// Input: unData - The data field that would be stored in ItemAudit +//----------------------------------------------------------------------------- +const AchievementAward_t *CEconItemSchema::GetAchievementRewardByDefIndex( uint16 usDefIndex ) const +{ + FOR_EACH_MAP_FAST( m_mapAchievementRewardsByData, nIndex ) + { + if( m_mapAchievementRewardsByData[nIndex]->m_vecDefIndex.HasElement( usDefIndex ) ) + return m_mapAchievementRewardsByData[nIndex]; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a rarity value for a name. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BGetItemRarityFromName( const char *pchName, uint8 *nRarity ) const +{ + if ( 0 == Q_stricmp( "any", pchName ) ) + { + *nRarity = k_unItemRarity_Any; + return true; + } + + FOR_EACH_MAP_FAST( m_mapRarities, i ) + { + if ( 0 == Q_stricmp( m_mapRarities[i].GetName(), pchName ) ) + { + *nRarity = m_mapRarities[i].GetDBValue(); + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets a quality value for a name. +// Input: pchName - The name to translate. +// nQuality - (out)The quality number for this name, if found. +// Output: True if the string matched a quality for this schema, false otherwise. +//----------------------------------------------------------------------------- +bool CEconItemSchema::BGetItemQualityFromName( const char *pchName, uint8 *nQuality ) const +{ + if ( 0 == Q_stricmp( "any", pchName ) ) + { + *nQuality = k_unItemQuality_Any; + return true; + } + + FOR_EACH_MAP_FAST( m_mapQualities, i ) + { + if ( 0 == Q_stricmp( m_mapQualities[i].GetName(), pchName ) ) + { + *nQuality = m_mapQualities[i].GetDBValue(); + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets a quality definition for an index +// Input: nQuality - The quality to get. +// Output: A pointer to the desired definition, or NULL if it is not found. +//----------------------------------------------------------------------------- +const CEconItemQualityDefinition *CEconItemSchema::GetQualityDefinition( int nQuality ) const +{ + int iIndex = m_mapQualities.Find( nQuality ); + if ( m_mapQualities.IsValidIndex( iIndex ) ) + return &m_mapQualities[iIndex]; + return NULL; +} + +const CEconItemQualityDefinition *CEconItemSchema::GetQualityDefinitionByName( const char *pszDefName ) const +{ + FOR_EACH_MAP_FAST( m_mapQualities, i ) + { + if ( V_stricmp( pszDefName, m_mapQualities[i].GetName()) == 0 ) + return &m_mapQualities[i]; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// ItemRarity +//----------------------------------------------------------------------------- +const CEconItemRarityDefinition *CEconItemSchema::GetRarityDefinitionByMapIndex( int nRarityIndex ) const +{ + if ( m_mapRarities.IsValidIndex( nRarityIndex ) ) + return &m_mapRarities[nRarityIndex]; + + return NULL; +} +//----------------------------------------------------------------------------- +const CEconItemRarityDefinition *CEconItemSchema::GetRarityDefinition( int nRarity ) const +{ + int iIndex = m_mapRarities.Find( nRarity ); + if ( m_mapRarities.IsValidIndex( iIndex ) ) + return &m_mapRarities[iIndex]; + return NULL; +} +//----------------------------------------------------------------------------- +const CEconItemRarityDefinition *CEconItemSchema::GetRarityDefinitionByName( const char *pszDefName ) const +{ + FOR_EACH_MAP_FAST( m_mapRarities, i ) + { + if ( !strcmp( pszDefName, m_mapRarities[i].GetName() ) ) + return &m_mapRarities[i]; + } + return NULL; +} +//----------------------------------------------------------------------------- +const char* CEconItemSchema::GetRarityName( uint8 iRarity ) +{ + const CEconItemRarityDefinition* pItemRarity = GetRarityDefinition( iRarity ); + if ( !pItemRarity ) + return NULL; + else + return pItemRarity->GetName(); +} +//----------------------------------------------------------------------------- +const char* CEconItemSchema::GetRarityLocKey( uint8 iRarity ) +{ + const CEconItemRarityDefinition* pItemRarity = GetRarityDefinition( iRarity ); + if ( !pItemRarity ) + return NULL; + else + return pItemRarity->GetLocKey(); +} +//----------------------------------------------------------------------------- +const char* CEconItemSchema::GetRarityColor( uint8 iRarity ) +{ + const CEconItemRarityDefinition* pItemRarity = GetRarityDefinition( iRarity ); + if ( !pItemRarity ) + return NULL; + else + return GetColorNameForAttribColor( pItemRarity->GetAttribColor() ); +} +//----------------------------------------------------------------------------- +int CEconItemSchema::GetRarityIndex( const char* pszRarity ) +{ + const CEconItemRarityDefinition* pRarity = GetRarityDefinitionByName( pszRarity ); + if ( pRarity ) + return pRarity->GetDBValue(); + else + return 0; +} + +//----------------------------------------------------------------------------- +bool CEconItemSchema::BGetItemSeries( const char* pchName, uint8 *nItemSeries ) const +{ + FOR_EACH_MAP_FAST( m_mapItemSeries, i ) + { + if ( 0 == Q_stricmp( m_mapItemSeries[i].GetName(), pchName ) ) + { + *nItemSeries = m_mapItemSeries[i].GetDBValue(); + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +const CEconItemSeriesDefinition *CEconItemSchema::GetItemSeriesDefinition( int iSeries ) const +{ + int iIndex = m_mapItemSeries.Find( iSeries ); + if ( m_mapItemSeries.IsValidIndex( iIndex ) ) + return &m_mapItemSeries[iIndex]; + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets an item definition for the specified definition index +// Input: iItemIndex - The index of the desired definition. +// Output: A pointer to the desired definition, or NULL if it is not found. +//----------------------------------------------------------------------------- +CEconItemDefinition *CEconItemSchema::GetItemDefinition( int iItemIndex ) +{ +#if defined(CLIENT_DLL) || defined(GAME_DLL) +#if !defined(CSTRIKE_DLL) + AssertMsg( GetDefaultItemDefinition(), "No default item definition set up for item schema." ); +#endif // CSTRIKE_DLL +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + + int iIndex = m_mapItems.Find( iItemIndex ); + if ( m_mapItems.IsValidIndex( iIndex ) ) + return m_mapItems[iIndex]; + +#if defined( GC_DLL ) || defined( EXTERNALTESTS_DLL ) + return NULL; +#else // !GC_DLL + if ( GetDefaultItemDefinition() ) + return GetDefaultItemDefinition(); + +#if !defined(CSTRIKE_DLL) + // We shouldn't ever get down here, but all the same returning a valid pointer is very slightly + // a better plan than returning an invalid pointer to code that won't check to see if it's valid. + static CEconItemDefinition *s_pEmptyDefinition = CreateEconItemDefinition(); + return s_pEmptyDefinition; +#else + return NULL; +#endif // CSTRIKE_DLL + +#endif // GC_DLL +} +const CEconItemDefinition *CEconItemSchema::GetItemDefinition( int iItemIndex ) const +{ + return const_cast<CEconItemSchema *>(this)->GetItemDefinition( iItemIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets an item definition that has a name matching the specified name. +// Input: pszDefName - The name of the desired definition. +// Output: A pointer to the desired definition, or NULL if it is not found. +//----------------------------------------------------------------------------- +CEconItemDefinition *CEconItemSchema::GetItemDefinitionByName( const char *pszDefName ) +{ + // This shouldn't happen, but let's not crash if it ever does. + Assert( pszDefName != NULL ); + if ( pszDefName == NULL ) + return NULL; + + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + if ( V_stricmp( pszDefName, m_mapItems[i]->GetDefinitionName()) == 0 ) + return m_mapItems[i]; + } + return NULL; +} + +const CEconItemDefinition *CEconItemSchema::GetItemDefinitionByName( const char *pszDefName ) const +{ + return const_cast<CEconItemSchema *>(this)->GetItemDefinitionByName( pszDefName ); +} + + +#ifdef GC_DLL +random_attrib_t *CEconItemSchema::GetRandomAttributeTemplateByName( const char *pszAttrTemplateName ) const +{ + int index = m_dictRandomAttributeTemplates.Find( pszAttrTemplateName ); + if ( index != m_dictRandomAttributeTemplates.InvalidIndex() ) + { + return m_dictRandomAttributeTemplates[index]; + } + + return NULL; +} +#endif // GC_DLL + + +//----------------------------------------------------------------------------- +// Purpose: Gets an attribute definition for an index +// Input: iAttribIndex - The index of the desired definition. +// Output: A pointer to the desired definition, or NULL if it is not found. +//----------------------------------------------------------------------------- +CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinition( int iAttribIndex ) +{ + int iIndex = m_mapAttributes.Find( iAttribIndex ); + if ( m_mapAttributes.IsValidIndex( iIndex ) ) + return &m_mapAttributes[iIndex]; + return NULL; +} +const CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinition( int iAttribIndex ) const +{ + return const_cast<CEconItemSchema *>(this)->GetAttributeDefinition( iAttribIndex ); +} + +CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinitionByName( const char *pszDefName ) +{ + Assert( pszDefName ); + if ( !pszDefName ) + return NULL; + + VPROF_BUDGET( "CEconItemSchema::GetAttributeDefinitionByName", VPROF_BUDGETGROUP_STEAM ); + FOR_EACH_MAP_FAST( m_mapAttributes, i ) + { + Assert( m_mapAttributes[i].GetDefinitionName() ); + if ( !m_mapAttributes[i].GetDefinitionName() ) + continue; + + if ( V_stricmp( pszDefName, m_mapAttributes[i].GetDefinitionName() ) == 0 ) + return &m_mapAttributes[i]; + } + return NULL; +} +const CEconItemAttributeDefinition *CEconItemSchema::GetAttributeDefinitionByName( const char *pszDefName ) const +{ + return const_cast<CEconItemSchema *>(this)->GetAttributeDefinitionByName( pszDefName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a recipe definition for an index +// Input: iRecipeIndex - The index of the desired definition. +// Output: A pointer to the desired definition, or NULL if it is not found. +//----------------------------------------------------------------------------- +CEconCraftingRecipeDefinition *CEconItemSchema::GetRecipeDefinition( int iRecipeIndex ) +{ + int iIndex = m_mapRecipes.Find( iRecipeIndex ); + if ( m_mapRecipes.IsValidIndex( iIndex ) ) + return m_mapRecipes[iIndex]; + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconColorDefinition *CEconItemSchema::GetColorDefinitionByName( const char *pszDefName ) +{ + FOR_EACH_VEC( m_vecColorDefs, i ) + { + if ( !Q_stricmp( m_vecColorDefs[i]->GetName(), pszDefName ) ) + return m_vecColorDefs[i]; + } + return NULL; +} +const CEconColorDefinition *CEconItemSchema::GetColorDefinitionByName( const char *pszDefName ) const +{ + return const_cast<CEconItemSchema *>(this)->GetColorDefinitionByName( pszDefName ); +} + + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemSchema::GetSteamPackageLocalizationToken( uint32 unPackageId ) const +{ + SteamPackageLocalizationTokenMap_t::IndexType_t i = m_mapSteamPackageLocalizationTokens.Find( unPackageId ); + if ( m_mapSteamPackageLocalizationTokens.IsValidIndex( i ) ) + return m_mapSteamPackageLocalizationTokens[i]; + return NULL; +} +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Return the attribute specified attachedparticlesystem_t* associated with the given id. +//----------------------------------------------------------------------------- +attachedparticlesystem_t* CEconItemSchema::GetAttributeControlledParticleSystem( int id ) +{ + int iIndex = m_mapAttributeControlledParticleSystems.Find( id ); + if ( m_mapAttributeControlledParticleSystems.IsValidIndex( iIndex ) ) + return &m_mapAttributeControlledParticleSystems[iIndex]; + return NULL; +} + +attachedparticlesystem_t* CEconItemSchema::FindAttributeControlledParticleSystem( const char *pchSystemName ) +{ + FOR_EACH_MAP_FAST( m_mapAttributeControlledParticleSystems, nSystem ) + { + if( !Q_stricmp( m_mapAttributeControlledParticleSystems[nSystem].pszSystemName, pchSystemName ) ) + return &m_mapAttributeControlledParticleSystems[nSystem]; + } + return NULL; +} + + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +bool CEconItemSchema::SetupPreviewItemDefinition( KeyValues *pKV ) +{ + int nMapIndex = m_mapItems.Find( PREVIEW_ITEM_DEFINITION_INDEX ); + if ( !m_mapItems.IsValidIndex( nMapIndex ) ) + { + nMapIndex = m_mapItems.Insert( PREVIEW_ITEM_DEFINITION_INDEX, CreateEconItemDefinition() ); + } + + CEconItemDefinition *pItemDef = m_mapItems[ nMapIndex ]; + return pItemDef->BInitFromKV( pKV ); +} +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: Returns all the foreign item imports for an app ID +//----------------------------------------------------------------------------- +const CEconItemDefinition *CEconItemSchema::GetAppItemImport( AppId_t unAppID, uint16 usDefIndex ) const +{ + int i = m_mapForeignImports.Find( unAppID ); + if( m_mapForeignImports.IsValidIndex( i ) ) + return m_mapForeignImports[i]->FindMapping( usDefIndex ); + else + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns all the foreign item imports for an app ID +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitForeignImports( CUtlVector<CUtlString> *pVecErrors ) +{ + FOR_EACH_MAP_FAST( m_mapItems, nItem ) + { + CEconItemDefinition *pDefn = m_mapItems[nItem]; + + KeyValues *pkvImport = pDefn->GetDefinitionKey( "import_from" ); + if( !pkvImport ) + continue; + + FOR_EACH_VALUE( pkvImport, pkvApp ) + { + CForeignAppImports *pAppImports = FindOrAddAppImports( Q_atoi( pkvApp->GetName() ) ); + pAppImports->AddMapping( pkvApp->GetInt(), pDefn ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns all the foreign item imports for an app ID +//----------------------------------------------------------------------------- +CForeignAppImports *CEconItemSchema::FindOrAddAppImports( AppId_t unAppID ) +{ + int i = m_mapForeignImports.Find( unAppID ); + if( m_mapForeignImports.IsValidIndex( i ) ) + return m_mapForeignImports[i]; + else + { + m_vecForeignApps.AddToTail( unAppID ); + CForeignAppImports *pApp = new CForeignAppImports(); + m_mapForeignImports.Insert( unAppID, pApp ); + return pApp; + } +} +#endif // GC_DLL + +bool CEconItemSchema::BCanStrangeFilterApplyToStrangeSlotInItem( uint32 /*strange_event_restriction_t*/ unRestrictionType, uint32 unRestrictionValue, const IEconItemInterface *pItem, int iStrangeSlot, uint32 *out_pOptionalScoreType ) const +{ + Assert( unRestrictionType != kStrangeEventRestriction_None ); + + // Do we have a type for this slot? If not, move on, with the exception: all user-custom scores + // we expect to have types, but certain weapons may not specify a base type and just use + // kills implicitly. + uint32 unStrangeScoreTypeBits = kKillEaterEvent_PlayerKill; + if ( !pItem->FindAttribute( GetKillEaterAttr_Type( iStrangeSlot ), &unStrangeScoreTypeBits ) && iStrangeSlot != 0 ) + return false; + + // Do we have a restriction already in this slot? If so, move on. + if ( pItem->FindAttribute( GetKillEaterAttr_Restriction( iStrangeSlot ) ) ) + return false; + + // We've found an open slot. Make sure that adding our restriction to this slot + // won't result in a duplicate score-type/restriction-type entry. + for ( int j = 0; j < GetKillEaterAttrCount(); j++ ) + { + // Don't compare against ourself. + if ( iStrangeSlot == j ) + continue; + + // Ignore this entry if we don't have a score type or if the score type differs from + // our search criteria above. + uint32 unAltStrangeScoreType; + if ( !pItem->FindAttribute( GetKillEaterAttr_Type( j ), &unAltStrangeScoreType ) || + unAltStrangeScoreType != unStrangeScoreTypeBits ) + { + continue; + } + + // This entry does have the same type, so tag us as a duplicate if we also have the same + // restriction that we're trying to apply. + uint32 unAltRestrictionType; + uint32 unAltRestrictionValue; + if ( pItem->FindAttribute( GetKillEaterAttr_Restriction( j ), &unAltRestrictionType ) && + unAltRestrictionType == unRestrictionType && + pItem->FindAttribute( GetKillEaterAttr_RestrictionValue( j ), &unAltRestrictionValue ) && + unAltRestrictionValue == unRestrictionValue ) + { + return false; + } + } + + if ( out_pOptionalScoreType ) + { + *out_pOptionalScoreType = *(float *)&unStrangeScoreTypeBits; + } + + // Everything seems alright. + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +#ifdef DBGFLAG_VALIDATE +void CEconItemSchema::Validate( CValidator &validator, const char *pchName ) +{ + VALIDATE_SCOPE(); + ValidateObj( m_mapQualities ); + + FOR_EACH_MAP_FAST( m_mapQualities, i ) + { + ValidateObj( m_mapQualities[i] ); + } + + ValidateObj( m_mapItems ); + + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + ValidateObj( m_mapItems[i] ); + } + + ValidateObj( m_mapUpgradeableBaseItems ); + + FOR_EACH_MAP_FAST( m_mapUpgradeableBaseItems, i ) + { + ValidateObj( m_mapUpgradeableBaseItems[i] ); + } + + ValidateObj( m_mapAttributes ); + + FOR_EACH_MAP_FAST( m_mapAttributes, i ) + { + ValidateObj( m_mapAttributes[i] ); + } + + ValidateObj( m_mapRecipes ); + + FOR_EACH_MAP_FAST( m_mapRecipes, i ) + { + ValidateObj( m_mapRecipes[i] ); + } + + FOR_EACH_VEC( m_vecTimedRewards, i ) + { + ValidateObj( m_vecTimedRewards[i] ); + } + ValidateObj( m_vecTimedRewards ); + +} +#endif // DBGFLAG_VALIDATE + +#ifdef GC_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSchema::BInitExperiements( KeyValues *pKVExperiments, CUtlVector<CUtlString> *pVecErrors ) +{ + m_vecExperiments.RemoveAll(); + + if ( NULL != pKVExperiments ) + { + FOR_EACH_TRUE_SUBKEY( pKVExperiments, pKVEntry ) + { + const char *listName = pKVEntry->GetName(); + + SCHEMA_INIT_CHECK( listName != NULL, "All experiments must have titles."); + + int idx = m_vecExperiments.AddToTail(); + SCHEMA_INIT_SUBSTEP( m_vecExperiments[idx].BInitFromKV( pKVEntry, pVecErrors ) ); + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// CExperimentDefinition +//----------------------------------------------------------------------------- +CExperimentDefinition::CExperimentDefinition( void ) + : m_bEnabled( false ) + , m_unExperimentID( 0 ) + , m_unNumParticipants( 0 ) + , m_unMaxParticipants( 0 ) + , m_pKeyValues( NULL ) +{ +} + +CExperimentDefinition::~CExperimentDefinition( void ) +{ + if ( m_pKeyValues ) + { + m_pKeyValues->deleteThis(); + } +} + +bool CExperimentDefinition::BInitFromKV( KeyValues *pKVExperiment, CUtlVector<CUtlString> *pVecErrors ) +{ + m_pKeyValues = pKVExperiment->MakeCopy(); + + m_unExperimentID = Q_atoi( m_pKeyValues->GetName() ); + m_bEnabled = m_pKeyValues->GetBool( "enabled" ); + m_unNumParticipants = 0; + m_unMaxParticipants = 0; + + KeyValues *pKVGroups = m_pKeyValues->FindKey( "groups" ); + if ( pKVGroups ) + { + FOR_EACH_TRUE_SUBKEY( pKVGroups, pKVEntry ) + { + int idx = m_vecGroups.AddToTail(); + experiment_group_t &group = m_vecGroups[idx]; + group.m_pKeyValues = pKVEntry; + group.m_pName = pKVEntry->GetName(); + group.m_unNumParticipants = 0; + group.m_unMaxParticipants = pKVEntry->GetInt( "num_participants", 0 ); + m_unMaxParticipants += group.m_unMaxParticipants; + } + } + + return SCHEMA_INIT_SUCCESS(); +} + +bool CExperimentDefinition::ChooseGroup( uint32 &unGroup ) +{ + CUtlVector< uint32 > vecGroupIndices; + FOR_EACH_VEC( m_vecGroups, i ) + { + experiment_group_t &group = m_vecGroups[i]; + if ( group.m_unNumParticipants < group.m_unMaxParticipants ) + { + vecGroupIndices.AddToTail( i ); + } + } + if ( vecGroupIndices.Count() == 0 ) + { + return false; + } + + uint32 idx = vecGroupIndices[ RandomInt( 0, vecGroupIndices.Count() - 1 ) ]; + experiment_group_t &group = m_vecGroups[ idx ]; + ++group.m_unNumParticipants; + unGroup = idx; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Give the chance after the schema has been initialized to sanity-check +// individual parts or interactions before moving forward. +//----------------------------------------------------------------------------- +#ifdef TF_GC_DLL +static bool BTestToolApplicability( CUtlVector<CUtlString> *pVecErrors ) +{ + static CSchemaItemDefHandle pItem_TeamPaint ( "Paint Can Team Color" ), // tools + pItem_Paint ( "Paint Can 14" ), + pItem_DescriptionTag ( "Description Tag" ), + pItem_NameTag ( "Name Tag" ), + pItem_Key ( "Decoder Ring" ), + pItem_SummerKey ( "Summer Key" ), + pItem_GiftWrap ( "Gift Wrap" ), + pItem_CustomTextureTool ( "Customize Texture Tool" ), + pItem_SupplyCrateGeneric ( "Supply Crate 2" ), // tool targets + pItem_SummerCrate ( "Summer Crate" ), + pItem_PaintableItem ( "Summer Hat" ), + //pItem_TeamPaintableItem ( "" ), + pItem_UnpaintableItem ( "Big Steel Jaw of Summer Fun" ), + //pItem_UnnameableWeapon ( "" ), + pItem_NameableWeapon ( "The Axtinguisher" ), + pItem_GiftWrappableItem ( "Supply Crate 3" ), + pItem_NonGiftWrappableItem ( "Wrapped Gift" ), + pItem_StampableObject ( "The Conscientious Objector" ), + pItem_NonstampableObject ( "Spiral Sallet" ); + + struct ToolValidityTest_t + { + const CEconItemDefinition *m_pTool; + const CEconItemDefinition *m_pTarget; + bool m_bExpectedValidity; + }; + + ToolValidityTest_t definitionTests[] = + { + { pItem_TeamPaint, pItem_TeamPaint, false }, + { pItem_TeamPaint, pItem_PaintableItem, true }, + //{ pItem_TeamPaint, pItem_TeamPaintableItem, true }, + { pItem_TeamPaint, pItem_UnpaintableItem, false }, + { pItem_Paint, pItem_PaintableItem, true }, + //{ pItem_Paint, pItem_TeamPaintableItem, false }, + { pItem_Paint, pItem_UnpaintableItem, false }, + { pItem_Paint, pItem_SupplyCrateGeneric, false }, + { pItem_Key, pItem_SupplyCrateGeneric, true }, + { pItem_Key, pItem_SummerCrate, false }, + { pItem_SummerKey, pItem_SupplyCrateGeneric, true }, // summer keys in staging are now regular keys and should... + { pItem_SummerKey, pItem_SummerCrate, false }, // ...be able to open regular crates + { pItem_NameTag, pItem_NameableWeapon, true }, + //{ pItem_NameTag, pItem_UnnameableWeapon, false }, + { pItem_DescriptionTag, pItem_NameableWeapon, true }, + //{ pItem_DescriptionTag, pItem_UnnameableWeapon, false }, + { pItem_CustomTextureTool, pItem_StampableObject, true }, + { pItem_CustomTextureTool, pItem_NonstampableObject, false }, + }; + + bool bAllSuccess = true; + for ( int i = 0; i < ARRAYSIZE( definitionTests ); i++ ) + { + const GameItemDefinition_t *pToolDef = dynamic_cast<const GameItemDefinition_t *>( definitionTests[i].m_pTool ), + *pTargetDef = dynamic_cast<const GameItemDefinition_t *>( definitionTests[i].m_pTarget ); + + if ( !pToolDef ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Tool is NULL.", i ).Access() ); + continue; + } + + if ( !pTargetDef ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Target is NULL.", i ).Access() ); + continue; + } + + if ( CEconSharedToolSupport::ToolCanApplyToDefinition( pToolDef, pTargetDef ) != definitionTests[i].m_bExpectedValidity ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: %s %s have been able to apply to %s.", + i, + pToolDef->GetDefinitionName(), + definitionTests[i].m_bExpectedValidity ? "should" : "shouldn't", + pTargetDef->GetDefinitionName() ).Access() ); + } + } + + // These tests require actual instances of the item--not just the definitions. + ToolValidityTest_t interfaceTests[] = + { + { pItem_GiftWrap, pItem_GiftWrappableItem, true }, + { pItem_GiftWrap, pItem_NonGiftWrappableItem, false }, + }; + + // Skip if we already have failures. + if ( bAllSuccess ) + { + for ( int i = 0; i < ARRAYSIZE( interfaceTests ); i++ ) + { + const GameItemDefinition_t *pToolDef = dynamic_cast< const GameItemDefinition_t * >( interfaceTests[ i ].m_pTool ), + *pTargetDef = dynamic_cast< const GameItemDefinition_t * >( interfaceTests[ i ].m_pTarget ); + + if ( !pToolDef ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Tool is NULL.", i ).Access() ); + continue; + } + + if ( !pTargetDef ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: Target is NULL.", i ).Access() ); + continue; + } + + CEconItem *pTool = GEconManager()->GetItemFactory().CreateSpecificItem( ( const CEconGameAccount *) NULL, pToolDef->GetDefinitionIndex() ); + CEconItem *pTarget = GEconManager()->GetItemFactory().CreateSpecificItem( ( const CEconGameAccount *) NULL, pTargetDef->GetDefinitionIndex() ); + + if ( CEconSharedToolSupport::ToolCanApplyTo( pTool, pTarget ) != interfaceTests[ i ].m_bExpectedValidity ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "Tool validity test %i failed: %s %s have been able to apply to %s.", + i, + pToolDef->GetDefinitionName(), + interfaceTests[ i ].m_bExpectedValidity ? "should" : "shouldn't", + pTargetDef->GetDefinitionName() ).Access() ); + } + + delete pTool; + delete pTarget; + } + } + + + + + return bAllSuccess; +} +#endif // TF_GC_DLL +#endif // defined(GC_DLL) + +bool CEconItemSchema::BPostSchemaInit( CUtlVector<CUtlString> *pVecErrors ) const +{ + bool bAllSuccess = true; + + // Make sure all of our tools are valid. We have to do this after the whole schema is initialized so + // that we don't run into circular reference problems with items referencing loot lists that reference + // items, etc. + FOR_EACH_MAP_FAST( m_mapItems, i ) + { + const CEconItemDefinition *pItemDef = m_mapItems[i]; + const IEconTool *pTool = pItemDef->GetEconTool(); + + if ( pTool && !const_cast<IEconTool *>( pTool )->BFinishInitialization() ) + { +#ifdef GC_DLL + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "BPostSchemaInit(): tool '%s' is invalid.", pItemDef->GetDefinitionName() ).Get() ); +#endif // GC_DLL + } +#if TF_GC_DLL + else + { + // all cosmetic items should have these two attributes from the + // cosmetic_killeater_attribs prefab in case we ever try to drop them as Strange + static CSchemaAttributeDefHandle pAttribDef_KillEaterScoreType( "kill eater score type" ); + static CSchemaAttributeDefHandle pAttribDef_KillEaterKillType( "kill eater kill type" ); + + const CTFItemDefinition *pTFItemDef = assert_cast< const CTFItemDefinition* >( pItemDef ); + if ( pTFItemDef ) + { + int nSlot = pTFItemDef->GetLoadoutSlot( 0 ); // 0 gives use the default slot + if ( ( nSlot == LOADOUT_POSITION_HEAD ) || ( nSlot == LOADOUT_POSITION_MISC ) || ( nSlot == LOADOUT_POSITION_MISC2 ) ) + { + bool bFoundScore = false; + bool bFoundKill = false; + + FOR_EACH_VEC( pTFItemDef->GetStaticAttributes(), iIndex ) + { + const static_attrib_t& staticAttrib = pTFItemDef->GetStaticAttributes()[iIndex]; + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( staticAttrib.iDefIndex ); + if ( pAttrDef == pAttribDef_KillEaterScoreType ) + { + bFoundScore = true; + } + else if ( pAttrDef == pAttribDef_KillEaterKillType ) + { + bFoundKill = true; + } + } + + if ( !bFoundScore || !bFoundKill ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "BPostSchemaInit(): '%s' is missing the standard cosmetic killeater attributes.", pItemDef->GetDefinitionName() ).Get() ); + } + } + } + } +#endif // TF_GC_DLL + } + +#if TF_GC_DLL + // Make sure our tool application code validity works correctly. + if ( !BTestToolApplicability( pVecErrors ) ) + { + bAllSuccess = false; + pVecErrors->AddToTail( "BPostSchemaInit(): error with tool application validity." ); + } + + const CEconLootListDefinition* pUnusualLootlist = GetItemSchema()->GetLootListByName( "all_particle_hats" ); + if ( !pUnusualLootlist ) + { + bAllSuccess = false; + pVecErrors->AddToTail( "No lootlist \"all_particle_hats\"" ); + } + else + { + auto& contents = pUnusualLootlist->GetLootListContents(); + FOR_EACH_VEC( contents, i ) + { + if ( contents[ i ].m_iItemOrLootlistDef > 0 ) + { + const CEconItemDefinition* pItemDef = GetItemDefinition( contents[ i ].m_iItemOrLootlistDef ); + if ( !( pItemDef->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "hat" ) ) + && !( pItemDef->GetEquipRegionMask() & GetItemSchema()->GetEquipRegionBitMaskByName( "whole_head" ) ) ) + { + bAllSuccess = false; + pVecErrors->AddToTail( CFmtStr( "Item \"%s\" is in all_particle_hats, but doesn't have equip region hat or whole_head, meaning it can't become unusual. REMOVE IT!", pItemDef->GetDefinitionName() ).Get() ); + } + } + } + } +#endif // TF_GC_DLL + + return bAllSuccess; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CItemLevelingDefinition::CItemLevelingDefinition( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Copy constructor +//----------------------------------------------------------------------------- +CItemLevelingDefinition::CItemLevelingDefinition( const CItemLevelingDefinition &that ) +{ + (*this) = that; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CItemLevelingDefinition::~CItemLevelingDefinition() +{ + // Free up strdup() memory. + free( m_pszLocalizedName_LocalStorage ); +} + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CItemLevelingDefinition &CItemLevelingDefinition::operator=( const CItemLevelingDefinition &other ) +{ + m_unLevel = other.m_unLevel; + m_unRequiredScore = other.m_unRequiredScore; + m_pszLocalizedName_LocalStorage = strdup( other.m_pszLocalizedName_LocalStorage ); + + return *this; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CItemLevelingDefinition::BInitFromKV( KeyValues *pKVItemLevel, const char *pszLevelBlockName, CUtlVector<CUtlString> *pVecErrors ) +{ + m_unLevel = Q_atoi( pKVItemLevel->GetName() ); + m_unRequiredScore = pKVItemLevel->GetInt( "score" ); + m_pszLocalizedName_LocalStorage = strdup( pKVItemLevel->GetString( "rank_name", CFmtStr( "%s%i", pszLevelBlockName, m_unLevel ).Access() ) ); + + return SCHEMA_INIT_SUCCESS(); +} + +#ifdef GC_DLL +EUniverse GetUniverse() +{ + return GGCHost()->GetUniverse(); +} +#endif // GC_DLL diff --git a/game/shared/econ/econ_item_schema.h b/game/shared/econ/econ_item_schema.h new file mode 100644 index 0000000..283526a --- /dev/null +++ b/game/shared/econ/econ_item_schema.h @@ -0,0 +1,3374 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: EconItemSchema: Defines a schema for econ items +// +//============================================================================= + +#ifndef ECONITEMSCHEMA_H +#define ECONITEMSCHEMA_H +#ifdef _WIN32 +#pragma once +#endif + +// Valve code doesn't play nicely with standard headers on some platforms sometimes. +#ifdef min + #undef min +#endif + +#ifdef max + #undef max +#endif + +#include <string> + +#include "KeyValues.h" +#include "tier1/utldict.h" +#include "tier1/utlhashmaplarge.h" +#include "econ_item_constants.h" + +#include "item_selection_criteria.h" +#include "bitvec.h" +#include "language.h" +#include "smartptr.h" +#include "rtime.h" +#include "checksum_sha1.h" + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +#include "engine/ivmodelinfo.h" +#include "engine/ivmodelrender.h" +#include "ilocalize.h" +#endif +#include "gamestringpool.h" + +class CEconItemSchema; +class CEconItem; +class CEconSharedObjectCache; +class CSOItemRecipe; + +union attribute_data_union_t +{ + float asFloat; + uint32 asUint32; + byte *asBlobPointer; +}; + +struct static_attrib_t +{ + static_attrib_t() + { + iDefIndex = 0; + m_value.asBlobPointer = NULL; +#ifdef GC_DLL + bForceGCToGenerate = false; + m_pKVCustomData = NULL; +#endif // GC_DLL + } + + ~static_attrib_t() + { +#ifdef GC_DLL + if ( m_pKVCustomData ) + m_pKVCustomData->deleteThis(); + m_pKVCustomData = NULL; +#endif + } + + static_attrib_t( const static_attrib_t& rhs ) + { + iDefIndex = rhs.iDefIndex; + m_value = rhs.m_value; +#ifdef GC_DLL + m_pKVCustomData = rhs.m_pKVCustomData ? rhs.m_pKVCustomData->MakeCopy() : NULL; + bForceGCToGenerate = rhs.bForceGCToGenerate; +#endif + } + + attrib_definition_index_t iDefIndex; + attribute_data_union_t m_value; +#ifdef GC_DLL + bool bForceGCToGenerate; + KeyValues *m_pKVCustomData; +#endif // GC_DLL + + // Parses a single subsection from a multi-line attribute block that looks like: + // + // "attributes" + // { + // "cannot trade" + // { + // "attribute_class" "cannot_trade" + // "value" "1" + // } + // "kill eater" + // { + // "attribute_class" "kill_eater" + // "force_gc_to_generate" "1" + // "use_custom_logic" "gifts_given_out" + // } + // } + // + // The "force_gc_to_generate" and "use_custom_logic" fields will only be parsed on the GC. Will return + // true/false based on whether the whole attribute and value parsed successfully. + bool BInitFromKV_MultiLine( const char *pszContext, KeyValues *pKVAttribute, CUtlVector<CUtlString> *pVecErrors ); + + // Parses a single subsection from a single-line attribute block that looks like: + // + // CharacterAttributes + // { + // "increase buff duration" 9.0 + // "damage bonus" 2.0 + // } + // + // It's impossible to specify GC-generated attributes in this format. Will return true/false based on + // whether the whole attribute and value parsed successfully. + bool BInitFromKV_SingleLine( const char *pszContext, KeyValues *pKVAttribute, CUtlVector<CUtlString> *pVecErrors, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode = true ); + + // Data access helpers. + const class CEconItemAttributeDefinition *GetAttributeDefinition() const; + const class ISchemaAttributeType *GetAttributeType() const; +}; + +typedef uint16 equipped_class_t; +typedef uint16 equipped_slot_t; +typedef uint8 equipped_preset_t; + +#define INVALID_EQUIPPED_SLOT ((equipped_slot_t)-1) +#define INVALID_STYLE_INDEX ((style_index_t)-1) +#define INVALID_PRESET_INDEX ((equipped_preset_t)-1) + +enum EEquipType_t +{ + EQUIP_TYPE_CLASS = 0, + EQUIP_TYPE_ACCOUNT, + + EQUIP_TYPE_INVALID, +}; + +// Streamable weapons cause stutters when people enter PVS. Turn it off for now. +// #define WITH_STREAMABLE_WEAPONS + +//----------------------------------------------------------------------------- +// IEconItemPropertyGenerator +//----------------------------------------------------------------------------- +class IEconItemPropertyGenerator +{ +public: + virtual ~IEconItemPropertyGenerator() { } + + MUST_CHECK_RETURN virtual bool BGenerateProperties( CEconItem *pItem ) const = 0; +}; + +//----------------------------------------------------------------------------- +// Item Series +//----------------------------------------------------------------------------- +class CEconItemSeriesDefinition +{ +public: + CEconItemSeriesDefinition( void ); + CEconItemSeriesDefinition( const CEconItemSeriesDefinition &that ); + CEconItemSeriesDefinition &operator=( const CEconItemSeriesDefinition& rhs ); + + ~CEconItemSeriesDefinition( void ) { } + + bool BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors = NULL ); + + int32 GetDBValue( void ) const { return m_nValue; } + const char *GetName( void ) const { return !m_strName.IsEmpty() ? m_strName.String() : "unknown"; } + const char *GetLocKey( void ) const { return !m_strLockKey.IsEmpty() ? m_strLockKey.String() : "unknown"; } + const char *GetUiFile( void ) const { return !m_strUiFile.IsEmpty() ? m_strUiFile.String() : "unknown"; } + +private: + + // The value that the game/DB will know this series by + int32 m_nValue; + + CUtlString m_strName; // Key Name + CUtlString m_strLockKey; // Localization key + CUtlString m_strUiFile; // Ui File (.res file) +}; +//----------------------------------------------------------------------------- +// CEconItemRarityDefinition +//----------------------------------------------------------------------------- +class CEconItemRarityDefinition +{ +public: + CEconItemRarityDefinition( void ); + + ~CEconItemRarityDefinition( void ) { } + + bool BInitFromKV( KeyValues *pKVItem, KeyValues *pKVRarityWeights, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors = NULL ); + + int32 GetDBValue( void ) const { return m_nValue; } + const char *GetName( void ) const { return !m_strName.IsEmpty() ? m_strName.String() : "unknown"; } + const char *GetLocKey( void ) const { return m_strLocKey.String(); } + const char *GetWepLocKey( void ) const { return m_strWepLocKey.String(); } + const char *GetDropSound( void ) const { return m_strDropSound.String(); } + attrib_colors_t GetAttribColor( void ) const { return m_iAttribColor; } + const char *GetNextRarity( void ) const { return m_strNextRarity.String(); } + int32 GetLootlistWeight( void ) const { return m_nLootlistWeight; } + +private: + + // The value that the game/DB will know this rarity by + int32 m_nValue; + + attrib_colors_t m_iAttribColor; + + // The English name of the rarity + CUtlString m_strName; + + // The localization key for this rarity. + CUtlString m_strLocKey; + // The localization key for this rarity, for weapons. + CUtlString m_strWepLocKey; + + // The loot list name associated with this rarity. + CUtlString m_strDropSound; + + CUtlString m_strNextRarity; + + int32 m_nLootlistWeight; + +}; + +//----------------------------------------------------------------------------- +// CEconItemQualityDefinition +// Template Definition of a randomly created item +//----------------------------------------------------------------------------- +class CEconItemQualityDefinition +{ +public: + CEconItemQualityDefinition( void ); + CEconItemQualityDefinition( const CEconItemQualityDefinition &that ); + CEconItemQualityDefinition &operator=( const CEconItemQualityDefinition& rhs ); + + ~CEconItemQualityDefinition( void ) { } + + bool BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors = NULL ); + + + int32 GetDBValue( void ) const { return m_nValue; } + const char *GetName( void ) const { return !m_strName.IsEmpty() ? m_strName.Get() : "unknown"; } + bool CanSupportSet( void ) const { return m_bCanSupportSet; } + const char *GetHexColor( void ) const { return !m_strHexColor.IsEmpty() ? m_strHexColor.Get() : "B2B2B2"; } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + VALIDATE_SCOPE(); + ValidateObj( m_strName ); + } +#endif // DBGFLAG_VALIDATE + +private: + + // The value that the game/DB will know this quality by + int32 m_nValue; + + // The English name of the quality + CUtlConstString m_strName; + + // if this is true the support tool is allowed to set this quality level on any item + bool m_bCanSupportSet; + + // A hex string representing the color this quality should display as. Used primarily for display on the Web. + CUtlConstString m_strHexColor; +}; + +//----------------------------------------------------------------------------- +// CEconColorDefinition +//----------------------------------------------------------------------------- +class CEconColorDefinition +{ +public: + bool BInitFromKV( KeyValues *pKVColor, CUtlVector<CUtlString> *pVecErrors = NULL ); + + const char *GetName( void ) const { return m_strName.Get(); } + const char *GetColorName( void ) const { return m_strColorName.Get(); } // meant for passing into VGUI styles, etc. + const char *GetHexColor( void ) const { return m_strHexColor.Get(); } + +private: + // The English name of this color. Only used for lookup. + CUtlConstString m_strName; + + // The VGUI name of the color in our schema. This will be used to set values + // for VGUI controls. + CUtlConstString m_strColorName; + + // The hex string value of this color. This will be used for Web display. + CUtlConstString m_strHexColor; +}; + +//----------------------------------------------------------------------------- +// CEconItemSetDefinition +// Definition of an item set +//----------------------------------------------------------------------------- +class CEconItemSetDefinition +{ +public: + CEconItemSetDefinition( void ); + CEconItemSetDefinition( const CEconItemSetDefinition &that ); + CEconItemSetDefinition &operator=( const CEconItemSetDefinition& rhs ); + + ~CEconItemSetDefinition( void ) {} + + bool BInitFromKV( KeyValues *pKVItemSet, CUtlVector<CUtlString> *pVecErrors = NULL ); + + void IterateAttributes( class IEconItemAttributeIterator *pIterator ) const; + +public: + + const char *m_pszName; + const char *m_pszLocalizedName; + CUtlVector<item_definition_index_t> m_iItemDefs; + int m_iBundleItemDef; // Item def of the store bundle for this set, if any + bool m_bIsHiddenSet; // If true, this set and any bonuses will only be visible if the whole set is equipped. + + struct itemset_attrib_t + { + attrib_definition_index_t m_iAttribDefIndex; + float m_flValue; + }; + CUtlVector<itemset_attrib_t> m_iAttributes; +}; + +//----------------------------------------------------------------------------- +class CEconItemCollectionDefinition +{ +public: + CEconItemCollectionDefinition( void ); + ~CEconItemCollectionDefinition( void ) {} + + bool BInitFromKV( KeyValues *pKVItemCollection, CUtlVector<CUtlString> *pVecErrors = NULL ); + + uint8 GetMinRarity() const { return m_iRarityMin; } + uint8 GetMaxRarity() const { return m_iRarityMax; } + +public: + + const char *m_pszName; + const char *m_pszLocalizedName; + const char *m_pszLocalizedDesc; + CUtlVector<item_definition_index_t> m_iItemDefs; + +private: + bool m_bIsReferenceCollection; + + uint8 m_iRarityMin; + uint8 m_iRarityMax; +}; + +//----------------------------------------------------------------------------- +class CEconItemPaintKitDefinition +{ +public: + CEconItemPaintKitDefinition( void ); + ~CEconItemPaintKitDefinition( void ); + + bool BInitFromKV( KeyValues *pKVItemPaintKit, CUtlVector<CUtlString> *pVecErrors = NULL ); + + //KeyValues *GetKVP() { return m_pKVItem; } + + const char *GetName() const { return m_pszName; } + const char *GetLocalizeName() const { return m_pszLocalizedName; } + + KeyValues *GetPaintKitWearKV( int nWear ); + +private: + const char *m_pszName; + const char *m_pszLocalizedName; + + CUtlVector< KeyValues * > m_vecPaintKitWearKVP; +}; + +//----------------------------------------------------------------------------- +class CEconOperationDefinition +{ +public: + CEconOperationDefinition( void ); + ~CEconOperationDefinition( void ); + + bool BInitFromKV( KeyValues *pKVOperation, CUtlVector<CUtlString> *pVecErrors = NULL ); + + const char *GetName() const { return m_pszName; } + operation_definition_index_t GetOperationID() const { return m_unOperationID; } + item_definition_index_t GetRequiredItemDefIndex() const { return m_unRequiredItemDefIndex; } + item_definition_index_t GetGatewayItemDefIndex() const { return m_unGatewayItemDefIndex; } + + KeyValues *GetKVP() { return m_pKVItem; } + + // use the date that we stop giving things to players as expiry date + bool IsExpired() const { return CRTime::RTime32TimeCur() > GetStopGivingToPlayerDate(); } + bool IsActive() const { return CRTime::RTime32TimeCur() >= GetStartDate() && !IsExpired(); } + + const char *GetQuestLogOverrideResFile() const { return m_pszQuestLogResFile; } + const char *GetQuestListOverrideResFile() const { return m_pszQuestListResFile; } + + RTime32 GetStartDate() const { return m_OperationStartDate; } + RTime32 GetStopGivingToPlayerDate() const { return m_StopGivingToPlayerDate; } + RTime32 GetStopAddingToQueueDate() const { return m_StopAddingToQueueDate; } + + const char *GetOperationLootlist() const { return m_pszOperationLootList; } + bool IsCampaign() const { return m_bIsCampaign; } + uint32 GetMaxDropCount() const { return m_unMaxDropCount; } + +#ifdef GC_DLL + enum EContractRewardLootlist_t + { + REWARD_CASE, + REWARD_WEAPON, + + NUM_REWARDS + }; + const char *GetContractRewardLootlist( EContractRewardLootlist_t eType ) const { return m_pszContractRewardLootlist[ eType ]; } + + RTime32 GetMinQueueFreq() const; + RTime32 GetMaxQueueFreq() const; + RTime32 GetMinDropFreq() const; + RTime32 GetMaxDropFreq() const; + + uint8 GetNumSeededContracts() const { return m_unSeed; } + uint16 GetNumMaxHeldDrops() const { return m_unMaxHeldDrops; } + + int GetNumMaxQueueCount() const { return m_nMaxQueueCount; } + + uint8 GetMaxDropPerThink() const { return m_unMaxDropPerThink; } + +#endif // GC_DLL + +private: + const char *m_pszName; + operation_definition_index_t m_unOperationID; + + // things operation periodically drops + const char *m_pszOperationLootList; + bool m_bIsCampaign; + uint32 m_unMaxDropCount; + + const char *m_pszQuestLogResFile; + const char *m_pszQuestListResFile; + + item_definition_index_t m_unRequiredItemDefIndex; + item_definition_index_t m_unGatewayItemDefIndex; // Defindex of the item users need to acquire in order to get the required item. Could be the required item itself. + + RTime32 m_OperationStartDate; // when the operation starts and gives out rewards + RTime32 m_StopGivingToPlayerDate; // when the operation stops giving quests to player + RTime32 m_StopAddingToQueueDate; // when the operation stops adding more quests to the bucket + +#ifdef GC_DLL + const char *m_pszContractRewardLootlist[ NUM_REWARDS ]; + + // in seconds + RTime32 m_rtQueueFreqMin; + RTime32 m_rtQueueFreqMax; + RTime32 m_rtDropFreqMin; + RTime32 m_rtDropFreqMax; + + uint8 m_unSeed; + uint16 m_unMaxHeldDrops; + int m_nMaxQueueCount; + + uint8 m_unMaxDropPerThink; + +#endif // GC_DLL + + KeyValues *m_pKVItem; +}; + +//----------------------------------------------------------------------------- +// CEconLootListDefinition +// Definition of a loot list +//----------------------------------------------------------------------------- +class IEconLootList +{ +public: + virtual ~IEconLootList() { } + + MUST_CHECK_RETURN virtual bool BPublicListContents() const = 0; + MUST_CHECK_RETURN virtual const char *GetLootListHeaderLocalizationKey() const = 0; + MUST_CHECK_RETURN virtual const char *GetLootListFooterLocalizationKey() const = 0; + MUST_CHECK_RETURN virtual const char *GetLootListCollectionReference() const = 0; + + class IEconLootListIterator + { + public: + virtual ~IEconLootListIterator() { } + virtual void OnIterate( item_definition_index_t unItemDefIndex ) = 0; + }; + + virtual void EnumerateUserFacingPotentialDrops( IEconLootListIterator *pIt ) const = 0; + +#ifdef GC_DLL + MUST_CHECK_RETURN virtual bool BGenerateSingleRollRandomItems( const CEconGameAccount *pGameAccount, bool bFreeAccount, CUtlVector<CEconItem *> *out_pvecItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs = NULL ) const = 0; +#endif // GC_DLL +}; + +#ifdef GC_DLL +struct lootlist_attrib_t +{ + static_attrib_t m_staticAttrib; + float m_flWeight; + + bool BInitFromKV( const char *pszContext, KeyValues *pKVKey, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors ); +}; + +struct random_attrib_t +{ + float m_flChanceOfRandomAttribute; + float m_flTotalAttributeWeight; + bool m_bPickAllAttributes; + CUtlVector<lootlist_attrib_t> m_RandomAttributes; + + bool RollRandomAttributes( CUtlVector< static_attrib_t >& vecAttributes, const CEconGameAccount *pGameAccount ) const; +}; +#endif // GC_DLL + +class CEconLootListDefinition : public IEconLootList +{ +public: + struct drop_period_t + { + bool IsValidForTime( const RTime32& time ) const; + + RTime32 m_DropStartDate; + RTime32 m_DropEndDate; + }; + + struct drop_item_t + { + int m_iItemOrLootlistDef; // negative values indicate nested loot lists + float m_flWeight; + drop_period_t m_dropPeriod; + }; + + struct loot_list_additional_drop_t + { + float m_fChance; + bool m_bPremiumOnly; + const char *m_pszLootListDefName; + int m_iRequiredHolidayIndex; + drop_period_t m_dropPeriod; + }; + + virtual ~CEconLootListDefinition(); + + bool BInitFromKV( KeyValues *pKVLootList, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors ); + + const char *GetName() const { return m_pszName; } + virtual const char *GetLootListHeaderLocalizationKey() const OVERRIDE { return m_pszLootListHeader; } + virtual const char *GetLootListFooterLocalizationKey() const OVERRIDE { return m_pszLootListFooter; } + virtual const char *GetLootListCollectionReference() const OVERRIDE { return m_pszCollectionReference; } + + const CUtlVector<drop_item_t>& GetLootListContents() const { return m_DropList; } +#ifdef GC_DLL + const CUtlVector<loot_list_additional_drop_t>& GetAdditionalDrops() const { return m_AdditionalDrops; } +#endif + virtual void EnumerateUserFacingPotentialDrops( IEconLootListIterator *pIt ) const OVERRIDE; + + virtual bool BPublicListContents() const OVERRIDE + { + return m_bPublicListContents; + } + +#ifdef GC_DLL + +public: + struct rolled_item_defs_t + { + const CEconItemDefinition *m_pItemDef; + CCopyableUtlVector< const CEconLootListDefinition * > m_vecAffectingLootLists; + }; + + bool AddRandomAtrributes( KeyValues *pRandomAttributesKV, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors = NULL ); + bool AddRandomAttributesFromTemplates( KeyValues *pRandomAttributesKV, CEconItemSchema &pschema, CUtlVector<CUtlString> *pVecErrors = NULL ); + + + // Generates a single roll for this loot list as well as each "additional drop" loot list specified. This will return + // true if all items were created successfully or false if anything went wrong in any of the relevant lootlists. All + // items created will be returned via out_pvecItems. + MUST_CHECK_RETURN virtual bool BGenerateSingleRollRandomItems( const CEconGameAccount *pGameAccount, bool bFreeAccount, CUtlVector<CEconItem *> *out_pvecItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs = NULL ) const OVERRIDE; + + void RollRandomAttributes( CUtlVector< static_attrib_t >& vecAttributes, const CEconGameAccount *pGameAccount ) const; + bool RollRandomItemsAndAdditionalItems( IUniformRandomStream *pRandomStream, bool bFreeAccount, CUtlVector<rolled_item_defs_t> *out_pVecRolledItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs = NULL ) const; + + uint8 GetRarity() const { return m_unRarity; } + void GetRarityLootLists( CUtlVector< const CEconLootListDefinition* > *out_pVecRarityLootList ) const; + void GetItemDefs( CUtlVector< item_definition_index_t > *out_pVecItemDefs ) const; + +private: + bool RollRandomItemDef( IUniformRandomStream *pRandomStream, bool bFreeAccount, CUtlVector<rolled_item_defs_t> *out_pVecRolledItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs = NULL ) const; + bool BIsInternalNoDupesLootList() const { return m_iNoDupesIterations >= 0; } + + MUST_CHECK_RETURN bool BInitPropertyGeneratorsFromKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ); +#endif + +private: + const char *m_pszName; + const char *m_pszLootListHeader; + const char *m_pszLootListFooter; + const char *m_pszCollectionReference; + CUtlVector<drop_item_t> m_DropList; + + bool m_bPublicListContents; // do not show loot list contents to users (ie., when listing crate contents on Steam) + +#ifdef GC_DLL + + MUST_CHECK_RETURN bool BAttachLootListAttributes( const CEconGameAccount *pGameAccount, CEconItem *pItem ) const; + + int m_iNoDupesIterations; // if less than zero, "no dupes" functionality disabled; if greater than or equal to zero, the number of iterations we want to run through passing no-dupe sets + + CUtlVector<random_attrib_t*> m_RandomAttribs; + CUtlVector<loot_list_additional_drop_t> m_AdditionalDrops; + CUtlVector<const IEconItemPropertyGenerator *> m_PropertyGenerators; + + uint8 m_unRarity; +#endif // GC_DLL +}; + +//----------------------------------------------------------------------------- +// CEconCraftingRecipeDefinition +// Template Definition of an item recipe +//----------------------------------------------------------------------------- +class CEconCraftingRecipeDefinition +{ +public: + CEconCraftingRecipeDefinition( void ); + virtual ~CEconCraftingRecipeDefinition( void ) { } + + bool BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors = NULL ); + +#ifdef GC_DLL + bool BIsCraftableByUnverifiedClients() const { return m_bIsCraftableByUnverifiedClient; } +#endif // GC_DLL + + virtual void CopyPolymorphic( const CEconCraftingRecipeDefinition *pSourceDef ) { *this = *pSourceDef; } + + void SetDefinitionIndex( uint32 iIndex ) { m_nDefIndex = iIndex; } + int32 GetDefinitionIndex( void ) const { return m_nDefIndex; } + const char *GetName( void ) const { return !m_strName.IsEmpty() ? m_strName.String() : "unknown"; } + const char *GetName_A( void ) const { return !m_strN_A.IsEmpty() ? m_strN_A.String() : "unknown"; } + const char *GetDescInputs( void ) const { return !m_strDescInputs.IsEmpty() ? m_strDescInputs.String() : "unknown"; } + const char *GetDescOutputs( void ) const { return !m_strDescOutputs.IsEmpty() ? m_strDescOutputs.String() : "unknown"; } + + const char *GetDescI_A( void ) const { return !m_strDI_A.IsEmpty() ? m_strDI_A.String() : "unknown"; } + const char *GetDescI_B( void ) const { return !m_strDI_B.IsEmpty() ? m_strDI_B.String() : "unknown"; } + const char *GetDescI_C( void ) const { return !m_strDI_C.IsEmpty() ? m_strDI_C.String() : "unknown"; } + const char *GetDescO_A( void ) const { return !m_strDO_A.IsEmpty() ? m_strDO_A.String() : "unknown"; } + const char *GetDescO_B( void ) const { return !m_strDO_B.IsEmpty() ? m_strDO_B.String() : "unknown"; } + const char *GetDescO_C( void ) const { return !m_strDO_C.IsEmpty() ? m_strDO_C.String() : "unknown"; } + + bool IsDisabled( void ) const { return m_bDisabled; } + bool RequiresAllSameClass( void ) { return m_bRequiresAllSameClass; } + bool RequiresAllSameSlot( void ) { return m_bRequiresAllSameSlot; } + bool IsPremiumAccountOnly( void ) const { return m_bPremiumAccountOnly; } + recipecategories_t GetCategory( void ) const { return m_iCategory; } + int GetTotalInputItemsRequired( void ) const; + int GetTotalOutputItems( void ) const { return m_OutputItemsCriteria.Count(); } + + // Returns true if the vector contains a set of items that matches the inputs for this recipe + virtual bool ItemListMatchesInputs( CUtlVector<CEconItem*> *vecCraftingItems, KeyValues *out_pCraftParams = NULL, bool bIgnoreSlop = false, CUtlVector<uint64> *vecChosenItems = NULL ) const; + + const CUtlVector<CItemSelectionCriteria> *GetInputItems( void ) const { return &m_InputItemsCriteria; } + const CUtlVector<uint32> &GetInputItemDupeCounts( void ) const { return m_InputItemDupeCounts; } + const CUtlVector<CItemSelectionCriteria> &GetOutputItems( void ) const { return m_OutputItemsCriteria; } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + VALIDATE_SCOPE(); + ValidateObj( m_InputItemsCriteria ); + ValidateObj( m_InputItemDupeCounts ); + ValidateObj( m_OutputItemsCriteria ); + } +#endif // DBGFLAG_VALIDATE + + // Serializes the criteria to and from messages + bool BSerializeToMsg( CSOItemRecipe & msg ) const; + bool BDeserializeFromMsg( const CSOItemRecipe & msg ); + +protected: + // The number used to refer to this definition in the DB + int32 m_nDefIndex; + + // Localization key strings + CUtlString m_strName; + CUtlString m_strN_A; + CUtlString m_strDescInputs; + CUtlString m_strDescOutputs; + CUtlString m_strDI_A; + CUtlString m_strDI_B; + CUtlString m_strDI_C; + CUtlString m_strDO_A; + CUtlString m_strDO_B; + CUtlString m_strDO_C; + + bool m_bDisabled; +#ifdef GC_DLL + bool m_bIsCraftableByUnverifiedClient; +#endif // GC_DLL + bool m_bRequiresAllSameClass; + bool m_bRequiresAllSameSlot; + int m_iCacheClassUsageForOutputFromItem; + int m_iCacheSlotUsageForOutputFromItem; + int m_iCacheSetForOutputFromItem; + bool m_bPremiumAccountOnly; + recipecategories_t m_iCategory; + + // The list of items that a required to make this recipe + CUtlVector<CItemSelectionCriteria> m_InputItemsCriteria; + CUtlVector<uint32> m_InputItemDupeCounts; + + // The list of items that are generated by this recipe + CUtlVector<CItemSelectionCriteria> m_OutputItemsCriteria; +}; + +//----------------------------------------------------------------------------- +// Purpose: Attribute definition details +//----------------------------------------------------------------------------- +enum +{ + ATTDESCFORM_VALUE_IS_PERCENTAGE, // Printed as: ((m_flValue*100)-100.0) + ATTDESCFORM_VALUE_IS_INVERTED_PERCENTAGE, // Printed as: ((m_flValue*100)-100.0) if it's > 1.0, or ((1.0-m_flModifier)*100) if it's < 1.0 + ATTDESCFORM_VALUE_IS_ADDITIVE, // Printed as: m_flValue + ATTDESCFORM_VALUE_IS_ADDITIVE_PERCENTAGE, // Printed as: (m_flValue*100) + ATTDESCFORM_VALUE_IS_OR, // Printed as: m_flValue, but results are ORd together instead of added + ATTDESCFORM_VALUE_IS_DATE, // Printed as a date + ATTDESCFORM_VALUE_IS_ACCOUNT_ID, // Printed as steam user name + ATTDESCFORM_VALUE_IS_PARTICLE_INDEX, // Printed as a particle description + ATTDESCFORM_VALUE_IS_KILLSTREAKEFFECT_INDEX,// Printed as killstreak effect description + ATTDESCFORM_VALUE_IS_KILLSTREAK_IDLEEFFECT_INDEX, // Printed as idle effect description + ATTDESCFORM_VALUE_IS_ITEM_DEF, // Printed as item name + ATTDESCFORM_VALUE_IS_FROM_LOOKUP_TABLE, // Printed as a string from a lookup table, specified by the attribute definition name +}; + +// Coloring for attribute lines +enum attrib_effect_types_t +{ + ATTRIB_EFFECT_UNUSUAL = 0, + ATTRIB_EFFECT_STRANGE, + ATTRIB_EFFECT_NEUTRAL, + ATTRIB_EFFECT_POSITIVE, + ATTRIB_EFFECT_NEGATIVE, + + NUM_EFFECT_TYPES, +}; + +enum EAssetClassAttrExportRule_t +{ + k_EAssetClassAttrExportRule_Default = 0, + k_EAssetClassAttrExportRule_Bucketed = ( 1 << 0 ), // attribute exports bucketed value to Steam Community + k_EAssetClassAttrExportRule_Skip = ( 1 << 1 ), // attribute value is not exported to Steam Community + k_EAssetClassAttrExportRule_GCOnly = ( 1 << 2 ), // attribute only lives on GC and not exported to any external request +}; + +//----------------------------------------------------------------------------- +// CEconItemAttributeDefinition +// Template definition of a randomly created attribute +//----------------------------------------------------------------------------- +class CEconItemAttributeDefinition +{ +public: + CEconItemAttributeDefinition( void ); + CEconItemAttributeDefinition( const CEconItemAttributeDefinition &that ); + CEconItemAttributeDefinition &operator=( const CEconItemAttributeDefinition& rhs ); + + ~CEconItemAttributeDefinition( void ); + + bool BInitFromKV( KeyValues *pKVAttribute, CUtlVector<CUtlString> *pVecErrors = NULL ); + + attrib_definition_index_t GetDefinitionIndex( void ) const { return m_nDefIndex; } + // Attribute name referenced in the db. + const char *GetDefinitionName( void ) const { return m_pszDefinitionName; } + + KeyValues *GetRawDefinition( void ) const { return m_pKVAttribute; } + + // Data accessing + bool IsHidden( void ) const { return m_bHidden; } + bool BForceWebSchemaOutput( void ) const { return m_bWebSchemaOutputForced; } + bool BIsSetBonusAttribute( void ) const { return m_bIsSetBonus; } + bool CanAffectMarketName( void ) const { return m_bCanAffectMarketName; } + bool CanAffectRecipeComponentName( void ) const { return m_bCanAffectRecipeComponentName; } + bool IsStoredAsInteger( void ) const { return m_bStoredAsInteger; } + bool IsStoredAsFloat( void ) const { return !m_bStoredAsInteger; } + int GetUserGenerationType( void ) const { return m_iUserGenerationType; } + bool IsInstanceData() const { return m_bInstanceData; } + EAssetClassAttrExportRule_t GetAssetClassAttrExportRule() const { return m_eAssetClassAttrExportRule; } + uint32 GetAssetClassBucket() const { return m_unAssetClassBucket; } + int GetDescriptionFormat( void ) const { return m_iDescriptionFormat; } + const char *GetDescriptionString( void ) const { return m_pszDescriptionString; } + const char *GetArmoryDescString( void ) const { return m_pszArmoryDesc; } + const char *GetAttributeClass( void ) const { return m_pszAttributeClass; } + econ_tag_handle_t GetItemDefinitionTag( void ) const { return m_ItemDefinitionTag; } + attrib_effect_types_t GetEffectType( void ) const { return m_iEffectType; } + + const class ISchemaAttributeType *GetAttributeType( void ) const { return m_pAttrType; } + +#ifndef GC_DLL + void ClearStringCache( void ) const { m_iszAttributeClass = NULL_STRING; } + string_t GetCachedClass( void ) const + { + if ( m_iszAttributeClass == NULL_STRING && m_pszAttributeClass ) + { + m_iszAttributeClass = AllocPooledString( m_pszAttributeClass ); + } + return m_iszAttributeClass; + } +#endif + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + VALIDATE_SCOPE(); + ValidatePtr( m_pKVAttribute ); + } +#endif // DBGFLAG_VALIDATE + +private: + // The raw keyvalues for this attribute definition. + KeyValues *m_pKVAttribute; + + // Required valued from m_pKVAttribute: + + // The number used to refer to this definition in the DB + attrib_definition_index_t m_nDefIndex; + + // A pointer to the schema-global type data for this attribute. This maps attribute types to functionality + // for loading/storing, both to memory and the DB. + const class ISchemaAttributeType *m_pAttrType; + + // --------------------------------------------- + // Display related data + // --------------------------------------------- + // If true, this attribute isn't shown in the item description + bool m_bHidden; + + // If true, this attribute's description is always output in web api calls regardless of the hidden flag. + bool m_bWebSchemaOutputForced; + + // Whether or not the value is stored as an integer in the DB. + bool m_bStoredAsInteger; + + // If this is true the attribute is counted as "instance" data for purposes of asset class in the Steam Economy. Non-instance + // properties are considered things that can differentiate items at a fundamental level (ie., definition index, quality); instance + // properties are more things like additional customizations -- score for strange items, paint color, etc. + bool m_bInstanceData; + EAssetClassAttrExportRule_t m_eAssetClassAttrExportRule; // if this is true the attribute will not be exported for asset class + uint32 m_unAssetClassBucket; // if this is set then attribute value is bucketed when exported for asset class + + // Set item bonus attributes use a different attribute parser and make assumptions about memory layout. We + // don't really use these for any new content currently and it isn't worth touching all the old code. + // + // At runtime, this flag is used to determine whether or not to rebuild dynamic attributes attached to + // players on respawn. + bool m_bIsSetBonus; + + // Whether or not this attribute is supposed to only come from user actions. These attributes are used for + // player item upgrades, etc. and cannot be set on items directly in the schema. + int m_iUserGenerationType; + + // Overall positive/negative effect. Used to color the attribute. + attrib_effect_types_t m_iEffectType; + + // Contains the description format & string for this attribute + int m_iDescriptionFormat; + const char *m_pszDescriptionString; + + // Contains information on how to describe items with this attribute in the Armory + const char *m_pszArmoryDesc; + + // Used to allow unique items to specify attributes by name. + const char *m_pszDefinitionName; + + // The class name of this attribute. Used in creation, and to hook the attribute into the actual code that uses it. + const char *m_pszAttributeClass; + + // Allowed to affect the market bucketization name. We dont want things like the strange level to affect the name, + // but we do want things like crate series number and strangifier targets to get their own buckets. + bool m_bCanAffectMarketName; + + // Allowed to list itself in the name of an item in the recipe component description. + bool m_bCanAffectRecipeComponentName; + + // Do item definitions with this attribute specified automatically get an additional tag applied? + econ_tag_handle_t m_ItemDefinitionTag; + +#ifndef GC_DLL + mutable string_t m_iszAttributeClass; // Same as the above, but used for fast lookup when applying attributes. +#endif +}; + + +//----------------------------------------------------------------------------- +// Visual data storage in item definitions +//----------------------------------------------------------------------------- +#define TEAM_VISUAL_SECTIONS 5 +#define MAX_VISUALS_CUSTOM_SOUNDS 10 + +struct attachedparticlesystem_t +{ + attachedparticlesystem_t() : + pszSystemName( NULL ) + , bFollowRootBone( NULL ) + , iCustomType( 0 ) + , nSystemID( 0 ) + , fRefireTime( 0 ) // only works for taunt effects, currently + , bDrawInViewModel( false ) + , bUseSuffixName( false ) + , bHasViewModelSpecificEffect ( false ) + { + V_memset( pszControlPoints, 0, sizeof( pszControlPoints ) ); + } + + const char *pszSystemName; + bool bFollowRootBone; + int iCustomType; + int nSystemID; + float fRefireTime; // only works for taunt effects, currently + bool bDrawInViewModel; + bool bUseSuffixName; + bool bHasViewModelSpecificEffect; + + const char *pszControlPoints[7]; +}; + + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +enum +{ + kAttachedModelDisplayFlag_WorldModel = 0x01, + kAttachedModelDisplayFlag_ViewModel = 0x02, + + kAttachedModelDisplayFlag_MaskAll = kAttachedModelDisplayFlag_WorldModel | kAttachedModelDisplayFlag_ViewModel, +}; + +struct attachedmodel_t +{ + const char *m_pszModelName; + int m_iModelDisplayFlags; +}; + +enum wearableanimplayback_t +{ + WAP_ON_SPAWN, // Play this animation immediately on spawning the wearable + WAP_START_BUILDING, // Game code will start this anim whenever a player wearing this item deploys their builder weapon. + WAP_STOP_BUILDING, // Game code will start this anim whenever a player wearing this item holsters their builder weapon. + WAP_START_TAUNTING, // Game code will start this anim whenever a player wearing this item taunts + WAP_STOP_TAUNTING, // Game code will start this anim whenever a player wearing this item stops taunting + + NUM_WAP_TYPES, +}; + +struct animation_on_wearable_t +{ + int iActivity; + const char *pszActivity; + const char *pszReplacement; + int iReplacement; // Replacement activity to play. Might be set to one of kActivityLookup_Unknown/kActivityLookup_Missing. + const char *pszSequence; + const char *pszRequiredItem; + const char *pszScene; +}; + +struct activity_on_wearable_t +{ + wearableanimplayback_t iPlayback; + int iActivity; + const char *pszActivity; +}; + +struct codecontrolledbodygroupdata_t +{ + const char *pFuncName; + void *pFunc; +}; + +// This is a workaround because Source practice is to disable operator=() for CUtlMap. +struct perteamvisuals_maps_t +{ + perteamvisuals_maps_t() + { + m_ModifiedBodyGroupNames.SetLessFunc( StringLessThan ); + m_CodeControlledBodyGroupNames.SetLessFunc( StringLessThan ); + } + + void operator=( const perteamvisuals_maps_t& other ) + { + DeepCopyMap( other.m_ModifiedBodyGroupNames, &m_ModifiedBodyGroupNames ); + DeepCopyMap( other.m_CodeControlledBodyGroupNames, &m_CodeControlledBodyGroupNames ); + } + + CUtlMap<const char*, int> m_ModifiedBodyGroupNames; // Better method: hide multiple body groups by name. + CUtlMap<const char*, codecontrolledbodygroupdata_t> m_CodeControlledBodyGroupNames; +}; + +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + +class CEconStyleInfo +{ +public: + CEconStyleInfo() + { + for ( int i = 0; i < TEAM_VISUAL_SECTIONS; i++ ) + { + m_iSkins[i] = 0; + m_iViewmodelSkins[i] = -1; + } + + m_pszName = NULL; + m_pszBasePlayerModel = NULL; + m_bIsSelectable = true; + m_pszInventoryImage = NULL; + + m_pszBodygroupName = NULL; + m_iBodygroupSubmodelIndex = -1; + + m_sIconURLSmall = ""; + m_sIconURLLarge = ""; + } + + virtual ~CEconStyleInfo() + { + // + } + + virtual void BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors ); + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + virtual void GeneratePrecacheModelStringsForStyle( CUtlVector<const char *> *out_pVecModelStrings ) const; +#endif + + int GetSkin( int iTeam, bool bViewmodel ) const + { + Assert( iTeam >= 0 ); + Assert( iTeam < TEAM_VISUAL_SECTIONS ); + + if ( bViewmodel && m_iViewmodelSkins[ iTeam ] != -1 ) + { + return m_iViewmodelSkins[ iTeam ]; + } + + return m_iSkins[iTeam]; + } + + const char *GetName() const { return m_pszName; } + const char *GetBasePlayerDisplayModel() const { return m_pszBasePlayerModel; } + const CUtlVector<const char *>& GetAdditionalHideBodygroups() const { return m_vecAdditionalHideBodygroups; } + bool IsSelectable() const { return m_bIsSelectable; } + const char *GetInventoryImage() const { return m_pszInventoryImage; } + + const char *GetBodygroupName() const { return m_pszBodygroupName; } + int GetBodygroupSubmodelIndex() const { return m_iBodygroupSubmodelIndex; } + + const char *GetIconURLSmall() const { return m_sIconURLSmall; } + const char *GetIconURLLarge() const { return m_sIconURLLarge; } + void SetIconURLSmall( const char *szURL ) { m_sIconURLSmall = szURL; } + void SetIconURLLarge( const char *szURL ) { m_sIconURLLarge = szURL; } + +protected: + int m_iSkins[TEAM_VISUAL_SECTIONS]; + int m_iViewmodelSkins[TEAM_VISUAL_SECTIONS]; + const char *m_pszName; + const char *m_pszBasePlayerModel; + bool m_bIsSelectable; + const char *m_pszInventoryImage; + + const char *m_pszBodygroupName; + int m_iBodygroupSubmodelIndex; + + CUtlVector<const char *> m_vecAdditionalHideBodygroups; + +private: + + CUtlString m_sIconURLSmall; + CUtlString m_sIconURLLarge; +}; + +struct perteamvisuals_t +{ + perteamvisuals_t() + { +#if defined(CLIENT_DLL) || defined(GAME_DLL) + iHideParentBodyGroup = -1; + + iSkin = -1; + bUsePerClassBodygroups = false; + pszMaterialOverride = NULL; + pszMuzzleFlash = NULL; + pszTracerEffect = NULL; + pszParticleEffect = NULL; + for ( int i = 0; i < MAX_VISUALS_CUSTOM_SOUNDS; i++ ) + { + pszCustomSounds[i] = NULL; + } + + for ( int i = 0; i < NUM_SHOOT_SOUND_TYPES; i++ ) + { + pszWeaponSoundReplacements[i] = NULL; + } + + m_iViewModelBodyGroupOverride = -1; + m_iViewModelBodyGroupStateOverride = -1; + m_iWorldModelBodyGroupOverride = -1; + m_iWorldModelBodyGroupStateOverride = -1; + +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + } + + ~perteamvisuals_t() + { + m_Styles.PurgeAndDeleteElements(); + } + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + int iHideParentBodyGroup; + + // Properties necessary for the game client/server but not for the GC. + perteamvisuals_maps_t m_Maps; + int iSkin; + bool bUsePerClassBodygroups; + CUtlVector<attachedmodel_t> m_AttachedModels; + CUtlVector<attachedmodel_t> m_AttachedModelsFestive; // Attr controlled Festive Attachments + CUtlVector<attachedparticlesystem_t> m_AttachedParticles; + CUtlVector<animation_on_wearable_t> m_Animations; + CUtlVector<activity_on_wearable_t> m_Activities; + const char *pszCustomSounds[MAX_VISUALS_CUSTOM_SOUNDS]; + const char *pszMaterialOverride; + const char *pszMuzzleFlash; + const char *pszTracerEffect; + const char *pszParticleEffect; + const char *pszWeaponSoundReplacements[NUM_SHOOT_SOUND_TYPES]; + int m_iViewModelBodyGroupOverride; + int m_iViewModelBodyGroupStateOverride; + int m_iWorldModelBodyGroupOverride; + int m_iWorldModelBodyGroupStateOverride; +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + + // The GC does care about styles. + CUtlVector<CEconStyleInfo *> m_Styles; +}; + +enum item_capabilities_t +{ + ITEM_CAP_NONE = 0, + ITEM_CAP_PAINTABLE = 1 << 0, + ITEM_CAP_NAMEABLE = 1 << 1, + ITEM_CAP_DECODABLE = 1 << 2, + ITEM_CAP_CAN_BE_CRAFTED_IF_PURCHASED = 1 << 3, // was ITEM_CAP_CAN_MOD_SOCKET + ITEM_CAP_CAN_CUSTOMIZE_TEXTURE = 1 << 4, + ITEM_CAP_USABLE = 1 << 5, + ITEM_CAP_USABLE_GC = 1 << 6, + ITEM_CAP_CAN_GIFT_WRAP = 1 << 7, + ITEM_CAP_USABLE_OUT_OF_GAME = 1 << 8, + ITEM_CAP_CAN_COLLECT = 1 << 9, + ITEM_CAP_CAN_CRAFT_COUNT = 1 << 10, + ITEM_CAP_CAN_CRAFT_MARK = 1 << 11, + ITEM_CAP_PAINTABLE_TEAM_COLORS = 1 << 12, + ITEM_CAP_CAN_BE_RESTORED = 1 << 13, // can users remove properties (paint, nametag, etc.) from this item via the in-game UI? + ITEM_CAP_CAN_USE_STRANGE_PARTS = 1 << 14, + ITEM_CAP_CAN_CARD_UPGRADE = 1 << 15, + ITEM_CAP_CAN_STRANGIFY = 1 << 16, + ITEM_CAP_CAN_KILLSTREAKIFY = 1 << 17, + ITEM_CAP_CAN_CONSUME = 1 << 18, + ITEM_CAP_CAN_SPELLBOOK_PAGE = 1 << 19, // IT'S A VERB OKAY + ITEM_CAP_HAS_SLOTS = 1 << 20, + ITEM_CAP_DUCK_UPGRADABLE = 1 << 21, + ITEM_CAP_CAN_UNUSUALIFY = 1 << 22, + NUM_ITEM_CAPS = 23, +}; + +enum { ITEM_CAP_DEFAULT = ITEM_CAP_CAN_CRAFT_MARK | ITEM_CAP_CAN_BE_RESTORED | ITEM_CAP_CAN_USE_STRANGE_PARTS | ITEM_CAP_CAN_CARD_UPGRADE | ITEM_CAP_CAN_STRANGIFY | ITEM_CAP_CAN_KILLSTREAKIFY | ITEM_CAP_CAN_CONSUME | ITEM_CAP_CAN_GIFT_WRAP }; // what are the default capabilities on an item? +enum { ITEM_CAP_TOOL_DEFAULT = ITEM_CAP_NONE }; // what are the default capabilities of a tool? + +struct bundleinfo_t +{ + CUtlVector<CEconItemDefinition *> vecItemDefs; +}; + +#ifdef GC_DLL +enum EPaymentRuleType +{ + kPaymentRule_SteamWorkshopFileID = 0x01, + kPaymentRule_PartnerSteamID = 0x02, + kPaymentRule_Bundle = 0x04, +}; + +struct econ_item_payment_rule_t +{ + double m_RevenueShare; + EPaymentRuleType m_eRuleType; + CCopyableUtlVector<uint64> m_vecValues; +}; +#endif // GC_DLL + +#ifdef CLIENT_DLL +namespace vgui +{ + class Panel; +} +#endif // CLIENT_DLL + +class IEconTool +{ + friend class CEconSharedToolSupport; + +public: + IEconTool( const char *pszTypeName, const char *pszUseString, const char *pszUsageRestriction, item_capabilities_t unCapabilities ) + : m_pszTypeName( pszTypeName ) + , m_pszUseString( pszUseString ) + , m_pszUsageRestriction( pszUsageRestriction ) + , m_unCapabilities( unCapabilities ) + { + // + } + + virtual ~IEconTool() { } + + // Shared code. + const char *GetUsageRestriction() const { return m_pszUsageRestriction; } + item_capabilities_t GetCapabilities() const { return m_unCapabilities; } + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const { Assert( pTool ); Assert( pToolSubject ); return true; } + virtual bool ShouldDisplayQuantity( const IEconItemInterface *pTool ) const; + virtual bool RequiresToolEscrowPeriod() const { return false; } + + // We don't support throwing exceptions from tool construction so this is intended to be checked afterwards + // whenever a new tool is created. (See CreateEconToolImpl().) + virtual bool BFinishInitialization() { return true; } + + // Used by the GC only for WebAPI responses and for some weird internal code. + const char *GetTypeName() const { return m_pszTypeName; } // would like to disable on the client so we aren't tempted to check against it, but used for building a unique tool list + const char *GetUseString() const { return m_pszUseString; } + +#ifdef CLIENT_DLL + virtual bool CanBeUsedNow( const IEconItemInterface *pItem ) const { return true; } + virtual bool ShouldShowContainedItemPanel( const IEconItemInterface *pItem ) const { Assert( !"IEconTool::ShouldShowContainedItemPanel(): we don't expect this to be called on anything besides gifts!" ); return false; } + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return true; } + virtual const char *GetUseCommandLocalizationToken( const IEconItemInterface *pItem, int i = 0 ) const; + virtual int GetUseCommandCount( const IEconItemInterface *pItem ) const { return 1; } + virtual const char* GetUseCommand( const IEconItemInterface *pItem, int i = 0 ) const; + + + // Client "do something" interface. At least one of these functions must be implemented or your tool + // won't do anything on the client. Some tools (ie., collections) will implement both because they + // have one application behavior and one client-UI behavior. + + // When the client attempts to use a consumable item of any kind, this function will be called. This + // is called from the UI in response to things like using dueling pistols, using a noisemaker, etc. + // Usually this opens up some UI, sends off a GC message, etc. + // + // There is a "default" implementation of this function in ClientConsumableTool_Generic() that can + // be called if specific behavior isn't needed. + virtual void OnClientUseConsumable( class C_EconItemView *pItem, vgui::Panel *pParent ) const + { + Assert( !"IEconTool::OnClientUseConsumable(): unimplemented call!" ); + } + + // When the client attempts to apply a tool to a specific other item in their inventory, this function + // will be called. This is called from the UI is response to things like putting paint on an item, + // using a key to unlock a crate, etc. + virtual void OnClientApplyTool( class C_EconItemView *pTool, class C_EconItemView *pSubject, vgui::Panel *pParent ) const + { + Assert( !"IEconTool::OnClientApplyTool(): unimplemented call!" ); + } +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; + virtual bool BGenerateDynamicAttributes( CEconItem* pItem, const CEconGameAccount *pGameAccount ) const { return true; } +#endif // GC_DLL + +private: + const char *m_pszTypeName; + const char *m_pszUseString; + const char *m_pszUsageRestriction; + item_capabilities_t m_unCapabilities; +}; + +//----------------------------------------------------------------------------- +// CQuestObjectiveDefinition +//----------------------------------------------------------------------------- +class CQuestObjectiveDefinition +{ +public: + + CQuestObjectiveDefinition( void ); + virtual ~CQuestObjectiveDefinition( void ); + + virtual bool BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors = NULL ); + + uint32 GetDefinitionIndex( void ) const { return m_nDefIndex; } + const char *GetDescriptionToken( void ) const { return m_pszDescriptionToken; } + bool IsOptional() const { return m_bOptional; } + bool IsAdvanced() const { return m_bAdvanced; } + uint32 GetPoints() const { return m_nPoints; } // TODO: change to a float + +private: + const char *m_pszDescriptionToken; + uint32 m_nDefIndex; + uint32 m_nPoints; + bool m_bOptional; + bool m_bAdvanced; +}; + +//----------------------------------------------------------------------------- +// CEconItemDefinition +// Template Definition of a randomly created item +//----------------------------------------------------------------------------- +class CEconItemDefinition +{ +public: + CEconItemDefinition( void ); + virtual ~CEconItemDefinition( void ); + + // BInitFromKV can be implemented on subclasses to parse additional values. + virtual bool BInitFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors = NULL ); +#if defined(CLIENT_DLL) || defined(GAME_DLL) + virtual bool BInitFromTestItemKVs( int iNewDefIndex, KeyValues *pKVItem, CUtlVector<CUtlString>* pVecErrors = NULL ); + virtual void GeneratePrecacheModelStrings( bool bDynamicLoad, CUtlVector<const char *> *out_pVecModelStrings ) const; + virtual void GeneratePrecacheSoundStrings( bool bDynamicLoad, CUtlVector<const char *> *out_pVecSoundStrings ) const; + virtual void CopyPolymorphic( const CEconItemDefinition *pSourceDef ) { *this = *pSourceDef; } +#endif + + bool BInitItemMappings( CUtlVector<CUtlString> *pVecErrors ); + + void BInitVisualBlockFromKV( KeyValues *pKVItem, CUtlVector<CUtlString> *pVecErrors = NULL ); + void BInitStylesBlockFromKV( KeyValues *pKVStyles, perteamvisuals_t *pVisData, CUtlVector<CUtlString> *pVecErrors ); + + item_definition_index_t GetDefinitionIndex( void ) const { return m_nDefIndex; } + bool BEnabled( void ) const { return m_bEnabled; } + bool BLoadOnDemand( void ) const { return m_bLoadOnDemand; } + bool BHasBeenLoaded( void ) const { return m_bHasBeenLoaded; } + const char *GetDefinitionName( void ) const { return m_pszDefinitionName; } + const char *GetItemDefinitionName( void ) const { return m_pszDefinitionName; } + const char *GetItemClass( void ) const { return m_pszItemClassname; } + const char *GetItemBaseName( void ) const { return m_pszItemBaseName; } + const char *GetBrassModelOverride( void ) const{ return m_pszBrassModelOverride; } + const char *GetItemTypeName( void ) const { return m_pszItemTypeName; } + uint8 GetMinLevel( void ) const { return m_unMinItemLevel; } + uint8 GetMaxLevel( void ) const { return m_unMaxItemLevel; } + uint8 GetItemSeries( void ) const { return m_unItemSeries; } + uint8 GetQuality( void ) const { return m_nItemQuality; } + void SetRarity( uint8 nRarity ) { Assert( m_nItemRarity == k_unItemRarity_Any ); m_nItemRarity = nRarity; } + uint8 GetRarity( void ) const { return m_nItemRarity; } + uint8 GetForcedQuality( void ) const { return m_nForcedItemQuality; } + uint16 GetDefaultDropQuantity( void ) const { return m_nDefaultDropQuantity; } + KeyValues *GetRawDefinition( void ) const { return m_pKVItem; } + const char *GetDefinitionString( const char *pszKeyName, const char *pszDefaultValue = "" ) const; + KeyValues *GetDefinitionKey( const char *pszKeyName ) const; + const CUtlVector<static_attrib_t> &GetStaticAttributes( void ) const { return m_vecStaticAttributes; } +#ifdef TF_CLIENT_DLL + uint32 GetNumConcreteItems() const { return m_unNumConcreteItems; } +#endif // TF_CLIENT_DLL + + // Data accessing + bool IsHidden( void ) const { return m_bHidden; } + bool IsImported( void ) const { return m_bImported; } + bool IsAllowedInMatch( void ) const { return m_bAllowedInThisMatch; } + bool IsBaseItem( void ) const { return m_bBaseItem; } + bool IsBundle( void ) const { return m_BundleInfo != NULL; } + bool HasProperName( void ) const { return m_bProperName; } + const char *GetClassToken( void ) const { return m_pszClassToken; } + const char *GetSlotToken( void ) const { return m_pszSlotToken; } + bool ShouldAttachToHands( void ) const { return m_bAttachToHands; } + bool ShouldAttachToHandsVMOnly( void ) const { return m_bAttachToHandsVMOnly; } + bool ShouldFlipViewmodels( void ) const { return m_bFlipViewModel; } + int GetInventoryImagePosition( int iIndex ) const { Assert( iIndex >= 0 && iIndex < 2); return m_iInventoryImagePosition[iIndex]; } + int GetInventoryImageSize( int iIndex ) const { Assert( iIndex >= 0 && iIndex < 2); return m_iInventoryImageSize[iIndex]; } + int GetDropType( void ) const { return m_iDropType; } + const char *GetHolidayRestriction( void ) const { return m_pszHolidayRestriction; } + int GetVisionFilterFlags( void ) const { return m_nVisionFilterFlags; } + int GetSubType( void ) const { return m_iSubType; } + item_capabilities_t GetCapabilities( void ) const { return m_iCapabilities; } + int GetArmoryRemap( void ) const { return m_iArmoryRemap; } + int GetStoreRemap( void ) const { return m_iStoreRemap; } + item_definition_index_t GetSetItemRemap() const { return m_unSetItemRemapDefIndex; } // what def index do we consider ourself for purposes of determining "is an item equipped that satisfies this set slot?" (ie., Festive Huntsman -> Huntsman); default is to point to itself + const char *GetXifierRemapClass() const { return m_pszXifierRemapClass; } + const char *GetBaseFunctionalItemName() const { return m_pszBaseFunctionalItemName; } + const char *GetParticleSuffix() const { return m_pszParticleSuffix; } + + const CEconItemSetDefinition *GetItemSetDefinition( void ) const { return m_pItemSetDef; } + void SetItemSetDefinition( const CEconItemSetDefinition *pItemSetDef ) { Assert( !m_pItemSetDef ); m_pItemSetDef = pItemSetDef; } + + const CEconItemCollectionDefinition *GetItemCollectionDefinition( void ) const { return m_pItemCollectionDef; } + void SetItemCollectionDefinition( const CEconItemCollectionDefinition *pItemCollectionDef ) { Assert( !m_pItemCollectionDef ); m_pItemCollectionDef = pItemCollectionDef; } + + CEconItemPaintKitDefinition *GetCustomPainkKitDefinition( void ) const { return m_pItemPaintKitDef; } + void SetItemPaintKitDefinition( CEconItemPaintKitDefinition *pItemPaintKitDef ) { Assert( !m_pItemPaintKitDef ); m_pItemPaintKitDef = pItemPaintKitDef; } + + perteamvisuals_t *GetPerTeamVisual( int iTeam ) const { return m_PerTeamVisuals[iTeam]; } + + bool IsTool() const { return m_bIsTool; } + const IEconTool *GetEconTool() const { return m_pTool; } + template < class T > + const T *GetTypedEconTool() const { return dynamic_cast<const T *>( GetEconTool() ); } + + const bundleinfo_t *GetBundleInfo( void ) const { return m_BundleInfo; } + virtual int GetBundleItemCount( void ) const { return m_BundleInfo ? m_BundleInfo->vecItemDefs.Count() : 0; } + virtual int GetBundleItem( int iIndex ) const { return m_BundleInfo ? m_BundleInfo->vecItemDefs[iIndex]->GetDefinitionIndex() : -1; } + + // Is this item contained in any bundles? GetContainingBundles() gets the CEconItemDefinitions for those bundles. + const CUtlVector< const CEconItemDefinition * > &GetContainingBundles() const { return m_vecContainingBundleItemDefs; } + uint32 GetContainingBundleCount() const { return m_vecContainingBundleItemDefs.Count(); } + + void AddSteamWorkshopContributor( uint32 unAccountID ) { if ( m_vecSteamWorkshopContributors.InvalidIndex() == m_vecSteamWorkshopContributors.Find( unAccountID ) ) { m_vecSteamWorkshopContributors.AddToTail( unAccountID ); } } + const CUtlVector< uint32 > &GetSteamWorkshopContributors() const { return m_vecSteamWorkshopContributors; } + bool BIsSteamWorkshopItem() const { return m_vecSteamWorkshopContributors.Count() > 0; } + + const char *GetIconClassname( void ) const { return m_pszItemIconClassname; } + const char *GetLogClassname( void ) const { return m_pszItemLogClassname; } + const char *GetInventoryModel( void ) const { return m_pszInventoryModel; } + const char *GetInventoryImage( void ) const { return m_pszInventoryImage; } + const char *GetInventoryOverlayImage( int idx ) const { if ( m_pszInventoryOverlayImages.IsValidIndex( idx ) ) return m_pszInventoryOverlayImages[idx]; else return NULL; } + int GetInventoryOverlayImageCount( void ) const { return m_pszInventoryOverlayImages.Count(); } + int GetInspectPanelDistance( void ) const { return m_iInspectPanelDistance; } + const char *GetIconURLSmall() const { return GetIconURL( "s" ); } // Plain small + const char *GetIconURLLarge() const { return GetIconURL( "l" ); } // Plain large + void SetIconURL( const char* pszKey, const char *szURL ) { m_pDictIcons->Insert( pszKey, CUtlString( szURL ) ); } + const char *GetIconURL( const char* pszKey ) const; + const char *GetBasePlayerDisplayModel() const { return m_pszBaseDisplayModel; } + int GetDefaultSkin() const { return m_iDefaultSkin; } + const char *GetWorldDisplayModel() const { return m_pszWorldDisplayModel; } + const char *GetCollectionReference() const { return m_pszCollectionReference; } + + // Some weapons need a custom model for icon generation. If this value is not present, the world model is used. + virtual const char *GetIconDisplayModel() const; + + const char *GetExtraWearableModel( void ) const { return m_pszWorldExtraWearableModel; } + const char *GetExtraWearableViewModel( void ) const { return m_pszWorldExtraWearableViewModel; } + const char *GetVisionFilteredDisplayModel() const { return m_pszVisionFilteredDisplayModel; } + const char *GetItemDesc( void ) const { return m_pszItemDesc; } + const char *GetArmoryDescString( void ) const { return m_pszArmoryDesc; } + RTime32 GetExpirationDate( void ) const { return m_rtExpiration; } + bool ShouldShowInArmory( void ) const { return m_bShouldShowInArmory; } + bool IsActingAsAWearable( void ) const { return m_bActAsWearable; } + bool IsActingAsAWeapon( void ) const { return m_bActAsWeapon; } + bool GetHideBodyGroupsDeployedOnly( void ) const { return m_bHideBodyGroupsDeployedOnly; } + bool IsPackBundle( void ) const { return m_bIsPackBundle; } + bool IsPackItem( void ) const { return m_bIsPackItem; } + CEconItemDefinition *GetOwningPackBundle() { return m_pOwningPackBundle; } + const CEconItemDefinition *GetOwningPackBundle() const { return m_pOwningPackBundle; } + const char *GetDatabaseAuditTableName( void ) const { return m_pszDatabaseAuditTable; } + + void SetIsPackItem( bool bIsPackItem ) { m_bIsPackItem = bIsPackItem; } + + equip_region_mask_t GetEquipRegionMask( void ) const { return m_unEquipRegionMask; } + equip_region_mask_t GetEquipRegionConflictMask( void ) const { return m_unEquipRegionConflictMask; } + + // Dynamic modification during gameplay + void SetAllowedInMatch( bool bAllowed ) { m_bAllowedInThisMatch = bAllowed; } + void SetHasBeenLoaded( bool bLoaded ) { m_bHasBeenLoaded = bLoaded; } + + // Generate and return a random level according to whatever leveling curve this definition uses. + uint32 RollItemLevel( void ) const; + + const char *GetFirstSaleDate( void ) const; + + void IterateAttributes( class IEconItemAttributeIterator *pIterator ) const; + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + // Visuals + // Attached models + int GetNumAttachedModels( int iTeam ) const; + attachedmodel_t *GetAttachedModelData( int iTeam, int iIdx ) const; + + int GetNumAttachedModelsFestivized( int iTeam ) const; + attachedmodel_t *GetAttachedModelDataFestivized( int iTeam, int iIdx ) const; + + // Attached particle systems + int GetNumAttachedParticles( int iTeam ) const; + attachedparticlesystem_t *GetAttachedParticleData( int iTeam, int iIdx ) const; + // Activities + int GetNumPlaybackActivities( int iTeam ) const; + activity_on_wearable_t *GetPlaybackActivityData( int iTeam, int iIdx ) const; + // Animations + int GetNumAnimations( int iTeam ) const; + animation_on_wearable_t *GetAnimationData( int iTeam, int iIdx ) const; + // Animation Overrides + Activity GetActivityOverride( int iTeam, Activity baseAct ) const; + const char *GetActivityOverride( int iTeam, const char *pszActivity ) const; + const char *GetReplacementForActivityOverride( int iTeam, Activity baseAct ) const; + // Should the content (meshes, etc.) for this be streamed or preloaded? + virtual bool IsContentStreamable() const; +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + + // FX Overrides + const char *GetMuzzleFlash( int iTeam ) const; + const char *GetTracerEffect( int iTeam ) const; + const char *GetParticleEffect( int iTeam ) const; + // Materials + const char *GetMaterialOverride( int iTeam ) const; + // Sounds + const char *GetCustomSound( int iTeam, int iSound ) const; + const char *GetWeaponReplacementSound( int iTeam, /*WeaponSound_t*/ int iSound ) const; + // Bodygroups + int GetHiddenParentBodygroup( int iTeam ) const; + int GetNumModifiedBodyGroups( int iTeam ) const; + const char* GetModifiedBodyGroup( int iTeam, int i, int& body ) const; + bool UsesPerClassBodygroups( int iTeam ) const; + int GetNumCodeControlledBodyGroups( int iTeam ) const; + const char* GetCodeControlledBodyGroup( int iIteam, int i, struct codecontrolledbodygroupdata_t &ccbgd ) const; + + style_index_t GetNumStyles() const; + style_index_t GetNumSelectableStyles() const; + const CEconStyleInfo *GetStyleInfo( style_index_t unStyle ) const; + + int GetViewmodelBodygroupOverride( int iTeam ) const; + int GetViewmodelBodygroupStateOverride( int iTeam ) const; + int GetWorldmodelBodygroupOverride( int iTeam ) const; + int GetWorldmodelBodygroupStateOverride( int iTeam ) const; + + int GetPopularitySeed() const { return m_nPopularitySeed; } + + bool HasEconTag( econ_tag_handle_t tag ) const { return m_vecTags.IsValidIndex( m_vecTags.Find( tag ) ); } + + bool BValidForShuffle( void ) const { return m_bValidForShuffle; } + bool BValidForSelfMade( void ) const { return m_bValidForSelfMade; } + +#ifdef GC_DLL +private: + MUST_CHECK_RETURN bool BInitializeEconItemGenerators( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ); + +public: + // If this returns true, all relevant property generators were applied to the item instance + // passed in. If this returns false, some or none of the generators may have been applied, + // but there are no guarantees about the item state. + MUST_CHECK_RETURN bool BApplyPropertyGenerators( CEconItem *pItem ) const; + + const CUtlVector<econ_tag_handle_t>& GetEconTags() const { return m_vecTags; } // meant for internal/debug use only, not for runtime iteration + const CUtlVector<econ_item_payment_rule_t>& GetPaymentRules() const { return m_vecPaymentRules; } + +private: + int AddPaymentRule( const econ_item_payment_rule_t& newRule ); // returns which payment rule number was just created +public: +#endif // GC_DLL + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + int GetStyleSkin( style_index_t unStyle, int iTeam, bool bViewmodel ) const; + const char* GetStyleInventoryImage( style_index_t unStyle ) const; + int GetBestVisualTeamData( int iTeam ) const; +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + VALIDATE_SCOPE(); + ValidateObj( m_vecStaticAttributes ); + ValidatePtr( m_pKVItem ); + ValidatePtr( m_pProxyCriteria ); + } +#endif // DBGFLAG_VALIDATE + + +private: + // Pointer to the raw KeyValue definition of the item + KeyValues * m_pKVItem; + + // Required values from m_pKVItem: + + // The number used to refer to this definition in the DB + item_definition_index_t m_nDefIndex; + + // False if this definition has been turned off and we're not using it to generate items + bool m_bEnabled; + + // These values specify the range of item levels that an item based off this definition can be generated within. + uint8 m_unMinItemLevel; + uint8 m_unMaxItemLevel; + + // This specifies an item quality that items from this definition must be set to. Used mostly to specify unique item definitions. + uint8 m_nItemQuality; + uint8 m_nForcedItemQuality; + uint8 m_nItemRarity; + + // Default drop quantity + uint16 m_nDefaultDropQuantity; + + // Item Series + uint8 m_unItemSeries; + + // Static attributes (ones that are always on these items) + CUtlVector<static_attrib_t> m_vecStaticAttributes; + + // Seeds the popular item list with this number of the item when the list is reset. + uint8 m_nPopularitySeed; + + // --------------------------------------------- + // Display related data + // --------------------------------------------- + // The base name of this item. i.e. "The Kritz-Krieg". + const char *m_pszItemBaseName; + bool m_bProperName; // If set, the name will have "The" prepended to it, unless it's got a non-unique quality + // in which case it'll have "A" prepended to the quality. i.e. A Community Kritzkrieg + + // The base type of this item. i.e. "Rocket Launcher" or "Shotgun". + // This is often the same as the base name, but not always. + const char *m_pszItemTypeName; + + // The item's non-attribute description. + const char *m_pszItemDesc; + + // expiration time + RTime32 m_rtExpiration; + + // The .mdl file used for this item when it's displayed in inventory-style boxes. + const char *m_pszInventoryModel; + // Alternatively, the image used for this item when it's displayed in inventory-style boxes. If specified, it's used over the model. + const char *m_pszInventoryImage; + // An optional image that's overlayed over the top of the base inventory image. It'll be RGB colored by the tint color of the item. + CUtlVector<const char*> m_pszInventoryOverlayImages; + int m_iInventoryImagePosition[2]; + int m_iInventoryImageSize[2]; + int m_iInspectPanelDistance; + + const char *m_pszBaseDisplayModel; + int m_iDefaultSkin; + bool m_bLoadOnDemand; + bool m_bHasBeenLoaded; + + bool m_bHideBodyGroupsDeployedOnly; + + // The .mdl file used for the world view. + // This is inferior to using a c_model, but because the geometry of the sticky bomb launcher's + // world model is significantly different from the view model the demoman pack requires + // using two separate models for now. + const char *m_pszWorldDisplayModel; + const char *m_pszWorldExtraWearableModel; // Some weapons attach an extra wearable item to the player + const char *m_pszWorldExtraWearableViewModel; // Some weapons attach an extra wearable view model item to the player + const char *m_pszVisionFilteredDisplayModel; // Some weapons display differently depending on the viewer's filters + + const char *m_pszCollectionReference; // Reference a colletion + + // If set, we use the base hands model for a viewmodel, and bonemerge the above player model + bool m_bAttachToHands; + bool m_bAttachToHandsVMOnly; + + // If set, we will force the view model to render flipped. Good for models built left handed. + bool m_bFlipViewModel; + + // This is a wearable that sits in a non-wearable loadout slot + bool m_bActAsWearable; + + // This is a weapon that sits in a wearable slot (Action) + bool m_bActAsWeapon; + + // Is this Item a tool + bool m_bIsTool; + + // The set this item is a member of + const CEconItemSetDefinition *m_pItemSetDef; + const CEconItemCollectionDefinition *m_pItemCollectionDef; + + CEconItemPaintKitDefinition *m_pItemPaintKitDef; + + // A list of per-team visual data used to modify base model for visual recognition + perteamvisuals_t *m_PerTeamVisuals[TEAM_VISUAL_SECTIONS]; + + // Optional override for specifying a custom shell ejection model + const char *m_pszBrassModelOverride; + + IEconTool *m_pTool; + bundleinfo_t *m_BundleInfo; + item_capabilities_t m_iCapabilities; + +#ifdef TF_CLIENT_DLL + uint32 m_unNumConcreteItems; // This is the number of items that will actually end up in a user's inventory - this can be 0 for some items (e.g. map stamps in TF), 1 for a "regular" item, or many for bundles, etc. +#endif // TF_CLIENT_DLL + + CUtlDict< CUtlString >* m_pDictIcons; + + // --------------------------------------------- + // Creation related data + // --------------------------------------------- + // The entity classname for this item. + const char *m_pszItemClassname; + + // The entity name that will be displayed in log files. + const char *m_pszItemLogClassname; + + // The name of the icon used in the death notices. + const char *m_pszItemIconClassname; + + // This is the script file name of this definition. Used to generate items by script name. + const char *m_pszDefinitionName; + + // This is used for auditing purposes + const char *m_pszDatabaseAuditTable; + + bool m_bHidden; + bool m_bShouldShowInArmory; + bool m_bBaseItem; + bool m_bImported; + + // A pack bundle is a bundle that contains items that are not for sale individually + bool m_bIsPackBundle; + + // A pack item is an item which is not for sale individually and is only for sale as part of a pack bundle. A 'regular' bundle can only include a pack bundle by explicitly including all of the pack bundle's items individually. + // If this pointer is non-NULL, this item is considered to be a pack item (see CEconItemDefinition::IsPackItem()). + CEconItemDefinition *m_pOwningPackBundle; + bool m_bIsPackItem; + + // Contains information on how to describe items with this attribute in the Armory + const char *m_pszArmoryDesc; + + // Temporary(?) solution to allow xifiers to work on botkiller and festive variants of weapons + const char *m_pszXifierRemapClass; + + // Base item name -- used for grouping weapon functionality + const char *m_pszBaseFunctionalItemName; + + // For particle effects that have derivatives, what is the suffix for this item + const char *m_pszParticleSuffix; + + // --------------------------------------------- + // Remapping data for armory/store + // --------------------------------------------- + int m_iArmoryRemap; + int m_iStoreRemap; + const char *m_pszArmoryRemap; + const char *m_pszStoreRemap; + + // --------------------------------------------- + // Crafting related data + // --------------------------------------------- + const char *m_pszClassToken; + const char *m_pszSlotToken; + + // --------------------------------------------- + // Gameplay related data + // --------------------------------------------- + // How to behave when the player wearing the item dies. + int m_iDropType; + + // Holiday restriction. Item only has an appearance when the holiday is in effect. + const char *m_pszHolidayRestriction; + + // Meet the pyro makes some items invisible unless you're wearing Pyro Goggles + int m_nVisionFilterFlags; + + // Temporary. Revisit this in the engineer update. Enables an additional buildable. + int m_iSubType; + + // Whitelist support for tournament mode + bool m_bAllowedInThisMatch; + + equip_region_mask_t m_unEquipRegionMask; // which equip regions does this item cover directly + equip_region_mask_t m_unEquipRegionConflictMask; // which equip regions does equipping this item prevent from having something in them + + item_definition_index_t m_unSetItemRemapDefIndex; // reference to the definition index we want to consider this item for set matching purposes; see GetSetItemRemap() + +#ifdef GC_DLL + CUtlVector<const IEconItemPropertyGenerator *> m_vecPropertyGenerators; + CUtlVector<econ_item_payment_rule_t> m_vecPaymentRules; +#endif // GC_DLL + + // False if this definition is not allowed to be part of a shuffled crate's contents + bool m_bValidForShuffle; + + // False if this definition should not grant self-made items + bool m_bValidForSelfMade; + +protected: + // Protected to allow subclasses to add/remove game-specific tags. + CUtlVector<econ_tag_handle_t> m_vecTags; + CUtlVector<const CEconItemDefinition *> m_vecContainingBundleItemDefs; // Item definition indices for any bundles which contain this item + CUtlVector<uint32> m_vecSteamWorkshopContributors; + + friend class CEconItemSchema; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline style_index_t CEconItemDefinition::GetNumStyles() const +{ + const perteamvisuals_t *pVisData = GetPerTeamVisual( 0 ); + + if ( !pVisData ) + return 0; + + // Bad things will happen if we ever get more styles than will fit in our + // style index type. Not Very Bad things, but bad things. Mostly we'll fail + // to iterate over all our styles. + return pVisData->m_Styles.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Similar to GetNumStyles, but only the selectable styles +//----------------------------------------------------------------------------- +inline style_index_t CEconItemDefinition::GetNumSelectableStyles() const +{ + const perteamvisuals_t *pVisData = GetPerTeamVisual(0); + + if (!pVisData) + return 0; + + style_index_t nCount = 0; + FOR_EACH_VEC( pVisData->m_Styles, i ) + { + if( pVisData->m_Styles[i]->IsSelectable() ) + { + ++nCount; + } + } + + return nCount; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const CEconStyleInfo *CEconItemDefinition::GetStyleInfo( style_index_t unStyle ) const +{ + const perteamvisuals_t *pBaseVisuals = GetPerTeamVisual( 0 ); + if ( !pBaseVisuals || !pBaseVisuals->m_Styles.IsValidIndex( unStyle ) ) + return NULL; + + return pBaseVisuals->m_Styles[unStyle]; +} + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetNumAttachedModels( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_AttachedModels.Count(); +#else + return 0; +#endif +} +//----------------------------------------------------------------------------- +inline attachedmodel_t *CEconItemDefinition::GetAttachedModelData( int iTeam, int iIdx ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + perteamvisuals_t *pVisuals = GetPerTeamVisual(iTeam); + Assert( pVisuals ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !pVisuals ) + return NULL; + + Assert( iIdx < pVisuals->m_AttachedModels.Count() ); + if ( iIdx >= pVisuals->m_AttachedModels.Count() ) + return NULL; + + return &pVisuals->m_AttachedModels[iIdx]; +#else + return NULL; +#endif +} +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetNumAttachedModelsFestivized( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + perteamvisuals_t *pVisuals = GetPerTeamVisual(iTeam); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !pVisuals ) + return 0; + return pVisuals->m_AttachedModelsFestive.Count(); +#else + return 0; +#endif +} +//----------------------------------------------------------------------------- +inline attachedmodel_t *CEconItemDefinition::GetAttachedModelDataFestivized( int iTeam, int iIdx ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + perteamvisuals_t *pVisuals = GetPerTeamVisual(iTeam); + Assert( pVisuals ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !pVisuals ) + return NULL; + + Assert( iIdx < pVisuals->m_AttachedModelsFestive.Count() ); + if ( iIdx >= pVisuals->m_AttachedModelsFestive.Count() ) + return NULL; + + return &pVisuals->m_AttachedModelsFestive[iIdx]; +#else + return NULL; +#endif +} +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetNumPlaybackActivities( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_Activities.Count(); +#else + return 0; +#endif +} + +inline activity_on_wearable_t *CEconItemDefinition::GetPlaybackActivityData( int iTeam, int iIdx ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + Assert( GetPerTeamVisual(iTeam) ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + + Assert( iIdx < GetPerTeamVisual(iTeam)->m_Activities.Count() ); + if ( iIdx >= GetPerTeamVisual(iTeam)->m_Activities.Count() ) + return NULL; + + return &GetPerTeamVisual(iTeam)->m_Activities[iIdx]; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetNumAnimations( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_Animations.Count(); +#else + return 0; +#endif +} +inline animation_on_wearable_t *CEconItemDefinition::GetAnimationData( int iTeam, int iIdx ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + Assert( GetPerTeamVisual(iTeam) ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + + Assert( iIdx < GetPerTeamVisual(iTeam)->m_Animations.Count() ); + if ( iIdx >= GetPerTeamVisual(iTeam)->m_Animations.Count() ) + return NULL; + + return &GetPerTeamVisual(iTeam)->m_Animations[iIdx]; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetNumAttachedParticles( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_AttachedParticles.Count(); +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline attachedparticlesystem_t *CEconItemDefinition::GetAttachedParticleData( int iTeam, int iIdx ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + Assert( GetPerTeamVisual(iTeam) ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + + Assert( iIdx < GetPerTeamVisual(iTeam)->m_AttachedParticles.Count() ); + if ( iIdx >= GetPerTeamVisual(iTeam)->m_AttachedParticles.Count() ) + return NULL; + + return &GetPerTeamVisual(iTeam)->m_AttachedParticles[iIdx]; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char *CEconItemDefinition::GetMaterialOverride( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + return GetPerTeamVisual(iTeam)->pszMaterialOverride; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char *CEconItemDefinition::GetMuzzleFlash( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + return GetPerTeamVisual(iTeam)->pszMuzzleFlash; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char *CEconItemDefinition::GetTracerEffect( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + return GetPerTeamVisual(iTeam)->pszTracerEffect; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char *CEconItemDefinition::GetParticleEffect( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + return GetPerTeamVisual(iTeam)->pszParticleEffect; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetHiddenParentBodygroup( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return -1; + return GetPerTeamVisual(iTeam)->iHideParentBodyGroup; +#else + return -1; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetNumModifiedBodyGroups( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return -1; + return GetPerTeamVisual(iTeam)->m_Maps.m_ModifiedBodyGroupNames.Count(); +#else + return -1; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char* CEconItemDefinition::GetModifiedBodyGroup( int iTeam, int i, int& body ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + body = GetPerTeamVisual(iTeam)->m_Maps.m_ModifiedBodyGroupNames[i]; + return GetPerTeamVisual(iTeam)->m_Maps.m_ModifiedBodyGroupNames.Key(i); +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetNumCodeControlledBodyGroups( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return -1; + return GetPerTeamVisual(iTeam)->m_Maps.m_CodeControlledBodyGroupNames.Count(); +#else + return -1; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char* CEconItemDefinition::GetCodeControlledBodyGroup( int iTeam, int i, codecontrolledbodygroupdata_t &ccbgd ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + ccbgd = GetPerTeamVisual(iTeam)->m_Maps.m_CodeControlledBodyGroupNames[i]; + return GetPerTeamVisual(iTeam)->m_Maps.m_CodeControlledBodyGroupNames.Key(i); +#else + return NULL; +#endif +} + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetStyleSkin( style_index_t unStyle, int iTeam, bool bViewmodel ) const +{ + const CEconStyleInfo *pStyle = GetStyleInfo( unStyle ); + + // Return our skin if we have a style or our default skin of -1 otherwise. + return pStyle + ? pStyle->GetSkin( iTeam, bViewmodel ) + : GetDefaultSkin(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char* CEconItemDefinition::GetStyleInventoryImage( style_index_t unStyle ) const +{ + const CEconStyleInfo *pStyle = GetStyleInfo( unStyle ); + + return pStyle ? pStyle->GetInventoryImage() : NULL; +} + +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetViewmodelBodygroupOverride( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_iViewModelBodyGroupOverride; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetViewmodelBodygroupStateOverride( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_iViewModelBodyGroupStateOverride; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetWorldmodelBodygroupOverride( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_iWorldModelBodyGroupOverride; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetWorldmodelBodygroupStateOverride( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return 0; + return GetPerTeamVisual(iTeam)->m_iWorldModelBodyGroupStateOverride; +#else + return 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline bool CEconItemDefinition::UsesPerClassBodygroups( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return false; + return GetPerTeamVisual(iTeam)->bUsePerClassBodygroups; +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char *CEconItemDefinition::GetCustomSound( int iTeam, int iSound ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + if ( iSound < 0 || iSound >= MAX_VISUALS_CUSTOM_SOUNDS ) + return NULL; + return GetPerTeamVisual(iTeam)->pszCustomSounds[iSound]; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline const char *CEconItemDefinition::GetWeaponReplacementSound( int iTeam, /* WeaponSound_t */ int iSound ) const +{ +#ifndef CSTRIKE_DLL + iTeam = GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS || !GetPerTeamVisual(iTeam) ) + return NULL; + if ( iSound < 0 || iSound >= NUM_SHOOT_SOUND_TYPES ) + return NULL; + return GetPerTeamVisual(iTeam)->pszWeaponSoundReplacements[iSound]; +#else + return NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline int CEconItemDefinition::GetBestVisualTeamData( int iTeam ) const +{ +#ifndef CSTRIKE_DLL + Assert( iTeam >= 0 && iTeam < TEAM_VISUAL_SECTIONS ); + // If we don't have data for the specified team, try to fall back to the base + // if ( !GetStaticData() ) + // return 0; + if ( (iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS) || (iTeam > 0 && !GetPerTeamVisual(iTeam)) ) + return 0; + return iTeam; +#else + return 0; +#endif +} +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + +//----------------------------------------------------------------------------- +// CTimedItemRewardDefinition +// Describes a periodic item reward +//----------------------------------------------------------------------------- +class CTimedItemRewardDefinition +{ +public: + CTimedItemRewardDefinition( void ); + CTimedItemRewardDefinition( const CTimedItemRewardDefinition &that ); + CTimedItemRewardDefinition &operator=( const CTimedItemRewardDefinition& rhs ); + + ~CTimedItemRewardDefinition( void ) { } + + bool BInitFromKV( KeyValues *pKVTimedReward, CUtlVector<CUtlString> *pVecErrors = NULL ); + + uint32 GetRandomFrequency( void ) const { return RandomFloat( m_unMinFreq, m_unMaxFreq ); } + uint32 GetMinFrequency( void ) const { return m_unMinFreq; } + uint32 GetMaxFrequency( void ) const { return m_unMaxFreq; } + float GetChance( void ) const { return m_flChance; } + const CItemSelectionCriteria &GetCriteria( void ) const { return m_criteria; } + const CEconLootListDefinition *GetLootList( void ) const { return m_pLootList; } + + bool BHasRequiredItem() const { return m_iRequiredItemDef != INVALID_ITEM_DEF_INDEX; } + item_definition_index_t GetRequiredItem() const { return m_iRequiredItemDef; } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + VALIDATE_SCOPE(); + ValidateObj( m_criteria ); + } +#endif // DBGFLAG_VALIDATE + +private: + // Frequency of how often the item is awarded + uint32 m_unMinFreq; + uint32 m_unMaxFreq; + + // The chance, between 0 and 1, that the item is rewarded + float m_flChance; + + // The criteria to use to select the item to reward + CItemSelectionCriteria m_criteria; + // Alternatively, the loot_list to use instead + const CEconLootListDefinition *m_pLootList; + + item_definition_index_t m_iRequiredItemDef; +}; + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// CExperimentDefinition +//----------------------------------------------------------------------------- +struct experiment_group_t +{ + const char* m_pName; + uint32 m_unNumParticipants; + uint32 m_unMaxParticipants; + KeyValues *m_pKeyValues; +}; + +class CExperimentDefinition +{ +public: + CExperimentDefinition( void ); + ~CExperimentDefinition( void ); + + bool BInitFromKV( KeyValues *pKVExperiment, CUtlVector<CUtlString> *pVecErrors = NULL ); + + CUtlVector< experiment_group_t > &GetGroups() { return m_vecGroups; } + + uint32 GetID() const { return m_unExperimentID; } + const char *GetName( void ) const { return m_pKeyValues->GetString( "name", "unknown" ); } + const char *GetDescription( void ) const { return m_pKeyValues->GetString( "description", "unknown" ); } + + bool IsEnabled() const { return m_bEnabled; } + bool IsFull() const { return m_unNumParticipants >= m_unMaxParticipants; } + + uint32 GetNumParticipants() const { return m_unNumParticipants; } + void SetNumParticipants( uint32 unNumParticipants ) { m_unNumParticipants = unNumParticipants; } + uint32 GetMaxParticipants() const { return m_unMaxParticipants; } + + bool ChooseGroup( uint32 &unGroup ); + +private: + bool m_bEnabled; + uint32 m_unExperimentID; + uint32 m_unNumParticipants; + uint32 m_unMaxParticipants; + KeyValues *m_pKeyValues; + CUtlVector< experiment_group_t > m_vecGroups; +}; +#endif + +//----------------------------------------------------------------------------- +// CItemLevelingDefinition +//----------------------------------------------------------------------------- +class CItemLevelingDefinition +{ +public: + CItemLevelingDefinition( void ); + CItemLevelingDefinition( const CItemLevelingDefinition &that ); + CItemLevelingDefinition &operator=( const CItemLevelingDefinition& rhs ); + + ~CItemLevelingDefinition( void ); + + bool BInitFromKV( KeyValues *pKVItemLevel, const char *pszLevelBlockName, CUtlVector<CUtlString> *pVecErrors = NULL ); + + uint32 GetLevel( void ) const { return m_unLevel; } + uint32 GetRequiredScore( void ) const { return m_unRequiredScore; } + const char *GetNameLocalizationKey( void ) const { return m_pszLocalizedName_LocalStorage; } + +private: + uint32 m_unLevel; + uint32 m_unRequiredScore; + char *m_pszLocalizedName_LocalStorage; +}; + +//----------------------------------------------------------------------------- +// AchievementAward_t +// Holds the item to give away and the Data value to audit it with ( for cross +// game achievements) +//----------------------------------------------------------------------------- +struct AchievementAward_t +{ + AchievementAward_t( const AchievementAward_t & rhs ) + : m_sNativeName( rhs.m_sNativeName ), + m_unSourceAppId( rhs.m_unSourceAppId ), + m_unAuditData( rhs.m_unAuditData ) + { + m_vecDefIndex.CopyArray( rhs.m_vecDefIndex.Base(), rhs.m_vecDefIndex.Count() ); + } + AchievementAward_t( ) {} + + CUtlString m_sNativeName; + AppId_t m_unSourceAppId; + uint32 m_unAuditData; + CUtlVector<uint16> m_vecDefIndex; +}; + +enum eTimedRewardType +{ + kTimedRewards_RegularDrop, + kTimedRewards_SupplyCrate, + kTimedRewards_FreeTrialDrop, + kTimedRewards_RecipeDrop, + kTimedRewards_EventDrop02, + kNumTimedRewards +}; + +struct kill_eater_score_type_t +{ + const char *m_pszTypeString; + const char *m_pszLevelBlockName; + bool m_bAllowBotVictims; // if true, we don't check for a valid Steam ID on the client before sending or a valid session on the GC before incrementing +#ifdef GC_DLL + bool m_bGCUpdateOnly; + bool m_AllowIncrementValues; // if true, clients are allowed to send up the amount to increment by (ie., "did 100 damage") rather than implicitly assuming a value of 1 + bool m_bIsBaseKillType; // if true, when clients send up a notification of this type we'll also look for other relevant things on the GC, like whether the victim was a friend, etc. +#endif +}; + +// Index-to-string table, currently used for attribute value string lookups. +struct schema_string_table_entry_t +{ + int m_iIndex; + const char *m_pszStr; +}; + +//----------------------------------------------------------------------------- +// CForeignAppImports +// Defines the way a single foreign app's items are mapped into this app +//----------------------------------------------------------------------------- + +class CForeignAppImports +{ +public: + CForeignAppImports() : m_mapDefinitions( DefLessFunc( uint16 ) ) {} + + void AddMapping( uint16 unForeignDefIndex, const CEconItemDefinition *pDefn ); + const CEconItemDefinition *FindMapping( uint16 unForeignDefIndex ) const; + +private: + CUtlMap< uint16, const CEconItemDefinition *> m_mapDefinitions; +}; + +//----------------------------------------------------------------------------- +// ISchemaAttributeType +//----------------------------------------------------------------------------- +#ifdef GC_DLL +namespace GCSDK +{ + class CColumnSet; + class CRecordBase; + class CWebAPIValues; +}; +#endif // GC_DLL + +// ISchemaAttributeType is the base interface for a "type" of attribute, where "type" is defined as +// "something that describes the memory layout, the DB layout, how to convert between them, etc.". +// Most of the low-level work done with attributes, including DB reading/writing, packing/unpacking +// for wire traffic, and other leaf code works exclusively through this interface. +// +// The class hierarchy looks like: +// +// ISchemaAttributeTypeBase< TAttribInMemoryType >: +// +// This describes a specific in-memory format for an attribute, without any association to +// a particular DB, wire format, etc. We can't template the base class because it's an +// interface. This implements about half of ISchemaAttributeType and has its own mini +// interface consisting of ConvertTypedValueToByteStream() and ConvertByteStreamToTypedValue(), +// both of which do work on statically-typed values that don't exist at higher levels. +// +// CSchemaAttributeTypeBase< TAttribSchType, TAttribInMemoryType >: +// +// This handles the schema-related functions on ISchemaAttributeType. These exist at a lower +// inheritance level than ISchemaAttributeTypeBase to allow code that needs to work type-safely +// on attributes in memory, but that doesn't know or need to know anything about databases, +// to exist. Examples of this include code that calls CEconItem::SetDynamicAttributeValue<T>(). +// +// Individual implementations of custom attribute type start making sense immediately as +// subclasses of CSchemaAttributeTypeBase, for example CSchemaAttributeType_Default, which +// implements all of the old, untyped attribute system logic. +// +// CSchemaAttributeTypeProtobufBase< TAttribSchType, TProtobufValueType > +// +// An easy way of automating most of the work for making a new attribute type is to have +// the in-memory format be a protobuf object, allowing reflection, automatic network support, +// etc. +// +// Creating a new custom protobuf attribute consists of three steps: +// +// - create a new DB table that will hold your attribute data. This needs an itemid_t-sized item ID +// column named "ItemID", an attrib_definition_index_t-sized definition index column named "AttrDefIndex", +// and then whatever data you want to store. +// +// - create a new protobuf message type that will hold your custom attribute contents. This exists +// on the client and the GC in the same format. +// +// - implement a subclass of CSchemaAttributeTypeProtobufBase<>, for example: +// +// class CSchemaAttributeType_StrangeScore : public CSchemaAttributeTypeProtobufBase< GC_SCH_REFERENCE( CSchItemAttributeStrangeScore ) CAttribute_StrangeScore > +// { +// virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const OVERRIDE; +// virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const OVERRIDE; +// }; +// +// Implement these two GC-only functions to convert from the in-memory format to the DB format and +// vice versa and you're good to go. +// +// - register the new type in CEconItemSchema::BInitAttributeTypes(). +// +// If the attribute type can't be silently converted to an already-existing attribute value type, a few other +// places will also fail to compile -- things like typed iteration, or compile-time type checking. +// +// Functions that start with "Convert" change the format of an attribute value (in a type-safe way wherever +// possible), copying the value from one of the passed-in parameters to the other. Functions that start with +// "Load" do a format conversion, but also add the post-conversion value to the passed-in CEconItem. This +// comes up most often when generating new items, either from the DB (LoadSch), the network (LoadByteSteam), +// or creation of a new item on the GC (LoadOrGenerate). +class ISchemaAttributeType +{ +public: + virtual ~ISchemaAttributeType() { } + + // Returns a unique integer describing the C++-in-memory-layout type used by this attribute type. + // For example, something that stores "int" might return 0 and "CSomeFancyWideAttributeType" might + // return 1. The actual values don't matter and can even differ between different runs of the game/GC. + // The only important thing is that during a single run the value for a single type is consistent. + virtual unsigned int GetTypeUniqueIdentifier() const = 0; + +#ifdef GC_DLL + // What's the whole column set (and associated DB table) that this attribute uses? Meant to be + // implemented by subclasses that have DB type information. + virtual const GCSDK::CColumnSet& GetFullColumnSet() const = 0; + + // Create an instance of a schema row. Mananging the memory is the responsibility of the caller. + // Meant to be implemented by subclasses that have DB type information. + virtual GCSDK::CRecordBase *CreateTypedSchRecord() const = 0; + + // ... + virtual bool BAssetClassExportedAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value ) const { return true; } + + // Prepare a DB row describing an instance of this attribute for writing. + virtual void ConvertEconAttributeValueToSch( itemid_t unItemId, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value, GCSDK::CRecordBase *out_pSchRecord ) const = 0; + + // We have a row read from the database and an item to add it as an attribute for. This + // does the opposite work of ConvertEconAttributeValueToSch() and also adds it to the CEconItem. + virtual void LoadSchToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const GCSDK::CRecordBase *pSchRecord ) const = 0; + + // Have this attribute type either copy the data straight out of the value union, or run the logic + // described by pszCustomLogicDesc to generate a new value. Either way, some correctly-typed data + // will wind up in an attribute on the target item. This is intended to call through to LoadEconAttributeValue() + // to do the actual assignment. This is only accessible on the GC. + virtual void LoadOrGenerateEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount ) const = 0; + + virtual void GenerateEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const static_attrib_t& staticAttrib, const CEconGameAccount *pGameAccount, attribute_data_union_t* out_pValue ) const = 0; +#endif // GC_DLL + + // Have this attribute type copy the data out of the value union and type-copy it onto the item. This + // is accessible on clients as well as the GC. + virtual void LoadEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const union attribute_data_union_t& value ) const = 0; + + // ... + virtual void ConvertEconAttributeValueToByteStream( const union attribute_data_union_t& value, std::string *out_psBytes ) const = 0; + + // ... + virtual bool BConvertStringToEconAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const char *pszValue, union attribute_data_union_t *out_pValue, bool bEnableTerribleBackwardsCompatibilitySchemaParsingCode = false ) const = 0; + + // ... + virtual void ConvertEconAttributeValueToString( const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value, std::string *out_ps ) const = 0; + + // Used to deserialize a byte stream, probably from an on-wire protobuf message, instead an instance + // of the attribute in memory. See ConvertByteStreamToTypedValue() for example implementation, or + // ConvertTypedValueToByteStream() for an example of the byte-stream generator code. + virtual void LoadByteStreamToEconAttributeValue( CEconItem *pTargetItem, const CEconItemAttributeDefinition *pAttrDef, const std::string& sBytes ) const = 0; + + // Give the subclass a chance to default-initialize a new value. For larger types, this may hit the + // heap. This must be called before otherwise manipulating [out_pValue] through Convert*() functions. + virtual void InitializeNewEconAttributeValue( attribute_data_union_t *out_pValue ) const = 0; + + // Free any heap-allocated memory from this attribute value. Is not responsible for zeroing out + // pointers, etc. + virtual void UnloadEconAttributeValue( union attribute_data_union_t *out_pValue ) const = 0; + + // ... + virtual bool OnIterateAttributeValue( class IEconItemAttributeIterator *pIterator, const CEconItemAttributeDefinition *pAttrDef, const attribute_data_union_t& value ) const = 0; + + // This could also be called "BIsHackyMessyOldAttributeType()". This determines whether the attribute + // can be set at runtime on a CEconItemView instance, whether the gameserver can replicate the value to + // game clients, etc. It really only makes sense for value types 32 bits or smaller. + virtual bool BSupportsGameplayModificationAndNetworking() const { return false; } +}; + +//----------------------------------------------------------------------------- +// CEconItemSchema +// Defines the way econ items can be used in a game +//----------------------------------------------------------------------------- +typedef CUtlDict<CUtlConstString, int> ArmoryStringDict_t; +typedef CUtlDict< CUtlVector<CItemLevelingDefinition> * > LevelBlockDict_t; +typedef CUtlMap<unsigned int, kill_eater_score_type_t> KillEaterScoreMap_t; +typedef CUtlDict< CUtlVector< schema_string_table_entry_t > * > SchemaStringTableDict_t; + +struct attr_type_t +{ + CUtlConstString m_sName; + const ISchemaAttributeType *m_pAttrType; + + attr_type_t( const char *pszName, const ISchemaAttributeType *pAttrType ) + : m_sName( pszName ) + , m_pAttrType( pAttrType ) + { + } +}; + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +class IDelayedSchemaData +{ +public: + virtual ~IDelayedSchemaData() {} + virtual bool InitializeSchema( CEconItemSchema *pItemSchema ) = 0; + +protected: + // Passing '0' as the expected version means "we weren't expecting any version in particular" and will + // skip the sanity checking. + bool InitializeSchemaInternal( CEconItemSchema *pItemSchema, CUtlBuffer& bufRawData, bool bInitAsBinary, uint32 nExpectedVersion ); +}; +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + +class CEconStorePriceSheet; + +class CEconItemSchema +{ +public: + CEconItemSchema( ); + +private: + CEconItemSchema( const CEconItemSchema & rhs ); + CEconItemSchema &operator=( CEconItemSchema & rhs ); + +public: + virtual ~CEconItemSchema( void ) { Reset(); }; + + // Setup & parse in the item data files. + virtual bool BInit( const char *fileName, const char *pathID, CUtlVector<CUtlString> *pVecErrors = NULL ); + bool BInitBinaryBuffer( CUtlBuffer &buffer, CUtlVector<CUtlString> *pVecErrors = NULL ); + bool BInitTextBuffer( CUtlBuffer &buffer, CUtlVector<CUtlString> *pVecErrors = NULL ); +#ifdef GC_DLL + virtual bool DoPostPriceSheetLoadInit( CEconStorePriceSheet *pPriceSheet ); // Called once the price sheet's been loaded +#endif + + uint32 GetVersion() const { return m_unVersion; } + CSHA GetSchemaSHA() const { return m_schemaSHA; } + uint32 GetResetCount() const { return m_unResetCount; } + + // Dump the schema for debug purposes + bool DumpItems ( const char *fileName, const char *pathID = NULL ); + + // Perform the computation used to calculate the schema version + static uint32 CalculateKeyValuesVersion( KeyValues *pKV ); + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + // This function will immediately reinitialize the schema if it's safe to do so, or store off the data + // if it isn't safe to update at the moment. + bool MaybeInitFromBuffer( IDelayedSchemaData *pDelayedSchemaData ); + + // If there is saved schema initialization data, initialize it now. If there is no saved data, this + // will return success. + bool BInitFromDelayedBuffer(); +#endif // defined(CLIENT_DLL) || defined(GAME_DLL) + + // Accessors to the base properties + EEquipType_t GetEquipTypeFromClassIndex( int iClass ) const; + equipped_class_t GetAccountIndex() const { return m_unAccoutClassIndex; } + equipped_class_t GetFirstValidClass() const { return m_unFirstValidClass; } + equipped_class_t GetLastValidClass() const { return m_unLastValidClass; } + bool IsValidClass( equipped_class_t unClass ) { return ( unClass >= m_unFirstValidClass && unClass <= m_unLastValidClass ) || unClass == GetAccountIndex(); } + bool IsValidItemSlot( equipped_slot_t unSlot, equipped_class_t unClass ) const { return IsValidItemSlot( unSlot, unClass == m_unAccoutClassIndex ? EQUIP_TYPE_ACCOUNT : EQUIP_TYPE_CLASS ); } + bool IsValidItemSlot( equipped_slot_t unSlot, EEquipType_t eType ) const + { + return eType == EQUIP_TYPE_ACCOUNT ? unSlot >= m_unFirstValidAccountItemSlot && unSlot <= m_unLastValidAccountItemSlot + : unSlot >= m_unFirstValidClassItemSlot && unSlot <= m_unLastValidClassItemSlot; + } + + enum { kMaxItemPresetCount = 4 }; + uint32 GetNumAllowedItemPresets() const { return kMaxItemPresetCount; } + bool IsValidPreset( equipped_preset_t unPreset ) const { return unPreset <= GetNumAllowedItemPresets(); } + + uint32 GetMinLevel() const { return m_unMinLevel; } + uint32 GetMaxLevel() const { return m_unMaxLevel; } + + // Accessors to the underlying sections + typedef CUtlHashMapLarge<int, CEconItemDefinition*> ItemDefinitionMap_t; + const ItemDefinitionMap_t &GetItemDefinitionMap() const { return m_mapItems; } + + typedef CUtlMap<int, CEconItemDefinition*, int> SortedItemDefinitionMap_t; + const SortedItemDefinitionMap_t &GetSortedItemDefinitionMap() const { return m_mapItemsSorted; } + + typedef CUtlMap<int, CEconItemDefinition*, int> ToolsItemDefinitionMap_t; + const ToolsItemDefinitionMap_t &GetToolsItemDefinitionMap() const { return m_mapToolsItems; } + + typedef CUtlMap<int, CEconItemDefinition*, int> BaseItemDefinitionMap_t; + const BaseItemDefinitionMap_t &GetBaseItemDefinitionMap() const { return m_mapBaseItems; } + + typedef CUtlMap<const char*, CEconLootListDefinition *, int> LootListDefinitionMap_t; + const LootListDefinitionMap_t &GetLootLists() const { return m_mapLootLists; } + + typedef CUtlMap<int, const char*> RevolvingLootListDefinitionMap_t; + const RevolvingLootListDefinitionMap_t &GetRevolvingLootLists() const { return m_mapRevolvingLootLists; } + + typedef CUtlMap<const char*, int> BodygroupStateMap_t; + const BodygroupStateMap_t &GetDefaultBodygroupStateMap() const { return m_mapDefaultBodygroupState; } + + typedef CUtlVector<CEconColorDefinition *> ColorDefinitionsList_t; + + typedef CUtlMap<const char *, KeyValues *, int> PrefabMap_t; + +#ifdef GC_DLL + struct periodic_score_t + { + eEconPeriodicScoreEvents m_eEventType; + bool m_bGCUpdateOnly; // if set, only code that runs on the GC can initiate a change of this stat (ie., counting gifts -> true; bots killed -> false) + uint32 m_unTimePeriodLengthInSeconds; + CEconItemDefinition *m_pRewardItemDefinition; + }; + + typedef CUtlVector<periodic_score_t> PeriodicScoreTypeList_t; +#endif // GC_DLL + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + CEconItemDefinition *GetDefaultItemDefinition() { return m_pDefaultItemDefinition; } + + bool SetupPreviewItemDefinition( KeyValues *pKV ); +#endif + + const CUtlMap<int, CEconItemQualityDefinition, int > &GetQualityDefinitionMap() const { return m_mapQualities; } + const CUtlMap<int, CEconItemAttributeDefinition, int > &GetAttributeDefinitionMap() const { return m_mapAttributes; } + + typedef CUtlMap<int, CEconCraftingRecipeDefinition*, int > RecipeDefinitionMap_t; + const RecipeDefinitionMap_t &GetRecipeDefinitionMap() const { return m_mapRecipes; } + + typedef CUtlMap<const char*, CEconItemSetDefinition*, int > ItemSetMap_t; + const ItemSetMap_t &GetItemSets() const { return m_mapItemSets; } + + typedef CUtlMap<const char*, CEconItemCollectionDefinition*, int > ItemCollectionMap_t; + const ItemCollectionMap_t &GetItemCollections() const { return m_mapItemCollections; } + + typedef CUtlVector< int > ItemCollectionCrateMap_t; + const ItemCollectionCrateMap_t &GetItemCollectionCrates() const { return m_vecItemCollectionCrates; } + + typedef CUtlMap<const char*, CEconItemPaintKitDefinition*, int > ItemPaintKitMap_t; + const ItemPaintKitMap_t &GetItemPaintKits() const { return m_mapItemPaintKits; } + + typedef CUtlMap<const char*, CEconOperationDefinition*, int > OperationDefinitionMap_t; + const OperationDefinitionMap_t &GetOperationDefinitions() const { return m_mapOperationDefinitions; } + + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + const ArmoryStringDict_t &GetArmoryDataItemClasses() const { return m_dictArmoryItemClassesDataStrings; } + const ArmoryStringDict_t &GetArmoryDataItemTypes() const { return m_dictArmoryItemTypesDataStrings; } + const ArmoryStringDict_t &GetArmoryDataItems() const { return m_dictArmoryItemDataStrings; } + const ArmoryStringDict_t &GetArmoryDataAttributes() const { return m_dictArmoryAttributeDataStrings; } +#elif defined(GC_DLL) + CUtlVector< CExperimentDefinition > &GetExperiments() { return m_vecExperiments; } + + const CUtlVector< AppId_t > & GetForeignApps() const { return m_vecForeignApps; } + const CEconItemDefinition *GetAppItemImport( AppId_t unAppID, uint16 usDefIndex ) const; +#endif + + const CTimedItemRewardDefinition* GetTimedReward( eTimedRewardType type ) const; + + const CEconLootListDefinition* GetLootListByName( const char* pListName, int *out_piIndex = NULL ) const; + const CEconLootListDefinition* GetLootListByIndex( int iIdx ) const { return m_mapLootLists.IsValidIndex(iIdx) ? m_mapLootLists[iIdx] : NULL; } + + const CQuestObjectiveDefinition* GetQuestObjectiveByDefIndex( int iIdx ) const; + const CUtlMap<int, CQuestObjectiveDefinition*, int >& GetQuestObjectives() const { return m_mapQuestObjectives; } + + uint8 GetDefaultQuality() const { return AE_UNIQUE; } + + void AssignDefaultBodygroupState( const char *pszBodygroupName, int iValue ); + + equip_region_mask_t GetEquipRegionMaskByName( const char *pRegionName ) const; + + struct EquipRegion + { + CUtlConstString m_sName; + unsigned int m_unBitIndex; // which bit are we claiming ownership over? there might be multiple equip regions with the same bit if we're in a "shared" block + equip_region_mask_t m_unMask; // full region conflict mask + }; + + typedef CUtlVector<EquipRegion> EquipRegionsList_t; + const EquipRegionsList_t& GetEquipRegionsList() const { return m_vecEquipRegionsList; } + + equip_region_mask_t GetEquipRegionBitMaskByName( const char *pRegionName ) const; + + KeyValues *FindDefinitionPrefabByName( const char *pszPrefabName ) const; + const PrefabMap_t& GetPrefabMap() const { return m_mapDefinitionPrefabs; } + + CUtlVector< CEconItemDefinition * > &GetBundles() { return m_vecBundles; } // Retrieve a cached list of all bundles + + const char *FindStringTableEntry( const char *pszTableName, int iIndex ) const; + +private: + void SetEquipRegionConflict( int iRegion, unsigned int unBit ); + int GetEquipRegionIndexByName( const char *pRegionName ) const; + +public: + // Common lookup methods + bool BGetItemQualityFromName( const char *pchName, uint8 *nQuality ) const; + const CEconItemQualityDefinition *GetQualityDefinition( int nQuality ) const; + const CEconItemQualityDefinition *GetQualityDefinitionByName( const char *pszDefName ) const; + + bool BGetItemRarityFromName( const char* pchName, uint8 *nRarity ) const; + const CEconItemRarityDefinition *GetRarityDefinitionByMapIndex( int nRarityIndex ) const; + const CEconItemRarityDefinition *GetRarityDefinition( int nRarity ) const; + const CEconItemRarityDefinition *GetRarityDefinitionByName( const char *pszDefName ) const; + virtual int GetRarityDefinitionCount( void ) const { return m_mapRarities.Count(); } + virtual const char* GetRarityName( uint8 iRarity ); + virtual const char* GetRarityLocKey( uint8 iRarity ); + virtual const char* GetRarityColor( uint8 iRarity ); + virtual int GetRarityIndex( const char* pszRarity ); + + const CEconItemCollectionDefinition *GetCollectionByName( const char* pCollectionName ); + + virtual int GetItemSeriesDefinitionCount( void ) const { return m_mapItemSeries.Count(); } + bool BGetItemSeries( const char* pchName, uint8 *nItemSeries ) const; + const CEconItemSeriesDefinition *GetItemSeriesDefinition( int nRarity ) const; + + CEconItemDefinition *GetItemDefinition( int iItemIndex ); + const CEconItemDefinition *GetItemDefinition( int iItemIndex ) const; + CEconItemAttributeDefinition *GetAttributeDefinition( int iAttribIndex ); + const CEconItemAttributeDefinition *GetAttributeDefinition( int iAttribIndex ) const; + CEconItemAttributeDefinition *GetAttributeDefinitionByName( const char *pszDefName ); + const CEconItemAttributeDefinition *GetAttributeDefinitionByName( const char *pszDefName ) const; + CEconCraftingRecipeDefinition *GetRecipeDefinition( int iRecipeIndex ); + CEconColorDefinition *GetColorDefinitionByName( const char *pszDefName ); + const CEconColorDefinition *GetColorDefinitionByName( const char *pszDefName ) const; +#ifdef CLIENT_DLL + const char *GetSteamPackageLocalizationToken( uint32 unPackageId ) const; +#endif // CLIENT_DLL + + bool BCanGSCreateItems( uint32 unIP ) const; +#ifdef GC_DLL + const AchievementAward_t *GetAchievementReward( const char *pchAchievementName, AppId_t unAppID ) const; + const AchievementAward_t *GetAchievementRewardByData( uint32 unData ) const; +#endif + const AchievementAward_t *GetAchievementRewardByDefIndex( uint16 usDefIndex ) const; + bool BHasAchievementRewards( void ) const { return (m_dictAchievementRewards.Count() > 0); } + + static CUtlString ComputeAchievementName( AppId_t unAppID, const char *pchNativeAchievementName ); + + // Iterating over the item definitions. Game needs this to precache data. + CEconItemDefinition *GetItemDefinitionByName( const char *pszDefName ); + const CEconItemDefinition *GetItemDefinitionByName( const char *pszDefName ) const; + +#ifdef GC_DLL + random_attrib_t *GetRandomAttributeTemplateByName( const char *pszAttrTemplateName ) const; +#endif // GC_DLL + + attachedparticlesystem_t* GetAttributeControlledParticleSystem( int id ); + attachedparticlesystem_t* FindAttributeControlledParticleSystem( const char *pchSystemName ); + typedef CUtlMap<int, attachedparticlesystem_t > ParticleDefinitionMap_t; + const ParticleDefinitionMap_t& GetAttributeControlledParticleSystems() const { return m_mapAttributeControlledParticleSystems; } + + const CUtlVector< int > *GetWeaponUnusualParticleIndexes() const { return &m_vecAttributeControlledParticleSystemsWeapons; } + const CUtlVector< int > *GetCosmeticUnusualParticleIndexes() const { return &m_vecAttributeControlledParticleSystemsCosmetics; } + const CUtlVector< int > *GetTauntUnusualParticleIndexes() const { return &m_vecAttributeControlledParticleSystemsTaunts; } + +#ifdef CLIENT_DLL + locchar_t *GetParticleSystemLocalizedName( int index ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + const PeriodicScoreTypeList_t& GetPeriodicScoreTypeList() const { return m_vecPeriodicScoreTypes; } + + int GetPeriodicScoreTypeCount() const { return GetPeriodicScoreTypeList().Count(); } // how many types of events are we tracking? the range goes from 0 through this return value + const periodic_score_t& GetPeriodicScoreInfo( int iPeriodicScoreIndex ) const; // get the full info block for this periodic score -- event type, time period, etc. + + // Only intended to be used for generating data for the WebAPI. + const KillEaterScoreMap_t& GetKillEaterScoreTypes() const { return m_mapKillEaterScoreTypes; } + const SchemaStringTableDict_t& GetStringTables() const { return m_dictStringTable; } +#endif // GC_DLL + + item_definition_index_t GetCommunityMarketRemappedDefinitionIndex( item_definition_index_t unSearchItemDef ) const; + + const CUtlVector<attr_type_t>& GetAttributeTypes() const { return m_vecAttributeTypes; } + const ISchemaAttributeType *GetAttributeType( const char *pszAttrTypeName ) const; + + const LevelBlockDict_t& GetItemLevelingDataDict() const { return m_vecItemLevelingData; } + + const CUtlVector<CItemLevelingDefinition> *GetItemLevelingData( const char *pszLevelBlockName ) const + { + LevelBlockDict_t::IndexType_t i = m_vecItemLevelingData.Find( pszLevelBlockName ); + if ( i == LevelBlockDict_t::InvalidIndex() ) + return NULL; + + return m_vecItemLevelingData[i]; + } + + const CItemLevelingDefinition *GetItemLevelForScore( const char *pszLevelBlockName, uint32 unScore ) const; + const char *GetKillEaterScoreTypeLocString( uint32 unScoreType ) const; + const char *GetKillEaterScoreTypeLevelingDataName( uint32 unScoreType ) const; + bool GetKillEaterScoreTypeAllowsBotVictims( uint32 unScoreType ) const; +#ifdef GC_DLL + bool GetKillEaterScoreTypeGCOnlyUpdate( uint32 unScoreType ) const; + bool GetKillEaterScoreTypeAllowsIncrementValues( uint32 unScoreType ) const; +#endif + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + void ItemTesting_CreateTestDefinition( int iCloneFromItemDef, int iNewDef, KeyValues *pNewKV ); + void ItemTesting_DiscardTestDefinition( int iDef ); +#endif + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ); +#endif // DBGFLAG_VALIDATE + + econ_tag_handle_t GetHandleForTag( const char *pszTagName ); // non-const because it may create a new tag handle + + typedef CUtlDict<econ_tag_handle_t> EconTagDict_t; +#ifdef GC_DLL + const EconTagDict_t& GetEconTagDict() const { return m_dictTags; } // meant for internal/debug use only, not for runtime iteration +#endif // GC_DLL + + virtual RTime32 GetCustomExpirationDate( const char *pszExpirationDate ) const { return k_RTime32Nil; } + +public: + // Subclass interface. + virtual CEconItemDefinition *CreateEconItemDefinition() { return new CEconItemDefinition; } + virtual CEconCraftingRecipeDefinition *CreateCraftingRecipeDefinition() { return new CEconCraftingRecipeDefinition; } + virtual CEconStyleInfo *CreateEconStyleInfo() { return new CEconStyleInfo; } + virtual CQuestObjectiveDefinition *CreateQuestDefinition() { return new CQuestObjectiveDefinition; } + + virtual IEconTool *CreateEconToolImpl( const char *pszToolType, const char *pszUseString, const char *pszUsageRestriction, item_capabilities_t unCapabilities, KeyValues *pUsageKV ); + +#ifdef GC_DLL + virtual random_attrib_t *CreateRandomAttribute( const char *pszContext, KeyValues *pRandomAttributesKV, CUtlVector<CUtlString> *pVecErrors = NULL ); +#endif // GC_DLL + + virtual bool BCanStrangeFilterApplyToStrangeSlotInItem( uint32 /*strange_event_restriction_t*/ unRestrictionType, uint32 unRestrictionValue, const IEconItemInterface *pItem, int iStrangeSlot, uint32 *out_pOptionalScoreType ) const; + bool AddQuestObjective( const CQuestObjectiveDefinition **ppQuestObjective, KeyValues *pKVObjective, CUtlVector<CUtlString> *pVecErrors ); + + bool BInsertLootlist( const char *pListName, KeyValues *pKVLootList, CUtlVector<CUtlString> *pVecErrors ); + +#ifdef GC_DLL + void PerformCaseBehaviorCheck(); +#endif +protected: + virtual void Reset( void ); + + virtual bool BInitSchema( KeyValues *pKVRawDefinition, CUtlVector<CUtlString> *pVecErrors = NULL ); +#ifdef TF_CLIENT_DLL + virtual int CalculateNumberOfConcreteItems( const CEconItemDefinition *pItemDef ); // Let derived classes handle custom item types +#endif // TF_CLIENT_DLL + +private: + bool BInitGameInfo( KeyValues *pKVGameInfo, CUtlVector<CUtlString> *pVecErrors ); + bool BInitAttributeTypes( CUtlVector<CUtlString> *pVecErrors ); +#ifdef GC_DLL + bool BInitPeriodicScoring( KeyValues *pKVGameInfo, CUtlVector<CUtlString> *pVecErrors ); +#endif // GC_DLL + bool BInitDefinitionPrefabs( KeyValues *pKVPrefabs, CUtlVector<CUtlString> *pVecErrors ); + bool BInitItemSeries( KeyValues *pKVSeries, CUtlVector<CUtlString> *pVecErrors ); + bool BVerifyBaseItemNames( CUtlVector<CUtlString> *pVecErrors ); + bool BInitRarities( KeyValues *pKVRarities, KeyValues *pKVRarityWeights, CUtlVector<CUtlString> *pVecErrors ); + bool BInitQualities( KeyValues *pKVAttributes, CUtlVector<CUtlString> *pVecErrors ); + bool BInitColors( KeyValues *pKVColors, CUtlVector<CUtlString> *pVecErrors ); + bool BInitAttributes( KeyValues *pKVAttributes, CUtlVector<CUtlString> *pVecErrors ); + bool BInitEquipRegions( KeyValues *pKVEquipRegions, CUtlVector<CUtlString> *pVecErrors ); + bool BInitEquipRegionConflicts( KeyValues *pKVEquipRegions, CUtlVector<CUtlString> *pVecErrors ); + bool BInitItems( KeyValues *pKVAttributes, CUtlVector<CUtlString> *pVecErrors ); + bool BInitItemSets( KeyValues *pKVItemSets, CUtlVector<CUtlString> *pVecErrors ); + bool BInitTimedRewards( KeyValues *pKVTimeRewards, CUtlVector<CUtlString> *pVecErrors ); + bool BInitAchievementRewards( KeyValues *pKVTimeRewards, CUtlVector<CUtlString> *pVecErrors ); +#ifdef GC_DLL + bool BInitRandomAttributeTemplates( KeyValues *pKVRandomAttributeTemplates, CUtlVector<CUtlString> *pVecErrors ); +#endif // GC_DLL + bool BInitRecipes( KeyValues *pKVRecipes, CUtlVector<CUtlString> *pVecErrors ); + bool BInitLootLists( KeyValues *pKVLootLists, CUtlVector<CUtlString> *pVecErrors ); + bool BInitRevolvingLootLists( KeyValues *pKVRevolvingLootLists, CUtlVector<CUtlString> *pVecErrors ); + bool BInitItemCollections( KeyValues *pKVItemSets, CUtlVector<CUtlString> *pVecErrors ); + bool BInitCollectionReferences( CUtlVector<CUtlString> *pVecErrors ); + bool BInitItemPaintKitDefinitions( KeyValues *pKVPaintKits, CUtlVector<CUtlString> *pVecErrors ); + bool BInitOperationDefinitions( KeyValues *pKVGameInfo, KeyValues *pOperations, CUtlVector<CUtlString> *pVecErrors ); + +#ifdef TF_CLIENT_DLL + bool BInitConcreteItemCounts( CUtlVector<CUtlString> *pVecErrors ); + bool BInitSteamPackageLocalizationToken( KeyValues *pKVSteamPackages, CUtlVector<CUtlString> *pVecErrors ); +#endif // TF_CLIENT_DLL + bool BInitItemLevels( KeyValues *pKVItemLevels, CUtlVector<CUtlString> *pVecErrors ); + bool BInitKillEaterScoreTypes( KeyValues *pKVItemLevels, CUtlVector<CUtlString> *pVecErrors ); + bool BInitStringTables( KeyValues *pKVStringTables, CUtlVector<CUtlString> *pVecErrors ); + bool BInitCommunityMarketRemaps( KeyValues *pKVCommunityMarketRemaps, CUtlVector<CUtlString> *pVecErrors ); + + bool BPostSchemaInit( CUtlVector<CUtlString> *pVecErrors ) const; + bool BInitAttributeControlledParticleSystems( KeyValues *pKVParticleSystems, CUtlVector<CUtlString> *pVecErrors ); + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + bool BInitArmoryData( KeyValues *pKVArmoryData, CUtlVector<CUtlString> *pVecErrors ); +#else + bool BInitExperiements( KeyValues *pKVExperiments, CUtlVector<CUtlString> *pVecErrors ); + bool BInitForeignImports( CUtlVector<CUtlString> *pVecErrors ); + + CForeignAppImports *FindOrAddAppImports( AppId_t unAppID ); +#endif + + bool BVerifyLootListItemDropDates( const CEconLootListDefinition* pLootList, CUtlVector<CUtlString> *pVecErrors ) const; + bool BRecurseiveVerifyLootListItemDropDates( const CEconLootListDefinition* pLootList, const CEconLootListDefinition* pRootLootList, CUtlVector<CUtlString> *pVecErrors ) const; + + // Note: this returns pointers to the inside of a vector and/or NULL. Pointers are not intended to be + // saved off and used later. + const kill_eater_score_type_t *FindKillEaterScoreType( uint32 unScoreType ) const; + + uint32 m_unResetCount; + + KeyValues *m_pKVRawDefinition; + uint32 m_unVersion; + CSHA m_schemaSHA; + + // Class range + equipped_class_t m_unFirstValidClass; + equipped_class_t m_unLastValidClass; + equipped_class_t m_unAccoutClassIndex; + + // Item slot range + equipped_slot_t m_unFirstValidClassItemSlot; + equipped_slot_t m_unLastValidClassItemSlot; + equipped_slot_t m_unFirstValidAccountItemSlot; + equipped_slot_t m_unLastValidAccountItemSlot; + + // Number of allowed presets + uint32 m_unNumItemPresets; + + // Allowable range of item levels for this app + uint32 m_unMinLevel; + uint32 m_unMaxLevel; + + // Total value of all the weights of the qualities + uint32 m_unSumQualityWeights; + + // Name-to-implementation list of all unique attribute types (ie., "wide strange score"). + CUtlVector<attr_type_t> m_vecAttributeTypes; + + // Contains the list of rarity definitions + CUtlMap<int, CEconItemSeriesDefinition, int > m_mapItemSeries; + + // Contains the list of rarity definitions + CUtlMap<int, CEconItemRarityDefinition, int > m_mapRarities; + + // Contains the list of item definitions read in from all data files. + CUtlMap<int, CEconItemQualityDefinition, int > m_mapQualities; + + // Contains the list of item definitions read in from all data files. + ItemDefinitionMap_t m_mapItems; + + CUtlMap<int, CQuestObjectiveDefinition*, int > m_mapQuestObjectives; + + // A sorted version of the same map, for instances where we really want sorted data + SortedItemDefinitionMap_t m_mapItemsSorted; + + // List of all the tool items, is a sublist of mapItems + ToolsItemDefinitionMap_t m_mapToolsItems; + + // List of all base items, is a sublist of mapItems + BaseItemDefinitionMap_t m_mapBaseItems; + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + // What is the default item definition we'll return in the client code if we can't find the correct one? + CEconItemDefinition *m_pDefaultItemDefinition; +#endif + + // Contains the list of attribute definitions read in from all data files. + CUtlMap<int, CEconItemAttributeDefinition, int > m_mapAttributes; + + // Contains the list of item recipes read in from all data files. + RecipeDefinitionMap_t m_mapRecipes; + + // Contains the list of item sets. + ItemSetMap_t m_mapItemSets; + ItemCollectionMap_t m_mapItemCollections; + ItemCollectionCrateMap_t m_vecItemCollectionCrates; + + OperationDefinitionMap_t m_mapOperationDefinitions; + + // Paint Kit defintions + ItemPaintKitMap_t m_mapItemPaintKits; + + // Revolving loot lists. + CUtlMap<int, const char*> m_mapRevolvingLootLists; + + // Contains the list of loot lists. + LootListDefinitionMap_t m_mapLootLists; + + // List of events that award items based on time played + CUtlVector<CTimedItemRewardDefinition> m_vecTimedRewards; + + // list of items that will be awarded from achievements + CUtlDict< AchievementAward_t *, int > m_dictAchievementRewards; + CUtlMap< uint32, AchievementAward_t * > m_mapAchievementRewardsByData; + +#ifdef GC_DLL + // list of random attribute templates + CUtlDict< random_attrib_t * > m_dictRandomAttributeTemplates; +#endif // GC_DLL + + // Contains information for attribute attached particle systems + CUtlMap<int, attachedparticlesystem_t > m_mapAttributeControlledParticleSystems; + CUtlVector< int > m_vecAttributeControlledParticleSystemsCosmetics; + CUtlVector< int > m_vecAttributeControlledParticleSystemsWeapons; + CUtlVector< int > m_vecAttributeControlledParticleSystemsTaunts; + + + // Contains information on which equip regions conflict with each other regions and how to + // test for overlap. + EquipRegionsList_t m_vecEquipRegionsList; + + // Contains information about prefab KeyValues blocks that be can referenced elsewhere + // in the schema. + PrefabMap_t m_mapDefinitionPrefabs; + + // Contains runtime color information, looked-up by name. + ColorDefinitionsList_t m_vecColorDefs; + + // Contains information about: a) every bodygroup that appears anywhere in the schema, and + // b) whether they default to on or off. + BodygroupStateMap_t m_mapDefaultBodygroupState; + + // Various definitions can have any number of unique tags associated with them. + EconTagDict_t m_dictTags; + +#ifdef GC_DLL + // Information about our periodic score accumulators. + PeriodicScoreTypeList_t m_vecPeriodicScoreTypes; +#endif // GC_DLL + + // List of item leveling data. + KillEaterScoreMap_t m_mapKillEaterScoreTypes; + + SchemaStringTableDict_t m_dictStringTable; + + typedef CUtlMap< item_definition_index_t, item_definition_index_t, item_definition_index_t > CommunityMarketDefinitionRemapMap_t; + CommunityMarketDefinitionRemapMap_t m_mapCommunityMarketDefinitionIndexRemap; + +#ifdef CLIENT_DLL + // Steam-package-ID-to-localization-token map, used for modifying tooltips in the store. + typedef CUtlMap< uint32, const char * > SteamPackageLocalizationTokenMap_t; + SteamPackageLocalizationTokenMap_t m_mapSteamPackageLocalizationTokens; +#endif // CLIENT_DLL + + LevelBlockDict_t m_vecItemLevelingData; + +#if defined(CLIENT_DLL) || defined(GAME_DLL) + // Contains Armory data key->localization string mappings + ArmoryStringDict_t m_dictArmoryItemTypesDataStrings; + ArmoryStringDict_t m_dictArmoryItemClassesDataStrings; + ArmoryStringDict_t m_dictArmoryAttributeDataStrings; + ArmoryStringDict_t m_dictArmoryItemDataStrings; + + // Used for delaying the parsing of the item schema until its safe to swap out the back end data. + IDelayedSchemaData *m_pDelayedSchemaData; +#elif defined(GC_DLL) + // GC only + CUtlVector< CExperimentDefinition > m_vecExperiments; + CUtlMap< AppId_t, CForeignAppImports *> m_mapForeignImports; + CUtlVector< AppId_t > m_vecForeignApps; +#endif + + CUtlVector< CEconItemDefinition * > m_vecBundles; // A cached list of all bundles +}; + +#ifdef GC_DLL + void PerformIncrementKillEaterAttributeScore( CEconUserSession *pLockedOwnerSession, CEconItem *pItem, uint32 unEventType, uint32 unIncrementCount, bool bGCOrigination, GCSDK::CSharedObjectTransactionEx *pTransaction ); +#endif // GC_DLL + +extern CEconItemSchema & GEconItemSchema(); + +//----------------------------------------------------------------------------- +// CSchemaFieldHandle +//----------------------------------------------------------------------------- +template < class T > +class CSchemaFieldHandle +{ +public: + explicit CSchemaFieldHandle( const char *szName ) + : m_szName( szName ) + { + m_pRef = GetTypedRef(); + m_unSchemaGeneration = GEconItemSchema().GetResetCount(); +#if _DEBUG + m_unVersion_Debug = GEconItemSchema().GetVersion(); +#endif + } + + operator const T *( void ) const + { + uint32 unSchemaGeneration = GEconItemSchema().GetResetCount(); + if ( m_unSchemaGeneration != unSchemaGeneration ) + { + m_pRef = GetTypedRef(); + m_unSchemaGeneration = unSchemaGeneration; +#if _DEBUG + m_unVersion_Debug = GEconItemSchema().GetVersion(); +#endif + } + +#if _DEBUG + Assert( m_unVersion_Debug == GEconItemSchema().GetVersion() ); +#endif + return m_pRef; + } + + const T *operator->( void ) const + { + return static_cast<const T *>( *this ); + } + + const char *GetName( void ) const + { + return m_szName; + } + +private: + const T *GetTypedRef() const; + +private: + const char *m_szName; + + mutable const T *m_pRef; + mutable uint32 m_unSchemaGeneration; +#if _DEBUG + mutable uint32 m_unVersion_Debug; +#endif +}; + +template < > +inline const CEconColorDefinition *CSchemaFieldHandle<CEconColorDefinition>::GetTypedRef( void ) const +{ + return GEconItemSchema().GetColorDefinitionByName( m_szName ); +} + +template < > +inline const CEconItemAttributeDefinition *CSchemaFieldHandle<CEconItemAttributeDefinition>::GetTypedRef( void ) const +{ + return GEconItemSchema().GetAttributeDefinitionByName( m_szName ); +} + +template < > +inline const CEconItemDefinition *CSchemaFieldHandle<CEconItemDefinition>::GetTypedRef( void ) const +{ + return GEconItemSchema().GetItemDefinitionByName( m_szName ); +} + +template < > +inline const CEconLootListDefinition *CSchemaFieldHandle<CEconLootListDefinition>::GetTypedRef( void ) const +{ + return GEconItemSchema().GetLootListByName( m_szName ); +} + +template < > +inline const attachedparticlesystem_t *CSchemaFieldHandle<attachedparticlesystem_t>::GetTypedRef( void ) const +{ + return GEconItemSchema().FindAttributeControlledParticleSystem( m_szName ); +} + +typedef CSchemaFieldHandle<CEconColorDefinition> CSchemaColorDefHandle; +typedef CSchemaFieldHandle<CEconItemAttributeDefinition> CSchemaAttributeDefHandle; +typedef CSchemaFieldHandle<CEconItemDefinition> CSchemaItemDefHandle; +typedef CSchemaFieldHandle<CEconLootListDefinition> CSchemaLootListDefHandle; +typedef CSchemaFieldHandle<attachedparticlesystem_t> CSchemaParticleHandle; + + +struct steam_market_gc_identifier_t +{ + item_definition_index_t m_unDefIndex; + uint8 m_unQuality; + + bool operator<( const struct steam_market_gc_identifier_t& b ) const + { + return (m_unDefIndex < b.m_unDefIndex) + || ((m_unDefIndex == b.m_unDefIndex) && (m_unQuality < b.m_unQuality)); + } +}; + +// Implementation reliant on earlier class content. +inline const CEconItemAttributeDefinition *static_attrib_t::GetAttributeDefinition() const +{ + return GEconItemSchema().GetAttributeDefinition( iDefIndex ); +} + +inline const ISchemaAttributeType *static_attrib_t::GetAttributeType() const +{ + const CEconItemAttributeDefinition *pAttrDef = GetAttributeDefinition(); + if ( !pAttrDef ) + return NULL; + + return pAttrDef->GetAttributeType(); +} + +// Utility function to convert datafile strings to ints. +int StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings, bool bDontAssert = false ); +int StringFieldToInt( const char *szValue, const CUtlVector<const char *>& vecValueStrings, bool bDontAssert = false ); + +#ifdef GC_DLL +// Global econ-level helper functionality. +EUniverse GetUniverse(); + +bool BYieldingGetChangedItemDefinitions( int iComparisonColumn, CUtlVector<item_definition_index_t>& out_vecChangedDefIndices ); +bool BYieldingUpdateItemDefinitionStateHashValue( GCSDK::CSQLAccess& sqlAccess, item_definition_index_t unItemDef, int iUpdatedColumn ); +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CAttributeLineItemLootList : public IEconLootList +{ +public: + static CSchemaAttributeDefHandle s_pAttrDef_RandomDropLineItems[4]; +#ifdef GC_DLL + static CSchemaAttributeDefHandle s_pAttrDef_RandomDropLineItemUnusualChance; + static CSchemaAttributeDefHandle s_pAttrDef_RandomDropLineItemUnusualList; +#endif // GC_DLL + static CSchemaAttributeDefHandle s_pAttrDef_RandomDropLineItemFooterDesc; + +public: + CAttributeLineItemLootList( const IEconItemInterface *pEconItem ) + : m_pEconItem( pEconItem ) + { + // + } + + virtual void EnumerateUserFacingPotentialDrops( IEconLootListIterator *pIt ) const OVERRIDE; + virtual bool BPublicListContents() const OVERRIDE { return true; } // any attribute data that clients have is public to them + virtual const char *GetLootListHeaderLocalizationKey() const OVERRIDE; + virtual const char *GetLootListFooterLocalizationKey() const OVERRIDE; + virtual const char *GetLootListCollectionReference() const OVERRIDE; + +#ifdef GC_DLL + MUST_CHECK_RETURN virtual bool BGenerateSingleRollRandomItems( const CEconGameAccount *pGameAccount, bool bFreeAccount, CUtlVector<CEconItem *> *out_pvecItems, const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs = NULL ) const OVERRIDE; +#endif // GC_DLL + +private: + const IEconItemInterface *m_pEconItem; +}; + +void MergeDefinitionPrefab( KeyValues *pKVWriteItem, KeyValues *pKVSourceItem ); + +#endif //ECONITEMSCHEMA_H diff --git a/game/shared/econ/econ_item_system.cpp b/game/shared/econ/econ_item_system.cpp new file mode 100644 index 0000000..29026b9 --- /dev/null +++ b/game/shared/econ/econ_item_system.cpp @@ -0,0 +1,694 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "tier1/KeyValues.h" +#include "econ_gcmessages.h" +#include "econ_item_system.h" +#include "econ_item_inventory.h" +#include "game_item_schema.h" +#include "gc_clientsystem.h" + +#include "utldict.h" +#include "filesystem.h" +#include "steam/isteamhttp.h" + + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +#include "gamestringpool.h" +#include "ihasattributes.h" +#include "tier0/icommandline.h" +#endif + +#if defined(CLIENT_DLL) +#include "igameevents.h" +#endif + +// FIXME FIXME FIXME +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) + #include "tf_item_system.h" +#endif // defined(TF_DLL) || defined(TF_CLIENT_DLL) + +#if defined (DOTA_CLIENT_DLL) || defined (DOTA_DLL) + #include "econ/dota_item_system.h" +#endif // defined (DOTA_CLIENT_DLL) || defined (DOTA_DLL) + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if ( defined( GAME_DLL ) || defined( CLIENT_DLL ) ) && ( defined( _DEBUG ) || defined( STAGING_ONLY ) ) +ConVar item_debug( "item_debug", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); +ConVar items_game_use_gc_copy( "items_game_use_gc_copy", "1", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_ARCHIVE, "If set, items_game.txt will be stomped by the GC." ); +ConVar item_debug_validation( "item_debug_validation", "1", FCVAR_REPLICATED | FCVAR_ARCHIVE, "If set, CEconEntity::ValidateEntityAttachedToPlayer behaves as it would in release builds and also allows bot players to take the same code path as real players." ); +#endif + +static ConVar item_quality_chance_unique( "item_quality_chance_unique", "0.1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Percentage chance that a random item is unique." ); +static ConVar item_quality_chance_rare( "item_quality_chance_rare", "0.5", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Percentage chance that a random item is a rare." ); +static ConVar item_quality_chance_common( "item_quality_chance_common", "1.0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Percentage chance that a random item is common." ); + + +//----------------------------------------------------------------------------- +// Purpose: Get at the global item system +//----------------------------------------------------------------------------- +CEconItemSystem *ItemSystem( void ) +{ + static GameItemSystem_t *pSystem = NULL; + if ( !pSystem ) + { + pSystem = new GameItemSystem_t(); + } + + return pSystem; +} + + +//----------------------------------------------------------------------------- +// Purpose: Global schema access, declared in game_item_schema.h +//----------------------------------------------------------------------------- +GameItemSchema_t *GetItemSchema() +{ + return ItemSystem()->GetItemSchema(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemSystem::CEconItemSystem( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemSystem::~CEconItemSystem( void ) +{ + Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Parse in our data files. +//----------------------------------------------------------------------------- +void CEconItemSystem::Init( void ) +{ +#if defined(USES_ECON_ITEMS) + ParseItemSchemaFile( "scripts/items/items_game.txt" ); +#endif + +#ifdef CLIENT_DLL + IGameEvent *event = gameeventmanager->CreateEvent( "item_schema_initialized" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemSystem::Shutdown( void ) +{ +} + +extern ConVar mp_tournament; + +#ifdef GAME_DLL +ConVar mp_tournament_whitelist( "mp_tournament_whitelist", "item_whitelist.txt", FCVAR_NONE, "Specifies the item whitelist file to use." ); +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemSystem::ReloadWhitelist( void ) +{ + // Default state of items depends on whether we're in tourney mode, and whether there's a whitelist + bool bDefault = true; + bool bFoundWhitelist = false; + + KeyValues *pWhitelistKV = new KeyValues( "item_whitelist" ); + +#ifdef GAME_DLL + if ( mp_tournament.GetBool() && mp_tournament_whitelist.GetString() ) + { + const char *pszWhitelistFile = mp_tournament_whitelist.GetString(); + if ( pWhitelistKV->LoadFromFile( filesystem, pszWhitelistFile ) ) + { + // Allow the whitelist to override the default, so they can turn it into a blacklist if they want to + bDefault = pWhitelistKV->GetBool( "unlisted_items_default_to" ); + bFoundWhitelist = true; + } + else if ( pszWhitelistFile && pszWhitelistFile[0] ) + { + Msg("Item Whitelist file '%s' could not be found. All items will be allowed.\n", pszWhitelistFile ); + } + } +#endif + + const CEconItemSchema::ItemDefinitionMap_t& mapItemDefs = m_itemSchema.GetItemDefinitionMap(); + FOR_EACH_MAP_FAST( mapItemDefs, i ) + { + mapItemDefs[i]->SetAllowedInMatch( bDefault ); + } + + // If we didn't find a file, we're done. + if ( !bFoundWhitelist ) + return; + + // Otherwise, go through the KVs and turn on the matching items. + Msg("Parsing item whitelist (default: %s)\n", bDefault ? "allowed" : "disallowed" ); + pWhitelistKV = pWhitelistKV->GetFirstSubKey(); + while ( pWhitelistKV ) + { + bool bAllow = pWhitelistKV->GetBool(); + + const char *pszItemName = pWhitelistKV->GetName(); + if ( pszItemName && pszItemName[0] && !FStrEq("unlisted_items_default_to", pszItemName) ) + { + CEconItemDefinition *pItemDef = m_itemSchema.GetItemDefinitionByName( pszItemName ); + if ( pItemDef ) + { + pItemDef->SetAllowedInMatch( bAllow ); + Msg(" -> %s '%s'\n", bAllow ? "Allowing" : "Removing", pszItemName ); + } + else + { + Warning(" -> Could not find an item definition named '%s'\n", pszItemName ); + } + } + + pWhitelistKV = pWhitelistKV->GetNextKey(); + } + Msg("Finished.\n"); +} + +#ifdef GAME_DLL +CON_COMMAND_F( item_show_whitelistable_definitions, "Lists the item definitions that can be whitelisted in the item_whitelist.txt file in tournament mode.", FCVAR_CHEAT ) +{ + Msg("Available item definitions for whitelisting:\n"); + const CEconItemSchema::SortedItemDefinitionMap_t& mapItemDefs = ItemSystem()->GetItemSchema()->GetSortedItemDefinitionMap(); + FOR_EACH_MAP( mapItemDefs, i ) + { + const CEconItemDefinition *pItemDef = mapItemDefs[i]; + if ( pItemDef && pItemDef->GetQuality() != AE_NORMAL && !pItemDef->IsHidden() ) + { + Msg(" '%s'\n", pItemDef->GetDefinitionName() ); + } + } +} +#endif // GAME_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemSystem::ResetAttribStringCache( void ) +{ + const CUtlMap<int, CEconItemAttributeDefinition, int> &mapDefs = m_itemSchema.GetAttributeDefinitionMap(); + FOR_EACH_MAP_FAST( mapDefs, i ) + { + mapDefs[i].ClearStringCache(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemSystem::DecryptItemFiles( KeyValues *pKV, const char *pName ) +{ + char szFullName[512]; + Q_snprintf(szFullName,sizeof(szFullName), "%s.ctx", pName ); + + FileHandle_t f = filesystem->Open( szFullName, "rb", "MOD" ); + + if (!f) + { +#if !defined(CSTRIKE_DLL) + Warning("No %s file found. May be unable to create items.\n", pName ); +#endif // CSTRIKE_DLL + return false; + } + + int fileSize = filesystem->Size(f); + char *buffer = (char*)MemAllocScratch(fileSize + 1); + + Assert(buffer); + + filesystem->Read(buffer, fileSize, f); // read into local buffer + buffer[fileSize] = 0; // null terminate file as EOF + filesystem->Close( f ); // close file after reading + + UTIL_DecodeICE( (unsigned char*)buffer, fileSize, GetEncryptionKey() ); + + bool retOK = pKV->LoadFromBuffer( szFullName, buffer, filesystem ); + + MemFreeScratch(); + + if ( !retOK ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Read the specified item schema file. Init the item schema with the contents +//----------------------------------------------------------------------------- +void CEconItemSystem::ParseItemSchemaFile( const char *pFilename ) +{ + CUtlVector< CUtlString > vecErrors; + bool bSuccess = m_itemSchema.BInit( pFilename, "MOD", &vecErrors ); + + if( !bSuccess ) + { + FOR_EACH_VEC( vecErrors, nError ) + { + // we want this to be an Error because several + // places rely on loading a valid item schema + Error( "%s\n", vecErrors[nError].String() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Generate a random item matching the specified criteria +//----------------------------------------------------------------------------- +item_definition_index_t CEconItemSystem::GenerateRandomItem( CItemSelectionCriteria *pCriteria, entityquality_t *outEntityQuality ) +{ + // First, pick a random item quality (use the one passed in first) + if ( !pCriteria->BQualitySet() ) + { + pCriteria->SetQuality( GetRandomQualityForItem() ); + } + + pCriteria->SetIgnoreEnabledFlag( true ); + + // Determine which item templates match the criteria + CUtlVector<item_definition_index_t> vecMatches; + const CEconItemSchema::ItemDefinitionMap_t &mapDefs = m_itemSchema.GetItemDefinitionMap(); + +HackMakeValidList: + FOR_EACH_MAP_FAST( mapDefs, i ) + { + if ( pCriteria->BEvaluate( mapDefs[i] ) ) + { + vecMatches.AddToTail( mapDefs.Key( i ) ); + } + } + + // No valid items? + int iValidItems = vecMatches.Count(); + if ( !iValidItems ) + { + // If we were searching for a unique item, drop back to a non-unique + if ( pCriteria->GetQuality() == AE_UNIQUE ) + { + pCriteria->SetQuality( GetRandomQualityForItem( true ) ); + goto HackMakeValidList; + } + return INVALID_ITEM_DEF_INDEX; + } + + // Choose a random match + int iChosenIdx = RandomInt( 0, (iValidItems-1) ); + item_definition_index_t iChosenItem = vecMatches[iChosenIdx]; + + const CEconItemDefinition *pItemDef = m_itemSchema.GetItemDefinition( iChosenItem ); + if ( !pItemDef ) + return INVALID_ITEM_DEF_INDEX; + + // If we haven't specified an entity quality, we want to use the item's specified one + if ( pCriteria->GetQuality() == AE_USE_SCRIPT_VALUE ) + { + int32 iScriptQuality = pItemDef->GetQuality(); + pCriteria->SetQuality( iScriptQuality == AE_UNDEFINED ? GetRandomQualityForItem( true ) : iScriptQuality ); + } + + // If we haven't specified an item level, we want to use the item's specified one. + if ( !pCriteria->BItemLevelSet() ) + { + pCriteria->SetItemLevel( RandomInt( pItemDef->GetMinLevel(), pItemDef->GetMaxLevel() ) ); + } + + if ( outEntityQuality ) + { + *outEntityQuality = pCriteria->GetQuality(); + } + return iChosenItem; +} + +//----------------------------------------------------------------------------- +// Purpose: Return a random quality for the item specified +//----------------------------------------------------------------------------- +entityquality_t CEconItemSystem::GetRandomQualityForItem( bool bPreventUnique ) +{ + // Start on the rarest, and work backwards + if ( !bPreventUnique ) + { + if ( RandomFloat(0,1) < item_quality_chance_unique.GetFloat() ) + return AE_UNIQUE; + } + + if ( RandomFloat(0,1) < item_quality_chance_rare.GetFloat() ) + return AE_RARITY2; + + if ( RandomFloat(0,1) < item_quality_chance_common.GetFloat() ) + return AE_RARITY1; + + return AE_NORMAL; +} + +static ISteamHTTP *GetISteamHTTP() +{ + if ( steamapicontext != NULL && steamapicontext->SteamHTTP() ) + { + return steamapicontext->SteamHTTP(); + } + #ifndef CLIENT_DLL + if ( steamgameserverapicontext != NULL ) + { + return steamgameserverapicontext->SteamHTTP(); + } + #endif + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Common functionality for using our raw buffer data to initialize +// the schema when safe. +//----------------------------------------------------------------------------- +bool IDelayedSchemaData::InitializeSchemaInternal( CEconItemSchema *pItemSchema, CUtlBuffer& bufRawData, bool bInitAsBinary, uint32 nExpectedVersion ) +{ + Msg( "Applying new item schema, version %08X\n", nExpectedVersion ); + + CUtlVector<CUtlString> vecErrors; + bool bSuccess = bInitAsBinary + ? pItemSchema->BInitBinaryBuffer( bufRawData, &vecErrors ) + : pItemSchema->BInitTextBuffer( bufRawData, &vecErrors ); + if( bSuccess ) + { + // Sanity-check that we received the version that they sent us + uint32 nOurVersion = pItemSchema->GetVersion(); + if ( nExpectedVersion != 0 && nOurVersion != nExpectedVersion ) + { + Warning( "**WARNING** Item schema mismatch after update!\n" ); + Warning( "GC told us to expect %08X, we got %08X\n", nExpectedVersion, nOurVersion ); + } + } + else + { + Warning( "**WARNING** Failed to apply item schema!\n" ); + FOR_EACH_VEC( vecErrors, nError ) + { + Warning( "%s\n", vecErrors[nError].Get() ); + } + } + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Purpose: The GC sent us a single block of binary data. +//----------------------------------------------------------------------------- +class DelayedSchemaData_GCDirectData : public IDelayedSchemaData +{ +public: + DelayedSchemaData_GCDirectData( const std::string& strBuffer ) + : m_bufRawData( strBuffer.data(), strBuffer.size(), CUtlBuffer::READ_ONLY ) + { + // + } + + virtual bool InitializeSchema( CEconItemSchema *pItemSchema ) + { + return InitializeSchemaInternal( pItemSchema, m_bufRawData, true, 0 ); + } + +private: + CUtlBuffer m_bufRawData; +}; + +extern bool CheckValveSignature( const void *data, uint32 nDataSize, const void *signature, uint32 nSignatureSize ); + +//----------------------------------------------------------------------------- +// Purpose: We received a text file from an HTML request. +//----------------------------------------------------------------------------- +class DelayedSchemaData_HTTPResponseData : public IDelayedSchemaData +{ +public: + DelayedSchemaData_HTTPResponseData( ISteamHTTP *pHTTP, HTTPRequestHandle handleHTTPRequest, uint32 unBodySize, uint32 nExpectedVersion, const std::string &sSignature ) + : m_nExpectedVersion( nExpectedVersion ) + { + Assert( pHTTP ); + + m_bufRawData.SetBufferType( true, true ); + m_bufRawData.SeekPut( CUtlBuffer::SEEK_HEAD, unBodySize ); + + m_bValid = pHTTP->GetHTTPResponseBodyData( handleHTTPRequest, (uint8*)m_bufRawData.Base(), m_bufRawData.TellPut() ); + if ( m_bValid ) + m_bValid = CheckValveSignature( m_bufRawData.Base(), m_bufRawData.TellPut(), sSignature.c_str(), sSignature.length() ); + } + + virtual bool InitializeSchema( CEconItemSchema *pItemSchema ) + { + if ( !m_bValid ) + return false; + + return InitializeSchemaInternal( pItemSchema, m_bufRawData, false, m_nExpectedVersion ); + } + +private: + bool m_bValid; + CUtlBuffer m_bufRawData; + uint32 m_nExpectedVersion; +}; + +#define GC_ITEM_SCHEMA_UPDATE_APPLIED "Applied updated item schema from GC. %d bytes, version %08X.\n" +#define GC_ITEM_SCHEMA_UPDATE_QUEUED "Received %d bytes item schema version %08X direct data; update is queued.\n" + +//----------------------------------------------------------------------------- +// Purpose: Update the item schema from the GC +//----------------------------------------------------------------------------- +class CGCUpdateItemSchema : public GCSDK::CGCClientJob +{ +public: + CGCUpdateItemSchema( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) { + m_szUrl[0] = '\0'; + m_nExpectedVersion = 0; + bHTTPCompleted = false; + } + + char m_szUrl[512]; + uint32 m_nExpectedVersion; + bool bHTTPCompleted; + CCallResult< CGCUpdateItemSchema, HTTPRequestCompleted_t > callback; + std::string m_sSignature; + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg< CMsgUpdateItemSchema > msg( pNetPacket ); + +#if ( defined( GAME_DLL ) || defined( CLIENT_DLL ) ) && ( defined( _DEBUG ) || defined( STAGING_ONLY ) ) + const bool bUseGCCopy = items_game_use_gc_copy.GetBool(); +#else + const bool bUseGCCopy = true; +#endif + + if ( bUseGCCopy == false && k_EUniversePublic != GetUniverse() ) + { + Msg( "Loading item schema from local file.\n" ); + KeyValuesAD pItemsGameKV( "ItemsGameFile" ); + if ( pItemsGameKV->LoadFromFile( g_pFullFileSystem, "scripts/items/items_game.txt", "GAME" ) ) + { + CUtlBuffer buffer; + pItemsGameKV->WriteAsBinary( buffer ); + + CUtlVector< CUtlString > vecErrors; + bool bSuccess = ItemSystem()->GetItemSchema()->BInitBinaryBuffer( buffer, &vecErrors ); + if( !bSuccess ) + { + FOR_EACH_VEC( vecErrors, nError ) + { + Warning( "%s\n", vecErrors[nError].Get() ); + } + } + } + + return true; + } + + // Check if we're already up-to-date + m_nExpectedVersion = msg.Body().item_schema_version(); + uint32 nCurrentSchemaVersion = ItemSystem()->GetItemSchema()->GetVersion(); + if ( m_nExpectedVersion != 0 && m_nExpectedVersion == nCurrentSchemaVersion ) + { + Msg( "Current item schema is up-to-date with version %08X.\n", nCurrentSchemaVersion ); + return true; + } + + m_sSignature = msg.Body().signature(); + + // !TEST! + //const char *szURL = "http://cdn.beta.steampowered.com/apps/440/scripts/items/items_game.b8b7a85b4dd98b139957004b86ec0bc070a59d18.txt"; + if ( msg.Body().has_items_game() ) + { + bool bDidInit = ItemSystem()->GetItemSchema()->MaybeInitFromBuffer( new DelayedSchemaData_GCDirectData( msg.Body().items_game() ) ); + Msg( bDidInit ? GC_ITEM_SCHEMA_UPDATE_APPLIED : GC_ITEM_SCHEMA_UPDATE_QUEUED, (int)msg.Body().items_game().size(), m_nExpectedVersion ); + } + else + { + // Remember URL + const char *szURL = msg.Body().items_game_url().c_str(); + if ( !szURL || !szURL[0] ) + { + Warning( "GC sent malformed CGCUpdateItemSchema message: No schema data, no URL\n" ); + } + else + { + Q_strncpy( m_szUrl, szURL, sizeof( m_szUrl ) ); + //Msg( "Fetching %s to update item schema\n", m_szUrl ); + + // Send an HTTP request for the file + ISteamHTTP *pHTTP = GetISteamHTTP(); + if ( !pHTTP ) + { + //Warning( "Can't get ISteamHTTP to update item schema\n"); + return true; + } + HTTPRequestHandle hReq = pHTTP->CreateHTTPRequest( k_EHTTPMethodGET, m_szUrl ); + pHTTP->SetHTTPRequestNetworkActivityTimeout( hReq, 10 ); + SteamAPICall_t hCall; + if ( !pHTTP->SendHTTPRequest( hReq, &hCall ) ) + { + Warning( "Failed to update item schema: couldn't fetch %s\n", m_szUrl ); + return true; + } + + // + // *Wait* for completion. + // + // This is important. The GC needs to be able to safely assume that + // we will not process the next message until we have finished + // dealing with this one. + // + bHTTPCompleted = false; + #ifndef CLIENT_DLL + if ( steamgameserverapicontext != NULL && pHTTP == steamgameserverapicontext->SteamHTTP() ) + { + callback.SetGameserverFlag(); + } + #endif + callback.Set( hCall, this, &CGCUpdateItemSchema::OnHTTPCompleted ); + + // Wait for it to finish. + while ( !bHTTPCompleted ) + { + BYieldingWaitOneFrame(); + } + } + } + + return true; + } + + void OnHTTPCompleted( HTTPRequestCompleted_t *arg, bool bFailed ) + { + // Clear flag, no matter what else, so we can stop yielding + bHTTPCompleted = true; + + ISteamHTTP *pHTTP = GetISteamHTTP(); + Assert( pHTTP ); + if ( !pHTTP ) return; + + if ( arg->m_eStatusCode != k_EHTTPStatusCode200OK ) + { + Warning( "Failed to update item schema: HTTP status %d fetching %s\n", arg->m_eStatusCode, m_szUrl ); + } + else + { + if ( !arg->m_bRequestSuccessful ) + { + bFailed = true; + } + if ( !bFailed ) + { + uint32 unBodySize; + if ( !pHTTP->GetHTTPResponseBodySize( arg->m_hRequest, &unBodySize ) ) + { + Assert( false ); + bFailed = true; + } + else + { + bool bDidInit = ItemSystem()->GetItemSchema()->MaybeInitFromBuffer( new DelayedSchemaData_HTTPResponseData( pHTTP, arg->m_hRequest, unBodySize, m_nExpectedVersion, m_sSignature ) ); + Msg( bDidInit ? GC_ITEM_SCHEMA_UPDATE_APPLIED : GC_ITEM_SCHEMA_UPDATE_QUEUED, unBodySize, m_nExpectedVersion ); + } + } + + if ( bFailed ) + { + Warning( "Failed to update item schema from %s\n", m_szUrl ); + } + } + + pHTTP->ReleaseHTTPRequest( arg->m_hRequest ); + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCUpdateItemSchema, "CGCUpdateItemSchema", k_EMsgGCUpdateItemSchema, GCSDK::k_EServerTypeGCClient ); + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: Update the item schema from the GC +//----------------------------------------------------------------------------- +CON_COMMAND_F( econ_show_items_with_tag, "Lists the item definitions that have a specified tag.", FCVAR_CLIENTDLL ) +{ + if ( args.ArgC() != 2 ) + return; + + econ_tag_handle_t tagHandle = GetItemSchema()->GetHandleForTag( args.Arg( 1 ) ); + FOR_EACH_MAP( GetItemSchema()->GetSortedItemDefinitionMap(), i ) + { + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionMap()[i]; + + if ( pItemDef->HasEconTag( tagHandle ) ) + { + Msg(" '%s'\n", pItemDef->GetDefinitionName() ); + } + } +} +#endif // CLIENT_DLL + +#ifdef STAGING_ONLY +//----------------------------------------------------------------------------- +// Purpose: Update the item schema from the GC +//----------------------------------------------------------------------------- +#ifdef CLIENT_DLL +CON_COMMAND_F( cl_reload_local_item_schema, "Reloads the local item schema copy.", FCVAR_CLIENTDLL ) +#else +CON_COMMAND_F( sv_reload_local_item_schema, "Reloads the local item schema copy.", FCVAR_GAMEDLL ) +#endif +{ +#ifdef CLIENT_DLL + engine->ClientCmd_Unrestricted( "cmd sv_reload_local_item_schema" ); +#endif + + Msg( "Loading item schema from local file.\n" ); + KeyValuesAD pItemsGameKV( "ItemsGameFile" ); + if ( pItemsGameKV->LoadFromFile( g_pFullFileSystem, "scripts/items/items_game.txt", "GAME" ) ) + { + CUtlBuffer buffer; + pItemsGameKV->WriteAsBinary( buffer ); + + CUtlVector< CUtlString > vecErrors; + bool bSuccess = ItemSystem()->GetItemSchema()->BInitBinaryBuffer( buffer, &vecErrors ); + if( !bSuccess ) + { + FOR_EACH_VEC( vecErrors, nError ) + { + Warning( "%s\n", vecErrors[nError].Get() ); + } + } + } +} +#endif diff --git a/game/shared/econ/econ_item_system.h b/game/shared/econ/econ_item_system.h new file mode 100644 index 0000000..329a86a --- /dev/null +++ b/game/shared/econ/econ_item_system.h @@ -0,0 +1,86 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef ECON_ITEM_SYSTEM_H +#define ECON_ITEM_SYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "econ_item_view.h" +#include "game_item_schema.h" + +//================================================================================== +// ITEM SYSTEM +//================================================================================== + +#define GC_MOTD_CACHE_FILE "cfg/motd_entries.txt" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEconItemSystem +{ +public: + CEconItemSystem( void ); + virtual ~CEconItemSystem( void ); + + // Setup & parse in the item data files. + void Init( void ); + void Shutdown( void ); + + // Return the static item data for the specified item index + GameItemDefinition_t *GetStaticDataForItemByDefIndex( item_definition_index_t iItemDefIndex ) + { + return (GameItemDefinition_t *)m_itemSchema.GetItemDefinition( iItemDefIndex ); + } + CEconItemDefinition *GetStaticDataForItemByName( const char *pszDefName ) + { + return m_itemSchema.GetItemDefinitionByName( pszDefName ); + } + CEconItemAttributeDefinition *GetStaticDataForAttributeByDefIndex( attrib_definition_index_t iAttribDefinitionIndex ) + { + return m_itemSchema.GetAttributeDefinition( iAttribDefinitionIndex ); + } + CEconItemAttributeDefinition *GetStaticDataForAttributeByName( const char *pszDefName ) + { + return m_itemSchema.GetAttributeDefinitionByName( pszDefName ); + } + + // Select and return a random item's definition index matching the specified criteria + item_definition_index_t GenerateRandomItem( CItemSelectionCriteria *pCriteria, entityquality_t *outEntityQuality ); + + // Select and return the base item definition index for a class's load-out slot + // Note: baseitemcriteria_t is game-specific and/or may not exist! + virtual item_definition_index_t GenerateBaseItem( struct baseitemcriteria_t *pCriteria ) { return INVALID_ITEM_DEF_INDEX; } + + // Return a random item quality + entityquality_t GetRandomQualityForItem( bool bPreventUnique = false ); + + // Decrypt the item files and return the keyvalue + bool DecryptItemFiles( KeyValues *pKV, const char *pName ); + + GameItemSchema_t *GetItemSchema() { return &m_itemSchema; } + + // Open the server's whitelist, and if it exists, set the appropriate items allowed. + void ReloadWhitelist( void ); + + void ResetAttribStringCache( void ); + +protected: + // Read the specified item schema file. Init the item schema with the contents + void ParseItemSchemaFile( const char *pFilename ); + + // Key to decrypt the item description files + const unsigned char *GetEncryptionKey( void ) { return (unsigned char *)"A5fSXbf7"; } + +private: + GameItemSchema_t m_itemSchema; +}; + +CEconItemSystem *ItemSystem( void ); + +#endif // ECON_ITEM_SYSTEM_H diff --git a/game/shared/econ/econ_item_tools.cpp b/game/shared/econ/econ_item_tools.cpp new file mode 100644 index 0000000..8fb70ee --- /dev/null +++ b/game/shared/econ/econ_item_tools.cpp @@ -0,0 +1,1777 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "cbase.h" +#include "game_item_schema.h" +#include "econ_item_interface.h" +#include "econ_item_tools.h" +#include "econ_item_constants.h" +#include "econ_dynamic_recipe.h" +#include "schemainitutils.h" + + + +bool BStringsEqual( const char *pszA, const char *pszB ) +{ + if ( pszA == NULL ) + return pszB == NULL; + + if ( pszB == NULL ) + return false; + + return !V_strcmp( pszA, pszB ); +} + +const unsigned int g_CapabilityApplicationMap[] = +{ + // if we have a tool that has this capability... + // ...then we check to see if the tool target has + // this capability + + ITEM_CAP_PAINTABLE, // ITEM_CAP_PAINTABLE + ITEM_CAP_NAMEABLE, // ITEM_CAP_NAMEABLE + ITEM_CAP_DECODABLE, // ITEM_CAP_DECODABLE + 0, // ITEM_CAP_UNUSED; was ITEM_CAP_CAN_MOD_SOCKET + ITEM_CAP_CAN_CUSTOMIZE_TEXTURE, // ITEM_CAP_CAN_CUSTOMIZE_TEXTURE + 0, // ITEM_CAP_USABLE + 0, // ITEM_CAP_USABLE_GC + ITEM_CAP_CAN_GIFT_WRAP, // ITEM_CAP_CAN_GIFT_WRAP + 0, // ITEM_CAP_USABLE_OUT_OF_GAME + ITEM_CAP_CAN_COLLECT, // ITEM_CAP_CAN_COLLECT + 0, // ITEM_CAP_CAN_CRAFT_COUNT + 0, // ITEM_CAP_CAN_CRAFT_MARK + ITEM_CAP_PAINTABLE_TEAM_COLORS | ITEM_CAP_PAINTABLE, // ITEM_CAP_PAINTABLE_TEAM_COLORS + 0, // ITEM_CAP_CAN_BE_RESTORED + ITEM_CAP_CAN_USE_STRANGE_PARTS, // ITEM_CAP_CAN_USE_STRANGE_PARTS + ITEM_CAP_CAN_CARD_UPGRADE, // ITEM_CAP_CAN_CARD_UPGRADE + ITEM_CAP_CAN_STRANGIFY, // ITEM_CAP_CAN_STRANGIFY + ITEM_CAP_CAN_KILLSTREAKIFY, // ITEM_CAP_CAN_KILLSTREAKIFY + ITEM_CAP_CAN_CONSUME, // ITEM_CAP_CAN_CONSUME + ITEM_CAP_CAN_SPELLBOOK_PAGE, // ITEM_CAP_CAN_SPELLBOOK_PAGE + ITEM_CAP_HAS_SLOTS, // ITEM_CAP_HAS_SLOTS + ITEM_CAP_DUCK_UPGRADABLE, // ITEM_CAP_DUCK_UPGRADABLE + ITEM_CAP_CAN_UNUSUALIFY, // ITEM_CAP_CAN_UNUSUALIFY +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( g_CapabilityApplicationMap ) == NUM_ITEM_CAPS ); + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +bool IEconTool::ShouldDisplayQuantity( const IEconItemInterface *pTool ) const +{ + Assert( pTool ); + + const GameItemDefinition_t *pItemDef = pTool->GetItemDefinition(); + if ( !pItemDef ) + return false; + + static CSchemaAttributeDefHandle pAttrDef_UnlimitedQuantity( "unlimited quantity" ); + if ( pTool->FindAttribute( pAttrDef_UnlimitedQuantity ) ) + return false; + + if ( pTool->GetQuantity() >= 0 ) + { + if ( (pItemDef->GetCapabilities() & ITEM_CAP_USABLE_GC) != 0 ) + return true; + + if ( pItemDef->IsTool() ) + return true; + } + + return false; +} + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +CEconTool_WrappedGift::CEconTool_WrappedGift( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) + , m_pszDeliveredGiftItemDefName( NULL ) + , m_pDeliveredGiftItemDef( NULL ) + , m_bIsGlobalGift( false ) + , m_bIsDirectGift( false ) +{ + if ( pUsageKV ) + { + m_bIsGlobalGift = pUsageKV->GetBool( "target_type_global", false ); + m_pszDeliveredGiftItemDefName = pUsageKV->GetString( "delivered_gift_item_def", NULL ); + m_bIsDirectGift = pUsageKV->GetBool( "is_direct_gift", false ); + } +} + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +bool CEconTool_WrappedGift::BFinishInitialization() +{ + // Now that we've finished parsing our definitions, look for a match. + if ( m_pszDeliveredGiftItemDefName ) + { + m_pDeliveredGiftItemDef = GetItemSchema()->GetItemDefinitionByName( m_pszDeliveredGiftItemDefName ); + } + + // We're done with this value. + m_pszDeliveredGiftItemDefName = NULL; + + return IEconTool::BFinishInitialization(); +} + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +CEconTool_GiftWrap::CEconTool_GiftWrap( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, NULL, NULL, unCapabilities ) + , m_pszWrappedGiftItemDefName( NULL ) + , m_pWrappedGiftItemDef( NULL ) +{ + if ( pUsageKV ) + { + m_pszWrappedGiftItemDefName = pUsageKV->GetString( "wrapped_gift_item_def", NULL ); + } +} + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +bool CEconTool_GiftWrap::BFinishInitialization() +{ + // Now that we've finished parsing our definitions, look for a match. + if ( m_pszWrappedGiftItemDefName ) + { + m_pWrappedGiftItemDef = GetItemSchema()->GetItemDefinitionByName( m_pszWrappedGiftItemDefName ); + } + + // We're done with this value. + m_pszWrappedGiftItemDefName = NULL; + + return m_pWrappedGiftItemDef != NULL + && IEconTool::BFinishInitialization(); +} + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +bool CEconTool_GiftWrap::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + if ( pToolSubject->GetQuality() == AE_SELFMADE || + pToolSubject->GetQuality() == AE_COMMUNITY || + pToolSubject->GetQuality() == AE_CUSTOMIZED || + pToolSubject->GetQuality() == AE_NORMAL ) + { + return false; + } + + // If an item is currently trade restricted, other flags don't matter (I think). + if ( ( pToolSubject->GetUntradabilityFlags() & k_Untradability_Temporary ) != 0 ) + return false; + + // One item still has the gift wrap cap, which is the engagement ring ( Something Special For Someone Special (Tool) ). + // However, the can_gift_wrap cap shouldn't overcome temporary trade restrictions. + static CSchemaAttributeDefHandle pAttrDef_ToolNeedsGiftwrap( "tool needs giftwrap" ); + + const bool cbSubjectNeedsGiftWrap = pToolSubject->FindAttribute( pAttrDef_ToolNeedsGiftwrap ); + const bool cbSubjectCanTrade = pToolSubject->IsTradable(); + const bool cbSubjectCanProceed = cbSubjectNeedsGiftWrap || cbSubjectCanTrade; + + if ( !cbSubjectCanProceed ) + return false; + + static CSchemaAttributeDefHandle pAttrDef_CannotGiftwrap( "cannot giftwrap" ); + if ( pToolSubject->FindAttribute( pAttrDef_CannotGiftwrap ) ) + return false; + + return IEconTool::CanApplyTo( pTool, pToolSubject ); +} + +CEconTool_StrangeCountTransfer::CEconTool_StrangeCountTransfer( const char *pszTypeName, item_capabilities_t unCapabilities ) + : IEconTool( pszTypeName, NULL, NULL, unCapabilities ) +{ +#ifdef CLIENT_DLL + m_pItemSrc = NULL; + m_pItemDest = NULL; +#endif // CLIENT_DLL +} + +bool CEconTool_StrangeCountTransfer::AreItemsEligibleForStrangeCountTransfer( const IEconItemInterface *pItem1, const IEconItemInterface *pItem2 ) +{ + if ( !pItem1 || !pItem2 ) + return false; + + const char *pItem1Xifier = pItem1->GetItemDefinition()->GetXifierRemapClass(); + const char *pItem2Xifier = pItem2->GetItemDefinition()->GetXifierRemapClass(); + + // if no xifier class, check if the item defs are the same + if ( !pItem1Xifier || !pItem2Xifier ) + { + if ( pItem1->GetItemDefinition() != pItem2->GetItemDefinition() ) + { + return false; + } + } + else if ( V_stricmp( pItem1Xifier, pItem2Xifier ) != 0 ) + { + return false; + } + + // Item defs are compatabible, are there attributes? Check strange + // Check if they are both strange (have kill eater). Quality is less important + if ( !BIsItemStrange( pItem1) || !BIsItemStrange( pItem2 ) ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconTool_StrangePart::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + // Abort if for some reason we don't know what type of attribute we're trying to track. + static CSchemaAttributeDefHandle pAttrDef_StrangePartCounterID( "strange part new counter ID" ); + + float flNewScoreType; + if ( !FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pTool, pAttrDef_StrangePartCounterID, &flNewScoreType ) ) + return false; + + // Make sure we're "strange" in that we have at least one attribute tracking scores. We can't + // explicitly test for quality here because now we can have strange vintages, etc. + if ( GetKillEaterAttrCount() <= 0 ) + return false; + + if ( !pToolSubject->FindAttribute( GetKillEaterAttr_Score( 0 ) ) ) + return false; + + // Make sure the target item doesn't already have the property this tool is trying to + // apply, unless that counter is restricted, in which case we allow a second stat to be + // added with the same value. + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + float flScoreType; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pToolSubject, GetKillEaterAttr_Type( i ), &flScoreType ) && // if we have a counter in this slot... + RoundFloatToInt( flScoreType ) == RoundFloatToInt( flNewScoreType ) && // ...and it's counting the same thing... + !pToolSubject->FindAttribute( GetKillEaterAttr_Restriction( i ) ) ) // ...and that counter isn't restricted + { + return false; + } + } + + // Make sure we have at least one empty customizable attribute slot. + bool bFoundEmptyAttributeSlot = false; + + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + // Ignore non-user-customizable attributes. + if ( !GetKillEaterAttr_IsUserCustomizable( i ) ) + continue; + + // We expect to have both or neither of a user-customizable attribute. If this isn't the + // case the later logic will be wrong. + const bool bFoundTypeAttribute = pToolSubject->FindAttribute( GetKillEaterAttr_Type( i ) ); + Assert( bFoundTypeAttribute == pToolSubject->FindAttribute( GetKillEaterAttr_Score( i ) ) ); + + if ( !bFoundTypeAttribute ) + { + bFoundEmptyAttributeSlot = true; + break; + } + } + + if ( !bFoundEmptyAttributeSlot ) + return false; + + // Check to make sure the definition of the item we're trying to apply to has the tags we need + // to match and doesn't have any tags that we need to avoid. We use these to avoid tracking + // damage done for a medigun, etc. + const GameItemDefinition_t *pSubjectItemDef = pToolSubject->GetItemDefinition(); + if ( !pSubjectItemDef ) + return false; + +#if defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) + // Strange Cosmetics can take on any part + if ( pSubjectItemDef->GetDefaultLoadoutSlot() == LOADOUT_POSITION_MISC ) + { + return true; + } +#endif + + FOR_EACH_VEC( m_RequiredTags.GetTagsList(), i ) + { + if ( !pSubjectItemDef->HasEconTag( m_RequiredTags.GetTagsList()[i] ) ) + return false; + } + + FOR_EACH_VEC( m_RequiredMissingTags.GetTagsList(), i ) + { + if ( pSubjectItemDef->HasEconTag( m_RequiredMissingTags.GetTagsList()[i] ) ) + return false; + } + + return IEconTool::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static const char *s_pszStrangeRestrictionTypes[] = +{ + "", // kStrangeEventRestriction_None + "victim_account_id", // kStrangeEventRestriction_VictimSteamAccount +#if defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) + "map", // kStrangeEventRestriction_Map + "competitive", // kStrangeEventRestriction_Competitive +#endif // defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) +}; + +COMPILE_TIME_ASSERT( ARRAYSIZE( s_pszStrangeRestrictionTypes ) == kStrangeEventRestrictionCount ); + +CEconTool_StrangePartRestriction::CEconTool_StrangePartRestriction( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) + , m_eRestrictionType( kStrangeEventRestriction_None ) // default-initialize to failure + , m_unRestrictionValue( (unsigned int)-1 ) +{ + Assert( pUsageKV != NULL ); + + // Parse our restriction type from a string. Anything invalid here will be handled below in the IsValid() + // check. + const int iRestrictionType = StringFieldToInt( pUsageKV->GetString( "restriction_type", "" ), &s_pszStrangeRestrictionTypes[0], ARRAYSIZE( s_pszStrangeRestrictionTypes ) ); + if ( iRestrictionType > 0 ) + { + m_eRestrictionType = (strange_event_restriction_t)iRestrictionType; + } + + // We'll use this value later from inside BFinishInitialization(). We may have to look at other + // values from parts of the schema that haven't been parsed yet, like map or item names. + m_pszRestrictionValue = pUsageKV->GetString( "restriction_value", NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconTool_StrangePartRestriction::BFinishInitialization() +{ + // Run different parsers based on our restriction type. + switch ( m_eRestrictionType ) + { + // We don't expect anything for our sub-value when dealing with Steam accounts. Asserting based on + // bad data is dumb but we don't really have any way of sending an error all the way up the tree. + case kStrangeEventRestriction_None: + case kStrangeEventRestriction_VictimSteamAccount: + if ( m_pszRestrictionValue ) + return false; + + m_unRestrictionValue = 0; + break; + +#if defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) + case kStrangeEventRestriction_Map: + { + if ( !m_pszRestrictionValue ) + return false; + + const MapDef_t *pSchemaMap = GetItemSchema()->GetMasterMapDefByName( m_pszRestrictionValue ); + if ( !pSchemaMap ) + return false; + + m_unRestrictionValue = pSchemaMap->m_nDefIndex; + } + break; + case kStrangeEventRestriction_Competitive: + { + if (!m_pszRestrictionValue) + return false; + + // Season string to int + m_unRestrictionValue = V_atoi(m_pszRestrictionValue); + } + break; +#endif // defined( TF_DLL ) || defined( TF_GC_DLL ) || defined( TF_CLIENT_DLL ) + + default: + AssertMsg1( false, "CEconTool_StrangePartRestriction() doesn't understand how to parse restriction type %i.", m_eRestrictionType ); + return false; + } + + // We're done with this value now. + m_pszRestrictionValue = NULL; + + return m_eRestrictionType != kStrangeEventRestriction_None + && m_unRestrictionValue != (unsigned int)-1 + && IEconTool::BFinishInitialization(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconTool_StrangePartRestriction::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pTool->GetItemDefinition() ); + Assert( pToolSubject ); + + if ( !IEconTool::CanApplyTo( pTool, pToolSubject ) ) + return false; + + // Abort if for some reason we don't know what type of restriction we're trying to apply. + if ( m_eRestrictionType == kStrangeEventRestriction_None ) + return false; + + // Abort if we don't have our tool information. We'll need this below to avoid duplicating + // scores/restrictions. + const CEconTool_StrangePartRestriction *pToolRestriction = pTool->GetItemDefinition()->GetTypedEconTool<CEconTool_StrangePartRestriction>(); + if ( !pToolRestriction ) + return false; + + // Make sure we're "strange" in that we have at least one attribute tracking scores. We can't + // explicitly test for quality here because now we can have strange vintages, etc. + if ( GetKillEaterAttrCount() <= 0 ) + return false; + + if ( !pToolSubject->FindAttribute( GetKillEaterAttr_Score( 0 ) ) ) + return false; + + // Look through all of the strange attributes on this item. We're looking for a slot that + // has a score that we're tracking where that score doesn't already have a restriction. + // If we find one, we can restrict that particular slot as long as doing so wouldn't generate + // a duplicate score (ie., "soldier kills on Hightower"). We don't need to enumerate them + // here, just find existence/nonexistence of at least one valid target. + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( GetItemSchema()->BCanStrangeFilterApplyToStrangeSlotInItem( pToolRestriction->GetRestrictionType(), pToolRestriction->GetRestrictionValue(), pToolSubject, i, NULL ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconTool_ItemDynamicRecipe::CEconTool_ItemDynamicRecipe( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) +{ + COMPILE_TIME_ASSERT( sizeof( attrib_value_t ) == sizeof( uint32 ) ); + COMPILE_TIME_ASSERT( sizeof( attrib_value_t ) == sizeof( float ) ); + + if ( pUsageKV ) + { + BInitFromKV( pUsageKV, &m_vecErrors ); + } +} + +const char* CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::m_pszUseParentNameIdentifier = "use_parents_item_def"; + +//----------------------------------------------------------------------------- +// Purpose: Make sure each of our components are fully formed. Return false +// if any components fail BFinishInitialization_Internal() +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::BFinishInitialization() +{ + CBaseRecipeComponent::ComponentAttribVector_t attribVec; + + FOR_EACH_VEC( m_vecComponents, i ) + { + m_vecComponents[i]->BFinishInitialization_Internal( &m_vecErrors, &attribVec ); + } + + // Emit any errors we've accumulated during initialization + FOR_EACH_VEC( m_vecErrors, i ) + { +#ifdef GC_DLL + EmitError( SPEW_GC, "%s\n", m_vecErrors[i].Get() ); +#else + AssertMsg1( 0, "%s\n", m_vecErrors[i].Get() ); +#endif + } + + if( m_vecErrors.Count() > 0 ) + return false; + + // Make sure we have at least one item required + return IEconTool::BFinishInitialization(); +} + +//----------------------------------------------------------------------------- +// Purpose: Iterate through attributes on pTool to see if any can accept pToolSubject. +// Return true if any attributes match. +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + // Iterate through all the attributes on the tool and see if the subject item matches + // any of the recipe component attributes + CRecipeComponentMatchingIterator matchingIterator( pTool, pToolSubject ); + pTool->IterateAttributes( &matchingIterator ); + + const CUtlVector< const CEconItemAttributeDefinition* >& vecMatchingAttribs = matchingIterator.GetMatchingComponentInputs(); + // No matches, can't apply! + if( vecMatchingAttribs.Count() == 0 ) + { + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::CBaseRecipeComponent( bool bIsOutput, const CBaseRecipeComponent* pParent ) + : m_bIsOutput( bIsOutput ) + , m_flChanceOfApplying( 1.f ) + , m_pParent( pParent ) + , m_flTotalWeights( 0.f ) + , m_eQuality( AE_UNDEFINED ) + , m_attributesMatchingType( ATTRIBUTES_MATCH_NONE ) +{} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::~CBaseRecipeComponent() +{ + m_vecAdditionalComponents.PurgeAndDeleteElements(); +} + +//----------------------------------------------------------------------------- +// Purpose: Roll chance of component applying to the tool +//----------------------------------------------------------------------------- +#ifdef GC_DLL +int CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::RollCount() const +{ + if( m_vecCountChances.Count() == 0 ) + return 1; + + float flRand = RandomFloat( 0.f, 1.f ) * m_flTotalWeights; + float flAccum = 0.f; + + // Go through and see which counts gets rolled + FOR_EACH_VEC( m_vecCountChances, i ) + { + const CountChance_t& countChance = m_vecCountChances[i]; + + flAccum += countChance.m_flChance; + if ( flRand <= flAccum ) + { + // Winner! Roll within its range + return RandomInt( countChance.m_nMinCount, countChance.m_nMaxCount ); + } + } + + AssertMsg( 0, "Failed to generate a count for recipe component. Defaulting to 1" ); + return 1; +} + +bool CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::RollChanceOfApplying() const +{ + // Guaranteed! + if( m_flChanceOfApplying == 1.f ) + return true; + + return RandomFloat() < m_flChanceOfApplying; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Set chance for attributes to apply. +//----------------------------------------------------------------------------- +void CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::SetChanceOfApplying( float flChance ) +{ + Assert( flChance >= 0.f && flChance <= 1.f ); + clamp( flChance, 0.f, 1.f ); + m_flChanceOfApplying = flChance; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::BFinishInitialization_Internal( CUtlVector<CUtlString>* pVecErrors, ComponentAttribVector_t* pAttribVec ) +{ + FOR_EACH_VEC( m_vecAdditionalComponents, i ) + { + SCHEMA_INIT_SUBSTEP( m_vecAdditionalComponents[i]->BFinishInitialization_Internal( pVecErrors, pAttribVec ) ); + } + + return SCHEMA_INIT_SUCCESS(); +} + +void CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::GetIsGuaranteed( int &nFlags ) const +{ + // If we've got a 100% chance of applying, or we're the root (no parent) then we + // can mark ourselves as guaranteed and continue to check our children to see if + // any of them are guaranteed as well. + if ( m_flChanceOfApplying == 1.f || !m_pParent ) + { + nFlags |= GetIsOutput() ? GUARANTEED_OUTPUT : GUARANTEED_INPUT; + + FOR_EACH_VEC( m_vecAdditionalComponents, i ) + { + m_vecAdditionalComponents[i]->GetIsGuaranteed( nFlags ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentDefinedItem::CDynamicRecipeComponentDefinedItem( bool bIsOutput, const CBaseRecipeComponent* pParent ) + : CBaseRecipeComponent( bIsOutput, pParent ) +{} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentDefinedItem::~CDynamicRecipeComponentDefinedItem() +{} + +//----------------------------------------------------------------------------- +// Purpose: Make sure m_strName refers to an actual item def. Return false +// if it doesn not. Child components are allowed to have "use_parents_item_def" +// as their item name. +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentDefinedItem::BFinishInitialization_Internal( CUtlVector<CUtlString>* pVecErrors, ComponentAttribVector_t* pAttribVec ) +{ + // Go through and make sure we have a bit of information that let's us describe an item + bool bAnyDataSet = false; + + // Item def? + if ( m_pParent == NULL && GetItemSchema()->GetItemDefinitionByName( m_strName ) ) + { + bAnyDataSet = true; + } + else if ( BStringsEqual( m_strName, m_pszUseParentNameIdentifier ) || GetItemSchema()->GetItemDefinitionByName( m_strName ) ) + { + bAnyDataSet = true; + } + + // Quality? + if ( m_eQuality != AE_UNDEFINED ) + { + bAnyDataSet = true; + } + + // Attributes? + if ( m_vecDynamicAttributes.Count() ) + { + bAnyDataSet = true; + } + + // We better have one of the above + SCHEMA_INIT_CHECK( bAnyDataSet, "Not enough data to describe component" ); + +#ifdef GC_DLL + // Get next available attrib def for defining the item + CEconItemAttributeDefinition* pAttribDef = GetNextAvailableAttributeWithBaseName( GetAttributeName(), pAttribVec ); + SCHEMA_INIT_CHECK( pAttribDef, "Too many potential components" ); + if( pAttribDef ) + { + pAttribVec->AddToTail( pAttribDef ); + } +#endif + + SCHEMA_INIT_SUBSTEP( BaseClass::BFinishInitialization_Internal( pVecErrors, pAttribVec ) ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Parse out our item definition name and quality. Return false if +// either does not exist +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentDefinedItem::ParseKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ) +{ + bool bNoItemDef = !!pKV->FindKey( "no_item_def" ); + bool bItemName = !!pKV->FindKey( "item_name" ); + + // Make sure only one of the above is set + SCHEMA_INIT_CHECK( bNoItemDef != bItemName, + "Both \"no_item_def\" and \"item_name\" specified in component." ); + + // Make sure at least one of the above is set + SCHEMA_INIT_CHECK( bNoItemDef || bItemName, + "Neither \"no_item_def\" or \"item_name\" specified in component." ); + + // If they specified an item name, then we need to grab it + if ( bItemName ) + { + m_strName = pKV->GetString( "item_name", NULL ); + } + + return BaseClass::ParseKV( pKV, pVecErrors ); +} + +//----------------------------------------------------------------------------- +// Purpose: Convert ourselves into an attribute. Return false if our encoded +// attributes exceed the allocated space for attributes +//----------------------------------------------------------------------------- +#ifdef GC_DLL +bool CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentDefinedItem::AddRecipeComponentAsAttribute( CEconItem *pItem, const CEconGameAccount *pGameAccount ) const +{ + // Check if we should even apply + if( !RollChanceOfApplying() ) + return true; + + // Gather up all the current attributes on the item + ComponentAttribVector_t attribVec; + CRecipeComponentMatchingIterator matchingIterator( pItem, NULL ); + pItem->IterateAttributes( &matchingIterator ); + attribVec.AddVectorToTail( matchingIterator.GetMatchingComponentInputs() ); + attribVec.AddVectorToTail( matchingIterator.GetMatchingComponentOutputs() ); + + // Get next available attrib def for defining the item + const CEconItemAttributeDefinition* pAttribDef = GetNextAvailableAttributeWithBaseName( GetAttributeName(), &attribVec ); + if( !pAttribDef ) + return false; + + uint32 nFlags = 0; + CAttribute_DynamicRecipeComponent typedValue; + + // Check if our item name is specified to use our parent's + const char* pszItemDefName = m_strName; + if ( m_strName && m_strName[0] ) + { + if( BStringsEqual( m_pszUseParentNameIdentifier, pszItemDefName ) && m_pParent ) + { + // It's only possible to have another CDynamicRecipeComponentDefinedItem as a parent + const CDynamicRecipeComponentDefinedItem* pParentDefinedItemComponent = dynamic_cast< const CDynamicRecipeComponentDefinedItem* >( m_pParent ); + AssertMsg( pParentDefinedItemComponent, "Parent attribute passed into defined item component is not a defined item component" ); + if( !pParentDefinedItemComponent ) + { + return false; + } + // Adopt our parent's item name + pszItemDefName = pParentDefinedItemComponent->m_strName; + } + + // Make sure this item def exists + CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinitionByName( pszItemDefName ); + AssertMsg1( pItemDef, "No item def named %s found when applying defined item component", pszItemDefName ); + if( !pItemDef ) + { + return false; + } + + // Set the item def + typedValue.set_def_index( (uint32)pItemDef->GetDefinitionIndex() ); + nFlags |= DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET; + } + + // Set the quality, if we're supposed to + if ( m_eQuality != AE_UNDEFINED ) + { + typedValue.set_item_quality( (uint32)m_eQuality ); + nFlags |= DYNAMIC_RECIPE_FLAG_PARAM_QUALITY_SET; + } + + nFlags |= m_bIsOutput ? DYNAMIC_RECIPE_FLAG_IS_OUTPUT : 0; + + if ( m_attributesMatchingType == ATTRIBUTES_MATCH_ALL ) + { + nFlags |= DYNAMIC_RECIPE_FLAG_PARAM_ATTRIBUTE_SET_ALL; + } + else if ( m_attributesMatchingType == ATTRIBUTES_MATCH_ANY ) + { + nFlags |= DYNAMIC_RECIPE_FLAG_PARAM_ATTRIBUTE_SET_ANY; + } + + // Make sure any of the flags (besides the output flag) is set + if ( ( nFlags & ~DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) == 0 ) + { + AssertMsg(0, "Created component without any data flags set!" ); + return false; + } + + typedValue.set_component_flags( nFlags ); + typedValue.set_num_required( RollCount() ); + typedValue.set_num_fulfilled( 0 ); + + // Write out attribute all attribute indexes and values, separated by what we expect to be an invalid character sequence + CUtlString strAttribs; + FOR_EACH_VEC( m_vecDynamicAttributes, i ) + { + if( i != 0 ) + { + strAttribs.Append( g_pszAttrEncodeSeparator ); + } + // Convert all of our attributes into a string. + strAttribs.Append( CFmtStr( "%d", m_vecDynamicAttributes[i].m_AttrIndex ) ); + strAttribs.Append( g_pszAttrEncodeSeparator ); + strAttribs.Append( m_vecDynamicAttributes[i].m_strAttrData.Get() ); + } + + // Make sure we're not too long + if( strAttribs.Length() >= 1024 ) + { + AssertMsg1( 0, "String-encoded attributes exceeds 1024 characters, when encoding component %s", m_strName.Get() ); + return false; + } + + // Set it in there! + typedValue.set_attributes_string( strAttribs.Get() ); + + // Check to see if we're about to create a duplicate. There's no need to spend another + // attribute to describe the same item. Let's instead just increase the count on the + // already-existing attribute. + FOR_EACH_VEC( attribVec, i ) + { + CAttribute_DynamicRecipeComponent existingValue; + if( pItem->FindAttribute( attribVec[i], &existingValue ) ) + { + if( typedValue.def_index() == existingValue.def_index() && + typedValue.item_quality() == existingValue.item_quality() && + typedValue.component_flags() == existingValue.component_flags() && + typedValue.attributes_string() == existingValue.attributes_string() ) + { + pAttribDef = attribVec[i]; + existingValue.set_num_required( existingValue.num_required() + typedValue.num_required() ); + typedValue = existingValue; + break; + } + } + } + + + pItem->SetDynamicAttributeValue( pAttribDef, typedValue ); + + // Go through and add any additional components that depend on this component + FOR_EACH_VEC( m_vecAdditionalComponents, i ) + { + CBaseRecipeComponent* pAdditionalComponent = m_vecAdditionalComponents[i]; + pAdditionalComponent->AddRecipeComponentAsAttribute( pItem, pGameAccount ); + } + + return true; +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::CDynamicRecipeComponentLootList( bool bIsOutput, const CBaseRecipeComponent* pParent ) + : CEconTool_ItemDynamicRecipe::CBaseRecipeComponent( bIsOutput, pParent ) +#ifdef GC_DLL + , m_eUniqueness( UNIQUE_AMONG_NOTHING ) +#endif +{} + + + +CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::~CDynamicRecipeComponentLootList() +{} + +//----------------------------------------------------------------------------- +// Purpose: Make sure m_strName actually refers to a lootlist +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::BFinishInitialization_Internal( CUtlVector<CUtlString>* pVecErrors, ComponentAttribVector_t* pAttribVec ) +{ + // The game doesn't have all of the lootlists +#ifdef GC_DLL + SCHEMA_INIT_CHECK( GEconItemSchema().GetLootListByName( m_strName ), + "CDynamicRecipeComponentLootList has invalid loot list: %s", m_strName.Get() ); + + // Get next available attrib def for defining the item + CEconItemAttributeDefinition* pAttribDef = GetNextAvailableAttributeWithBaseName( GetAttributeName(), pAttribVec ); + SCHEMA_INIT_CHECK( pAttribDef, "Too many potential recipe components!" ); + if( pAttribDef ) + { + pAttribVec->AddToTail( pAttribDef ); + } + + // Inputs are not allowed to be marked UNIQUE_AMONG_OUTPUTS. Rather, mark the outputs UNIQUE_AMONG_INPUTS + SCHEMA_INIT_CHECK( !(m_eUniqueness == UNIQUE_AMONG_OUTPUTS && !GetIsOutput() ), "Input component marked to be unique among inputs. Not supported!" ); +#endif + + // Skip defined item + SCHEMA_INIT_SUBSTEP( BaseClass::BFinishInitialization_Internal( pVecErrors, pAttribVec ) ); + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Parse in our lootlist name and quality. Return false if either doesn't exist +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::ParseKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ) +{ + const char* pszLootListName = pKV->GetString( "lootlist_name" ); + SCHEMA_INIT_CHECK( pszLootListName != NULL, + "Missing lootlist name in lootlist recipe component" ); + + m_strName = pszLootListName; + + bool bBaseResult = BaseClass::ParseKV( pKV, pVecErrors ); + +#ifdef GC_DLL + // By default, outputs try to avoid rolling as input, and inputs dont try to avoid anything + m_eUniqueness = GetIsOutput() ? UNIQUE_AMONG_INPUTS : UNIQUE_AMONG_NOTHING; + const char* pszUniqueness = pKV->GetString( "uniqueness" ); + if ( !V_stricmp( "unique_among_inputs", pszUniqueness ) ) + { + m_eUniqueness = UNIQUE_AMONG_INPUTS; + } + else if ( !V_stricmp( "unique_among_outputs", pszUniqueness ) ) + { + m_eUniqueness = UNIQUE_AMONG_OUTPUTS; + } + else if ( !V_stricmp( "unique_among_everything", pszUniqueness ) ) + { + m_eUniqueness = UNIQUE_AMONG_EVERYTHING; + } +#endif + + return bBaseResult; +} + + +//----------------------------------------------------------------------------- +// Purpose: Roll our item definition, then call our base to convert ourselves into an attribute +//----------------------------------------------------------------------------- +#ifdef GC_DLL +bool CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::AddRecipeComponentAsAttribute( CEconItem *pItem, const CEconGameAccount *pGameAccount ) const +{ + // Check if we should even apply + if( !RollChanceOfApplying() ) + return true; + + // See if there's any item defs we should try to avoid + CRecipeComponentInputDefIndexIterator inputIterator( m_eUniqueness ); + pItem->IterateAttributes( &inputIterator ); + + const char* pszItemDefName = NULL; + // Roll the item and any additional attributes + CUtlVector< StringEncodedAttribute_t > vecLootlistGeneratedAttributes; + if (!RollLootlistItemAndAttributes( vecLootlistGeneratedAttributes, &pszItemDefName, &inputIterator.GetMatchingComponentInputs(), pGameAccount ) ) + { + return false; + } + vecLootlistGeneratedAttributes.AddVectorToTail( m_vecDynamicAttributes ); + + // Create a temporary defined item component based on the lootlist roll + CDynamicRecipeComponentDefinedItem definedItem( m_bIsOutput, this ); + definedItem.m_eQuality = m_eQuality; + definedItem.m_strName = pszItemDefName; + definedItem.m_attributesMatchingType = m_attributesMatchingType; + definedItem.m_vecDynamicAttributes = vecLootlistGeneratedAttributes; + definedItem.m_vecCountChances = m_vecCountChances; + definedItem.m_flTotalWeights = m_flTotalWeights; + + // Write out this defined item + definedItem.AddRecipeComponentAsAttribute( pItem, pGameAccount ); + + // Create any additional components that depend on this component existing + FOR_EACH_VEC( m_vecAdditionalComponents, i ) + { + CBaseRecipeComponent* pAdditionalComponent = m_vecAdditionalComponents[i]; + // Update the parent to be the item we generated + pAdditionalComponent->SetParent( &definedItem ); + pAdditionalComponent->AddRecipeComponentAsAttribute( pItem, pGameAccount ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Roll our item definition, then call our base to convert ourselves into an attribute +//----------------------------------------------------------------------------- +class CAttributeToStringIterator : public IEconItemUntypedAttributeIterator +{ +public: + CAttributeToStringIterator( CUtlVector< CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::StringEncodedAttribute_t >& vecAdditionalAttribs, CEconItem* pItem ) + : m_vecAdditionalAttribs( vecAdditionalAttribs ) + , m_pItem( pItem ) + {} + + virtual bool OnIterateAttributeValueUntyped( const CEconItemAttributeDefinition *pAttrDef ) + { + const CEconItem::attribute_t *pAttrInternalData = m_pItem->FindDynamicAttributeInternal( pAttrDef ); + + // Only export attributes that we have dynamic data for. + if ( pAttrInternalData ) + { + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + + // Set the definition index + auto& attrib = m_vecAdditionalAttribs[ m_vecAdditionalAttribs.AddToTail() ]; + attrib.m_AttrIndex = pAttrDef->GetDefinitionIndex(); + + // Convert the value to a string + std::string sAttrValue; + pAttrType->ConvertEconAttributeValueToString( pAttrDef, pAttrInternalData->m_value, &sAttrValue ); + + Assert( sAttrValue.length() > 0 ); + attrib.m_strAttrData.Set( sAttrValue.c_str() ); + } + + return true; + } + +private: + + CUtlVector< CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::StringEncodedAttribute_t >& m_vecAdditionalAttribs; + CEconItem* m_pItem; +}; + +//----------------------------------------------------------------------------- +// Purpose: Roll our item definition. This will give us a list of item definition. From this list +// we take the first one and set our item def name and quality to be its. We then take any +// attributes that it may have rolled and add them to our attributes as well. Return false +// if any of these steps fail. +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CDynamicRecipeComponentLootList::RollLootlistItemAndAttributes( CUtlVector< StringEncodedAttribute_t >& vecAdditionalAttribs + , const char** pszDefName + , const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs + , const CEconGameAccount *pGameAccount ) const +{ + const CEconLootListDefinition* pLootList = GEconItemSchema().GetLootListByName( m_strName ); + + if( !pLootList ) + { + AssertMsg1( 0, "Lootlist %s not found when adding dynamic attribute", m_strName.Get() ); + return false; + } + + CUtlVector<CEconItem *> vecRolledItems; + // Roll our items + CDefaultUniformRandomStream RandomStream; + if ( !pLootList->BGenerateSingleRollRandomItems( pGameAccount, false, &vecRolledItems ) ) + { + AssertMsg1( 0, "Error generating item defs from lootlist \"%s\"", m_strName.Get() ); + return false; + } + + // We're just going to use the first one + CEconItem* pGeneratedItem = vecRolledItems.Head(); + + // Set our name and quality + (*pszDefName) = pGeneratedItem->GetItemDefinition()->GetDefinitionName(); + const_cast<CDynamicRecipeComponentLootList*>(this)->m_eQuality = (EEconItemQuality)pGeneratedItem->GetQuality(); + + // Sniff and encode the attributes + CAttributeToStringIterator attrToString( vecAdditionalAttribs, pGeneratedItem ); + pGeneratedItem->IterateAttributes( &attrToString ); + + // Cleanup + for( auto pItem : vecRolledItems ) + { + delete pItem; + } + + return true; +} + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Delete all the things +//----------------------------------------------------------------------------- +CEconTool_ItemDynamicRecipe::~CEconTool_ItemDynamicRecipe() +{ + m_vecComponents.PurgeAndDeleteElements(); +} + +CEconTool_ItemDynamicRecipe::CRecipeComponentInputDefIndexIterator::CRecipeComponentInputDefIndexIterator( EItemDefUniqueness_t eUniqueness ) + : m_eUniqueness( eUniqueness ) +{} + +bool CEconTool_ItemDynamicRecipe::CRecipeComponentInputDefIndexIterator::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) +{ + // We dont care + if ( m_eUniqueness == CEconTool_ItemDynamicRecipe::UNIQUE_AMONG_NOTHING ) + return true; + + // Only check against outputs + if ( m_eUniqueness == CEconTool_ItemDynamicRecipe::UNIQUE_AMONG_OUTPUTS && !( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) ) + return true; + + // Only check against inputs + if ( m_eUniqueness == CEconTool_ItemDynamicRecipe::UNIQUE_AMONG_INPUTS && value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) + return true; + + // Add this item def if we haven't seen it + if ( m_vecInputItemDefs.Find( value.def_index() ) == m_vecInputItemDefs.InvalidIndex() ) + { + m_vecInputItemDefs.AddToTail( value.def_index() ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Parse all of our inputs then outputs +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::BInitFromKV( KeyValues *pKVDef, CUtlVector<CUtlString> *pVecErrors ) +{ + // Parse the components blocks + SCHEMA_INIT_SUBSTEP( CBaseRecipeComponent::ParseComponentsBlock( pKVDef, m_vecComponents, pVecErrors, NULL ) ); + + // Sort the component vector such that inputs go first + struct RecipeComponentSorter + { + static int SortRecipeComponentVector( CEconTool_ItemDynamicRecipe::CBaseRecipeComponent* const *pComponent1, CEconTool_ItemDynamicRecipe::CBaseRecipeComponent* const *pComponent2 ) + { + return (*pComponent1)->GetIsOutput() && !(*pComponent2)->GetIsOutput(); + } + }; + + m_vecComponents.Sort( &RecipeComponentSorter::SortRecipeComponentVector ); + +#ifdef GC_DLL + int nFlags = 0; + FOR_EACH_VEC( m_vecComponents, i ) + { + m_vecComponents[i]->GetIsGuaranteed( nFlags ); + } + + SCHEMA_INIT_CHECK( nFlags & GUARANTEED_OUTPUT, "No guaranteed outputs for dynamic recipe" ); + SCHEMA_INIT_CHECK( nFlags & GUARANTEED_INPUT, "No guaranteed inputs for dynamic recipe" ); +#endif + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Parse the component blocks, determining if this component is an input or output +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::ParseComponentsBlock( KeyValues *pKV, CUtlVector<CBaseRecipeComponent*>& vecComponents, CUtlVector<CUtlString> *pVecErrors, const CBaseRecipeComponent* pParent ) +{ + // The components block doesn't exist on the client + + KeyValues *pKVComponents = pKV->FindKey( "components" ); +#ifdef GC_DLL + SCHEMA_INIT_CHECK( pKVComponents || pParent, "Failed to parse components block in dynamic recipe" ); +#endif + if ( pKVComponents ) + { + // There's duplicate code when we read in like this, but it makes the + // item defs much easier to read + + // Parse all the inputs + KeyValues *pKVParameters = pKVComponents->FindKey( "input" ); + if( pKVParameters ) + { + ParseComponents( pKVParameters, vecComponents, false, pVecErrors, pParent ); + } + +#ifdef GC_DLL + // Parse all the outputs + pKVParameters = pKVComponents->FindKey( "output" ); + if( pKVParameters ) + { + ParseComponents( pKVParameters, vecComponents, true, pVecErrors, pParent ); + } +#endif + } + + + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Create the appropriate component types and have them parse themselves. +// Returns true if at least one of the components parsed has a 100% chance +// of applying, and we don't have a parent +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::ParseComponents( KeyValues *pKV, CUtlVector<CBaseRecipeComponent*>& vecComponents, bool bIsOutput, CUtlVector<CUtlString> *pVecErrors, const CBaseRecipeComponent* pParent ) +{ + // Go through each entry in the tool + KeyValues *pEntry = pKV->GetFirstSubKey(); + while( pEntry ) + { + // Find which type is being specified. + CBaseRecipeComponent *pComponent = NULL; + if( pEntry->FindKey( "lootlist_name" ) ) + { + pComponent = vecComponents[vecComponents.AddToTail( new CDynamicRecipeComponentLootList( bIsOutput, pParent ) )]; + } + else if ( pEntry->FindKey( "item_name" ) || pEntry->FindKey( "no_item_def" ) ) + { + pComponent = vecComponents[vecComponents.AddToTail( new CDynamicRecipeComponentDefinedItem( bIsOutput, pParent ) )]; + } + else + { + SCHEMA_INIT_CHECK( false, "Unrecognized recipe component type!" ); + } + + // Now that we've got the right type, parse! + if( pComponent ) + { + pComponent->ParseKV( pEntry, pVecErrors ); + } + + pEntry = pEntry->GetNextKey(); + } + + return SCHEMA_INIT_SUCCESS(); +} + +//----------------------------------------------------------------------------- +// Purpose: Parse in all of the attributes for this component. Attributes have +// already been parsed in, so we can immediately check if the attribute exists. +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::ParseKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ) +{ + // Get the quality string + const char* pszQuality = pKV->GetString( "quality", NULL ); + if ( pszQuality ) + { + // Convert the quality string to a item quality + m_eQuality = EconQuality_GetQualityFromString( pszQuality ); + SCHEMA_INIT_CHECK( m_eQuality != AE_UNDEFINED, "Invalid item quality \"%s\" specified for component \"%s\"", pszQuality, m_strName.Get() ); + } + + const char* pszAttributesMatchingType = pKV->GetString( "attributes_matching_type", NULL ); + if ( pszAttributesMatchingType ) + { + if ( !V_stricmp( pszAttributesMatchingType, "all" ) ) + { + m_attributesMatchingType = ATTRIBUTES_MATCH_ALL; + } + else if ( !V_stricmp( pszAttributesMatchingType, "any" ) ) + { + m_attributesMatchingType = ATTRIBUTES_MATCH_ANY; + } + else + { + SCHEMA_INIT_CHECK( 0, "Invalid attributes_matching_type \"%s\"", pszAttributesMatchingType ); + } + } + + // Parse all of our attributes + KeyValues* pKVAttribs = pKV->FindKey( "attributes" ); + if( pKVAttribs ) + { + FOR_EACH_SUBKEY( pKVAttribs, pKVAttribute ) + { + static_attrib_t staticAttrib; + + SCHEMA_INIT_SUBSTEP( staticAttrib.BInitFromKV_SingleLine( "attributes", pKVAttribute, pVecErrors ) ); + + const CEconItemAttributeDefinition * pAttrDef = staticAttrib.GetAttributeDefinition(); + SCHEMA_INIT_CHECK( pAttrDef != NULL, "Attribute index %i not found when specifying dynamic recipe", staticAttrib.iDefIndex ); + + StringEncodedAttribute_t& attrib = m_vecDynamicAttributes[ m_vecDynamicAttributes.AddToTail() ]; + attrib.m_AttrIndex = pAttrDef->GetDefinitionIndex(); + attrib.m_strAttrData = pKVAttribs->GetString( pKVAttribute->GetName(), "" ); + + Assert( !attrib.m_strAttrData.IsEmpty() ); + } + } + + // Get our chance of applying. Default to 100% + float flChance = pKV->GetFloat( "chance", 1.f ); + // Make sure it's in the range (0,1] + SCHEMA_INIT_CHECK( flChance > 0.f && flChance <= 1.f, "Recipe component chance to apply out of bounds: %f", flChance ); + SetChanceOfApplying( flChance ); + + // Read in the "counts" block if it exists + KeyValues *pKVCounts = pKV->FindKey( "counts" ); + if( pKVCounts ) + { + // Read check count entry + FOR_EACH_SUBKEY( pKVCounts, pKVEntry ) + { + // Split out count range of the format "#-#", or just "#" + CUtlStringList vecCount; + const char* pszCount = pKVEntry->GetName(); + V_SplitString( pszCount, "-", vecCount ); + SCHEMA_INIT_CHECK( vecCount.Count() == 1 || vecCount.Count() == 2, "Malformed count value: %s", pszCount ); + + CountChance_t& countChance = m_vecCountChances[ m_vecCountChances.AddToTail() ]; + + // Add up the chances + countChance.m_flChance = (float)pKVEntry->GetInt(); + m_flTotalWeights += countChance.m_flChance; + // Set the min and the max. If no max is specifid, then max is the min + countChance.m_nMinCount = V_atoi( vecCount[0] ); + countChance.m_nMaxCount = vecCount.Count() > 1 ? V_atoi( vecCount[1] ) : countChance.m_nMinCount; + // Make sure max >= min and min > 0 + SCHEMA_INIT_CHECK( countChance.m_nMaxCount >= countChance.m_nMinCount, "Recipe component count max is less than the min: %s", pszCount ); + SCHEMA_INIT_CHECK( countChance.m_nMinCount > 0, "Recipe component count min less than 0: %s", pszCount ); + } + } + + // Init any components we may have in us + SCHEMA_INIT_CHECK( ParseComponentsBlock( pKV, m_vecAdditionalComponents, pVecErrors, this ), "Failed to parse nested components block in dynamic recipe" ); + + return SCHEMA_INIT_SUCCESS(); +} + + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: This tool will apply recipe components as attributes. Go through each +// of our attributes, roll to see if it applies, and convert it to an attribute +// and add it to the item if we do. Return false if we ever fail. +//----------------------------------------------------------------------------- +bool CEconTool_ItemDynamicRecipe::BGenerateDynamicAttributes( CEconItem* pItem, const CEconGameAccount *pGameAccount ) const +{ + // Go through our inputs and write them out into attributes + FOR_EACH_VEC( m_vecComponents, i ) + { + if( !m_vecComponents[i]->AddRecipeComponentAsAttribute( pItem, pGameAccount ) ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Scan the item for numbered attributes with the passed in base name. +// Return the attribute of the next available index or NULL if there are +// none available +//----------------------------------------------------------------------------- +CEconItemAttributeDefinition* CEconTool_ItemDynamicRecipe::CBaseRecipeComponent::GetNextAvailableAttributeWithBaseName( const char* pszBaseAttribName, ComponentAttribVector_t* pAttribVec ) +{ + Assert( pAttribVec ); + if( !pAttribVec ) + return NULL; + + CEconItemAttributeDefinition *pAttribDef = NULL; + int i=1; + while( pAttribDef == NULL ) + { + const char* pszAttribName = CFmtStr( "%s %d", pszBaseAttribName, i++ ); + + CEconItemAttributeDefinition *pTempAttribDef = GEconItemSchema().GetAttributeDefinitionByName( pszAttribName ); + + // Check to see if this attribute we're talking about even exists + if( !pTempAttribDef ) + { + return NULL; + } + + // Check if the vector doesn't have this attribute. If not, it's available + if( pAttribVec->Find( pTempAttribDef ) == pAttribVec->InvalidIndex() ) + { + pAttribDef = pTempAttribDef; + } + } + + return pAttribDef; +} +#endif + +//----------------------------------------------------------------------------- +bool CEconTool_Xifier::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + const CEconItemDefinition *pSubjectItemDef = pToolSubject->GetItemDefinition(); + if ( !pSubjectItemDef ) + return false; + + // Check to make sure the definition of the item we're trying to apply to has the tags we need to match. + FOR_EACH_VEC( m_RequiredTags.GetTagsList(), i ) + { + if ( !pSubjectItemDef->HasEconTag( m_RequiredTags.GetTagsList()[i] ) ) + return false; + } + + // If this xifier has target restrictions, ensure our subject is one of them + if ( m_ItemDefTargetRestrictions.Count() > 0 ) + { + bool bPassed = false; + FOR_EACH_VEC( m_ItemDefTargetRestrictions, i ) + { + const CEconItemDefinition *pTargetItemDef = GetItemSchema()->GetItemDefinition( m_ItemDefTargetRestrictions[i] ); + if ( ItemDefMatch( pSubjectItemDef, pTargetItemDef ) ) + { + bPassed = true; + break; + } + } + + if ( bPassed == false ) + return false; + } + + // if rarity restriction, target needs rarity of this or lower + if ( m_ItemRarityRestriction != k_unItemRarity_Any ) + { + uint8 unSubjectRarity = pToolSubject->GetItemDefinition()->GetRarity(); + if ( unSubjectRarity == k_unItemRarity_Any || unSubjectRarity == 0 || unSubjectRarity > m_ItemRarityRestriction ) + return false; + + // needs to be equippable + static CSchemaAttributeDefHandle pAttribDef_StatModule( "weapon_uses_stattrak_module" ); + if ( !pToolSubject->FindAttribute( pAttribDef_StatModule ) ) + return false; + } + + // Check if we have a restriction as an attribute + static CSchemaAttributeDefHandle pAttribDef_ToolTargetItem( "tool target item" ); + float value; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pTool, pAttribDef_ToolTargetItem, &value ) ) + { + const CEconItemDefinition *pTargetDef = GetItemSchema()->GetItemDefinition( value ); + + // Check for a match (might have NULL here for target definition but that's safe to pass in) + if ( !ItemDefMatch( pSubjectItemDef, pTargetDef ) ) + { + return false; + } + } + + return IEconTool::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +bool CEconTool_Xifier::ItemDefMatch( const CEconItemDefinition* pTargetItemDef, const CEconItemDefinition* pSubjectItemDef ) const +{ + if ( pTargetItemDef && pSubjectItemDef ) + { + // Item def match counts + if ( pTargetItemDef == pSubjectItemDef ) + return true; + + // If these item defs have the same XifierRemapClass then they are allowed to match as well + if ( pTargetItemDef->GetXifierRemapClass() && *pTargetItemDef->GetXifierRemapClass() + && pSubjectItemDef->GetXifierRemapClass() && *pSubjectItemDef->GetXifierRemapClass() ) + { + if (BStringsEqual(pSubjectItemDef->GetXifierRemapClass(), pTargetItemDef->GetXifierRemapClass())) + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +bool CEconTool_Strangifier::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + // Do not allow for already strange items + if ( pToolSubject->GetQuality() == AE_STRANGE ) + return false; + + // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply + for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) + { + if ( pToolSubject->FindAttribute( GetKillEaterAttr_Score( i ) ) ) + { + return false; + } + } + + // Default rules + return CEconTool_Xifier::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +bool CEconTool_KillStreakifier::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + // Make sure the item doesn't already have an effect + static CSchemaAttributeDefHandle pAttribDef_KillStreakEffect( "killstreak tier" ); + float flEffectIndex = 0.0; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pToolSubject, pAttribDef_KillStreakEffect, &flEffectIndex ) ) + return false; + + // Default rules + return CEconTool_Xifier::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +bool CEconTool_Festivizer::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + const CEconItemDefinition *pSubjectItemDef = pToolSubject->GetItemDefinition(); + if ( !pSubjectItemDef ) + return false; + + // Make sure the item doesn't already have an effect + static CSchemaAttributeDefHandle pAttribDef_Festivizer( "is_festivized" ); + if ( FindAttribute( pToolSubject, pAttribDef_Festivizer ) ) + return false; + + // Default rules + return CEconTool_Xifier::CanApplyTo( pTool, pToolSubject ); +} + +bool CEconTool_Unusualifier::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + // don't stomp item that's already unusual + if ( pToolSubject->GetQuality() == AE_UNUSUAL ) + return false; + + // Default rules + return CEconTool_Xifier::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +bool CEconTool_ItemEaterRecharger::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + const CEconItemDefinition *pSubjectItemDef = pToolSubject->GetItemDefinition(); + if ( !pSubjectItemDef ) + return false; + + // Check to make sure the definition of the item we're trying to apply to has the tags we need to match. + FOR_EACH_VEC( m_RequiredTags.GetTagsList(), i ) + { + if ( !pSubjectItemDef->HasEconTag( m_RequiredTags.GetTagsList()[i] ) ) + return false; + } + + // If this eater has target restrictions, ensure our subject is one of them + if ( m_ItemDefTargetRestrictions.Count() > 0 ) + { + bool bPassed = false; + item_definition_index_t iSubject = pSubjectItemDef->GetDefinitionIndex(); + FOR_EACH_VEC( m_ItemDefTargetRestrictions, i ) + { + if ( m_ItemDefTargetRestrictions[i] == iSubject ) + { + bPassed = true; + break; + } + } + + if ( bPassed == false ) + return false; + } + + return IEconTool::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +int CEconTool_ItemEaterRecharger::GetChargesForItemDefId( item_definition_index_t defIndex ) const +{ + FOR_EACH_VEC( m_ItemDefTargetRestrictions, i ) + { + if ( m_ItemDefTargetRestrictions[i] == defIndex ) + { + return m_ItemDefTargetChargeValues[i]; + } + } + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconTool_UpgradeCard::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + const CEconItemDefinition *pSubjectItemDef = pToolSubject->GetItemDefinition(); + if ( !pSubjectItemDef ) + return false; + + // Abort if we're trying to apply to a base item. + if ( pSubjectItemDef->IsBaseItem() ) + return false; + + // Abort if for some reason we don't know what type of attribute we would attach. + if ( m_vecAttributes.Count() <= 0 ) + return false; + + // Make sure that none of the attributes we're going to try to apply already exist on the item. We don't + // allow double-stacking the same attribute partially for balance purposes, but also because the database + // back-end doesn't support two attributes of the same type on the same item. + FOR_EACH_VEC( m_vecAttributes, i ) + { + if ( pToolSubject->FindAttribute( m_vecAttributes[i].m_pAttrDef ) ) + return false; + } + + // Make sure the item that we're thinking of applying to has enough room to have another card's + // worth of items attached. We do this in sort of a roundabout way, by having the attributes themselves + // know whether they came from a card or not. + CCountUserGeneratedAttributeIterator countIterator; + pToolSubject->IterateAttributes( &countIterator ); + + if ( countIterator.GetCount() >= GetMaxCardUpgradesPerItem() ) + return false; + + // Check to make sure the definition of the item we're trying to apply to has the tags we need + // to match. + FOR_EACH_VEC( m_RequiredTags.GetTagsList(), i ) + { + if ( !pSubjectItemDef->HasEconTag( m_RequiredTags.GetTagsList()[i] ) ) + return false; + } + + return IEconTool::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconTool_ClassTransmogrifier::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + const GameItemDefinition_t *pSubjectItemDef = pToolSubject->GetItemDefinition(); + if ( !pSubjectItemDef ) + return false; + + // Abort if we're trying to apply to a base item. + if ( pSubjectItemDef->IsBaseItem() ) + return false; + + // Abort if we're trying to apply to a Self-Made or Community item + if ( pToolSubject->GetQuality() == AE_SELFMADE || + pToolSubject->GetQuality() == AE_COMMUNITY ) + { + return false; + } + + // Abort if we somehow got here before we know what class we were trying to produce items for. + if ( m_iClass <= 0 || m_iClass >= LOADOUT_COUNT ) + return false; + + // Check to make sure the definition of the item we're trying to apply to has the tags we need + // to match. + FOR_EACH_VEC( m_RequiredTags.GetTagsList(), i ) + { + if ( !pSubjectItemDef->HasEconTag( m_RequiredTags.GetTagsList()[i] ) ) + return false; + } + + return IEconTool::CanApplyTo( pTool, pToolSubject ); +} + +//----------------------------------------------------------------------------- +bool CEconTool_DuckToken::CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const +{ + Assert( pTool ); + Assert( pToolSubject ); + + static CSchemaAttributeDefHandle pAttrDef_DuckBadgeLevel( "duck badge level" ); + uint32 unOldBadgeLevel = 0; + if ( !FindAttribute( pToolSubject, pAttrDef_DuckBadgeLevel, &unOldBadgeLevel ) ) + return false; + + if ( unOldBadgeLevel >= 5 ) + return false; + + // Default rules + return IEconTool::CanApplyTo( pTool, pToolSubject ); +} + +//--------------------------------------------------------------------------------------- +// Purpose: given a tool and an item to apply the tool's effects upon, return true if the +// tool is allowed to affect the subject. This is used on the client for UI and +// on the GC for actual application validity testing. +//--------------------------------------------------------------------------------------- +/* static */ bool CEconSharedToolSupport::ToolCanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) +{ + if ( pTool == NULL || pToolSubject == NULL ) + return false; + + const GameItemDefinition_t *pToolDef = pTool->GetItemDefinition(); + if ( pToolDef == NULL ) + return false; + + // If we have a tool that's in escrow it can't be used on anything. + static CSchemaAttributeDefHandle pAttrib_ToolEscrowUntil( "tool escrow until date" ); + if ( pTool->FindAttribute( pAttrib_ToolEscrowUntil ) ) + return false; + + if ( pToolSubject->IsTemporaryItem() ) + return false; + + // Cannot modify preview items. Should be caught by temporary-item check above. + Assert( pToolSubject->GetOrigin() != kEconItemOrigin_PreviewItem ); + + const GameItemDefinition_t *pToolSubjectDef = pToolSubject->GetItemDefinition(); + if ( pToolSubjectDef == NULL ) + return false; + + if ( !ToolCanApplyToDefinition( pToolDef, pToolSubjectDef ) ) + return false; + + // If we can apply to the definition then we should be known to have valid tool data. + // If our tool has no tool metadata then we don't allow it to be applied to anything. + const IEconTool *pEconTool = pToolDef->GetEconTool(); + Assert( pEconTool ); + + return pEconTool->CanApplyTo( pTool, pToolSubject ); +} + +/* static */ bool CEconSharedToolSupport::ToolCanApplyToDefinition( const GameItemDefinition_t *pToolDef, const GameItemDefinition_t *pToolSubjectDef ) +{ + if ( !pToolDef || !pToolSubjectDef || !pToolDef->IsTool() ) + { + // not a tool + return false; + } + + // If our tool has no tool metadata then we don't allow it to be applied to anything. + const IEconTool *pEconTool = pToolDef->GetEconTool(); + if ( !pEconTool ) + return false; + + unsigned int unToolUsageCaps = 0; + if ( pEconTool->GetCapabilities() ) + { + for ( unsigned int i = 0; i < NUM_ITEM_CAPS; i++ ) + { + if ( pEconTool->GetCapabilities() & (1 << i) ) + { + unToolUsageCaps |= g_CapabilityApplicationMap[i]; + } + } + } + + // Check for base applicability of this tool to this object. + if ( (unToolUsageCaps & pToolSubjectDef->GetCapabilities()) == 0 ) + return false; + + // check to see if either the tool or the tool target have usage restriction + const IEconTool *pSubjectEconTool = pToolSubjectDef->GetEconTool(); + + if ( pSubjectEconTool ) + { + // If this tool can apply to anything then we don't care about the checks below + // making sure restrictions match. + const char *pszToolRestriction = BStringsEqual( pEconTool->GetUsageRestriction(), "any" ) + ? pSubjectEconTool->GetUsageRestriction() + : pEconTool->GetUsageRestriction(); + + if ( !BStringsEqual( pszToolRestriction, pSubjectEconTool->GetUsageRestriction() ) ) + return false; + } + + return true; +} + +// WARNING +// DO NOT USE THIS CODE IF YOUR TOOL HAS Attribute restrictions like "tool_target_item" or similar restriction attributes +/* static */ bool CEconSharedToolSupport::ToolCanApplyToBaseItem( const GameItemDefinition_t *pToolDef, const GameItemDefinition_t *pToolSubjectDef ) +{ + if ( !pToolSubjectDef ) + return false; + + // We are targetting the "Upgradeable" version of a base item and not a base item itself + if ( pToolSubjectDef->IsBaseItem() || pToolSubjectDef->IsHidden() || pToolSubjectDef->GetQuality() == AE_NORMAL || Q_strnicmp( pToolSubjectDef->GetDefinitionName(), "Upgradeable ", 12 ) ) + return false; + + return ToolCanApplyToDefinition( pToolDef, pToolSubjectDef ); +} diff --git a/game/shared/econ/econ_item_tools.h b/game/shared/econ/econ_item_tools.h new file mode 100644 index 0000000..b6adfe9 --- /dev/null +++ b/game/shared/econ/econ_item_tools.h @@ -0,0 +1,1043 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#ifndef ECONITEMTOOLS_H +#define ECONITEMTOOLS_H +#ifdef _WIN32 +#pragma once +#endif + +enum EConsumptionAttemptResult +{ + kConsumptionResult_CannotConsume, // could be bum definitions or doesnt meet criteria or anything -- this is failure + kConsumptionResult_CanConsume, // able to consume + kConsumptionResult_WillCompleteCollection, // able to consume and is the final item to be consumed +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconSharedToolSupport +{ +public: + // Can the given tool instance apply to a specific instance of an item. This should be used in the general + // case whenever a CEconItem or a CEconItemView is available. + static bool ToolCanApplyTo( const IEconItemInterface *pToolDef, const IEconItemInterface *pToolSubject ); + + // Can the given tool definition apply to an item definition? This will check things like restrictions, + // matching tool capabilities, etc. but will ignore instance-specific properties. This should only be used + // by code that doesn't have any access to an instance of the definition. + static bool ToolCanApplyToDefinition( const GameItemDefinition_t *pToolDef, const GameItemDefinition_t *pToolSubjectDef ); + + // Can the given tool definition apply to a base item definition? + static bool ToolCanApplyToBaseItem( const GameItemDefinition_t *pToolDef, const GameItemDefinition_t *pToolSubjectDef ); +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_DuelingMinigame : public IEconTool +{ +public: + CEconTool_DuelingMinigame( const char *pszTypeName, const char *pszUseString ) : IEconTool( pszTypeName, pszUseString, NULL, ITEM_CAP_NONE ) { } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_Noisemaker : public IEconTool +{ +public: + CEconTool_Noisemaker( const char *pszTypeName, const char *pszUseString ) : IEconTool( pszTypeName, pszUseString, NULL, ITEM_CAP_NONE ) { } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_WrappedGift : public IEconTool +{ +public: + CEconTool_WrappedGift( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ); + + virtual bool BFinishInitialization() OVERRIDE; + + bool BIsGlobalGift() const { return m_bIsGlobalGift; } + // Allows the item to be directly used rather than via the trading system + bool BIsDirectGift() const { return m_bIsDirectGift; } + const CEconItemDefinition *GetDeliveredItemDefinition() const { return m_pDeliveredGiftItemDef; } // can return NULL! (means "don't change definitions on delivery") + +#ifdef CLIENT_DLL + virtual bool CanBeUsedNow( const IEconItemInterface *pItem ) const; + virtual bool ShouldShowContainedItemPanel( const IEconItemInterface *pItem ) const; + virtual const char *GetUseCommandLocalizationToken( const IEconItemInterface *pItem, int i = 0 ) const; + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; + virtual int GetUseCommandCount( const IEconItemInterface *pItem ) const; + virtual const char* GetUseCommand( const IEconItemInterface *pItem, int i = 0 ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL + +private: + const char *m_pszDeliveredGiftItemDefName; // points to memory inside our init KV -- only valid between the constructor call and the BFinishInitialization() call (this is messy but Fletcher and I agree it makes more sense than switching to a full two-pass schema parse just for this) + + const CEconItemDefinition *m_pDeliveredGiftItemDef; + bool m_bIsGlobalGift; + bool m_bIsDirectGift; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_WeddingRing : public IEconTool +{ +public: + CEconTool_WeddingRing( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities ) : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) { } + + virtual bool RequiresToolEscrowPeriod() const { return false; } + +#ifdef CLIENT_DLL + virtual const char *GetUseCommandLocalizationToken( const IEconItemInterface *pItem, int i = 0 ) const; + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_TagsList +{ +public: + CEconTool_TagsList( KeyValues *pKVTags ) + { + if ( pKVTags ) + { + FOR_EACH_SUBKEY( pKVTags, pKVTag ) + { + m_vecTags.AddToTail( GetItemSchema()->GetHandleForTag( pKVTag->GetName() ) ); + } + } + } + + const CUtlVector<econ_tag_handle_t>& GetTagsList() const { return m_vecTags; } + +private: + CUtlVector<econ_tag_handle_t> m_vecTags; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_StrangeCountTransfer : public IEconTool +{ +public: + CEconTool_StrangeCountTransfer( const char *pszTypeName, item_capabilities_t unCapabilities ); + + static bool AreItemsEligibleForStrangeCountTransfer( const IEconItemInterface *pItem1, const IEconItemInterface *pItem2 ); + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; + //virtual void OnClientApplyCommit( CEconItemView *pTool, CEconItemView *pSubject ) const; + + bool SetItems( CEconItemView *pItem1, CEconItemView *pItem2 ); + + CEconItemView *m_pItemSrc; + CEconItemView *m_pItemDest; +#endif +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_StrangePart : public IEconTool +{ +public: + CEconTool_StrangePart( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) + , m_RequiredTags( pUsageKV ? pUsageKV->FindKey( "required_tags" ) : NULL ) + , m_RequiredMissingTags( pUsageKV ? pUsageKV->FindKey( "required_missing_tags" ) : NULL ) + { + // + } + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef CLIENT_DLL + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return false; } + + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +private: + CEconTool_TagsList m_RequiredTags; + CEconTool_TagsList m_RequiredMissingTags; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_StrangePartRestriction : public IEconTool +{ +public: + CEconTool_StrangePartRestriction( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ); + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + virtual bool BFinishInitialization() OVERRIDE; +#ifdef CLIENT_DLL + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return false; } + + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + + unsigned int GetRestrictionType() const { return m_eRestrictionType; } + unsigned int GetRestrictionValue() const { return m_unRestrictionValue; } + +private: + unsigned int /*strange_event_restriction_t*/ m_eRestrictionType; + + const char *m_pszRestrictionValue; // points to memory inside our init KV -- only valid between the constructor call and the BFinishInitialization() call (this is messy but Fletcher and I agree it makes more sense than switching to a full two-pass schema parse just for this) + unsigned int m_unRestrictionValue; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: New crafting! This new systems allows for dynamic crafting recipes to be +// generated in the form of an item itself. Players can "feed" in items on the +// recipe's input list, either all at once or once at a time, until the inputs +// are all fulfilled. Once that happens at which the outputs of the recipe are given to the player. +// +// This is done using new attribute types that encode the recipe's inputs and outputs. +// Inputs and outputs can either be specific items of specific qualities, or lootlist +// with a specific quality -- for now. Lootlist will roll the specific item to be the input/output +// when the recipe item is created. Any gc generated attributes that would come +// from the lootlists will also get encoded as a string in the recipe's attribute, so things like +// unusual particle effects will get applied to outputs. These string-encoded attributes +// are ignored during input criteria matching for now. +// +// Components are allowed to have nested components defined within them. These child +// components only roll their chance to apply if their parent successfully rolls their +// chance to apply. +//--------------------------------------------------------------------------------------- +class CEconTool_ItemDynamicRecipe : public IEconTool +{ +public: + + // This enum lets the CDynamicRecipeComponentLootList class specify + // "uniqueness" of the item def that it will roll. This allows us to + // do things like ensure that the output item will never be one of the + // input items, or there's never duplicate inputs. + enum EItemDefUniqueness_t + { + UNIQUE_AMONG_INPUTS = 0, + UNIQUE_AMONG_OUTPUTS, + UNIQUE_AMONG_EVERYTHING, + UNIQUE_AMONG_NOTHING, + }; + + CEconTool_ItemDynamicRecipe( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ); + ~CEconTool_ItemDynamicRecipe(); + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + virtual bool BFinishInitialization() OVERRIDE; + +#ifdef CLIENT_DLL + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return false; } +#endif // CLIENT_DLL + + + virtual bool BInitFromKV( KeyValues *pKVDefinition, CUtlVector<CUtlString> *pVecErrors ); +#ifdef GC_DLL + virtual bool BGenerateDynamicAttributes( CEconItem* pItem, const CEconGameAccount *pGameAccount ) const OVERRIDE; +#endif + + class CBaseRecipeComponent + { + public: + + struct StringEncodedAttribute_t + { + attrib_definition_index_t m_AttrIndex; + CUtlConstString m_strAttrData; + }; + + struct CountChance_t + { + int m_nMinCount; + int m_nMaxCount; + float m_flChance; + }; + + typedef CUtlVector<const CEconItemAttributeDefinition *> ComponentAttribVector_t; + + CBaseRecipeComponent( bool bIsOutput, const CBaseRecipeComponent* pParent ); + virtual ~CBaseRecipeComponent(); + + static bool ParseComponentsBlock( KeyValues *pKV, CUtlVector<CBaseRecipeComponent*>& vecComponents, CUtlVector<CUtlString> *pVecErrors, const CBaseRecipeComponent* pParent ); + static bool ParseComponents( KeyValues *pKV, CUtlVector<CBaseRecipeComponent*>& vecComponents, bool bIsOutput, CUtlVector<CUtlString> *pVecErrors, const CBaseRecipeComponent* pParent ); + virtual bool ParseKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ); + void SetIsOutput( bool bIsOutput ) { m_bIsOutput = bIsOutput; } + void SetParent( CBaseRecipeComponent* pParent ) { m_pParent = pParent; } + void SetChanceOfApplying( float flChance ); + virtual bool BFinishInitialization_Internal( CUtlVector<CUtlString>* pVecErrors, ComponentAttribVector_t* attribVec ); + + bool GetIsOutput() const { return m_bIsOutput; } + void GetIsGuaranteed( int &nFlags ) const; + const CUtlVector< CountChance_t >& GetRollChances() const { return m_vecCountChances; } +#ifdef GC_DLL + bool RollChanceOfApplying() const; + float GetRollChance() const { return m_flChanceOfApplying; } + int RollCount() const; + virtual bool AddRecipeComponentAsAttribute( CEconItem *pItem, const CEconGameAccount *pGameAccount ) const = 0; +#endif + protected: +#ifdef GC_DLL + virtual const char* GetAttributeName() const { return "recipe component defined item"; } + static CEconItemAttributeDefinition* GetNextAvailableAttributeWithBaseName( const char* pszBaseAttribName, ComponentAttribVector_t *pAttribVec ); +#endif + const CBaseRecipeComponent* m_pParent; + CUtlVector< CBaseRecipeComponent* > m_vecAdditionalComponents; + float m_flChanceOfApplying; + bool m_bIsOutput; + CUtlVector< CountChance_t > m_vecCountChances; + float m_flTotalWeights; + + EEconItemQuality m_eQuality; + + enum EAttributesMatchingType_t + { + ATTRIBUTES_MATCH_NONE = 0, + ATTRIBUTES_MATCH_ALL, + ATTRIBUTES_MATCH_ANY, + }; + + EAttributesMatchingType_t m_attributesMatchingType; + CUtlVector< StringEncodedAttribute_t > m_vecDynamicAttributes; + + CUtlString m_strName; + + static const char* m_pszUseParentNameIdentifier; + }; + +public: + + class CDynamicRecipeComponentLootList; + // Defined item type: Use this when you want to quickly define a specific item as + // a component for a recipe. A "defined item" is considered an + // itemdef, a quality, and any additional attributes. + class CDynamicRecipeComponentDefinedItem : public CBaseRecipeComponent + { + typedef CBaseRecipeComponent BaseClass; + public: + CDynamicRecipeComponentDefinedItem( bool bIsOutput, const CBaseRecipeComponent* pParent ); + virtual ~CDynamicRecipeComponentDefinedItem(); + virtual bool BFinishInitialization_Internal( CUtlVector<CUtlString>* pVecErrors, ComponentAttribVector_t* attribVec ) OVERRIDE; +#ifdef GC_DLL + virtual bool AddRecipeComponentAsAttribute( CEconItem *pItem, const CEconGameAccount *pGameAccount ) const OVERRIDE; +#endif + protected: + + virtual bool ParseKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ) OVERRIDE; + + friend class CDynamicRecipeComponentLootList; + }; + + // Lootlist type: Use this when you want a random item from a lootlist to be a + // component in a recipe. You must specify a quality in the definition + // but it can get stomped if the lootlist-generated item gets an "elevate quality" + // attribute rolled onto it. + class CDynamicRecipeComponentLootList : public CBaseRecipeComponent + { + typedef CBaseRecipeComponent BaseClass; + public: + CDynamicRecipeComponentLootList( bool bIsOutput, const CBaseRecipeComponent* pParent ); + virtual ~CDynamicRecipeComponentLootList(); + virtual bool BFinishInitialization_Internal( CUtlVector<CUtlString>* pVecErrors, ComponentAttribVector_t* attribVec ) OVERRIDE; + protected: + virtual bool ParseKV( KeyValues *pKV, CUtlVector<CUtlString> *pVecErrors ) OVERRIDE; + + private: +#ifdef GC_DLL + virtual bool AddRecipeComponentAsAttribute( CEconItem *pItem, const CEconGameAccount *pGameAccount ) const OVERRIDE; + bool RollLootlistItemAndAttributes( CUtlVector< StringEncodedAttribute_t >& vecAdditionalAttributes + , const char** pszDefName + , const CUtlVector< item_definition_index_t > *pVecAvoidItemDefs + , const CEconGameAccount *pGameAccount ) const; + + EItemDefUniqueness_t m_eUniqueness; +#endif + }; + + class CRecipeComponentInputDefIndexIterator : public CEconItemSpecificAttributeIterator + { + public: + CRecipeComponentInputDefIndexIterator( EItemDefUniqueness_t eUniqueness ); + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, + const CAttribute_DynamicRecipeComponent& value ) OVERRIDE; + + const CUtlVector< item_definition_index_t >& GetMatchingComponentInputs() const { return m_vecInputItemDefs; } + + private: + + CUtlVector< item_definition_index_t > m_vecInputItemDefs; + EItemDefUniqueness_t m_eUniqueness; + }; + + const CUtlVector<CBaseRecipeComponent*>& GetComponents() const { return m_vecComponents; } + +private: + + // All the different components for this recipe + CUtlVector<CBaseRecipeComponent*> m_vecComponents; + // Errors we ecounter during initialization + CUtlVector<CUtlString> m_vecErrors; +}; + +class CWorldItemPlacementAttributeIterator : public CEconItemSpecificAttributeIterator +{ +public: + CWorldItemPlacementAttributeIterator() {} + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement &value ) OVERRIDE + { + Assert( pAttrDef ); + if ( pAttrDef ) + { + m_vecPlacementAttributes.AddToTail( pAttrDef ); + } + + return true; + } + + const CUtlVector< const CEconItemAttributeDefinition* > &GetPlacementAttributes( void ) const { return m_vecPlacementAttributes; } +private: + + CUtlVector< const CEconItemAttributeDefinition* > m_vecPlacementAttributes; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: Turns valid target items in to strange +//--------------------------------------------------------------------------------------- +class CEconTool_Xifier : public IEconTool +{ +public: + CEconTool_Xifier( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) + , m_RequiredTags( pUsageKV ? pUsageKV->FindKey( "required_tags" ) : NULL ) + { + if ( pUsageKV ) + { + KeyValues *pKVItemDefRestrictions = pUsageKV->FindKey( "itemdef_restrictions" ); + if ( pKVItemDefRestrictions ) + { + FOR_EACH_SUBKEY( pKVItemDefRestrictions, pKVTag ) + { + m_ItemDefTargetRestrictions.AddToTail( atoi(pKVTag->GetName()) ); + } + } + + m_ItemRarityRestriction = pUsageKV->GetInt( "itemrarity_restrictions", k_unItemRarity_Any ); + } + + m_sItemDescLocToken = pUsageKV ? pUsageKV->GetString( "item_desc_tool_target", "" ) : ""; + } + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + + const char *GetItemDescToolTargetLocToken() const { return m_sItemDescLocToken.String(); } + +#ifdef GC + virtual CEconItem *GenerateNewItem( const IEconItemInterface *pTool, const CEconItem *pTarget ) const = 0; +#endif + +#ifdef CLIENT_DLL + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return false; } + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +private: + bool ItemDefMatch( const CEconItemDefinition* pTargetItemDef, const CEconItemDefinition* pSubjectItemDef ) const; + + CUtlString m_sItemDescLocToken; + CEconTool_TagsList m_RequiredTags; + CUtlVector<item_definition_index_t> m_ItemDefTargetRestrictions; + + uint8 m_ItemRarityRestriction; +}; + +class CEconTool_Strangifier : public CEconTool_Xifier +{ +public: + CEconTool_Strangifier( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : CEconTool_Xifier( pszTypeName, pszUseString, unCapabilities, pUsageKV ) {} + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef GC + virtual CEconItem *GenerateNewItem( const IEconItemInterface *pTool, const CEconItem *pTarget ) const; +#endif + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +}; +//--------------------------------------------------------------------------------------- +class CEconTool_KillStreakifier : public CEconTool_Xifier +{ +public: + CEconTool_KillStreakifier( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : CEconTool_Xifier( pszTypeName, pszUseString, unCapabilities, pUsageKV ) {} + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef GC + virtual CEconItem *GenerateNewItem( const IEconItemInterface *pTool, const CEconItem *pTarget ) const; +#endif + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; +//--------------------------------------------------------------------------------------- +class CEconTool_Festivizer : public CEconTool_Xifier +{ +public: + CEconTool_Festivizer( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : CEconTool_Xifier( pszTypeName, pszUseString, unCapabilities, pUsageKV ) + { + } + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef GC + virtual CEconItem *GenerateNewItem( const IEconItemInterface *pTool, const CEconItem *pTarget ) const; +#endif + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +class CEconTool_Unusualifier : public CEconTool_Xifier +{ +public: + CEconTool_Unusualifier( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : CEconTool_Xifier( pszTypeName, pszUseString, unCapabilities, pUsageKV ) {} + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef GC + virtual CEconItem *GenerateNewItem( const IEconItemInterface *pTool, const CEconItem *pTarget ) const; +#endif + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +// Eats an item to give it charges +//--------------------------------------------------------------------------------------- +class CEconTool_ItemEaterRecharger: public IEconTool +{ +public: + CEconTool_ItemEaterRecharger( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) + , m_RequiredTags( pUsageKV ? pUsageKV->FindKey( "required_tags" ) : NULL ) + { + if ( pUsageKV ) + { + KeyValues *pKVItemDefRestrictions = pUsageKV->FindKey( "itemdef_restrictions" ); + + if ( pKVItemDefRestrictions ) + { + FOR_EACH_SUBKEY( pKVItemDefRestrictions, pKVTag ) + { + m_ItemDefTargetRestrictions.AddToTail( atoi(pKVTag->GetName()) ); + m_ItemDefTargetChargeValues.AddToTail( pKVItemDefRestrictions->GetInt( pKVTag->GetName(), 0 ) ); + } + } + } + } + + int GetChargesForItemDefId ( item_definition_index_t defIndex ) const; + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef CLIENT_DLL + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return false; } + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL + +private: + CEconTool_TagsList m_RequiredTags; + CUtlVector<item_definition_index_t> m_ItemDefTargetRestrictions; + CUtlVector<int> m_ItemDefTargetChargeValues; +}; +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_UpgradeCard : public IEconTool +{ +public: + struct upgrade_card_attr_value_t + { + const CEconItemAttributeDefinition *m_pAttrDef; + attrib_value_t m_value; + }; + + typedef CUtlVectorFixedGrowable<upgrade_card_attr_value_t, 1> UpgradeCardAttributeVec_t; + + CEconTool_UpgradeCard( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) + , m_RequiredTags( pUsageKV ? pUsageKV->FindKey( "required_tags" ) : NULL ) + { + COMPILE_TIME_ASSERT( sizeof( attrib_value_t ) == sizeof( uint32 ) ); + COMPILE_TIME_ASSERT( sizeof( attrib_value_t ) == sizeof( float ) ); + + if ( pUsageKV ) + { + KeyValues *pAttributesKV = pUsageKV->FindKey( "attributes" ); + if ( pAttributesKV ) + { + FOR_EACH_SUBKEY( pAttributesKV, pAttrKV ) + { + const char *pszAttributeName = pAttrKV->GetName(); + const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( pszAttributeName ); + + // Kyle says: this is bad, dumb code, and more importantly it's bad dumb code that doesn't + // make any sense here, way down inside the "parse a tool" function. + attrib_value_t value; + + const bool bParseAsFloat = pAttrDef && pAttrDef->IsStoredAsFloat(); + if ( bParseAsFloat ) + { + *(float *)&value = pAttrKV->GetFloat(); + } + else + { + *(uint32 *)&value = pAttrKV->GetInt(); + } + + // Add this attribute to our list. Adding a NULL pointer is safe here. We'll use that to check + // later in BFinishInitialization() whether we had a successful init or not. + upgrade_card_attr_value_t attrValue = { pAttrDef, value }; + m_vecAttributes.AddToTail( attrValue ); + } + } + } + } + + virtual bool BFinishInitialization() OVERRIDE + { + // Make sure we didn't fail to find any attributes. + FOR_EACH_VEC( m_vecAttributes, i ) + { + if ( m_vecAttributes[i].m_pAttrDef == NULL ) + return false; + } + + // Make sure we have a non-zero number of attributes. If we don't have at least one, applicable would be + // a nonsensical action. + return m_vecAttributes.Count() > 0 + && IEconTool::BFinishInitialization(); + } + + const UpgradeCardAttributeVec_t& GetAttributes() const { return m_vecAttributes; } + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef CLIENT_DLL + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return false; } + + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +private: + CEconTool_TagsList m_RequiredTags; + UpgradeCardAttributeVec_t m_vecAttributes; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_ClassTransmogrifier : public IEconTool +{ +public: + CEconTool_ClassTransmogrifier( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, unCapabilities ) + , m_RequiredTags( pUsageKV ? pUsageKV->FindKey( "required_tags" ) : NULL ) + , m_iClass( -1 ) + { + KeyValues *pKVOutputClass = pUsageKV->FindKey( "output_class" ); + if ( pKVOutputClass ) + { + m_iClass = StringFieldToInt( pKVOutputClass->GetString( "" ), GetItemSchema()->GetClassUsabilityStrings() ); + } + } + + virtual bool BFinishInitialization() OVERRIDE + { + return m_iClass > 0 + && m_iClass < LOADOUT_COUNT + && IEconTool::BFinishInitialization(); + } + + int GetOutputClass() const { return m_iClass; } + const CEconTool_TagsList& GetRequiredTags() const { return m_RequiredTags; } + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef CLIENT_DLL + virtual bool ShouldDisplayAsUseableOnItemsInArmory() const { return false; } + + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +private: + CEconTool_TagsList m_RequiredTags; // required for both the input item and the output item + int m_iClass; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_BackpackExpander : public IEconTool +{ +public: + CEconTool_BackpackExpander ( const char *pszTypeName, const char *pszUseString, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, ITEM_CAP_NONE ) + , m_iBackpackSlots( 0 ) + { + if ( pUsageKV ) + { + m_iBackpackSlots = pUsageKV->GetInt( "backpack_slots", 0 ); + } + } + + virtual bool BFinishInitialization() OVERRIDE + { + return m_iBackpackSlots > 0 + && IEconTool::BFinishInitialization(); + } + + int GetBackpackSlots() const { return m_iBackpackSlots; } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL + +private: + int m_iBackpackSlots; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_AccountUpgradeToPremium : public IEconTool +{ +public: + CEconTool_AccountUpgradeToPremium( const char *pszTypeName, const char *pszUseString ) : IEconTool( pszTypeName, pszUseString, NULL, ITEM_CAP_NONE ) { } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL +}; + +//--------------------------------------------------------------------------------------- +class CEconTool_DuckToken: public IEconTool +{ +public: + CEconTool_DuckToken( const char *pszTypeName, item_capabilities_t unCapabilities ) : IEconTool( pszTypeName, NULL, NULL, unCapabilities ) { } + + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + //virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL +}; + +//--------------------------------------------------------------------------------------- +class CEconTool_GrantOperationPass : public IEconTool +{ +public: + CEconTool_GrantOperationPass( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ) : IEconTool( pszTypeName, pszUseString, NULL, ITEM_CAP_NONE ) + { + m_pOperationPassName = NULL; + m_pOptionalBonusLootList = NULL; + if ( pUsageKV ) + { + // Find the Item + m_pOperationPassName = pUsageKV->GetString( "operation_pass", NULL ); + m_pOptionalBonusLootList = pUsageKV->GetString( "bonus_lootlist", NULL ); + } + } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL + + const char *m_pOperationPassName; + const char *m_pOptionalBonusLootList; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_ClaimCode : public IEconTool +{ +public: + CEconTool_ClaimCode ( const char *pszTypeName, const char *pszUseString, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, ITEM_CAP_NONE ) + , m_pszClaimType( NULL ) + { + if ( pUsageKV ) + { + m_pszClaimType = pUsageKV->GetString( "claim_type", NULL ); + } + } + + virtual bool BFinishInitialization() OVERRIDE + { + return m_pszClaimType + && IEconTool::BFinishInitialization(); + } + + const char *GetClaimType() const { return m_pszClaimType; } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL + +private: + const char *m_pszClaimType; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +enum EGiftTargetRule +{ + kGiftTargetRule_OnlyOthers = 0, + kGiftTargetRule_OnlySelf = 1, +}; + +class CEconTool_Gift : public IEconTool +{ +public: + CEconTool_Gift ( const char *pszTypeName, const char *pszUseString, KeyValues *pUsageKV ) + : IEconTool( pszTypeName, pszUseString, NULL, ITEM_CAP_NONE ) + , m_pszLootListName( NULL ) + , m_iMaxRecipients( 0 ) + , m_eTargetRule( kGiftTargetRule_OnlySelf ) + { + if ( pUsageKV ) + { + m_pszLootListName = pUsageKV->GetString( "loot_list", NULL ); + m_iMaxRecipients = pUsageKV->GetInt( "max_recipients", 0 ); + m_eTargetRule = !Q_stricmp( pUsageKV->GetString( "target_rule", "only_others" ), "only_self" ) + ? kGiftTargetRule_OnlySelf + : kGiftTargetRule_OnlyOthers; + } + } + + virtual bool BFinishInitialization() OVERRIDE + { + return m_pszLootListName + && GetItemSchema()->GetLootListByName( m_pszLootListName ) + && m_iMaxRecipients > 0 + && IEconTool::BFinishInitialization(); + } + + const char *GetLootListName() const { return m_pszLootListName; } + int GetMaxRecipients() const { return m_iMaxRecipients; } + EGiftTargetRule GetTargetRule() const { return m_eTargetRule; } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +#ifdef GC_DLL + virtual class CGCEconConsumableBehavior *CreateGCConsumableBehavior() const; +#endif // GC_DLL + +private: + const char *m_pszLootListName; + int m_iMaxRecipients; + EGiftTargetRule m_eTargetRule; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_PaintCan : public IEconTool +{ +public: + CEconTool_PaintCan( const char *pszTypeName, item_capabilities_t unCapabilities ) : IEconTool( pszTypeName, NULL, NULL, unCapabilities ) { } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_GiftWrap : public IEconTool +{ +public: + CEconTool_GiftWrap( const char *pszTypeName, const char *pszUseString, item_capabilities_t unCapabilities, KeyValues *pUsageKV ); + + virtual bool BFinishInitialization() OVERRIDE; + virtual bool CanApplyTo( const IEconItemInterface *pTool, const IEconItemInterface *pToolSubject ) const; + virtual bool RequiresToolEscrowPeriod() const { return false; } + + const CEconItemDefinition *GetWrappedItemDefinition() const { Assert( m_pWrappedGiftItemDef ); return m_pWrappedGiftItemDef; } + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL + +private: + const char *m_pszWrappedGiftItemDefName; // points to memory inside our init KV -- only valid between the constructor call and the BFinishInitialization() call (this is messy but Fletcher and I agree it makes more sense than switching to a full two-pass schema parse just for this) + const CEconItemDefinition *m_pWrappedGiftItemDef; +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_NameTag : public IEconTool +{ +public: + CEconTool_NameTag( const char *pszTypeName, item_capabilities_t unCapabilities ) : IEconTool( pszTypeName, NULL, NULL, unCapabilities ) { } + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_DescTag : public IEconTool +{ +public: + CEconTool_DescTag( const char *pszTypeName, item_capabilities_t unCapabilities ) : IEconTool( pszTypeName, NULL, NULL, unCapabilities ) { } + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_CustomizeTexture : public IEconTool +{ +public: + CEconTool_CustomizeTexture( const char *pszTypeName, item_capabilities_t unCapabilities ) : IEconTool( pszTypeName, NULL, NULL, unCapabilities ) { } + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_CrateKey : public IEconTool +{ +public: + CEconTool_CrateKey( const char *pszTypeName, const char *pszUsageRestriction, item_capabilities_t unCapabilities ) : IEconTool( pszTypeName, NULL, pszUsageRestriction, unCapabilities ) { } + +#ifdef CLIENT_DLL + virtual void OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//--------------------------------------------------------------------------------------- +// Purpose: +//--------------------------------------------------------------------------------------- +class CEconTool_Default : public IEconTool +{ +public: + CEconTool_Default( const char *pszTypeName, const char *pszUseString, const char *pszUsageRestriction, item_capabilities_t unCapabilities ) + : IEconTool( pszTypeName, pszUseString, pszUsageRestriction, unCapabilities ) + { + Assert( pszTypeName ); + } + +#ifdef CLIENT_DLL + virtual void OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const; +#endif // CLIENT_DLL +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +class CCountUserGeneratedAttributeIterator : public IEconItemUntypedAttributeIterator +{ +public: + CCountUserGeneratedAttributeIterator() : m_iCount( 0 ) { } + + virtual bool OnIterateAttributeValueUntyped( const CEconItemAttributeDefinition *pAttrDef ) OVERRIDE + { + if ( pAttrDef->GetUserGenerationType() != 0 ) + { + m_iCount++; + } + + return true; + } + + int GetCount() const { return m_iCount; } + +private: + int m_iCount; +}; + +#endif // ECONITEMTOOLS_H diff --git a/game/shared/econ/econ_item_view.cpp b/game/shared/econ/econ_item_view.cpp new file mode 100644 index 0000000..e25390b --- /dev/null +++ b/game/shared/econ/econ_item_view.cpp @@ -0,0 +1,1971 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "econ_item_view.h" +#include "econ_item_system.h" +#include "econ_item_description.h" +#include "econ_item_inventory.h" + +#include "rtime.h" +#include "econ_gcmessages.h" +#include "gamestringpool.h" + +// For localization +#include "tier3/tier3.h" +#include "vgui/ILocalize.h" + +#include "isaverestore.h" +#include "dt_utlvector_send.h" +#include "dt_utlvector_recv.h" +#include <vgui_controls/Panel.h> + +#ifdef CLIENT_DLL +#ifndef DEDICATED +#include "vgui/IScheme.h" +#endif +#endif + +#if defined(TF_CLIENT_DLL) +#include "tf_duel_summary.h" +#include "econ_contribution.h" +#include "tf_player_info.h" +#include "tf_gcmessages.h" +#include "c_tf_freeaccount.h" +#include "c_tf_player.h" +#endif + +#include "materialsystem/itexture.h" +#include "materialsystem/itexturecompositor.h" + +#include "activitylist.h" + +#ifdef CLIENT_DLL +#include "gc_clientsystem.h" +#endif // CLIENT_DLL + +#ifdef GC_DLL +#error "CEconItemView is not meant to be compiled on the GC! There are silent assumptions made about attributes, etc." +#endif // GC_DLL + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#ifdef STAGING_ONLY +ConVar econ_force_style_index( "econ_force_style_index", "-1", FCVAR_REPLICATED ); +#endif // STAGING_ONLY + + +// Networking tables for attributes +BEGIN_NETWORK_TABLE_NOBASE( CEconItemAttribute, DT_ScriptCreatedAttribute ) + + // Note: we are networking the value as an int, even though it's a "float", because really it isn't + // a float. It's 32 raw bits. + +#ifndef CLIENT_DLL + SendPropInt( SENDINFO(m_iAttributeDefinitionIndex), -1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO_NAME(m_flValue, m_iRawValue32), 32, SPROP_UNSIGNED ), +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING + SendPropInt( SENDINFO(m_nRefundableCurrency), -1, SPROP_UNSIGNED ), +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING +#else + RecvPropInt( RECVINFO(m_iAttributeDefinitionIndex) ), + RecvPropInt( RECVINFO_NAME(m_flValue, m_iRawValue32) ), + RecvPropFloat( RECVINFO(m_flValue), SPROP_NOSCALE ), // for demo compatibility only +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING + RecvPropInt( RECVINFO(m_nRefundableCurrency) ), +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING +#endif +END_NETWORK_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemAttribute::CEconItemAttribute( void ) +{ + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemAttribute::CEconItemAttribute( const attrib_definition_index_t iAttributeIndex, float flValue ) +{ + Init(); + + m_iAttributeDefinitionIndex = iAttributeIndex; + SetValue( flValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemAttribute::CEconItemAttribute( const attrib_definition_index_t iAttributeIndex, uint32 unValue ) +{ + Init(); + + m_iAttributeDefinitionIndex = iAttributeIndex; + SetIntValue( unValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemAttribute::SetValue( float flValue ) +{ +// Assert( GetStaticData() && GetStaticData()->IsStoredAsFloat() ); + m_flValue = flValue; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemAttribute::SetIntValue( uint32 unValue ) +{ + // @note we don't check the storage type here, because this is how it is set from the data file + // Note that numbers approaching two billion cannot be stored in a float + // representation because they will map to NaNs. Numbers below 16 million + // will fail if denormals are disabled. + m_flValue = *(float*)&unValue; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemAttribute::Init( void ) +{ + m_iAttributeDefinitionIndex = INVALID_ATTRIB_DEF_INDEX; + m_flValue = 0.0f; + +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING + m_nRefundableCurrency = 0; +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemAttribute::operator=( const CEconItemAttribute &val ) +{ + m_iAttributeDefinitionIndex = val.m_iAttributeDefinitionIndex; + m_flValue = val.m_flValue; + +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING + m_nRefundableCurrency = val.m_nRefundableCurrency; +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconItemAttributeDefinition *CEconItemAttribute::GetStaticData( void ) const +{ + return GetItemSchema()->GetAttributeDefinition( m_iAttributeDefinitionIndex ); +} + +BEGIN_NETWORK_TABLE_NOBASE( CAttributeList, DT_AttributeList ) +#if !defined( CLIENT_DLL ) +SendPropUtlVectorDataTable( m_Attributes, MAX_ATTRIBUTES_PER_ITEM, DT_ScriptCreatedAttribute ), +#else +RecvPropUtlVectorDataTable( m_Attributes, MAX_ATTRIBUTES_PER_ITEM, DT_ScriptCreatedAttribute ), +#endif // CLIENT_DLL +END_NETWORK_TABLE() + +BEGIN_DATADESC_NO_BASE( CAttributeList ) +END_DATADESC() + +//=========================================================================================================================== +// SCRIPT CREATED ITEMS +//=========================================================================================================================== +BEGIN_NETWORK_TABLE_NOBASE( CEconItemView, DT_ScriptCreatedItem ) +#if !defined( CLIENT_DLL ) + SendPropInt( SENDINFO( m_iItemDefinitionIndex ), 20, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iEntityLevel ), 8 ), + //SendPropInt( SENDINFO( m_iItemID ), 64, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iItemIDHigh ), 32, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iItemIDLow ), 32, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iAccountID ), 32, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iEntityQuality ), 5 ), + SendPropBool( SENDINFO( m_bInitialized ) ), + SendPropBool( SENDINFO( m_bOnlyIterateItemViewAttributes) ), + SendPropDataTable(SENDINFO_DT(m_AttributeList), &REFERENCE_SEND_TABLE(DT_AttributeList)), + SendPropInt( SENDINFO( m_iTeamNumber ) ), + SendPropDataTable(SENDINFO_DT( m_NetworkedDynamicAttributesForDemos ), &REFERENCE_SEND_TABLE( DT_AttributeList ) ), +#else + RecvPropInt( RECVINFO( m_iItemDefinitionIndex ) ), + RecvPropInt( RECVINFO( m_iEntityLevel ) ), + //RecvPropInt( RECVINFO( m_iItemID ) ), + RecvPropInt( RECVINFO( m_iItemIDHigh ) ), + RecvPropInt( RECVINFO( m_iItemIDLow ) ), + RecvPropInt( RECVINFO( m_iAccountID ) ), + RecvPropInt( RECVINFO( m_iEntityQuality ) ), + RecvPropBool( RECVINFO( m_bInitialized ) ), + RecvPropBool( RECVINFO( m_bOnlyIterateItemViewAttributes ) ), + RecvPropDataTable(RECVINFO_DT(m_AttributeList), 0, &REFERENCE_RECV_TABLE(DT_AttributeList)), + RecvPropInt( RECVINFO( m_iTeamNumber ) ), + RecvPropDataTable( RECVINFO_DT( m_NetworkedDynamicAttributesForDemos ), 0, &REFERENCE_RECV_TABLE( DT_AttributeList ) ), +#endif // CLIENT_DLL +END_NETWORK_TABLE() + +BEGIN_DATADESC_NO_BASE( CEconItemView ) + DEFINE_FIELD( m_iItemDefinitionIndex, FIELD_INTEGER ), + DEFINE_FIELD( m_iEntityQuality, FIELD_INTEGER ), + DEFINE_FIELD( m_iEntityLevel, FIELD_INTEGER ), + DEFINE_FIELD( m_iItemID, FIELD_INTEGER ), + // DEFINE_FIELD( m_wszItemName, FIELD_STRING ), Regenerated post-save + // DEFINE_FIELD( m_szItemName, FIELD_STRING ), Regenerated post-save + // DEFINE_FIELD( m_szAttributeDescription, FIELD_STRING ), Regenerated post-save + // m_AttributeLineColors // Regenerated post-save + // m_Attributes // Custom handling in Save()/Restore() + DEFINE_FIELD( m_bInitialized, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bOnlyIterateItemViewAttributes, FIELD_BOOLEAN ), + DEFINE_EMBEDDED( m_AttributeList ), + DEFINE_EMBEDDED( m_NetworkedDynamicAttributesForDemos ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemView::CEconItemView( void ) +{ + m_iItemDefinitionIndex = INVALID_ITEM_DEF_INDEX; + m_iEntityQuality = (int)AE_UNDEFINED; + m_iEntityLevel = 0; + SetItemID( INVALID_ITEM_ID ); + m_iInventoryPosition = 0; + m_bInitialized = false; + m_bOnlyIterateItemViewAttributes = false; + m_iAccountID = 0; + m_pNonSOEconItem = NULL; + m_bColorInit = false; + m_bPaintOverrideInit = false; + m_bHasPaintOverride = false; + m_flOverrideIndex = 0.f; +#if defined( CLIENT_DLL ) + m_bIsTradeItem = false; + m_iEntityQuantity = 1; + m_unClientFlags = 0; + m_unOverrideStyle = INVALID_STYLE_INDEX; + m_unOverrideOrigin = kEconItemOrigin_Max; +#endif +#if BUILD_ITEM_NAME_AND_DESC + m_pDescription = NULL; + m_pszGrayedOutReason = NULL; +#endif + +#ifdef CLIENT_DLL + m_pWeaponSkinBase = NULL; + m_pWeaponSkinBaseCompositor = NULL; + m_iLastGeneratedTeamSkin = TF_TEAM_RED; + m_bWeaponSkinUseHighRes = false; + m_bWeaponSkinUseLowRes = false; +#endif // CLIENT_DLL + + m_iTeamNumber = TF_TEAM_RED; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemView::~CEconItemView( void ) +{ +#ifdef CLIENT_DLL + SafeRelease( &m_pWeaponSkinBase ); + SafeRelease( &m_pWeaponSkinBaseCompositor ); +#endif // CLIENT_DLL + + DestroyAllAttributes(); + +#if BUILD_ITEM_NAME_AND_DESC + MarkDescriptionDirty(); + free( m_pszGrayedOutReason ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemView::CEconItemView( const CEconItemView &src ) +{ +#if BUILD_ITEM_NAME_AND_DESC + m_pDescription = NULL; + m_pszGrayedOutReason = NULL; +#endif + +#ifdef CLIENT_DLL + // Need to null these out here for initial behavior. + m_pWeaponSkinBase = NULL; + m_pWeaponSkinBaseCompositor = NULL; + m_nWeaponSkinGeneration = 0; + m_iLastGeneratedTeamSkin = TF_TEAM_RED; + m_unWeaponSkinBaseCreateFlags = 0; + m_bWeaponSkinUseHighRes = src.m_bWeaponSkinUseHighRes; + m_bWeaponSkinUseLowRes = src.m_bWeaponSkinUseLowRes; +#endif //CLIENT_DLL + + m_iTeamNumber = src.m_iTeamNumber; // keep the same team from the first creation + + *this = src; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::Init( int iDefIndex, int iQuality, int iLevel, uint32 iAccountID ) +{ + m_AttributeList.Init(); + m_NetworkedDynamicAttributesForDemos.Init(); + + m_iItemDefinitionIndex = iDefIndex; + CEconItemDefinition *pData = GetStaticData(); + if ( !pData ) + { + // We've got an item that we don't have static data for. + return; + } + + SetItemID( INVALID_ITEM_ID ); + m_bInitialized = true; + m_iAccountID = iAccountID; + + if ( iQuality == AE_USE_SCRIPT_VALUE ) + { + m_iEntityQuality = pData->GetQuality(); + + // Kyle says: this is a horrible hack because AE_UNDEFINED will get stuffed into a uint8 when + // loaded into the item definition, but then read back out into a regular int here. + if ( m_iEntityQuality == (uint8)AE_UNDEFINED ) + { + m_iEntityQuality = (int)AE_NORMAL; + } + } + else if ( iQuality == k_unItemQuality_Any ) + { + m_iEntityQuality = (int)AE_RARITY1; + } + else + { + m_iEntityQuality = iQuality; + } + + if ( iLevel == AE_USE_SCRIPT_VALUE ) + { + m_iEntityLevel = pData->RollItemLevel(); + } + else + { + m_iEntityLevel = iLevel; + } + + // We made changes to quality, level, etc. so mark the description as dirty. + MarkDescriptionDirty(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemView& CEconItemView::operator=( const CEconItemView& src ) +{ + m_iItemDefinitionIndex = src.m_iItemDefinitionIndex; + m_iEntityQuality = src.m_iEntityQuality; + m_iEntityLevel = src.m_iEntityLevel; + SetItemID( src.GetItemID() ); + m_iInventoryPosition = src.m_iInventoryPosition; + m_bInitialized = src.m_bInitialized; + m_bOnlyIterateItemViewAttributes = src.m_bOnlyIterateItemViewAttributes; + m_iAccountID = src.m_iAccountID; + SetNonSOEconItem(src.m_pNonSOEconItem); + m_bColorInit = false; // reset Color init + m_bPaintOverrideInit = false; + m_bHasPaintOverride = false; + m_flOverrideIndex = 0.f; +#ifdef CLIENT_DLL + m_iLastGeneratedTeamSkin = src.m_iLastGeneratedTeamSkin; + m_bIsTradeItem = src.m_bIsTradeItem; + m_iEntityQuantity = src.m_iEntityQuantity; + m_unClientFlags = src.m_unClientFlags; + m_unOverrideStyle = src.m_unOverrideStyle; + m_unOverrideOrigin = src.m_unOverrideOrigin; + + SafeAssign( &m_pWeaponSkinBase, src.m_pWeaponSkinBase ); + SafeAssign( &m_pWeaponSkinBaseCompositor, src.m_pWeaponSkinBaseCompositor ); + + m_nWeaponSkinGeneration = src.m_nWeaponSkinGeneration; + m_unWeaponSkinBaseCreateFlags = src.m_unWeaponSkinBaseCreateFlags; + + m_bWeaponSkinUseHighRes = src.m_bWeaponSkinUseHighRes; + m_bWeaponSkinUseLowRes = src.m_bWeaponSkinUseLowRes; + +#endif // CLIENT_DLL + + m_iTeamNumber = src.m_iTeamNumber; // keep the same team from the first creation + + DestroyAllAttributes(); + + m_AttributeList = src.m_AttributeList; + m_NetworkedDynamicAttributesForDemos = src.m_NetworkedDynamicAttributesForDemos; + + // TODO: Copying the description pointer and refcounting it would work also. + MarkDescriptionDirty(); + + // Clear out any overrides we currently have, they'll get reset up on demand. + ResetMaterialOverrides(); + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconItemView::operator==( const CEconItemView &other ) const +{ + if ( IsValid() != other.IsValid() ) + return false; + if ( ( GetItemID() != INVALID_ITEM_ID || other.GetItemID() != INVALID_ITEM_ID ) && GetItemID() != other.GetItemID() ) + return false; + if ( GetItemDefIndex() != other.GetItemDefIndex() ) + return false; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +GameItemDefinition_t *CEconItemView::GetStaticData( void ) const +{ + CEconItemDefinition *pRet = GetItemSchema()->GetItemDefinition( m_iItemDefinitionIndex ); + GameItemDefinition_t *pTypedRet = dynamic_cast<GameItemDefinition_t *>( pRet ); + + AssertMsg( pRet == pTypedRet, "Item definition of inappropriate type." ); + + return pTypedRet; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int32 CEconItemView::GetQuality() const +{ + return GetSOCData() + ? GetSOCData()->GetQuality() +#ifdef TF_CLIENT_DLL + : GetFlags() & kEconItemFlagClient_StoreItem + ? AE_UNIQUE +#endif + : GetOrigin() != kEconItemOrigin_Invalid + ? GetItemQuality() + : AE_NORMAL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +style_index_t CEconItemView::GetStyle() const +{ + return GetItemStyle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +uint8 CEconItemView::GetFlags() const +{ + uint8 unSOCFlags = GetSOCData() ? GetSOCData()->GetFlags() : 0; + +#if !defined( GAME_DLL ) + return unSOCFlags | m_unClientFlags; +#else // defined( GAME_DLL ) + return unSOCFlags; +#endif // !defined( GAME_DLL ) +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +eEconItemOrigin CEconItemView::GetOrigin() const +{ +#ifdef CLIENT_DLL + if( m_unOverrideOrigin != kEconItemOrigin_Max ) + { + return m_unOverrideOrigin; + } +#endif//CLIENT_DLL + + return GetSOCData() ? GetSOCData()->GetOrigin() : kEconItemOrigin_Invalid; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconItemView::GetQuantity() const +{ + return GetItemQuantity(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemView::GetCustomName() const +{ + return GetSOCData() ? GetSOCData()->GetCustomName() : NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemView::GetCustomDesc() const +{ + return GetSOCData() ? GetSOCData()->GetCustomDesc() : NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::IterateAttributes( class IEconItemAttributeIterator *pIterator ) const +{ + Assert( pIterator ); + + // Note if we have network attribs, because m_NetworkedDynamicAttributesForDemos might be the iterator + // which we're about to fill up below. + const bool bHasNetworkedAttribsForDemos = m_NetworkedDynamicAttributesForDemos.GetNumAttributes() > 0; + + // First, we iterate over the attributes we have local copies of. If we have any attribute + // values here they'll override whatever values we would otherwise have pulled from our + // definition/CEconItem. + const CAttributeList *pAttrList = GetAttributeList(); + if ( pAttrList ) + { + pAttrList->IterateAttributes( pIterator ); + } + + if ( m_bOnlyIterateItemViewAttributes ) + return; + + // This wraps any other iterator class and will prevent double iteration of any attributes + // that exist on us. + class CEconItemAttributeIterator_EconItemViewWrapper : public IEconItemAttributeIterator + { + public: + CEconItemAttributeIterator_EconItemViewWrapper( const CEconItemView *pEconItemView, IEconItemAttributeIterator *pIterator ) + : m_pEconItemView( pEconItemView ) + , m_pIterator( pIterator ) + { + Assert( m_pEconItemView ); + Assert( m_pIterator ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) + { + Assert( pAttrDef ); + + return m_pEconItemView->GetAttributeList()->GetAttributeByID( pAttrDef->GetDefinitionIndex() ) + ? true + : m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) + { + Assert( pAttrDef ); + + return m_pEconItemView->GetAttributeList()->GetAttributeByID( pAttrDef->GetDefinitionIndex() ) + ? true + : m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const uint64& value ) + { + Assert( pAttrDef ); + + return m_pEconItemView->GetAttributeList()->GetAttributeByID( pAttrDef->GetDefinitionIndex() ) + ? true + : m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_String& value ) + { + Assert( pAttrDef ); + + return m_pEconItemView->GetAttributeList()->GetAttributeByID( pAttrDef->GetDefinitionIndex() ) + ? true + : m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) + { + Assert( pAttrDef ); + + return m_pEconItemView->GetAttributeList()->GetAttributeByID( pAttrDef->GetDefinitionIndex() ) + ? true + : m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_ItemSlotCriteria& value ) + { + Assert( pAttrDef ); + + return m_pEconItemView->GetAttributeList()->GetAttributeByID( pAttrDef->GetDefinitionIndex() ) + ? true + : m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_WorldItemPlacement& value ) + { + Assert( pAttrDef ); + + return m_pEconItemView->GetAttributeList()->GetAttributeByID( pAttrDef->GetDefinitionIndex() ) + ? true + : m_pIterator->OnIterateAttributeValue( pAttrDef, value ); + } + + private: + const CEconItemView *m_pEconItemView; + IEconItemAttributeIterator *m_pIterator; + }; + + CEconItemAttributeIterator_EconItemViewWrapper iteratorWrapper( this, pIterator ); + + // Next, iterate over our database-backed item if we have one... if we do have a DB + // backing for our item here, that will also feed in the definition attributes. + CEconItem *pEconItem = GetSOCData(); + if ( pEconItem ) + { + pEconItem->IterateAttributes( &iteratorWrapper ); + } + else if ( GetItemID() != INVALID_ITEM_ID && bHasNetworkedAttribsForDemos ) + { + // Since there's no persistent data available, try the networked values! + // note: only copies the default type and floats + m_NetworkedDynamicAttributesForDemos.IterateAttributes( &iteratorWrapper ); + } + // If we didn't have a DB backing, we can still iterate over our item definition + // attributes ourselves. This can happen if we're previewing an item in the store, etc. + else if ( GetStaticData() ) + { + GetStaticData()->IterateAttributes( &iteratorWrapper ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::EnsureDescriptionIsBuilt() const +{ +#if BUILD_ITEM_NAME_AND_DESC + if ( m_pDescription ) + { + return; + } + + m_pDescription = new CEconItemDescription; + + IEconItemDescription::YieldingFillOutEconItemDescription( m_pDescription, GLocalizationProvider(), this ); + + // We use the empty string to mean "grey out but don't specify a user-facing reason". + if ( m_pszGrayedOutReason && m_pszGrayedOutReason[0] ) + { + m_pDescription->AddEmptyDescLine(); + m_pDescription->LocalizedAddDescLine( GLocalizationProvider(), m_pszGrayedOutReason, ATTRIB_COL_NEGATIVE, kDescLineFlag_Misc ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::MarkDescriptionDirty() +{ +#if BUILD_ITEM_NAME_AND_DESC + if ( m_pDescription ) + { + delete m_pDescription; + m_pDescription = NULL; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::SetGrayedOutReason( const char *pszGrayedOutReason ) +{ +#if BUILD_ITEM_NAME_AND_DESC + if ( m_pszGrayedOutReason ) + { + free( m_pszGrayedOutReason ); + m_pszGrayedOutReason = NULL; + } + + if ( pszGrayedOutReason ) + { + m_pszGrayedOutReason = strdup(pszGrayedOutReason); + } + + MarkDescriptionDirty(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconItemView::GetItemQuantity() const +{ + CEconItem *pSOCData = GetSOCData(); + if ( pSOCData ) + { + return pSOCData->GetQuantity(); + } +#ifdef CLIENT_DLL + return m_iEntityQuantity; +#else + return 1; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +style_index_t CEconItemView::GetItemStyle() const +{ +#ifdef STAGING_ONLY + if ( econ_force_style_index.GetInt() != -1 ) + return econ_force_style_index.GetInt(); +#endif // STAGING_ONLY + +#ifdef CLIENT_DLL + // Are we overriding the backing store style? + if ( m_unOverrideStyle != INVALID_STYLE_INDEX ) + return m_unOverrideStyle; +#endif // CLIENT_DLL + + static CSchemaAttributeDefHandle pAttrDef_ItemStyleOverride( "item style override" ); + float fStyleOverride = 0.f; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, pAttrDef_ItemStyleOverride, &fStyleOverride ) ) + { + return fStyleOverride; + } + + static CSchemaAttributeDefHandle pAttrDef_ItemStyleStrange( "style changes on strange level" ); + uint32 iMaxStyle = 0; + if ( pAttrDef_ItemStyleStrange && FindAttribute( pAttrDef_ItemStyleStrange, &iMaxStyle ) ) + { + // Use the strange prefix if the weapon has one. + uint32 unScore = 0; + if ( !FindAttribute( GetKillEaterAttr_Score( 0 ), &unScore ) ) + return 0; + + // What type of event are we tracking and how does it describe itself? + uint32 unKillEaterEventType = 0; + // This will overwrite our default 0 value if we have a value set but leave it if not. + float fKillEaterEventType; + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, GetKillEaterAttr_Type( 0 ), &fKillEaterEventType ) ) + { + unKillEaterEventType = fKillEaterEventType; + } + + const char *pszLevelingDataName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( unKillEaterEventType ); + if ( !pszLevelingDataName ) + { + pszLevelingDataName = KILL_EATER_RANK_LEVEL_BLOCK_NAME; + } + + const CItemLevelingDefinition *pLevelDef = GetItemSchema()->GetItemLevelForScore( pszLevelingDataName, unScore ); + if ( !pLevelDef ) + return 0; + + return Min( pLevelDef->GetLevel(), iMaxStyle ); + } + + + CEconItem *pSOCData = GetSOCData(); + if ( pSOCData ) + return pSOCData->GetStyle(); + + return INVALID_STYLE_INDEX; +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::SetClientItemFlags( uint8 unFlags ) +{ + // Generally speaking, we have two uses for client flags: + // + // - we don't have a backing store (a real CEconItem) but want to pretend we do + // for purposes of generating tooltips, graying out icons, etc. + // + // - we may or may not have a backing store but want to shove client-specific + // information into the structure -- things like "this is the item being + // actively previewed", etc. + // + // If neither of these two cases is true, then we're going to get unexpected + // behavior where the GC and the client disagree about the item flags and then + // Terrible Things happen. We assert to make sure we're in one of the above cases. + Assert( !GetSOCData() || (unFlags & kEconItemFlags_CheckFlags_AllGCFlags) == 0 ); + + m_unClientFlags = unFlags; + MarkDescriptionDirty(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::SetItemStyleOverride( style_index_t unNewStyleOverride ) +{ + // We should only ever override the style on items that don't have a real + // backing store or we'll start getting disagreements about what the client + // wants to happen and what's being stored on the GC. Unfortunately we can't + // assert on this because we do it sometimes when previewing items. + //Assert( !GetSOCData() ); + + m_unOverrideStyle = unNewStyleOverride; + MarkDescriptionDirty(); +} + + +void CEconItemView::SetItemOriginOverride( eEconItemOrigin unNewOriginOverride ) +{ + Assert( !GetSOCData() || m_pNonSOEconItem ); + Assert( unNewOriginOverride >= kEconItemOrigin_Invalid ); + Assert( unNewOriginOverride <= kEconItemOrigin_Max ); // Allow max. We ignore this value if it's max + + m_unOverrideOrigin = unNewOriginOverride; +} +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItem *CEconItemView::GetSOCData( void ) const +{ + if ( m_pNonSOEconItem ) + return m_pNonSOEconItem; + +#ifdef CLIENT_DLL + // We need to find the inventory that contains this item. If we're not connected + // to a server, and the owner is the same as the local player, use the local inventory. + // We need to do this for trading, since we are subscribed to the other person's cache. + if ( !engine->IsInGame() && InventoryManager()->GetLocalInventory()->GetOwner().GetAccountID() == m_iAccountID ) + return InventoryManager()->GetLocalInventory()->GetSOCDataForItem( GetItemID() ); +#endif // CLIENT_DLL + + // We're in-game. Find the inventory with our account ID. + CPlayerInventory *pInventory = InventoryManager()->GetInventoryForAccount( m_iAccountID ); + if ( pInventory ) + return pInventory->GetSOCDataForItem( GetItemID() ); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the model to use for model panels containing this item +//----------------------------------------------------------------------------- +const char *CEconItemView::GetInventoryModel( void ) +{ + if ( !GetStaticData() ) + return NULL; + return GetStaticData()->GetInventoryModel(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the image to use for model panels containing this item +//----------------------------------------------------------------------------- +const char *CEconItemView::GetInventoryImage( void ) +{ + if ( !GetStaticData() ) + return NULL; + + // Do we have a style set? + const char* pStyleImage = NULL; + if ( GetStaticData()->GetNumStyles() ) + pStyleImage = GetStaticData()->GetStyleInventoryImage( GetItemStyle() ); + + if ( pStyleImage && *pStyleImage ) + return pStyleImage; + + return GetStaticData()->GetInventoryImage(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the drawing data for the image to use for model panels containing this item +//----------------------------------------------------------------------------- +bool CEconItemView::GetInventoryImageData( int *iPosition, int *iSize ) +{ + if ( !GetStaticData() ) + return false; + for ( int i = 0; i < 2; i++ ) + { + iPosition[i] = GetStaticData()->GetInventoryImagePosition(i); + iSize[i] = GetStaticData()->GetInventoryImageSize(i); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the image to use for model panels containing this item +//----------------------------------------------------------------------------- +const char *CEconItemView::GetInventoryOverlayImage( int idx ) +{ + if ( !GetStaticData() ) + return NULL; + return GetStaticData()->GetInventoryOverlayImage( idx ); +} + +int CEconItemView::GetInventoryOverlayImageCount( void ) +{ + if ( !GetStaticData() ) + return 0; + return GetStaticData()->GetInventoryOverlayImageCount(); +} + +//----------------------------------------------------------------------------- +// Purpose: Return the model to use when displaying this model on the player character model, if any +//----------------------------------------------------------------------------- +const char *CEconItemView::GetPlayerDisplayModel( int iClass, int iTeam ) const +{ + const CEconItemDefinition *pDef = GetStaticData(); + if ( !pDef ) + return NULL; + + // If we have styles, give the style system a chance to change the mesh used for this + // player class. + if ( pDef->GetNumStyles() ) + { + style_index_t unStyle = GetItemStyle(); + + const CEconStyleInfo *pStyle = pDef->GetStyleInfo( unStyle ); + + // It's possible to get back a NULL pStyle if GetItemStyle() returns INVALID_STYLE_INDEX. + if ( pStyle ) + { +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + // TF styles support per-class models. + const CTFStyleInfo *pTFStyle = assert_cast<const CTFStyleInfo *>( pStyle ); + if ( pTFStyle->GetPlayerDisplayModel( iClass, iTeam ) ) + return pTFStyle->GetPlayerDisplayModel( iClass, iTeam ); +#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + + if ( pStyle->GetBasePlayerDisplayModel() ) + return pStyle->GetBasePlayerDisplayModel(); + } + } + +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + // If we don't have a style, we still a couple potential overrides. + if ( iClass >= 0 && iClass < LOADOUT_COUNT ) + { + // We don't support overriding meshes in the visuals section, but we might still be overriding + // the model for each class at the schema level. + const CTFItemDefinition *pTFDef = dynamic_cast<const CTFItemDefinition *>( pDef ); + if ( pTFDef ) + { + const char *pszModel = pTFDef->GetPlayerDisplayModel(iClass); + if ( pszModel && pszModel[0] ) + return pszModel; + } + } +#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + + return pDef->GetBasePlayerDisplayModel(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconItemView::GetSkin( int iTeam, bool bViewmodel /*= false*/ ) const +{ + int iDefaultSkin = -1; +#ifndef CSTRIKE_DLL + // Immediately abort if we're out of range. + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS ) + return 0; + + // Do we have a style set? + if ( GetStaticData()->GetNumStyles() ) + return GetStaticData()->GetStyleSkin( GetItemStyle(), iTeam, bViewmodel ); + + iTeam = GetStaticData()->GetBestVisualTeamData( iTeam ); + if ( iTeam < 0 || iTeam >= TEAM_VISUAL_SECTIONS ) + return -1; + + // Do we have per-team skins set? + const perteamvisuals_t *pVisData = GetStaticData()->GetPerTeamVisual( iTeam ); + if ( pVisData ) + return pVisData->iSkin; + + iDefaultSkin = GetItemDefinition()->GetDefaultSkin(); +#endif + + // Fallback case. + return iDefaultSkin; +} + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Handle assignment for textures, which involves some reference counting shenanigans. +//----------------------------------------------------------------------------- +void CEconItemView::SetWeaponSkinBase( ITexture* pBaseTex ) +{ + SafeAssign( &m_pWeaponSkinBase, pBaseTex ); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle assignment for compositors, which involves some reference counting shenanigans. +//----------------------------------------------------------------------------- +void CEconItemView::SetWeaponSkinBaseCompositor( ITextureCompositor * pTexCompositor ) +{ + SafeAssign( &m_pWeaponSkinBaseCompositor, pTexCompositor ); +} + +//----------------------------------------------------------------------------- +// Purpose: Cancels a pending composite, if one is currently in process. +//----------------------------------------------------------------------------- +void CEconItemView::CancelWeaponSkinComposite( ) +{ + SafeRelease( &m_pWeaponSkinBaseCompositor ); +} +#endif // CLIENT_DLL + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemView::GetWorldDisplayModel() const +{ + CEconItemDefinition *pData = GetStaticData(); + if ( !pData ) + return NULL; + + return pData->GetWorldDisplayModel(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CEconItemView::GetExtraWearableModel() const +{ + CEconItemDefinition *pData = GetStaticData(); + if ( !pData ) + return NULL; + + return pData->GetExtraWearableModel(); +} + +const char *CEconItemView::GetExtraWearableViewModel() const +{ + CEconItemDefinition *pData = GetStaticData(); + if ( !pData ) + return NULL; + + return pData->GetExtraWearableViewModel(); +} + +const char *CEconItemView::GetVisionFilteredDisplayModel() const +{ + CEconItemDefinition *pData = GetStaticData(); + if ( !pData ) + return NULL; + + return pData->GetVisionFilteredDisplayModel(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconItemView::GetQualityParticleType() const +{ + static CSchemaParticleHandle pSparkleSystem( "community_sparkle" ); + + CEconItem* pItem = GetSOCData(); + if ( !pItem ) + return 0; + + if( GetSOCData()->GetQuality() == AE_SELFMADE || GetSOCData()->GetQuality() == AE_COMMUNITY ) + return pSparkleSystem ? pSparkleSystem->nSystemID : 0; + else + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the animation set that this item wants the player to use (ie., melee, item1, pda) +//----------------------------------------------------------------------------- +int CEconItemView::GetAnimationSlot( void ) const +{ + if ( !GetStaticData() ) + return -1; + +#if defined( CSTRIKE_DLL ) || defined( DOTA_DLL ) + return -1; +#else + return GetStaticData()->GetAnimSlot(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Return an int that indicates whether the item should be dropped from a dead owner. +//----------------------------------------------------------------------------- +int CEconItemView::GetDropType( void ) +{ + if ( !GetStaticData() ) + return 0; + return GetStaticData()->GetDropType(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::DestroyAllAttributes( void ) +{ + m_AttributeList.DestroyAllAttributes(); + m_NetworkedDynamicAttributesForDemos.DestroyAllAttributes(); + NetworkStateChanged(); + MarkDescriptionDirty(); +} + +extern const char *g_EffectTypes[NUM_EFFECT_TYPES]; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#ifdef CLIENT_DLL +const wchar_t *CEconItemView::GetItemName() const +{ + static const wchar_t *pwzDefaultName = L""; + + const CEconItemDescription *pDescription = GetDescription(); + if ( !pDescription ) + return pwzDefaultName; + + const econ_item_description_line_t *pNameDescLine = pDescription->GetFirstLineWithMetaType( kDescLineFlag_Name ); + if ( !pNameDescLine ) + return pwzDefaultName; + + return pNameDescLine->sText.Get(); +} + +//----------------------------------------------------------------------------- +void CEconItemView::GetRenderBounds( Vector& mins, Vector& maxs ) +{ + const CEconItemDefinition *pDef = GetStaticData(); + if ( !pDef ) + return; + + int iClass = 0; + int iTeam = 0; + +#ifdef TF_CLIENT_DLL + C_TFPlayer *pTFPlayer = ToTFPlayer( GetPlayerByAccountID( GetAccountID() ) ); + if ( pTFPlayer ) + { + iClass = pTFPlayer->GetPlayerClass()->GetClassIndex(); + iTeam = pTFPlayer->GetTeamNumber(); + } +#endif // TF_CLIENT_DLL + + const char * pszModel = GetPlayerDisplayModel( iClass, iTeam ); + if ( !pszModel ) + return; + + int iIndex = modelinfo->GetModelIndex( pszModel ); + + if ( iIndex == -1 ) + { + // hard load the model to get its bounds + MDLHandle_t hMDLFindResult = g_pMDLCache->FindMDL( pszModel ); + MDLHandle_t hMDL = pszModel ? hMDLFindResult : MDLHANDLE_INVALID; + if ( g_pMDLCache->IsErrorModel( hMDL ) ) + return; + + const studiohdr_t * pStudioHdr = g_pMDLCache->GetStudioHdr( hMDL ); + VectorCopy( pStudioHdr->hull_min, mins ); + VectorCopy( pStudioHdr->hull_max, maxs ); + + g_pMDLCache->Release( hMDLFindResult ); + } + else + { + const model_t *pModel = modelinfo->GetModel( iIndex ); + modelinfo->GetModelRenderBounds( pModel, mins, maxs ); + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconItemView::InitNetworkedDynamicAttributesForDemos( void ) +{ + if ( !GetSOCData() ) + return; + + class CEconDynamicAttributesForDemosIterator : public CEconItemSpecificAttributeIterator + { + public: + CEconDynamicAttributesForDemosIterator( CAttributeList* out_NetworkedDynamicAttributesForDemos ) + : m_NetworkedDynamicAttributesForDemos( out_NetworkedDynamicAttributesForDemos ) + { + m_bAdded = false; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, attrib_value_t value ) OVERRIDE + { + CEconItemAttribute attribute( pAttrDef->GetDefinitionIndex(), value ); + m_NetworkedDynamicAttributesForDemos->AddAttribute( &attribute ); + m_bAdded = true; + return true; + } + + virtual bool OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float value ) OVERRIDE + { + CEconItemAttribute attribute( pAttrDef->GetDefinitionIndex(), value ); + m_NetworkedDynamicAttributesForDemos->AddAttribute( &attribute ); + m_bAdded = true; + return true; + } + + bool BAdded( void ){ return m_bAdded; } + + private: + bool m_bAdded; + CAttributeList *m_NetworkedDynamicAttributesForDemos; + }; + + m_NetworkedDynamicAttributesForDemos.DestroyAllAttributes(); + + CEconDynamicAttributesForDemosIterator it( &m_NetworkedDynamicAttributesForDemos ); + IterateAttributes( &it ); + + if ( it.BAdded() ) + { + NetworkStateChanged(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static int RemapOverridePaintIndexToRGB( uint32 unIndex, uint32 unTeamIndex ) +{ + enum { kSamplePoints = 256, }; + + static uint32 k_unWitchYellow[] = { + 5328971, 5328971, 5328971, 5328971, 5328971, 5328971, 5328971, 5328971, + 5328971, 5328971, 5395018, 5591625, 5723465, 5855050, 5921095, 6052679, + 6315591, 6447429, 6513222, 6710852, 7039299, 7170884, 7433793, 7631425, + 7894336, 8222784, 8354622, 8618045, 8815420, 9078333, 9406779, 9604411, + 9802040, 10064952, 10328376, 10459959, 10722870, 11051318, 11314483, 11511859, + 11709490, 11841074, 12103729, 12301360, 12498479, 12630063, 12761901, 12959022, + 13090604, 13156651, 13288236, 13485099, 13616683, 13682474, 13748010, 13813801, + 13945386, 13945386, 14011433, 14011433, 14011433, 13945386, 13945386, 13879594, + 13814314, 13813801, 13748010, 13682474, 13616681, 13616683, 13550890, 13550890, + 13354027, 13288234, 13288236, 13222443, 13222443, 13156651, 13156651, 13025322, + 13025324, 12959531, 12893740, 12893740, 12893740, 12762413, 12696621, 12696621, + 12630828, 12630574, 12630574, 12499760, 12433713, 12433713, 12367922, 12302642, + 12302387, 12236596, 12236598, 12171575, 12105528, 12039736, 11908409, 11974204, + 11908413, 11842880, 11842880, 11842880, 11908421, 11776837, 11645765, 11579973, + 11579975, 11514182, 11448137, 11382344, 11316298, 11184712, 11053387, 11053387, + 10921802, 10855755, 10724941, 10659150, 10658896, 10527311, 10527057, 10395986, + 10330195, 10264147, 10264147, 10264149, 10263895, 10198104, 10132313, 10198106, + 10132058, 10263133, 10263133, 10328928, 10394721, 10394723, 10328675, 10394981, + 10460263, 10526056, 10329190, 10394983, 10329188, 10394723, 10328928, 10328926, + 10526042, 10526040, 10394452, 10459985, 10394446, 10460235, 10525511, 10525763, + 10657089, 10657341, 10723131, 10854456, 10920502, 11183156, 11380273, 11512111, + 11906350, 12038190, 12301100, 12629804, 12827436, 13221676, 13419306, 13616170, + 13813803, 13945386, 14077226, 13879593, 13813801, 13616170, 13485099, 13221674, + 12893227, 12630057, 12301098, 12103723, 11775018, 11511594, 11117353, 10853929, + 10459431, 10130984, 9868327, 9670182, 9472807, 9275686, 9013027, 8815396, + 8618019, 8420385, 8289569, 8223006, 8026399, 7894813, 7763484, 7829020, + 7763738, 7894810, 7960857, 8158234, 8552475, 8684059, 8881690, 9079324, + 9210908, 9407772, 9605405, 9671198, 9736989, 9736989, 9736991, 9605918, + 9473565, 9341979, 9078809, 8684059, 8355096, 7763224, 7434010, 6973206, + 6447895, 6118679, 5723416, 5394715, 5131549, 5000483, 5000234, 4999981, + 5000242, 4999990, 4868407, 4671291, 4539707, 4539709, 4605502, 4671041, + 4868420, 5000006, 5065799, 5131592, 5197385, 5263178, 5328971, 5263178, + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_unWitchYellow ) == kSamplePoints ); + + static uint32 k_unDistinctiveLackOfSanity[] = + { + 5720667, 5720667, 5786460, 5720667, 5786460, 5786460, 5851996, 5851741, + 6048606, 6048606, 6114145, 6114145, 6245474, 6311013, 6311013, 6441829, + 6507624, 6573417, 6704491, 6704491, 6704493, 6901359, 6901359, 7097970, + 7097970, 7163252, 7229045, 7294840, 7425656, 7491449, 7622523, 7688062, + 7819137, 7950209, 8147077, 8277894, 8409226, 8540301, 8540303, 8671376, + 8737171, 8737171, 8867985, 8671376, 8671374, 8540301, 8475019, 8475273, + 8278408, 8147334, 8082309, 7885441, 7819902, 7754366, 7754621, 7558012, + 7492217, 7426937, 7361142, 7164533, 7164531, 7164529, 7033456, 6967663, + 6902382, 6836587, 6771564, 6771564, 6639978, 6574185, 6640232, 6508903, + 6443110, 6574439, 6443110, 6442599, 6442599, 6573415, 6508392, 6573417, + 6507624, 6572905, 6572905, 6572907, 6638187, 6703212, 6768494, 6834289, + 6965105, 6964850, 7030132, 7029621, 7226486, 7160439, 7225720, 7356538, + 7421820, 7421820, 7487102, 7617920, 7617921, 7748226, 7814021, 7879303, + 7944328, 7944330, 8074635, 8271500, 8271246, 8336527, 8336529, 8598420, + 8532629, 8597911, 8794521, 8859803, 8859803, 8990878, 9121440, 9120928, + 9317793, 9317539, 9382823, 9448103, 9513898, 9644714, 9709995, 9840813, + 9840815, 9906095, 9971890, 9971378, 10102194, 10167989, 10233269, 10364085, + 10364087, 10363833, 10429369, 10429114, 10560186, 10625725, 10625468, 10691007, + 10691007, 10756543, 10756289, 10756289, 10756289, 10756289, 10691007, 10625214, + 10625468, 10559932, 10560186, 10429114, 10363576, 10298040, 10232501, 10101429, + 10101683, 9970608, 9970608, 9773998, 9773998, 9642923, 9511848, 9511848, + 9381030, 9381030, 9249956, 9053345, 8987806, 8922270, 8856731, 8725656, + 8594839, 8463510, 8397971, 8201360, 8070287, 8070285, 7939211, 7807625, + 7676805, 7545732, 7479937, 7283582, 7152508, 7086972, 6890361, 6759029, + 6627700, 6496882, 6366064, 6300269, 6169194, 6103399, 5840997, 5775716, + 5513312, 5448030, 5251162, 5054551, 5054806, 4857684, 4661073, 4595534, + 4398666, 4267337, 4136776, 4070981, 4005186, 3874111, 3677502, 3480634, + 3414840, 3283767, 3152951, 3152949, 3153203, 3153203, 3153460, 3153460, + 3219253, 3285046, 3350839, 3416632, 3482425, 3614011, 3811133, 3942465, + 4008260, 4336455, 4533321, 4664908, 4730701, 4993362, 5124948, 5256536, + 5650013, 5781599, 6044003, 6175589, 6306920, 6504042, 6701167, 6898035, + 7029621, 7292279, 7489404, 7686526, 7752064, 8014722, 8080261, 8277127, + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_unDistinctiveLackOfSanity ) == kSamplePoints ); + + static uint32 k_unOverabundanceOfRottingFlesh[] = + { + 12703514, 12703514, 12703514, 12703516, 12703516, 12703514, 12572700, 12506907, + 12506907, 12506652, 12506907, 12506907, 12506652, 12506652, 12506907, 12506652, + 12506652, 12506652, 12506654, 12506654, 12440861, 12441374, 12441374, 12375581, + 12375581, 12375583, 12375583, 12375583, 12375583, 12309536, 12309536, 12309536, + 12309536, 12309537, 12112928, 12243744, 12243744, 12177697, 12046881, 12046881, + 11981090, 11915299, 11915299, 11915044, 11915044, 11849253, 11783206, 11783206, + 11717415, 11717417, 11651624, 11585576, 11519785, 11519787, 11453994, 11388460, + 11387948, 11256621, 11190830, 11190832, 11124785, 11058993, 11058995, 10992948, + 10992950, 10861364, 10795572, 10729527, 10729527, 10663736, 10597690, 10466106, + 10466106, 10465852, 10400062, 10334015, 10202431, 10202433, 10136640, 10136385, + 10004801, 10004803, 10004549, 9938243, 10004037, 9938244, 9938246, 9872199, + 9937478, 9937224, 9871431, 9871433, 9871433, 9805640, 9870921, 9805128, + 9805128, 9805128, 9805128, 9805128, 9739337, 9804360, 9804360, 9804360, + 9804360, 9804360, 9869640, 9869640, 9803847, 9803847, 9803847, 9803847, + 9738054, 9803334, 9737541, 9672261, 9672261, 9672261, 9606468, 9606470, + 9606470, 9540677, 9475397, 9475397, 9409604, 9409604, 9212739, 9212741, + 9212741, 9146948, 9081668, 9081668, 8950596, 8950596, 9015875, 9015875, + 9081668, 9081668, 9081664, 8950850, 8950848, 8885568, 8819775, 8951359, + 8950846, 9016894, 9016892, 9016892, 9016890, 9147962, 9016376, 9147958, + 9082165, 9213494, 9081907, 9147698, 9147698, 9278768, 9212975, 9212973, + 9212716, 9278508, 9212713, 9212456, 9343272, 9212454, 9343524, 9343011, + 9277218, 9408034, 9342241, 9407523, 9341730, 9341730, 9341219, 9275426, + 9406242, 9339938, 9339938, 9339171, 9273380, 9272613, 9272613, 9141030, + 9206310, 9140519, 9140265, 9205290, 9138985, 9138985, 9138987, 9073453, + 9007662, 9007150, 9007150, 9137968, 9072945, 8941618, 9138484, 9007413, + 9138999, 9204788, 9270581, 9205556, 9271604, 9272115, 9469490, 9404210, + 9536049, 9536817, 9603119, 9734960, 9866542, 9801772, 9999147, 9999913, + 10197288, 10263848, 10264613, 10462244, 10462754, 10463776, 10661152, 10792988, + 10859547, 10991642, 11057688, 11190040, 11256086, 11388435, 11520274, 11521042, + 11652625, 11784463, 11784974, 11916813, 11916813, 11982604, 12048397, 12114188, + 12179981, 12246028, 12246028, 12311308, 12311308, 12311308, 12311308, 12377101, + 12377101, 12377101, 12377101, 12377101, 12377101, 12377101, 12377101, 12377101, + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_unOverabundanceOfRottingFlesh ) == kSamplePoints ); + + // orange_flash + static uint32 k_unTheFlamesBelow[] = + { + 11548953, 11614745, 11746074, 11877659, 12008987, 12140572, 12337693, 12469278, + 12666655, 12863776, 13060897, 13258274, 13455395, 13652772, 13849893, 14047014, + 14244391, 14375976, 14573097, 14704681, 14836010, 14967595, 15033387, 15164716, + 15164972, 15230508, 15230508, 15230508, 15164716, 15033387, 14967595, 14836010, + 14638889, 14507304, 14310183, 14112806, 13915685, 13718308, 13455651, 13258274, + 13060897, 12863776, 12666399, 12469278, 12272157, 12075036, 11943451, 11811866, + 11680538, 11614745, 11548953, 11548953, 11548953, 11614745, 11680538, 11746330, + 11877659, 12009244, 12206364, 12403485, 12600862, 12797983, 12995360, 13192482, + 13389859, 13652772, 13849893, 14047270, 14244391, 14441512, 14638889, 14770474, + 14901802, 15033387, 15099179, 15164972, 15230508, 15230508, 15098924, 14835755, + 14572586, 14177832, 13783078, 13322789, 12862755, 12468001, 12139295, 11811101, + 11614491, 11548954, 11549209, 11747351, 12011542, 12407317, 12869139, 13396754, + 13924369, 14451983, 14979598, 15506957, 15902989, 16298508, 16562443, 16759819, + 16760075, 16562188, 16166669, 15704590, 15110927, 14451217, 13791507, 13132053, + 12538390, 12011032, 11681049, 11548953, 11614745, 11812888, 12142615, 12538646, + 13066005, 13593875, 14187538, 14780944, 15308815, 15836430, 16232204, 16561932, + 16694283, 16760075, 16561932, 16100621, 15506958, 14846992, 14121490, 13395732, + 12670486, 12142359, 11746585, 11548953, 11614745, 11812632, 12076824, 12472343, + 12934165, 13395988, 13923859, 14517265, 15044880, 15506702, 15968269, 16298508, + 16562187, 16759819, 16760075, 16627979, 16298508, 15902733, 15375374, 14781967, + 14188560, 13594897, 13066771, 12539156, 12077334, 11747351, 11549209, 11548954, + 11680028, 11876637, 12139295, 12533793, 12928291, 13323045, 13783335, 14178088, + 14572586, 14836011, 15098924, 15230508, 15230508, 15164972, 15099179, 15033387, + 14901802, 14770474, 14638889, 14441768, 14244391, 14047270, 13849893, 13652772, + 13455395, 13192482, 12995360, 12797983, 12600862, 12403485, 12206364, 12074780, + 11877659, 11746330, 11680538, 11614745, 11548953, 11548953, 11548953, 11614745, + 11680538, 11811866, 11943451, 12075036, 12272157, 12403742, 12600863, 12863776, + 13060897, 13258274, 13455395, 13718308, 13915685, 14112806, 14310183, 14507304, + 14638889, 14836010, 14967339, 15033387, 15164716, 15230508, 15230508, 15230508, + 15164972, 15164716, 15033387, 14967595, 14836010, 14704681, 14573097, 14375976, + 14244391, 14047014, 13849893, 13652772, 13455395, 13258274, 13060897, 12863776, + 12666655, 12469278, 12337693, 12140572, 12008987, 11877659, 11746074, 11614745, + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_unTheFlamesBelow ) == kSamplePoints ); + + // green_pulse + static uint32 k_unThatQueesyFeeling[] = + { + 7439904, 7571489, 7703329, 7900706, 8032547, 8295716, 8493349, 8756518, + 9019943, 9283369, 9546794, 9810219, 10073644, 10402606, 10666031, 10929200, + 11192625, 11390259, 11653428, 11851061, 12048437, 12180278, 12312119, 12443703, + 12509496, 12575288, 12575288, 12509496, 12443703, 12312119, 12180278, 11982901, + 11785268, 11522099, 11258674, 10995249, 10731823, 10468398, 10139181, 9810219, + 9546794, 9217576, 8954151, 8690726, 8427557, 8229924, 7966755, 7834914, + 7637537, 7571488, 7440160, 7439904, 7439904, 7505696, 7637281, 7769121, + 7900962, 8098339, 8361508, 8624933, 8888359, 9151784, 9415209, 9744171, + 10073388, 10336814, 10666031, 10929456, 11192882, 11456051, 11719476, 11982645, + 12114486, 12311863, 12443447, 12509496, 12575288, 12575288, 12509496, 12443703, + 12311863, 12180278, 11982645, 11785012, 11521843, 11258674, 10995249, 10731823, + 10402862, 10139181, 9810219, 9546794, 9283368, 8954151, 8690982, 8427557, + 8229924, 8032291, 7834914, 7703073, 7571489, 7505696, 7439904, 7439904, + 7505696, 7571745, 7703329, 7900706, 8098083, 8295716, 8559141, 8822310, + 9085736, 9349161, 9678378, 9941804, 10270765, 10534447, 10863408, 11126833, + 11390258, 11653428, 11851061, 12048694, 12246070, 12377911, 12509240, 12575288, + 12575288, 12575288, 12509240, 12377911, 12246070, 12048694, 11851061, 11653428, + 11390258, 11126833, 10863408, 10534447, 10270765, 9941804, 9678378, 9349161, + 9085736, 8822310, 8559141, 8295716, 8098083, 7900706, 7703329, 7571745, + 7505696, 7439904, 7439904, 7505696, 7571489, 7703073, 7834914, 8032291, + 8229924, 8427557, 8690982, 8954151, 9283368, 9546794, 9810219, 10139181, + 10402862, 10731823, 10995249, 11258674, 11521843, 11785012, 11982645, 12180278, + 12311863, 12443703, 12509496, 12575288, 12575288, 12509496, 12443447, 12311863, + 12180022, 11982645, 11719476, 11521843, 11258418, 10929456, 10666031, 10336814, + 10073388, 9744427, 9480745, 9151784, 8888359, 8624933, 8361508, 8163875, + 7900962, 7769121, 7637281, 7505696, 7439904, 7439904, 7440160, 7571488, + 7637537, 7834914, 7966755, 8164132, 8427557, 8690726, 8954151, 9217576, + 9546538, 9810219, 10139181, 10402606, 10731823, 10995249, 11258674, 11521843, + 11785268, 11982645, 12180278, 12312119, 12443703, 12509496, 12575288, 12575288, + 12509496, 12443703, 12312119, 12180278, 12048437, 11851061, 11653428, 11390259, + 11192625, 10929200, 10666031, 10402606, 10073644, 9810219, 9546794, 9283369, + 9019943, 8756518, 8493349, 8295716, 8032547, 7900706, 7703329, 7571489, + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_unThatQueesyFeeling ) == kSamplePoints ); + + // blue_pulse + static uint32 k_unBubbleBubble[] = + { + 9094364, 9160156, 9291485, 9357278, 9488607, 9685472, 9816801, 10013922, + 10145251, 10342372, 10539237, 10736358, 10933479, 11130345, 11327466, 11458795, + 11655916, 11852781, 11984366, 12115695, 12247024, 12378352, 12444145, 12509681, + 12575474, 12641010, 12641010, 12575474, 12509681, 12444145, 12312816, 12181487, + 12049902, 11853037, 11655916, 11458795, 11261930, 11064808, 10802151, 10605030, + 10407908, 10211043, 10013922, 9816801, 9619936, 9488607, 9357022, 9225693, + 9160156, 9094364, 9094364, 9094364, 9159900, 9225693, 9357022, 9553886, + 9751008, 9948129, 10210786, 10473444, 10736102, 11064551, 11392745, 11655659, + 11983853, 12312303, 12640496, 12903410, 13166068, 13494262, 13691383, 13954040, + 14150905, 14282490, 14413819, 14545148, 14545148, 14545148, 14545148, 14413819, + 14282490, 14151162, 13954040, 13756919, 13494262, 13231604, 12968947, 12706289, + 12377839, 12115181, 11786732, 11458538, 11195880, 10867430, 10604773, 10342115, + 10079458, 9816800, 9619679, 9422814, 9291485, 9160156, 9094364, 9094364, + 9094364, 9094364, 9160157, 9291485, 9422814, 9554143, 9685472, 9882593, + 10079458, 10276579, 10473701, 10736358, 10933479, 11130345, 11327466, 11590123, + 11787245, 11918574, 12115695, 12247024, 12378352, 12509681, 12575218, 12641010, + 12641010, 12641010, 12575218, 12509681, 12378352, 12247024, 12115695, 11918574, + 11787245, 11590123, 11327466, 11130345, 10933479, 10736358, 10473701, 10276579, + 10079458, 9882593, 9685472, 9554143, 9422814, 9291485, 9160157, 9094364, + 9094364, 9094364, 9094364, 9160156, 9291485, 9422814, 9619679, 9816800, + 10079458, 10342115, 10604773, 10867430, 11195880, 11458538, 11786732, 12115181, + 12377839, 12706289, 12968947, 13231604, 13494262, 13756919, 13954040, 14151162, + 14282490, 14413819, 14545148, 14545148, 14545148, 14545148, 14413819, 14282490, + 14150905, 13954040, 13691383, 13494262, 13231604, 12903410, 12640497, 12312303, + 11983853, 11721195, 11392745, 11064551, 10801638, 10473444, 10210786, 9948129, + 9751008, 9553887, 9357022, 9225693, 9159900, 9094364, 9094364, 9094364, + 9160156, 9225693, 9357022, 9488351, 9619935, 9816801, 10013922, 10210787, + 10407908, 10605030, 10802151, 11064808, 11261674, 11458795, 11655916, 11853037, + 12049902, 12181231, 12312560, 12443889, 12575217, 12641010, 12641010, 12641010, + 12575474, 12575217, 12509681, 12378352, 12247024, 12115695, 11984366, 11852781, + 11655916, 11524331, 11327466, 11130345, 10933479, 10736358, 10539237, 10342372, + 10145251, 10013922, 9816801, 9685472, 9554143, 9422814, 9291485, 9160156, + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_unBubbleBubble ) == kSamplePoints ); + + // purple_orange_rand + static uint32 k_unAfraidOfShadowsDark[] = + { + 4536928, 4536928, 4602721, 4602721, 4668515, 4668515, 4734308, 4734308, + 4734308, 4734309, 4734309, 4734309, 4865896, 4865896, 4931689, 4931689, + 4997226, 4997226, 5063019, 5063019, 5128813, 5194606, 5194607, 5260400, + 5260400, 5391730, 5457523, 5457524, 5457524, 5523317, 5589111, 5654904, + 5654905, 5654905, 5720698, 5852028, 5917821, 5917822, 5983615, 5983615, + 6115202, 6115202, 6115203, 6180996, 6312326, 6378119, 6378120, 6443913, + 6444683, 6576785, 6577556, 6709913, 6710941, 6843555, 6844583, 6845868, + 6912945, 6914230, 6915515, 6982592, 6983877, 6985161, 6986446, 6987986, + 6989014, 6924762, 6926046, 6926817, 6862308, 6863079, 6863593, 6798827, + 6799597, 6799083, 6929385, 6863079, 6927845, 6992866, 7057632, 7187677, + 7252442, 7382487, 7447251, 7577296, 7641805, 7772362, 7836871, 7967172, + 8097473, 8227774, 8292283, 8422841, 8487607, 8749750, 8814516, 8879794, + 8945072, 9075888, 8946614, 8752575, 8427464, 8102353, 7842008, 7647197, + 7582176, 7842777, 8233938, 8559562, 8950979, 9277117, 9537977, 9733814, + 9734070, 9734070, 9734070, 9734070, 9734071, 9799863, 9799863, 9799863, + 9799863, 9799863, 9799863, 9799863, 9799863, 9799604, 9799863, 9799604, + 9799344, 9733548, 9733032, 9732771, 9732509, 9731992, 9731473, 9731211, + 9796485, 9861502, 9861239, 9926256, 10057321, 10122338, 10253403, 10450004, + 10580557, 10777414, 10974016, 11235897, 11432754, 11629612, 11825958, 12088353, + 12350747, 12547606, 12875538, 13072654, 13203723, 13531656, 13729032, 13729032, + 13794568, 13991688, 14188808, 14254344, 14385672, 14451464, 14648328, 14845704, + 14977032, 15108360, 15239689, 15436809, 15568393, 15831050, 15962892, 16160781, + 16226832, 16293140, 16294170, 16294943, 16295713, 16296229, 16297257, 16363566, + 16364595, 16365365, 16431930, 16432957, 16433729, 16500037, 16501065, 16501836, + 16502864, 16503634, 16569685, 16570712, 16571482, 16637789, 16638303, 16639072, + 16639586, 16640355, 16640869, 16707173, 16707430, 16707943, 16708456, 16708712, + 16709224, 16775016, 16775272, 16775784, 16776040, 16776552, 16776295, 16776551, + 16776551, 16776039, 16709991, 16709736, 16709224, 16511336, 16247913, 16116074, + 15786858, 15457899, 15193963, 14865004, 14601837, 14206573, 13811822, 13351022, + 12890479, 12429935, 11969392, 11508591, 11048049, 10587504, 10192752, 9732208, + 9337455, 8877166, 8482670, 8153453, 7758957, 7430252, 7101291, 6772586, + 6509418, 6246505, 5983337, 5786216, 5588840, 5391719, 5194599, 5063014, + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_unAfraidOfShadowsDark ) == kSamplePoints ); + + static uint32 *k_pPointSampleContent_Team0[] = + { + &k_unWitchYellow[0], + &k_unDistinctiveLackOfSanity[0], + &k_unOverabundanceOfRottingFlesh[0], + &k_unTheFlamesBelow[0], + &k_unThatQueesyFeeling[0], + &k_unAfraidOfShadowsDark[0], + }; + + static uint32 *k_pPointSampleContent_Team1[] = + { + &k_unWitchYellow[0], + &k_unDistinctiveLackOfSanity[0], + &k_unOverabundanceOfRottingFlesh[0], + &k_unBubbleBubble[0], + &k_unThatQueesyFeeling[0], + &k_unAfraidOfShadowsDark[0], + }; + COMPILE_TIME_ASSERT( ARRAYSIZE( k_pPointSampleContent_Team0 ) == ARRAYSIZE( k_pPointSampleContent_Team1 ) ); + + if ( unIndex >= ARRAYSIZE( k_pPointSampleContent_Team0 ) ) + return 0; + + if ( unTeamIndex > 1 ) + return 0; + + const float fScaledTime = gpGlobals->curtime * 22.0f; // arbitrary time scalar people liked + const unsigned int unSamplePoint0 = (unsigned int)fScaledTime % kSamplePoints; + const unsigned int unSamplePoint1 = (unSamplePoint0 + 1) % kSamplePoints; + + const float fDelta = fScaledTime - (unsigned int)fScaledTime; // offset between two sample points for blending + + const uint32 *punData = (unTeamIndex == 0 ? k_pPointSampleContent_Team0 : k_pPointSampleContent_Team1)[unIndex]; + + Color c0; + c0.SetRawColor( punData[unSamplePoint0] ); + + Color c1; + c1.SetRawColor( punData[unSamplePoint1] ); + + const Color cBlend( Lerp( fDelta, c0.r(), c1.r() ), + Lerp( fDelta, c0.g(), c1.g() ), + Lerp( fDelta, c0.b(), c1.b() ) ); + + return cBlend.GetRawColor(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get RGB modifying attribute value +//----------------------------------------------------------------------------- +int CEconItemView::GetModifiedRGBValue( bool bAltColor ) +{ + enum + { + kPaintConstant_Default = 0, + kPaintConstant_OldTeamColor = 1, + }; + + static CSchemaAttributeDefHandle pAttr_Paint( "set item tint rgb" ); + static CSchemaAttributeDefHandle pAttr_Paint2( "set item tint rgb 2" ); + + static CSchemaAttributeDefHandle pAttr_PaintOverride( "SPELL: set item tint RGB" ); + + // Do we have an override paint color? This takes precedence over base paints and team + // paints. +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + extern bool TF_IsHolidayActive( /*EHoliday*/ int eHoliday ); + + if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) ) +#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + { + if ( !m_bPaintOverrideInit ) + { + m_bHasPaintOverride = FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, pAttr_PaintOverride, &m_flOverrideIndex ); + m_bPaintOverrideInit = true; + } + + if ( m_bHasPaintOverride ) + return RemapOverridePaintIndexToRGB( (uint32)m_flOverrideIndex, bAltColor ? 1 : 0 ); + } + + if ( !m_bColorInit ) + { + // See if we also have a secondary paint color. + uint32 unRGB = kPaintConstant_Default; + uint32 unRGBAlt = kPaintConstant_Default; + float fRGB; + float fRGBAlt; + + // If we have no base paint color we don't do anything special. + if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, pAttr_Paint, &fRGB ) ) + { + unRGB = (uint32)fRGB; + unRGBAlt = unRGB; + } + + // Backwards compatibility for old team colored items. + if ( unRGB == kPaintConstant_OldTeamColor ) + { + unRGB = RGB_INT_RED; + unRGBAlt = RGB_INT_BLUE; + } + else if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( this, pAttr_Paint2, &fRGBAlt ) ) + { + unRGBAlt = (uint32)fRGBAlt; + } + else + { + // By default our secondary color will match our primary if we can't find a replacement. + unRGBAlt = unRGB; + } + + m_unRGB = unRGB; + m_unAltRGB = unRGBAlt; + + m_bColorInit = true; + } + + return bAltColor ? m_unAltRGB : m_unRGB; +} + +uint64 CEconItemView::GetCustomUserTextureID() +{ + static CSchemaAttributeDefHandle pAttr_CustomTextureLo( "custom texture lo" ); + static CSchemaAttributeDefHandle pAttr_CustomTextureHi( "custom texture hi" ); + + uint32 unLowVal, unHighVal; + const bool bHasLowVal = FindAttribute( pAttr_CustomTextureLo, &unLowVal ), + bHasHighVal = FindAttribute( pAttr_CustomTextureHi, &unHighVal ); + + // We should have both, or neither. We should never have just one + Assert( bHasLowVal == bHasHighVal ); + + if ( bHasLowVal && bHasHighVal ) + { + return ((uint64)unHighVal << 32) | (uint64)unLowVal; + } + + // No attribute set + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAttributeList::CAttributeList() +{ + m_pManager = NULL; + m_Attributes.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::SetManager( CAttributeManager *pManager ) +{ + m_pManager = pManager; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::Init() +{ + m_Attributes.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::IterateAttributes( class IEconItemAttributeIterator *pIterator ) const +{ + Assert( pIterator ); + + FOR_EACH_VEC( m_Attributes, i ) + { + const CEconItemAttribute *pAttrInst = &m_Attributes[i]; + + const CEconItemAttributeDefinition *pAttrDef = pAttrInst->GetStaticData(); + if ( !pAttrDef ) + continue; + + const ISchemaAttributeType *pAttrType = pAttrDef->GetAttributeType(); + Assert( pAttrType ); + Assert( pAttrType->BSupportsGameplayModificationAndNetworking() ); + + // We know (and assert) that we only need 32 bits of data to store this attribute + // data. We don't know anything about the type but we'll let the type handle it + // below. + attribute_data_union_t value; + value.asFloat = pAttrInst->m_flValue; + + if ( !pAttrType->OnIterateAttributeValue( pIterator, pAttrDef, value ) ) + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::DestroyAllAttributes( void ) +{ + if ( m_Attributes.Count() ) + { + m_Attributes.Purge(); + NotifyManagerOfAttributeValueChanges(); + NetworkStateChanged(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::AddAttribute( CEconItemAttribute *pAttribute ) +{ + Assert( pAttribute ); + + // Only add attributes to the attribute list if they have a definition we can + // pull data from. + if ( !pAttribute->GetStaticData() ) + return; + + m_Attributes.AddToTail( *pAttribute ); + NetworkStateChanged(); + NotifyManagerOfAttributeValueChanges(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::SetRuntimeAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float flValue ) +{ + Assert( pAttrDef ); + + // Look for an existing attribute. + const int iAttributes = GetNumAttributes(); + for ( int i = 0; i < iAttributes; i++ ) + { + CEconItemAttribute *pAttribute = GetAttribute(i); + + if ( pAttribute->GetAttribIndex() == pAttrDef->GetDefinitionIndex() ) + { + // Found existing attribute -- change value. + pAttribute->m_flValue = flValue; + NotifyManagerOfAttributeValueChanges(); + return; + } + } + + // Couldn't find an existing attribute for this definition -- make a new one. + CEconItemAttribute attribute; + attribute.m_iAttributeDefinitionIndex = pAttrDef->GetDefinitionIndex(); + attribute.m_flValue = flValue; + + m_Attributes.AddToTail( attribute ); + NotifyManagerOfAttributeValueChanges(); +} + +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::SetRuntimeAttributeRefundableCurrency( const CEconItemAttributeDefinition *pAttrDef, int iRefundableCurrency ) +{ + Assert( pAttrDef ); + + // Look for an existing attribute. + const int iAttributes = GetNumAttributes(); + for ( int i = 0; i < iAttributes; i++ ) + { + CEconItemAttribute *pAttribute = GetAttribute(i); + + if ( pAttribute->GetAttribIndex() == pAttrDef->GetDefinitionIndex() ) + { + // Found existing attribute -- change value. + pAttribute->m_nRefundableCurrency = iRefundableCurrency; + return; + } + } + + AssertMsg1( false, "Unable to find attribute '%s' for setting currency!", pAttrDef->GetDefinitionName() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CAttributeList::GetRuntimeAttributeRefundableCurrency( const CEconItemAttributeDefinition *pAttrDef ) const +{ + const int iAttributes = GetNumAttributes(); + for ( int i = 0; i < iAttributes; i++ ) + { + const CEconItemAttribute *pAttribute = GetAttribute(i); + + if ( pAttribute->GetAttribIndex() == pAttrDef->GetDefinitionIndex() ) + return pAttribute->m_nRefundableCurrency; + } + + AssertMsg1( false, "Unable to find attribute '%s' for getting currency!", pAttrDef->GetDefinitionName() ); + return 0; +} +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING + +//----------------------------------------------------------------------------- +// Purpose: Remove an attribute by name +//----------------------------------------------------------------------------- +void CAttributeList::RemoveAttribute( const CEconItemAttributeDefinition *pAttrDef ) +{ + const int iAttributes = m_Attributes.Count(); + for ( int i = 0; i < iAttributes; i++ ) + { + if ( m_Attributes[i].GetStaticData() == pAttrDef ) + { + m_Attributes.Remove( i ); + NotifyManagerOfAttributeValueChanges(); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove an attribute by index +//----------------------------------------------------------------------------- +void CAttributeList::RemoveAttributeByIndex( int iIndex ) +{ + if ( iIndex < 0 || iIndex >= GetNumAttributes() ) + return; + + m_Attributes.Remove( iIndex ); + NotifyManagerOfAttributeValueChanges(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconItemAttribute *CAttributeList::GetAttributeByID( int iAttributeID ) const +{ + int iAttributes = m_Attributes.Count(); + for ( int i = 0; i < iAttributes; i++ ) + { + const CEconItemAttributeDefinition *pData = m_Attributes[i].GetStaticData(); + + if ( pData && ( pData->GetDefinitionIndex() == iAttributeID ) ) + return &m_Attributes[i]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconItemAttribute *CAttributeList::GetAttributeByName( const char *pszAttribDefName ) const +{ + CEconItemAttributeDefinition *pDef = GetItemSchema()->GetAttributeDefinitionByName( pszAttribDefName ); + if ( !pDef ) + return NULL; + + int iAttributes = m_Attributes.Count(); + for ( int i = 0; i < iAttributes; i++ ) + { + if ( m_Attributes[i].GetStaticData()->GetDefinitionIndex() == pDef->GetDefinitionIndex() ) + return &m_Attributes[i]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::operator=( const CAttributeList& src ) +{ + m_Attributes = src.m_Attributes; + + // HACK: We deliberately don't copy managers, because attributelists are contained inside + // CEconItemViews, which we duplicate inside CItemModelPanels all the time. If the manager + // is copied, copies will mess with the attribute caches of the copied item. + // Our manager will be setup properly by the CAttributeManager itself if we have an associated entity. + m_pManager = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAttributeList::NotifyManagerOfAttributeValueChanges( void ) +{ + if ( m_pManager ) + { + m_pManager->OnAttributeValuesChanged(); + } +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool DoesItemPassSearchFilter( const IEconItemDescription *pDescription, const wchar_t* wszFilter ) +{ + // check if item matches name filter + if ( wszFilter && *wszFilter ) + { + if ( !pDescription ) + { + return false; + } + + wchar_t wszBuffer[ 4096 ] = L""; + for ( unsigned int i = 0; i < pDescription->GetLineCount(); i++ ) + { + const econ_item_description_line_t& line = pDescription->GetLine(i); + + if ( !(line.unMetaType & ( kDescLineFlag_Collection | kDescLineFlag_CollectionCurrentItem ) ) ) + { + V_wcscat_safe( wszBuffer, line.sText.Get() ); + } + } + + V_wcslower( wszBuffer ); + if ( !wcsstr( wszBuffer, wszFilter ) ) + { + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBasePlayer *GetPlayerByAccountID( uint32 unAccountID ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer == NULL ) + continue; + + CSteamID steamIDPlayer; + if ( !pPlayer->GetSteamID( &steamIDPlayer ) ) + continue; + + // return the player with the matching ID + if ( steamIDPlayer.GetAccountID() == unAccountID ) + { + return pPlayer; + } + } + + return NULL; +} + +#endif // CLIENT_DLL diff --git a/game/shared/econ/econ_item_view.h b/game/shared/econ/econ_item_view.h new file mode 100644 index 0000000..5ccf825 --- /dev/null +++ b/game/shared/econ/econ_item_view.h @@ -0,0 +1,455 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef ECON_ITEM_CONSTANTS_H +#define ECON_ITEM_CONSTANTS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "game_item_schema.h" +#include "econ_item_constants.h" +#include "localization_provider.h" +#include "econ_item_interface.h" +#include "econ_item.h" + +#if defined(CLIENT_DLL) +#include "iclientrenderable.h" +#endif + +#if defined(TF_DLL) +#include "tf_item_schema.h" +#endif + +#if defined(CLIENT_DLL) +#define CEconItemView C_EconItemView +#endif + +#if defined(GC_DLL) +#error "econ_item_view.h is not intended to be built on the GC!" +#endif + +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) + #define ENABLE_ATTRIBUTE_CURRENCY_TRACKING 1 +#else + #define ENABLE_ATTRIBUTE_CURRENCY_TRACKING 0 +#endif + +class CEconItemAttribute; +class CAttributeManager; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CAttributeList +{ + friend class CEconItemView; + friend class CTFPlayer; + + DECLARE_CLASS_NOBASE( CAttributeList ); +public: + DECLARE_EMBEDDED_NETWORKVAR(); + DECLARE_DATADESC(); + + CAttributeList(); + void operator=( const CAttributeList &src ); + + void Init(); + void SetManager( CAttributeManager *pManager ); + + void IterateAttributes( class IEconItemAttributeIterator *pIterator ) const; + + // Remove all attributes on this item + void DestroyAllAttributes( void ); + + void AddAttribute( CEconItemAttribute *pAttribute ); + + // Remove an attribute by name + void RemoveAttribute( const CEconItemAttributeDefinition *pAttrDef ); + void RemoveAttributeByIndex( int iIndex ); + +public: + // Returns the attribute that matches the attribute defname + const CEconItemAttribute *GetAttributeByName( const char *pszAttribDefName ) const; + + // Returns the attribute that matches the attribute id + const CEconItemAttribute *GetAttributeByID( int iAttributeID ) const; + + // The only way to set the value of an attribute after its creation is through the attribute list + // that contains it. This way the matching attribute manager is told one of its attributes has changed. + void SetRuntimeAttributeValue( const CEconItemAttributeDefinition *pAttrDef, float flValue ); +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING + void SetRuntimeAttributeRefundableCurrency( const CEconItemAttributeDefinition *pAttrDef, int iRefundableCurrency ); + int GetRuntimeAttributeRefundableCurrency( const CEconItemAttributeDefinition *pAttrDef ) const; + + void AdjustRuntimeAttributeRefundableCurrency( const CEconItemAttributeDefinition *pAttrDef, int iRefundableCurrencyAdjustment ) + { + SetRuntimeAttributeRefundableCurrency( pAttrDef, GetRuntimeAttributeRefundableCurrency( pAttrDef ) + iRefundableCurrencyAdjustment ); + } +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING + +private: + void NotifyManagerOfAttributeValueChanges(); + + // Attribute accessing + int GetNumAttributes( void ) const { return m_Attributes.Count(); } + CEconItemAttribute *GetAttribute( int iIndex ) { Assert( iIndex >= 0 && iIndex < m_Attributes.Count()); return &m_Attributes[iIndex]; } + const CEconItemAttribute *GetAttribute( int iIndex ) const { Assert( iIndex >= 0 && iIndex < m_Attributes.Count()); return &m_Attributes[iIndex]; } + + // Our list of attributes + CUtlVector<CEconItemAttribute> m_Attributes; + + CAttributeManager *m_pManager; +}; + +//----------------------------------------------------------------------------- +// Purpose: An attribute that knows how to read itself from a datafile, describe itself to the user, +// and serialize itself between Servers, Clients, and Steam. +// Unlike the attributes created in the Game DLL, this attribute doesn't know how to actually +// do anything in the game, it just knows how to describe itself. +//----------------------------------------------------------------------------- +class CEconItemAttribute +{ + DECLARE_CLASS_NOBASE( CEconItemAttribute ); +public: + DECLARE_EMBEDDED_NETWORKVAR(); + + CEconItemAttribute(); + CEconItemAttribute( const attrib_definition_index_t iAttributeIndex, float flValue ); + CEconItemAttribute( const attrib_definition_index_t iAttributeIndex, uint32 unValue ); + + void operator=( const CEconItemAttribute &val ); + + // Get the index of this attribute's definition inside the script file + attrib_definition_index_t GetAttribIndex( void ) const { return m_iAttributeDefinitionIndex; } + void SetAttribIndex( attrib_definition_index_t iIndex ) { m_iAttributeDefinitionIndex = iIndex; } + + // Get the static data contained in this attribute's definition + const CEconItemAttributeDefinition *GetStaticData( void ) const; + + // Get the float value of this attribute. + //float GetValue( void ) const; + +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING + int GetRefundableCurrency( void ) const { return m_nRefundableCurrency; } +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING + +private: + // The only way to set the value of an attribute after its creation is through the attribute list + // that contains it. This way the matching attribute manager is told one of its attributes has changed. + + // Set the float value of this attribute. + // Note that the value must be stored as a float! + void SetValue( float flValue ); + + // Set the value of this attribute as an unsigned integer. + // Note that the value must be stored as an integer! + // See CEconItemAttributeDefinition + void SetIntValue( uint32 unValue ); + + friend class CAttributeList; + + void Init( void ); + + //-------------------------------------------------------- +private: + // This is the index of the attribute into the attributes read from the data files + CNetworkVar( attrib_definition_index_t, m_iAttributeDefinitionIndex ); + + // This is the value of the attribute. Used to modify the item's variables. + CNetworkVar( float, m_flValue ); + +#if ENABLE_ATTRIBUTE_CURRENCY_TRACKING + // This is the value that the attribute was first set to by an item definition + CNetworkVar( int, m_nRefundableCurrency ); +#endif // ENABLE_ATTRIBUTE_CURRENCY_TRACKING +}; + +//----------------------------------------------------------------------------- +// Purpose: An item that knows how to read itself from a datafile, describe itself to the user, +// and serialize itself between Servers, Clients, and Steam. +// +// In the client DLL, we derive it from CDefaultClientRenderable so that +// it can be passed in the pProxyData parameter of material proxies. +//----------------------------------------------------------------------------- +#if defined(CLIENT_DLL) +class CEconItemView : public CDefaultClientRenderable, public CMaterialOverrideContainer< IEconItemInterface > +#else +class CEconItemView : public CMaterialOverrideContainer< IEconItemInterface > +#endif +{ + DECLARE_CLASS_NOBASE( CEconItemView ); +public: + DECLARE_EMBEDDED_NETWORKVAR(); + DECLARE_DATADESC(); + +public: + CEconItemView(); + CEconItemView( const CEconItemView &src ); + ~CEconItemView(); + CEconItemView& operator=( const CEconItemView &src ); + bool operator==( const CEconItemView &other ) const; + bool operator!=( const CEconItemView &other ) const { return !operator==( other ); } + + virtual const GameItemDefinition_t *GetItemDefinition() const + { + return GetStaticData(); + } + +public: + + // IEconItemInterface implementation. + virtual itemid_t GetID() const { return GetItemID(); } + virtual int32 GetQuality() const; + virtual style_index_t GetStyle() const; + virtual uint8 GetFlags() const; + virtual eEconItemOrigin GetOrigin() const; + virtual int GetQuantity() const; + uint64 GetOriginalID() const { return GetSOCData() ? GetSOCData()->GetOriginalID() : 0; } + + virtual const char *GetCustomName() const; + virtual const char *GetCustomDesc() const; + + virtual bool GetInUse() const { return GetSOCData() ? GetSOCData()->GetInUse() : false; } + + virtual void IterateAttributes( class IEconItemAttributeIterator *pIterator ) const OVERRIDE; + + bool IsValid( void ) const { return m_bInitialized; } + void Invalidate( void ) { m_bInitialized = false; m_iItemDefinitionIndex = INVALID_ITEM_DEF_INDEX; m_iItemID = INVALID_ITEM_ID; } + void InvalidateColor() { m_bColorInit = false; } + void InvalidateOverrideColor() { m_bPaintOverrideInit = false; } + + // Initialize from the specified data + // client will load SO cache as needed + void Init( int iDefIndex, int iQuality, int iLevel, uint32 iAccountID = 0 ); + void SetInitialized( bool bInit ) { m_bInitialized = bInit; } + + // Get the static data contained in this item's definition + GameItemDefinition_t *GetStaticData( void ) const; + + void SetNonSOEconItem( CEconItem* pItem ) { m_pNonSOEconItem.SetItem( pItem ); } + + void OnAttributeValuesChanged() + { + NetworkStateChanged(); + MarkDescriptionDirty(); + } + +private: + void EnsureDescriptionIsBuilt( void ) const; + void MarkDescriptionDirty( void ); +public: + void SetGrayedOutReason( const char *pszGrayedOutReason ); + + // Set & Get the index of this item's definition inside the script file + void SetItemDefIndex( item_definition_index_t iIndex ) { m_iItemDefinitionIndex = iIndex; MarkDescriptionDirty(); } + virtual item_definition_index_t GetItemDefIndex( void ) const { return m_iItemDefinitionIndex; } + + // Set & Get the quality & level of this item. + void SetItemQuality( int iQuality ) { m_iEntityQuality = iQuality; MarkDescriptionDirty(); } + int GetItemQuality( void ) const { return m_iEntityQuality; } + void SetItemLevel( uint32 unLevel ) { m_iEntityLevel = unLevel; MarkDescriptionDirty(); } + uint32 GetItemLevel( void ) const { return m_iEntityLevel; } + + int GetItemQuantity() const; +#ifdef CLIENT_DLL + void SetIsTradeItem( bool bIsTradeItem ) { m_bIsTradeItem = bIsTradeItem; MarkDescriptionDirty(); } + void SetItemQuantity( int iQuantity ) { m_iEntityQuantity = iQuantity; MarkDescriptionDirty(); } + void SetClientItemFlags( uint8 unFlags ); + + void SetItemStyleOverride( style_index_t unNewStyleOverride ); + void SetItemOriginOverride( eEconItemOrigin unNewOriginOverride ); +#endif + style_index_t GetItemStyle() const; + + // Access the worldwide global index of this item + void SetItemID( itemid_t iIdx ) { m_iItemID = iIdx; m_iItemIDHigh = (m_iItemID >> 32); m_iItemIDLow = (m_iItemID & 0xFFFFFFFF); } +#ifdef CLIENT_DLL + // On the client, we need to rebuild it from the high & low networked pieces + itemid_t GetItemID( void ) const { uint64 iTmp = ((((int64)m_iItemIDHigh)<<32) | m_iItemIDLow); return (itemid_t)iTmp; } +#else + itemid_t GetItemID( void ) const { return m_iItemID; } +#endif + + uint32 GetAccountID( void ) const { return m_iAccountID; } + void SetOverrideAccountID( uint32 nAccountID ) { m_iAccountID = nAccountID; } + + // Access the inventory position of this item + void SetInventoryPosition( uint32 iPosition ) { m_iInventoryPosition = iPosition; } + const uint32 GetInventoryPosition( void ) const { return m_iInventoryPosition; } + + // Return the model to use for model panels containing this item + const char *GetInventoryModel( void ); + // Return the image to use for model panels containing this item + const char *GetInventoryImage( void ); + bool GetInventoryImageData( int *iPosition, int *iSize ); + const char *GetInventoryOverlayImage( int idx ); + int GetInventoryOverlayImageCount( void ); + + // Return the model to use when displaying this model on the player character model, if any + const char *GetPlayerDisplayModel( int iClass, int iTeam ) const; + + // Return the model to use when displaying this model in the world. See the notes on this in econ_item_schema.h + const char *GetWorldDisplayModel() const; + const char *GetExtraWearableModel() const; + const char *GetExtraWearableViewModel() const; + const char *GetVisionFilteredDisplayModel() const; + + // Return the load-out slot that this item must be placed into + int GetAnimationSlot( void ) const; + + // Return an int that indicates whether the item should be dropped from a dead owner. + int GetDropType( void ); + + // Remove all attributes on this item + void DestroyAllAttributes( void ); + + void InitNetworkedDynamicAttributesForDemos( void ); + + // Items that have attributes that modify their RGB values + int GetModifiedRGBValue( bool bAltColor=false ); + + // Returns the UGC file ID of the custom texture assigned to this item. If non-zero, then it has a custom texture. + uint64 GetCustomUserTextureID(); + + CEconItem *GetSOCData( void ) const; + + bool IsEquipped( void ) const { return GetSOCData() && GetSOCData()->IsEquipped(); } + bool IsEquippedForClass( equipped_class_t unClass ) const { return GetSOCData() && GetSOCData()->IsEquippedForClass( unClass ); } + equipped_slot_t GetEquippedPositionForClass( equipped_class_t unClass ) const { return GetSOCData() ? GetSOCData()->GetEquippedPositionForClass( unClass ) : INVALID_EQUIPPED_SLOT; } + + // Attached particle systems + int GetQualityParticleType() const; + + int GetSkin( int iTeam, bool bViewmodel = false ) const; + +public: + // ... + CAttributeList *GetAttributeList() { return &m_AttributeList; } + const CAttributeList *GetAttributeList() const { return &m_AttributeList; } + +public: + virtual CEconItemPaintKitDefinition *GetCustomPainkKitDefinition( void ) const { return GetItemDefinition()->GetCustomPainkKitDefinition(); } + +#ifdef CLIENT_DLL + void SetWeaponSkinBase( ITexture* pBaseTex ); + void SetWeaponSkinBaseCompositor( ITextureCompositor * pTexCompositor ); + inline void SetWeaponSkinGeneration( RTime32 nGeneration ) { m_nWeaponSkinGeneration = nGeneration; } + inline void SetWeaponSkinGenerationTeam( int iTeam ) { m_iLastGeneratedTeamSkin = iTeam; } + inline void SetWeaponSkinBaseCreateFlags( uint32 flags ) { m_unWeaponSkinBaseCreateFlags = flags; } + void CancelWeaponSkinComposite( ); + inline void SetWeaponSkinUseHighRes( bool bUseHighRes ) { m_bWeaponSkinUseHighRes = bUseHighRes; } + inline void SetWeaponSkinUseLowRes( bool bUseLowRes ) { m_bWeaponSkinUseLowRes = bUseLowRes; } + + inline ITexture *GetWeaponSkinBase() const { return m_pWeaponSkinBase; } + inline ITextureCompositor *GetWeaponSkinBaseCompositor() const { return m_pWeaponSkinBaseCompositor; } + inline uint32 GetWeaponSkinBaseCreateFlags() const { return m_unWeaponSkinBaseCreateFlags; } + + inline RTime32 GetWeaponSkinGeneration() const { return m_nWeaponSkinGeneration; } + inline int GetWeaponSkinGenerationTeam() const { return m_iLastGeneratedTeamSkin; } + + inline bool ShouldWeaponSkinUseHighRes() const { return m_bWeaponSkinUseHighRes; } + inline bool ShouldWeaponSkinUseLowRes() const { return m_bWeaponSkinUseLowRes; } +#endif // CLIENT_DLL + + inline int GetTeamNumber() const { return m_iTeamNumber; } + inline void SetTeamNumber( int iTeamNumber ) { m_iTeamNumber = iTeamNumber; } + +protected: + // Index of the item definition in the item script file. + CNetworkVar( item_definition_index_t, m_iItemDefinitionIndex ); + + // The quality of this item. + CNetworkVar( int, m_iEntityQuality ); + + // The level of this item. + CNetworkVar( uint32, m_iEntityLevel ); + + // The global index of this item, worldwide. + itemid_t m_iItemID; + CNetworkVar( uint32, m_iItemIDHigh ); + CNetworkVar( uint32, m_iItemIDLow ); + + // Account ID of the person who has this in their inventory + CNetworkVar( uint32, m_iAccountID ); + + // Position inside the player's inventory + CNetworkVar( uint32, m_iInventoryPosition ); + + // This is an alternate source of data, if this item models something that isn't in the SO cache. + CEconItemHandle m_pNonSOEconItem; + +#if defined( CLIENT_DLL ) + // exist on the client only + bool m_bIsTradeItem; + int m_iEntityQuantity; + uint8 m_unClientFlags; + + // clients have the ability to force a style on an item view -- this is used for store previews, + // character panels, etc. + style_index_t m_unOverrideStyle; + // clients can also force an origin on an item view -- this is used for crafting item previews + eEconItemOrigin m_unOverrideOrigin; +#endif + + bool m_bColorInit; + bool m_bPaintOverrideInit; + bool m_bHasPaintOverride; + float m_flOverrideIndex; + uint32 m_unRGB; + uint32 m_unAltRGB; + +#ifdef CLIENT_DLL + ITexture* m_pWeaponSkinBase; + ITextureCompositor* m_pWeaponSkinBaseCompositor; + RTime32 m_nWeaponSkinGeneration; + uint32 m_unWeaponSkinBaseCreateFlags; + int m_iLastGeneratedTeamSkin; + bool m_bWeaponSkinUseHighRes; + bool m_bWeaponSkinUseLowRes; +#endif // CLIENT_DLL + + CNetworkVar( int, m_iTeamNumber ); + + CNetworkVar( bool, m_bInitialized ); + +#ifdef CLIENT_DLL // we avoid using "BUILD_ITEM_NAME_AND_DESC" to prevent everything depending on the CEconItemDescription +public: + // Return the single-line name of this item. + const wchar_t *GetItemName( void ) const; + + // Return the full structure with all of our description lines. + const class CEconItemDescription *GetDescription() const { EnsureDescriptionIsBuilt(); return m_pDescription; } + +private: + mutable class CEconItemDescription *m_pDescription; + mutable char *m_pszGrayedOutReason; + + // IClientRenderable + virtual const Vector& GetRenderOrigin( void ) { return vec3_origin; } + virtual const QAngle& GetRenderAngles( void ) { return vec3_angle; } + virtual bool ShouldDraw( void ) { return false; } + virtual bool IsTransparent( void ) { return false;} + virtual const matrix3x4_t &RenderableToWorldTransform() { static matrix3x4_t mat; SetIdentityMatrix( mat ); return mat; } + virtual void GetRenderBounds( Vector& mins, Vector& maxs ); +#endif + +private: + CNetworkVarEmbedded( CAttributeList, m_AttributeList ); + CNetworkVarEmbedded( CAttributeList, m_NetworkedDynamicAttributesForDemos ); + + // Some custom gamemodes are using server plugins to modify weapon attributes. + // This variable allows them to completely set their own attributes on a weapon + // and have the client and server ignore the static attributes. + CNetworkVar( bool, m_bOnlyIterateItemViewAttributes ); +}; + +#ifdef CLIENT_DLL +bool DoesItemPassSearchFilter( const class IEconItemDescription *pDescription, const wchar_t* wszFilter ); +CBasePlayer *GetPlayerByAccountID( uint32 unAccountID ); +#endif // CLIENT_DLL + +#endif // ECON_ITEM_CONSTANTS_H diff --git a/game/shared/econ/econ_quests.cpp b/game/shared/econ/econ_quests.cpp new file mode 100644 index 0000000..b7a3ef7 --- /dev/null +++ b/game/shared/econ/econ_quests.cpp @@ -0,0 +1,71 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Functions related to dynamic recipes +// +//============================================================================= + + +#include "cbase.h" +#include "econ_quests.h" +#ifndef GC_DLL +#include "quest_objective_manager.h" +#endif + +bool IsQuestItemUnidentified( const CEconItem* pQuestItem ) +{ + return pQuestItem && IsUnacknowledged( pQuestItem->GetInventoryToken() ); +} + +bool IsQuestItemReadyToTurnIn( const IEconItemInterface* pQuestItem ) +{ + uint32 nRequiredPoints = pQuestItem->GetItemDefinition()->GetQuestDef()->GetMaxStandardPoints(); + uint32 nEarnedStandardPoints = GetEarnedStandardPoints( pQuestItem ); + uint32 nEarnedBonusPoints = GetEarnedBonusPoints( pQuestItem ); + + return ( nEarnedStandardPoints + nEarnedBonusPoints ) >= nRequiredPoints; +} + +bool IsQuestItemFullyCompleted( const IEconItemInterface* pQuestItem ) +{ + uint32 nRequiredStandardPoints = pQuestItem->GetItemDefinition()->GetQuestDef()->GetMaxStandardPoints(); + uint32 nRequiredBonusPoints = pQuestItem->GetItemDefinition()->GetQuestDef()->GetMaxBonusPoints(); + uint32 nEarnedStandardPoints = GetEarnedStandardPoints( pQuestItem ); + uint32 nEarnedBonusPoints = GetEarnedBonusPoints( pQuestItem ); + + return ( nEarnedStandardPoints + nEarnedBonusPoints ) == ( nRequiredStandardPoints + nRequiredBonusPoints ); +} + +uint32 GetEarnedStandardPoints( const IEconItemInterface* pQuestItem ) +{ +#ifndef GC_DLL + const CQuestItemTracker* pItemTracker = assert_cast< const CQuestItemTracker* >( QuestObjectiveManager()->GetTypedTracker< CQuestItemTracker* >( pQuestItem->GetID() ) ); + if ( pItemTracker ) + { + return pItemTracker->GetEarnedStandardPoints(); + } +#endif + + uint32 nEarnedStandardPoints = 0; + static CSchemaAttributeDefHandle pAttribDef_EarnedStandardPoints( "quest earned standard points" ); + pQuestItem->FindAttribute( pAttribDef_EarnedStandardPoints, &nEarnedStandardPoints ); + + + return nEarnedStandardPoints; +} + +uint32 GetEarnedBonusPoints( const IEconItemInterface* pQuestItem ) +{ + +#ifndef GC_DLL + const CQuestItemTracker* pItemTracker = assert_cast< const CQuestItemTracker* >( QuestObjectiveManager()->GetTypedTracker< CQuestItemTracker* >( pQuestItem->GetID() ) ); + if ( pItemTracker ) + { + return pItemTracker->GetEarnedBonusPoints(); + } +#endif + uint32 nEarnedBonusPoints = 0; + static CSchemaAttributeDefHandle pAttribDef_EarnedBonusPoints( "quest earned bonus points" ); + pQuestItem->FindAttribute( pAttribDef_EarnedBonusPoints, &nEarnedBonusPoints ); + + return nEarnedBonusPoints; +}
\ No newline at end of file diff --git a/game/shared/econ/econ_quests.h b/game/shared/econ/econ_quests.h new file mode 100644 index 0000000..e7beb8e --- /dev/null +++ b/game/shared/econ/econ_quests.h @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Functions related to quests +// +//============================================================================= + +#ifndef ECON_QUESTS +#define ECON_QUESTS +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Given a quest item, return if the quest is considered "unidentified" +//----------------------------------------------------------------------------- +bool IsQuestItemUnidentified( const CEconItem* pQuestItem ); +bool IsQuestItemReadyToTurnIn( const IEconItemInterface* pQuestItem ); +bool IsQuestItemFullyCompleted( const IEconItemInterface* pQuestItem ); +uint32 GetEarnedStandardPoints( const IEconItemInterface* pQuestItem ); +uint32 GetEarnedBonusPoints( const IEconItemInterface* pQuestItem ); + +#endif // ECON_QUESTS
\ No newline at end of file diff --git a/game/shared/econ/econ_store.cpp b/game/shared/econ/econ_store.cpp new file mode 100644 index 0000000..9c69363c --- /dev/null +++ b/game/shared/econ/econ_store.cpp @@ -0,0 +1,1672 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Common objects and utilities related to the in-game item store +// +//============================================================================= + +#include "cbase.h" +#include "econ_store.h" +#include "gcsdk/enumutils.h" + +#if defined(CLIENT_DLL) +#include "econ_ui.h" +#include "store/store_panel.h" +#include "econ_item_inventory.h" +#else +#include "gcsdk/gcconstants.h" +#endif + +#if defined(CLIENT_DLL) || defined(GAME_DLL) +#include "econ_item_system.h" +#endif + +// For localization +#include "tier3/tier3.h" +#include "vgui/ILocalize.h" +#include "tier0/icommandline.h" + +#ifdef CLIENT_DLL +// For formatting in locale +#include <string> +#include <sstream> +#endif // CLIENT_DLL + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar store_version( "store_version", "1", FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_ARCHIVE, "Which version of the store to display." ); + +ENUMSTRINGS_START( ECurrency ) + { k_ECurrencyUSD, "USD" }, + { k_ECurrencyGBP, "GBP" }, + { k_ECurrencyEUR, "EUR" }, + { k_ECurrencyRUB, "RUB" }, + { k_ECurrencyBRL, "BRL" }, + { k_ECurrencyJPY, "JPY" }, + { k_ECurrencyNOK, "NOK" }, + { k_ECurrencyIDR, "IDR" }, + { k_ECurrencyMYR, "MYR" }, + { k_ECurrencyPHP, "PHP" }, + { k_ECurrencySGD, "SGD" }, + { k_ECurrencyTHB, "THB" }, + { k_ECurrencyVND, "VND" }, + { k_ECurrencyKRW, "KRW" }, + { k_ECurrencyTRY, "TRY" }, + { k_ECurrencyUAH, "UAH" }, + { k_ECurrencyMXN, "MXN" }, + { k_ECurrencyCAD, "CAD" }, + { k_ECurrencyAUD, "AUD" }, + { k_ECurrencyNZD, "NZD" }, + { k_ECurrencyPLN, "PLN" }, + { k_ECurrencyCHF, "CHF" }, + { k_ECurrencyCNY, "CNY" }, + { k_ECurrencyTWD, "TWD" }, + { k_ECurrencyHKD, "HKD" }, + { k_ECurrencyINR, "INR" }, + { k_ECurrencyAED, "AED" }, + { k_ECurrencySAR, "SAR" }, + { k_ECurrencyZAR, "ZAR" }, + { k_ECurrencyCOP, "COP" }, + { k_ECurrencyPEN, "PEN" }, + { k_ECurrencyCLP, "CLP" }, + { k_ECurrencyInvalid, "Invalid" } +ENUMSTRINGS_REVERSE( ECurrency, k_ECurrencyInvalid ) + +ENUMSTRINGS_START( EPurchaseState ) +{ k_EPurchaseStateInvalid, "Invalid" }, +{ k_EPurchaseStateInit, "Init" }, +{ k_EPurchaseStateWaitingForAuthorization, "WaitingForAuthorization" }, +{ k_EPurchaseStatePending, "Pending" }, +{ k_EPurchaseStateComplete, "Complete" }, +{ k_EPurchaseStateFailed, "Failed" }, +{ k_EPurchaseStateCanceled, "Canceled" }, +{ k_EPurchaseStateRefunded, "Refunded" }, +{ k_EPurchaseStateChargeback, "Chargeback" }, +{ k_EPurchaseStateChargebackReversed, "Chargeback Reversed" }, +ENUMSTRINGS_END( EPurchaseState ) + +ENUMSTRINGS_START( EPurchaseResult ) +{ k_EPurchaseResultOK, "OK" }, +{ k_EPurchaseResultFail, "Fail" }, +{ k_EPurchaseResultInvalidParam, "InvalidParam" }, +{ k_EPurchaseResultInternalError, "InternalError" }, +{ k_EPurchaseResultNotApproved, "NotApproved" }, +{ k_EPurchaseResultAlreadyCommitted, "AlreadyCommitted" }, +{ k_EPurchaseResultUserNotLoggedIn, "UserNotLoggedIn" }, +{ k_EPurchaseResultWrongCurrency, "WrongCurrency" }, +{ k_EPurchaseResultAccountError, "AccountError" }, +{ k_EPurchaseResultInsufficientFunds, "InsufficientFunds Reversed" }, +{ k_EPurchaseResultTimedOut, "TimedOut" }, +{ k_EPurchaseResultAcctDisabled, "AcctDisabled" }, +{ k_EPurchaseResultAcctCannotPurchase, "AcctCannotPurchase" }, +{ k_EMicroTxnResultFailedFraudChecks, "PurchaseFailedSupport" }, // this string is mismatched so "fraud" doesn't appear in the client code +{ k_EPurchaseResultOldPriceSheet, "OldPriceSheet" }, +{ k_EPurchaseResultTxnNotFound, "TxnNotFound" }, +ENUMSTRINGS_END( EPurchaseResult ) + +ENUMSTRINGS_START( EGCTransactionAuditReason ) +{ k_EGCTransactionAudit_GCTransactionCompleted, "Completed" }, +{ k_EGCTransactionAudit_GCTransactionInit, "Init" }, +{ k_EGCTransactionAudit_GCTransactionPostInit, "Post Init" }, +{ k_EGCTransactionAudit_GCTransactionFinalize, "Finalize" }, +{ k_EGCTransactionAudit_GCTransactionFinalizeFailed, "Finalize Failed" }, +{ k_EGCTransactionAudit_GCTransactionCanceled, "Canceled" }, +{ k_EGCTransactionAudit_SteamFailedMismatch, "Steam Failed State Mismatch" }, +{ k_EGCTransactionAudit_GCRemovePurchasedItems, "Remove Purchased Items" }, +{ k_EGCTransactionAudit_GCTransactionInsert, "Transaction Insert" }, +{ k_EGCTransactionAudit_GCTransactionCompletedPostChargeback, "Completed (Post-Chargeback)" }, +ENUMSTRINGS_END( EGCTransactionAuditReason ) + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void econ_store_entry_t::InitCategoryTags( const char *pTags ) +{ + // Default to "unrentable". + m_fRentalPriceScale = 1.0f; + + if ( !pTags || !pTags[0] ) + return; + + // Cache tags - this pointer comes out of a KeyValues that isn't deleted + m_pchCategoryTags = pTags; + + // Split the tags apart + CUtlVector< char * > vecTokens; + V_SplitString( pTags, "+", vecTokens ); + + // Sanity check the results of V_SplitString() +#ifdef _DEBUG + const int nTagsLength = V_strlen( pTags ); + int nSepCount = 0; // # of separators + for ( int i = 0; i < nTagsLength; ++i ) + { + if ( pTags[i] == '+' ) + ++nSepCount; + } +// Assert( ( vecTokens.Size() - 1 ) == nSepCount ); +#endif + + // Calculate the maximum that we want to charge for this rental. + float fMaxRentalPriceScale = 0.0f; + + // Generate symbols for each tag + FOR_EACH_VEC( vecTokens, i ) + { + CategoryTag_t info; + info.m_strName = vecTokens[i]; + info.m_unID = CEconStoreCategoryManager::GetCategoryID( vecTokens[i] ); + m_vecCategoryTags.AddToTail( info ); + + const float fCategoryRentalPriceScale = GetEconPriceSheet()->GetRentalPriceScale( vecTokens[i] ); + fMaxRentalPriceScale = MAX( fMaxRentalPriceScale, fCategoryRentalPriceScale ); + } + + m_fRentalPriceScale = fMaxRentalPriceScale <= 0.0f || fMaxRentalPriceScale >= 100.0f + ? 1.0f + : fMaxRentalPriceScale * 0.01f; + + // Clean up + vecTokens.PurgeAndDeleteElements(); +} + +void econ_store_entry_t::SetItemDefinitionIndex( item_definition_index_t usDefIndex ) +{ + AssertMsg( usDefIndex != INVALID_ITEM_DEF_INDEX, "Invalid item definition index!" ); + m_usDefIndex = usDefIndex; +} + +bool econ_store_entry_t::IsListedInCategory( StoreCategoryID_t unID ) const +{ + AssertMsg( unID != CEconStoreCategoryManager::k_CategoryID_Invalid, "Did you mean to search for an invalid symbol?" ); + + FOR_EACH_VEC( m_vecCategoryTags, i ) + { + if ( m_vecCategoryTags[i].m_unID == unID ) + return true; + } + + return false; +} + +bool econ_store_entry_t::IsListedInSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const +{ + FOR_EACH_VEC( Category.m_vecSubcategories, iSubCategory ) + { + if ( IsListedInCategory( Category.m_vecSubcategories[iSubCategory]->m_unID ) ) + return true; + } + + return false; +} + +bool econ_store_entry_t::IsListedInCategoryOrSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const +{ + return IsListedInCategory( Category.m_unID ) || + IsListedInSubcategories( Category ); +} + +bool econ_store_entry_t::IsOnSale( ECurrency eCurrency ) const +{ + return GetSalePrice( eCurrency ) > 0 + && GetSalePrice( eCurrency ) < GetBasePrice( eCurrency ); +} + +bool econ_store_entry_t::IsRentable() const +{ + return m_fRentalPriceScale < 1.0f + && m_fRentalPriceScale > 0.0f; +} + +#ifdef CLIENT_DLL +bool econ_store_entry_t::HasDiscount( ECurrency eCurrency, item_price_t *out_punOptionalBasePrice ) const +{ + // Items on sale always report as being discounted. + if ( IsOnSale( eCurrency ) ) + { + if ( out_punOptionalBasePrice ) + { + *out_punOptionalBasePrice = GetBasePrice( eCurrency ); + } + + return true; + } + + // If we're not a bundle we have no other way of being on sale -- abort. + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( GetItemDefinitionIndex() ); + if ( !pItemDef || !pItemDef->IsBundle() ) + return false; + + const bundleinfo_t *pBundleInfo = pItemDef->GetBundleInfo(); + Assert( pBundleInfo ); + + item_price_t unTotalPriceOfBundleItems = 0; + FOR_EACH_VEC( pBundleInfo->vecItemDefs, bundleIdx ) + { + const econ_store_entry_t *pCurEntry = pBundleInfo->vecItemDefs[bundleIdx] + ? GetEconPriceSheet()->GetEntry( pBundleInfo->vecItemDefs[bundleIdx]->GetDefinitionIndex() ) + : NULL; + if ( pCurEntry ) + { + unTotalPriceOfBundleItems += pCurEntry->GetCurrentPrice( eCurrency ); + } + } + + // Did the bundle provide an actual discount? + const item_price_t unBasePrice = GetCurrentPrice( eCurrency ); + if ( unBasePrice < unTotalPriceOfBundleItems ) + { + if ( out_punOptionalBasePrice ) + { + *out_punOptionalBasePrice = unTotalPriceOfBundleItems; + } + + return true; + } + + // No discount. Leave the base price pointer untouched. + return false; +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +StoreCategoryID_t econ_store_entry_t::GetCategoryTagIDFromIndex( uint32 iIndex ) const +{ + if ( !IsValidCategoryTagIndex( iIndex ) ) + return CEconStoreCategoryManager::k_CategoryID_Invalid; + + return m_vecCategoryTags[ iIndex ].m_unID; +} + +item_price_t econ_store_entry_t::GetCurrentPrice( ECurrency eCurrency ) const +{ +#ifdef CLIENT_DLL + if ( m_bIsMarketItem ) + { + const client_market_data_t *pClientMarketData = GetClientMarketData( GetItemDefinitionIndex(), AE_UNIQUE ); + if ( !pClientMarketData ) + return 0; + + return pClientMarketData->m_unLowestPrice; + } +#endif + return IsOnSale( eCurrency ) + ? GetSalePrice( eCurrency ) + : GetBasePrice( eCurrency ); +} + +float econ_store_entry_t::GetRentalPriceScale() const +{ + Assert( IsRentable() ); + + return m_fRentalPriceScale; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void econ_store_entry_t::ValidatePrice( ECurrency eCurrency, item_price_t unPrice ) +{ + if ( m_bIsMarketItem ) + return; + + // If the price is 0 and this is not a pack item (an item that is not individually for sale, but is sold as part of a pack bundle), post an alert + if ( unPrice == 0 && !m_bIsPackItem ) + { + CFmtStr fmtError( "Warning: Invalid price for item (item def=%i)", GetItemDefinitionIndex() ); +#if defined( GC_DLL ) + GGCGameBase()->PostAlert( GCSDK::k_EAlertTypeReport, true, fmtError.Access() ); +#endif + AssertMsg( false, "%s", fmtError.Access() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CEconStorePriceSheet::CEconStorePriceSheet() + : m_pKVRaw( NULL ) + , m_mapEntries( DefLessFunc( uint16 ) ) + , m_mapRentalPriceScales( DefLessFunc( const char * ) ) + , m_RTimeVersionStamp( 0 ) + , m_pStorePromotionFirstTimePurchaseItem( NULL ) + , m_pStorePromotionFirstTimeWebPurchaseItem( NULL ) + , m_unFeaturedItemIndex( 0 ) + , m_eEconStoreSortType( kEconStoreSortType_Name_AToZ ) +{ + Clear(); + m_FeaturedItems.m_pchName = "featured_items"; + + m_mapCurrencyPricePoints.SetLessFunc( &price_point_map_key_t::Less ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CEconStorePriceSheet::~CEconStorePriceSheet() +{ + Clear(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Clears out the parsed data +//----------------------------------------------------------------------------- +void CEconStorePriceSheet::Clear() +{ + if ( m_pKVRaw ) + { + m_pKVRaw->deleteThis(); + m_pKVRaw = NULL; + } + + m_RTimeVersionStamp = 0; + m_mapEntries.RemoveAll(); + m_FeaturedItems.m_vecEntries.RemoveAll(); + m_flPreviewPeriodDiscount = 0; + + m_mapCurrencyPricePoints.Purge(); + +#ifdef CLIENT_DLL + m_vecFeaturedItems.Purge(); +#endif // CLIENT_DLL + + // Clear categories in category manager + ClearEconStoreCategoryManager(); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the entry details for a specific item +//----------------------------------------------------------------------------- +const econ_store_entry_t *CEconStorePriceSheet::GetEntry( item_definition_index_t usDefIndex ) const +{ + int iIndex = m_mapEntries.Find( usDefIndex ); + if ( m_mapEntries.IsValidIndex( iIndex ) ) + return &m_mapEntries[iIndex]; + + return NULL; +} + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: Gets the entry details for a specific item and lets us modify the contents (GC-only) +//----------------------------------------------------------------------------- +econ_store_entry_t *CEconStorePriceSheet::GetEntryWriteable( item_definition_index_t unDefIndex ) +{ + int iIndex = m_mapEntries.Find( unDefIndex ); + if ( m_mapEntries.IsValidIndex( iIndex ) ) + return &m_mapEntries[iIndex]; + + return NULL; +} +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BInsertSinglePricePoint( CurrencyPricePointMap_t& out_mapPricePoints, const price_point_map_key_t& key, item_price_t unValue ) +{ + if ( out_mapPricePoints.Find( key ) != out_mapPricePoints.InvalidIndex() ) + return false; + + out_mapPricePoints.Insert( key, unValue ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BInitializeCurrencyPricePoints( CurrencyPricePointMap_t& out_mapPricePoints, KeyValues *pKVPricePoints ) +{ + if ( !pKVPricePoints ) + return false; + + // Zero-price-point is universal. + FOR_EACH_CURRENCY( eCurrency ) + { + const price_point_map_key_t key = { 0, eCurrency }; + if ( !BInsertSinglePricePoint( out_mapPricePoints, key, 0 ) ) + return false; + } + + // Individual price points specified in our store config. + FOR_EACH_TRUE_SUBKEY( pKVPricePoints, pKVUSD ) + { + const item_price_t unUSD = Q_atoi( pKVUSD->GetName() ); + + // USD key, for ease of lookup. + { + const price_point_map_key_t key = { unUSD, k_ECurrencyUSD }; + if ( !BInsertSinglePricePoint( out_mapPricePoints, key, unUSD ) ) + return false; + } + + // Exchange rates. + FOR_EACH_VALUE( pKVUSD, pKVOtherCurrency ) + { + const char *pszCurrencyName = pKVOtherCurrency->GetName(); + const ECurrency eCurrency = ECurrencyFromName( pszCurrencyName ); + + if ( eCurrency == k_ECurrencyInvalid ) + { + // Spew +#ifdef GC_DLL + EmitError( SPEW_GC, "Unknown Currency [%s] found in price sheet. Currency is unsupported!\n", pszCurrencyName ); +#endif + // don't crash, just conintue + continue; + } + + const price_point_map_key_t key = { unUSD, eCurrency }; + if ( !BInsertSinglePricePoint( out_mapPricePoints, key, pKVOtherCurrency->GetInt() ) ) + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Parses the KV version of the price sheet +//----------------------------------------------------------------------------- +bool CEconStorePriceSheet::InitFromKV( KeyValues *pKVRoot ) +{ + Clear(); + m_pKVRaw = pKVRoot->MakeCopy(); + + // Initialize categories - needed to initialize store entries + if ( !GEconStoreCategoryManager()->BInit( this, m_pKVRaw ) ) + { +#ifdef GC_DLL + EmitError( SPEW_GC, "Unable to Init GEconStoreCategoryManager \n" ); +#endif + return false; + } + + // Initialize map of currency price points. + if ( !BInitializeCurrencyPricePoints( m_mapCurrencyPricePoints, pKVRoot->FindKey( "inventoryvalvegcpricesheet" ) ) ) + { +#ifdef GC_DLL + EmitError( SPEW_GC, "Unable to Init CurrencyPricePoints \n" ); +#endif + return false; + } + + m_unFeaturedItemIndex = pKVRoot->GetInt( "featured_item_index", 0 ); + + // first time purchase gift + m_pStorePromotionFirstTimePurchaseItem = GetItemSchema()->GetItemDefinitionByName( pKVRoot->GetString( "promotion_first_time_purchase_gift" ) ); + m_pStorePromotionFirstTimeWebPurchaseItem = GetItemSchema()->GetItemDefinitionByName( pKVRoot->GetString( "promotion_first_time_web_purchase_gift" ) ); + + EUniverse eUniverse = GetUniverse(); + + m_unPreviewPeriod = pKVRoot->GetInt( eUniverse == k_EUniversePublic ? "preview_period" : "preview_period_nonpublic" ); + m_unBonusDiscountPeriod = pKVRoot->GetInt( eUniverse == k_EUniversePublic ? "bonus_discount_period" : "bonus_discount_period_nonpublic" ); + m_flPreviewPeriodDiscount = pKVRoot->GetFloat( "preview_period_discount" ); + +#ifdef ENABLE_STORE_RENTAL_BACKEND + KeyValues *pRentalPriceScalesKV = pKVRoot->FindKey( "rental_price_scale" ); + if ( pRentalPriceScalesKV ) + { + FOR_EACH_SUBKEY( pRentalPriceScalesKV, pCategoryKV ) + { + m_mapRentalPriceScales.InsertOrReplace( pCategoryKV->GetName(), pCategoryKV->GetFloat() ); + } + } +#endif + + memset( &m_StorePromotionSpendForFreeItem, 0, sizeof( m_StorePromotionSpendForFreeItem ) ); + KeyValues *pPromotionsKV = m_pKVRaw->FindKey( "promotion_spend_for_free_item" ); + if ( pPromotionsKV ) + { + item_price_t unFreeItemSpendAmountUSD = pPromotionsKV->GetInt( "price_threshold", 0 ); + FOR_EACH_CURRENCY( eCurrency ) + { + const price_point_map_key_t key = { unFreeItemSpendAmountUSD, eCurrency }; + const CurrencyPricePointMap_t::IndexType_t unIdx = m_mapCurrencyPricePoints.Find( key ); + if ( unIdx == m_mapCurrencyPricePoints.InvalidIndex() ) + { +#ifdef GC_DLL + EmitError( SPEW_GC, "Unable to Find Currency %s in Currency Map. Likely missing from inventoryvalvegcpricesheet.vdf \n", PchNameFromECurrency(eCurrency) ); +#endif + continue; + } + + m_StorePromotionSpendForFreeItem.m_rgusPriceThreshold[ eCurrency ] = m_mapCurrencyPricePoints[unIdx]; + } + +#if !defined(CLIENT_DLL) && !defined(GAME_DLL) + CEconItemSchema *pSchema = GEconManager()->GetItemSchema(); + m_StorePromotionSpendForFreeItem.m_pItemDef = pSchema->GetItemDefinitionByName( pPromotionsKV->GetString( "item_definition" ) ); + AssertMsg( m_StorePromotionSpendForFreeItem.m_pItemDef || !pPromotionsKV->GetString( "item_definition", NULL ), + "Could find not the specified item for \"promotion_spend_for_free_item\"" ); +#endif + } + + // Get the normal sections + KeyValues *pEntriesKV = m_pKVRaw->FindKey( "entries" ); + if ( pEntriesKV ) + { + FOR_EACH_TRUE_SUBKEY( pEntriesKV, pKVEntry ) + { + if ( !BInitEntryFromKV( pKVEntry ) ) + { +#ifdef GC_DLL + EmitError( SPEW_GC, "Unable to Find Entries in Currency KVP \n" ); +#endif + continue; + } + } + } + + // Get a list of Market entries to populate in to the 'store'. GC never cares or reads these items + // Only for Client +#ifdef CLIENT_DLL + KeyValues *pMarketEntriesKV = m_pKVRaw->FindKey( "market_entries" ); + if ( pMarketEntriesKV ) + { + FOR_EACH_TRUE_SUBKEY( pMarketEntriesKV, pKVEntry ) + { + if ( !BInitMarketEntryFromKV( pKVEntry ) ) + { + return false; + } + } + } + + KeyValues *pFeaturedItemsKV = m_pKVRaw->FindKey( "featured_items" ); + if ( pFeaturedItemsKV ) + { + FOR_EACH_SUBKEY( pFeaturedItemsKV, pKVEntry ) + { + const char *pszItemName = pKVEntry->GetName(); + const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pszItemName ); + if ( !pDef ) + { + AssertMsg1( 0, "Unable to find item: %s", pszItemName ); + continue; + } + + m_vecFeaturedItems.AddToTail( pDef->GetDefinitionIndex() ); + } + } +#endif + + // Generate a hash of all item def indices and cache it off + m_unHashForAllItems = CalculateHashFromItems(); + +#ifdef GC_DLL + // Parse the sales block on the GC. We'll use this to dynamically adjust prices. + KeyValues *pTimedSalesKV = m_pKVRaw->FindKey( "timed_sales" ); + if ( pTimedSalesKV ) + { + FOR_EACH_TRUE_SUBKEY( pTimedSalesKV, pKVSale ) + { + if ( !InitTimedSaleEntryFromKV( pKVSale ) ) + { + EmitError( SPEW_GC, "Unable to Init Timed Sale \n" ); + } + return false; + } + + // Verify that none of our timed sales have overlapping items. + if ( !VerifyTimedSaleEntries() ) + return false; + } +#endif // GC_DLL + + // Now that store entries are loaded, let the category manager do more stuff + if ( !GEconStoreCategoryManager()->BOnPriceSheetLoaded( this ) ) + return false; + + return true; +} + +uint32 CEconStorePriceSheet::CalculateHashFromItems() const +{ + CRC32_t unHash; + CRC32_Init( &unHash ); + + FOR_EACH_MAP_FAST( m_mapEntries, idx ) + { + const econ_store_entry_t &entry = m_mapEntries[idx]; + item_definition_index_t usDefIndexTmp = entry.GetItemDefinitionIndex(); + CRC32_ProcessBuffer( &unHash, &usDefIndexTmp, sizeof( usDefIndexTmp ) ); + } + + CRC32_Final( &unHash ); + + return (uint32)unHash; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool BInitializeStoreEntryPricePoints( econ_store_entry_t& out_entry, const CurrencyPricePointMap_t& mapCurrencyPricePoints, KeyValues *pKVPrice, int nSalePercent ) +{ + if ( !pKVPrice ) + return false; + + FOR_EACH_CURRENCY( eCurrency ) + { + const price_point_map_key_t key = { (item_price_t)pKVPrice->GetInt(), eCurrency }; + const CurrencyPricePointMap_t::IndexType_t unIdx = mapCurrencyPricePoints.Find( key ); + + // Looking for a price point that doesn't exist, or doesn't exist for this currency? + if ( unIdx == mapCurrencyPricePoints.InvalidIndex() ) + { +#ifdef GC_DLL + EmitError( SPEW_GC, "Unable to Find Currency %s in init price points. Currency is missing from inventoryvalvegcpricesheet.vdf \n", PchNameFromECurrency( eCurrency ) ); +#endif + continue; + } + + // Weird initialization pattern: we're making sure that the value we read from the KeyValues + // block is the value we're storing in memory. We do this to avoid integer conversion problems, + // especially overflow (!). + const item_price_t unPrice = mapCurrencyPricePoints[unIdx]; + out_entry.SetBasePrice( eCurrency, unPrice ); +#pragma warning(push) +#pragma warning(disable : 4389) + Assert( out_entry.GetBasePrice( eCurrency ) == unPrice ); // NOTE: DO NOT CAST unPrice - CHECKING FOR OVERFLOW +#pragma warning(pop) + + if ( ( nSalePercent > 0 ) && ( nSalePercent < 100 ) ) + { + const item_price_t unSalePrice = out_entry.CalculateSalePrice( &out_entry, eCurrency, (float)nSalePercent ); + out_entry.SetSalePrice( eCurrency, unSalePrice ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Parses the KV section piece and add a econ_store_entry_t +//----------------------------------------------------------------------------- +bool CEconStorePriceSheet::BInitEntryFromKV( KeyValues *pKVEntry ) +{ + const bool bIsPackItem = pKVEntry->GetBool( "is_pack_item", false ); + +#if defined( CLIENT_DLL ) + // Skip pack items on the client + if ( bIsPackItem ) + return true; +#endif + + econ_store_entry_t entry; + entry.InitCategoryTags( pKVEntry->GetString( "category_tags" ) ); + + const bool bIsNew = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_New ); + entry.m_bLimited = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Limited ); + entry.m_bNew = bIsNew; + entry.m_bPreviewAllowed = !bIsNew && entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Weapons ); + entry.m_bIsMarketItem = false; + const bool bIsHighlighted = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Highlighted ); + entry.m_bHighlighted = bIsHighlighted; + + if ( entry.GetCategoryTagCount() == 0 ) + { + AssertMsg1( 0, "Unable to get category_tags for entry: %s", pKVEntry->GetName() ); + return false; + } + + const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pKVEntry->GetString( "item_link" ) ); + if ( !pDef ) + { + AssertMsg1( 0, "Unable to find item: %s", pKVEntry->GetString( "item_link" ) ); + return false; + } + + entry.SetItemDefinitionIndex( pDef->GetDefinitionIndex() ); + + int iQuantity = pKVEntry->GetInt( "quantity", 1 ); + entry.SetQuantity( iQuantity ); + Assert( entry.GetQuantity() == iQuantity ); + + entry.SetSteamGiftPackageID( pKVEntry->GetUint64( "steam_gift_package_id", 0 ) ); + +#if defined( TF_CLIENT_DLL ) || defined( TF_GC_DLL ) + entry.SetDate( pDef->GetFirstSaleDate() ); +#else + entry.SetDate( pKVEntry->GetString( "date", "1/1/1900" ) ); +#endif + + // Is the item sold out of the store? + entry.m_bSoldOut = pKVEntry->GetBool( "sold_out", false ); + + // Is the item a pack item? + entry.m_bIsPackItem = bIsPackItem; + + int nSalePercent = pKVEntry->GetInt( "sale_percentage", 0 ); + if ( ( nSalePercent < 0 ) || ( nSalePercent >= 100 ) ) + { + AssertMsg( false, "Sale percentage for %s set to an invalid value: %d\n", pDef->GetDefinitionName(), nSalePercent ); + return false; + } + + if ( !BInitializeStoreEntryPricePoints( entry, m_mapCurrencyPricePoints, pKVEntry->FindKey( "base_price" ), nSalePercent ) ) + return false; + + m_mapEntries.InsertOrReplace( entry.GetItemDefinitionIndex(), entry ); + return true; +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: Parses the KV section piece and add a econ_store_entry_t +//----------------------------------------------------------------------------- +bool CEconStorePriceSheet::BInitMarketEntryFromKV( KeyValues *pKVEntry ) +{ + //"The Alien Cranium" + //{ + // "item_link" "The Alien Cranium" + // "category_tags" "Cosmetics+Halloween" + // "date" "11/13/2014" + // "base_price" "1299" + //} + + econ_store_entry_t entry; + entry.InitCategoryTags( pKVEntry->GetString( "category_tags" ) ); + + const bool bIsNew = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_New ); + entry.m_bLimited = entry.IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Limited ); + entry.m_bNew = bIsNew; + entry.m_bPreviewAllowed = false; + entry.m_bIsMarketItem = true; + + if ( entry.GetCategoryTagCount() == 0 ) + { + AssertMsg1( 0, "Unable to get category_tags for entry: %s", pKVEntry->GetName() ); + return false; + } + + const CEconItemDefinition *pDef = GetItemSchema()->GetItemDefinitionByName( pKVEntry->GetString( "item_link" ) ); + if ( !pDef ) + { + AssertMsg1( 0, "Unable to find item: %s", pKVEntry->GetString( "item_link" ) ); + return false; + } + + entry.SetItemDefinitionIndex( pDef->GetDefinitionIndex() ); + entry.SetQuantity( 1 ); + entry.SetDate( pDef->GetFirstSaleDate() ); // FIXME? + + entry.m_bSoldOut = false; + entry.m_bIsPackItem = false; + + // Entry should never already exist in mapEntries + if ( m_mapEntries.Find( entry.GetItemDefinitionIndex() ) != m_mapEntries.InvalidIndex() ) + { + AssertMsg1( 0, "Market Entry already eixsts : %s", pKVEntry->GetString( "item_link" ) ); + return false; + } + + m_mapEntries.InsertOrReplace( entry.GetItemDefinitionIndex(), entry ); + return true; +} +#endif // CLIENT_DLL + +#ifdef GC_DLL +//----------------------------------------------------------------------------- +// Purpose: Parses the KV section piece and add a econ_store_entry_t +//----------------------------------------------------------------------------- +static RTime32 ConvertKVDateToRTime32( KeyValues *pKV, const char *pszKey ) +{ + Assert( pKV ); + Assert( pszKey ); + + const char *pszTime = pKV->GetString( pszKey, NULL ); + if ( !pszTime || !pszTime[0] ) + return 0; + + RTime32 unConvertedTime = CRTime::RTime32FromString( pszTime ); + if ( unConvertedTime == (RTime32)-1 ) + return 0; + + return unConvertedTime; +} + +bool CEconStorePriceSheet::InitTimedSaleEntryFromKV( KeyValues *pKVTimedSaleEntry ) +{ + Assert( pKVTimedSaleEntry ); + + econ_store_timed_sale_t TimedSale; + + TimedSale.m_bSaleCurrentlyActive = false; + TimedSale.m_sIdentifier = pKVTimedSaleEntry->GetName(); + + TimedSale.m_SaleStartTime = ConvertKVDateToRTime32( pKVTimedSaleEntry, "sale_start_date" ); + TimedSale.m_SaleEndTime = ConvertKVDateToRTime32( pKVTimedSaleEntry, "sale_end_date" ); + + // Sanity check -- make sure this sale lasts a greater-than-zero amount of time. This will also + // catch any cases where the end time was invalid and so returned 0. + if ( TimedSale.m_SaleStartTime >= TimedSale.m_SaleEndTime ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid duration\n", pKVTimedSaleEntry->GetName() ); + return false; + } + + // Make sure the end time is also greater than 0. + if ( TimedSale.m_SaleStartTime <= 0 ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid start time\n", pKVTimedSaleEntry->GetName() ); + return false; + } + + // What items does this sale apply to? + KeyValues *pKVSaleItems = pKVTimedSaleEntry->FindKey( "sale_items" ); + if ( !pKVSaleItems ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' missing \"sale_items\" section\n", pKVTimedSaleEntry->GetName() ); + return false; + } + + FOR_EACH_TRUE_SUBKEY( pKVSaleItems, pKVSaleItem ) + { + const char *pszName = pKVSaleItem->GetString( "name", NULL ); + if ( !pszName ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid/missing item name for entry '%s'\n", pKVTimedSaleEntry->GetName(), pKVSaleItem->GetName() ); + return false; + } + + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinitionByName( pszName ); + if ( !pItemDef ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has missing item named '%s' for entry '%s'\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName() ); + return false; + } + + const float fSalePercentageOff = pKVSaleItem->GetFloat( "sale_percentage_off", -1.0f ); + if ( fSalePercentageOff <= 0.0f || fSalePercentageOff > 100.0f ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has invalid sale percentage for item named '%s' for entry '%s'\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName() ); + return false; + } + + const econ_store_entry_t *pStoreEntry = GetEntry( pItemDef->GetDefinitionIndex() ); + if ( !pStoreEntry ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has item named '%s' for entry '%s' that is not in the store\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName() ); + return false; + } + + // Verify that no item in a timed sale is already in a hard-coded sale as well. This would break + // our fragile pricing math assumptions. + FOR_EACH_CURRENCY( eCurrency ) + { + if ( pStoreEntry->IsOnSale( eCurrency ) ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has item named '%s' for entry '%s' that hard-coded to be on sale (currency: %i)\n", pKVTimedSaleEntry->GetName(), pszName, pKVSaleItem->GetName(), eCurrency ); + return false; + } + } + + econ_store_timed_sale_item_t SaleItem; + SaleItem.m_unItemDef = pItemDef->GetDefinitionIndex(); + SaleItem.m_fPricePercentage = fSalePercentageOff; + + TimedSale.m_vecSaleItems.AddToTail( SaleItem ); + } + + + if ( TimedSale.m_vecSaleItems.Count() <= 0 ) + { + EG_ERROR( GCSDK::SPEW_GC, "Timed sale '%s' has no valid items to put on sale\n", pKVTimedSaleEntry->GetName() ); + return false; + } + + // We made it this far with no errors, so add this as a timed sale block if it actually affects + // any items. + m_vecTimedSales.AddToTail( TimedSale ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconStorePriceSheet::VerifyTimedSaleEntries() +{ + // We could write an interval tree and be smart about this, but Fletcher made + // faces at me when I suggested it so we just brute force it -- we find each + // item in each sale sequentially, find each sale that overlaps with the current + // sale, and make sure that it doesn't have the same item. + for ( int i = 0; i < m_vecTimedSales.Count(); i++ ) + { + const econ_store_timed_sale_t& BaseSale = m_vecTimedSales[i]; + Assert( BaseSale.m_vecSaleItems.Count() > 0 ); + + for ( int j = 0; j < BaseSale.m_vecSaleItems.Count(); j++ ) + { + const item_definition_index_t unSearchDefIndex = BaseSale.m_vecSaleItems[j].m_unItemDef; + + for ( int k = i; k < m_vecTimedSales.Count(); k++ ) + { + // Does this sale overlap with our current sale? + const econ_store_timed_sale_t& OtherSale = m_vecTimedSales[k]; + + if ( k == i || MAX( BaseSale.m_SaleStartTime, OtherSale.m_SaleStartTime ) <= MIN( BaseSale.m_SaleEndTime, OtherSale.m_SaleEndTime ) ) + { + // We overlap, so make sure this item doesn't show up in both lists. Start the search in our + // current sale to make sure the same entry doesn't show up twice and then look at the full + // list of items in other overlapping sales. + for ( int l = (k == i ? j + 1 : 0); l < OtherSale.m_vecSaleItems.Count(); l++ ) + { + const item_definition_index_t unMaybeMatchDefIndex = OtherSale.m_vecSaleItems[l].m_unItemDef; + if ( unSearchDefIndex == unMaybeMatchDefIndex ) + { + EG_ERROR( GCSDK::SPEW_GC, "Conflict detected for item index '%i' betweens timed sales '%s' / '%s'\n", + unSearchDefIndex, + BaseSale.m_sIdentifier.Get(), + OtherSale.m_sIdentifier.Get() ); + return false; + } + } + } + } + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconStorePriceSheet::UpdatePricesForTimedSales( const RTime32 curTime ) +{ + FOR_EACH_VEC( m_vecTimedSales, i ) + { + econ_store_timed_sale_t& TimedSale = m_vecTimedSales[i]; + Assert( TimedSale.m_vecSaleItems.Count() > 0 ); + + // Is this sale active in our current time? + const bool bSaleShouldBeActive = (curTime >= TimedSale.m_SaleStartTime) + && (curTime <= TimedSale.m_SaleEndTime); + + // Last time we processed it, was this sale active? + const bool bSaleIsActive = TimedSale.m_bSaleCurrentlyActive; + + // State transition? + if ( bSaleShouldBeActive != bSaleIsActive ) + { + FOR_EACH_VEC( TimedSale.m_vecSaleItems, i ) + { + econ_store_entry_t *unSaleStoreEntry = GetEntryWriteable( TimedSale.m_vecSaleItems[i].m_unItemDef ); + Assert( unSaleStoreEntry ); + + // We don't support items being on sale in multiple ways at the same time (ie., a + // sale specified in the base prices in store.txt and also a timed sale) so we just stomp + // whatever the sale price is with the price we think we should be on sale for. + FOR_EACH_CURRENCY( eCurrency ) + { + unSaleStoreEntry->SetSalePrice( eCurrency, + bSaleIsActive ? unSaleStoreEntry->GetBasePrice( eCurrency ) * TimedSale.m_vecSaleItems[i].m_fPricePercentage : 0 ); + } + } + + // Update our last-updated timestamp if any of these sales changed -- use the most recent + // "start of sale" date. This will allow people using the store prices WebAPI to know whether + // prices have changed. + + // If items just went on sale, their price changed at the start of this sale. + if ( bSaleIsActive ) + { + m_RTimeVersionStamp = MAX( m_RTimeVersionStamp, TimedSale.m_SaleStartTime ); + } + + // If items just got taken off sale, their price changed at the end of this sale. + else // if ( !bSaleIsActive ) + { + m_RTimeVersionStamp = MAX( m_RTimeVersionStamp, TimedSale.m_SaleEndTime ); + } + + // Update our cached state. + TimedSale.m_bSaleCurrentlyActive = bSaleShouldBeActive; + + // Debug output. + EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Timed sale '%s' has been %s.\n", TimedSale.m_sIdentifier.Get(), bSaleShouldBeActive ? "enabled" : "disabled" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconStorePriceSheet::DumpTimeSaleState( const RTime32 curTime ) const +{ + char curTimeBuf[k_RTimeRenderBufferSize]; + EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Current sale calculation time: %s\n", CRTime::RTime32ToString( curTime, curTimeBuf ) ); + + FOR_EACH_VEC( m_vecTimedSales, i ) + { + const econ_store_timed_sale_t& TimedSale = m_vecTimedSales[i]; + Assert( TimedSale.m_vecSaleItems.Count() > 0 ); + + if ( !TimedSale.m_bSaleCurrentlyActive ) + { + EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "\tSale '%s' (not active)\n", TimedSale.m_sIdentifier.Get() ); + } + else + { + EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "\tSale '%s' (active):\n", TimedSale.m_sIdentifier.Get() ); + + FOR_EACH_VEC( TimedSale.m_vecSaleItems, i ) + { + EmitInfo( GCSDK::SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "\t\t%s is %.0f%% off\n", + GetItemSchema()->GetItemDefinition( TimedSale.m_vecSaleItems[i].m_unItemDef )->GetDefinitionName(), + TimedSale.m_vecSaleItems[i].m_fPricePercentage ); + } + } + } +} +#endif // GC_DLL + +//----------------------------------------------------------------------------- +// Performs calculation of a discounted price given a base price, and will then handle ensuring the appropriate number of zeros at the end of the price via rounding +//----------------------------------------------------------------------------- +/*static*/ item_price_t econ_store_entry_t::GetDiscountedPrice( ECurrency eCurrency, item_price_t unBasePrice, float fDiscountPercentage ) +{ + Assert( fDiscountPercentage > 0.0f ); + Assert( fDiscountPercentage < 100.0f ); + + //if the base price is zero, it should never be anything but zero + if( unBasePrice == 0 ) + return unBasePrice; + + item_price_t unNewPrice = ( item_price_t )( ( double )unBasePrice * ( clamp( 100.0 - fDiscountPercentage, 0.0, 100.0 ) / 100.0 ) ); + + //determine what the unit of granularity we want to use for pricing. For example if you use 1, we keep 1/100th level precision, if you use 100 we'll round it to the nearest 100th (in USD this would be round to + //the nearest dollar, etc) + item_price_t unPriceGranularity = 1; + switch ( eCurrency ) + { + case k_ECurrencyRUB: unPriceGranularity = 100; break; + case k_ECurrencyJPY: unPriceGranularity = 100; break; + case k_ECurrencyNOK: unPriceGranularity = 100; break; + case k_ECurrencyPHP: unPriceGranularity = 100; break; + case k_ECurrencyTHB: unPriceGranularity = 100; break; + case k_ECurrencyKRW: unPriceGranularity = 1000; break; + case k_ECurrencyUAH: unPriceGranularity = 10; break; + case k_ECurrencyIDR: unPriceGranularity = 100; break; + case k_ECurrencyVND: unPriceGranularity = 1000; break; + case k_ECurrencyCNY: unPriceGranularity = 100; break; + case k_ECurrencyTWD: unPriceGranularity = 100; break; + case k_ECurrencyINR: unPriceGranularity = 100; break; + case k_ECurrencyCOP: unPriceGranularity = 100; break; + case k_ECurrencyCLP: unPriceGranularity = 100; break; + } + + + //now handle the rounding to the specified price granularity + if( unPriceGranularity > 1 ) + { + const item_price_t unRemainder = unNewPrice % unPriceGranularity; + //make sure to never let the price go to zero though + if( ( unRemainder >= unPriceGranularity / 2 ) || ( unNewPrice < unPriceGranularity ) ) + { + //round up + unNewPrice += unPriceGranularity - unRemainder; + } + else + { + //round down + unNewPrice -= unRemainder; + } + + //sanity check + Assert( ( unNewPrice % unPriceGranularity == 0 ) && ( unNewPrice > 0 ) ); + } + + return unNewPrice; +} + +//----------------------------------------------------------------------------- +// Purpose: This will calculate what the sale price will be for a particular +// item -- the result should be non-zero, but this does not mean the item is +// currently on sale. You can optionally pass in a pointer to a uint32 which +// will get you the actual discount percentage, which can be different for +// NXP, which has its own nonlinear pricing structure. +//----------------------------------------------------------------------------- +/*static*/ item_price_t econ_store_entry_t::CalculateSalePrice( const econ_store_entry_t* pSaleStoreEntry, ECurrency eCurrency, float fDiscountPercentage, int32 *out_pAdjustedDiscountPercentage/*=NULL*/ ) +{ + item_price_t unSalePrice = 0; +/* + // TF2 doesn't support NXP or RMB yet + if ( eCurrency == k_ECurrencyNXP || eCurrency == k_ECurrencyRMB ) + { + // For these currencies, we calculate the sale price based on the discount percentage times the *USD* base price -- rather than the discount percentage + // times the base price for the given currency. + const item_price_t unSalePrice_USD = econ_store_entry_t::GetDiscountedPrice( k_ECurrencyUSD, pSaleStoreEntry->GetBasePrice( k_ECurrencyUSD ), fDiscountPercentage ); + unSalePrice = ( eCurrency == k_ECurrencyNXP ) ? ConvertUSDToNXP( unSalePrice_USD ) : ConvertUSDToRMB( unSalePrice_USD ); + + // Ensure that the sale price is strictly less + Assert( unSalePrice < pSaleStoreEntry->GetBasePrice( eCurrency ) ); + if ( unSalePrice >= pSaleStoreEntry->GetBasePrice( eCurrency ) ) + { + unSalePrice = pSaleStoreEntry->GetBasePrice( eCurrency ); + } + } + else +*/ + { + unSalePrice = econ_store_entry_t::GetDiscountedPrice( eCurrency, pSaleStoreEntry->GetBasePrice( eCurrency ), fDiscountPercentage ); + } + + Assert( unSalePrice > 0 ); + + // Also set a percentage per currency, since they can be different. RMB and NXP, for example, + // calculate a sale price based on the USD sale price. + const bool bUseDefaultDiscountPercentage = ( eCurrency == k_ECurrencyUSD ); + const double fActualPercent = 100.0 * ( 1.0 - ( double )unSalePrice / ( double )pSaleStoreEntry->GetBasePrice( eCurrency ) ); + + const int32 nDiscountPercentageForCurrency = bUseDefaultDiscountPercentage ? + RoundFloatToInt( fDiscountPercentage ) : + RoundFloatToInt( fActualPercent ); + AssertMsg( nDiscountPercentageForCurrency >= 0 && nDiscountPercentageForCurrency < 100, "Invalid discount percentage of %u specified for item %u currency %u", nDiscountPercentageForCurrency, pSaleStoreEntry->GetItemDefinitionIndex(), eCurrency ); + + if ( out_pAdjustedDiscountPercentage ) + { + *out_pAdjustedDiscountPercentage = nDiscountPercentageForCurrency; + } + + return unSalePrice; +} + +//---------------------------------------------------------------------------- +// Purpose: Sort Entries by Name +//---------------------------------------------------------------------------- +int ItemNameSortComparator( const econ_store_entry_t *const *ppEntryA, const econ_store_entry_t *const *ppEntryB ) +{ + Assert( ppEntryA ); + Assert( ppEntryB ); + Assert( *ppEntryA ); + Assert( *ppEntryB ); + + const CEconItemDefinition *pItemDefA = GetItemSchema()->GetItemDefinition( (*ppEntryA)->GetItemDefinitionIndex() ); + const CEconItemDefinition *pItemDefB = GetItemSchema()->GetItemDefinition( (*ppEntryB)->GetItemDefinitionIndex() ); + + int nComp = Q_stricmp( pItemDefA->GetItemTypeName(), pItemDefB->GetItemTypeName() ); + if ( nComp != 0 ) + return nComp; + + // If the type matches, then sort by the item name + return Q_stricmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort by whatever sort type our price sheet is requesting +//----------------------------------------------------------------------------- +int FirstSaleDateSortComparator( const econ_store_entry_t *const *ppItemA, const econ_store_entry_t *const *ppItemB ) +{ + Assert( ppItemA ); + Assert( ppItemB ); + Assert( *ppItemA ); + Assert( *ppItemB ); + + const CEconItemDefinition *pItemDefA = GetItemSchema()->GetItemDefinition( (*ppItemA)->GetItemDefinitionIndex() ); + const CEconItemDefinition *pItemDefB = GetItemSchema()->GetItemDefinition( (*ppItemB)->GetItemDefinitionIndex() ); + Assert( pItemDefA ); + Assert( pItemDefB ); + + const char *pDateAddedA = pItemDefA->GetFirstSaleDate(); + const char *pDateAddedB = pItemDefB->GetFirstSaleDate(); + + return -strcmp( pDateAddedA, pDateAddedB ); +} + +bool CEconStoreEntryLess::Less( const uint16& lhs, const uint16& rhs, void *pContext ) +{ + CEconItemSchema *pSchema = GetItemSchema(); + + CEconStorePriceSheet* pPriceSheet = (CEconStorePriceSheet*)pContext; + eEconStoreSortType sortType = pPriceSheet->GetEconStoreSortType(); + const econ_store_entry_t *pItemA = pPriceSheet->GetEntry( lhs ); + const econ_store_entry_t *pItemB = pPriceSheet->GetEntry( rhs ); + Assert( pItemA ); + Assert( pItemB ); + + CEconItemDefinition *pItemDefA = pSchema->GetItemDefinition( pItemA->GetItemDefinitionIndex() ); + CEconItemDefinition *pItemDefB = pSchema->GetItemDefinition( pItemB->GetItemDefinitionIndex() ); + +#ifdef CLIENT_DLL + ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency(); +#else + ECurrency eCurrency = k_ECurrencyUSD; +#endif + + if ( pItemDefA && pItemDefB ) + { + switch ( sortType ) + { + case kEconStoreSortType_Price_HighestToLowest: + if ( pItemA->GetCurrentPrice( eCurrency ) == pItemB->GetCurrentPrice( eCurrency ) ) + { + return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) < 0; + } + return pItemA->GetCurrentPrice( eCurrency ) > pItemB->GetCurrentPrice( eCurrency ); + + case kEconStoreSortType_Price_LowestToHighest: + if ( pItemA->GetCurrentPrice( eCurrency ) == pItemB->GetCurrentPrice( eCurrency ) ) + { + return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) < 0; + } + return pItemA->GetCurrentPrice( eCurrency ) < pItemB->GetCurrentPrice( eCurrency ); + + case kEconStoreSortType_DevName_AToZ: + return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) < 0; + + case kEconStoreSortType_DevName_ZToA: + return Q_strcmp( pItemDefA->GetItemBaseName(), pItemDefB->GetItemBaseName() ) > 0; + + case kEconStoreSortType_DateNewest: + case kEconStoreSortType_DateOldest: + { + int iSortResult = FirstSaleDateSortComparator( &pItemA, &pItemB ); + + if ( iSortResult < 0 ) + return sortType == kEconStoreSortType_DateNewest; + + if ( iSortResult > 0 ) + return sortType == kEconStoreSortType_DateOldest; + + // Intentionally fall through to sorting by localized name if our dates match. + } + + case kEconStoreSortType_Name_AToZ: + case kEconStoreSortType_Name_ZToA: + if ( g_pVGuiLocalize ) + { + wchar_t *wszItemNameA = g_pVGuiLocalize->Find( pItemDefA->GetItemBaseName() ); + wchar_t *wszItemNameB = g_pVGuiLocalize->Find( pItemDefB->GetItemBaseName() ); + if ( wszItemNameA && wszItemNameB ) + { + // Note: locale-savvy string sorting uses wcscoll, not wcscmp + return sortType == kEconStoreSortType_Name_ZToA + ? wcscoll( wszItemNameA, wszItemNameB ) > 0 // only sort in reverse alphabetical order if asked to -- fall-through cases uses forward order + : wcscoll( wszItemNameA, wszItemNameB ) < 0; + } + } + break; + + case kEconStoreSortType_ItemDefIndex: + return pItemDefA->GetDefinitionIndex() < pItemDefB->GetDefinitionIndex(); + + case kEconStoreSortType_ReverseItemDefIndex: + return pItemDefB->GetDefinitionIndex() < pItemDefA->GetDefinitionIndex(); + } + } + + // default, highest to lowest price + return pItemA->GetCurrentPrice( eCurrency ) > pItemB->GetCurrentPrice( eCurrency ); +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: return the CLocale name that works with setlocale() +//----------------------------------------------------------------------------- +const char *GetLanguageCLocaleName( ELanguage eLang ) +{ + if ( eLang == k_Lang_None ) + return ""; + +#ifdef _WIN32 + // table for Win32 is here: http://msdn.microsoft.com/en-us/library/hzz3tw78(v=VS.80).aspx + // shortname works except for chinese + + switch ( eLang ) + { + case k_Lang_Simplified_Chinese: + return "chs"; // or "chinese-simplified" + case k_Lang_Traditional_Chinese: + return "cht"; // or "chinese-traditional" + case k_Lang_Korean: + return "korean"; // steam likes "koreana" for the name for some reason. + case k_Lang_Brazilian: + return "ptb"; // "portuguese-brazil" - that string fails even though it's in the MS lang table; ptb does work. + default: + return GetLanguageShortName( eLang ); + } + +#else + switch ( eLang ) + { + case k_Lang_Simplified_Chinese: + case k_Lang_Traditional_Chinese: + return "zh_CN"; + default: + ; + } + + // ICU codes work on linux/osx + return GetLanguageICUName( eLang ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Get an I/O stream into the right local/settings for printing money - so to speak +//----------------------------------------------------------------------------- +static void InitStreamLocale( std::wostringstream &stream, ELanguage eLang, uint32 nExpectedAmount, ECurrency eCurrencyCode ) +{ + const char *pszLocale = GetLanguageCLocaleName( eLang ); + +#ifdef _PS3 + stream.imbue(std::locale(pszLocale)); // no exception for PS3 +#else + try + { + stream.imbue(std::locale(pszLocale)); + } + catch (const std::exception &e) + { + Log( "stream::imbue() failed with locale: '%s', exception: %s\n", pszLocale, e.what() ); + stream.imbue( std::locale("C") ); + } +#endif + + // Don't display fractional rubles + // But if our amount is fractional, we should show it regardless + // hack hack - certain currencies should not show fractional symbol - see if we can wire this through from config at some point + if ( ( eCurrencyCode == k_ECurrencyRUB || eCurrencyCode == k_ECurrencyJPY || eCurrencyCode == k_ECurrencyIDR || eCurrencyCode == k_ECurrencyKRW ) + && nExpectedAmount % 100 == 0 ) + { + stream.precision( 0 ); + stream.setf( std::ios_base::fixed ); + } + else + { + stream.precision( 2 ); + stream.setf( std::ios_base::fixed, std::ios_base::floatfield ); + } +} + +// +// Partial Integration from \steam\main\src\common\amount.cpp +// +int MakeMoneyStringInternal( wchar_t *pchDest, uint32 nDest, item_price_t unPrice, ECurrency eCurrencyCode, ELanguage eLanguage ) +{ + // Use the actual currency symbol with the local number formatting. + // assume local locale - should not be used from server to send to client + // without passing in a valid pszCLocale parameter. + std::wostringstream stream; + InitStreamLocale( stream, eLanguage, unPrice, eCurrencyCode ); + + stream << (unPrice/100.0); + + const auto sAmount = stream.str(); + const wchar_t *wszAmount = sAmount.c_str(); + + // this code will be used by the client. An old client might encounter a currency code it doesn't know about. Handle that. + if ( eCurrencyCode == k_ECurrencyInvalid ) + { + // just return the value + return V_snwprintf( pchDest, nDest, L"%ls", wszAmount ); + } + + // select symbol + const char *pchSymbol = ""; + switch( eCurrencyCode ) + { + case k_ECurrencyUSD: + pchSymbol = "$"; + break; + + case k_ECurrencyGBP: + pchSymbol = "\xC2\xA3"; + break; + + case k_ECurrencyEUR: + pchSymbol = "\xE2\x82\xAC"; + break; + + case k_ECurrencyCHF: + pchSymbol = "CHF"; + break; + + case k_ECurrencyRUB: + pchSymbol = "\xD1\x80\xD1\x83\xD0\xB1"; // localized py6 + break; + + case k_ECurrencyBRL: + pchSymbol = "R$"; + break; + + case k_ECurrencyJPY: + pchSymbol = "\xC2\xA5"; + break; + + case k_ECurrencyIDR: + pchSymbol = "Rp"; + break; + + case k_ECurrencyMYR: + pchSymbol = "RM"; + break; + + case k_ECurrencyPHP: + pchSymbol = "\xE2\x82\xB1"; + break; + + case k_ECurrencySGD: + pchSymbol = "S$"; + break; + + case k_ECurrencyTHB: + pchSymbol = "\xE0\xB8\xBF"; + break; + + case k_ECurrencyVND: + pchSymbol = "\xE2\x82\xAB"; + break; + + case k_ECurrencyKRW: + pchSymbol = "\xe2\x82\xa9"; + break; + + case k_ECurrencyTRY: + pchSymbol = "TL"; + break; + + case k_ECurrencyUAH: + pchSymbol = "\xe2\x82\xb4"; + break; + + case k_ECurrencyMXN: + pchSymbol = "Mex$"; + break; + + case k_ECurrencyCAD: + pchSymbol = "C$"; + break; + + case k_ECurrencyAUD: + pchSymbol = "A$"; + break; + + case k_ECurrencyNZD: + pchSymbol = "NZ$"; + break; + + case k_ECurrencyNOK: + pchSymbol = "kr"; + break; + + case k_ECurrencyPLN: + pchSymbol = "z\xc5\x82"; + break; + + case k_ECurrencyCNY: + pchSymbol = "\xc2\xa5"; + break; + + case k_ECurrencyINR: + pchSymbol = "\xe2\x82\xb9"; + break; + + case k_ECurrencyCLP: + pchSymbol = "$"; // bugbug - prefix it with CLP? + break; + + case k_ECurrencyPEN: + pchSymbol = "S/."; + break; + + case k_ECurrencyCOP: + pchSymbol = "COL$"; + break; + + case k_ECurrencyZAR: + pchSymbol = "R"; + break; + + case k_ECurrencyHKD: + pchSymbol = "HK$"; + break; + + case k_ECurrencyTWD: + pchSymbol = "NT$"; + break; + + case k_ECurrencySAR: + pchSymbol = "SR"; + break; + + case k_ECurrencyAED: + pchSymbol = "DH"; + break; + + default: + AssertMsg( false, "Unknown currency code" ); + pchSymbol = "$"; + break; + } + + // BEGIN HACK GAME CLIENT CHARACTER SET CONVERSION + wchar_t wsSymbol[ 16 ]; + V_UTF8ToUnicode( pchSymbol, wsSymbol, ARRAYSIZE( wsSymbol ) ); + // END HACK GAME CLIENT CHARACTER SET CONVERSION + + bool bFirstSymbolThenAmount = true; // Whether to show "$5" or "5E" + bool bSpaceBetweenTokens = false; // Whether to render a space between tokens like "$5" has no space, but "5 pyb" has a space + + switch ( eCurrencyCode ) + { + case k_ECurrencyEUR: + case k_ECurrencyUAH: + bFirstSymbolThenAmount = false; + bSpaceBetweenTokens = false; + break; + case k_ECurrencyRUB: + case k_ECurrencyVND: + case k_ECurrencyNOK: + case k_ECurrencyTRY: + case k_ECurrencyPLN: + case k_ECurrencySAR: + case k_ECurrencyAED: + bFirstSymbolThenAmount = false; + bSpaceBetweenTokens = true; + break; + case k_ECurrencyIDR: + case k_ECurrencyMXN: + case k_ECurrencyCAD: + case k_ECurrencyAUD: + case k_ECurrencyNZD: + case k_ECurrencyPEN: + case k_ECurrencyCOP: + case k_ECurrencyZAR: + case k_ECurrencyHKD: + case k_ECurrencyTWD: + case k_ECurrencyKRW: + case k_ECurrencyCHF: + bFirstSymbolThenAmount = true; + bSpaceBetweenTokens = true; + default: + bFirstSymbolThenAmount = true; + bSpaceBetweenTokens = false; + } + + return V_snwprintf( pchDest, nDest, L"%ls%ls%ls", + ( bFirstSymbolThenAmount ? wsSymbol : wszAmount ), + ( bSpaceBetweenTokens ? L" " : L"" ), + ( bFirstSymbolThenAmount ? wszAmount : wsSymbol ) ); +} + +static ELanguage GetStoreLanguage() +{ + if ( !engine ) + return k_Lang_English; + + char uilanguage[ 64 ]; + uilanguage[0] = 0; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + + return PchLanguageToELanguage( uilanguage ); +} + +void MakeMoneyString( wchar_t *pchDest, uint32 nDest, item_price_t unPrice, ECurrency eCurrencyCode ) +{ + (void)MakeMoneyStringInternal( pchDest, nDest, unPrice, eCurrencyCode, GetStoreLanguage() ); +} +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconStorePriceSheet::BItemExistsInPriceSheet( item_definition_index_t unDefIndex ) const +{ + CEconStoreCategoryManager *pCategoryManager = GEconStoreCategoryManager(); + const int nCategoryCount = pCategoryManager->GetNumCategories(); + + for ( int i = 0; i < nCategoryCount; ++i ) + { + const CEconStoreCategoryManager::StoreCategory_t *pCat = pCategoryManager->GetCategoryFromIndex( i ); + + // Intentionally not using CUtlSortVector<>::Find(), since it calls Less(), which is slow as shit for m_vecEntries. + FOR_EACH_VEC( pCat->m_vecEntries, j ) + { + if ( pCat->m_vecEntries[j] == unDefIndex ) + return true; + } + } + + return false; +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool ShouldUseNewStore() +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int GetStoreVersion() +{ + return 2; +} +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const CEconStorePriceSheet *GetEconPriceSheet() +{ +#ifdef GC_DLL + return GEconManager()->GetPriceSheet(); +#else + return EconUI() && EconUI()->GetStorePanel() + ? EconUI()->GetStorePanel()->GetPriceSheet() + : NULL; +#endif +} + diff --git a/game/shared/econ/econ_store.h b/game/shared/econ/econ_store.h new file mode 100644 index 0000000..56cf599 --- /dev/null +++ b/game/shared/econ/econ_store.h @@ -0,0 +1,621 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Common objects and utilities related to the in-game item store +// +//============================================================================= + +#ifndef ECON_STORE_H +#define ECON_STORE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "UtlSortVector.h" +#include "vstdlib/IKeyValuesSystem.h" +#include "econ/econ_storecategory.h" +#ifdef CLIENT_DLL +#include "client_community_market.h" +#endif // CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: Error code enum for purchase messages +//----------------------------------------------------------------------------- +enum EPurchaseResult +{ + k_EPurchaseResultOK = 1, // Success + k_EPurchaseResultFail = 2, // Generic error + k_EPurchaseResultInvalidParam = 3, // Invalid parameter + k_EPurchaseResultInternalError = 4, // Internal error + k_EPurchaseResultNotApproved = 5, // Tried to finalize a transaction that has not yet been approved + k_EPurchaseResultAlreadyCommitted = 6, // Tried to finalize a transaction that has already been committed + k_EPurchaseResultUserNotLoggedIn = 7, // User is not logged into Steam + k_EPurchaseResultWrongCurrency = 8, // Microtransaction's currency does not match user's wallet currency + k_EPurchaseResultAccountError = 9, // User's account does not exist or is temporarily unavailable + k_EPurchaseResultInvalidItem = 10, // User is trying to purchase an item that doesn't exist or is not for sale + k_EPurchaseResultNotEnoughBackpackSpace = 11, // User did not have enough backpack space + k_EPurchaseResultLimitedQuantityItemsUnavailable = 12, // User tried to purchase limited-quantity items but there weren't enough left in stock + + k_EPurchaseResultInsufficientFunds = 100, // User does not have wallet funds + k_EPurchaseResultTimedOut = 101, // Time limit for finalization has been exceeded + k_EPurchaseResultAcctDisabled = 102, // Steam account is disabled + k_EPurchaseResultAcctCannotPurchase = 103, // Steam account is not allowed to make a purchase + k_EMicroTxnResultFailedFraudChecks = 104, // Fraud checks inside of Steam failed + + k_EPurchaseResultOldPriceSheet = 150, // Information on the purchase didn't match the current price sheet + k_EPurchaseResultTxnNotFound = 151 // Could not find the transaction specified +}; + + +const char *PchNameFromEPurchaseResult( EPurchaseResult ePurchaseState ); + +//----------------------------------------------------------------------------- +// Purpose: State of a transaction +// +// WARNING: VALUES STORED IN DATABASE. DO NOT RENUMBER!!! +//----------------------------------------------------------------------------- +enum EPurchaseState +{ + k_EPurchaseStateInvalid = 0, // Invalid + k_EPurchaseStateInit = 1, // We have sent InitPurchase to Steam + k_EPurchaseStateWaitingForAuthorization = 2, // We have gotten initial authorization from Steam. Waiting for user to authorize. + k_EPurchaseStatePending = 3, // We are attempting to commit the transaction + k_EPurchaseStateComplete = 4, // The transaction was successful + k_EPurchaseStateFailed = 5, // The transaction failed + k_EPurchaseStateCanceled = 6, // The transaction was canceled + k_EPurchaseStateRefunded = 7, // The transaction was refunded + k_EPurchaseStateChargeback = 8, // The transaction was charged back + k_EPurchaseStateChargebackReversed = 9, // A chargeback has failed and we got the money + k_EPurchaseStateLast = k_EPurchaseStateChargebackReversed, +}; + +const char *PchNameFromEPurchaseState( EPurchaseState ePurchaseState ); + +// DO NOT RENUMBER! These values are stored in the audit log table. +enum EGCTransactionAuditReason +{ + k_EGCTransactionAudit_GCTransactionCompleted = 0, // The transaction completed successfully on the GC. + k_EGCTransactionAudit_GCTransactionInit = 1, // The transaction was initialized. + k_EGCTransactionAudit_GCTransactionPostInit = 2, // The result of attempting to initialize the transaction. This is where the SteamTxnID is set. + k_EGCTransactionAudit_GCTransactionFinalize = 3, // We have started to finalize the transaction (so probably set it to pending). + k_EGCTransactionAudit_GCTransactionFinalizeFailed = 4, // Our attempt to finalize the transaction failed for some reason (not due to a timeout). + k_EGCTransactionAudit_GCTransactionCanceled = 5, // The client requested that we cancel the transaction. + k_EGCTransactionAudit_SteamFailedMismatch = 6, // Steam failed the transaction but we did not. + k_EGCTransactionAudit_GCRemovePurchasedItems = 7, // We are attempting to remove the purchased items from their backpack (due to rollback or failure). + k_EGCTransactionAudit_GCTransactionInsert = 8, // We are inserting a transaction, usually one created by a web interface (i.e.: a cd-key operation) instead of a standard store interaction. + k_EGCTransactionAudit_GCTransactionCompletedPostChargeback = 9, // We thought this transaction had been charged back but Steam came back later and told us it was successful after all. + + k_EGCTransactionAuditLast = k_EGCTransactionAudit_GCTransactionCompletedPostChargeback, +}; + +const char *PchNameFromEGCTransactionAuditReason( EGCTransactionAuditReason eAuditReason ); + +enum EGCTransactionAuditInsertReason +{ + k_EGCTransactionAuditInsert_Invalid = 0, // + k_EGCTransactionAuditInsert_CDKey = 1, // A CD Key was used for this transaction. + k_EGCTransactionAuditInsert_SettlementNoMatch_Failed = 2, // We inserted a failed record during settlement to unblock the process. + k_EGCTransactionAuditInsert_SettlementNoMatch_Pending = 3, // We inserted a pending record during settlement to unblock the process. + + k_EGCTransactionAuditInsertLast = k_EGCTransactionAuditInsert_SettlementNoMatch_Pending, +}; + +//----------------------------------------------------------------------------- +// Purpose: Currencies we support +// +// WARNING: VALUES STORED IN DATABASE. DO NOT RENUMBER!!! +// WARNING: THESE DON'T MATCH THE STEAM NUMERIC IDS!!! WE TALK USING CURRENCY +// CODES LIKE "VND" AND IF YOU SAY "15" TERRIBLE THINGS WILL HAPPEN +//----------------------------------------------------------------------------- +enum ECurrency +{ + k_ECurrencyFirst = 0, + k_ECurrencyUSD = 0, + k_ECurrencyGBP = 1, + k_ECurrencyEUR = 2, + k_ECurrencyRUB = 3, + k_ECurrencyBRL = 4, + // space for Dota currencies + k_ECurrencyJPY = 8, + k_ECurrencyNOK = 9, + k_ECurrencyIDR = 10, + k_ECurrencyMYR = 11, + k_ECurrencyPHP = 12, + k_ECurrencySGD = 13, + k_ECurrencyTHB = 14, + k_ECurrencyVND = 15, + k_ECurrencyKRW = 16, + k_ECurrencyTRY = 17, + k_ECurrencyUAH = 18, + k_ECurrencyMXN = 19, + k_ECurrencyCAD = 20, + k_ECurrencyAUD = 21, + k_ECurrencyNZD = 22, + k_ECurrencyPLN = 23, + k_ECurrencyCHF = 24, + k_ECurrencyCNY = 25, + k_ECurrencyTWD = 26, + k_ECurrencyHKD = 27, + k_ECurrencyINR = 28, + k_ECurrencyAED = 29, + k_ECurrencySAR = 30, + k_ECurrencyZAR = 31, + k_ECurrencyCOP = 32, + k_ECurrencyPEN = 33, + k_ECurrencyCLP = 34, + + // NOTE: Not actually the Maximum currency value, but the Terminator for the possible currency code range. + k_ECurrencyMax = 35, + + // make this a big number so we can avoid having to move it when we add another currency type + k_ECurrencyInvalid = 255, + k_ECurrencyCDKeyTransaction = k_ECurrencyInvalid, +}; + +// Macro for looping across all valid currencies +#define FOR_EACH_CURRENCY( _i ) for ( ECurrency _i = GetFirstValidCurrency(); _i != k_ECurrencyInvalid; _i = GetNextValidCurrency( _i ) ) + +const char *PchNameFromECurrency( ECurrency eCurrency ); // NOTE: Defined with ENUMSTRINGS_START/ENUMSTRINGS_REVERSE macros +ECurrency ECurrencyFromName( const char *pchName ); // + +inline bool BIsCurrencyValid( ECurrency eCurrency ) +{ + switch ( eCurrency ) + { + case k_ECurrencyUSD: + case k_ECurrencyGBP: + case k_ECurrencyEUR: + case k_ECurrencyRUB: + case k_ECurrencyBRL: + case k_ECurrencyJPY: + case k_ECurrencyNOK: + case k_ECurrencyIDR: + case k_ECurrencyMYR: + case k_ECurrencyPHP: + case k_ECurrencySGD: + case k_ECurrencyTHB: + case k_ECurrencyVND: + case k_ECurrencyKRW: + case k_ECurrencyTRY: + case k_ECurrencyUAH: + case k_ECurrencyMXN: + case k_ECurrencyCAD: + case k_ECurrencyAUD: + case k_ECurrencyNZD: + //case k_ECurrencyPLN: + case k_ECurrencyCHF: + case k_ECurrencyCNY: + case k_ECurrencyTWD: + case k_ECurrencyHKD: + case k_ECurrencyINR: + case k_ECurrencyAED: + case k_ECurrencySAR: + case k_ECurrencyZAR: + case k_ECurrencyCOP: + case k_ECurrencyPEN: + case k_ECurrencyCLP: + return true; + } + + return false; +} + +inline ECurrency GetFirstValidCurrency() +{ + for ( int i = k_ECurrencyFirst; i < k_ECurrencyMax; i++ ) + { + if ( BIsCurrencyValid( (ECurrency)i ) ) + return (ECurrency)i; + } + return k_ECurrencyInvalid; +} + +inline ECurrency GetNextValidCurrency( ECurrency ePrevious ) +{ + for ( int i = ePrevious + 1; i < k_ECurrencyMax; i++ ) + { + if ( BIsCurrencyValid( (ECurrency)i ) ) + return (ECurrency)i; + } + return k_ECurrencyInvalid; +} + +//----------------------------------------------------------------------------- +// Purpose: Simple struct for pairing a sort type with a localization string +//----------------------------------------------------------------------------- +struct ItemSortTypeData_t +{ + const char *szSortDesc; // localization string + uint32 iSortType; // maps to the GC-specific sort value +}; + +// Description of a single item for sale +struct econ_store_entry_t +{ + // Constructor + econ_store_entry_t() + : m_pchCategoryTags( NULL ), + m_unGiftSteamPackageID( 0 ), + m_bHighlighted( false ) + { + V_memset( m_unBaseCosts, 0, sizeof(m_unBaseCosts) ); + V_memset( m_unSaleCosts, 0, sizeof(m_unSaleCosts) ); + } + + void SetItemDefinitionIndex( item_definition_index_t usDefIndex ); + item_definition_index_t GetItemDefinitionIndex() const { return m_usDefIndex; } + + void InitCategoryTags( const char *pTags ); // Sets m_pchCategoryTags and initializes m_vecTagIds and m_fRentalPriceScale + + bool IsListedInCategory( StoreCategoryID_t unID ) const; // Is this item listed in the given category? + bool IsListedInSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const; // Is this item listed in one of Category's subcategories? + bool IsListedInCategoryOrSubcategories( const CEconStoreCategoryManager::StoreCategory_t &Category ) const; // Is this item listed in Category or one of Category's subcategories? + + bool IsOnSale( ECurrency eCurrency ) const; + bool IsRentable() const; +#ifdef CLIENT_DLL + bool HasDiscount( ECurrency eCurrency, item_price_t *out_punOptionalBasePrice ) const; // returns true if we're on sale or if we're a bundle with a discounted total price +#endif // CLIENT_DLL + item_price_t GetCurrentPrice( ECurrency eCurrency ) const; + float GetRentalPriceScale() const; + + uint32 GetGiftSteamPackageID() const { return m_unGiftSteamPackageID; } + + // Helper function -- so we do this calculation in a single place. + static item_price_t GetDiscountedPrice( ECurrency eCurrency, item_price_t unBasePrice, float fDiscountPercentage ); + + static item_price_t CalculateSalePrice( const econ_store_entry_t* pSaleStoreEntry, ECurrency eCurrency, float fDiscountPercentage, int32 *out_pAdjustedDiscountPercentage = NULL ); + + item_price_t GetBasePrice( ECurrency eCurrency ) const + { + Assert( eCurrency >= k_ECurrencyFirst ); + Assert( eCurrency < k_ECurrencyMax ); + if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) ) + return 0; +#ifdef CLIENT_DLL + if ( m_bIsMarketItem ) + { + const client_market_data_t *pClientMarketData = GetClientMarketData( GetItemDefinitionIndex(), AE_UNIQUE ); + if ( !pClientMarketData ) + return 0; + return pClientMarketData->m_unLowestPrice; + } +#endif + // Weird-looking pattern: we're making sure that the value we're about to return fits correctly + // into the variable we're about to put it into. We do this to avoid integer conversion problems, + // especially overflow (!) where someone changes one of the return type or the storage type but + // not the other. + Assert( (item_price_t)m_unBaseCosts[eCurrency] == m_unBaseCosts[eCurrency] ); + return m_unBaseCosts[eCurrency]; + } + + item_price_t GetSalePrice( ECurrency eCurrency ) const + { + Assert( eCurrency >= k_ECurrencyFirst ); + Assert( eCurrency < k_ECurrencyMax ); + if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) ) + return 0; +#ifdef CLIENT_DLL + if ( m_bIsMarketItem ) + { + const client_market_data_t *pClientMarketData = GetClientMarketData( GetItemDefinitionIndex(), AE_UNIQUE ); + if ( !pClientMarketData ) + return 0; + return pClientMarketData->m_unLowestPrice; + } +#endif + // Weird-looking pattern: we're making sure that the value we're about to return fits correctly + // into the variable we're about to put it into. We do this to avoid integer conversion problems, + // especially overflow (!) where someone changes one of the return type or the storage type but + // not the other. + Assert( (item_price_t)m_unSaleCosts[eCurrency] == m_unSaleCosts[eCurrency] ); + return m_unSaleCosts[eCurrency]; + } + + uint16 GetQuantity() const + { + return m_usQuantity; + } + + const char* GetDate() const + { + return m_strDate.Get(); + } + + bool CanPreview() const + { + // No previewing of new items or weapons. + return m_bPreviewAllowed; + } + + void SetQuantity( uint16 usQuantity ) + { + Assert( usQuantity > 0 ); + m_usQuantity = usQuantity; + } + + void ValidatePrice( ECurrency eCurrency, item_price_t unPrice ); + + void SetBasePrice( ECurrency eCurrency, item_price_t unPrice ) + { + Assert( eCurrency >= k_ECurrencyFirst ); + Assert( eCurrency < k_ECurrencyMax ); + if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) ) + return; + + ValidatePrice( eCurrency, unPrice ); + + m_unBaseCosts[eCurrency] = unPrice; + } + + void SetSalePrice( ECurrency eCurrency, item_price_t unPrice ) + { + Assert( eCurrency >= k_ECurrencyFirst ); + Assert( eCurrency < k_ECurrencyMax ); + if ( !( eCurrency >= 0 && eCurrency < k_ECurrencyMax ) ) + return; + + ValidatePrice( eCurrency, unPrice ); + + // It's legal to have a sale price of zero, meaninig "this item is not on sale" in this + // currency. + // Assert( unPrice > 0 ); + m_unSaleCosts[eCurrency] = unPrice; + } + + void SetSteamGiftPackageID( uint32 unGiftSteamPackageID ) + { + m_unGiftSteamPackageID = unGiftSteamPackageID; + } + + void SetDate( const char* pszDate ) + { + m_strDate.Set( pszDate ); + } + + + bool IsValidCategoryTagIndex( uint32 iIndex ) const + { + AssertMsg( m_vecCategoryTags.IsValidIndex( iIndex ), "Category tag index out of range." ); + return m_vecCategoryTags.IsValidIndex( iIndex ); + } + + uint32 GetCategoryTagCount() const + { + return m_vecCategoryTags.Count(); + } + + const char *GetCategoryTagNameFromIndex( uint32 iIndex ) const + { + if ( !IsValidCategoryTagIndex( iIndex ) ) + return NULL; + + return m_vecCategoryTags[ iIndex ].m_strName; + } + + StoreCategoryID_t GetCategoryTagIDFromIndex( uint32 iIndex ) const; + + const char *GetCategoryTagString() const + { + return m_pchCategoryTags; + } + + bool m_bLimited; // Item is a limited sale + bool m_bNew; // Item is new + bool m_bHighlighted; // Item is highlighted + CUtlString m_strDate; // Date Added + bool m_bSoldOut; // True if the item is sold out from the store (for example if the item is a ticket or another physical item) + bool m_bPreviewAllowed; // Is this item previewable? + bool m_bIsPackItem; // Is this item a pack item? Pack items are items which are not individually for sale, but are sold via a bundle known as a "pack bundle" + + bool m_bIsMarketItem; // Is Market Item Link + +private: + item_definition_index_t m_usDefIndex; // DefIndex of the item + + // Private data so that we can check in the accessor functions that the data fits before returning it. + item_price_t m_unBaseCosts[k_ECurrencyMax]; // Costs of the items indexed by ECurrency -- if the items are on sale, this will be the current sale price + item_price_t m_unSaleCosts[k_ECurrencyMax]; // Original costs of the items indexed by ECurrency -- if the items are on sale, this will be the pre-sale price + uint16 m_usQuantity; // Quantity sold in a single purchase (ie., dueling pistols come in stacks of five) + float m_fRentalPriceScale; // 100.0 or greater means "unavailable to rent" + uint32 m_unGiftSteamPackageID; // if non-zero, when this item is purchased (including inside bundles, etc.), grant a gift copy of this Steam package + + struct CategoryTag_t + { + CUtlString m_strName; // Individual tag name, like "Weapons," "New," etc. + StoreCategoryID_t m_unID; // The category ID + }; + CCopyableUtlVector< CategoryTag_t > m_vecCategoryTags; // Category tag data + + const char *m_pchCategoryTags; // All tags - this string will something like: "New" or "Weapons+New" etc. +}; + +#ifdef GC_DLL +struct econ_store_timed_sale_item_t +{ + item_definition_index_t m_unItemDef; + float m_fPricePercentage; // 100.0 = regular price; 50.0 = half price +}; + +struct econ_store_timed_sale_t +{ + bool m_bSaleCurrentlyActive; // set in ::UpdatePricesForTimedSales() + CUtlConstString m_sIdentifier; // can't point to memory in the base KV because we toss it afterwards + RTime32 m_SaleStartTime; + RTime32 m_SaleEndTime; + CUtlVector<econ_store_timed_sale_item_t> m_vecSaleItems; + + // Work around protected default vector constructor. + econ_store_timed_sale_t() { } + econ_store_timed_sale_t( const econ_store_timed_sale_t& other ) + : m_bSaleCurrentlyActive( other.m_bSaleCurrentlyActive ) + , m_sIdentifier( other.m_sIdentifier ) + , m_SaleStartTime( other.m_SaleStartTime ) + , m_SaleEndTime( other.m_SaleEndTime ) + { + m_vecSaleItems.CopyArray( other.m_vecSaleItems.Base(), other.m_vecSaleItems.Count() ); + } +}; +#endif // GC_DLL + +// Spend xxx amount of money, get a free item from the loot list +struct store_promotion_spend_for_free_item_t +{ + const CEconItemDefinition *m_pItemDef; + item_price_t m_rgusPriceThreshold[k_ECurrencyMax]; // Price threshold to get an item from the loot list indexed by ECurrency +}; + + +//----------------------------------------------------------------------------- +// Purpose: Class that represents what's currently for sale in TF +//----------------------------------------------------------------------------- +typedef enum +{ + kEconStoreSortType_Price_HighestToLowest = 0, + kEconStoreSortType_Price_LowestToHighest = 1, + kEconStoreSortType_DevName_AToZ = 2, + kEconStoreSortType_DevName_ZToA = 3, + kEconStoreSortType_Name_AToZ = 4, + kEconStoreSortType_Name_ZToA = 5, + kEconStoreSortType_ItemDefIndex = 6, + kEconStoreSortType_ReverseItemDefIndex = 7, + kEconStoreSortType_DateNewest = 8, + kEconStoreSortType_DateOldest = 9, +} eEconStoreSortType; + +struct price_point_map_key_t +{ + item_price_t m_unPriceUSD; + ECurrency m_eCurrency; + + static bool Less( const price_point_map_key_t& a, const price_point_map_key_t& b ) + { + if ( a.m_eCurrency == b.m_eCurrency ) + return a.m_unPriceUSD < b.m_unPriceUSD; + + return a.m_eCurrency < b.m_eCurrency; + } +}; + +typedef CUtlMap<price_point_map_key_t, item_price_t> CurrencyPricePointMap_t; + +class CEconStorePriceSheet +{ +public: + typedef CUtlMap<item_definition_index_t, econ_store_entry_t> StoreEntryMap_t; + typedef CUtlMap<const char *, float> RentalPriceScaleMap_t; + typedef CUtlVector<item_definition_index_t> FeaturedItems_t; + + CEconStorePriceSheet(); + ~CEconStorePriceSheet(); + + bool InitFromKV( KeyValues *pKVPrices ); + + // Gets or sets the version stamp. This is just a number the GC can use + // to know if the client is in sync without sending it down on every + // request. + RTime32 GetVersionStamp( void ) const { return m_RTimeVersionStamp; } + void SetVersionStamp( RTime32 stamp ) { m_RTimeVersionStamp = stamp; } + + uint32 GetHashForAllItems() const { return m_unHashForAllItems; } + + typedef CUtlMap<uint16, econ_store_entry_t> EconStoreEntryMap_t; + EconStoreEntryMap_t &GetEntries() { return m_mapEntries; } + +#ifdef GC_DLL + econ_store_entry_t *GetEntryWriteable( item_definition_index_t unDefIndex ); +#endif // GC_DLL + + const StoreEntryMap_t &GetEntries() const { return m_mapEntries; } + const CEconStoreCategoryManager::StoreCategory_t *GetFeaturedItems( void ) { return &m_FeaturedItems; } + const econ_store_entry_t *GetEntry( item_definition_index_t usDefIndex ) const; + + uint32 GetFeaturedItemIndex() const { return m_unFeaturedItemIndex; } + void SetFeaturedItemIndex( uint32 unIdx ) { m_unFeaturedItemIndex = unIdx; } + + void SetEconStoreSortType( eEconStoreSortType eType ) { m_eEconStoreSortType = eType; } + eEconStoreSortType GetEconStoreSortType() { return m_eEconStoreSortType; } + + const store_promotion_spend_for_free_item_t *GetStorePromotion_SpendForFreeItem() const { return &m_StorePromotionSpendForFreeItem; } + const CEconItemDefinition * GetStorePromotion_FirstTimePurchaseItem() const { return m_pStorePromotionFirstTimePurchaseItem; } + const CEconItemDefinition * GetStorePromotion_FirstTimeWebPurchaseItem() const { return m_pStorePromotionFirstTimeWebPurchaseItem; } + + uint32 GetPreviewPeriod() const { return m_unPreviewPeriod; } + uint32 GetBonusDiscountPeriod() const { return m_unBonusDiscountPeriod; } + float GetPreviewPeriodDiscount() const { return m_flPreviewPeriodDiscount; } + + bool BItemExistsInPriceSheet( item_definition_index_t unDefIndex ) const; + + float GetRentalPriceScale( const char *pszCategory ) const + { + RentalPriceScaleMap_t::IndexType_t i = m_mapRentalPriceScales.Find( pszCategory ); + if ( i == RentalPriceScaleMap_t::InvalidIndex() ) + return 1.0f; + + return m_mapRentalPriceScales[i]; + } + + KeyValues *GetRawData() const { return m_pKVRaw; } + +#ifdef GC_DLL + void UpdatePricesForTimedSales( const RTime32 curTime ); + void DumpTimeSaleState( const RTime32 curTime ) const; +#endif // GC_DLL + +#ifdef CLIENT_DLL + const FeaturedItems_t& GetFeaturedItems() const { return m_vecFeaturedItems; } +#endif // CLIENT_DLL + +private: + bool BInitEntryFromKV( KeyValues *pKVEntry ); +#ifdef CLIENT_DLL + bool BInitMarketEntryFromKV( KeyValues *pKVEntry ); +#endif // CLIENT_DLL + +#ifdef GC_DLL + bool InitTimedSaleEntryFromKV( KeyValues *pKVTimedSaleEntry ); + bool VerifyTimedSaleEntries(); +#endif // GC_DLL + +private: + void Clear(); + uint32 CalculateHashFromItems() const; + + KeyValues *m_pKVRaw; + RTime32 m_RTimeVersionStamp; + CEconStoreCategoryManager::StoreCategory_t m_FeaturedItems; // Special section, not a tab, kept outside m_vecContents + StoreEntryMap_t m_mapEntries; + RentalPriceScaleMap_t m_mapRentalPriceScales; + store_promotion_spend_for_free_item_t m_StorePromotionSpendForFreeItem; + CEconItemDefinition* m_pStorePromotionFirstTimePurchaseItem; + CEconItemDefinition* m_pStorePromotionFirstTimeWebPurchaseItem; + +#ifdef CLIENT_DLL + FeaturedItems_t m_vecFeaturedItems; +#endif // CLIENT_DLL + +#ifdef GC_DLL + CUtlVector<econ_store_timed_sale_t> m_vecTimedSales; +#endif // GC_DLL + + // changes based on experiments + uint32 m_unFeaturedItemIndex; + eEconStoreSortType m_eEconStoreSortType; + + uint32 m_unPreviewPeriod; + uint32 m_unBonusDiscountPeriod; + float m_flPreviewPeriodDiscount; + uint32 m_unHashForAllItems; + + // price point lookup + CurrencyPricePointMap_t m_mapCurrencyPricePoints; +}; + +#ifdef CLIENT_DLL +void MakeMoneyString( wchar_t *pchDest, uint32 nDest, item_price_t unPrice, ECurrency eCurrencyCode ); + +bool ShouldUseNewStore(); +int GetStoreVersion(); +#endif // CLIENT_DLL + +const CEconStorePriceSheet *GetEconPriceSheet(); + +#endif // ECON_STORE_H diff --git a/game/shared/econ/econ_storecategory.cpp b/game/shared/econ/econ_storecategory.cpp new file mode 100644 index 0000000..610f2da --- /dev/null +++ b/game/shared/econ/econ_storecategory.cpp @@ -0,0 +1,267 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Base class for generating store meta data. Abstract methods need +// to be overridden on a per-product basis. +// +//------------------------------------------------------------------------------------------------------------------------------- + +#include "cbase.h" +#include "econ_storecategory.h" +#include "econ_store.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------------------------------------------------------- + +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Invalid = (StoreCategoryID_t)0; +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_New = CEconStoreCategoryManager::GetCategoryID( "New" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Weapons = CEconStoreCategoryManager::GetCategoryID( "Weapons" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Limited = CEconStoreCategoryManager::GetCategoryID( "Limited" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Maps = CEconStoreCategoryManager::GetCategoryID( "Maps" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Cosmetics = CEconStoreCategoryManager::GetCategoryID( "Cosmetics" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Taunts = CEconStoreCategoryManager::GetCategoryID( "Taunts" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Tools = CEconStoreCategoryManager::GetCategoryID( "Tools" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Bundles = CEconStoreCategoryManager::GetCategoryID( "Bundles" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Collections= CEconStoreCategoryManager::GetCategoryID( "Collections" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Popular = CEconStoreCategoryManager::GetCategoryID( "Popular" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_OnSale = CEconStoreCategoryManager::GetCategoryID( "OnSale" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Featured = CEconStoreCategoryManager::GetCategoryID( "Featured" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_ClassBundles = CEconStoreCategoryManager::GetCategoryID( "Class_Bundles" ); +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::k_CategoryID_Highlighted = CEconStoreCategoryManager::GetCategoryID( "Highlighted" ); + + +//------------------------------------------------------------------------------------------------------------------------------- + +CEconStoreCategoryManager::CEconStoreCategoryManager() +{ + m_unHomeCategoryID = k_CategoryID_Invalid; +} + +bool CEconStoreCategoryManager::BInit( CEconStorePriceSheet *pPriceSheet, KeyValues *pStoreMetaDataKV ) +{ + KeyValues *pCategoriesKV = pStoreMetaDataKV->FindKey( "categories" ); + if ( !pCategoriesKV ) + { + AssertMsg( 0, "Could not find 'categories' subkey!" ); + return false; + } + + FOR_EACH_TRUE_SUBKEY( pCategoriesKV, pKVCurCategory ) + { + const int iIndex = m_vecCategories.AddToTail(); + StoreCategory_t &curCategory = m_vecCategories[ iIndex ]; + if ( !BInitCategory( pPriceSheet, &curCategory, pKVCurCategory ) ) + return false; + + // If the current category is the home page, cache off its ID + if ( curCategory.m_bIsHome ) + { + m_unHomeCategoryID = curCategory.m_unID; + } + } + + // Verify that any parents point to valid categories + FOR_EACH_VEC( m_vecCategories, i ) + { + const StoreCategory_t &curCategory = m_vecCategories[i]; + + // Skip current category if it refers to invalid, which is fine + if ( curCategory.m_unParentCategoryID == k_CategoryID_Invalid ) + continue; + + // A category can't be a parent to itself + if ( curCategory.m_unID == curCategory.m_unParentCategoryID ) + { + AssertMsg( 0, "Store category %s is using itself as a parent category!", curCategory.m_pchName ); + } + + // Attempt to find the current section's parent ID + bool bFound = false; + FOR_EACH_VEC( m_vecCategories, j ) + { + // Don't compare against self + if ( i == j ) + continue; + + if ( m_vecCategories[j].m_unID == curCategory.m_unParentCategoryID ) + { + bFound = true; + break; + } + } + + // If we couldn't find the current section's parent ID, assert + if ( !bFound ) + { + AssertMsg( 0, "Category %s refers to an unknown parent category - check your spelling!", curCategory.m_pchName ); + } + } + + // Setup child category lists - looping twice to keep this code clean and easy to read + FOR_EACH_VEC( m_vecCategories, i ) + { + const StoreCategory_t &curCategory = m_vecCategories[i]; + + if ( k_CategoryID_Invalid == curCategory.m_unParentCategoryID ) + continue; + + StoreCategory_t *pParentCategory = GetStoreCategoryFromID( curCategory.m_unParentCategoryID ); + if ( !pParentCategory ) + continue; + + pParentCategory->m_vecSubcategories.AddToTail( &curCategory ); + } + + return true; +} + +//------------------------------------------------------------------------------------------------------------------------------- + +bool CEconStoreCategoryManager::BInitCategory( CEconStorePriceSheet *pPriceSheet, StoreCategory_t *pCategory, KeyValues* pKVTab ) +{ + const char *pDefaultResFile = "Resource/UI/econ/store/v2/StorePage.res"; + + // Get the main category name + const char* pCategoryName = pKVTab->GetName(); + if ( !pCategoryName || !pCategoryName[0] ) + { + AssertMsg( 0, "Invalid category name!" ); + return false; + } + + const bool bIsHome = pKVTab->GetBool( "home", false ); + pCategory->m_bIsHome = bIsHome; + + pCategory->m_pchRawName = pCategoryName; + pCategory->m_unID = GetCategoryID( pCategoryName ); + + pCategory->m_bUseLargeCells = pKVTab->GetBool( "use_large_cells", false ); + pCategory->m_bVisible = pKVTab->GetBool( "visible", true ); + pCategory->m_bInGameOnly = pKVTab->GetBool( "ingame_only", false ); + pCategory->m_bDefaultTab = pKVTab->GetBool( "default", false ); + +#if defined( GC_DLL ) + // Until we replace the in-game store with the web store, we have this hacky override property, so + // that we can call the home page "HOME" in the game client and "TOP SELLERS" on the web. The home page + // will be evolving shortly to include a lot more than just a list of top sellers. + const char *pchLabelTokenWebOverride = pKVTab->GetString( "web_label_token_override", NULL ); +#else + const char *pchLabelTokenWebOverride = NULL; +#endif + pCategory->m_pchName = pchLabelTokenWebOverride ? pchLabelTokenWebOverride : pKVTab->GetString( "label_token", "#Store_Unknown" ); + + pCategory->m_pchPageClass = pKVTab->GetString( "page_class", "CStorePage" ); + pCategory->m_pchPageRes = pKVTab->GetString( "page_res", pDefaultResFile ); + pCategory->m_pchSortType = pKVTab->GetString( "sort_type", "" ); + + // Important for web store but not needed for VGUI store +#if defined( GC_DLL ) + if ( !bIsHome ) + { + const char *pchDropdownPrefabName = pKVTab->GetString( "dropdown_prefab", NULL ); + pCategory->m_pDropdownPrefab = GEconStoreMetaData()->FindDropdownPrefab( pchDropdownPrefabName ); + if ( !pCategory->m_pDropdownPrefab ) + { + AssertMsg( pCategory->m_pDropdownPrefab, CFmtStr( "Invalid dropdown prefab name, '%s'!", pchDropdownPrefabName ).Access() ); + return false; + } + } + else + { + pCategory->m_pDropdownPrefab = NULL; + } +#endif + + // Look for a parent category for non-home categories + if ( !bIsHome ) + { + const char *pParentCategoryName = pKVTab->GetString( "parent", NULL ); + if ( pParentCategoryName ) + { + pCategory->m_unParentCategoryID = GetCategoryID( pParentCategoryName ); + } + else + { + pCategory->m_unParentCategoryID = k_CategoryID_Invalid; + } + } + + return true; +} + +bool CEconStoreCategoryManager::BOnPriceSheetLoaded( CEconStorePriceSheet *pPriceSheet ) +{ + // Go through all categories/subcategories and add a list of items to each. + // If an item belongs to a subcategory, it will also be added to its parent category. For example, + // a hat will be added to both the "hats" subcategory and the "items" parent category. + FOR_EACH_VEC( m_vecCategories, iCategory ) + { + StoreCategory_t &Category = m_vecCategories[iCategory]; + + // find all entries that match + const CEconStorePriceSheet::EconStoreEntryMap_t &mapEntries = pPriceSheet->GetEntries(); + FOR_EACH_MAP_FAST( mapEntries, idx ) + { + const econ_store_entry_t &entry = mapEntries[idx]; + + if ( entry.IsListedInCategoryOrSubcategories( Category ) ) + { + Category.m_vecEntries.InsertNoSort( entry.GetItemDefinitionIndex() ); + } + } + } + + return true; +} + +//------------------------------------------------------------------------------------------------------------------------------- + +/*static*/ StoreCategoryID_t CEconStoreCategoryManager::GetCategoryID( const char *pCategoryName ) +{ + // Make the input lower case + CUtlString strLowerCase = pCategoryName; + strLowerCase.ToLower(); + + return CRC32_ProcessSingleBuffer( (void*)strLowerCase.Get(), strLowerCase.Length() ); +} + +//------------------------------------------------------------------------------------------------------------------------------- + +const CEconStoreCategoryManager::StoreCategory_t *CEconStoreCategoryManager::GetStoreCategoryFromID( StoreCategoryID_t unID ) const +{ + return const_cast< CEconStoreCategoryManager * >( this )->GetStoreCategoryFromID( unID ); +} + +CEconStoreCategoryManager::StoreCategory_t *CEconStoreCategoryManager::GetStoreCategoryFromID( StoreCategoryID_t unID ) +{ + if ( k_CategoryID_Invalid != unID ) + { + FOR_EACH_VEC( m_vecCategories, i ) + { + if ( unID == m_vecCategories[i].m_unID ) + return &m_vecCategories[i]; + } + } + + return NULL; +} + +//------------------------------------------------------------------------------------------------------------------------------- + +static CEconStoreCategoryManager *gs_pEconStoreCategoryManager = NULL; +CEconStoreCategoryManager *GEconStoreCategoryManager() +{ + if ( !gs_pEconStoreCategoryManager ) + { + gs_pEconStoreCategoryManager = new CEconStoreCategoryManager(); + } + + return gs_pEconStoreCategoryManager; +} + +void ClearEconStoreCategoryManager() +{ + delete gs_pEconStoreCategoryManager; + gs_pEconStoreCategoryManager = NULL; +}
\ No newline at end of file diff --git a/game/shared/econ/econ_storecategory.h b/game/shared/econ/econ_storecategory.h new file mode 100644 index 0000000..a36e1fa --- /dev/null +++ b/game/shared/econ/econ_storecategory.h @@ -0,0 +1,110 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//------------------------------------------------------------------------------------------------------------------------------- + +#ifndef ECON_STORECATEGORY_H +#define ECON_STORECATEGORY_H +#ifdef _WIN32 +#pragma once +#endif + +#if defined( GC_DLL ) +#include "econ/econ_storemetadata.h" +#endif + +//------------------------------------------------------------------------------------------------------------------------------- + +typedef CRC32_t StoreCategoryID_t; + +//------------------------------------------------------------------------------------------------------------------------------- + +// sort by price highest to lowest +class CEconStoreEntryLess +{ +public: + bool Less( const uint16& lhs, const uint16& rhs, void *pContext ); +}; + +//------------------------------------------------------------------------------------------------------------------------------- + +class CEconStoreCategoryManager +{ +public: + CEconStoreCategoryManager(); + + bool BInit( CEconStorePriceSheet *pPriceSheet, KeyValues *pStoreMetaDataKV ); + bool BOnPriceSheetLoaded( CEconStorePriceSheet *pPriceSheet ); + + static StoreCategoryID_t GetCategoryID( const char *pCategoryName ); + + static StoreCategoryID_t k_CategoryID_Invalid; + static StoreCategoryID_t k_CategoryID_New; + static StoreCategoryID_t k_CategoryID_Weapons; + static StoreCategoryID_t k_CategoryID_Limited; + static StoreCategoryID_t k_CategoryID_Maps; + static StoreCategoryID_t k_CategoryID_Cosmetics; + static StoreCategoryID_t k_CategoryID_Taunts; + static StoreCategoryID_t k_CategoryID_Tools; + static StoreCategoryID_t k_CategoryID_Bundles; + static StoreCategoryID_t k_CategoryID_Collections; + static StoreCategoryID_t k_CategoryID_Popular; + static StoreCategoryID_t k_CategoryID_OnSale; + static StoreCategoryID_t k_CategoryID_Featured; + static StoreCategoryID_t k_CategoryID_ClassBundles; + static StoreCategoryID_t k_CategoryID_Highlighted; + + struct StoreCategory_t + { + StoreCategory_t() { V_memset( this, 0, sizeof( StoreCategory_t ) ); } + + int GetNumSubcategories() const { return m_vecSubcategories.Count(); } + bool HasSubcategories() const { return m_vecSubcategories.Count() > 1; } + bool BIsSubcategory() const { return m_unParentCategoryID != CEconStoreCategoryManager::k_CategoryID_Invalid; } + + bool m_bIsHome:1; // Home page? Default=no. + bool m_bUseLargeCells:1; // Display large icons in the store. Default=no. + bool m_bDefaultTab:1; // Is this the default tab? Default=no + bool m_bVisible:1; // Should this tab be displayed in the store? Default=yes. + bool m_bInGameOnly:1; // Is this category only to be displayed in the in-game store (vs. the web store)? + const char *m_pchRawName; // Raw name of the tab + const char *m_pchName; // Name of the tab + const char *m_pchPageClass; // Code class of the store page. + const char *m_pchSortType; // How to sort the page. + const char *m_pchPageRes; // Res file to use for the page. + StoreCategoryID_t m_unID; // A unique ID that is stable across sessions + StoreCategoryID_t m_unParentCategoryID; + CUtlVector<const StoreCategory_t *> m_vecSubcategories; // A list of ID's for all subcategories + CUtlSortVector<uint16, CEconStoreEntryLess> m_vecEntries; // Vector of items for sale +#if defined( GC_DLL ) + const CEconStoreMetaData::DropdownPrefabInfo_t *m_pDropdownPrefab; +#endif + }; + + const StoreCategoryID_t GetHomeCategoryID() const { Assert( m_unHomeCategoryID != k_CategoryID_Invalid ); return m_unHomeCategoryID; } + + const StoreCategory_t *GetStoreCategoryFromID( StoreCategoryID_t unID ) const; + int GetNumCategories( void ) const { return m_vecCategories.Count(); } + const StoreCategory_t *GetCategoryFromIndex( int i ) const { Assert(i >= 0 && i < m_vecCategories.Count()); return &m_vecCategories[i]; } + + StoreCategory_t *GetFeaturedItems() const { return NULL; } + +private: + bool BInitCategory( CEconStorePriceSheet *pPriceSheet, StoreCategory_t *pCategory, KeyValues *pKVTab ); + + StoreCategory_t *GetStoreCategoryFromID( StoreCategoryID_t unID ); + + CUtlVector< StoreCategory_t > m_vecCategories; + + StoreCategoryID_t m_unHomeCategoryID; // The ID for the home tab +}; + +//------------------------------------------------------------------------------------------------------------------------------- + +CEconStoreCategoryManager *GEconStoreCategoryManager(); +void ClearEconStoreCategoryManager(); + +//------------------------------------------------------------------------------------------------------------------------------- + +#endif
\ No newline at end of file diff --git a/game/shared/econ/econ_wearable.cpp b/game/shared/econ/econ_wearable.cpp new file mode 100644 index 0000000..b7bea59 --- /dev/null +++ b/game/shared/econ/econ_wearable.cpp @@ -0,0 +1,860 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "econ_wearable.h" +#include "vcollide_parse.h" + +#ifdef CLIENT_DLL +#include "functionproxy.h" +#include "c_te_effect_dispatch.h" +#endif // CLIENT_DLL + +#ifdef TF_CLIENT_DLL +#include "c_team.h" +#include "tf_shareddefs.h" +#include "tf_weapon_jar.h" +#include "c_tf_player.h" +#endif // TF_CLIENT_DLL + +#ifdef TF_DLL +#include "tf_player.h" +#endif // TF_DLL + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( wearable_item, CEconWearable ); +IMPLEMENT_NETWORKCLASS_ALIASED( EconWearable, DT_WearableItem ) + +// Network Table -- +BEGIN_NETWORK_TABLE( CEconWearable, DT_WearableItem ) +END_NETWORK_TABLE() +// -- Network Table + +// Data Desc -- +BEGIN_DATADESC( CEconWearable ) +END_DATADESC() +// -- Data Desc + +PRECACHE_REGISTER( wearable_item ); + +IMPLEMENT_NETWORKCLASS_ALIASED( TFWearableItem, DT_TFWearableItem ) + +// Network Table -- +BEGIN_NETWORK_TABLE( CTFWearableItem, DT_TFWearableItem ) +END_NETWORK_TABLE() +// -- Network Table + +// Data Desc -- +BEGIN_DATADESC( CTFWearableItem ) +END_DATADESC() +// -- Data Desc + +//----------------------------------------------------------------------------- +// SHARED CODE +//----------------------------------------------------------------------------- + +CEconWearable::CEconWearable() +{ + m_bAlwaysAllow = false; +}; + +void CEconWearable::InternalSetPlayerDisplayModel( void ) +{ + int iClass = 0; + int iTeam = 0; + +#if defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pTFPlayer ) + { + iClass = pTFPlayer->GetPlayerClass()->GetClassIndex(); + iTeam = pTFPlayer->GetTeamNumber(); + } +#endif // defined( TF_DLL ) || defined( TF_CLIENT_DLL ) + + // Set our model to the player model + CEconItemView *pItem = GetAttributeContainer()->GetItem(); + if ( pItem && pItem->IsValid() ) + { + const char *pszPlayerDisplayModel = pItem->GetPlayerDisplayModel( iClass, iTeam ); + if ( pszPlayerDisplayModel ) + { + if ( pItem->GetStaticData()->IsContentStreamable() ) + { + modelinfo->RegisterDynamicModel( pszPlayerDisplayModel, IsClient() ); + + if ( pItem->GetVisionFilteredDisplayModel() && pItem->GetVisionFilteredDisplayModel()[ 0 ] != '\0' ) + { + modelinfo->RegisterDynamicModel( pItem->GetVisionFilteredDisplayModel(), IsClient() ); + } + } + SetModel( pszPlayerDisplayModel ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set up the item. GC data may not be available here depending on +// where we're called from. +//----------------------------------------------------------------------------- +void CEconWearable::Spawn( void ) +{ + InitializeAttributes(); + + Precache(); + + InternalSetPlayerDisplayModel(); + + BaseClass::Spawn(); + + AddEffects( EF_BONEMERGE ); + AddEffects( EF_BONEMERGE_FASTCULL ); + +#if !defined( CLIENT_DLL ) + SetCollisionGroup( COLLISION_GROUP_WEAPON ); + SetBlocksLOS( false ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Player touches the item. Currently wearables don't appear in the +// world, so this is only called directly during equipment assignment. +//----------------------------------------------------------------------------- +void CEconWearable::GiveTo( CBaseEntity *pOther ) +{ + CBasePlayer *pPlayer = ToBasePlayer(pOther); + if ( !pPlayer ) + return; + +#if !defined( CLIENT_DLL ) + pPlayer->EquipWearable( this ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconWearable::RemoveFrom( CBaseEntity *pOther ) +{ + CBasePlayer *pPlayer = ToBasePlayer(pOther); + if ( !pPlayer ) + return; + +#if !defined( CLIENT_DLL ) + pPlayer->RemoveWearable( this ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconWearable::GetSkin( void ) +{ + CEconItemView *pItem = GetAttributeContainer()->GetItem(); // Safe. Checked in base class call. + if ( pItem ) + { + int iSkin = pItem->GetSkin( GetTeamNumber() ); + if ( iSkin > -1 ) + { + return iSkin; + } + } + + return ( GetTeamNumber() == (LAST_SHARED_TEAM+1) ) ? 0 : 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Attaches the item to the player. +//----------------------------------------------------------------------------- +void CEconWearable::Equip( CBasePlayer* pOwner ) +{ + if ( !CanEquip( pOwner ) ) + { + RemoveFrom( pOwner ); + return; + } + + SetTouch( NULL ); + SetAbsVelocity( vec3_origin ); + + CBaseEntity *pFollowEntity = pOwner; + + if ( IsViewModelWearable() ) + { + pFollowEntity = pOwner->GetViewModel(); + } + + FollowEntity( pFollowEntity, true ); + + SetOwnerEntity( pOwner ); + + ReapplyProvision(); + + ChangeTeam( pOwner->GetTeamNumber() ); + m_nSkin = GetSkin(); + +#ifdef GAME_DLL + UpdateModelToClass(); + UpdateBodygroups( pOwner, true ); + PlayAnimForPlaybackEvent( WAP_ON_SPAWN ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove item from the player. +//----------------------------------------------------------------------------- +void CEconWearable::UnEquip( CBasePlayer* pOwner ) +{ +#ifdef CLIENT_DLL + SetParticleSystemsVisible( PARTICLE_SYSTEM_STATE_NOT_VISIBLE ); +#endif + +#ifdef GAME_DLL + UpdateBodygroups( pOwner, false ); +#endif + + StopFollowingEntity(); + SetOwnerEntity( NULL ); + + ReapplyProvision(); +} +/* +//----------------------------------------------------------------------------- +// Purpose: Hides or shows masked bodygroups associated with this item. +//----------------------------------------------------------------------------- +bool CEconWearable::UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState ) +{ + if ( !pOwner ) + return false; + + CAttributeContainer *pCont = GetAttributeContainer(); + if ( !pCont ) + return false; + + CEconItemView *pItem = pCont->GetItem(); + if ( !pItem ) + return false; + + int iTeam = pOwner->GetTeamNumber(); + int iNumBodyGroups = pItem->GetNumModifiedBodyGroups( iTeam ); + for ( int i=0; i<iNumBodyGroups; ++i ) + { + int iBody = 0; + const char *pszBodyGroup = pItem->GetModifiedBodyGroup( iTeam, i, iBody ); + int iBodyGroup = pOwner->FindBodygroupByName( pszBodyGroup ); + + if ( iBodyGroup == -1 ) + continue; + + pOwner->SetBodygroup( iBodyGroup, iState ); + } + + return true; +} +*/ +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconWearable::OnWearerDeath( void ) +{ +#ifdef CLIENT_DLL + UpdateParticleSystems(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CEconWearable::GetDropType() +{ + CAttributeContainer *pCont = GetAttributeContainer(); + if ( !pCont ) + return 0; + + CEconItemView *pItem = pCont->GetItem(); + if ( pItem ) + return pItem->GetDropType(); + else + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Ensures that a player's correct body groups are enabled on client respawn. +//----------------------------------------------------------------------------- +void CEconWearable::UpdateWearableBodyGroups( CBasePlayer* pPlayer ) +{ + if ( !pPlayer ) + return; + + for ( int i=0; i<pPlayer->GetNumWearables(); ++i ) + { + CEconWearable* pItem = pPlayer->GetWearable(i); + if ( !pItem ) + continue; + + // Dynamic models which are not yet rendering do not modify bodygroups + if ( pItem->IsDynamicModelLoading() ) + continue; + + // On the client, ignore items that aren't valid. +#ifdef TF_CLIENT_DLL + if ( pItem->EntityDeemedInvalid() ) + continue; +#endif + + int nVisibleState = 1; +#ifdef TF_CLIENT_DLL + if ( pItem->ShouldHideForVisionFilterFlags() ) + { + // Items that shouldn't draw (pyro-vision filtered) shouldn't change any body group states + // unless they have no model (hatless hats) + nVisibleState = 0; + } +#endif + + pItem->UpdateBodygroups( pPlayer, nVisibleState ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFWearableItem::CTFWearableItem() +{ +} + +//----------------------------------------------------------------------------- +// SERVER ONLY CODE +//----------------------------------------------------------------------------- + +#if defined( GAME_DLL ) + +#endif + +//----------------------------------------------------------------------------- +// CLIENT ONLY CODE +//----------------------------------------------------------------------------- + +#if defined( CLIENT_DLL ) + +//----------------------------------------------------------------------------- +// Purpose: Mirror should draw logic. +//----------------------------------------------------------------------------- +ShadowType_t CEconWearable::ShadowCastType() +{ + if ( ShouldDraw() ) + { + return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC; + } + + return SHADOWS_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconWearable::ShouldDraw( void ) +{ + CBasePlayer *pPlayerOwner = ToBasePlayer( GetOwnerEntity() ); + if ( !pPlayerOwner ) + { + return false; + } + + bool bUseViewModel = !pPlayerOwner->ShouldDrawThisPlayer(); + + // Don't show view models if we're drawing the real player, and don't show non view models if using view models. + if ( bUseViewModel ) + { + // VM mode. + if ( !IsViewModelWearable() ) + { + return false; + } + } + else + { + // Non-viewmodel mode. + if ( IsViewModelWearable() ) + { + return false; + } + } + + if ( !ShouldDrawWhenPlayerIsDead() && !pPlayerOwner->IsAlive() ) + { + return false; + } + + if ( pPlayerOwner->GetTeamNumber() == TEAM_SPECTATOR ) + { + return false; + } + + return BaseClass::ShouldDraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconWearable::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } + + // Update our visibility in case our parents' has changed. + UpdateVisibility(); + UpdateParticleSystems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEconWearable::ClientThink( void ) +{ + BaseClass::ClientThink(); + + UpdateParticleSystems(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEconWearable::ShouldDrawParticleSystems( void ) +{ + // Make sure the entity we're attaching to is being drawn + CBasePlayer *pPlayerOwner = ToBasePlayer( GetOwnerEntity() ); + if ( !pPlayerOwner ) + { + Assert ( "CEconWearable has no owner?" ); // Not sure what this means - is is visible or not? + return false; + } + if ( pPlayerOwner->ShouldDrawThisPlayer() ) + { + return true; + } + return false; +} + +RenderGroup_t CEconWearable::GetRenderGroup() +{ + if ( IsViewModelWearable() ) + return RENDER_GROUP_VIEW_MODEL_TRANSLUCENT; + + return BaseClass::GetRenderGroup(); +} + +//----------------------------------------------------------------------------- +// Purpose: Wearable tint colors +//----------------------------------------------------------------------------- +class CProxyItemTintColor : public CResultProxy +{ +public: + void OnBind( void *pC_BaseEntity ) + { + Assert( m_pResult ); + Vector vResult = Vector( 0, 0, 0 ); + + if ( pC_BaseEntity ) + { + CEconItemView *pScriptItem = NULL; + + IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity; + C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); + if ( pEntity ) + { + CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity ); + if ( pItem ) + { + pScriptItem = pItem->GetAttributeContainer()->GetItem(); + } + else if ( pEntity->GetOwnerEntity() ) + { + // Try the owner (for viewmodels, etc). + pEntity = pEntity->GetOwnerEntity(); + pItem = dynamic_cast< CEconEntity* >( pEntity ); + if ( pItem ) + { + pScriptItem = pItem->GetAttributeContainer()->GetItem(); + } + } + } + else + { + // Proxy data can be a script created item itself, if we're in a vgui CModelPanel + pScriptItem = dynamic_cast< CEconItemView* >( pRend ); + } + +#ifdef TF_CLIENT_DLL + if ( !pScriptItem ) + { + // Might be a throwable + CTFWeaponBaseGrenadeProj *pProjectile = dynamic_cast< CTFWeaponBaseGrenadeProj* >( pEntity ); + if ( pProjectile ) + { + CEconEntity *pItem = dynamic_cast< CEconEntity* >( pProjectile->GetLauncher() ); + if ( pItem ) + { + pScriptItem = pItem->GetAttributeContainer()->GetItem(); + } + } + } + + if ( pScriptItem && pScriptItem->IsValid() ) + { + const bool bAltColor = pEntity && pEntity->GetTeam() > 0 + ? pEntity->GetTeam()->GetTeamNumber() == TF_TEAM_BLUE + : pScriptItem->GetFlags() & kEconItemFlagClient_ForceBlueTeam + ? true + : false; + + int iModifiedRGB = pScriptItem->GetModifiedRGBValue( bAltColor ); + if ( iModifiedRGB ) + { + // The attrib returns a packed RGB with values between 0 & 255 packed into the bottom 3 bytes. + Color clr = Color( ((iModifiedRGB & 0xFF0000) >> 16), ((iModifiedRGB & 0xFF00) >> 8), (iModifiedRGB & 0xFF) ); + + vResult.x = clamp( clr.r() * (1.f / 255.0f), 0.f, 1.0f ); + vResult.y = clamp( clr.g() * (1.f / 255.0f), 0.f, 1.0f ); + vResult.z = clamp( clr.b() * (1.f / 255.0f), 0.f, 1.0f ); + } + } +#endif // TF_CLIENT_DLL + } + + m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z ); + } +}; +EXPOSE_INTERFACE( CProxyItemTintColor, IMaterialProxy, "ItemTintColor" IMATERIAL_PROXY_INTERFACE_VERSION ); + + + +//============================================================================================================================ +extern ConVar r_propsmaxdist; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_EconWearableGib::C_EconWearableGib() +{ + m_fDeathTime = -1; + m_iHealth = 0; + m_bParented = false; + m_bDelayedInit = false; +} + +C_EconWearableGib::~C_EconWearableGib() +{ + PhysCleanupFrictionSounds( this ); + VPhysicsDestroyObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_EconWearableGib::Initialize( bool bWillBeParented ) +{ + m_bParented = bWillBeParented; + return InitializeAsClientEntity( STRING( GetModelName() ), RENDER_GROUP_OPAQUE_ENTITY ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStudioHdr* C_EconWearableGib::OnNewModel() +{ + CStudioHdr* pCStudioHdr = BaseClass::OnNewModel(); + if ( m_bDelayedInit && !IsDynamicModelLoading() ) + { + FinishModelInitialization(); + } + return pCStudioHdr; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EconWearableGib::SpawnClientEntity( void ) +{ + if ( !IsDynamicModelLoading() ) + { + FinishModelInitialization(); + } + else + { + m_bDelayedInit = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_EconWearableGib::FinishModelInitialization( void ) +{ + UpdateThinkState(); + + const model_t *mod = GetModel(); + if ( mod ) + { + Vector mins, maxs; + modelinfo->GetModelBounds( mod, mins, maxs ); + SetCollisionBounds( mins, maxs ); + } + + if ( !m_bParented ) + { + // Create the object in the physics system + solid_t tmpSolid; + if ( !PhysModelParseSolid( tmpSolid, this, GetModelIndex() ) ) + { + DevMsg("C_EconWearableGib::FinishModelInitialization: PhysModelParseSolid failed for entity %i.\n", GetModelIndex() ); + return false; + } + else + { + m_pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid ); + + if ( !m_pPhysicsObject ) + { + // failed to create a physics object + DevMsg(" C_EconWearableGib::FinishModelInitialization: VPhysicsInitNormal() failed for %s.\n", STRING(GetModelName()) ); + return false; + } + } + } + + Spawn(); + + if ( m_fadeMinDist < 0 ) + { + // start fading out at 75% of r_propsmaxdist + m_fadeMaxDist = r_propsmaxdist.GetFloat(); + m_fadeMinDist = r_propsmaxdist.GetFloat() * 0.75f; + } + + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + + UpdatePartitionListEntry(); + + CollisionProp()->UpdatePartition(); + + SetBlocksLOS( false ); // this should be a small object + + // Set up shadows; do it here so that objects can change shadowcasting state + CreateShadow(); + + UpdateVisibility(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EconWearableGib::Spawn() +{ + BaseClass::Spawn(); + m_takedamage = DAMAGE_EVENTS_ONLY; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_EconWearableGib::ValidateEntityAttachedToPlayer( bool &bShouldRetry ) +{ + bShouldRetry = false; + + // Always valid as long as we're not parented to anything + return (GetMoveParent() == NULL); +} + +#define WEARABLE_FADEOUT_TIME 1.0f + +//----------------------------------------------------------------------------- +// Purpose: Figure out if we need to think or not +//----------------------------------------------------------------------------- +bool C_EconWearableGib::UpdateThinkState( void ) +{ + if ( m_fDeathTime > 0 ) + { + // If we're in the active fadeout portion, think rapidly. Otherwise, wait for that time. + if ( (m_fDeathTime - gpGlobals->curtime) > WEARABLE_FADEOUT_TIME ) + { + SetNextClientThink( m_fDeathTime - WEARABLE_FADEOUT_TIME ); + } + else + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } + return true; + } + + SetNextClientThink( CLIENT_THINK_NEVER ); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EconWearableGib::ClientThink( void ) +{ + if ( (m_fDeathTime > 0) && ((m_fDeathTime - gpGlobals->curtime) <= WEARABLE_FADEOUT_TIME) ) + { + if ( m_fDeathTime <= gpGlobals->curtime ) + { + Release(); // Die + return; + } + + // fade out + float alpha = (m_fDeathTime - gpGlobals->curtime) / WEARABLE_FADEOUT_TIME; + SetRenderMode( kRenderTransTexture ); + SetRenderColorA( alpha * 256 ); + } + + UpdateThinkState(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EconWearableGib::StartFadeOut( float fDelay ) +{ + m_fDeathTime = gpGlobals->curtime + fDelay + WEARABLE_FADEOUT_TIME; + UpdateThinkState(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_EconWearableGib::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) +{ + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + + if( !pPhysicsObject ) + return; + + Vector dir = pTrace->endpos - pTrace->startpos; + int iDamage = 0; + + if ( iDamageType & DMG_BLAST ) + { + iDamage = VectorLength( dir ); + dir *= 500; // adjust impact strenght + + // apply force at object mass center + pPhysicsObject->ApplyForceCenter( dir ); + } + else + { + Vector hitpos; + + VectorMA( pTrace->startpos, pTrace->fraction, dir, hitpos ); + VectorNormalize( dir ); + + // guess avg damage + if ( iDamageType == DMG_BULLET ) + { + iDamage = 30; + } + else + { + iDamage = 50; + } + + dir *= 4000; // adjust impact strenght + + // apply force where we hit it + pPhysicsObject->ApplyForceOffset( dir, hitpos ); + } +} + +#if 0 +#ifdef _DEBUG +#include "econ_item_system.h" + +static CUtlVector< const char * > s_possibleModels; +static CUtlVector< const GameItemDefinition_t * > s_possibleDefinitions; +void Dbg_TestDynamicWearableGibs( void ) +{ + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pLocalPlayer ) + return; + + C_EconWearableGib *pEntity = new C_EconWearableGib(); + if ( !pEntity ) + return; + + Vector forward; + pLocalPlayer->EyeVectors( &forward ); + trace_t tr; + UTIL_TraceLine( pLocalPlayer->EyePosition(), pLocalPlayer->EyePosition() + (forward * 256), MASK_NPCSOLID, pLocalPlayer, COLLISION_GROUP_NONE, &tr ); + + Vector position = tr.endpos; + + if ( s_possibleModels.Count() == 0 ) + { + FOR_EACH_MAP( ItemSystem()->GetItemSchema()->GetItemDefinitionMap(), nDefn ) + { + const GameItemDefinition_t *pDefn = dynamic_cast<GameItemDefinition_t *>( ItemSystem()->GetItemSchema()->GetItemDefinitionMap()[nDefn] ); + if ( !pDefn ) + continue; + + const char *pszModel = pDefn->GetPlayerDisplayModel( 0 ); + if ( pszModel && pszModel[0] && pszModel[0] != '?' && pDefn->BLoadOnDemand() && pDefn->GetDropType() == ITEM_DROP_TYPE_DROP ) + { + s_possibleModels.AddToTail( pszModel ); + s_possibleDefinitions.AddToTail( pDefn ); + } + } + } + + Assert( s_possibleModels.Count() ); + + int spawnIndex = random->RandomInt( 0, s_possibleModels.Count() - 1 ); + const char *pszModelName = s_possibleModels[ spawnIndex ]; + const GameItemDefinition_t *pDefn = s_possibleDefinitions[ spawnIndex ]; + Msg( "Spawning: %s\n", pszModelName ); + pEntity->SetModelName( AllocPooledString( pszModelName ) ); + pEntity->SetAbsOrigin( position ); + pEntity->SetAbsAngles( vec3_angle ); + pEntity->SetOwnerEntity( pLocalPlayer ); + pEntity->ChangeTeam( pLocalPlayer->GetTeamNumber() ); // our gibs will match our team; this will probably not be used for anything besides team coloring + // Copy the script created item data over + pEntity->GetAttributeContainer()->GetItem()->Init( pDefn->GetDefinitionIndex(), pDefn->GetQuality(), pDefn->GetMinLevel(), true ); + + if ( !pEntity->Initialize( false ) ) + { + pEntity->Release(); + return; + } + + pEntity->StartFadeOut( 15.0f ); + return; + + IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); + if ( !pPhysicsObject ) + { + pEntity->Release(); + return; + } + + // randomize velocity by 5% + Vector rndVel = Vector(0,0,100); + pPhysicsObject->AddVelocity( &rndVel, &vec3_origin ); +} +static ConCommand dbg_testdynamicwearablegib( "dbg_testdynamicwearablegib", Dbg_TestDynamicWearableGibs, "", FCVAR_CHEAT ); +#endif // _DEBUG +#endif + +#endif // client only diff --git a/game/shared/econ/econ_wearable.h b/game/shared/econ/econ_wearable.h new file mode 100644 index 0000000..6bdea85 --- /dev/null +++ b/game/shared/econ/econ_wearable.h @@ -0,0 +1,138 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef ECON_WEARABLE_H +#define ECON_WEARABLE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "econ_entity.h" + +enum +{ + MAX_WEARABLES_SENT_FROM_SERVER = +#ifdef LOADOUT_MAX_WEARABLES_COUNT // we actually do want to just check for macro definition here -- undefined means "fall back to whatever default" + LOADOUT_MAX_WEARABLES_COUNT +#else + 8 // hard-coded constant to match old behavior +#endif +}; + +#if defined( CLIENT_DLL ) +#define CEconWearable C_EconWearable +#define CTFWearableItem C_TFWearableItem +#endif + +enum +{ + ITEM_DROP_TYPE_NULL, + ITEM_DROP_TYPE_NONE, + ITEM_DROP_TYPE_DROP, + ITEM_DROP_TYPE_BREAK, +}; + +class CEconWearable : public CEconEntity +{ + DECLARE_CLASS( CEconWearable, CEconEntity ); +public: + DECLARE_NETWORKCLASS(); + DECLARE_DATADESC(); + + CEconWearable(); + + virtual bool IsWearable( void ) const { return true; } + + // Shared + virtual void Spawn( void ); + virtual void GiveTo( CBaseEntity *pOther ); + virtual void RemoveFrom( CBaseEntity *pOther ); + virtual bool CanEquip( CBaseEntity *pOther ) { return true; } + virtual void Equip( CBasePlayer *pOwner ); + virtual void UnEquip( CBasePlayer* pOwner ); + virtual void OnWearerDeath( void ); + virtual int GetDropType( void ); +// virtual bool UpdateBodygroups( CBasePlayer* pOwner, int iState ); + + void SetAlwaysAllow( bool bVal ) { m_bAlwaysAllow = bVal; } + bool AlwaysAllow( void ) { return m_bAlwaysAllow; } + + virtual bool IsViewModelWearable( void ) { return false; } + + // Server +#if defined( GAME_DLL ) +#endif + + // Client +#if defined( CLIENT_DLL ) + virtual ShadowType_t ShadowCastType() OVERRIDE; + virtual bool ShouldDraw(); + virtual bool ShouldDrawWhenPlayerIsDead() { return true; } + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink( void ); + virtual bool ShouldDrawParticleSystems( void ); + virtual RenderGroup_t GetRenderGroup(); +#endif + + virtual int GetSkin( void ); + + // Static + static void UpdateWearableBodyGroups( CBasePlayer *pPlayer ); + +protected: + virtual void InternalSetPlayerDisplayModel( void ); + +private: + bool m_bAlwaysAllow; // Wearable will not be removed by ManageRegularWeapons. Only use this for wearables managed by other items! +}; + +//----------------------------------------------------------------------------- +// Purpose: For backwards compatibility with older demos +//----------------------------------------------------------------------------- +class CTFWearableItem : public CEconWearable +{ + DECLARE_CLASS( CTFWearableItem, CEconWearable ); +public: + DECLARE_NETWORKCLASS(); + DECLARE_DATADESC(); + + CTFWearableItem(); +}; + +#ifdef CLIENT_DLL +// Clientside wearable physics props. Used to have wearables fall off dying players. +class C_EconWearableGib : public CEconEntity +{ + DECLARE_CLASS( C_EconWearableGib, CEconEntity ); +public: + C_EconWearableGib(); + ~C_EconWearableGib(); + + bool Initialize( bool bWillBeParented ); + bool FinishModelInitialization( void ); + + virtual CStudioHdr *OnNewModel( void ); + + virtual bool ValidateEntityAttachedToPlayer( bool &bShouldRetry ); + + virtual void SpawnClientEntity(); + virtual void Spawn(); + virtual void ClientThink( void ); + void StartFadeOut( float fDelay ); + virtual void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ); + virtual CollideType_t GetCollideType( void ) { return ENTITY_SHOULD_RESPOND; } + + bool UpdateThinkState( void ); + +private: + bool m_bParented; + bool m_bDelayedInit; + float m_fDeathTime; // Point at which this object self destructs. + // The default of -1 indicates the object shouldn't destruct. +}; +#endif + +#endif // ECON_WEARABLE_H
\ No newline at end of file diff --git a/game/shared/econ/game_item_schema.h b/game/shared/econ/game_item_schema.h new file mode 100644 index 0000000..1f1c61c --- /dev/null +++ b/game/shared/econ/game_item_schema.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#ifndef GAME_ITEM_SCHEMA_H +#define GAME_ITEM_SCHEMA_H +#ifdef _WIN32 +#pragma once +#endif + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) || defined(TF_GC_DLL) +// TF + class CTFItemSchema; + class CTFItemDefinition; + class CTFItemSystem; + + typedef CTFItemSchema GameItemSchema_t; + typedef CTFItemDefinition GameItemDefinition_t; + typedef CTFItemSystem GameItemSystem_t; + + #include "tf_item_schema.h" +#elif defined( DOTA_CLIENT_DLL ) || defined( DOTA_DLL ) || defined ( DOTA_GC_DLL ) +// DOTA + class CDOTAItemSchema; + class CDOTAItemDefinition; + class CDOTAItemSystem; + + typedef CDOTAItemSchema GameItemSchema_t; + typedef CDOTAItemDefinition GameItemDefinition_t; + typedef CDOTAItemSystem GameItemSystem_t; + + #include "econ/dota_item_schema.h" +#else + // Fallback Case + class CEconItemSchema; + class CEconItemDefinition; + class CEconItemSystem; + + typedef CEconItemSchema GameItemSchema_t; + typedef CEconItemDefinition GameItemDefinition_t; + typedef CEconItemSystem GameItemSystem_t; + + #include "econ_item_schema.h" +#endif + +extern GameItemSchema_t *GetItemSchema(); + +#endif // GAME_ITEM_SYSTEM_H diff --git a/game/shared/econ/ihasattributes.h b/game/shared/econ/ihasattributes.h new file mode 100644 index 0000000..62b63e4 --- /dev/null +++ b/game/shared/econ/ihasattributes.h @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef IHASATTRIBUTES_H +#define IHASATTRIBUTES_H +#ifdef _WIN32 +#pragma once +#endif + +//#include "attribute_manager.h" + +class CAttributeManager; +class CAttributeContainer; +class CBaseEntity; +class CAttributeList; + +// To allow an entity to have attributes, derive it from IHasAttributes and +// contain an CAttributeManager in it. Then: +// - Call InitializeAttributes() before your entity's Spawn() +// - Call AddAttribute() to add attributes to the entity +// - Call all the CAttributeManager hooks at the appropriate times in your entity. +// To get networking of the attributes to work on your entity: +// - Add this to your entity's send table: +// SendPropDataTable( SENDINFO_DT( m_AttributeManager ), &REFERENCE_SEND_TABLE(DT_AttributeManager) ), +// - Call this inside your entity's OnDataChanged(): +// GetAttributeManager()->OnDataChanged( updateType ); + +//----------------------------------------------------------------------------- +// Purpose: Derive from this if your entity wants to contain attributes. +//----------------------------------------------------------------------------- +class IHasAttributes +{ +public: + virtual CAttributeManager *GetAttributeManager( void ) = 0; + virtual CAttributeContainer *GetAttributeContainer( void ) = 0; + virtual CBaseEntity *GetAttributeOwner( void ) = 0; + virtual CAttributeList *GetAttributeList( void ) = 0; + + // Reapply yourself to whoever you should be providing attributes to. + virtual void ReapplyProvision( void ) = 0; +}; + +#endif // IHASATTRIBUTES_H diff --git a/game/shared/econ/ihasowner.h b/game/shared/econ/ihasowner.h new file mode 100644 index 0000000..77ac139 --- /dev/null +++ b/game/shared/econ/ihasowner.h @@ -0,0 +1,24 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef IHASOWNER_H +#define IHASOWNER_H +#ifdef _WIN32 +#pragma once +#endif + +class CBaseEntity; + +//----------------------------------------------------------------------------- +// Purpose: Allows an entity to access its owner regardless of entity type +//----------------------------------------------------------------------------- +class IHasOwner +{ +public: + virtual CBaseEntity *GetOwnerViaInterface( void ) = 0; +}; + +#endif // IHASOWNER_H diff --git a/game/shared/econ/item_selection_criteria.cpp b/game/shared/econ/item_selection_criteria.cpp new file mode 100644 index 0000000..c0f1f05 --- /dev/null +++ b/game/shared/econ/item_selection_criteria.cpp @@ -0,0 +1,614 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: CItemSelectionCriteria, which serves as a criteria for selection +// of a econ item +// +//============================================================================= + + +#include "cbase.h" +#include "item_selection_criteria.h" + +#include "gcsdk/gcsystemmsgs.h" + +#if defined(TF_CLIENT_DLL) || defined(TF_DLL) +#include "tf_gcmessages.h" +#endif + +#include "gcsdk/enumutils.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +// copied from \common\econ_item_view.h +#define AE_USE_SCRIPT_VALUE 9999 // Can't be -1, due to unsigned ints used on the backend + +ENUMSTRINGS_START( EItemCriteriaOperator ) +{ k_EOperator_String_EQ, "string==" }, +{ k_EOperator_String_Not_EQ, "!string==" }, +{ k_EOperator_Float_EQ, "float==" }, +{ k_EOperator_Float_Not_EQ, "!float==" }, +{ k_EOperator_Float_LT, "float<" }, +{ k_EOperator_Float_Not_LT, "!float<" }, +{ k_EOperator_Float_LTE, "float<=" }, +{ k_EOperator_Float_Not_LTE, "!float<=" }, +{ k_EOperator_Float_GT, "float>" }, +{ k_EOperator_Float_Not_GT, "!float>" }, +{ k_EOperator_Float_GTE, "float>=" }, +{ k_EOperator_Float_Not_GTE, "!float>=" }, +{ k_EOperator_Subkey_Contains, "contains" }, +{ k_EOperator_Subkey_Not_Contains, "!contains" }, +ENUMSTRINGS_REVERSE( EItemCriteriaOperator, k_EItemCriteriaOperator_Count ) + +using namespace GCSDK; + +//----------------------------------------------------------------------------- +// Purpose: Copy Constructor +//----------------------------------------------------------------------------- +CItemSelectionCriteria::CItemSelectionCriteria( const CItemSelectionCriteria &that ) +{ + (*this) = that; +} + + +//----------------------------------------------------------------------------- +// Purpose: Operator= +//----------------------------------------------------------------------------- +CItemSelectionCriteria &CItemSelectionCriteria::operator=( const CItemSelectionCriteria &rhs ) +{ + + // Leverage the serialization code we already have for the copy + CSOItemCriteria msgTemp; + rhs.BSerializeToMsg( msgTemp ); + BDeserializeFromMsg( msgTemp ); + + return *this; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CItemSelectionCriteria::~CItemSelectionCriteria( void ) +{ + m_vecConditions.PurgeAndDeleteElements(); +} + +//----------------------------------------------------------------------------- +// Purpose: Look through our conditions and find the first of the specified type, +// and return the value it's looking for. +//----------------------------------------------------------------------------- +const char *CItemSelectionCriteria::GetValueForFirstConditionOfType( EItemCriteriaOperator eType ) const +{ + // Only supporting this for string conditions right now + Assert( eType == k_EOperator_String_EQ || eType == k_EOperator_String_Not_EQ ); + + FOR_EACH_VEC( m_vecConditions, i ) + { + if ( m_vecConditions[i]->GetEOp() == eType ) + return m_vecConditions[i]->GetValue(); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Look through our conditions and find the first of the specified type, +// and return the value it's looking for. +//----------------------------------------------------------------------------- +const char *CItemSelectionCriteria::GetFieldForFirstConditionOfType( EItemCriteriaOperator eType ) const +{ + FOR_EACH_VEC( m_vecConditions, i ) + { + if ( m_vecConditions[i]->GetEOp() == eType ) + return m_vecConditions[i]->GetField(); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize from a KV structure +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::BInitFromKV( KeyValues *pKVCriteria ) +{ + // Read in the base fields + if ( pKVCriteria->FindKey( "level" ) ) + { + SetItemLevel( pKVCriteria->GetInt( "level" ) ); + } + + if ( pKVCriteria->FindKey( "quality" ) ) + { + uint8 nQuality; + if ( !GetItemSchema()->BGetItemQualityFromName( pKVCriteria->GetString( "quality" ), &nQuality ) ) + return false; + + SetQuality( nQuality ); + } + + if ( pKVCriteria->FindKey( "inventoryPos" ) ) + { + SetInitialInventory( pKVCriteria->GetInt( "inventoryPos" ) ); + } + + if ( pKVCriteria->FindKey( "quantity" ) ) + { + SetInitialQuantity( pKVCriteria->GetInt( "quantity" ) ); + } + + if ( pKVCriteria->FindKey( "ignore_enabled" ) ) + { + SetIgnoreEnabledFlag( pKVCriteria->GetBool( "ignore_enabled" ) ); + } + + if ( pKVCriteria->FindKey( "tags" ) ) + { + SetTags( pKVCriteria->GetString( "tags" ) ); + } + + KeyValues *pKVConditions = pKVCriteria->FindKey( "conditions", true ); + + FOR_EACH_TRUE_SUBKEY( pKVConditions, pKVElement ) + { + // Check for required fields + if ( !pKVElement->FindKey( "field" ) || + !pKVElement->FindKey( "operator" ) || + !pKVElement->FindKey( "value" ) ) + return false; + + const char *pszField = pKVElement->GetString( "field" ); + bool bRequired = pKVElement->GetBool( "required" ); + const char *pszValue = pKVElement->GetString( "value" ); + + // Get the operator + const char *pszOperator = pKVElement->GetString( "operator" ); + EItemCriteriaOperator eOp = EItemCriteriaOperatorFromName( pszOperator ); + if ( k_EItemCriteriaOperator_Count == eOp ) + return false; + + BAddCondition( pszField, eOp, pszValue, bRequired ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItemSelectionCriteria::SetTags( const char *pszTags ) +{ + m_vecTags.Purge(); + + m_strTags = pszTags; + CSplitString splitString( pszTags, " " ); + for ( int i=0; i<splitString.Count(); ++i ) + { + econ_tag_handle_t tagHandle = GetItemSchema()->GetHandleForTag( splitString[i] ); + if ( !m_vecTags.HasElement( tagHandle ) ) + { + m_vecTags.AddToTail( tagHandle ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::BAddCondition( CItemSelectionCriteria::ICondition *pCondition ) +{ + CPlainAutoPtr<ICondition> pConditionPtr( pCondition ); + + // Check for condition limit + if ( UCHAR_MAX == GetConditionsCount() ) + { + AssertMsg( false, "Too many conditions on a a CItemSelectionCriteria. Max: 255" ); + return false; + } + + m_vecConditions.AddToTail( pConditionPtr.Detach() ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a condition to the selection criteria +// Input: pszField - Field to evaluate on +// eOp - Operator to apply to the value of the field +// flValue - The value to compare. +// bRequired - When true, causes BEvauluate to fail if pszField doesn't +// exist in the KV being checked. +// Output: True if the condition was added, false otherwise +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::BAddCondition( const char *pszField, EItemCriteriaOperator eOp, float flValue, bool bRequired ) +{ + // Enforce maximum string lengths + if ( Q_strlen( pszField ) >= k_cchCreateItemLen ) + return false; + + // Create the appropriate condition for the operator + switch ( eOp ) + { + case k_EOperator_Float_EQ: + case k_EOperator_Float_Not_EQ: + case k_EOperator_Float_LT: + case k_EOperator_Float_Not_LT: + case k_EOperator_Float_LTE: + case k_EOperator_Float_Not_LTE: + case k_EOperator_Float_GT: + case k_EOperator_Float_Not_GT: + case k_EOperator_Float_GTE: + case k_EOperator_Float_Not_GTE: + return BAddCondition( new CFloatCondition( pszField, eOp, flValue, bRequired ) ); + + default: + AssertMsg1( false, "Bad operator (%d) passed to BAddCondition. Float based operator required for this overload.", eOp ); + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a condition to the selection criteria +// Input: pszField - Field to evaluate on +// eOp - Operator to apply to the value of the field +// pszValue - The value to compare. +// bRequired - When true, causes BEvauluate to fail if pszField doesn't +// exist in the KV being checked. +// Output: True if the condition was added, false otherwise +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::BAddCondition( const char *pszField, EItemCriteriaOperator eOp, const char * pszValue, bool bRequired ) +{ + // Enforce maximum string lengths + if ( Q_strlen( pszField ) >= k_cchCreateItemLen || Q_strlen( pszValue ) >= k_cchCreateItemLen ) + return false; + + // Create the appropriate condition for the operator + switch ( eOp ) + { + case k_EOperator_String_EQ: + case k_EOperator_String_Not_EQ: + return BAddCondition( new CStringCondition( pszField, eOp, pszValue, bRequired ) ); + return true; + + case k_EOperator_Subkey_Contains: + case k_EOperator_Subkey_Not_Contains: + return BAddCondition( new CSetCondition( pszField, eOp, pszValue, bRequired ) ); + return true; + + default: + // Try the float operators + return BAddCondition( pszField, eOp, Q_atof( pszValue ), bRequired ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Checks if a given item matches the item selection criteria +// Input: itemDef - The item definition to evaluate against +// Output: True is the item passes the filter, false otherwise +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::BEvaluate( const CEconItemDefinition* pItemDef ) const +{ + // Disabled items never match + if ( !m_bIgnoreEnabledFlag && !pItemDef->BEnabled() ) + return false; + + // Filter against level + if ( BItemLevelSet() && (GetItemLevel() != AE_USE_SCRIPT_VALUE) && + ( GetItemLevel() < pItemDef->GetMinLevel() || GetItemLevel() > pItemDef->GetMaxLevel() ) ) + return false; + + // Filter against quality + if ( BQualitySet() && (GetQuality() != AE_USE_SCRIPT_VALUE) ) + { + if ( GetQuality() != pItemDef->GetQuality() ) + { + // Filter out item defs that have a non-any quality if we have a non-matching & non-any quality criteria + if ( k_unItemQuality_Any != GetQuality() && k_unItemQuality_Any != pItemDef->GetQuality() ) + return false; + } + } + + // Filter against the additional conditions + FOR_EACH_VEC( m_vecConditions, i ) + { + if ( !m_vecConditions[i]->BItemDefinitionPassesCriteria( pItemDef ) ) + return false; + } + + // Check if we have "any" tags + if ( m_vecTags.Count() > 0 ) + { + bool bHasTag = false; + FOR_EACH_VEC( m_vecTags, i ) + { + if ( pItemDef->HasEconTag( m_vecTags[i] ) ) + { + bHasTag = true; + break; + } + } + + if ( !bHasTag ) + { + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Determines if the item matches this condition of the criteria +// Input: pKVItem - Pointer to the raw KeyValues definition of the item +// Output: True is the item matches, false otherwise +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::CCondition::BEvaluate( KeyValues *pKVItem ) const +{ + KeyValues *pKVField = pKVItem->FindKey( m_sField.String() ); + + // Treat an empty string as a missing field as well. + bool bIsEmptyString = false; + if ( m_EOp == k_EOperator_String_EQ || m_EOp == k_EOperator_String_Not_EQ ) + { + const char *pszItemVal = pKVField ? pKVField->GetString() : NULL; + bIsEmptyString = ( pszItemVal == NULL || pszItemVal[0] == '\0' ); + } + + // Deal with missing fields + if ( NULL == pKVField || bIsEmptyString ) + { + if ( m_bRequired ) + return false; + else + return true; + } + + // Run the operator specific check + bool bRet = BInternalEvaluate( pKVItem ); + + // If this is a "not" operator, reverse the result + if ( m_EOp & k_EOperator_Not ) + return !bRet; + else + return bRet; +} + + +//----------------------------------------------------------------------------- +// Purpose: Runs the operator specific check for this condition +// Input: pKVItem - Pointer to the raw KeyValues definition of the item +// Output: True is the item matches, false otherwise +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::CStringCondition::BInternalEvaluate( KeyValues *pKVItem ) const +{ + Assert( k_EOperator_String_EQ == m_EOp || k_EOperator_String_Not_EQ == m_EOp ); + if( !( k_EOperator_String_EQ == m_EOp || k_EOperator_String_Not_EQ == m_EOp ) ) + return false; + + const char *pszItemVal = pKVItem->GetString( m_sField.String() ); + return ( 0 == Q_stricmp( m_sValue.String(), pszItemVal ) ); +} + +bool CItemSelectionCriteria::CStringCondition::BSerializeToMsg( CSOItemCriteriaCondition & msg ) const +{ + CCondition::BSerializeToMsg( msg ); + msg.set_string_value( m_sValue.Get() ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Runs the operator specific check for this condition +// Input: pKVItem - Pointer to the raw KeyValues definition of the item +// Output: True is the item matches, false otherwise +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::CSetCondition::BInternalEvaluate( KeyValues *pKVItem ) const +{ + Assert( k_EOperator_Subkey_Contains == m_EOp || k_EOperator_Subkey_Not_Contains == m_EOp ); + if( !( k_EOperator_Subkey_Contains == m_EOp || k_EOperator_Subkey_Not_Contains == m_EOp ) ) + return false; + + return ( NULL != pKVItem->FindKey( m_sField.String() )->FindKey( m_sValue.String() ) ); +} + +bool CItemSelectionCriteria::CSetCondition::BSerializeToMsg( CSOItemCriteriaCondition & msg ) const +{ + CCondition::BSerializeToMsg( msg ); + msg.set_string_value( m_sValue.Get() ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Runs the operator specific check for this condition +// Input: pKVItem - Pointer to the raw KeyValues definition of the item +// Output: True is the item matches, false otherwise +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::CFloatCondition::BInternalEvaluate( KeyValues *pKVItem ) const +{ + float itemValue = pKVItem->GetFloat( m_sField.String() ); + + switch ( m_EOp ) + { + case k_EOperator_Float_EQ: + case k_EOperator_Float_Not_EQ: + return ( itemValue == m_flValue ); + + case k_EOperator_Float_LT: + case k_EOperator_Float_Not_LT: + return ( itemValue < m_flValue ); + + case k_EOperator_Float_LTE: + case k_EOperator_Float_Not_LTE: + return ( itemValue <= m_flValue ); + + case k_EOperator_Float_GT: + case k_EOperator_Float_Not_GT: + return ( itemValue > m_flValue ); + + case k_EOperator_Float_GTE: + case k_EOperator_Float_Not_GTE: + return ( itemValue >= m_flValue ); + + default: + AssertMsg1( false, "Unknown operator: %d", m_EOp ); + return false; + } +} + +bool CItemSelectionCriteria::CFloatCondition::BSerializeToMsg( CSOItemCriteriaCondition & msg ) const +{ + CCondition::BSerializeToMsg( msg ); + msg.set_float_value( m_flValue ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Serialize the item selection criteria to the given message +// Input: msg - The message to serialize to. +// Output: True if the operation was successful, false otherwise. +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::BSerializeToMsg( CSOItemCriteria & msg ) const +{ + msg.set_item_level( m_unItemLevel ); + msg.set_item_quality( m_nItemQuality ); + msg.set_item_level_set( m_bItemLevelSet ); + msg.set_item_quality_set( m_bQualitySet ); + msg.set_initial_inventory( m_unInitialInventory ); + msg.set_initial_quantity( m_unInitialQuantity ); + msg.set_ignore_enabled_flag( m_bIgnoreEnabledFlag ); + msg.set_tags( m_strTags ); + + FOR_EACH_VEC( m_vecConditions, i ) + { + CSOItemCriteriaCondition *pConditionMsg = msg.add_conditions(); + m_vecConditions[i]->BSerializeToMsg( *pConditionMsg ); + } + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Deserializes the item selection criteria from the given message +// Input: msg - The message to deserialize from. +// Output: True if the operation was successful, false otherwise. +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::BDeserializeFromMsg( const CSOItemCriteria & msg ) +{ + m_unItemLevel = msg.item_level(); + m_nItemQuality = msg.item_quality(); + m_bItemLevelSet = msg.item_level_set(); + m_bQualitySet = msg.item_quality_set(); + m_unInitialInventory = msg.initial_inventory(); + m_unInitialQuantity = msg.initial_quantity(); + m_bIgnoreEnabledFlag = msg.ignore_enabled_flag(); + + SetTags( msg.tags().c_str() ); + + uint32 unCount = msg.conditions_size(); + m_vecConditions.EnsureCapacity( unCount ); + + for ( uint32 i = 0; i < unCount; i++ ) + { + const CSOItemCriteriaCondition & cond = msg.conditions( i ); + EItemCriteriaOperator eOp = (EItemCriteriaOperator)cond.op(); + bool bRequired = cond.required(); + + // Read the value specific to the condition and add the condition + switch ( eOp ) + { + case k_EOperator_Float_EQ: + case k_EOperator_Float_Not_EQ: + case k_EOperator_Float_LT: + case k_EOperator_Float_Not_LT: + case k_EOperator_Float_LTE: + case k_EOperator_Float_Not_LTE: + case k_EOperator_Float_GT: + case k_EOperator_Float_Not_GT: + case k_EOperator_Float_GTE: + case k_EOperator_Float_Not_GTE: + { + if ( !BAddCondition( cond.field().c_str(), eOp, cond.float_value(), bRequired ) ) return false; + break; + } + + case k_EOperator_String_EQ: + case k_EOperator_String_Not_EQ: + case k_EOperator_Subkey_Contains: + case k_EOperator_Subkey_Not_Contains: + { + if ( !BAddCondition( cond.field().c_str(), eOp, cond.string_value().c_str(), bRequired ) ) return false; + break; + } + + default: + AssertMsg1( false, "Unknown operator (%d) read.", eOp ); + return false; + } + } + + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Serializes a condition to a message. +// Input: msg - The message to serialize to. +// Output: True if the operation was successful, false otherwise. +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::CCondition::BSerializeToMsg( CSOItemCriteriaCondition & msg ) const +{ + msg.set_op( m_EOp ); + msg.set_field( m_sField.String() ); + msg.set_required( m_bRequired ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CItemSelectionCriteria::CCondition::BItemDefinitionPassesCriteria( const CEconItemDefinition *pItemDef ) const +{ + return BEvaluate( pItemDef->GetRawDefinition() ); +} + +// Validation +#ifdef DBGFLAG_VALIDATE + +//----------------------------------------------------------------------------- +// Purpose: Run a global validation pass on all of our data structures and memory +// allocations. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CItemSelectionCriteria::Validate( CValidator &validator, const char *pchName ) +{ + VALIDATE_SCOPE(); + ValidateObj( m_vecConditions ); + FOR_EACH_VEC( m_vecConditions, i ) + { + ValidatePtr( m_vecConditions[i] ); + } +} + +void CItemSelectionCriteria::CCondition::Validate( CValidator &validator, const char *pchName ) +{ + ValidateObj( m_sField ); +} + +void CItemSelectionCriteria::CStringCondition::Validate( CValidator &validator, const char *pchName ) +{ + CCondition::Validate( validator, pchName ); + ValidateObj( m_sValue ); +} + +void CItemSelectionCriteria::CSetCondition::Validate( CValidator &validator, const char *pchName ) +{ + CCondition::Validate( validator, pchName ); + ValidateObj( m_sValue ); +} + +#endif diff --git a/game/shared/econ/item_selection_criteria.h b/game/shared/econ/item_selection_criteria.h new file mode 100644 index 0000000..a4da52d --- /dev/null +++ b/game/shared/econ/item_selection_criteria.h @@ -0,0 +1,290 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: CItemSelectionCriteria, which serves as a criteria for selection +// of a econ item +// +//============================================================================= + +#ifndef ITEM_SELECTION_CRITERIA_H +#define ITEM_SELECTION_CRITERIA_H +#ifdef _WIN32 +#pragma once +#endif + +// Maximum string length in item create APIs +const int k_cchCreateItemLen = 64; + +// Operators for BAddNewItemCriteria +enum EItemCriteriaOperator +{ + k_EOperator_String_EQ = 0, // Field is string equal to value + k_EOperator_Not = 1, // Logical not + k_EOperator_String_Not_EQ = 1, // Field is not string equal to value + k_EOperator_Float_EQ = 2, // Field as a float is equal to value + k_EOperator_Float_Not_EQ = 3, // Field as a float is not equal to value + k_EOperator_Float_LT = 4, // Field as a float is less than value + k_EOperator_Float_Not_LT = 5, // Field as a float is not less than value + k_EOperator_Float_LTE = 6, // Field as a float is less than or equal value + k_EOperator_Float_Not_LTE = 7, // Field as a float is not less than or equal value + k_EOperator_Float_GT = 8, // Field as a float is greater than value + k_EOperator_Float_Not_GT = 9, // Field as a float is not greater than value + k_EOperator_Float_GTE = 10, // Field as a float is greater than or equal value + k_EOperator_Float_Not_GTE = 11, // Field as a float is not greater than or equal value + k_EOperator_Subkey_Contains = 12, // Field contains value as a subkey + k_EOperator_Subkey_Not_Contains = 13, // Field does not contain value as a subkey + + // Must be last + k_EItemCriteriaOperator_Count = 14, +}; + + +EItemCriteriaOperator EItemCriteriaOperatorFromName( const char *pch ); +const char *PchNameFromEItemCriteriaOperator( int eItemCriteriaOperator ); + +class CEconItemSchema; +class CEconItemDefinition; +class CSOItemCriteria; +class CSOItemCriteriaCondition; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// CItemSelectionCriteria +// A class that contains all the conditions a server needs to specify what +// kind of random item they wish to generate. +//----------------------------------------------------------------------------- +class CItemSelectionCriteria +{ +public: + // Constructors and destructor + CItemSelectionCriteria() : + m_bItemLevelSet( false ), + m_unItemLevel( 0 ), + m_bQualitySet( false ), + m_nItemQuality( k_unItemQuality_Any ), + m_unInitialInventory( 0 ), + m_bInitialQuantitySet( false ), + m_unInitialQuantity( 1 ), + m_bIgnoreEnabledFlag( false ) + { + } + + CItemSelectionCriteria( const CItemSelectionCriteria &that ); + CItemSelectionCriteria &operator=( const CItemSelectionCriteria& rhs ); + ~CItemSelectionCriteria(); + + // Accessors and Settors + bool BItemLevelSet( void ) const { return m_bItemLevelSet; } + uint32 GetItemLevel( void ) const { Assert( m_bItemLevelSet ); return m_unItemLevel; } + void SetItemLevel( uint32 unLevel ) { m_unItemLevel = unLevel; m_bItemLevelSet = true; } + bool BQualitySet( void ) const { return m_bQualitySet; } + int32 GetQuality( void ) const { Assert( m_bQualitySet ); return m_nItemQuality; } + void SetQuality( int32 nQuality ) { m_nItemQuality = nQuality; m_bQualitySet = true; } + uint32 GetInitialInventory( void ) const { return m_unInitialInventory; } + void SetInitialInventory( uint32 unInventory ) { m_unInitialInventory = unInventory; } + bool BInitialQuantitySet( void ) const { return m_bQualitySet; } + uint32 GetInitialQuantity( void ) const { Assert( m_bQualitySet ); return m_unInitialQuantity; } + void SetInitialQuantity( uint32 unQuantity ) { m_unInitialQuantity = unQuantity; m_bInitialQuantitySet = true; } + void SetIgnoreEnabledFlag( bool bIgnore ) { m_bIgnoreEnabledFlag = bIgnore; } + + // Tags + void SetTags( const char *pszTags ); + + + // Add conditions to the criteria + class ICondition + { + public: + virtual ~ICondition() { } + + virtual bool BItemDefinitionPassesCriteria( const CEconItemDefinition *pItemDef ) const = 0; + + virtual EItemCriteriaOperator GetEOp() const { return k_EItemCriteriaOperator_Count; } + virtual const char *GetField() const { return ""; } + virtual const char *GetValue() const { return ""; } + + virtual bool BSerializeToMsg( CSOItemCriteriaCondition & msg ) const { Assert( !"BSerializeToMsg() called on for unimplementing ICondition!" ); return false; } + }; + + bool BAddCondition( const char *pszField, EItemCriteriaOperator eOp, float flValue, bool bRequired ); + bool BAddCondition( const char *pszField, EItemCriteriaOperator eOp, const char * pszValue, bool bRequired ); + bool BAddCondition( ICondition *pCondition ); + int GetConditionsCount() { return m_vecConditions.Count(); } + const char *GetValueForFirstConditionOfType( EItemCriteriaOperator eType ) const; + const char *GetFieldForFirstConditionOfType( EItemCriteriaOperator eType ) const; + + // Alternate ways of initializing + bool BInitFromKV( KeyValues *pKVCriteria ); + + // Serializes the criteria to and from messages + bool BSerializeToMsg( CSOItemCriteria & msg ) const; + bool BDeserializeFromMsg( const CSOItemCriteria & msg ); + + // Evaluates an item definition against this criteria. Returns true if + // the definition passes the filter + bool BEvaluate( const CEconItemDefinition* pItemDef ) const; + + // Validation +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ); +#endif + +private: + //----------------------------------------------------------------------------- + // CItemSelectionCriteria::CCondition + // Represents one condition of the criteria + //----------------------------------------------------------------------------- + class CCondition : public ICondition + { + public: + CCondition( const char *pszField, EItemCriteriaOperator eOp, bool bRequired ) + : m_sField( pszField ), m_EOp( eOp ), m_bRequired( bRequired ) + { + } + + // ICondition interface. + virtual bool BItemDefinitionPassesCriteria( const CEconItemDefinition *pItemDef ) const OVERRIDE; + + // Serializes the condition to the message + virtual bool BSerializeToMsg( CSOItemCriteriaCondition & msg ) const; + + // Validation +#ifdef DBGFLAG_VALIDATE + virtual void Validate( CValidator &validator, const char *pchName ); +#endif + + EItemCriteriaOperator GetEOp( void ) const OVERRIDE { return m_EOp; } + virtual const char *GetField( void ) const OVERRIDE { return m_sField.Get(); } + virtual const char *GetValue( void ) const OVERRIDE { Assert(0); return NULL; } + + private: + // Returns if the given KeyValues block passes this condition + // Performs common checks and calls BInternalEvaluate + bool BEvaluate( KeyValues *pKVItem ) const; + + protected: + // Returns true if applying the element's operator on m_sField of + // pKVItem returns true. This is only called if m_pszField exists in pKVItem + virtual bool BInternalEvaluate( KeyValues *pKVItem ) const = 0; + + // The field of the raw KeyValue form of the item definition to check + CUtlString m_sField; + // The operator this clause uses + EItemCriteriaOperator m_EOp; + // When true, BEvaluate returns false if m_sField does not exist in pKVItem + bool m_bRequired; + }; + + + //----------------------------------------------------------------------------- + // CItemSelectionCriteria::CStringCondition + // CCondition that handles the string-based operators + //----------------------------------------------------------------------------- + class CStringCondition : public CCondition + { + public: + CStringCondition( const char *pszField, EItemCriteriaOperator eOp, const char *pszValue, bool bRequired ) + : CCondition( pszField, eOp, bRequired ), m_sValue( pszValue ) + { + } + + virtual ~CStringCondition( ) { } + + virtual const char *GetValue( void ) const OVERRIDE { return m_sValue.Get(); } + + // Validation +#ifdef DBGFLAG_VALIDATE + virtual void Validate( CValidator &validator, const char *pchName ); +#endif + + protected: + virtual bool BInternalEvaluate( KeyValues *pKVItem ) const; + virtual bool BSerializeToMsg( CSOItemCriteriaCondition & msg ) const; + + // The value to check against + CUtlString m_sValue; + }; + + + //----------------------------------------------------------------------------- + // CItemSelectionCriteria::CFloatCondition + // CCondition that handles the float-based operators + //----------------------------------------------------------------------------- + class CFloatCondition : public CCondition + { + public: + CFloatCondition( const char *pszField, EItemCriteriaOperator eOp, float flValue, bool bRequired ) + : CCondition( pszField, eOp, bRequired ), m_flValue( flValue ) + { + } + + virtual ~CFloatCondition( ) { } + + protected: + virtual bool BInternalEvaluate( KeyValues *pKVItem ) const; + virtual bool BSerializeToMsg( CSOItemCriteriaCondition & msg ) const; + + // The value to check against + float m_flValue; + }; + + + //----------------------------------------------------------------------------- + // CItemSelectionCriteria::CSetCondition + // CCondition that handles subkey checks + //----------------------------------------------------------------------------- + class CSetCondition : public CCondition + { + public: + CSetCondition( const char *pszField, EItemCriteriaOperator eOp, const char *pszValue, bool bRequired ) + : CCondition( pszField, eOp, bRequired ), m_sValue( pszValue ) + { + } + + virtual ~CSetCondition( ) { } + + // Validation +#ifdef DBGFLAG_VALIDATE + virtual void Validate( CValidator &validator, const char *pchName ); +#endif + + protected: + virtual bool BInternalEvaluate( KeyValues *pKVItem ) const; + + virtual bool BSerializeToMsg( CSOItemCriteriaCondition & msg ) const; + + // The subkey to look for + CUtlString m_sValue; + }; + + // True if item level is specified in this criteria + bool m_bItemLevelSet; + // The level of the item to generate + uint32 m_unItemLevel; + // True if quality is specified in this criteria + bool m_bQualitySet; + // The quality of the item to generate + int32 m_nItemQuality; + // The initial inventory token of the item + uint32 m_unInitialInventory; + // True if initial quantity is specified in this criteria. + bool m_bInitialQuantitySet; + // The initial quantity of the item + uint32 m_unInitialQuantity; + // Enforced explicit quality matching + bool m_bForcedQualityMatch; + // Ignoring enabled flag (used when crafting) + bool m_bIgnoreEnabledFlag; + + // A list of tags + CUtlString m_strTags; + CUtlVector<econ_tag_handle_t> m_vecTags; + + // A list of the conditions + CUtlVector<ICondition *> m_vecConditions; +}; + + +#endif //ITEM_SELECTION_CRITERIA_H diff --git a/game/shared/econ/localization_provider.cpp b/game/shared/econ/localization_provider.cpp new file mode 100644 index 0000000..78e5e2c --- /dev/null +++ b/game/shared/econ/localization_provider.cpp @@ -0,0 +1,199 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#include "cbase.h" +#include "localization_provider.h" + +enum { kScratchBufferSize = 1024 }; + + + +// ---------------------------------------------------------------------------- +// Find a localized string, but return something safe if the key is null or the localized +// string is missing. +// ---------------------------------------------------------------------------- +locchar_t* CLocalizationProvider::FindSafe( const char* pchKey ) const +{ + if ( pchKey ) + { + locchar_t* wszLocalized = Find( pchKey ); + if ( !wszLocalized ) + { +#ifdef STAGING_ONLY + return const_cast< locchar_t* >(LOCCHAR("<NULL LOC STRING>")); // Super janky cast alert! This method should really return a const locchar_t* but making that change breaks all the callsites...fix later. +#else + return const_cast<locchar_t*>(LOCCHAR("")); +#endif + } + else + { + return wszLocalized; + } + } + else + { +#ifdef STAGING_ONLY + return const_cast<locchar_t*>(LOCCHAR("<NULL LOC KEY>")); +#else + return const_cast<locchar_t*>(LOCCHAR("")); +#endif + } +} + +#ifdef GC +#include "gcsdk/gcbase.h" + +// GC Localization implementation + +static CGCLocalizationProvider *GGCLocalizationProvider() +{ + static CGCLocalizationProvider *g_pGCLocalizationProvider = NULL; + if ( !g_pGCLocalizationProvider ) + g_pGCLocalizationProvider = new CGCLocalizationProvider( GGCGameBase() ); + return g_pGCLocalizationProvider; +} + +CLocalizationProvider *GLocalizationProvider() +{ + AssertMsg( false, "Using global localization provider in GC - All strings will be in English. For proper localization, CLocalizationProvider instance should be created and passed in." ); + return GGCLocalizationProvider(); +} + +locchar_t *CGCLocalizationProvider::Find( const char *pchKey ) const +{ + // we emulate VGUI's behavior of returning an empty string for keys that are not found + return (locchar_t*)m_pGC->LocalizeToken( pchKey, m_eLang, false ); +} + +bool CGCLocalizationProvider::BEnsureCleanUTF8Truncation( char *unicodeOutput ) +{ + int nStringLength = V_strlen( unicodeOutput ); + + // make sure we're not in the middle of a multibyte character + int iPos = nStringLength - 1; + char c = unicodeOutput[iPos]; + if ( (c & 0x80) != 0 ) + { + // not an ascii char, so do some multibyte char checking + int cBytes = 0; + // count up all continuation bytes + while ( (c & 0xC0) == 0x80 && iPos > 0 ) // first two bits are 10xxxx, continuation + { + cBytes++; + c = unicodeOutput[--iPos]; + } + + // make sure we had the expected number of continuation bytes for the last + // multibyte lead character + bool bTruncateOK = true; + if ( ( c & 0xF8 ) == 0xF0 ) // first 5 bits are 11110, should be 3 following bytes + bTruncateOK = ( cBytes == 3 ); + else if ( ( c & 0xF0 ) == 0xE0 ) // first 4 bits are 1110, should be 2 following bytes + bTruncateOK = ( cBytes == 2 ); + else if ( ( c & 0xE0 ) == 0xC0 ) // first 3 bits are 110, should be 1 following byte + bTruncateOK = ( cBytes == 1 ); + + // if we truncated in the middle of a multi-byte char, move the end point back to this character + if ( !bTruncateOK ) + unicodeOutput[iPos] = '\0'; + + return !bTruncateOK; + } + + return false; +} + +void CGCLocalizationProvider::ConvertLoccharToANSI( const locchar_t *loc_In, CUtlConstString *out_ansi ) const +{ + *out_ansi = loc_In; +} + +void CGCLocalizationProvider::ConvertLoccharToUnicode( const locchar_t *loc_In, CUtlConstWideString *out_unicode ) const +{ + wchar_t utf16_Scratch[kScratchBufferSize]; + + V_UTF8ToUnicode( loc_In, utf16_Scratch, kScratchBufferSize ); + *out_unicode = utf16_Scratch; +} + +void CGCLocalizationProvider::ConvertUTF8ToLocchar( const char *utf8_In, CUtlConstStringBase<locchar_t> *out_loc ) const +{ + *out_loc = utf8_In; +} + +int CGCLocalizationProvider::ConvertLoccharToANSI( const locchar_t *loc, char *ansi, int ansiBufferSize ) const +{ + Q_strncpy( ansi, loc, ansiBufferSize ); + return 0; +} + +int CGCLocalizationProvider::ConvertLoccharToUnicode( const locchar_t *loc, wchar_t *unicode, int unicodeBufferSize ) const +{ + return V_UTF8ToUnicode( loc, unicode, unicodeBufferSize ); +} + + +void CGCLocalizationProvider::ConvertUTF8ToLocchar( const char *utf8, locchar_t *locchar, int loccharBufferSize ) const +{ + Q_strncpy( locchar, utf8, loccharBufferSize ); +} + + +#else + +CLocalizationProvider *GLocalizationProvider() +{ + static CVGUILocalizationProvider g_VGUILocalizationProvider; + return &g_VGUILocalizationProvider; +} + +// vgui localization implementation + +CVGUILocalizationProvider::CVGUILocalizationProvider() +{ + +} + +locchar_t *CVGUILocalizationProvider::Find( const char *pchKey ) const +{ + return (locchar_t*)g_pVGuiLocalize->Find( pchKey ); +} + +void CVGUILocalizationProvider::ConvertLoccharToANSI( const locchar_t *loc_In, CUtlConstString *out_ansi ) const +{ + char ansi_Scratch[kScratchBufferSize]; + + g_pVGuiLocalize->ConvertUnicodeToANSI( loc_In, ansi_Scratch, kScratchBufferSize ); + *out_ansi = ansi_Scratch; +} + +void CVGUILocalizationProvider::ConvertLoccharToUnicode( const locchar_t *loc_In, CUtlConstWideString *out_unicode ) const +{ + *out_unicode = loc_In; +} + +void CVGUILocalizationProvider::ConvertUTF8ToLocchar( const char *utf8_In, CUtlConstStringBase<locchar_t> *out_loc ) const +{ + locchar_t loc_Scratch[kScratchBufferSize]; + + V_UTF8ToUnicode( utf8_In, loc_Scratch, kScratchBufferSize ); + *out_loc = loc_Scratch; +} + +void CVGUILocalizationProvider::ConvertUTF8ToLocchar( const char *utf8, locchar_t *locchar, int loccharBufferSize ) const +{ + V_UTF8ToUnicode( utf8, locchar, loccharBufferSize ); +} + +int CVGUILocalizationProvider::ConvertLoccharToANSI( const locchar_t *loc, char *ansi, int ansiBufferSize ) const +{ + return g_pVGuiLocalize->ConvertUnicodeToANSI( loc, ansi, ansiBufferSize ); +} + +int CVGUILocalizationProvider::ConvertLoccharToUnicode( const locchar_t *loc, wchar_t *unicode, int unicodeBufferSize ) const +{ + Q_wcsncpy( unicode, loc, unicodeBufferSize ); + return 0; +} + + +#endif
\ No newline at end of file diff --git a/game/shared/econ/localization_provider.h b/game/shared/econ/localization_provider.h new file mode 100644 index 0000000..0ee2fdd --- /dev/null +++ b/game/shared/econ/localization_provider.h @@ -0,0 +1,103 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: provide a layer of abstraction between GC and vgui localization systems +// +//============================================================================= + +#ifndef LOCALIZATION_PROVIDER_H +#define LOCALIZATION_PROVIDER_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "language.h" +#include "ilocalize.h" + + +// interface matches a subset of VGUI functions +class CLocalizationProvider +{ +public: + virtual locchar_t *Find( const char *pchKey ) const = 0; + locchar_t* FindSafe( const char* pchKey ) const; + + // new interface + virtual void ConvertLoccharToANSI ( const locchar_t *loc_In, CUtlConstString *out_ansi ) const = 0; + virtual void ConvertLoccharToUnicode( const locchar_t *loc_In, CUtlConstWideString *out_unicode ) const = 0; + virtual void ConvertUTF8ToLocchar ( const char *utf8_In, CUtlConstStringBase<locchar_t> *out_loc ) const = 0; + + // old C-style interface + virtual int ConvertLoccharToANSI( const locchar_t *loc, OUT_Z_BYTECAP(ansiBufferSize) char *ansi, int ansiBufferSize ) const = 0; + virtual int ConvertLoccharToUnicode( const locchar_t *loc, OUT_Z_BYTECAP(unicodeBufferSize) wchar_t *unicode, int unicodeBufferSize ) const = 0; + + virtual void ConvertUTF8ToLocchar( const char *utf8, OUT_Z_BYTECAP(loccharBufferSize) locchar_t *locchar, int loccharBufferSize ) const = 0; + + virtual ELanguage GetELang() const = 0; +}; +CLocalizationProvider *GLocalizationProvider(); + +#ifdef GC +// GC localization is handled by the GC itself +class CGCLocalizationProvider : public CLocalizationProvider +{ +public: + CGCLocalizationProvider( CGCGameBase *pGC, ELanguage eLang = k_Lang_English ) + { + m_pGC = pGC; + m_eLang = eLang; + } + + static bool BEnsureCleanUTF8Truncation( char *unicodeOutput ); + + virtual locchar_t *Find( const char *pchKey ) const; + + // new interface + virtual void ConvertLoccharToANSI ( const locchar_t *loc_In, CUtlConstString *out_ansi ) const; + virtual void ConvertLoccharToUnicode( const locchar_t *loc_In, CUtlConstWideString *out_unicode ) const; + virtual void ConvertUTF8ToLocchar ( const char *utf8_In, CUtlConstStringBase<locchar_t> *out_loc ) const; + + // old C-style interface + virtual int ConvertLoccharToANSI( const locchar_t *loc, char *ansi, int ansiBufferSize ) const; + virtual int ConvertLoccharToUnicode( const locchar_t *loc, wchar_t *unicode, int unicodeBufferSize ) const; + + virtual void ConvertUTF8ToLocchar( const char *utf8, locchar_t *locchar, int loccharBufferSize ) const; + + virtual ELanguage GetELang() const { return m_eLang; } + +private: + CGCGameBase *m_pGC; + ELanguage m_eLang; +}; + + +#else + +#include "vgui/ILocalize.h" +extern vgui::ILocalize *g_pVGuiLocalize; + +// Game localization is handled by vgui +class CVGUILocalizationProvider : public CLocalizationProvider +{ +public: + CVGUILocalizationProvider(); + + virtual locchar_t *Find( const char *pchKey ) const; + + // new interface + virtual void ConvertLoccharToANSI ( const locchar_t *loc_In, CUtlConstString *out_ansi ) const; + virtual void ConvertLoccharToUnicode( const locchar_t *loc_In, CUtlConstWideString *out_unicode ) const; + virtual void ConvertUTF8ToLocchar ( const char *utf8_In, CUtlConstStringBase<locchar_t> *out_loc ) const; + + // old C-style interface + virtual int ConvertLoccharToANSI( const locchar_t *loc, char *ansi, int ansiBufferSize ) const; + virtual int ConvertLoccharToUnicode( const locchar_t *loc, wchar_t *unicode, int unicodeBufferSize ) const; + + virtual void ConvertUTF8ToLocchar( const char *utf8, locchar_t *locchar, int loccharBufferSize ) const; + + virtual ELanguage GetELang() const { return k_Lang_None; } + +}; +#endif + +#endif // LOCALIZATION_PROVIDER_H
\ No newline at end of file |