//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "cbase.h" #include "c_tf_projectile_arrow.h" #include "particles_new.h" #include "SpriteTrail.h" #include "c_tf_player.h" #include "collisionutils.h" #include "util_shared.h" #include "c_rope.h" //----------------------------------------------------------------------------- IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Arrow, DT_TFProjectile_Arrow ) BEGIN_NETWORK_TABLE( C_TFProjectile_Arrow, DT_TFProjectile_Arrow ) RecvPropBool( RECVINFO( m_bArrowAlight ) ), RecvPropBool( RECVINFO( m_bCritical ) ), RecvPropInt( RECVINFO( m_iProjectileType ) ), END_NETWORK_TABLE() //----------------------------------------------------------------------------- IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_HealingBolt, DT_TFProjectile_HealingBolt ) BEGIN_NETWORK_TABLE( C_TFProjectile_HealingBolt, DT_TFProjectile_HealingBolt ) END_NETWORK_TABLE() IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook ) BEGIN_NETWORK_TABLE( C_TFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook ) END_NETWORK_TABLE() #define NEAR_MISS_THRESHOLD 120 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_TFProjectile_Arrow::C_TFProjectile_Arrow( void ) { m_fAttachTime = 0.f; m_nextNearMissCheck = 0.f; m_bNearMiss = false; m_bArrowAlight = false; m_bCritical = true; m_pCritEffect = NULL; m_iCachedDeflect = false; m_flLifeTime = 40.0f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_TFProjectile_Arrow::~C_TFProjectile_Arrow( void ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_TFProjectile_Arrow::OnDataChanged( DataUpdateType_t updateType ) { if ( updateType == DATA_UPDATE_CREATED ) { SetNextClientThink( CLIENT_THINK_ALWAYS ); #ifdef STAGING_ONLY if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET ) { switch ( GetTeamNumber() ) { case TF_TEAM_BLUE: ParticleProp()->Create( "bullet_distortion_trail", PATTACH_ABSORIGIN_FOLLOW ); break; case TF_TEAM_RED: ParticleProp()->Create( "bullet_distortion_trail", PATTACH_ABSORIGIN_FOLLOW ); break; } } else if ( m_bArrowAlight ) #else if ( m_bArrowAlight ) #endif // STAGING_ONLY { ParticleProp()->Create( "flying_flaming_arrow", PATTACH_POINT_FOLLOW, "muzzle" ); } } if ( m_bCritical ) { if ( updateType == DATA_UPDATE_CREATED || m_iCachedDeflect != GetDeflected() ) { CreateCritTrail(); } } m_iCachedDeflect = GetDeflected(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_TFProjectile_Arrow::NotifyBoneAttached( C_BaseAnimating* attachTarget ) { BaseClass::NotifyBoneAttached( attachTarget ); m_fAttachTime = gpGlobals->curtime; SetNextClientThink( CLIENT_THINK_ALWAYS ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_TFProjectile_Arrow::ClientThink( void ) { // Perform a near-miss check. if ( !m_bNearMiss && (gpGlobals->curtime > m_nextNearMissCheck) ) { CheckNearMiss(); m_nextNearMissCheck = gpGlobals->curtime + 0.05f; } // Remove crit effect if we hit a wall. if ( GetMoveType() == MOVETYPE_NONE && m_pCritEffect ) { ParticleProp()->StopEmission( m_pCritEffect ); m_pCritEffect = NULL; } BaseClass::ClientThink(); // DO THIS LAST: Destroy us automatically after a period of time. if ( m_pAttachedTo ) { if ( gpGlobals->curtime - m_fAttachTime > m_flLifeTime ) { Release(); return; } else if ( m_pAttachedTo->IsEffectActive( EF_NODRAW ) && !IsEffectActive( EF_NODRAW ) ) { AddEffects( EF_NODRAW ); UpdateVisibility(); } else if ( !m_pAttachedTo->IsEffectActive( EF_NODRAW ) && IsEffectActive( EF_NODRAW ) && (m_pAttachedTo != C_BasePlayer::GetLocalPlayer()) ) { RemoveEffects( EF_NODRAW ); UpdateVisibility(); } } if ( IsDormant() && !IsEffectActive( EF_NODRAW ) ) { AddEffects( EF_NODRAW ); UpdateVisibility(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_TFProjectile_Arrow::CheckNearMiss( void ) { // Check against the local player. If we're near him play a near miss sound. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer || !pLocalPlayer->IsAlive() ) return; // If we are attached to something or stationary we don't want to do near miss checks. if ( m_pAttachedTo || (GetMoveType() == MOVETYPE_NONE) ) { m_bNearMiss = true; return; } // Can't hear near miss sounds from friendly arrows. if ( pLocalPlayer->GetTeamNumber() == GetTeamNumber() ) return; Vector vecPlayerPos = pLocalPlayer->GetAbsOrigin(); Vector vecArrowPos = GetAbsOrigin(), forward; AngleVectors( GetAbsAngles(), &forward ); Vector vecArrowDest = GetAbsOrigin() + forward * 200.f; // If the arrow is moving away from the player just stop checking. float dist1 = vecArrowPos.DistToSqr( vecPlayerPos ); float dist2 = vecArrowDest.DistToSqr( vecPlayerPos ); if ( dist2 > dist1 ) { m_bNearMiss = true; return; } // Check to see if the arrow is passing near the player. Vector vecClosestPoint; float dist; CalcClosestPointOnLineSegment( vecPlayerPos, vecArrowPos, vecArrowDest, vecClosestPoint, &dist ); dist = vecPlayerPos.DistTo( vecClosestPoint ); if ( dist > NEAR_MISS_THRESHOLD ) return; // The arrow is passing close to the local player. m_bNearMiss = true; SetNextClientThink( CLIENT_THINK_NEVER ); // If the arrow is about to hit something, don't play the sound and stop this check. trace_t tr; UTIL_TraceLine( vecArrowPos, vecArrowPos + forward * 400.f, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.DidHit() ) return; // We're good for a near miss! float soundlen = 0; EmitSound_t params; params.m_flSoundTime = 0; params.m_pSoundName = "Weapon_Arrow.Nearmiss"; params.m_pflSoundDuration = &soundlen; CSingleUserRecipientFilter localFilter( pLocalPlayer ); EmitSound( localFilter, pLocalPlayer->entindex(), params ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_TFProjectile_Arrow::CreateCritTrail( void ) { if ( IsDormant() ) return; if ( m_pCritEffect ) { ParticleProp()->StopEmission( m_pCritEffect ); m_pCritEffect = NULL; } if ( m_bCritical ) { switch( GetTeamNumber() ) { case TF_TEAM_BLUE: m_pCritEffect = ParticleProp()->Create( "critical_rocket_blue", PATTACH_ABSORIGIN_FOLLOW ); break; case TF_TEAM_RED: m_pCritEffect = ParticleProp()->Create( "critical_rocket_red", PATTACH_ABSORIGIN_FOLLOW ); break; default: break; } } } //----------------------------------------------------------------------------- void C_TFProjectile_HealingBolt::OnDataChanged( DataUpdateType_t updateType ) { if ( updateType == DATA_UPDATE_CREATED ) { switch( GetTeamNumber() ) { case TF_TEAM_BLUE: ParticleProp()->Create( "healshot_trail_blue", PATTACH_ABSORIGIN_FOLLOW ); break; case TF_TEAM_RED: ParticleProp()->Create( "healshot_trail_red", PATTACH_ABSORIGIN_FOLLOW ); break; } } BaseClass::OnDataChanged( updateType ); } void C_TFProjectile_GrapplingHook::OnDataChanged( DataUpdateType_t updateType ) { BaseClass::OnDataChanged( updateType ); if ( updateType == DATA_UPDATE_CREATED ) { int nTeam = GetTeamNumber(); C_TFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() ); if ( pTFPlayer && pTFPlayer->IsPlayerClass( TF_CLASS_SPY ) && pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pTFPlayer->GetTeamNumber() != GetLocalPlayerTeam() ) { nTeam = pTFPlayer->m_Shared.GetDisguiseTeam(); } const char *pszMaterialName = "cable/cable"; switch ( nTeam ) { case TF_TEAM_BLUE: pszMaterialName = "cable/cable_blue"; break; case TF_TEAM_RED: pszMaterialName = "cable/cable_red"; break; } C_BaseEntity *pStartEnt = GetOwnerEntity(); int iAttachment = 0; if ( pTFPlayer ) { CTFWeaponBase *pWeapon = assert_cast< CTFWeaponBase* >( pTFPlayer->GetActiveWeapon() ); if ( pWeapon ) { pStartEnt = pWeapon; int iMuzzle = pWeapon->LookupAttachment( "muzzle" ); if ( iMuzzle != -1 ) { iAttachment = iMuzzle; } } } int iHookAttachment = LookupAttachment( "rope_locator" ); if ( iHookAttachment == -1 ) iHookAttachment = 0; m_hRope = C_RopeKeyframe::Create( pStartEnt, this, iAttachment, iHookAttachment, 2, pszMaterialName ); SetNextClientThink( CLIENT_THINK_ALWAYS ); } } void C_TFProjectile_GrapplingHook::UpdateOnRemove() { RemoveRope(); BaseClass::UpdateOnRemove(); } void C_TFProjectile_GrapplingHook::ClientThink() { UpdateRope(); } void C_TFProjectile_GrapplingHook::UpdateRope() { C_TFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() ); if ( !pTFPlayer || !pTFPlayer->IsAlive() ) { RemoveRope(); return; } Vector vecStart = pTFPlayer->WorldSpaceCenter(); if ( pTFPlayer->GetActiveWeapon() ) { int iAttachment = pTFPlayer->GetActiveWeapon()->LookupAttachment( "muzzle" ); if ( iAttachment != -1 ) { GetAttachment( iAttachment, vecStart ); } } float flDist = vecStart.DistTo( WorldSpaceCenter() ); if ( m_hRope ) { float flHangDist = pTFPlayer->GetGrapplingHookTarget() ? 0.1f * flDist : 1.5f * flDist; assert_cast< C_RopeKeyframe* >( m_hRope.Get() )->SetupHangDistance( flHangDist ); } } void C_TFProjectile_GrapplingHook::RemoveRope() { if ( m_hRope ) { m_hRope->Release(); m_hRope = NULL; } }