summaryrefslogtreecommitdiff
path: root/game/server/tf/tf_projectile_flare.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/tf_projectile_flare.cpp')
-rw-r--r--game/server/tf/tf_projectile_flare.cpp625
1 files changed, 625 insertions, 0 deletions
diff --git a/game/server/tf/tf_projectile_flare.cpp b/game/server/tf/tf_projectile_flare.cpp
new file mode 100644
index 0000000..ecf1779
--- /dev/null
+++ b/game/server/tf/tf_projectile_flare.cpp
@@ -0,0 +1,625 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// TF Nail
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_projectile_flare.h"
+#include "soundent.h"
+#include "tf_fx.h"
+#include "tf_player.h"
+#include "tf_weapon_flaregun.h"
+#include "tf_gamerules.h"
+
+//=============================================================================
+//
+// TF Flare Projectile functions (Server specific).
+//
+#define FLARE_MODEL "models/weapons/w_models/w_flaregun_shell.mdl"
+#define FLARE_GRAVITY 0.3f
+#define FLARE_SPEED 2000.0f
+
+#define FLARE_THINK_CONTEXT "CTFProjectile_FlareThink"
+
+LINK_ENTITY_TO_CLASS( tf_projectile_flare, CTFProjectile_Flare );
+PRECACHE_WEAPON_REGISTER( tf_projectile_flare );
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Flare, DT_TFProjectile_Flare )
+
+BEGIN_NETWORK_TABLE( CTFProjectile_Flare, DT_TFProjectile_Flare )
+ SendPropBool( SENDINFO( m_bCritical ) ),
+END_NETWORK_TABLE()
+
+BEGIN_DATADESC( CTFProjectile_Flare )
+DEFINE_THINKFUNC( ImpactThink ),
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Flare::CTFProjectile_Flare()
+{
+ m_bIsFromTaunt = false;
+ m_bCritical = false;
+ m_bImpact = false;
+ m_flImpactTime = 0.0f;
+ m_flNextSeekUpdate = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Flare::~CTFProjectile_Flare()
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Flare *CTFProjectile_Flare::Create( CBaseEntity *pLauncher, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, CBaseEntity *pScorer )
+{
+ CTFProjectile_Flare *pFlare = static_cast<CTFProjectile_Flare*>( CBaseEntity::CreateNoSpawn( "tf_projectile_flare", vecOrigin, vecAngles, pOwner ) );
+ if ( !pFlare )
+ return NULL;
+
+ pFlare->SetLauncher( pLauncher );
+
+ // Initialize the owner.
+ pFlare->SetOwnerEntity( pOwner );
+
+ // Set team.
+ pFlare->ChangeTeam( pOwner->GetTeamNumber() );
+
+ // Save the scoring player.
+ pFlare->SetScorer( pScorer );
+
+ // Spawn.
+ DispatchSpawn( pFlare );
+
+ // Setup the initial velocity.
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
+
+ float flLaunchSpeed = pFlare->GetProjectileSpeed();
+
+ Vector vecVelocity = vecForward * flLaunchSpeed;
+ pFlare->SetAbsVelocity( vecVelocity );
+ pFlare->SetupInitialTransmittedGrenadeVelocity( vecVelocity );
+
+ float flGravity = FLARE_GRAVITY;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pLauncher, flGravity, mult_projectile_speed );
+ pFlare->SetGravity( flGravity );
+
+ // Setup the initial angles.
+ QAngle angles;
+ VectorAngles( vecVelocity, angles );
+ pFlare->SetAbsAngles( angles );
+
+ return pFlare;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::Spawn()
+{
+ SetModel( FLARE_MODEL );
+ BaseClass::Spawn();
+
+ float flHeatSeekPower = GetHeatSeekPower();
+
+ if ( flHeatSeekPower > 0.0f )
+ {
+ SetMoveType( MOVETYPE_CUSTOM, MOVECOLLIDE_DEFAULT );
+ SetGravity( FLARE_GRAVITY );
+ }
+ else
+ {
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ SetGravity( FLARE_GRAVITY );
+ }
+
+ // Set team.
+ m_nSkin = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
+
+ m_flCreationTime = gpGlobals->curtime;
+
+ CTFPlayer *pScorer = ToTFPlayer( GetScorer() );
+ if ( pScorer && pScorer->IsTaunting() && m_flCreationTime >= pScorer->GetTauntAttackTime() )
+ {
+ m_bIsFromTaunt = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::Precache()
+{
+ PrecacheModel( FLARE_MODEL );
+ PrecacheParticleSystem( "flaregun_trail_red" );
+ PrecacheParticleSystem( "flaregun_trail_crit_red" );
+ PrecacheParticleSystem( "flaregun_trail_blue" );
+ PrecacheParticleSystem( "flaregun_trail_crit_blue" );
+ PrecacheParticleSystem( "drg_manmelter_projectile" );
+ PrecacheParticleSystem( "scorchshot_trail_red" );
+ PrecacheParticleSystem( "scorchshot_trail_crit_red" );
+ PrecacheParticleSystem( "scorchshot_trail_blue" );
+ PrecacheParticleSystem( "scorchshot_trail_crit_blue" );
+ PrecacheParticleSystem( "Explosions_MA_FlyingEmbers" );
+
+ PrecacheParticleSystem( "pyrovision_flaregun_trail_blue" );
+ PrecacheParticleSystem( "pyrovision_flaregun_trail_red" );
+ PrecacheParticleSystem( "pyrovision_flaregun_trail_crit_blue" );
+ PrecacheParticleSystem( "pyrovision_flaregun_trail_crit_red" );
+ PrecacheParticleSystem( "pyrovision_scorchshot_trail_blue" );
+ PrecacheParticleSystem( "pyrovision_scorchshot_trail_red" );
+ PrecacheParticleSystem( "pyrovision_scorchshot_trail_crit_blue" );
+ PrecacheParticleSystem( "pyrovision_scorchshot_trail_crit_red" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::SetScorer( CBaseEntity *pScorer )
+{
+ m_Scorer = pScorer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBasePlayer *CTFProjectile_Flare::GetScorer( void )
+{
+ return dynamic_cast<CBasePlayer *>( m_Scorer.Get() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFProjectile_Flare::GetDamageType()
+{
+ int iDmgType = BaseClass::GetDamageType();
+ if ( m_bCritical )
+ {
+ iDmgType |= DMG_CRITICAL;
+ }
+
+ return iDmgType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::Explode( trace_t *pTrace, CBaseEntity *pOther )
+{
+ Vector vecOrigin = GetAbsOrigin();
+
+ CBaseEntity *pAttacker = GetOwnerEntity();
+ IScorer *pScorerInterface = dynamic_cast<IScorer*>( pAttacker );
+ if ( pScorerInterface )
+ {
+ pAttacker = pScorerInterface->GetScorer();
+ }
+
+ CTFPlayer *pTFVictim = ToTFPlayer( pOther );
+
+ CTFFlareGun *pFlareGun = dynamic_cast< CTFFlareGun* >( GetLauncher() );
+ if ( pFlareGun )
+ {
+ if ( pFlareGun->GetFlareGunType() == FLAREGUN_SCORCHSHOT )
+ {
+ // When the scorch shot hits a player...
+ if ( pTFVictim )
+ {
+ // Now it only collides with the world
+ SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+
+ Vector vVelocity = GetAbsVelocity();
+
+ // Check if burning before damage
+ bool bIsBurningVictim = pTFVictim->m_Shared.InCond( TF_COND_BURNING );
+ int iDamageType = GetDamageType();
+
+ // Prevent the normal push force cause we are going to add it
+ iDamageType |= DMG_PREVENT_PHYSICS_FORCE;
+
+ // Damage the player to push them back
+ CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vecOrigin, GetDamage(), iDamageType, m_bIsFromTaunt ? TF_DMG_CUSTOM_FLARE_PELLET : 0 );
+ pTFVictim->TakeDamage( info );
+
+ bool bIsEnemy = pAttacker && pTFVictim->GetTeamNumber() != pAttacker->GetTeamNumber();
+
+ // Quick Fix Uber and teammates are immune to the force
+ if ( !pTFVictim->m_Shared.InCond( TF_COND_MEGAHEAL ) && bIsEnemy )
+ {
+ Vector vecToTarget;
+ vecToTarget = vVelocity;
+ VectorNormalize( vecToTarget );
+ vecToTarget.z = 1.0;
+
+ // apply airblast - Apply stun if they are effectively grounded so we can knock them up
+ if ( !pTFVictim->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) )
+ {
+ pTFVictim->m_Shared.StunPlayer( 0.5, 1.f, TF_STUN_MOVEMENT, ToTFPlayer( pAttacker ) );
+ }
+
+ float flForce = bIsBurningVictim ? 400.0f : 100.0f;
+ pTFVictim->ApplyAirBlastImpulse( vecToTarget * flForce );
+ }
+
+ // It loses almost all of its speed and pops into the air
+ vVelocity.x *= 0.07f;
+ vVelocity.y *= 0.07f;
+ vVelocity.z = 100.0f;
+ SetAbsVelocity( vVelocity + RandomVector( -2.0f, 2.0f ) );
+
+ // Point the new direction and randomly flip
+ QAngle angForward;
+ VectorAngles( vVelocity, angForward );
+ SetAbsAngles( angForward );
+
+ QAngle angRotation = RandomAngle( 180.0f, 720.0f );
+ angRotation.x *= ( RandomInt( 0, 1 ) == 0 ? 1 : -1 );
+ angRotation.y *= ( RandomInt( 0, 1 ) == 0 ? 1 : -1 );
+ angRotation.z *= ( RandomInt( 0, 1 ) == 0 ? 1 : -1 );
+
+ SetLocalAngularVelocity( angRotation );
+
+ CPVSFilter filter( vecOrigin );
+ EmitSound( filter, entindex(), "Rubber.BulletImpact" );
+
+ // Save this entity as enemy, they will take 100% damage.
+ if ( m_hEnemy.Get() == NULL )
+ {
+ m_hEnemy = pTFVictim;
+ }
+
+ return;
+ }
+ }
+ }
+
+ // If we've already got an impact time, don't impact again.
+ if ( m_flImpactTime > 0.0 )
+ return;
+
+ // Save this entity as enemy, they will take 100% damage.
+ if ( m_hEnemy.Get() == NULL )
+ {
+ m_hEnemy = pOther;
+ }
+
+ if ( !pTFVictim )
+ {
+ m_bImpact = true;
+ }
+
+ // Invisible.
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+
+ bool bDetonate = false;
+ bool bNoRandomCrit = false;
+ if ( pFlareGun )
+ {
+ switch ( pFlareGun->GetFlareGunType() )
+ {
+ case FLAREGUN_DETONATE:
+ bDetonate = true;
+ break;
+
+ case FLAREGUN_GRORDBORT:
+ bNoRandomCrit = true;
+ break;
+
+ case FLAREGUN_SCORCHSHOT:
+ bDetonate = true;
+ bNoRandomCrit = true;
+ break;
+ }
+ }
+
+ // Flares that hit a burning player crit, unless it's a detonate flare - they mini-crit
+ if ( pTFVictim && pTFVictim->m_Shared.InCond( TF_COND_BURNING ) && !bDetonate && !bNoRandomCrit )
+ {
+ m_bCritical = true;
+ }
+
+ CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vecOrigin, GetDamage(), GetDamageType(), TF_DMG_CUSTOM_BURNING_FLARE );
+ pOther->TakeDamage( info );
+
+ // Remove the flare.
+ if ( m_bImpact )
+ {
+ SetMoveType( MOVETYPE_FLY );
+ SetAbsVelocity( vec3_origin );
+
+ m_vecImpactNormal = pTrace->plane.normal;
+ m_flImpactTime = gpGlobals->curtime + 0.1f;
+
+ // Stick into and object and fizzle a little while.
+ SetContextThink( &CTFProjectile_Flare::ImpactThink, gpGlobals->curtime, FLARE_THINK_CONTEXT );
+
+ // Only do this for the Detonator
+ if ( bDetonate )
+ {
+ // Scorch Shot can still light others in this case
+ Detonate( pFlareGun->GetFlareGunType() != FLAREGUN_SCORCHSHOT );
+ }
+ }
+ else
+ {
+ // Impact player sound.
+ CPVSFilter filter( vecOrigin );
+ EmitSound( filter, pOther->entindex(), "TFPlayer.FlareImpact" );
+
+ SendDeathNotice();
+ UTIL_Remove( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Custom explode for air burst flare
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::Explode_Air( trace_t *pTrace, int bitsDamageType, bool bSelfOnly )
+{
+ // Invisible.
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+
+ // Play explosion sound and effect.
+ Vector vecOrigin = GetAbsOrigin();
+ CPVSFilter filter( vecOrigin );
+
+ CBaseEntity *pAttacker = GetOwnerEntity();
+ int nDefID = -1;
+ WeaponSound_t nSound = SPECIAL1;
+ if ( pAttacker )
+ {
+ CTFFlareGun *pFlareGun = dynamic_cast<CTFFlareGun*>( ToTFPlayer( pAttacker )->GetActiveWeapon() );
+ if ( pFlareGun )
+ {
+ CEconItemView *pItem = pFlareGun->GetAttributeContainer()->GetItem();
+ nDefID = pItem->GetItemDefIndex();
+ }
+
+ // Damage.
+ IScorer *pScorerInterface = dynamic_cast<IScorer*>( pAttacker );
+ if ( pScorerInterface )
+ {
+ pAttacker = pScorerInterface->GetScorer();
+ }
+
+ float flRadius = bSelfOnly ? 0.f : GetRadius();
+
+ if ( bSelfOnly )
+ {
+ bitsDamageType |= DMG_BLAST;
+ nSound = SPECIAL2;
+ }
+
+#if defined( _DEBUG ) && defined( STAGING_ONLY )
+ // Debug!
+ ConVarRef tf_rocket_show_radius( "tf_rocket_show_radius" );
+ if ( tf_rocket_show_radius.GetBool() )
+ {
+ DrawRadius( flRadius );
+ }
+#endif
+ CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vecOrigin, GetDamage(), bitsDamageType | DMG_HALF_FALLOFF, TF_DMG_CUSTOM_FLARE_EXPLOSION );
+ CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, flRadius, NULL, TF_FLARE_RADIUS_FOR_FJS );
+ TFGameRules()->RadiusDamage( radiusinfo );
+ }
+
+ const char *pszParticle = bSelfOnly ? "Explosions_MA_FlyingEmbers" : "ExplosionCore_MidAir_Flare";
+ TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, GetWeaponID(), entindex(), nDefID, nSound );
+ TE_TFParticleEffect( filter, 0.0f, pszParticle, vecOrigin, pTrace->plane.normal, vec3_angle );
+ CSoundEnt::InsertSound ( SOUND_COMBAT, vecOrigin, 1024, 3.0 );
+
+ SendDeathNotice();
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Alt-fire air burst flare
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::Detonate( bool bSelfOnly )
+{
+ trace_t tr;
+ Vector vecSpot;
+
+ SetThink( NULL );
+
+ vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );
+ UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -32 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr);
+
+ Explode_Air( &tr, GetDamageType(), bSelfOnly );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFProjectile_Flare::GetRadius( void )
+{
+ float flRadius = TF_FLARE_DET_RADIUS;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flRadius, mult_explosion_radius );
+ return flRadius;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Let the flaregun know we're gone
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::SendDeathNotice( void )
+{
+ CBaseEntity *pAttacker = GetOwnerEntity();
+ if ( !pAttacker )
+ return;
+
+ CTFFlareGun *pFlareGun = dynamic_cast<CTFFlareGun*>( ToTFPlayer( pAttacker )->GetActiveWeapon() );
+ if ( pFlareGun && pFlareGun->GetFlareGunType() == FLAREGUN_DETONATE )
+ {
+ pFlareGun->DeathNotice( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::ImpactThink( void )
+{
+ if ( gpGlobals->curtime > m_flImpactTime )
+ {
+ // If we hit anything other than the player create an impact - effect, sound, etc.
+ if ( m_hEnemy.Get() )
+ {
+ Vector vecOrigin = GetAbsOrigin();
+ CPVSFilter filter( vecOrigin );
+ TE_TFExplosion( filter, 0.0f, vecOrigin, m_vecImpactNormal, GetWeaponID(), m_hEnemy.Get()->entindex() );
+ }
+
+ SendDeathNotice();
+ UTIL_Remove( this );
+ SetContextThink( NULL, 0, FLARE_THINK_CONTEXT );
+ }
+ else
+ {
+ SetContextThink( &CTFProjectile_Flare::ImpactThink, gpGlobals->curtime + 0.1f, FLARE_THINK_CONTEXT );
+ }
+}
+
+void CTFProjectile_Flare::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity )
+{
+ if ( m_flNextSeekUpdate < gpGlobals->curtime )
+ {
+ CTFPlayer *pBestTarget = NULL;
+ const float flMaxSeekDistanceSqr = ( 1024.0f * 1024.0f );
+ float flBestDistance = flMaxSeekDistanceSqr;
+
+ // Loop through players and attempt to find a seek target
+ int i;
+ for ( i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
+ if ( !pPlayer )
+ continue;
+
+ bool bBurning = pPlayer->m_Shared.InCond( TF_COND_BURNING );
+
+ if ( !bBurning ||
+ pPlayer->InSameTeam( this ) ||
+ ( pPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() && !bBurning ) ||
+ ( pPlayer->m_Shared.IsStealthed() && !bBurning ) ||
+ pPlayer->GetTeamNumber() == TEAM_SPECTATOR ||
+ !pPlayer->IsAlive() )
+ {
+ continue;
+ }
+
+ Vector vToTarget = pPlayer->WorldSpaceCenter() - GetAbsOrigin();
+ VectorNormalize( vToTarget );
+
+ Vector vForward = *pNewVelocity;
+ VectorNormalize( vForward );
+
+ if ( vToTarget.Dot( vForward ) < -0.25f )
+ continue;
+
+ float flDistanceSqr = pPlayer->WorldSpaceCenter().DistToSqr( GetAbsOrigin() );
+ if ( flBestDistance > flDistanceSqr )
+ {
+ trace_t tr;
+ UTIL_TraceLine( pPlayer->WorldSpaceCenter(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr );
+ if ( !tr.DidHit() || tr.m_pEnt == this )
+ {
+ flBestDistance = flDistanceSqr;
+ pBestTarget = pPlayer;
+ }
+ }
+ }
+
+ float flHeatSeekPower = GetHeatSeekPower();
+
+ QAngle angToTarget = *pNewAngles;
+
+ if ( pBestTarget )
+ {
+ Vector vToTarget = pBestTarget->WorldSpaceCenter() - GetAbsOrigin();
+ VectorAngles( vToTarget, angToTarget );
+ }
+
+ const float flUpdatesPerSecond = 4.0f;
+
+ if ( angToTarget != *pNewAngles )
+ {
+ pNewAngVelocity->x = Approach( UTIL_AngleDiff( angToTarget.x, pNewAngles->x ) * flUpdatesPerSecond, pNewAngVelocity->x, flHeatSeekPower );
+ pNewAngVelocity->y = Approach( UTIL_AngleDiff( angToTarget.y, pNewAngles->y ) * flUpdatesPerSecond, pNewAngVelocity->y, flHeatSeekPower );
+ pNewAngVelocity->z = Approach( UTIL_AngleDiff( angToTarget.z, pNewAngles->z ) * flUpdatesPerSecond, pNewAngVelocity->z, flHeatSeekPower );
+
+ const float flMaxAngularVelocity = 360.0f;
+ pNewAngVelocity->x = clamp( pNewAngVelocity->x, -flMaxAngularVelocity, flMaxAngularVelocity );
+ pNewAngVelocity->y = clamp( pNewAngVelocity->y, -flMaxAngularVelocity, flMaxAngularVelocity );
+ pNewAngVelocity->z = clamp( pNewAngVelocity->z, -flMaxAngularVelocity, flMaxAngularVelocity );
+ }
+
+ m_flNextSeekUpdate = gpGlobals->curtime + ( 1.0f / flUpdatesPerSecond );
+ }
+
+ *pNewAngles += *pNewAngVelocity * gpGlobals->frametime;
+
+ Vector vForward;
+ AngleVectors( *pNewAngles, &vForward );
+ *pNewVelocity = vForward * GetProjectileSpeed();
+
+ *pNewPosition += *pNewVelocity * gpGlobals->frametime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Flare was deflected.
+//-----------------------------------------------------------------------------
+void CTFProjectile_Flare::Deflected( CBaseEntity *pDeflectedBy, Vector &vecDir )
+{
+ CTFPlayer *pTFDeflector = ToTFPlayer( pDeflectedBy );
+ if ( !pTFDeflector )
+ return;
+
+ ChangeTeam( pTFDeflector->GetTeamNumber() );
+ SetLauncher( pTFDeflector->GetActiveWeapon() );
+
+ CTFPlayer* pOldOwner = ToTFPlayer( GetOwnerEntity() );
+ SetOwnerEntity( pTFDeflector );
+
+ if ( pOldOwner )
+ {
+ pOldOwner->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:1,victim:1" );
+ }
+
+ if ( pTFDeflector->m_Shared.IsCritBoosted() )
+ {
+ SetCritical( true );
+ }
+
+ CTFWeaponBase::SendObjectDeflectedEvent( pTFDeflector, pOldOwner, GetWeaponID(), this );
+
+ IncrementDeflected();
+ SetScorer( pTFDeflector );
+}
+
+float CTFProjectile_Flare::GetProjectileSpeed( void ) const
+{
+ float flLaunchSpeed = FLARE_SPEED;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flLaunchSpeed, mult_projectile_speed );
+
+ return flLaunchSpeed;
+}
+
+float CTFProjectile_Flare::GetHeatSeekPower( void ) const
+{
+ float flHeatSeekPower = 0.0;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flHeatSeekPower, mod_projectile_heat_seek_power );
+
+ return flHeatSeekPower;
+} \ No newline at end of file