summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_throwable.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_weapon_throwable.cpp')
-rw-r--r--game/shared/tf/tf_weapon_throwable.cpp1234
1 files changed, 1234 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_throwable.cpp b/game/shared/tf/tf_weapon_throwable.cpp
new file mode 100644
index 0000000..8e352ed
--- /dev/null
+++ b/game/shared/tf/tf_weapon_throwable.cpp
@@ -0,0 +1,1234 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "tf_weapon_throwable.h"
+#include "tf_gamerules.h"
+#include "in_buttons.h"
+#include "basetypes.h"
+#include "tf_weaponbase_gun.h"
+#include "effect_dispatch_data.h"
+
+// Client specific.
+#ifdef CLIENT_DLL
+#include "c_tf_player.h"
+// Server specific.
+#else
+#include "tf_player.h"
+#include "tf_fx.h"
+#include "te_effect_dispatch.h"
+#include "bone_setup.h"
+#include "tf_target_dummy.h"
+#endif
+
+
+// Base
+// Launcher
+IMPLEMENT_NETWORKCLASS_ALIASED( TFThrowable, DT_TFWeaponThrowable )
+BEGIN_NETWORK_TABLE( CTFThrowable, DT_TFWeaponThrowable )
+#ifdef CLIENT_DLL
+RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ),
+#else
+SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFThrowable )
+END_PREDICTION_DATA()
+
+//LINK_ENTITY_TO_CLASS( tf_weapon_throwable, CTFThrowable );
+//PRECACHE_WEAPON_REGISTER( tf_weapon_throwable );
+
+#ifdef STAGING_ONLY
+CREATE_SIMPLE_WEAPON_TABLE( TFThrowablePrimary, tf_weapon_throwable_primary )
+CREATE_SIMPLE_WEAPON_TABLE( TFThrowableSecondary, tf_weapon_throwable_secondary )
+CREATE_SIMPLE_WEAPON_TABLE( TFThrowableMelee, tf_weapon_throwable_melee )
+CREATE_SIMPLE_WEAPON_TABLE( TFThrowableUtility, tf_weapon_throwable_utility )
+#endif
+
+// Projectile
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Throwable, DT_TFProjectile_Throwable )
+BEGIN_NETWORK_TABLE( CTFProjectile_Throwable, DT_TFProjectile_Throwable )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_throwable, CTFProjectile_Throwable );
+PRECACHE_WEAPON_REGISTER( tf_projectile_throwable );
+
+// Projectile Repel
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowableRepel, DT_TFProjectile_ThrowableRepel )
+BEGIN_NETWORK_TABLE( CTFProjectile_ThrowableRepel, DT_TFProjectile_ThrowableRepel )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_throwable_repel, CTFProjectile_ThrowableRepel );
+PRECACHE_WEAPON_REGISTER( tf_projectile_throwable_repel );
+
+// Projectile Brick
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowableBrick, DT_TFProjectile_ThrowableBrick )
+BEGIN_NETWORK_TABLE( CTFProjectile_ThrowableBrick, DT_TFProjectile_ThrowableBrick )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_throwable_brick, CTFProjectile_ThrowableBrick );
+PRECACHE_WEAPON_REGISTER( tf_projectile_throwable_brick );
+
+// Projectile Bread Monster
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowableBreadMonster, DT_TFProjectile_ThrowableBreadMonster )
+BEGIN_NETWORK_TABLE( CTFProjectile_ThrowableBreadMonster, DT_TFProjectile_ThrowableBreadMonster )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_throwable_breadmonster, CTFProjectile_ThrowableBreadMonster );
+PRECACHE_WEAPON_REGISTER( tf_projectile_throwable_breadmonster );
+
+#ifdef STAGING_ONLY
+// Projectile Target Dummy
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowableTargetDummy, DT_TFProjectile_ThrowableTargetDummy )
+BEGIN_NETWORK_TABLE( CTFProjectile_ConcGrenade, DT_TFProjectile_ThrowableTargetDummy )
+END_NETWORK_TABLE()
+LINK_ENTITY_TO_CLASS( tf_projectile_target_dummy, CTFProjectile_ThrowableTargetDummy );
+PRECACHE_WEAPON_REGISTER( tf_projectile_target_dummy );
+
+// Projectile Concussion Grenade
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ConcGrenade, DT_TFProjectile_ConcGrenade )
+BEGIN_NETWORK_TABLE( CTFProjectile_ConcGrenade, DT_TFProjectile_ConcGrenade )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_grenade_concussion, CTFProjectile_ConcGrenade );
+PRECACHE_WEAPON_REGISTER( tf_projectile_grenade_concussion );
+
+// Projectile Teleport Grenade
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_TeleportGrenade, DT_TFProjectile_TeleportGrenade )
+BEGIN_NETWORK_TABLE( CTFProjectile_TeleportGrenade, DT_TFProjectile_TeleportGrenade )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_grenade_teleport, CTFProjectile_TeleportGrenade );
+PRECACHE_WEAPON_REGISTER( tf_projectile_grenade_teleport );
+
+// Projectile Chain Grenade
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_GravityGrenade, DT_TFProjectile_GravityGrenade )
+BEGIN_NETWORK_TABLE( CTFProjectile_GravityGrenade, DT_TFProjectile_GravityGrenade )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_grenade_gravity, CTFProjectile_GravityGrenade );
+PRECACHE_WEAPON_REGISTER( tf_projectile_grenade_gravity );
+
+// Projectile Chain Grenade
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_ThrowingKnife, DT_TFProjectile_ThrowingKnife )
+BEGIN_NETWORK_TABLE( CTFProjectile_ThrowingKnife, DT_TFProjectile_ThrowingKnife )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_throwing_knife, CTFProjectile_ThrowingKnife );
+PRECACHE_WEAPON_REGISTER( tf_projectile_throwing_knife );
+
+// Projectile Smoke Grenade
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SmokeGrenade, DT_TFProjectile_SmokeGrenade )
+BEGIN_NETWORK_TABLE( CTFProjectile_SmokeGrenade, DT_TFProjectile_SmokeGrenade )
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_projectile_grenade_smoke, CTFProjectile_SmokeGrenade );
+PRECACHE_WEAPON_REGISTER( tf_projectile_grenade_smoke );
+#endif // STAGING_ONLY
+
+#define TF_GRENADE_TIMER "Weapon_Grenade.Timer"
+#define TF_GRENADE_CHARGE "Weapon_LooseCannon.Charge"
+
+//****************************************************************************
+// Throwable Weapon
+//****************************************************************************
+CTFThrowable::CTFThrowable( void )
+{
+ m_flChargeBeginTime = -1.0f;
+}
+
+//-----------------------------------------------------------------------------
+void CTFThrowable::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheModel( g_pszArrowModels[MODEL_BREAD_MONSTER] );
+ PrecacheModel( g_pszArrowModels[MODEL_THROWING_KNIFE] );
+#ifdef STAGING_ONLY
+ PrecacheScriptSound( "Weapon_Grenade_Concussion.Explode" );
+ PrecacheScriptSound( "Weapon_Grenade_Teleport.Explode" );
+ PrecacheScriptSound( TF_GRENADE_TIMER );
+#endif // STAGING_ONLY
+ PrecacheScriptSound( TF_GRENADE_CHARGE );
+
+ PrecacheScriptSound( "Weapon_bm_throwable.throw" );
+ PrecacheScriptSound( "Weapon_bm_throwable.smash" );
+
+ PrecacheParticleSystem( "grenade_smoke_cycle" );
+ PrecacheParticleSystem( "blood_bread_biting" );
+}
+
+//-----------------------------------------------------------------------------
+float CTFThrowable::InternalGetEffectBarRechargeTime( void )
+{
+ float flRechargeTime = 0;
+ CALL_ATTRIB_HOOK_FLOAT( flRechargeTime, throwable_recharge_time );
+ if ( flRechargeTime )
+ return flRechargeTime;
+ return 10.0f; // default
+}
+
+//-----------------------------------------------------------------------------
+float CTFThrowable::GetDetonationTime()
+{
+ float flDetonationTime = 0;
+ CALL_ATTRIB_HOOK_FLOAT( flDetonationTime, throwable_detonation_time );
+ if ( flDetonationTime )
+ return flDetonationTime;
+ return 5.0f; // default
+}
+
+//-----------------------------------------------------------------------------
+void CTFThrowable::PrimaryAttack( void )
+{
+ if ( !CanCharge() )
+ {
+ // Fire
+ BaseClass::PrimaryAttack();
+ return;
+ }
+
+ if ( m_flChargeBeginTime > 0 )
+ return;
+
+ // Do all the Checks and start a charged (primed) attack
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return;
+
+ // Check for ammunition.
+ if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) < 1 )
+ return;
+
+ // Are we capable of firing again?
+ if ( m_flNextPrimaryAttack > gpGlobals->curtime )
+ return;
+
+ if ( pPlayer->GetWaterLevel() == WL_Eyes )
+ return;
+
+ if ( !CanAttack() )
+ return;
+
+ if ( m_flChargeBeginTime <= 0 )
+ {
+ // Set the weapon mode.
+ m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
+ SendWeaponAnim( ACT_VM_PULLBACK ); // TODO : Anim!
+#ifdef GAME_DLL
+ // save that we had the attack button down
+ m_flChargeBeginTime = gpGlobals->curtime;
+#endif // GAME_LL
+
+#ifdef CLIENT_DLL
+ if ( pPlayer == C_BasePlayer::GetLocalPlayer() )
+ {
+ int iCanBeCharged = 0;
+ CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
+ if ( iCanBeCharged )
+ {
+ EmitSound( TF_GRENADE_CHARGE );
+ }
+ else
+ {
+ EmitSound( TF_GRENADE_TIMER );
+ }
+ }
+#endif // CLIENT_DLL
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFThrowable::ItemPostFrame( void )
+{
+ // Get the player owning the weapon.
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return;
+
+ if ( m_flChargeBeginTime > 0.f && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 )
+ {
+ bool bFiredWeapon = false;
+ // If we're not holding down the attack button, launch our grenade
+ if ( !( pPlayer->m_nButtons & IN_ATTACK ) )
+ {
+ FireProjectile( pPlayer );
+ bFiredWeapon = true;
+ }
+ // Misfire
+ else if ( m_flChargeBeginTime + GetDetonationTime() < gpGlobals->curtime )
+ {
+ CTFProjectile_Throwable * pThrowable = dynamic_cast<CTFProjectile_Throwable*>( FireProjectile( pPlayer ) );
+ if ( pThrowable )
+ {
+#ifdef GAME_DLL
+ pThrowable->Misfire();
+#endif // GAME_DLL
+ }
+
+ bFiredWeapon = true;
+ }
+
+ if ( bFiredWeapon )
+ {
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+ pPlayer->SetAnimation( PLAYER_ATTACK1 );
+#ifdef GAME_DLL
+ m_flChargeBeginTime = -1.0f; // reset
+#endif // GAME_DLL
+ // Set next attack times.
+ float flFireDelay = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay;
+ m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
+ SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() );
+#ifdef CLIENT_DLL
+ int iCanBeCharged = 0;
+ CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
+ if ( iCanBeCharged )
+ {
+ StopSound( TF_GRENADE_CHARGE );
+ }
+#endif // CLIENT_DLL
+ }
+ }
+ BaseClass::ItemPostFrame();
+}
+
+// ITFChargeUpWeapon
+//-----------------------------------------------------------------------------
+// Primable is for timed explosions
+// Charagable is for things like distance or power increases
+// Can't really have both but can have neither
+bool CTFThrowable::CanCharge()
+{
+ int iCanBePrimed = 0;
+ CALL_ATTRIB_HOOK_INT( iCanBePrimed, is_throwable_primable );
+
+ int iCanBeCharged = 0;
+ CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
+
+ return iCanBeCharged || iCanBePrimed ;
+}
+
+//-----------------------------------------------------------------------------
+float CTFThrowable::GetChargeBeginTime( void )
+{
+ float flDetonateTimeLength = GetDetonationTime();
+// float flModDetonateTimeLength = 0;
+
+ int iCanBePrimed = 0;
+ CALL_ATTRIB_HOOK_INT( iCanBePrimed, is_throwable_primable );
+
+ // Use reverse logic for primable grenades (Counts down to boom)
+ // Full charge since we haven't fired
+ if ( iCanBePrimed )
+ {
+ if ( m_flChargeBeginTime < 0 )
+ {
+ return gpGlobals->curtime - flDetonateTimeLength;
+ }
+ return gpGlobals->curtime - Clamp( m_flChargeBeginTime + flDetonateTimeLength - gpGlobals->curtime, 0.f, flDetonateTimeLength );
+ }
+
+ return m_flChargeBeginTime;
+}
+
+//-----------------------------------------------------------------------------
+float CTFThrowable::GetChargeMaxTime( void )
+{
+ return GetDetonationTime();
+}
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFThrowable::FireJar( CTFPlayer *pPlayer )
+{
+#ifdef GAME_DLL
+ return FireProjectileInternal();
+#endif
+ return NULL;
+}
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+void CTFThrowable::TossJarThink( void )
+{
+ FireProjectileInternal();
+}
+
+//-----------------------------------------------------------------------------
+CTFProjectile_Throwable *CTFThrowable::FireProjectileInternal( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return NULL;
+
+ CAttribute_String attrProjectileEntityName;
+ GetProjectileEntityName( &attrProjectileEntityName );
+ if ( !attrProjectileEntityName.has_value() )
+ return NULL;
+
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( pPlayer->EyeAngles(), &vecForward, &vecRight, &vecUp );
+
+ float fRight = 8.f;
+ if ( IsViewModelFlipped() )
+ {
+ fRight *= -1;
+ }
+ Vector vecSrc = pPlayer->Weapon_ShootPosition();
+
+ // Make spell toss position at the hand
+ vecSrc = vecSrc + ( vecUp * -9.0f ) + ( vecRight * 7.0f ) + ( vecForward * 3.0f );
+
+ trace_t trace;
+ Vector vecEye = pPlayer->EyePosition();
+ CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
+ UTIL_TraceHull( vecEye, vecSrc, -Vector( 8, 8, 8 ), Vector( 8, 8, 8 ), MASK_SOLID_BRUSHONLY, &traceFilter, &trace );
+
+ // If we started in solid, don't let them fire at all
+ if ( trace.startsolid )
+ return NULL;
+
+ CalcIsAttackCritical();
+
+ // Create the Grenade and Intialize it appropriately
+ CTFProjectile_Throwable *pGrenade = static_cast<CTFProjectile_Throwable*>( CBaseEntity::CreateNoSpawn( attrProjectileEntityName.value().c_str(), trace.endpos, pPlayer->EyeAngles(), pPlayer ) );
+ if ( pGrenade )
+ {
+ // Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly.
+ pGrenade->SetPipebombMode();
+ pGrenade->SetLauncher( this );
+ pGrenade->SetCritical( IsCurrentAttackACrit() );
+
+ DispatchSpawn( pGrenade );
+
+ // Calculate a charge percentage
+ // For now Charge just effects exit velocity
+ int iCanBeCharged = 0;
+ float flChargePercent = 0;
+ float flDetonateTime = GetDetonationTime();
+ CALL_ATTRIB_HOOK_INT( iCanBeCharged, is_throwable_chargeable );
+ if ( iCanBeCharged )
+ {
+ flChargePercent = RemapVal( gpGlobals->curtime, m_flChargeBeginTime, m_flChargeBeginTime + flDetonateTime, 0.0f, 1.0f );
+ }
+
+ Vector vecVelocity = pGrenade->GetVelocityVector( vecForward, vecRight, vecUp, flChargePercent );
+ AngularImpulse angVelocity = pGrenade->GetAngularImpulse();
+
+ pGrenade->InitGrenade( vecVelocity, angVelocity, pPlayer, GetTFWpnData() );
+ pGrenade->InitThrowable( flChargePercent );
+ pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
+
+ if ( flDetonateTime > 0 )
+ {
+ // Check if this has been primed
+ int iCanBePrimed = 0;
+ CALL_ATTRIB_HOOK_INT( iCanBePrimed, is_throwable_primable );
+ if ( m_flChargeBeginTime > 0 && iCanBePrimed > 0 )
+ {
+ flDetonateTime = ( m_flChargeBeginTime + flDetonateTime - gpGlobals->curtime );
+ }
+ pGrenade->SetDetonateTimerLength( flDetonateTime );
+ }
+ pGrenade->m_flFullDamage = 0;
+
+ if ( pGrenade->GetThrowSoundEffect() )
+ {
+ pGrenade->EmitSound( pGrenade->GetThrowSoundEffect() );
+ }
+ }
+
+ StartEffectBarRegen();
+
+ return pGrenade;
+}
+#endif // GAME_DLL
+
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+// Throwable Projectile
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+#ifdef GAME_DLL
+CTFProjectile_Throwable::CTFProjectile_Throwable( void )
+{
+ m_flChargePercent = 0;
+ m_bHit = false;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+// Get Initial Velocity
+Vector CTFProjectile_Throwable::GetVelocityVector( const Vector &vecForward, const Vector &vecRight, const Vector &vecUp, float flCharge )
+{
+ // Scale the projectile speed up to a maximum of 3000?
+ float flSpeed = RemapVal( flCharge, 0, 1.0f, GetProjectileSpeed(), GetProjectileMaxSpeed() );
+
+ return ( ( flSpeed * vecForward ) +
+ ( ( random->RandomFloat( -10.0f, 10.0f ) + 200.0f ) * vecUp ) +
+ ( random->RandomFloat( -10.0f, 10.0f ) * vecRight ) );
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+void CTFProjectile_Throwable::OnHit( CBaseEntity *pOther )
+{
+ if ( m_bHit )
+ return;
+
+ if ( ExplodesOnHit() )
+ {
+ Explode();
+ }
+
+ m_bHit = true;
+}
+//-----------------------------------------------------------------------------
+void CTFProjectile_Throwable::Explode()
+{
+ trace_t tr;
+ Vector vecSpot;// trace starts here!
+ 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( &tr, GetDamageType() );
+}
+
+//-----------------------------------------------------------------------------
+void CTFProjectile_Throwable::Explode( trace_t *pTrace, int bitsDamageType )
+{
+ if ( GetThrower() )
+ {
+ InitialExplodeEffects( NULL, pTrace );
+
+ // Particle
+ const char* pszExplodeEffect = GetExplodeEffectParticle();
+ if ( pszExplodeEffect && pszExplodeEffect[0] != '\0' )
+ {
+ CPVSFilter filter( GetAbsOrigin() );
+ TE_TFParticleEffect( filter, 0.0, pszExplodeEffect, GetAbsOrigin(), vec3_angle );
+ }
+
+ // Sounds
+ const char* pszSoundEffect = GetExplodeEffectSound();
+ if ( pszSoundEffect && pszSoundEffect[0] != '\0' )
+ {
+ EmitSound( pszSoundEffect );
+ }
+ }
+
+ SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
+ SetTouch( NULL );
+
+ AddEffects( EF_NODRAW );
+ SetAbsVelocity( vec3_origin );
+}
+
+//-----------------------------------------------------------------------------
+// THROWABLE REPEL
+//-----------------------------------------------------------------------------
+void CTFProjectile_ThrowableRepel::OnHit( CBaseEntity *pOther )
+{
+ if ( m_bHit )
+ return;
+
+ CTFPlayer *pPlayer = dynamic_cast< CTFPlayer*>( pOther );
+
+ if ( pPlayer && !pPlayer->InSameTeam( GetThrower() ) )
+ {
+ CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
+ trace_t trace;
+ UTIL_TraceLine( GetAbsOrigin(), pPlayer->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
+
+ // Apply AirBlast Force
+ Vector vecToTarget;
+ vecToTarget = pPlayer->GetAbsOrigin() - this->GetAbsOrigin();
+ vecToTarget.z = 0;
+ VectorNormalize( vecToTarget );
+
+ // Quick Fix Uber is immune
+ if ( pPlayer->m_Shared.InCond( TF_COND_MEGAHEAL ))
+ return;
+
+ float flForce = 300.0f * m_flChargePercent + 350.0f;
+ pPlayer->ApplyAirBlastImpulse( vecToTarget * flForce + Vector( 0, 0, flForce ) );
+ pPlayer->ApplyPunchImpulseX( RandomInt( -50, -30 ) );
+
+ // Apply Damage to Victim
+ CTakeDamageInfo info;
+ info.SetAttacker( GetThrower() );
+ info.SetInflictor( this );
+ info.SetWeapon( GetLauncher() );
+ info.SetDamage( GetDamage() );
+ info.SetDamageCustom( GetCustomDamageType() );
+ info.SetDamagePosition( this->GetAbsOrigin() );
+ info.SetDamageType( DMG_CLUB | DMG_PREVENT_PHYSICS_FORCE );
+
+ //Vector dir;
+ //AngleVectors( GetAbsAngles(), &dir );
+
+ pPlayer->DispatchTraceAttack( info, vecToTarget, &trace );
+ ApplyMultiDamage();
+ }
+
+ BaseClass::OnHit( pOther );
+}
+//-----------------------------------------------------------------------------
+// THROWABLE BRICK
+//-----------------------------------------------------------------------------
+void CTFProjectile_ThrowableBrick::OnHit( CBaseEntity *pOther )
+{
+ if ( m_bHit )
+ return;
+
+ CTFPlayer *pPlayer = dynamic_cast< CTFPlayer*>( pOther );
+
+ if ( pPlayer && !pPlayer->InSameTeam( GetThrower() ) )
+ {
+ CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
+ trace_t trace;
+ UTIL_TraceLine( GetAbsOrigin(), pPlayer->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
+
+ Vector vecToTarget;
+ vecToTarget = pPlayer->WorldSpaceCenter() - this->WorldSpaceCenter();
+ VectorNormalize( vecToTarget );
+
+ // Apply Damage to Victim
+ CTakeDamageInfo info;
+ info.SetAttacker( GetThrower() );
+ info.SetInflictor( this );
+ info.SetWeapon( GetLauncher() );
+ info.SetDamage( GetDamage() );
+ info.SetDamageCustom( GetCustomDamageType() );
+ info.SetDamagePosition( GetAbsOrigin() );
+ info.SetDamageType( DMG_CLUB );
+
+ pPlayer->DispatchTraceAttack( info, vecToTarget, &trace );
+ pPlayer->ApplyPunchImpulseX( RandomInt( 15, 20 ) );
+ ApplyMultiDamage();
+ }
+
+ BaseClass::OnHit( pOther );
+}
+//-----------------------------------------------------------------------------
+// THROWABLE BREADMONSTER
+//-----------------------------------------------------------------------------
+void CTFProjectile_ThrowableBreadMonster::OnHit( CBaseEntity *pOther )
+{
+ if ( m_bHit )
+ return;
+
+ CTFPlayer *pVictim = dynamic_cast< CTFPlayer*>( pOther );
+ CTFPlayer *pOwner = dynamic_cast< CTFPlayer*>( GetThrower() );
+
+ if ( pVictim && pOwner && !pVictim->InSameTeam( pOwner ) )
+ {
+ m_bHit = true;
+ CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
+ trace_t trace;
+ Vector vEndPos = pVictim->WorldSpaceCenter();
+ vEndPos.z = WorldSpaceCenter().z + 1.0f;
+ UTIL_TraceLine( WorldSpaceCenter(), vEndPos, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &tracefilter, &trace );
+
+ Vector vecToTarget;
+ vecToTarget = pVictim->WorldSpaceCenter() - this->WorldSpaceCenter();
+ VectorNormalize( vecToTarget );
+
+ // Apply Damage to Victim
+ CTakeDamageInfo info;
+ info.SetAttacker( GetThrower() );
+ info.SetInflictor( this );
+ info.SetWeapon( GetLauncher() );
+ info.SetDamage( GetDamage() );
+ info.SetDamageCustom( GetCustomDamageType() );
+ info.SetDamagePosition( GetAbsOrigin() );
+
+ int iDamageType = DMG_CLUB;
+ if ( IsCritical() )
+ {
+ iDamageType |= DMG_CRITICAL;
+ }
+ info.SetDamageType( iDamageType );
+
+ pVictim->DispatchTraceAttack( info, vecToTarget, &trace );
+ pVictim->ApplyPunchImpulseX( RandomInt( 15, 20 ) );
+ pVictim->m_Shared.MakeBleed( pOwner, dynamic_cast< CTFWeaponBase * >( GetLauncher() ), 5.0f, 1.0f );
+ ApplyMultiDamage();
+
+ // Bread Particle
+ CPVSFilter filter( vEndPos );
+ TE_TFParticleEffect( filter, 0.0, "blood_bread_biting", vEndPos, vec3_angle );
+
+ // Attach Breadmonster to Victim
+ CreateStickyAttachmentToTarget( pOwner, pVictim, &trace );
+
+ BaseClass::Explode();
+ return;
+ }
+ else // its a dud
+ {
+ BaseClass::Explode();
+ return;
+ }
+ BaseClass::OnHit( pOther );
+}
+
+//-----------------------------------------------------------------------------
+void CTFProjectile_ThrowableBreadMonster::Detonate()
+{
+ SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
+ SetTouch( NULL );
+
+ AddEffects( EF_NODRAW );
+ SetAbsVelocity( vec3_origin );
+}
+
+//-----------------------------------------------------------------------------
+void CTFProjectile_ThrowableBreadMonster::Explode( trace_t *pTrace, int bitsDamageType )
+{
+ if ( !m_bHit )
+ {
+ // TODO, Spawn Debris / Flopping BreadInstead
+ trace_t tr;
+ Vector velDir = m_vCollisionVelocity;
+ VectorNormalize( velDir );
+ Vector vecSpot = GetAbsOrigin() - velDir * 32;
+ UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr );
+ if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY )
+ {
+ // We hit the skybox, go away soon.
+ return;
+ }
+
+ // Create a breadmonster in the world
+ CEffectData data;
+ data.m_vOrigin = tr.endpos;
+ data.m_vNormal = velDir;
+ data.m_nEntIndex = 0;
+ data.m_nAttachmentIndex = 0;
+ data.m_nMaterial = 0;
+ data.m_fFlags = TF_PROJECTILE_BREAD_MONSTER;
+ data.m_nColor = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
+
+ DispatchEffect( "TFBoltImpact", data );
+ }
+
+ BaseClass::Explode( pTrace, bitsDamageType );
+}
+
+#endif // GAME_DLL
+//
+//#ifdef CLIENT_DLL
+//
+//static CUtlMap< const char*, CUtlString > s_TeamParticleMap;
+//static bool s_TeamParticleMapInited = false;
+//
+////-----------------------------------------------------------------------------
+//const char *CTFProjectile_Throwable::GetTrailParticleName( void )
+//{
+// // Check for Particles
+// int iDynamicParticleEffect = 0;
+// CALL_ATTRIB_HOOK_INT_ON_OTHER( GetLauncher(), iDynamicParticleEffect, set_attached_particle );
+// if ( iDynamicParticleEffect > 0 )
+// {
+// // Init Map Once
+// if ( !s_TeamParticleMapInited )
+// {
+// SetDefLessFunc( s_TeamParticleMap );
+// s_TeamParticleMapInited = true;
+// }
+//
+// attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iDynamicParticleEffect );
+// if ( pParticleSystem )
+// {
+// // TF Team Color Particles
+// const char * pName = pParticleSystem->pszSystemName;
+// if ( GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pName, "_teamcolor_red" ))
+// {
+// int index = s_TeamParticleMap.Find( pName );
+// if ( !s_TeamParticleMap.IsValidIndex( index ) )
+// {
+// char pBlue[256];
+// V_StrSubst( pName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
+// CUtlString pBlueString( pBlue );
+// index = s_TeamParticleMap.Insert( pName, pBlueString );
+// }
+// return s_TeamParticleMap[index].String();
+// }
+// else if ( GetTeamNumber() == TF_TEAM_RED && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_blue" ))
+// {
+// // Guard against accidentally giving out the blue team color (support tool)
+// int index = s_TeamParticleMap.Find( pName );
+// if ( !s_TeamParticleMap.IsValidIndex( index ) )
+// {
+// char pRed[256];
+// V_StrSubst( pName, "_teamcolor_blue", "_teamcolor_red", pRed, 256 );
+// CUtlString pRedString( pRed );
+// index = s_TeamParticleMap.Insert( pName, pRedString );
+// }
+// return s_TeamParticleMap[index].String();
+// }
+//
+// return pName;
+// }
+// }
+//
+// if ( GetTeamNumber() == TF_TEAM_BLUE )
+// {
+// return "trail_basic_blue";
+// }
+// else
+// {
+// return "trail_basic_red";
+// }
+//}
+//
+//#endif // CLIENT_DLL
+
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+#ifdef STAGING_ONLY
+#ifdef GAME_DLL
+
+
+void CTFProjectile_ThrowableTargetDummy::Explode()
+{
+ CTFPlayer *pPlayer = ToTFPlayer( GetThrower() );
+ if ( !pPlayer )
+ return;
+
+ CTFTargetDummy::Create( GetAbsOrigin(), GetAbsAngles(), pPlayer );
+ BaseClass::Explode();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_ConcGrenade::Detonate( )
+{
+ Explode();
+}
+//-----------------------------------------------------------------------------
+void CTFProjectile_ConcGrenade::Misfire( )
+{
+ CTFPlayer *pPlayer = ToTFPlayer( GetThrower() );
+ if ( pPlayer )
+ {
+ SetAbsOrigin( pPlayer->GetAbsOrigin() );
+ }
+
+ Explode();
+}
+//-----------------------------------------------------------------------------
+void CTFProjectile_ConcGrenade::Explode( )
+{
+ // Apply pushback
+ const float flMaxForce = 900.f;
+ const float flMaxSelfForce = 800.f;
+ const int nMaxEnts = MAX_PLAYERS;
+ CBaseEntity *pObjects[ nMaxEnts ];
+ int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, GetAbsOrigin(), GetDamageRadius(), FL_CLIENT );
+ CTFPlayer *pThrower = ToTFPlayer( GetThrower() );
+
+ for ( int i = 0; i < nCount; i++ )
+ {
+ if ( !pObjects[i] )
+ continue;
+
+ if ( !pObjects[i]->IsAlive() )
+ continue;
+
+ // Only affect the thrower from same team
+ if ( InSameTeam( pObjects[i] ) && pObjects[i] != pThrower )
+ continue;
+
+ if ( !FVisible( pObjects[i], MASK_OPAQUE ) )
+ continue;
+
+ if ( !pObjects[i]->IsPlayer() )
+ continue;
+
+ CTFPlayer *pTFPlayer = static_cast< CTFPlayer* >( pObjects[i] );
+ if ( !pTFPlayer )
+ continue;
+
+ // Conc does more force the further away you are from the blast radius
+ Vector vecPushDir = pTFPlayer->GetAbsOrigin() - GetAbsOrigin();
+ float flForce = RemapVal( vecPushDir.Length(), 0, GetDamageRadius(), 0, flMaxForce );
+
+ if ( flForce < 250.0f && pObjects[i] == pThrower ) // Hold case
+ {
+ AngularImpulse ang;
+ pTFPlayer->GetVelocity( &vecPushDir, &ang );
+ flForce = flMaxSelfForce;
+ }
+ VectorNormalize( vecPushDir );
+ vecPushDir.z *= 1.5f;
+ pTFPlayer->ApplyAirBlastImpulse( vecPushDir * flForce );
+ pTFPlayer->ApplyPunchImpulseX( RandomInt( -50, -30 ) );
+
+// if ( pObjects[i] == pThrower )
+// {
+// // Apply 'Bonk' lines to make target more visible for 2 seconds
+// pThrower->m_Shared.AddCond( TF_COND_SELF_CONC, 2 );
+// }
+ }
+
+ BaseClass::Explode();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_TeleportGrenade::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ SetContextThink( &CTFProjectile_TeleportGrenade::RecordPosThink, gpGlobals->curtime + 0.05f, "RecordThink" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_TeleportGrenade::RecordPosThink( void )
+{
+ m_vecTrailingPos.AddToTail( GetAbsOrigin() );
+
+ // Only retain 5 positions
+ if ( m_vecTrailingPos.Count() > 5 )
+ {
+ m_vecTrailingPos.Remove( 0 );
+ }
+
+ SetContextThink( &CTFProjectile_TeleportGrenade::RecordPosThink, gpGlobals->curtime + 0.05f, "RecordThink" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_TeleportGrenade::Explode( trace_t *pTrace, int bitsDamageType )
+{
+ CTFPlayer *pThrower = ToTFPlayer( GetThrower() );
+ if ( !pThrower || !pThrower->IsAlive() )
+ return;
+
+ trace_t traceHull;
+ CTraceFilterIgnoreTeammates traceFilter( this, COLLISION_GROUP_PLAYER_MOVEMENT, GetTeamNumber() );
+ unsigned int nMask = pThrower->GetTeamNumber() == TF_TEAM_RED ? CONTENTS_BLUETEAM : CONTENTS_REDTEAM;
+ nMask |= MASK_PLAYERSOLID;
+
+ // Try a few spots
+ FOR_EACH_VEC_BACK( m_vecTrailingPos, i )
+ {
+ // Try positions starting with the current, and moving back in time a bit
+ Vector vecStart = m_vecTrailingPos[i];
+ UTIL_TraceHull( vecStart, vecStart, VEC_HULL_MIN, VEC_HULL_MAX, nMask, &traceFilter, &traceHull );
+
+ if ( !traceHull.DidHit() )
+ {
+ // Place a teleport effect where they came from
+ const Vector& vecOrigin = pThrower->GetAbsOrigin();
+ CPVSFilter pvsFilter( vecOrigin );
+ TE_TFParticleEffect( pvsFilter, 0.f, GetExplodeEffectParticle(), vecOrigin, vec3_angle );
+
+ // Move 'em!
+ pThrower->Teleport( &vecStart, &pThrower->GetAbsAngles(), NULL );
+
+ // Do a zoom effect
+ pThrower->SetFOV( pThrower, 0.f, 0.3f, 120.f );
+
+ // Screen flash
+ color32 fadeColor = { 255, 255, 255, 100 };
+ UTIL_ScreenFade( pThrower, fadeColor, 0.25f, 0.4f, FFADE_IN );
+
+ if ( TFGameRules() )
+ {
+ TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_TELEPORT, ( pThrower->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED );
+ }
+ }
+ }
+
+ BaseClass::Explode( pTrace, bitsDamageType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_GravityGrenade::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ m_flStartTime = -1.f;
+ m_flNextPulseEffectTime = -1.f;
+ m_bHitWorld = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_GravityGrenade::TrapThink( void )
+{
+ if ( gpGlobals->curtime > m_flStartTime + 5.f )
+ {
+ SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
+ SetTouch( NULL );
+
+ AddEffects( EF_NODRAW );
+ SetAbsVelocity( vec3_origin );
+ return;
+ }
+
+ PulseTrap();
+
+ SetContextThink( &CTFProjectile_GravityGrenade::TrapThink, gpGlobals->curtime + 0.15f, "TrapThink" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_GravityGrenade::OnHitWorld( void )
+{
+ if ( !m_bHitWorld )
+ {
+ SetDetonateTimerLength( FLT_MAX );
+
+ m_bHitWorld = true;
+ m_flStartTime = gpGlobals->curtime;
+
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetTouch( NULL );
+
+ SetContextThink( &CTFProjectile_GravityGrenade::TrapThink, gpGlobals->curtime, "TrapThink" );
+ }
+
+ BaseClass::OnHitWorld();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_GravityGrenade::PulseTrap( void )
+{
+ const int nMaxEnts = 32;
+
+ Vector vecPos = GetAbsOrigin();
+ CBaseEntity *pObjects[ nMaxEnts ];
+ int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, vecPos, GetDamageRadius(), FL_CLIENT );
+
+ // Iterate through sphere's contents
+ for ( int i = 0; i < nCount; i++ )
+ {
+ CBaseCombatCharacter *pEntity = pObjects[i]->MyCombatCharacterPointer();
+ if ( !pEntity )
+ continue;
+
+ if ( InSameTeam( pEntity ) )
+ continue;
+
+ if ( !FVisible( pEntity, MASK_OPAQUE ) )
+ continue;
+
+ // Draw player toward us
+ Vector vecSourcePos = pEntity->GetAbsOrigin();
+ Vector vecTargetPos = GetAbsOrigin();
+ Vector vecVelocity = ( vecTargetPos - vecSourcePos ) * 2.f;
+ vecVelocity.z += 50.f;
+
+ if ( pEntity->GetFlags() & FL_ONGROUND )
+ {
+ vecVelocity.z += 150.f;
+ pEntity->SetGroundEntity( NULL );
+ pEntity->SetGroundChangeTime( gpGlobals->curtime + 0.5f );
+ }
+
+ pEntity->Teleport( NULL, NULL, &vecVelocity );
+ }
+
+ // NDebugOverlay::Sphere( vecPos, GetDamageRadius(), 0, 255, 0, false, 0.35f );
+
+ PulseEffects();
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CTFProjectile_GravityGrenade::PulseEffects( void )
+{
+ if ( gpGlobals->curtime < m_flNextPulseEffectTime )
+ return;
+
+ Vector vecOrigin = GetAbsOrigin();
+ CPVSFilter filter( vecOrigin );
+ TE_TFParticleEffect( filter, 0.f, "Explosion_ShockWave_01", vecOrigin, vec3_angle );
+ EmitSound( filter, entindex(), "Weapon_Upgrade.ExplosiveHeadshot" );
+
+ m_flNextPulseEffectTime = gpGlobals->curtime + 1.f;
+}
+
+//-----------------------------------------------------------------------------
+// THROWABLE KNIFE
+//-----------------------------------------------------------------------------
+Vector CTFProjectile_ThrowingKnife::GetVelocityVector( const Vector &vecForward, const Vector &vecRight, const Vector &vecUp, float flCharge )
+{
+ // Scale the projectile speed up to a maximum of 3000?
+ float flSpeed = RemapVal( flCharge, 0, 1.0f, GetProjectileSpeed(), GetProjectileMaxSpeed() );
+
+ return ( ( flSpeed * vecForward ) +
+ ( flSpeed * 0.1 * vecUp ) );
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+void CTFProjectile_ThrowingKnife::OnHit( CBaseEntity *pOther )
+{
+ if ( m_bHit )
+ return;
+
+ CTFPlayer *pVictim = dynamic_cast< CTFPlayer*>( pOther );
+ CTFPlayer *pOwner = dynamic_cast< CTFPlayer*>( GetThrower() );
+
+ if ( pVictim && pOwner && !pVictim->InSameTeam( pOwner ) )
+ {
+ CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
+ trace_t trace;
+ UTIL_TraceLine( GetAbsOrigin(), pVictim->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
+
+ Vector entForward;
+ AngleVectors( pVictim->EyeAngles(), &entForward );
+ Vector toEnt = pVictim->GetAbsOrigin() - this->GetAbsOrigin();
+ toEnt.z = 0;
+ entForward.z = 0;
+ toEnt.NormalizeInPlace();
+ entForward.NormalizeInPlace();
+
+ // Is from behind?
+ bool bIsFromBehind = DotProduct( toEnt, entForward ) > 0.7071f;
+
+ // Apply Damage to Victim
+ CTakeDamageInfo info;
+ info.SetAttacker( GetThrower() );
+ info.SetInflictor( this );
+ info.SetWeapon( GetLauncher() );
+ info.SetDamageCustom( GetCustomDamageType() );
+ info.SetDamagePosition( GetAbsOrigin() );
+
+ int iDamageType = DMG_CLUB;
+ if ( bIsFromBehind )
+ {
+ iDamageType |= DMG_CRITICAL;
+ }
+ info.SetDamageType( iDamageType );
+ info.SetDamage( bIsFromBehind ? GetBackHitDamage() : GetDamage() );
+
+ pVictim->DispatchTraceAttack( info, toEnt, &trace );
+ ApplyMultiDamage();
+
+ CreateStickyAttachmentToTarget( pOwner, pVictim, &trace );
+
+ Explode();
+ return;
+ }
+ else // its a dud, mark as hit and let it roll around
+ {
+ m_bHit = true;
+ }
+ BaseClass::OnHit( pOther );
+}
+
+//-----------------------------------------------------------------------------
+void CTFProjectile_ThrowingKnife::Detonate()
+{
+ SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
+ SetTouch( NULL );
+
+ AddEffects( EF_NODRAW );
+ SetAbsVelocity( vec3_origin );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_SmokeGrenade::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ m_flStartTime = -1.f;
+ m_bHitWorld = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_SmokeGrenade::OnHitWorld( void )
+{
+ if ( !m_bHitWorld )
+ {
+ SetDetonateTimerLength( FLT_MAX );
+ // VPhysicsGetObject()->EnableMotion( false );
+
+ m_bHitWorld = true;
+ m_flStartTime = gpGlobals->curtime;
+
+ const char *pszSoundEffect = GetExplodeEffectSound();
+ if ( pszSoundEffect && pszSoundEffect[0] != '\0' )
+ {
+ EmitSound( pszSoundEffect );
+ }
+
+ AddSolidFlags( FSOLID_TRIGGER );
+ SetTouch( NULL );
+
+ SetContextThink( &CTFProjectile_SmokeGrenade::SmokeThink, gpGlobals->curtime, "SmokeThink" );
+ }
+
+ BaseClass::OnHitWorld();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_SmokeGrenade::SmokeThink( void )
+{
+ if ( gpGlobals->curtime > m_flStartTime + 6.f )
+ {
+ SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
+
+ AddEffects( EF_NODRAW );
+ SetAbsVelocity( vec3_origin );
+ return;
+ }
+
+ CPVSFilter filter( GetAbsOrigin() );
+ TE_TFParticleEffect( filter, 0.f, "grenade_smoke_cycle", GetAbsOrigin(), vec3_angle );
+
+ const int nMaxEnts = 32;
+
+ Vector vecPos = GetAbsOrigin();
+ CBaseEntity *pObjects[ nMaxEnts ];
+ int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, vecPos, GetDamageRadius(), FL_CLIENT );
+
+ // Iterate through sphere's contents
+ for ( int i = 0; i < nCount; i++ )
+ {
+ CBaseCombatCharacter *pEntity = pObjects[i]->MyCombatCharacterPointer();
+ if ( !pEntity )
+ continue;
+
+ if ( !InSameTeam( pEntity ) )
+ continue;
+
+ if ( !FVisible( pEntity, MASK_OPAQUE ) )
+ continue;
+
+ if ( !pEntity->IsPlayer() )
+ continue;
+
+ CTFPlayer *pTFPlayer = ToTFPlayer( pEntity );
+ if ( !pTFPlayer )
+ continue;
+
+ pTFPlayer->m_Shared.AddCond( TF_COND_OBSCURED_SMOKE, 0.5f );
+ }
+
+ // NDebugOverlay::Sphere( vecPos, GetDamageRadius(), 0, 255, 0, false, 0.35f );
+
+ SetContextThink( &CTFProjectile_SmokeGrenade::SmokeThink, gpGlobals->curtime + 0.3f, "SmokeThink" );
+}
+#endif // GAME_DLL
+#endif // STAGING_ONLY