//========= 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; iGetModifiedBodyGroup( 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; iGetNumWearables(); ++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( 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