//========= 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 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; im_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( 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( 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( 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 *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 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; ipszControlPoints ); ++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; iGetModifiedBodyGroup( 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() { }