summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_flamethrower.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_weapon_flamethrower.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/tf/tf_weapon_flamethrower.cpp')
-rw-r--r--game/shared/tf/tf_weapon_flamethrower.cpp3100
1 files changed, 3100 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_flamethrower.cpp b/game/shared/tf/tf_weapon_flamethrower.cpp
new file mode 100644
index 0000000..b10d829
--- /dev/null
+++ b/game/shared/tf/tf_weapon_flamethrower.cpp
@@ -0,0 +1,3100 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// TF Flame Thrower
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_weapon_flamethrower.h"
+#include "tf_fx_shared.h"
+#include "in_buttons.h"
+#include "ammodef.h"
+#include "tf_gamerules.h"
+
+#if defined( CLIENT_DLL )
+
+ #include "c_tf_player.h"
+ #include "vstdlib/random.h"
+ #include "engine/IEngineSound.h"
+ #include "soundenvelope.h"
+ #include "prediction.h"
+ #include "haptics/ihaptics.h"
+ #include "c_tf_gamestats.h"
+#else
+
+ #include "explode.h"
+ #include "tf_player.h"
+ #include "tf_gamestats.h"
+ #include "ilagcompensationmanager.h"
+ #include "collisionutils.h"
+ #include "tf_team.h"
+ #include "tf_obj.h"
+ #include "tf_weapon_grenade_pipebomb.h"
+ #include "particle_parse.h"
+ #include "tf_weaponbase_grenadeproj.h"
+ #include "tf_weapon_compound_bow.h"
+ #include "tf_projectile_arrow.h"
+ #include "tf_gamestats.h"
+ #include "NextBot/NextBotManager.h"
+ #include "halloween/merasmus/merasmus_trick_or_treat_prop.h"
+ #include "tf_logic_robot_destruction.h"
+#ifdef STAGING_ONLY
+ #include "tf_fx.h"
+#endif // STAGING_ONLY
+ #include "tf_passtime_logic.h"
+
+ ConVar tf_debug_flamethrower("tf_debug_flamethrower", "0", FCVAR_CHEAT , "Visualize the flamethrower damage." );
+ ConVar tf_flamethrower_velocity( "tf_flamethrower_velocity", "2300.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Initial velocity of flame damage entities." );
+ ConVar tf_flamethrower_drag("tf_flamethrower_drag", "0.87", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Air drag of flame damage entities." );
+ ConVar tf_flamethrower_float("tf_flamethrower_float", "50.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Upward float velocity of flame damage entities." );
+ ConVar tf_flamethrower_vecrand("tf_flamethrower_vecrand", "0.05", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Random vector added to initial velocity of flame damage entities." );
+ ConVar tf_flamethrower_boxsize("tf_flamethrower_boxsize", "12.0", FCVAR_CHEAT , "Size of flame damage entities." );
+ ConVar tf_flamethrower_maxdamagedist("tf_flamethrower_maxdamagedist", "350.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Maximum damage distance for flamethrower." );
+ ConVar tf_flamethrower_shortrangedamagemultiplier("tf_flamethrower_shortrangedamagemultiplier", "1.2", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Damage multiplier for close-in flamethrower damage." );
+ ConVar tf_flamethrower_velocityfadestart("tf_flamethrower_velocityfadestart", ".3", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution starts to fade." );
+ ConVar tf_flamethrower_velocityfadeend("tf_flamethrower_velocityfadeend", ".5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution finishes fading." );
+ ConVar tf_flamethrower_burst_zvelocity( "tf_flamethrower_burst_zvelocity", "350", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+
+ static const char *s_pszFlameThrowerHitTargetThink = "FlameThrowerHitTargetThink";
+
+ extern ConVar tf_player_movement_stun_time;
+
+#endif
+
+#include "tf_pumpkin_bomb.h"
+
+ConVar tf_flamethrower_burstammo("tf_flamethrower_burstammo", "20", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "How much ammo does the air burst uses per shot." );
+ConVar tf_flamethrower_flametime("tf_flamethrower_flametime", "0.5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Time to live of flame damage entities." );
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// position of end of muzzle relative to shoot position
+#define TF_FLAMETHROWER_MUZZLEPOS_FORWARD 70.0f
+#define TF_FLAMETHROWER_MUZZLEPOS_RIGHT 12.0f
+#define TF_FLAMETHROWER_MUZZLEPOS_UP -12.0f
+
+#define TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK 14.0f
+
+#define TF_FLAMETHROWER_HITACCURACY_MED 40.0f
+#define TF_FLAMETHROWER_HITACCURACY_HIGH 60.0f
+
+//-----------------------------------------------------------------------------
+
+#define TF_WEAPON_BUBBLE_WAND_MODEL "models/player/items/pyro/mtp_bubble_wand.mdl"
+
+//-----------------------------------------------------------------------------
+
+#ifndef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Only send to local player
+//-----------------------------------------------------------------------------
+void* SendProxy_SendLocalFlameThrowerDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
+{
+ // Get the weapon entity
+ CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pVarData;
+ if ( pWeapon )
+ {
+ // Only send this chunk of data to the player carrying this weapon
+ CBasePlayer *pPlayer = ToBasePlayer( pWeapon->GetOwner() );
+ if ( pPlayer )
+ {
+ pRecipients->SetOnly( pPlayer->GetClientIndex() );
+ return (void*)pVarData;
+ }
+ }
+
+ return NULL;
+}
+REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendLocalFlameThrowerDataTable );
+#endif // CLIENT_DLL
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameThrower, DT_WeaponFlameThrower )
+
+//-----------------------------------------------------------------------------
+// Purpose: Only sent to the local player
+//-----------------------------------------------------------------------------
+BEGIN_NETWORK_TABLE_NOBASE( CTFFlameThrower, DT_LocalFlameThrower )
+ #if defined( CLIENT_DLL )
+ RecvPropInt( RECVINFO( m_iActiveFlames ) ),
+ RecvPropInt( RECVINFO( m_iDamagingFlames ) ),
+ RecvPropBool( RECVINFO( m_bHasHalloweenSpell ) ),
+ #else
+ SendPropInt( SENDINFO( m_iActiveFlames ), 5, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
+ SendPropInt( SENDINFO( m_iDamagingFlames ), 10, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
+ SendPropBool( SENDINFO( m_bHasHalloweenSpell ) ),
+ #endif
+END_NETWORK_TABLE()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+BEGIN_NETWORK_TABLE( CTFFlameThrower, DT_WeaponFlameThrower )
+ #if defined( CLIENT_DLL )
+ RecvPropInt( RECVINFO( m_iWeaponState ) ),
+ RecvPropBool( RECVINFO( m_bCritFire ) ),
+ RecvPropBool( RECVINFO( m_bHitTarget ) ),
+ RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ),
+ RecvPropDataTable("LocalFlameThrowerData", 0, 0, &REFERENCE_RECV_TABLE( DT_LocalFlameThrower ) ),
+ #else
+ SendPropInt( SENDINFO( m_iWeaponState ), 4, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
+ SendPropBool( SENDINFO( m_bCritFire ) ),
+ SendPropBool( SENDINFO( m_bHitTarget ) ),
+ SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
+ SendPropDataTable("LocalFlameThrowerData", 0, &REFERENCE_SEND_TABLE( DT_LocalFlameThrower ), SendProxy_SendLocalFlameThrowerDataTable ),
+ #endif
+END_NETWORK_TABLE()
+
+#if defined( CLIENT_DLL )
+BEGIN_PREDICTION_DATA( CTFFlameThrower )
+ DEFINE_PRED_FIELD( m_iWeaponState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_bCritFire, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_FIELD( m_flChargeBeginTime, FIELD_FLOAT ),
+END_PREDICTION_DATA()
+#endif
+
+LINK_ENTITY_TO_CLASS( tf_weapon_flamethrower, CTFFlameThrower );
+PRECACHE_WEAPON_REGISTER( tf_weapon_flamethrower );
+
+BEGIN_DATADESC( CTFFlameThrower )
+END_DATADESC()
+
+// ------------------------------------------------------------------------------------------------ //
+// CTFFlameThrower implementation.
+// ------------------------------------------------------------------------------------------------ //
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFFlameThrower::CTFFlameThrower()
+#if defined( CLIENT_DLL )
+: m_FlameEffects( this )
+, m_MmmmphEffect( this )
+#endif
+{
+ WeaponReset();
+
+#if defined( CLIENT_DLL )
+ m_pFiringStartSound = NULL;
+ m_pFiringLoop = NULL;
+ m_pFiringAccuracyLoop = NULL;
+ m_pFiringHitLoop = NULL;
+ m_bFiringLoopCritical = false;
+ m_pPilotLightSound = NULL;
+ m_pSpinUpSound = NULL;
+ m_szAccuracySound = NULL;
+ m_bEffectsThinking = false;
+ m_bFullRageEffect = false;
+#else
+ m_flTimeToStopHitSound = 0;
+#endif
+ m_bHasHalloweenSpell.Set( false );
+
+ ListenForGameEvent( "recalculate_holidays" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFFlameThrower::~CTFFlameThrower()
+{
+ DestroySounds();
+#if defined( CLIENT_DLL )
+ StopFullCritEffect();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::Precache( void )
+{
+ BaseClass::Precache();
+
+ int iModelIndex = PrecacheModel( TF_WEAPON_BUBBLE_WAND_MODEL );
+ PrecacheGibsForModel( iModelIndex );
+
+ PrecacheParticleSystem( "pyro_blast" );
+ PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttack" );
+ PrecacheScriptSound( "TFPlayer.AirBlastImpact" );
+ PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttackDeflect" );
+ PrecacheParticleSystem( "deflect_fx" );
+ PrecacheParticleSystem( "drg_bison_idle" );
+ PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_blue" );
+ PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_red" );
+ PrecacheParticleSystem( "halloween_burningplayer_flyingbits");
+
+#ifdef STAGING_ONLY
+ PrecacheScriptSound( "Equipment.RocketPack_Activate" );
+ PrecacheParticleSystem( "muzzle_bignasty" );
+#endif // STAGING_ONLY
+}
+
+bool CTFFlameThrower::CanAirBlast() const
+{
+ int iAirblastDisabled = 0;
+ CALL_ATTRIB_HOOK_INT( iAirblastDisabled, airblast_disabled );
+
+ bool bAllowed = ( iAirblastDisabled == 0 );
+
+#ifdef STAGING_ONLY
+ int nRocketPack = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), nRocketPack, rocket_pack );
+ if ( nRocketPack )
+ {
+ bAllowed = false;
+ }
+#endif // STAGING_ONLY
+
+ return bAllowed;
+}
+
+void CTFFlameThrower::DestroySounds( void )
+{
+#if defined( CLIENT_DLL )
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ if ( m_pFiringStartSound )
+ {
+ controller.SoundDestroy( m_pFiringStartSound );
+ m_pFiringStartSound = NULL;
+ }
+ if ( m_pFiringLoop )
+ {
+ controller.SoundDestroy( m_pFiringLoop );
+ m_pFiringLoop = NULL;
+ }
+ if ( m_pPilotLightSound )
+ {
+ controller.SoundDestroy( m_pPilotLightSound );
+ m_pPilotLightSound = NULL;
+ }
+ if ( m_pSpinUpSound )
+ {
+ controller.SoundDestroy( m_pSpinUpSound );
+ m_pSpinUpSound = NULL;
+ }
+ if ( m_pFiringAccuracyLoop )
+ {
+ controller.SoundDestroy( m_pFiringAccuracyLoop );
+ m_pFiringAccuracyLoop = NULL;
+ }
+
+ StopHitSound();
+#endif
+
+}
+void CTFFlameThrower::WeaponReset( void )
+{
+ BaseClass::WeaponReset();
+
+ SetWeaponState( FT_STATE_IDLE );
+ m_bCritFire = false;
+ m_bHitTarget = false;
+ m_flStartFiringTime = 0;
+ m_flAmmoUseRemainder = 0;
+ m_flChargeBeginTime = 0;
+ m_flSpinupBeginTime = 0;
+ ResetFlameHitCount();
+
+ DestroySounds();
+
+#if defined( CLIENT_DLL )
+ StopFullCritEffect();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::Spawn( void )
+{
+ m_iAltFireHint = HINT_ALTFIRE_FLAMETHROWER;
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ SetWeaponState( FT_STATE_IDLE );
+ m_bCritFire = false;
+ m_bHitTarget = false;
+ m_flChargeBeginTime = 0;
+
+#if defined ( CLIENT_DLL )
+ StopFlame();
+ StopPilotLight();
+ StopFullCritEffect();
+
+ m_bEffectsThinking = false;
+#endif
+
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::ItemPostFrame()
+{
+ if ( m_bLowered )
+ return;
+
+ // Get the player owning the weapon.
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return;
+
+#ifdef CLIENT_DLL
+ if ( !m_bEffectsThinking )
+ {
+ m_bEffectsThinking = true;
+ SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" );
+ }
+#endif
+
+ int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType );
+
+ m_bFiredSecondary = false;
+ if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK2 ) )
+ {
+ SecondaryAttack();
+ }
+
+ // Fixes an exploit where the airblast effect repeats while +attack is active
+ if ( m_bFiredBothAttacks )
+ {
+ if ( pOwner->m_nButtons & IN_ATTACK && !( pOwner->m_nButtons & IN_ATTACK2 ) )
+ {
+ pOwner->m_nButtons &= ~IN_ATTACK;
+ }
+ m_bFiredBothAttacks = false;
+ }
+
+ if ( pOwner->m_nButtons & IN_ATTACK && pOwner->m_nButtons & IN_ATTACK2 )
+ {
+ m_bFiredBothAttacks = true;
+ }
+
+ if ( !m_bFiredSecondary )
+ {
+ bool bSpinDown = m_flSpinupBeginTime > 0.0f;
+
+ if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK ) && iAmmo > 0 )
+ {
+ PrimaryAttack();
+ bSpinDown = false;
+ }
+ else if ( m_iWeaponState > FT_STATE_IDLE )
+ {
+ SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
+ pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_POST );
+ SetWeaponState( FT_STATE_IDLE );
+ m_bCritFire = false;
+ m_bHitTarget = false;
+ }
+
+ if ( bSpinDown )
+ {
+ m_flSpinupBeginTime = 0.0f;
+
+#if defined( CLIENT_DLL )
+ if ( m_pSpinUpSound )
+ {
+ float flSpinUpTime = GetSpinUpTime();
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangePitch( m_pSpinUpSound, 40, flSpinUpTime * 0.5f );
+ controller.SoundChangeVolume( m_pSpinUpSound, 0.0f, flSpinUpTime * 2.0f );
+ }
+#endif
+ }
+ }
+
+ if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_RELOAD)) || (!(pOwner->m_nButtons & IN_ATTACK2) || !m_bFiredSecondary))
+ {
+ // no fire buttons down or reloading
+ if ( !ReloadOrSwitchWeapons() && ( m_bInReload == false ) )
+ {
+ WeaponIdle();
+ }
+ }
+
+ // charged airblast
+ int iChargedAirblast = 0;
+ CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
+ if ( iChargedAirblast != 0 )
+ {
+ if ( m_flChargeBeginTime > 0 )
+ {
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ // If we're not holding down the attack button, launch the flame rocket
+ if ( !(pPlayer->m_nButtons & IN_ATTACK2) )
+ {
+ //FireProjectile( pOwner );
+ float flMultAmmoPerShot = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost );
+ int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot;
+ FireAirBlast( iAmmoPerShot );
+ }
+ }
+ }
+}
+
+class CTraceFilterIgnoreObjects : public CTraceFilterSimple
+{
+public:
+ // It does have a base, but we'll never network anything below here..
+ DECLARE_CLASS( CTraceFilterIgnoreObjects, CTraceFilterSimple );
+
+ CTraceFilterIgnoreObjects( const IHandleEntity *passentity, int collisionGroup )
+ : CTraceFilterSimple( passentity, collisionGroup )
+ {
+ }
+
+ virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
+ {
+ CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
+
+ if ( pEntity && pEntity->IsBaseObject() )
+ return false;
+
+ return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::PrimaryAttack()
+{
+ float flSpinUpTime = GetSpinUpTime();
+
+ if ( flSpinUpTime > 0.0f )
+ {
+ if ( m_flSpinupBeginTime > 0.0f )
+ {
+ if ( gpGlobals->curtime - m_flSpinupBeginTime < flSpinUpTime )
+ {
+ return;
+ }
+ }
+ else
+ {
+ m_flSpinupBeginTime = gpGlobals->curtime;
+
+#if defined( CLIENT_DLL )
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ if ( !m_pSpinUpSound )
+ {
+ // Create the looping pilot light sound
+ const char *pchSpinUpSound = GetShootSound( RELOAD );
+ CLocalPlayerFilter filter;
+ m_pSpinUpSound = controller.SoundCreate( filter, entindex(), pchSpinUpSound );
+
+ controller.Play( m_pSpinUpSound, 0.0f, 40 );
+ }
+
+ if ( m_pSpinUpSound )
+ {
+ controller.SoundChangePitch( m_pSpinUpSound, 100, flSpinUpTime );
+ controller.SoundChangeVolume( m_pSpinUpSound, 1.0f, flSpinUpTime * 0.1f );
+ }
+#endif
+ return;
+ }
+ }
+
+ // Are we capable of firing again?
+ if ( m_flNextPrimaryAttack > gpGlobals->curtime )
+ return;
+
+ // Get the player owning the weapon.
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return;
+
+ if ( !CanAttack() )
+ {
+#if defined ( CLIENT_DLL )
+ StopFlame();
+#endif
+ SetWeaponState( FT_STATE_IDLE );
+ return;
+ }
+
+ m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
+
+ CalcIsAttackCritical();
+
+ // Because the muzzle is so long, it can stick through a wall if the player is right up against it.
+ // Make sure the weapon can't fire in this condition by tracing a line between the eye point and the end of the muzzle.
+ trace_t trace;
+ Vector vecEye = pOwner->EyePosition();
+ Vector vecMuzzlePos = GetVisualMuzzlePos();
+ CTraceFilterIgnoreObjects traceFilter( this, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( vecEye, vecMuzzlePos, MASK_SOLID, &traceFilter, &trace );
+ if ( trace.fraction < 1.0 && ( !trace.m_pEnt || trace.m_pEnt->m_takedamage == DAMAGE_NO ) )
+ {
+ // there is something between the eye and the end of the muzzle, most likely a wall, don't fire, and stop firing if we already are
+ if ( m_iWeaponState > FT_STATE_IDLE )
+ {
+#if defined ( CLIENT_DLL )
+ StopFlame();
+#endif
+ SetWeaponState( FT_STATE_IDLE );
+ }
+ return;
+ }
+
+ switch ( m_iWeaponState )
+ {
+ case FT_STATE_IDLE:
+ {
+ // Just started, play PRE and start looping view model anim
+
+ pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE );
+
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+
+ m_flStartFiringTime = gpGlobals->curtime + 0.16; // 5 frames at 30 fps
+
+ SetWeaponState( FT_STATE_STARTFIRING );
+ }
+ break;
+ case FT_STATE_STARTFIRING:
+ {
+ // if some time has elapsed, start playing the looping third person anim
+ if ( gpGlobals->curtime > m_flStartFiringTime )
+ {
+ SetWeaponState( FT_STATE_FIRING );
+ m_flNextPrimaryAttackAnim = gpGlobals->curtime;
+ }
+ }
+ break;
+ case FT_STATE_FIRING:
+ {
+ if ( gpGlobals->curtime >= m_flNextPrimaryAttackAnim )
+ {
+ pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
+ m_flNextPrimaryAttackAnim = gpGlobals->curtime + 1.4; // fewer than 45 frames!
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+#ifdef CLIENT_DLL
+ // Restart our particle effect if we've transitioned across water boundaries
+ if ( m_iParticleWaterLevel != -1 && pOwner->GetWaterLevel() != m_iParticleWaterLevel )
+ {
+ if ( m_iParticleWaterLevel == WL_Eyes || pOwner->GetWaterLevel() == WL_Eyes )
+ {
+ RestartParticleEffect();
+ }
+ }
+#endif
+
+#if !defined (CLIENT_DLL)
+ // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation.
+ pOwner->NoteWeaponFired();
+
+ pOwner->SpeakWeaponFire();
+ CTF_GameStats.Event_PlayerFiredWeapon( pOwner, m_bCritFire );
+
+ // Move other players back to history positions based on local player's lag
+ lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
+
+ // PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp
+ // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
+ if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
+ {
+ g_pPasstimeLogic->GetBall()->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
+ }
+
+#endif
+#ifdef CLIENT_DLL
+ C_CTF_GameStats.Event_PlayerFiredWeapon( pOwner, IsCurrentAttackACrit() );
+#endif
+
+ float flFiringInterval = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay;
+
+#ifdef STAGING_ONLY
+ if ( ShootsNapalm() )
+ {
+ flFiringInterval *= 4.f;
+ }
+#endif // STAGING_ONLY
+
+ // Don't attack if we're underwater
+ if ( pOwner->GetWaterLevel() != WL_Eyes )
+ {
+ // Find eligible entities in a cone in front of us.
+ // Vector vOrigin = pOwner->Weapon_ShootPosition();
+ Vector vForward, vRight, vUp;
+ QAngle vAngles = pOwner->EyeAngles() + pOwner->GetPunchAngle();
+ AngleVectors( vAngles, &vForward, &vRight, &vUp );
+
+ #define NUM_TEST_VECTORS 30
+
+#ifdef CLIENT_DLL
+ bool bWasCritical = m_bCritFire;
+#endif
+
+ // Burn & Ignite 'em
+ int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ];
+ m_bCritFire = IsCurrentAttackACrit();
+ if ( m_bCritFire )
+ {
+ iDmgType |= DMG_CRITICAL;
+ }
+
+#ifdef CLIENT_DLL
+ if ( bWasCritical != m_bCritFire )
+ {
+ RestartParticleEffect();
+ }
+#endif
+
+
+#ifdef GAME_DLL
+ // create the flame entity
+ int iDamagePerSec = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
+ float flDamage = (float)iDamagePerSec * flFiringInterval;
+ CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg );
+
+ int iCritFromBehind = 0;
+ CALL_ATTRIB_HOOK_INT( iCritFromBehind, set_flamethrower_back_crit );
+
+#ifdef STAGING_ONLY
+ if ( ShootsNapalm() )
+ {
+ CTFProjectile_Napalm::Create( pOwner, this );
+ }
+ else
+#endif // STAGING_ONLY
+ {
+ CTFFlameEntity::Create( GetFlameOriginPos(), pOwner->EyeAngles(), this, tf_flamethrower_velocity.GetFloat(), iDmgType, flDamage, iCritFromBehind == 1 );
+ }
+
+ // Pyros can become invis in some game modes. Hitting fire normally handles this,
+ // but in the case of flamethrowers it's likely that stealth will be applied while
+ // the fire button is down, so we have to call into RemoveInvisibility here, too.
+ if ( pOwner->m_Shared.IsStealthed() )
+ {
+ pOwner->RemoveInvisibility();
+ }
+#endif
+ }
+
+#ifdef GAME_DLL
+ // Figure how much ammo we're using per shot and add it to our remainder to subtract. (We may be using less than 1.0 ammo units
+ // per frame, depending on how constants are tuned, so keep an accumulator so we can expend fractional amounts of ammo per shot.)
+ // Note we do this only on server and network it to client. If we predict it on client, it can get slightly out of sync w/server
+ // and cause ammo pickup indicators to appear
+ float flAmmoPerSecond = TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK;
+ CALL_ATTRIB_HOOK_FLOAT( flAmmoPerSecond, mult_flame_ammopersec );
+ m_flAmmoUseRemainder += flAmmoPerSecond * flFiringInterval;
+ // take the integer portion of the ammo use accumulator and subtract it from player's ammo count; any fractional amount of ammo use
+ // remains and will get used in the next shot
+ int iAmmoToSubtract = (int) m_flAmmoUseRemainder;
+ if ( iAmmoToSubtract > 0 )
+ {
+ pOwner->RemoveAmmo( iAmmoToSubtract, m_iPrimaryAmmoType );
+ m_flAmmoUseRemainder -= iAmmoToSubtract;
+ // round to 2 digits of precision
+ m_flAmmoUseRemainder = (float) ( (int) (m_flAmmoUseRemainder * 100) ) / 100.0f;
+ }
+#endif
+
+ m_flNextPrimaryAttack = gpGlobals->curtime + flFiringInterval;
+ m_flTimeWeaponIdle = gpGlobals->curtime + flFiringInterval;
+
+#if !defined (CLIENT_DLL)
+ lagcompensation->FinishLagCompensation( pOwner );
+
+ // PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp
+ // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
+ if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
+ {
+ g_pPasstimeLogic->GetBall()->FinishLagCompensation( pOwner );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float AirBurstDamageForce( const Vector &size, float damage, float scale )
+{
+ float force = damage * ((48 * 48 * 82.0) / (size.x * size.y * size.z)) * scale;
+
+ if ( force > 1000.0)
+ {
+ force = 1000.0;
+ }
+
+ return force;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::SupportsAirBlastFunction( EFlameThrowerAirblastFunction eFunction ) const
+{
+ int iSupportedAirBlastFunctions = 0;
+ CALL_ATTRIB_HOOK_INT( iSupportedAirBlastFunctions, airblast_functionality_flags );
+
+ // If we don't have this attribute specified, or it is set to the value 0, we interpret
+ // that as "I can do everything!".
+ if ( iSupportedAirBlastFunctions == 0 )
+ {
+ // They can do everything unless airblast is disabled, in which case they can do nothing
+ return CanAirBlast();
+ }
+
+ return (iSupportedAirBlastFunctions & eFunction) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::FireAirBlast( int iAmmoPerShot )
+{
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return;
+
+ m_bFiredSecondary = true;
+
+#ifdef CLIENT_DLL
+ // Stop the flame if we're currently firing
+ StopFlame( false );
+#endif
+
+ SetWeaponState( FT_STATE_SECONDARY );
+
+#ifdef GAME_DLL
+ SendWeaponAnim( ACT_VM_SECONDARYATTACK );
+ pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY );
+
+ int nDash = 0;
+ CALL_ATTRIB_HOOK_INT( nDash, airblast_dashes );
+
+ if ( !nDash )
+ {
+ DeflectProjectiles();
+ }
+ else
+ {
+#ifdef STAGING_ONLY
+ Vector vDashDir;
+ AngleVectors( pOwner->EyeAngles() + QAngle( 0.0f, 180.0f, 0.0f ), &vDashDir );
+#else
+ Vector vDashDir = pOwner->GetAbsVelocity();
+ if ( !pOwner->GetGroundEntity() || vDashDir.Length() == 0.0f )
+ {
+ AngleVectors( pOwner->EyeAngles(), &vDashDir );
+ }
+#endif
+ vDashDir.z = 0.0f;
+ VectorNormalize( vDashDir );
+
+ Vector vCenter = pOwner->WorldSpaceCenter();
+ Vector vSize = GetDeflectionSize();
+ DeflectPlayer( pOwner, pOwner, vDashDir, vCenter, vSize );
+ }
+
+ // for charged airblast
+ int iChargedAirblast = 0;
+ CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
+ if ( iChargedAirblast != 0 )
+ {
+ m_flChargeBeginTime = 0;
+ }
+
+ // compression blast doesn't go through the normal "weapon fired" code path
+ TheNextBots().OnWeaponFired( pOwner, this );
+#endif
+
+#ifdef CLIENT_DLL
+ if ( prediction->IsFirstTimePredicted() == true )
+ {
+ StartFlame();
+ }
+#endif
+
+ float fAirblastRefireTimeScale = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( fAirblastRefireTimeScale, mult_airblast_refire_time );
+ if ( fAirblastRefireTimeScale <= 0.0f )
+ {
+ fAirblastRefireTimeScale = 1.0f;
+ }
+
+ float fAirblastPrimaryRefireTimeScale = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( fAirblastPrimaryRefireTimeScale, mult_airblast_primary_refire_time );
+ if ( fAirblastPrimaryRefireTimeScale <= 0.0f )
+ {
+ fAirblastPrimaryRefireTimeScale = 1.0f;
+ }
+
+ // Haste Powerup Rune adds multiplier to fire delay time
+ if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
+ {
+ fAirblastRefireTimeScale *= 0.5f;
+ }
+
+ m_flNextSecondaryAttack = gpGlobals->curtime + (0.75f * fAirblastRefireTimeScale);
+ m_flNextPrimaryAttack = gpGlobals->curtime + (1.0f * fAirblastRefireTimeScale * fAirblastPrimaryRefireTimeScale);
+ m_flResetBurstEffect = gpGlobals->curtime + 0.05f;
+
+ pOwner->RemoveAmmo( iAmmoPerShot, m_iPrimaryAmmoType );
+}
+
+float CTFFlameThrower::GetSpinUpTime( void ) const
+{
+ float flSpinUpTime = 0.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flSpinUpTime, mod_flamethrower_spinup_time );
+
+ return flSpinUpTime;
+}
+
+void CTFFlameThrower::SetWeaponState( int nWeaponState )
+{
+ if ( m_iWeaponState == nWeaponState )
+ return;
+
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+
+ switch ( nWeaponState )
+ {
+ case FT_STATE_IDLE:
+ if ( pOwner )
+ {
+ float flFiringForwardPull = 0.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull );
+ if ( flFiringForwardPull )
+ {
+ pOwner->m_Shared.RemoveCond( TF_COND_SPEED_BOOST );
+ }
+ }
+ break;
+
+ case FT_STATE_STARTFIRING:
+ if ( pOwner )
+ {
+ float flFiringForwardPull = 0.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull );
+ if ( flFiringForwardPull )
+ {
+ pOwner->m_Shared.AddCond( TF_COND_SPEED_BOOST );
+ }
+ }
+ break;
+ }
+
+ m_iWeaponState = nWeaponState;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::UseRage( void )
+{
+ if ( !IsRageFull() )
+ return;
+
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ if ( !pPlayer->IsAllowedToTaunt() )
+ return;
+
+ float flNextAttack = m_flNextSecondaryAttack;
+
+#if GAME_DLL
+ // Do a taunt so everyone has a chance to run
+ pPlayer->Taunt( TAUNT_BASE_WEAPON );
+ if ( pPlayer->m_Shared.IsRageDraining() )
+ {
+ // taunt succeeded
+ flNextAttack = gpGlobals->curtime + 1.0f;
+ }
+#else
+ flNextAttack = gpGlobals->curtime + 1.0f;
+#endif
+
+ m_flNextSecondaryAttack = flNextAttack;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::SecondaryAttack()
+{
+ if ( m_flChargeBeginTime > 0 )
+ {
+ m_bFiredSecondary = true;
+ return;
+ }
+
+ if ( m_flNextSecondaryAttack > gpGlobals->curtime )
+ {
+#ifndef CLIENT_DLL
+ if ( m_flResetBurstEffect <= gpGlobals->curtime )
+ {
+ SetWeaponState( FT_STATE_IDLE );
+ }
+#endif
+ return;
+ }
+
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return;
+
+ if ( pOwner->GetWaterLevel() == WL_Eyes )
+ return;
+
+ if ( !CanAttack() )
+ {
+ SetWeaponState( FT_STATE_IDLE );
+ return;
+ }
+
+ int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType );
+
+ // charged airblast
+ int iChargedAirblast = 0;
+ CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
+ int iBuffType = 0;
+ CALL_ATTRIB_HOOK_INT( iBuffType, set_buff_type );
+ float flMultAmmoPerShot = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost );
+ int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot;
+
+ if ( iBuffType != 0 )
+ {
+ UseRage();
+ return;
+ }
+
+ if ( iAmmo < iAmmoPerShot )
+ return;
+
+ // normal air blast?
+ if ( iChargedAirblast == 0 && CanAirBlast() )
+ {
+ FireAirBlast( iAmmoPerShot );
+ return;
+ }
+
+#ifdef CLIENT_DLL
+ // Stop the flame if we're currently firing
+ StopFlame( false );
+#endif
+
+ SetWeaponState( FT_STATE_SECONDARY );
+
+#ifdef STAGING_ONLY
+ if ( RocketPackCanActivate( iAmmoPerShot ) )
+ {
+ RocketPackLaunch( iAmmoPerShot );
+ return;
+ }
+#endif // STAGING_ONLY
+
+#ifdef GAME_DLL
+ m_iWeaponMode = TF_WEAPON_SECONDARY_MODE;
+ m_flChargeBeginTime = gpGlobals->curtime;
+ SendWeaponAnim( ACT_VM_PULLBACK );
+ // @todo replace with the correct one
+ WeaponSound( SINGLE );
+#endif
+}
+
+#ifdef GAME_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CTFFlameThrower::GetDeflectionSize()
+{
+ const Vector vecBaseDeflectionSize = BaseClass::GetDeflectionSize();
+ float fMultiplier = 1.0f;
+
+ // int iChargedAirblast = 0;
+ // CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
+ // if ( iChargedAirblast != 0 )
+ // {
+ // fMultiplier *= RemapValClamped( ( gpGlobals->curtime - m_flChargeBeginTime ),
+ // 0.0f,
+ // GetChargeMaxTime(),
+ // AIRBLAST_CHARGE_MULT_MIN,
+ // AIRBLAST_CHARGE_MULT_MAX );
+ // }
+
+ // Allow custom attributes to scale the deflection size.
+ CALL_ATTRIB_HOOK_FLOAT( fMultiplier, deflection_size_multiplier );
+
+ return vecBaseDeflectionSize * fMultiplier;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+#ifdef _DEBUG
+ConVar tf_pushbackscalescale( "tf_pushbackscalescale", "1.0" );
+ConVar tf_pushbackscalescale_vertical( "tf_pushbackscalescale_vertical", "1.0" );
+#endif
+
+void ExtinguishPlayer( CEconEntity *pExtinguisher, CTFPlayer *pOwner, CTFPlayer *pTarget, const char *pExtinguisherName )
+{
+ pTarget->EmitSound( "TFPlayer.FlameOut" );
+
+ pTarget->m_Shared.RemoveCond( TF_COND_BURNING );
+
+ // we're going to limit the number of times you can be awarded bonus points to prevent exploits
+ if ( pOwner->ShouldGetBonusPointsForExtinguishEvent( pTarget->GetUserID() ) )
+ {
+ CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, 10 );
+ }
+
+ CRecipientFilter involved_filter;
+ involved_filter.AddRecipient( pOwner );
+ involved_filter.AddRecipient( pTarget );
+
+ UserMessageBegin( involved_filter, "PlayerExtinguished" );
+ WRITE_BYTE( pOwner->entindex() );
+ WRITE_BYTE( pTarget->entindex() );
+ MessageEnd();
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "player_extinguished" );
+ if ( event )
+ {
+ event->SetInt( "victim", pTarget->entindex() );
+ event->SetInt( "healer", pOwner->entindex() );
+
+ gameeventmanager->FireEvent( event, true );
+ }
+
+ // stats
+ EconEntity_OnOwnerKillEaterEvent( pExtinguisher, pOwner, pTarget, kKillEaterEvent_BurningAllyExtinguished );
+
+ UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"player_extinguished\" against \"%s<%i><%s><%s>\" with \"%s\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n",
+ pOwner->GetPlayerName(), pOwner->GetUserID(), pOwner->GetNetworkIDString(), pOwner->GetTeam()->GetName(),
+ pTarget->GetPlayerName(), pTarget->GetUserID(), pTarget->GetNetworkIDString(), pTarget->GetTeam()->GetName(),
+ pExtinguisherName, (int)pOwner->GetAbsOrigin().x, (int)pOwner->GetAbsOrigin().y, (int)pOwner->GetAbsOrigin().z,
+ (int)pTarget->GetAbsOrigin().x, (int)pTarget->GetAbsOrigin().y, (int)pTarget->GetAbsOrigin().z );
+}
+
+bool CTFFlameThrower::DeflectPlayer( CTFPlayer *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize )
+{
+ if ( pTarget->GetTeamNumber() == pOwner->GetTeamNumber() && pTarget != pOwner )
+ {
+ if ( pTarget->m_Shared.InCond( TF_COND_BURNING ) && SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUT_OUT_TEAMMATES ) )
+ {
+ ExtinguishPlayer( this, pOwner, pTarget, "tf_weapon_flamethrower" );
+
+ // Return health to the Pyro.
+ // We may want to cap the amount of health per extinguish but for now lets test this
+ int iRestoreHealthOnExtinguish = 0;
+ CALL_ATTRIB_HOOK_INT( iRestoreHealthOnExtinguish, extinguish_restores_health );
+ if ( iRestoreHealthOnExtinguish > 0 )
+ {
+ pOwner->TakeHealth( iRestoreHealthOnExtinguish, DMG_GENERIC );
+ IGameEvent *healevent = gameeventmanager->CreateEvent( "player_healonhit" );
+ if ( healevent )
+ {
+ healevent->SetInt( "amount", iRestoreHealthOnExtinguish );
+ healevent->SetInt( "entindex", pOwner->entindex() );
+ item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX;
+ if ( GetAttributeContainer() && GetAttributeContainer()->GetItem() )
+ {
+ healingItemDef = GetAttributeContainer()->GetItem()->GetItemDefIndex();
+ }
+ healevent->SetInt( "weapon_def_index", healingItemDef );
+
+ gameeventmanager->FireEvent( healevent );
+ }
+ }
+ }
+
+ return false;
+ }
+
+ if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK ) )
+ {
+ int iReverseBlast = 0;
+ CALL_ATTRIB_HOOK_INT( iReverseBlast, reverse_airblast );
+
+ // Against players, let's force the pyro to be actually looking at them.
+ // We'll be a bit more laxed when it comes to aiming at rockets and grenades.
+ Vector vecToTarget;
+
+ if ( pTarget == pOwner )
+ {
+ vecToTarget = vecForward;
+ }
+ else
+ {
+ vecToTarget = pTarget->WorldSpaceCenter() - pOwner->WorldSpaceCenter();
+ VectorNormalize( vecToTarget );
+ }
+
+ // Quick Fix Uber is immune
+ if ( pTarget->m_Shared.InCond( TF_COND_MEGAHEAL ))
+ return false;
+
+
+ // Require our target be in a cone in front of us. Default threshold is the dot-product needs to be at least 0.8 = 1 - 0.2.
+ float flDot = DotProduct( vecForward, vecToTarget );
+ float flAirblastConeScale = 0.2f;
+ CALL_ATTRIB_HOOK_FLOAT( flAirblastConeScale, mult_airblast_cone_scale );
+ float flAirblastConeThreshold = Clamp(1.0f - flAirblastConeScale, 0.0f, 1.0f);
+ if (flDot < flAirblastConeThreshold)
+ {
+ return false;
+ }
+
+ if ( pTarget != pOwner )
+ {
+ pTarget->SetAbsVelocity( vec3_origin );
+
+ if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__STUN ) )
+ {
+ if ( !pTarget->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) )
+ {
+ pTarget->m_Shared.StunPlayer( tf_player_movement_stun_time.GetFloat(), 1.f, TF_STUN_MOVEMENT, pOwner );
+ }
+ }
+
+ if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__VIEW_PUNCH ) )
+ {
+ pTarget->ApplyPunchImpulseX( RandomInt( 10, 15 ) );
+ }
+ }
+
+ pTarget->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:0,victim:1" );
+
+ float flForce = AirBurstDamageForce( pTarget->WorldAlignSize(), 60, 6.f );
+
+ CALL_ATTRIB_HOOK_FLOAT( flForce, airblast_pushback_scale );
+
+#ifdef _DEBUG
+ Vector vecForce = vecToTarget * flForce * tf_pushbackscalescale.GetFloat();
+#else
+ Vector vecForce = vecToTarget * flForce;
+#endif
+
+ if ( iReverseBlast )
+ {
+ vecForce = -vecForce;
+ }
+
+ float flVerticalPushbackScale = tf_flamethrower_burst_zvelocity.GetFloat();
+ if ( iReverseBlast )
+ {
+ // Don't give quite so big a vertical kick if we're sucking rather than blowing...
+ flVerticalPushbackScale *= 0.75f;
+ }
+
+#ifdef STAGING_ONLY
+ if ( !( pTarget == pOwner && pOwner->GetGroundEntity() ) )
+#endif
+ {
+ CALL_ATTRIB_HOOK_FLOAT( flVerticalPushbackScale, airblast_vertical_pushback_scale );
+ }
+
+#ifdef _DEBUG
+ vecForce.z += flVerticalPushbackScale * tf_pushbackscalescale_vertical.GetFloat();
+
+ /*
+ // Kyle says: this will force players off the ground for at least one frame.
+ // This is disabled on purpose right now to match previous flamethrower functionality.
+ if ( pTarget->GetFlags() & FL_ONGROUND )
+ {
+ vecForce.z += 268.3281572999747f;
+ }
+ */
+#else
+ vecForce.z += flVerticalPushbackScale;
+#endif
+
+ // Apply AirBlastImpulse
+ pTarget->ApplyAirBlastImpulse( vecForce );
+
+ // Make sure we get credit for the airblast if the target falls to its death
+ pTarget->m_AchievementData.AddDamagerToHistory( pOwner );
+
+ SendObjectDeflectedEvent( pOwner, pTarget, TF_WEAPON_NONE, pTarget ); // TF_WEAPON_NONE means the player got pushed
+
+ // If the target is charging, stop the charge and keep the charge meter where it is.
+ pTarget->m_Shared.InterruptCharge();
+
+ // Track for achievements
+ pTarget->m_AchievementData.AddPusherToHistory( pOwner );
+
+ // Give bonus points whenever a pyro pushes high-value targets back
+ if ( TFGameRules() && ( pTarget->IsMiniBoss() || pTarget->m_Shared.IsInvulnerable() ) )
+ {
+ int nAmount = pTarget->IsMiniBoss() ? 10 : 5;
+ CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, nAmount );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::PlayDeflectionSound( bool bPlayer )
+{
+ if ( bPlayer )
+ {
+ EmitSound( "TFPlayer.AirBlastImpact" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::DeflectEntity( CBaseEntity *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize )
+{
+ Assert( pTarget );
+ Assert( pOwner );
+
+ if ( !SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_REFLECT_PROJECTILES ) )
+ return false;
+
+ // can't deflect things on our own team
+ // except the passtime ball when in passtime mode
+ if ( (pTarget->GetTeamNumber() == pOwner->GetTeamNumber())
+ && !(g_pPasstimeLogic && (g_pPasstimeLogic->GetBall() == pTarget)) )
+ {
+ return false;
+ }
+
+ // Grab the owner of the projectile *before* we reflect it.
+ CTFPlayer *pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pTarget );
+ if ( !pTFPlayerVictim )
+ {
+ pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pTarget->GetOwnerEntity() );
+ }
+
+ if ( !pTFPlayerVictim )
+ {
+ // We can't use OwnerEntity for grenades, because then the owner can't shoot them with his hitscan weapons (due to collide rules)
+ // Thrower is used to store the person who threw the grenade, for damage purposes.
+ CBaseGrenade *pBaseGrenade = dynamic_cast< CBaseGrenade*>( pTarget );
+ if ( pBaseGrenade )
+ {
+ pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pBaseGrenade->GetThrower() );
+ }
+ }
+
+ if ( !pTFPlayerVictim )
+ {
+ // Is the OwnerEntity() a base object, like a sentry gun shooting rockets at us?
+ if ( pTarget->GetOwnerEntity() && pTarget->GetOwnerEntity()->IsBaseObject() )
+ {
+ CBaseObject *pObj = dynamic_cast<CBaseObject *>( pTarget->GetOwnerEntity() );
+ if ( pObj )
+ {
+ pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pObj->GetOwner() );
+ }
+ }
+ }
+
+ bool bDeflected = BaseClass::DeflectEntity( pTarget, pOwner, vecForward, vecCenter, vecSize );
+ if ( bDeflected )
+ {
+ pTarget->EmitSound( "Weapon_FlameThrower.AirBurstAttackDeflect" );
+
+ EconEntity_OnOwnerKillEaterEvent( this, pOwner, pTFPlayerVictim, kKillEaterEvent_ProjectileReflect );
+ }
+ return bDeflected;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::Lower( void )
+{
+ if ( BaseClass::Lower() )
+ {
+ // If we were firing, stop
+ if ( m_iWeaponState > FT_STATE_IDLE )
+ {
+ SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
+ SetWeaponState( FT_STATE_IDLE );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the position of the tip of the muzzle at it appears visually
+//-----------------------------------------------------------------------------
+Vector CTFFlameThrower::GetVisualMuzzlePos()
+{
+ return GetMuzzlePosHelper( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the position at which to spawn flame damage entities
+//-----------------------------------------------------------------------------
+Vector CTFFlameThrower::GetFlameOriginPos()
+{
+ return GetMuzzlePosHelper( false );
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFFlameThrower::GetFlameHitRatio( void )
+{
+ // Safety net to avoid divide by zero
+ if ( m_iActiveFlames == 0 )
+ return 0.1f;
+
+ float flRatio = ( ( (float)m_iDamagingFlames ) / ( (float)m_iActiveFlames ) );
+ //Msg( "Act: %d Dmg: %d\n", m_iActiveFlames, m_iDamagingFlames );
+
+ return flRatio;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::IncrementFlameDamageCount( void )
+{
+ m_iDamagingFlames++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::DecrementFlameDamageCount( void )
+{
+ if ( m_iDamagingFlames <= 0 )
+ return;
+
+ m_iDamagingFlames--;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::IncrementActiveFlameCount( void )
+{
+ m_iActiveFlames++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::DecrementActiveFlameCount( void )
+{
+ if ( m_iActiveFlames <= 0 )
+ return;
+
+ m_iActiveFlames--;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::ResetFlameHitCount( void )
+{
+ m_iDamagingFlames = 0;
+ m_iActiveFlames = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: UI Progress
+//-----------------------------------------------------------------------------
+float CTFFlameThrower::GetProgress( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return 0.f;
+
+ return pPlayer->m_Shared.GetRageMeter() / 100.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: UI Progress (same as GetProgress() without the division by 100.0f)
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::IsRageFull( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return false;
+
+ return ( pPlayer->m_Shared.GetRageMeter() >= 100.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::EffectMeterShouldFlash( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return false;
+
+ if ( pPlayer && (IsRageFull() || pPlayer->m_Shared.IsRageDraining()) )
+ return true;
+ else
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the position of the tip of the muzzle
+//-----------------------------------------------------------------------------
+Vector CTFFlameThrower::GetMuzzlePosHelper( bool bVisualPos )
+{
+ Vector vecMuzzlePos;
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( pOwner )
+ {
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( pOwner->GetAbsAngles(), &vecForward, &vecRight, &vecUp );
+ vecMuzzlePos = pOwner->Weapon_ShootPosition();
+ vecMuzzlePos += vecRight * TF_FLAMETHROWER_MUZZLEPOS_RIGHT;
+ // if asking for visual position of muzzle, include the forward component
+ if ( bVisualPos )
+ {
+ vecMuzzlePos += vecForward * TF_FLAMETHROWER_MUZZLEPOS_FORWARD;
+ }
+ }
+ return vecMuzzlePos;
+}
+
+void CTFFlameThrower::CalculateHalloweenSpell( void )
+{
+ m_bHasHalloweenSpell.Set( false );
+ if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
+ {
+ int iHalloweenSpell = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( this, iHalloweenSpell, halloween_green_flames );
+ m_bHasHalloweenSpell.Set( iHalloweenSpell > 0 );
+ }
+}
+
+bool CTFFlameThrower::Deploy( void )
+{
+#if defined( CLIENT_DLL )
+ StartPilotLight();
+ m_flFlameHitRatio = 0;
+ m_flPrevFlameHitRatio = -1;
+ m_flChargeBeginTime = 0;
+
+ m_bEffectsThinking = true;
+ SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" );
+
+ StopFullCritEffect();
+#endif // CLIENT_DLL
+
+ CalculateHalloweenSpell();
+
+ return BaseClass::Deploy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::FireGameEvent( IGameEvent *event )
+{
+ if ( FStrEq( event->GetName(), "recalculate_holidays" ) )
+ {
+ CalculateHalloweenSpell();
+ }
+}
+
+#if defined( CLIENT_DLL )
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::OnDataChanged(DataUpdateType_t updateType)
+{
+ BaseClass::OnDataChanged(updateType);
+
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ C_TFPlayer *pPlayerOwner = GetTFPlayerOwner();
+
+ //
+ bool bLocalPlayerAmmo = true;
+
+ if ( pPlayerOwner == pLocalPlayer )
+ {
+ bLocalPlayerAmmo = GetPlayerOwner()->GetAmmoCount( m_iPrimaryAmmoType ) > 0;
+ }
+
+ if ( IsCarrierAlive() && ( WeaponState() == WEAPON_IS_ACTIVE ) && bLocalPlayerAmmo == true )
+ {
+ if ( m_iWeaponState > FT_STATE_IDLE )
+ {
+ if ( ( m_iWeaponState == FT_STATE_SECONDARY && GetPlayerOwner() != C_BasePlayer::GetLocalPlayer() ) || m_iWeaponState != FT_STATE_SECONDARY )
+ {
+ StartFlame();
+
+#ifdef STAGING_ONLY
+ if ( ShootsNapalm() )
+ {
+ RestartParticleEffect();
+ }
+#endif // STAGING_ONLY
+ }
+ }
+ else
+ {
+ StartPilotLight();
+ }
+ }
+ else
+ {
+ StopFlame();
+ StopPilotLight();
+ StopFullCritEffect();
+ m_bEffectsThinking = false;
+ }
+
+ if ( pPlayerOwner == pLocalPlayer )
+ {
+ if ( m_pFiringLoop )
+ {
+ m_flFlameHitRatio = GetFlameHitRatio();
+ m_flFlameHitRatio = RemapValClamped( m_flFlameHitRatio, 0.0f, 1.0f, 1.0f, 100.f );
+
+ //Msg ( "%f\n", m_flFlameHitRatio );
+
+ if ( m_flFlameHitRatio != m_flPrevFlameHitRatio )
+ {
+ m_flPrevFlameHitRatio = m_flFlameHitRatio;
+
+ CLocalPlayerFilter filter;
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ // We play accent sounds based on accuracy
+ if ( m_flFlameHitRatio >= TF_FLAMETHROWER_HITACCURACY_HIGH )
+ {
+ controller.SoundChangePitch( m_pFiringLoop, 140, 0.1 );
+ m_szAccuracySound = "Weapon_FlameThrower.FireHitHard";
+ }
+ else
+ {
+ controller.SoundChangePitch( m_pFiringLoop, 100, 0.1 );
+
+ // If our accuracy is too low
+ if ( m_pFiringAccuracyLoop )
+ {
+ controller.SoundDestroy( m_pFiringAccuracyLoop );
+ m_pFiringAccuracyLoop = NULL;
+ }
+
+ return;
+ }
+
+ // Only start a new sound if there's been a change
+ if ( !m_pFiringAccuracyLoop )
+ {
+ m_pFiringAccuracyLoop = controller.SoundCreate( filter, entindex(), m_szAccuracySound );
+ controller.Play( m_pFiringAccuracyLoop, 1.0, 100 );
+ }
+
+ }
+ }
+ else if ( m_pFiringAccuracyLoop )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringAccuracyLoop );
+ m_pFiringAccuracyLoop = NULL;
+ }
+
+ if ( GetBuffType() > 0 )
+ {
+ if ( !m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() >= 100.0f )
+ {
+ m_bFullRageEffect = true;
+ m_MmmmphEffect.StartEffects( FullCritChargedEffectName() );
+ }
+ else if ( m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() < 100.0f )
+ {
+ StopFullCritEffect();
+ m_MmmmphEffect.StopEffects();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::UpdateOnRemove( void )
+{
+ m_FlameEffects.StopEffects();
+ m_MmmmphEffect.StopEffects();
+ StopPilotLight();
+ StopFullCritEffect();
+ m_bEffectsThinking = false;
+
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::SetDormant( bool bDormant )
+{
+ // If I'm going from active to dormant and I'm carried by another player, stop our firing sound.
+ if ( !IsCarriedByLocalPlayer() )
+ {
+ if ( !IsDormant() && bDormant )
+ {
+ StopFlame();
+ StopPilotLight();
+ StopFullCritEffect();
+ m_bEffectsThinking = false;
+ }
+ }
+
+ // Deliberately skip base combat weapon to avoid being holstered
+ C_BaseEntity::SetDormant( bDormant );
+}
+
+int CTFFlameThrower::GetWorldModelIndex( void )
+{
+ int iParticleEffectIndex = 0;
+ CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
+
+ // Pyro bubble wand support.
+ if ( iParticleEffectIndex == 3 )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
+ if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && pPlayer->m_Shared.GetTauntIndex() == TAUNT_BASE_WEAPON )
+ {
+ // While we are taunting, replace our normal world model with the bubble wand.
+ m_iWorldModelIndex = modelinfo->GetModelIndex( TF_WEAPON_BUBBLE_WAND_MODEL );
+ return m_iWorldModelIndex;
+ }
+ }
+
+ return BaseClass::GetWorldModelIndex();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::StartFlame()
+{
+ if ( m_iWeaponState == FT_STATE_SECONDARY )
+ {
+ GetAppropriateWorldOrViewModel()->ParticleProp()->Create( "pyro_blast", PATTACH_POINT_FOLLOW, "muzzle" );
+ CLocalPlayerFilter filter;
+ const char *shootsound = GetShootSound( WPN_DOUBLE );
+ EmitSound( filter, entindex(), shootsound );
+
+ return;
+ }
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ // normally, crossfade between start sound & firing loop in 3.5 sec
+ float flCrossfadeTime = 3.5;
+
+ if ( m_pFiringLoop && ( m_bCritFire != m_bFiringLoopCritical ) )
+ {
+ // If we're firing and changing between critical & noncritical, just need to change the firing loop.
+ // Set crossfade time to zero so we skip the start sound and go to the loop immediately.
+
+ flCrossfadeTime = 0;
+ StopFlame( true );
+ }
+
+ StopPilotLight();
+
+ if ( !m_pFiringStartSound && !m_pFiringLoop )
+ {
+ // NVNT if the local player is owning this weapon, process the start event
+ if ( C_BasePlayer::GetLocalPlayer() == GetOwner() && haptics )
+ haptics->ProcessHapticEvent(2,"Weapons","flamer_start");
+
+ RestartParticleEffect();
+ CLocalPlayerFilter filter;
+
+ // Play the fire start sound
+ const char *shootsound = GetShootSound( SINGLE );
+ if ( flCrossfadeTime > 0.0 )
+ {
+ // play the firing start sound and fade it out
+ m_pFiringStartSound = controller.SoundCreate( filter, entindex(), shootsound );
+ controller.Play( m_pFiringStartSound, 1.0, 100 );
+ controller.SoundChangeVolume( m_pFiringStartSound, 0.0, flCrossfadeTime );
+ }
+
+ // Start the fire sound loop and fade it in
+ if ( m_bCritFire )
+ {
+ shootsound = GetShootSound( BURST );
+ }
+ else
+ {
+ shootsound = GetShootSound( SPECIAL1 );
+ }
+ m_pFiringLoop = controller.SoundCreate( filter, entindex(), shootsound );
+ m_bFiringLoopCritical = m_bCritFire;
+
+ // play the firing loop sound and fade it in
+ if ( flCrossfadeTime > 0.0 )
+ {
+ controller.Play( m_pFiringLoop, 0.0, 100 );
+ controller.SoundChangeVolume( m_pFiringLoop, 1.0, flCrossfadeTime );
+ }
+ else
+ {
+ controller.Play( m_pFiringLoop, 1.0, 100 );
+ }
+ }
+
+ // check our "hit" sound
+ if ( m_bHitTarget != m_bFiringHitTarget )
+ {
+ if ( m_bHitTarget == false )
+ {
+ StopHitSound();
+ }
+ else
+ {
+ char *pchFireHitSound = "Weapon_FlameThrower.FireHit";
+
+ int iParticleEffectIndex = 0;
+ CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
+
+ if ( iParticleEffectIndex == 3 )
+ {
+ pchFireHitSound = "Weapon_Rainblower.FireHit";
+ }
+
+ CLocalPlayerFilter filter;
+ m_pFiringHitLoop = controller.SoundCreate( filter, entindex(), pchFireHitSound );
+ controller.Play( m_pFiringHitLoop, 1.0, 100 );
+ }
+
+ m_bFiringHitTarget = m_bHitTarget;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::StopHitSound()
+{
+ if ( m_pFiringHitLoop )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringHitLoop );
+ m_pFiringHitLoop = NULL;
+ }
+
+ m_bHitTarget = m_bFiringHitTarget = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::StopFlame( bool bAbrupt /* = false */ )
+{
+ if ( ( m_pFiringLoop || m_pFiringStartSound ) && !bAbrupt )
+ {
+ // play a quick wind-down poof when the flame stops
+ CLocalPlayerFilter filter;
+ const char *shootsound = GetShootSound( SPECIAL3 );
+ EmitSound( filter, entindex(), shootsound );
+ }
+
+ if ( m_pFiringLoop )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringLoop );
+ m_pFiringLoop = NULL;
+ }
+
+ if ( m_pFiringStartSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringStartSound );
+ m_pFiringStartSound = NULL;
+ }
+
+ if ( m_FlameEffects.StopEffects() )
+ {
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer && pLocalPlayer == GetOwner() )
+ {
+ // NVNT local player is finished firing. send the stop event.
+ if ( haptics )
+ haptics->ProcessHapticEvent(2,"Weapons","flamer_stop");
+ }
+ }
+
+ if ( !bAbrupt )
+ {
+ StopHitSound();
+ }
+
+ m_iParticleWaterLevel = -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::StartPilotLight()
+{
+ if ( !m_pPilotLightSound )
+ {
+ StopFlame();
+
+ // Create the looping pilot light sound
+ const char *pilotlightsound = GetShootSound( SPECIAL2 );
+ CLocalPlayerFilter filter;
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ m_pPilotLightSound = controller.SoundCreate( filter, entindex(), pilotlightsound );
+
+ controller.Play( m_pPilotLightSound, 1.0, 100 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::StopPilotLight()
+{
+ if ( m_pPilotLightSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pPilotLightSound );
+ m_pPilotLightSound = NULL;
+ }
+}
+
+void CTFFlameThrower::StopFullCritEffect()
+{
+ m_bFullRageEffect = false;
+
+ m_MmmmphEffect.StopEffects();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::RestartParticleEffect( void )
+{
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return;
+
+ if ( m_iWeaponState != FT_STATE_FIRING && m_iWeaponState != FT_STATE_STARTFIRING )
+ {
+ return;
+ }
+
+ m_iParticleWaterLevel = pOwner->GetWaterLevel();
+
+ bool bIsFirstPersonView = IsFirstPersonView();
+
+ // Start the appropriate particle effect
+ const char *pszParticleEffect;
+ if ( pOwner->GetWaterLevel() == WL_Eyes )
+ {
+ pszParticleEffect = "flamethrower_underwater";
+ }
+ else
+ {
+ if ( m_bCritFire )
+ {
+ pszParticleEffect = FlameCritEffectName( bIsFirstPersonView );
+ }
+ else
+ {
+ pszParticleEffect = FlameEffectName( bIsFirstPersonView );
+ }
+ }
+
+ m_FlameEffects.StartEffects( pszParticleEffect );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char* CTFFlameThrower::FlameEffectName( bool bIsFirstPersonView )
+{
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return NULL;
+
+#ifdef STAGING_ONLY
+ if ( ShootsNapalm() )
+ {
+ return "muzzle_bignasty";
+ }
+#endif // STAGING_ONLY
+
+ // Halloween Spell
+ if ( m_bHasHalloweenSpell )
+ {
+ return "flamethrower_halloween";
+ }
+
+ int iParticleEffectIndex = 0;
+ CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
+
+ switch ( iParticleEffectIndex )
+ {
+ case 1: return "drg_phlo_stream";
+ case 2: return "flamethrower_giant_mvm";
+ case 3: return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" );
+ default: return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_blue" : "flamethrower" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char* CTFFlameThrower::FlameCritEffectName( bool bIsFirstPersonView )
+{
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return NULL;
+
+#ifdef STAGING_ONLY
+ if ( ShootsNapalm() )
+ {
+ return "muzzle_bignasty";
+ }
+#endif // STAGING_ONLY
+
+ // Halloween Spell
+ if ( m_bHasHalloweenSpell )
+ {
+ return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_halloween_crit_blue" : "flamethrower_halloween_crit_red" );
+ }
+
+ int iParticleEffectIndex = 0;
+ CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
+
+ switch ( iParticleEffectIndex )
+ {
+ case 1: return "drg_phlo_stream_crit";
+ case 2: return "flamethrower_crit_giant_mvm";
+ case 3: return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" );
+ default: return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_crit_blue" : "flamethrower_crit_red" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char* CTFFlameThrower::FullCritChargedEffectName( void )
+{
+ switch( GetTeamNumber() )
+ {
+ case TF_TEAM_BLUE: return "medicgun_invulnstatus_fullcharge_blue";
+ case TF_TEAM_RED: return "medicgun_invulnstatus_fullcharge_red";
+ default: return "";
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::ClientEffectsThink( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ if ( !pPlayer->IsLocalPlayer() )
+ return;
+
+ if ( !pPlayer->GetViewModel() )
+ return;
+
+ if ( !m_bEffectsThinking )
+ return;
+
+ float flRageInverse = 1.f;
+
+ if ( GetBuffType() > 0 )
+ {
+ flRageInverse = 1.0f - ( pPlayer->m_Shared.GetRageMeter() / 100.0f );
+ if ( flRageInverse < 1.0f )
+ {
+ // We have some rage, let's spark!
+ ParticleProp()->Init( this );
+ CNewParticleEffect* pEffect = ParticleProp()->Create( "drg_bison_idle", PATTACH_POINT_FOLLOW, "muzzle" );
+ if ( pEffect )
+ {
+ pEffect->SetControlPoint( CUSTOM_COLOR_CP1, GetParticleColor( 1 ) );
+ pEffect->SetControlPoint( CUSTOM_COLOR_CP2, GetParticleColor( 2 ) );
+ }
+ }
+ }
+
+ SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime + 0.1f + RandomFloat( 1.0f, 5.0f ) * flRageInverse, "EFFECTS_THINK" );
+}
+
+void CTFFlameThrower::FlameEffect_t::StartEffects( const char* pszEffectName )
+{
+ // Stop any old flame effects
+ StopEffects();
+
+ // Figure out which weapon this flame effect is to be attached to. Store this for
+ // later so we know which weapon to deactivate the effect on
+ m_hEffectWeapon = m_pOwner->GetWeaponForEffect();
+
+ if( m_hEffectWeapon )
+ {
+ CParticleProperty* pParticleProp = m_hEffectWeapon->ParticleProp();
+ if( pParticleProp )
+ {
+ // Flame on
+ m_pFlameEffect = pParticleProp->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
+ }
+ }
+}
+
+bool CTFFlameThrower::FlameEffect_t::StopEffects()
+{
+ bool bStopped = false;
+ // Stop any old flame effects
+ if ( m_pFlameEffect && m_hEffectWeapon )
+ {
+ m_hEffectWeapon->ParticleProp()->StopEmission( m_pFlameEffect );
+ bStopped = true;
+ }
+
+ m_pFlameEffect = NULL;
+ m_hEffectWeapon = NULL;
+
+ return bStopped;
+}
+
+#else
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::HitTargetThink( void )
+{
+ if ( ( m_flTimeToStopHitSound > 0 ) && ( m_flTimeToStopHitSound < gpGlobals->curtime ) )
+ {
+ m_bHitTarget = false;
+ m_flTimeToStopHitSound = 0;
+ SetContextThink( NULL, 0, s_pszFlameThrowerHitTargetThink );
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameThrower::SetHitTarget( void )
+{
+ if ( m_iWeaponState > FT_STATE_IDLE )
+ {
+ m_bHitTarget = true;
+ m_flTimeToStopHitSound = gpGlobals->curtime + 0.2;
+
+ // Start the hit target thinking
+ SetContextThink( &CTFFlameThrower::HitTargetThink, gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink );
+ }
+}
+
+#endif
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::RocketPackCanActivate( int nAmmoCost )
+{
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return false;
+
+ int nRocketPack = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, nRocketPack, rocket_pack );
+ if ( !nRocketPack )
+ return false;
+
+ if ( pOwner->m_Shared.IsLoser() )
+ return false;
+
+ if ( pOwner->m_Shared.InCond( TF_COND_STUNNED ) )
+ return false;
+
+ if ( pOwner->IsTaunting() )
+ return false;
+
+// if ( pOwner->m_Shared.GetChargeMeter() < 100.f )
+// return false;
+
+ if ( pOwner->GetAmmoCount( TF_AMMO_PRIMARY ) < nAmmoCost )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::RocketPackLaunch( int nAmmoCost )
+{
+ CTFPlayer *pOwner = GetTFPlayerOwner();
+ if ( !pOwner )
+ return false;
+
+#ifdef CLIENT_DLL
+ StopFlame( false );
+#endif // CLIENT_DLL
+
+#ifdef GAME_DLL
+ // Launch
+ if ( !pOwner->m_Shared.InCond( TF_COND_ROCKETPACK ) )
+ {
+ pOwner->m_Shared.AddCond( TF_COND_ROCKETPACK );
+ pOwner->m_Shared.StunPlayer( 0.5f, 1.0f, TF_STUN_MOVEMENT );
+ }
+
+ Vector vecDir;
+ pOwner->EyeVectors( &vecDir );
+ pOwner->SetAbsVelocity( vec3_origin );
+ Vector vecFlightDir = -vecDir;
+ VectorNormalize( vecFlightDir );
+ float flForce = 450.f;
+
+ const float flPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 1.8f; // Greater force while airborne
+ const float flVertPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 0.25f; // Less vertical force while airborne
+ Vector vecForce = vecFlightDir * -flForce * flPushScale;
+ vecForce.z += 1.f * flForce * flVertPushScale;
+ pOwner->RemoveFlag( FL_ONGROUND );
+ pOwner->ApplyAbsVelocityImpulse( vecForce );
+
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.75f;
+ m_flNextPrimaryAttack = gpGlobals->curtime + 1.f;
+ m_flResetBurstEffect = gpGlobals->curtime + 0.05f;
+ m_bFiredSecondary = true;
+ m_flChargeBeginTime = 0;
+
+ pOwner->RemoveAmmo( nAmmoCost, m_iPrimaryAmmoType );
+ pOwner->EmitSound( "Equipment.RocketPack_Activate" );
+#endif // GAME_DLL
+
+#ifdef CLIENT_DLL
+ if ( prediction->IsFirstTimePredicted() == true )
+ {
+ StartFlame();
+ }
+#endif // CLIENT_DLL
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameThrower::ShootsNapalm( void )
+{
+ int iNapalm = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iNapalm, mod_flamethrower_napalm );
+ return ( iNapalm > 0 );
+}
+#endif // STAGING_ONLY
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameRocket, DT_TFFlameRocket )
+BEGIN_NETWORK_TABLE( CTFFlameRocket, DT_TFFlameRocket )
+END_NETWORK_TABLE()
+
+#ifdef GAME_DLL
+LINK_ENTITY_TO_CLASS( tf_flame, CTFFlameEntity );
+IMPLEMENT_AUTO_LIST( ITFFlameEntityAutoList );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFFlameEntity::CTFFlameEntity()
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawns this entity
+//-----------------------------------------------------------------------------
+void CTFFlameEntity::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ // don't collide with anything, we do our own collision detection in our think method
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( FSOLID_NOT_SOLID );
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+ // move noclip: update position from velocity, that's it
+ SetMoveType( MOVETYPE_NOCLIP, MOVECOLLIDE_DEFAULT );
+ AddEFlags( EFL_NO_WATER_VELOCITY_CHANGE );
+
+ float iBoxSize = tf_flamethrower_boxsize.GetFloat();
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), iBoxSize, mult_flame_size );
+ UTIL_SetSize( this, -Vector( iBoxSize, iBoxSize, iBoxSize ), Vector( iBoxSize, iBoxSize, iBoxSize ) );
+
+ // Setup attributes.
+ m_takedamage = DAMAGE_NO;
+ m_vecInitialPos = GetAbsOrigin();
+ m_vecPrevPos = m_vecInitialPos;
+
+ // Track total active flame entities
+ m_hFlameThrower = dynamic_cast< CTFFlameThrower* >( GetOwnerEntity() );
+ if ( m_hFlameThrower )
+ {
+ m_hFlameThrower->IncrementActiveFlameCount();
+ m_bBurnedEnemy = false;
+
+ float flFlameLife = tf_flamethrower_flametime.GetFloat();
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), flFlameLife, mult_flame_life );
+ m_flTimeRemove = gpGlobals->curtime + ( flFlameLife * random->RandomFloat( 0.9f, 1.1f ) );
+ }
+ else
+ {
+ m_flTimeRemove = gpGlobals->curtime + 3.f;
+ }
+
+ // Setup the think function.
+ SetThink( &CTFFlameEntity::FlameThink );
+ SetNextThink( gpGlobals->curtime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates an instance of this entity
+//-----------------------------------------------------------------------------
+CTFFlameEntity *CTFFlameEntity::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flSpeed, int iDmgType, float flDmgAmount, bool bAlwaysCritFromBehind, bool bRandomize )
+{
+ CTFFlameEntity *pFlame = static_cast<CTFFlameEntity*>( CBaseEntity::Create( "tf_flame", vecOrigin, vecAngles, pOwner ) );
+ if ( !pFlame )
+ return NULL;
+
+ // Initialize the owner.
+ pFlame->SetOwnerEntity( pOwner );
+ if ( pOwner->GetOwnerEntity() )
+ pFlame->m_hAttacker = pOwner->GetOwnerEntity();
+ else
+ pFlame->m_hAttacker = pOwner;
+ CBaseEntity *pAttacker = (CBaseEntity *) pFlame->m_hAttacker;
+ if ( pAttacker )
+ {
+ pFlame->m_iAttackerTeam = pAttacker->GetTeamNumber();
+ }
+
+ // Set team.
+ pFlame->ChangeTeam( pOwner->GetTeamNumber() );
+ pFlame->m_iDmgType = iDmgType;
+ pFlame->m_flDmgAmount = flDmgAmount;
+
+ // Setup the initial velocity.
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
+
+ float flFlameLifeMult = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, flFlameLifeMult, mult_flame_life );
+ float velocity = flFlameLifeMult * flSpeed;
+ pFlame->m_vecBaseVelocity = vecForward * velocity;
+ float iFlameSizeMult = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, iFlameSizeMult, mult_flame_size );
+ if ( bRandomize )
+ {
+ pFlame->m_vecBaseVelocity += RandomVector( -velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat(), velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat() );
+ }
+ if ( pOwner->GetOwnerEntity() )
+ {
+ pFlame->m_vecAttackerVelocity = pOwner->GetOwnerEntity()->GetAbsVelocity();
+ }
+ pFlame->SetAbsVelocity( pFlame->m_vecBaseVelocity );
+ // Setup the initial angles.
+ pFlame->SetAbsAngles( vecAngles );
+ pFlame->SetCritFromBehind( bAlwaysCritFromBehind );
+
+ return pFlame;
+}
+
+//-----------------------------------------------------------------------------
+class CFlameEntityEnum : public IEntityEnumerator
+{
+public:
+ CFlameEntityEnum( CBaseEntity *pShooter )
+ {
+ m_pShooter = pShooter;
+ }
+
+ virtual bool EnumEntity( IHandleEntity *pHandleEntity )
+ {
+ CBaseEntity *pEnt = static_cast<CBaseEntity*>( pHandleEntity );
+
+ // Ignore collisions with the shooter
+ if ( pEnt == m_pShooter )
+ return true;
+
+ if ( pEnt->IsPlayer() && pEnt->IsAlive() )
+ {
+ m_Targets.AddToTail( pEnt );
+ }
+ else if ( pEnt->MyNextBotPointer() && pEnt->IsAlive() )
+ {
+ // add non-player bots
+ m_Targets.AddToTail( pEnt );
+ }
+ else if ( pEnt->IsBaseObject() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() )
+ {
+ // only add enemy objects
+ m_Targets.AddToTail( pEnt );
+ }
+ else if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() && FClassnameIs( pEnt, "tf_robot_destruction_robot" ) )
+ {
+ // only add enemy robots
+ m_Targets.AddToTail( pEnt );
+ }
+ else if ( FClassnameIs( pEnt, "func_breakable" ) || FClassnameIs( pEnt, "tf_pumpkin_bomb" ) || FClassnameIs( pEnt, "tf_merasmus_trick_or_treat_prop" ) )
+ {
+ m_Targets.AddToTail( pEnt );
+ }
+
+ return true;
+ }
+
+ const CUtlVector< CBaseEntity* >& GetTargets() { return m_Targets; }
+
+public:
+ Ray_t *m_pRay;
+ CBaseEntity *m_pShooter;
+ CUtlVector< CBaseEntity* > m_Targets;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Think method
+//-----------------------------------------------------------------------------
+void CTFFlameEntity::FlameThink( void )
+{
+ TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 )
+ // if we've expired, remove ourselves
+ if ( gpGlobals->curtime >= m_flTimeRemove )
+ {
+ RemoveFlame();
+ return;
+ }
+ else
+ {
+ // Always think, if we haven't died due to our timeout.
+ SetNextThink( gpGlobals->curtime );
+ }
+
+ // Did we move? should we check collision?
+ if ( GetAbsOrigin() != m_vecPrevPos )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s Collision", __FUNCTION__ );
+ CTFPlayer *pAttacker = dynamic_cast<CTFPlayer *>( (CBaseEntity *) m_hAttacker );
+ if ( !pAttacker )
+ return;
+
+ // Create a ray for flame entity to trace
+ Ray_t rayWorld;
+ rayWorld.Init( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );
+
+ // check against world first
+ // if we collide with world, just destroy the flame
+ trace_t trWorld;
+ UTIL_TraceRay( rayWorld, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &trWorld );
+
+ bool bHitWorld = trWorld.startsolid || trWorld.fraction < 1.f;
+
+ // update the ray
+ Ray_t rayEnt;
+ rayEnt.Init( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );
+
+ // burn all entities that we should collide with
+ CFlameEntityEnum eFlameEnum( pAttacker );
+ enginetrace->EnumerateEntities( rayEnt, false, &eFlameEnum );
+
+ bool bHitSomething = false;
+ FOR_EACH_VEC( eFlameEnum.GetTargets(), i )
+ {
+ CBaseEntity *pEnt = eFlameEnum.GetTargets()[i];
+
+ // skip ent that's already burnt by this flame
+ int iIndex = m_hEntitiesBurnt.Find( pEnt );
+ if ( iIndex != m_hEntitiesBurnt.InvalidIndex() )
+ continue;
+
+ // if we're removing the flame this frame from hitting world, check if we hit this ent before hitting the world
+ if ( bHitWorld )
+ {
+ trace_t trEnt;
+ enginetrace->ClipRayToEntity( rayWorld, MASK_SOLID | CONTENTS_HITBOX, pEnt, &trEnt );
+ // hit world before this ent, skip it
+ if ( trEnt.fraction >= trWorld.fraction )
+ continue;
+ }
+
+ // burn them all!
+ if ( pEnt->IsPlayer() && pEnt->InSameTeam( pAttacker ) )
+ {
+ OnCollideWithTeammate( ToTFPlayer( pEnt ) );
+ }
+ else
+ {
+ OnCollide( pEnt );
+ }
+
+ bHitSomething = true;
+ }
+
+ // now, let's see if the flame visual could have actually hit this player. Trace backward from the
+ // point of impact to where the flame was fired, see if we hit anything.
+ if ( bHitSomething && tf_debug_flamethrower.GetBool() )
+ {
+ NDebugOverlay::SweptBox( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 255, 0, 100, 5.0 );
+ NDebugOverlay::EntityBounds( this, 255, 255, 0, 100, 5.0 );
+ }
+
+ // remove the flame if it hits the world
+ if ( bHitWorld )
+ {
+ if ( tf_debug_flamethrower.GetInt() )
+ {
+ NDebugOverlay::SweptBox( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 0, 0, 100, 3.0 );
+ }
+
+ RemoveFlame();
+ }
+ }
+
+ // Reduce our base velocity by the air drag constant
+ m_vecBaseVelocity *= GetFlameDrag();
+
+ // Add our float upward velocity
+ Vector vecVelocity = m_vecBaseVelocity + Vector( 0, 0, GetFlameFloat() ) + m_vecAttackerVelocity;
+
+ // Update our velocity
+ SetAbsVelocity( vecVelocity );
+
+ // Render debug visualization if convar on
+ if ( tf_debug_flamethrower.GetInt() )
+ {
+ if ( m_hEntitiesBurnt.Count() > 0 )
+ {
+ int val = ( (int) ( gpGlobals->curtime * 10 ) ) % 255;
+ NDebugOverlay::EntityBounds(this, val, 255, val, 0 ,0 );
+ }
+ else
+ {
+ NDebugOverlay::EntityBounds(this, 0, 100, 255, 0 ,0) ;
+ }
+ }
+
+ m_vecPrevPos = GetAbsOrigin();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameEntity::SetHitTarget( void )
+{
+ if ( !m_hFlameThrower )
+ return;
+
+ m_hFlameThrower->SetHitTarget();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameEntity::RemoveFlame()
+{
+ UpdateFlameThrowerHitRatio();
+
+ UTIL_Remove( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when we've collided with another entity
+//-----------------------------------------------------------------------------
+void CTFFlameEntity::OnCollide( CBaseEntity *pOther )
+{
+ int nContents = UTIL_PointContents( GetAbsOrigin() );
+ if ( (nContents & MASK_WATER) )
+ {
+ RemoveFlame();
+ return;
+ }
+
+ // remember that we've burnt this player
+ m_hEntitiesBurnt.AddToTail( pOther );
+
+ float flDistance = GetAbsOrigin().DistTo( m_vecInitialPos );
+ float flDamage = m_flDmgAmount * RemapValClamped( flDistance, tf_flamethrower_maxdamagedist.GetFloat()/2, tf_flamethrower_maxdamagedist.GetFloat(), 1.0f, 0.70f );
+
+ flDamage = MAX( flDamage, 1.0 );
+ if ( tf_debug_flamethrower.GetInt() )
+ {
+ Msg( "Flame touch dmg: %.1f\n", flDamage );
+ }
+
+ CBaseEntity *pAttacker = m_hAttacker;
+ if ( !pAttacker )
+ return;
+
+ SetHitTarget();
+
+ int iDamageType = m_iDmgType;
+
+ if ( pOther && pOther->IsPlayer() )
+ {
+ CTFPlayer *pVictim = ToTFPlayer( pOther );
+ if ( IsBehindTarget( pOther ) )
+ {
+ if ( m_bCritFromBehind == true )
+ {
+ iDamageType |= DMG_CRITICAL;
+ }
+
+ if ( pVictim )
+ {
+ pVictim->HandleAchievement_Pyro_BurnFromBehind( ToTFPlayer( pAttacker ) );
+ }
+ }
+
+ // Pyro-specific
+ if ( pAttacker->IsPlayer() && pVictim )
+ {
+ CTFPlayer *pPlayerAttacker = ToTFPlayer( pAttacker );
+ if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_PYRO ) )
+ {
+ // burn the victim while taunting?
+ if ( pVictim->m_Shared.InCond( TF_COND_TAUNTING ) )
+ {
+ static CSchemaItemDefHandle flipTaunt( "Flippin' Awesome Taunt" );
+ // if I'm the one being flipped, and getting lit on fire
+ if ( !pVictim->IsTauntInitiator() && pVictim->GetTauntEconItemView() && pVictim->GetTauntEconItemView()->GetItemDefinition() == flipTaunt )
+ {
+ pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_IGNITE_PLAYER_BEING_FLIPPED );
+ }
+ }
+
+ pVictim->m_Shared.AddCond( TF_COND_HEALING_DEBUFF, 2.f, pAttacker );
+ }
+ }
+ }
+
+ CTakeDamageInfo info( GetOwnerEntity(), pAttacker, GetOwnerEntity(), flDamage, iDamageType, TF_DMG_CUSTOM_BURNING );
+ info.SetReportedPosition( pAttacker->GetAbsOrigin() );
+
+ if ( info.GetDamageType() & DMG_CRITICAL )
+ {
+ info.SetCritType( CTakeDamageInfo::CRIT_FULL );
+ }
+
+ // terrible hack for flames hitting the Merasmus props to get the particle effect in the correct position
+ if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) )
+ {
+ info.SetDamagePosition( GetAbsOrigin() );
+ }
+
+ // Track hits for the Flamethrower, which is used to change the weapon sound based on hit ratio
+ if ( m_hFlameThrower )
+ {
+ m_bBurnedEnemy = true;
+ m_hFlameThrower->IncrementFlameDamageCount();
+ }
+
+ // We collided with pOther, so try to find a place on their surface to show blood
+ trace_t pTrace;
+ UTIL_TraceLine( WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_SOLID|CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &pTrace );
+
+ pOther->DispatchTraceAttack( info, GetAbsVelocity(), &pTrace );
+ ApplyMultiDamage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameEntity::OnCollideWithTeammate( CTFPlayer *pPlayer )
+{
+ // Only care about Snipers
+ if ( !pPlayer->IsPlayerClass(TF_CLASS_SNIPER) )
+ return;
+
+ int iIndex = m_hEntitiesBurnt.Find( pPlayer );
+ if ( iIndex != m_hEntitiesBurnt.InvalidIndex() )
+ return;
+
+ m_hEntitiesBurnt.AddToTail( pPlayer );
+
+ // Does he have the bow?
+ CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
+ if ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_COMPOUND_BOW )
+ {
+ CTFCompoundBow *pBow = static_cast<CTFCompoundBow*>( pWpn );
+ pBow->SetArrowAlight( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFFlameEntity::IsBehindTarget( CBaseEntity *pTarget )
+{
+ return ( DotProductToTarget( pTarget ) > 0.8 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Utility to calculate dot product between facing angles of flame and target
+//-----------------------------------------------------------------------------
+float CTFFlameEntity::DotProductToTarget( CBaseEntity *pTarget )
+{
+ Assert( pTarget );
+
+ // Get the forward view vector of the target, ignore Z
+ Vector vecVictimForward;
+ AngleVectors( pTarget->EyeAngles(), &vecVictimForward, NULL, NULL );
+ vecVictimForward.z = 0.0f;
+ vecVictimForward.NormalizeInPlace();
+
+ Vector vecTraveling = m_vecBaseVelocity;
+ vecTraveling.z = 0.0f;
+ vecTraveling.NormalizeInPlace();
+
+ return DotProduct( vecVictimForward, vecTraveling );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFFlameEntity::UpdateFlameThrowerHitRatio( void )
+{
+ if ( !m_hFlameThrower )
+ return;
+
+ if ( m_bBurnedEnemy )
+ {
+ m_hFlameThrower->DecrementFlameDamageCount();
+ }
+
+ m_hFlameThrower->DecrementActiveFlameCount();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFFlameEntity::GetFlameFloat( void )
+{
+ return tf_flamethrower_float.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFFlameEntity::GetFlameDrag( void )
+{
+ return tf_flamethrower_drag.GetFloat();
+}
+
+#endif // GAME_DLL
+
+#ifdef STAGING_ONLY
+//-----------------------------------------------------------------------------
+// Purpose: Napalm
+//-----------------------------------------------------------------------------
+#ifdef GAME_DLL
+#define NAPALM_THINK_CONTEXT "CTFMedigunShield_ShieldThink"
+#endif // GAME_DLL
+
+LINK_ENTITY_TO_CLASS( tf_projectile_napalm, CTFProjectile_Napalm );
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Napalm, DT_TFProjectile_Napalm )
+
+BEGIN_NETWORK_TABLE( CTFProjectile_Napalm, DT_TFProjectile_Napalm )
+#ifdef GAME_DLL
+ SendPropEHandle( SENDINFO( m_hFlameThrower ) ),
+#else
+ RecvPropEHandle( RECVINFO( m_hFlameThrower ) ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFProjectile_Napalm )
+END_PREDICTION_DATA()
+
+// Data
+BEGIN_DATADESC( CTFProjectile_Napalm )
+#ifdef GAME_DLL
+DEFINE_THINKFUNC( NapalmThink ),
+#endif // GAME_DLL
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Napalm::CTFProjectile_Napalm()
+{
+#ifdef GAME_DLL
+ m_flRemoveTime = 0.f;
+ m_nHitCount = 0;
+ m_flLastBurnTime = 0.f;
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Napalm::~CTFProjectile_Napalm()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::Precache()
+{
+ // PrecacheModel( TF_MODEL_NAPALM );
+ PrecacheParticleSystem( "burninggibs" );
+ PrecacheParticleSystem( "flaming_arrow" );
+
+ PrecacheScriptSound( "Player.PlasmaDamage" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::Spawn()
+{
+ Precache();
+
+#ifdef GAME_DLL
+ m_flRemoveTime = gpGlobals->curtime + 2.2f;
+ SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime, NAPALM_THINK_CONTEXT );
+#endif // GAME_DLL
+
+ BaseClass::Spawn();
+
+#ifdef GAME_DLL
+ SetDetonateTimerLength( FLT_MAX );
+#endif // GAME_DLL
+}
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Napalm *CTFProjectile_Napalm::Create( CBaseCombatCharacter *pOwner, CTFFlameThrower *pLauncher )
+{
+ if ( pOwner )
+ {
+ Vector vecForward;
+ AngleVectors( pOwner->EyeAngles(), &vecForward, NULL, NULL );
+ CTFProjectile_Napalm *pProjectile = static_cast< CTFProjectile_Napalm* >( CBaseEntity::Create( "tf_projectile_napalm", pLauncher->GetVisualMuzzlePos(), pOwner->EyeAngles() ) );
+ if ( pProjectile )
+ {
+ pProjectile->ChangeTeam( pOwner->GetTeamNumber() );
+
+ // Setup the initial velocity.
+ float flVelocity = 1100.f;
+ pProjectile->m_vecBaseVelocity = vecForward * flVelocity;
+ pProjectile->m_vecBaseVelocity += RandomVector( -flVelocity * tf_flamethrower_vecrand.GetFloat(), flVelocity * tf_flamethrower_vecrand.GetFloat() );
+ pProjectile->InitGrenade( pProjectile->m_vecBaseVelocity, vec3_origin, pOwner, pLauncher->GetTFWpnData() );
+ pProjectile->m_hFlameThrower = pLauncher;
+
+ return pProjectile;
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFProjectile_Napalm::UpdateTransmitState()
+{
+ return SetTransmitState( FL_EDICT_PVSCHECK );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::NapalmThink( void )
+{
+ if ( gpGlobals->curtime > m_flRemoveTime )
+ {
+ SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
+ SetTouch( NULL );
+ return;
+ }
+
+ SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime + 0.1f, NAPALM_THINK_CONTEXT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::Explode( trace_t *pTrace, int bitsDamageType )
+{
+ if ( !m_nHitCount )
+ {
+ SetModelName( NULL_STRING );
+ AddSolidFlags( FSOLID_TRIGGER );
+
+ m_takedamage = DAMAGE_NO;
+
+ // Pull out of the wall a bit.
+ if ( pTrace->fraction != 1.f )
+ {
+ SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
+ }
+
+ CTFPlayer *pThrower = ToTFPlayer( GetThrower() );
+ if ( pThrower )
+ {
+ const Vector& vecOrigin = GetAbsOrigin();
+
+ // Any effects from the initial explosion
+ if ( InitialExplodeEffects( pThrower, pTrace ) )
+ {
+ // Particle
+ if ( GetImpactEffect() )
+ {
+ CPVSFilter filter( vecOrigin );
+
+ // Stick effect on the player
+ CBaseEntity *pEnt = pTrace->m_pEnt;
+ if ( pEnt && pEnt->IsPlayer() && !pThrower->InSameTeam( pEnt ) )
+ {
+ // TE_TFParticleEffect( filter, 0.f, GetImpactEffect(), pEnt->GetAbsOrigin(), vec3_angle, pEnt, PATTACH_ABSORIGIN_FOLLOW );
+ m_flRemoveTime = gpGlobals->curtime;
+ }
+ // World
+ else
+ {
+ TE_TFParticleEffect( filter, 0.0, GetImpactEffect(), vecOrigin, vec3_angle );
+ }
+ }
+
+ // Sounds
+ // EmitSound( "Player.PlasmaDamage" );
+
+ // Treat this trace exactly like radius damage
+ CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE );
+
+ // Burn players in impact range
+ CBaseEntity *pListOfEntities[32];
+ int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC );
+ for ( int i = 0; i < iEntities; ++i )
+ {
+ if ( pThrower->InSameTeam( pListOfEntities[i] ) )
+ continue;
+
+ CBaseCombatCharacter *pBaseCombatCharacter = NULL;
+ CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] );
+ if ( !pPlayer )
+ {
+ pBaseCombatCharacter = dynamic_cast< CBaseCombatCharacter* >( pListOfEntities[i] );
+ }
+ else
+ {
+ pBaseCombatCharacter = pPlayer;
+ }
+
+ if ( !pBaseCombatCharacter || !pBaseCombatCharacter->IsAlive() )
+ continue;
+
+ // Do a quick trace to see if there's any geometry in the way.
+ trace_t pImpactTrace;
+ UTIL_TraceLine( GetAbsOrigin(), pBaseCombatCharacter->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &pImpactTrace );
+ if ( pImpactTrace.DidHitWorld() )
+ continue;
+
+ // Effects on the individual players
+ ExplodeEffectOnTarget( pThrower, pPlayer, pBaseCombatCharacter );
+ }
+
+ ApplyBlastDamage( pThrower, vecOrigin );
+ }
+ }
+
+ AddEffects( EF_NODRAW );
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_NONE );
+ }
+
+ m_nHitCount++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::PipebombTouch( CBaseEntity *pOther )
+{
+ if ( gpGlobals->curtime - m_flLastBurnTime < 0.15f )
+ return;
+
+ if ( InSameTeam( pOther ) )
+ return;
+
+ if ( !m_hFlameThrower || !m_hFlameThrower->GetOwnerEntity() )
+ return;
+
+ CTakeDamageInfo info;
+ info.SetAttacker( m_hFlameThrower->GetOwnerEntity() );
+ info.SetInflictor( m_hFlameThrower );
+ info.SetWeapon( m_hFlameThrower );
+ info.SetDamage( 2.f );
+ info.SetDamageCustom( GetCustomDamageType() );
+ info.SetDamagePosition( GetAbsOrigin() );
+ info.SetDamageType( DMG_BURN );
+ pOther->TakeDamage( info );
+
+ m_flLastBurnTime = gpGlobals->curtime;
+
+ BaseClass::PipebombTouch( pOther );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Radius damage
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin )
+{
+ CTakeDamageInfo info;
+ info.SetAttacker( pThrower );
+ info.SetInflictor( this );
+ info.SetWeapon( m_hFlameThrower );
+ info.SetDamage( 25.f );
+ info.SetDamageCustom( GetCustomDamageType() );
+ info.SetDamagePosition( vecOrigin );
+ info.SetDamageType( DMG_BURN );
+
+ CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, 100.f, pThrower );
+ TFGameRules()->RadiusDamage( radiusinfo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFProjectile_Napalm::InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace )
+{
+ // Added Particle
+ Vector vecOrigin = GetAbsOrigin();
+ // Particle
+ CPVSFilter filter( vecOrigin );
+ TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, TF_WEAPON_FLAMETHROWER, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Direct hit
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget )
+{
+ if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() )
+ return;
+
+ if ( pTarget )
+ {
+ if ( pTarget->m_Shared.IsInvulnerable() )
+ return;
+
+ if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
+ return;
+ }
+
+ Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin();
+ VectorNormalize( vecDir );
+
+ const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
+ trace_t *pNewTrace = const_cast<trace_t*>( pTrace );
+
+ CBaseEntity *pInflictor = GetLauncher();
+ CTakeDamageInfo info;
+ info.SetAttacker( pThrower );
+ info.SetInflictor( this );
+ info.SetWeapon( pInflictor );
+ info.SetDamage( 50 );
+ info.SetDamageCustom( GetCustomDamageType() );
+ info.SetDamagePosition( GetAbsOrigin() );
+ info.SetDamageType( DMG_IGNITE );
+
+ // Hurt 'em.
+ Vector dir;
+ AngleVectors( GetAbsAngles(), &dir );
+ pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace );
+ ApplyMultiDamage();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFProjectile_Napalm::GetImpactEffect( void )
+{
+ return "burninggibs";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::SetCustomPipebombModel( void )
+{
+ SetModel( "models/weapons/w_models/w_flaregun_shell.mdl" );
+}
+#endif // GAME_DLL
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Napalm::OnDataChanged( DataUpdateType_t updateType )
+{
+ BaseClass::OnDataChanged( updateType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFProjectile_Napalm::GetTrailParticleName( void )
+{
+ if ( GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ return "flaming_arrow";
+ }
+ else
+ {
+ return "flaming_arrow";
+ }
+}
+#endif // CLIENT_DLL
+#endif // STAGING_ONLY