Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3100 lines
91 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // TF Flame Thrower
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_weapon_flamethrower.h"
  8. #include "tf_fx_shared.h"
  9. #include "in_buttons.h"
  10. #include "ammodef.h"
  11. #include "tf_gamerules.h"
  12. #if defined( CLIENT_DLL )
  13. #include "c_tf_player.h"
  14. #include "vstdlib/random.h"
  15. #include "engine/IEngineSound.h"
  16. #include "soundenvelope.h"
  17. #include "prediction.h"
  18. #include "haptics/ihaptics.h"
  19. #include "c_tf_gamestats.h"
  20. #else
  21. #include "explode.h"
  22. #include "tf_player.h"
  23. #include "tf_gamestats.h"
  24. #include "ilagcompensationmanager.h"
  25. #include "collisionutils.h"
  26. #include "tf_team.h"
  27. #include "tf_obj.h"
  28. #include "tf_weapon_grenade_pipebomb.h"
  29. #include "particle_parse.h"
  30. #include "tf_weaponbase_grenadeproj.h"
  31. #include "tf_weapon_compound_bow.h"
  32. #include "tf_projectile_arrow.h"
  33. #include "tf_gamestats.h"
  34. #include "NextBot/NextBotManager.h"
  35. #include "halloween/merasmus/merasmus_trick_or_treat_prop.h"
  36. #include "tf_logic_robot_destruction.h"
  37. #ifdef STAGING_ONLY
  38. #include "tf_fx.h"
  39. #endif // STAGING_ONLY
  40. #include "tf_passtime_logic.h"
  41. ConVar tf_debug_flamethrower("tf_debug_flamethrower", "0", FCVAR_CHEAT , "Visualize the flamethrower damage." );
  42. ConVar tf_flamethrower_velocity( "tf_flamethrower_velocity", "2300.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Initial velocity of flame damage entities." );
  43. ConVar tf_flamethrower_drag("tf_flamethrower_drag", "0.87", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Air drag of flame damage entities." );
  44. ConVar tf_flamethrower_float("tf_flamethrower_float", "50.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Upward float velocity of flame damage entities." );
  45. ConVar tf_flamethrower_vecrand("tf_flamethrower_vecrand", "0.05", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Random vector added to initial velocity of flame damage entities." );
  46. ConVar tf_flamethrower_boxsize("tf_flamethrower_boxsize", "12.0", FCVAR_CHEAT , "Size of flame damage entities." );
  47. ConVar tf_flamethrower_maxdamagedist("tf_flamethrower_maxdamagedist", "350.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Maximum damage distance for flamethrower." );
  48. ConVar tf_flamethrower_shortrangedamagemultiplier("tf_flamethrower_shortrangedamagemultiplier", "1.2", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Damage multiplier for close-in flamethrower damage." );
  49. ConVar tf_flamethrower_velocityfadestart("tf_flamethrower_velocityfadestart", ".3", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution starts to fade." );
  50. ConVar tf_flamethrower_velocityfadeend("tf_flamethrower_velocityfadeend", ".5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution finishes fading." );
  51. ConVar tf_flamethrower_burst_zvelocity( "tf_flamethrower_burst_zvelocity", "350", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  52. static const char *s_pszFlameThrowerHitTargetThink = "FlameThrowerHitTargetThink";
  53. extern ConVar tf_player_movement_stun_time;
  54. #endif
  55. #include "tf_pumpkin_bomb.h"
  56. ConVar tf_flamethrower_burstammo("tf_flamethrower_burstammo", "20", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "How much ammo does the air burst uses per shot." );
  57. ConVar tf_flamethrower_flametime("tf_flamethrower_flametime", "0.5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Time to live of flame damage entities." );
  58. // memdbgon must be the last include file in a .cpp file!!!
  59. #include "tier0/memdbgon.h"
  60. // position of end of muzzle relative to shoot position
  61. #define TF_FLAMETHROWER_MUZZLEPOS_FORWARD 70.0f
  62. #define TF_FLAMETHROWER_MUZZLEPOS_RIGHT 12.0f
  63. #define TF_FLAMETHROWER_MUZZLEPOS_UP -12.0f
  64. #define TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK 14.0f
  65. #define TF_FLAMETHROWER_HITACCURACY_MED 40.0f
  66. #define TF_FLAMETHROWER_HITACCURACY_HIGH 60.0f
  67. //-----------------------------------------------------------------------------
  68. #define TF_WEAPON_BUBBLE_WAND_MODEL "models/player/items/pyro/mtp_bubble_wand.mdl"
  69. //-----------------------------------------------------------------------------
  70. #ifndef CLIENT_DLL
  71. //-----------------------------------------------------------------------------
  72. // Purpose: Only send to local player
  73. //-----------------------------------------------------------------------------
  74. void* SendProxy_SendLocalFlameThrowerDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
  75. {
  76. // Get the weapon entity
  77. CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pVarData;
  78. if ( pWeapon )
  79. {
  80. // Only send this chunk of data to the player carrying this weapon
  81. CBasePlayer *pPlayer = ToBasePlayer( pWeapon->GetOwner() );
  82. if ( pPlayer )
  83. {
  84. pRecipients->SetOnly( pPlayer->GetClientIndex() );
  85. return (void*)pVarData;
  86. }
  87. }
  88. return NULL;
  89. }
  90. REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendLocalFlameThrowerDataTable );
  91. #endif // CLIENT_DLL
  92. IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameThrower, DT_WeaponFlameThrower )
  93. //-----------------------------------------------------------------------------
  94. // Purpose: Only sent to the local player
  95. //-----------------------------------------------------------------------------
  96. BEGIN_NETWORK_TABLE_NOBASE( CTFFlameThrower, DT_LocalFlameThrower )
  97. #if defined( CLIENT_DLL )
  98. RecvPropInt( RECVINFO( m_iActiveFlames ) ),
  99. RecvPropInt( RECVINFO( m_iDamagingFlames ) ),
  100. RecvPropBool( RECVINFO( m_bHasHalloweenSpell ) ),
  101. #else
  102. SendPropInt( SENDINFO( m_iActiveFlames ), 5, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
  103. SendPropInt( SENDINFO( m_iDamagingFlames ), 10, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
  104. SendPropBool( SENDINFO( m_bHasHalloweenSpell ) ),
  105. #endif
  106. END_NETWORK_TABLE()
  107. //-----------------------------------------------------------------------------
  108. // Purpose:
  109. //-----------------------------------------------------------------------------
  110. BEGIN_NETWORK_TABLE( CTFFlameThrower, DT_WeaponFlameThrower )
  111. #if defined( CLIENT_DLL )
  112. RecvPropInt( RECVINFO( m_iWeaponState ) ),
  113. RecvPropBool( RECVINFO( m_bCritFire ) ),
  114. RecvPropBool( RECVINFO( m_bHitTarget ) ),
  115. RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ),
  116. RecvPropDataTable("LocalFlameThrowerData", 0, 0, &REFERENCE_RECV_TABLE( DT_LocalFlameThrower ) ),
  117. #else
  118. SendPropInt( SENDINFO( m_iWeaponState ), 4, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
  119. SendPropBool( SENDINFO( m_bCritFire ) ),
  120. SendPropBool( SENDINFO( m_bHitTarget ) ),
  121. SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
  122. SendPropDataTable("LocalFlameThrowerData", 0, &REFERENCE_SEND_TABLE( DT_LocalFlameThrower ), SendProxy_SendLocalFlameThrowerDataTable ),
  123. #endif
  124. END_NETWORK_TABLE()
  125. #if defined( CLIENT_DLL )
  126. BEGIN_PREDICTION_DATA( CTFFlameThrower )
  127. DEFINE_PRED_FIELD( m_iWeaponState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
  128. DEFINE_PRED_FIELD( m_bCritFire, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
  129. DEFINE_FIELD( m_flChargeBeginTime, FIELD_FLOAT ),
  130. END_PREDICTION_DATA()
  131. #endif
  132. LINK_ENTITY_TO_CLASS( tf_weapon_flamethrower, CTFFlameThrower );
  133. PRECACHE_WEAPON_REGISTER( tf_weapon_flamethrower );
  134. BEGIN_DATADESC( CTFFlameThrower )
  135. END_DATADESC()
  136. // ------------------------------------------------------------------------------------------------ //
  137. // CTFFlameThrower implementation.
  138. // ------------------------------------------------------------------------------------------------ //
  139. //-----------------------------------------------------------------------------
  140. // Purpose:
  141. //-----------------------------------------------------------------------------
  142. CTFFlameThrower::CTFFlameThrower()
  143. #if defined( CLIENT_DLL )
  144. : m_FlameEffects( this )
  145. , m_MmmmphEffect( this )
  146. #endif
  147. {
  148. WeaponReset();
  149. #if defined( CLIENT_DLL )
  150. m_pFiringStartSound = NULL;
  151. m_pFiringLoop = NULL;
  152. m_pFiringAccuracyLoop = NULL;
  153. m_pFiringHitLoop = NULL;
  154. m_bFiringLoopCritical = false;
  155. m_pPilotLightSound = NULL;
  156. m_pSpinUpSound = NULL;
  157. m_szAccuracySound = NULL;
  158. m_bEffectsThinking = false;
  159. m_bFullRageEffect = false;
  160. #else
  161. m_flTimeToStopHitSound = 0;
  162. #endif
  163. m_bHasHalloweenSpell.Set( false );
  164. ListenForGameEvent( "recalculate_holidays" );
  165. }
  166. //-----------------------------------------------------------------------------
  167. // Purpose:
  168. //-----------------------------------------------------------------------------
  169. CTFFlameThrower::~CTFFlameThrower()
  170. {
  171. DestroySounds();
  172. #if defined( CLIENT_DLL )
  173. StopFullCritEffect();
  174. #endif
  175. }
  176. //-----------------------------------------------------------------------------
  177. // Purpose:
  178. //-----------------------------------------------------------------------------
  179. void CTFFlameThrower::Precache( void )
  180. {
  181. BaseClass::Precache();
  182. int iModelIndex = PrecacheModel( TF_WEAPON_BUBBLE_WAND_MODEL );
  183. PrecacheGibsForModel( iModelIndex );
  184. PrecacheParticleSystem( "pyro_blast" );
  185. PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttack" );
  186. PrecacheScriptSound( "TFPlayer.AirBlastImpact" );
  187. PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttackDeflect" );
  188. PrecacheParticleSystem( "deflect_fx" );
  189. PrecacheParticleSystem( "drg_bison_idle" );
  190. PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_blue" );
  191. PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_red" );
  192. PrecacheParticleSystem( "halloween_burningplayer_flyingbits");
  193. #ifdef STAGING_ONLY
  194. PrecacheScriptSound( "Equipment.RocketPack_Activate" );
  195. PrecacheParticleSystem( "muzzle_bignasty" );
  196. #endif // STAGING_ONLY
  197. }
  198. bool CTFFlameThrower::CanAirBlast() const
  199. {
  200. int iAirblastDisabled = 0;
  201. CALL_ATTRIB_HOOK_INT( iAirblastDisabled, airblast_disabled );
  202. bool bAllowed = ( iAirblastDisabled == 0 );
  203. #ifdef STAGING_ONLY
  204. int nRocketPack = 0;
  205. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), nRocketPack, rocket_pack );
  206. if ( nRocketPack )
  207. {
  208. bAllowed = false;
  209. }
  210. #endif // STAGING_ONLY
  211. return bAllowed;
  212. }
  213. void CTFFlameThrower::DestroySounds( void )
  214. {
  215. #if defined( CLIENT_DLL )
  216. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  217. if ( m_pFiringStartSound )
  218. {
  219. controller.SoundDestroy( m_pFiringStartSound );
  220. m_pFiringStartSound = NULL;
  221. }
  222. if ( m_pFiringLoop )
  223. {
  224. controller.SoundDestroy( m_pFiringLoop );
  225. m_pFiringLoop = NULL;
  226. }
  227. if ( m_pPilotLightSound )
  228. {
  229. controller.SoundDestroy( m_pPilotLightSound );
  230. m_pPilotLightSound = NULL;
  231. }
  232. if ( m_pSpinUpSound )
  233. {
  234. controller.SoundDestroy( m_pSpinUpSound );
  235. m_pSpinUpSound = NULL;
  236. }
  237. if ( m_pFiringAccuracyLoop )
  238. {
  239. controller.SoundDestroy( m_pFiringAccuracyLoop );
  240. m_pFiringAccuracyLoop = NULL;
  241. }
  242. StopHitSound();
  243. #endif
  244. }
  245. void CTFFlameThrower::WeaponReset( void )
  246. {
  247. BaseClass::WeaponReset();
  248. SetWeaponState( FT_STATE_IDLE );
  249. m_bCritFire = false;
  250. m_bHitTarget = false;
  251. m_flStartFiringTime = 0;
  252. m_flAmmoUseRemainder = 0;
  253. m_flChargeBeginTime = 0;
  254. m_flSpinupBeginTime = 0;
  255. ResetFlameHitCount();
  256. DestroySounds();
  257. #if defined( CLIENT_DLL )
  258. StopFullCritEffect();
  259. #endif
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose:
  263. //-----------------------------------------------------------------------------
  264. void CTFFlameThrower::Spawn( void )
  265. {
  266. m_iAltFireHint = HINT_ALTFIRE_FLAMETHROWER;
  267. BaseClass::Spawn();
  268. }
  269. //-----------------------------------------------------------------------------
  270. // Purpose:
  271. //-----------------------------------------------------------------------------
  272. bool CTFFlameThrower::Holster( CBaseCombatWeapon *pSwitchingTo )
  273. {
  274. SetWeaponState( FT_STATE_IDLE );
  275. m_bCritFire = false;
  276. m_bHitTarget = false;
  277. m_flChargeBeginTime = 0;
  278. #if defined ( CLIENT_DLL )
  279. StopFlame();
  280. StopPilotLight();
  281. StopFullCritEffect();
  282. m_bEffectsThinking = false;
  283. #endif
  284. return BaseClass::Holster( pSwitchingTo );
  285. }
  286. //-----------------------------------------------------------------------------
  287. // Purpose:
  288. //-----------------------------------------------------------------------------
  289. void CTFFlameThrower::ItemPostFrame()
  290. {
  291. if ( m_bLowered )
  292. return;
  293. // Get the player owning the weapon.
  294. CTFPlayer *pOwner = GetTFPlayerOwner();
  295. if ( !pOwner )
  296. return;
  297. #ifdef CLIENT_DLL
  298. if ( !m_bEffectsThinking )
  299. {
  300. m_bEffectsThinking = true;
  301. SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" );
  302. }
  303. #endif
  304. int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType );
  305. m_bFiredSecondary = false;
  306. if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK2 ) )
  307. {
  308. SecondaryAttack();
  309. }
  310. // Fixes an exploit where the airblast effect repeats while +attack is active
  311. if ( m_bFiredBothAttacks )
  312. {
  313. if ( pOwner->m_nButtons & IN_ATTACK && !( pOwner->m_nButtons & IN_ATTACK2 ) )
  314. {
  315. pOwner->m_nButtons &= ~IN_ATTACK;
  316. }
  317. m_bFiredBothAttacks = false;
  318. }
  319. if ( pOwner->m_nButtons & IN_ATTACK && pOwner->m_nButtons & IN_ATTACK2 )
  320. {
  321. m_bFiredBothAttacks = true;
  322. }
  323. if ( !m_bFiredSecondary )
  324. {
  325. bool bSpinDown = m_flSpinupBeginTime > 0.0f;
  326. if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK ) && iAmmo > 0 )
  327. {
  328. PrimaryAttack();
  329. bSpinDown = false;
  330. }
  331. else if ( m_iWeaponState > FT_STATE_IDLE )
  332. {
  333. SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
  334. pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_POST );
  335. SetWeaponState( FT_STATE_IDLE );
  336. m_bCritFire = false;
  337. m_bHitTarget = false;
  338. }
  339. if ( bSpinDown )
  340. {
  341. m_flSpinupBeginTime = 0.0f;
  342. #if defined( CLIENT_DLL )
  343. if ( m_pSpinUpSound )
  344. {
  345. float flSpinUpTime = GetSpinUpTime();
  346. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  347. controller.SoundChangePitch( m_pSpinUpSound, 40, flSpinUpTime * 0.5f );
  348. controller.SoundChangeVolume( m_pSpinUpSound, 0.0f, flSpinUpTime * 2.0f );
  349. }
  350. #endif
  351. }
  352. }
  353. if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_RELOAD)) || (!(pOwner->m_nButtons & IN_ATTACK2) || !m_bFiredSecondary))
  354. {
  355. // no fire buttons down or reloading
  356. if ( !ReloadOrSwitchWeapons() && ( m_bInReload == false ) )
  357. {
  358. WeaponIdle();
  359. }
  360. }
  361. // charged airblast
  362. int iChargedAirblast = 0;
  363. CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
  364. if ( iChargedAirblast != 0 )
  365. {
  366. if ( m_flChargeBeginTime > 0 )
  367. {
  368. CTFPlayer *pPlayer = GetTFPlayerOwner();
  369. if ( !pPlayer )
  370. return;
  371. // If we're not holding down the attack button, launch the flame rocket
  372. if ( !(pPlayer->m_nButtons & IN_ATTACK2) )
  373. {
  374. //FireProjectile( pOwner );
  375. float flMultAmmoPerShot = 1.0f;
  376. CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost );
  377. int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot;
  378. FireAirBlast( iAmmoPerShot );
  379. }
  380. }
  381. }
  382. }
  383. class CTraceFilterIgnoreObjects : public CTraceFilterSimple
  384. {
  385. public:
  386. // It does have a base, but we'll never network anything below here..
  387. DECLARE_CLASS( CTraceFilterIgnoreObjects, CTraceFilterSimple );
  388. CTraceFilterIgnoreObjects( const IHandleEntity *passentity, int collisionGroup )
  389. : CTraceFilterSimple( passentity, collisionGroup )
  390. {
  391. }
  392. virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
  393. {
  394. CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
  395. if ( pEntity && pEntity->IsBaseObject() )
  396. return false;
  397. return BaseClass::ShouldHitEntity( pServerEntity, contentsMask );
  398. }
  399. };
  400. //-----------------------------------------------------------------------------
  401. // Purpose:
  402. //-----------------------------------------------------------------------------
  403. void CTFFlameThrower::PrimaryAttack()
  404. {
  405. float flSpinUpTime = GetSpinUpTime();
  406. if ( flSpinUpTime > 0.0f )
  407. {
  408. if ( m_flSpinupBeginTime > 0.0f )
  409. {
  410. if ( gpGlobals->curtime - m_flSpinupBeginTime < flSpinUpTime )
  411. {
  412. return;
  413. }
  414. }
  415. else
  416. {
  417. m_flSpinupBeginTime = gpGlobals->curtime;
  418. #if defined( CLIENT_DLL )
  419. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  420. if ( !m_pSpinUpSound )
  421. {
  422. // Create the looping pilot light sound
  423. const char *pchSpinUpSound = GetShootSound( RELOAD );
  424. CLocalPlayerFilter filter;
  425. m_pSpinUpSound = controller.SoundCreate( filter, entindex(), pchSpinUpSound );
  426. controller.Play( m_pSpinUpSound, 0.0f, 40 );
  427. }
  428. if ( m_pSpinUpSound )
  429. {
  430. controller.SoundChangePitch( m_pSpinUpSound, 100, flSpinUpTime );
  431. controller.SoundChangeVolume( m_pSpinUpSound, 1.0f, flSpinUpTime * 0.1f );
  432. }
  433. #endif
  434. return;
  435. }
  436. }
  437. // Are we capable of firing again?
  438. if ( m_flNextPrimaryAttack > gpGlobals->curtime )
  439. return;
  440. // Get the player owning the weapon.
  441. CTFPlayer *pOwner = GetTFPlayerOwner();
  442. if ( !pOwner )
  443. return;
  444. if ( !CanAttack() )
  445. {
  446. #if defined ( CLIENT_DLL )
  447. StopFlame();
  448. #endif
  449. SetWeaponState( FT_STATE_IDLE );
  450. return;
  451. }
  452. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
  453. CalcIsAttackCritical();
  454. // Because the muzzle is so long, it can stick through a wall if the player is right up against it.
  455. // Make sure the weapon can't fire in this condition by tracing a line between the eye point and the end of the muzzle.
  456. trace_t trace;
  457. Vector vecEye = pOwner->EyePosition();
  458. Vector vecMuzzlePos = GetVisualMuzzlePos();
  459. CTraceFilterIgnoreObjects traceFilter( this, COLLISION_GROUP_NONE );
  460. UTIL_TraceLine( vecEye, vecMuzzlePos, MASK_SOLID, &traceFilter, &trace );
  461. if ( trace.fraction < 1.0 && ( !trace.m_pEnt || trace.m_pEnt->m_takedamage == DAMAGE_NO ) )
  462. {
  463. // 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
  464. if ( m_iWeaponState > FT_STATE_IDLE )
  465. {
  466. #if defined ( CLIENT_DLL )
  467. StopFlame();
  468. #endif
  469. SetWeaponState( FT_STATE_IDLE );
  470. }
  471. return;
  472. }
  473. switch ( m_iWeaponState )
  474. {
  475. case FT_STATE_IDLE:
  476. {
  477. // Just started, play PRE and start looping view model anim
  478. pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE );
  479. SendWeaponAnim( ACT_VM_PRIMARYATTACK );
  480. m_flStartFiringTime = gpGlobals->curtime + 0.16; // 5 frames at 30 fps
  481. SetWeaponState( FT_STATE_STARTFIRING );
  482. }
  483. break;
  484. case FT_STATE_STARTFIRING:
  485. {
  486. // if some time has elapsed, start playing the looping third person anim
  487. if ( gpGlobals->curtime > m_flStartFiringTime )
  488. {
  489. SetWeaponState( FT_STATE_FIRING );
  490. m_flNextPrimaryAttackAnim = gpGlobals->curtime;
  491. }
  492. }
  493. break;
  494. case FT_STATE_FIRING:
  495. {
  496. if ( gpGlobals->curtime >= m_flNextPrimaryAttackAnim )
  497. {
  498. pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
  499. m_flNextPrimaryAttackAnim = gpGlobals->curtime + 1.4; // fewer than 45 frames!
  500. }
  501. }
  502. break;
  503. default:
  504. break;
  505. }
  506. #ifdef CLIENT_DLL
  507. // Restart our particle effect if we've transitioned across water boundaries
  508. if ( m_iParticleWaterLevel != -1 && pOwner->GetWaterLevel() != m_iParticleWaterLevel )
  509. {
  510. if ( m_iParticleWaterLevel == WL_Eyes || pOwner->GetWaterLevel() == WL_Eyes )
  511. {
  512. RestartParticleEffect();
  513. }
  514. }
  515. #endif
  516. #if !defined (CLIENT_DLL)
  517. // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation.
  518. pOwner->NoteWeaponFired();
  519. pOwner->SpeakWeaponFire();
  520. CTF_GameStats.Event_PlayerFiredWeapon( pOwner, m_bCritFire );
  521. // Move other players back to history positions based on local player's lag
  522. lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
  523. // PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp
  524. // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
  525. if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
  526. {
  527. g_pPasstimeLogic->GetBall()->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
  528. }
  529. #endif
  530. #ifdef CLIENT_DLL
  531. C_CTF_GameStats.Event_PlayerFiredWeapon( pOwner, IsCurrentAttackACrit() );
  532. #endif
  533. float flFiringInterval = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay;
  534. #ifdef STAGING_ONLY
  535. if ( ShootsNapalm() )
  536. {
  537. flFiringInterval *= 4.f;
  538. }
  539. #endif // STAGING_ONLY
  540. // Don't attack if we're underwater
  541. if ( pOwner->GetWaterLevel() != WL_Eyes )
  542. {
  543. // Find eligible entities in a cone in front of us.
  544. // Vector vOrigin = pOwner->Weapon_ShootPosition();
  545. Vector vForward, vRight, vUp;
  546. QAngle vAngles = pOwner->EyeAngles() + pOwner->GetPunchAngle();
  547. AngleVectors( vAngles, &vForward, &vRight, &vUp );
  548. #define NUM_TEST_VECTORS 30
  549. #ifdef CLIENT_DLL
  550. bool bWasCritical = m_bCritFire;
  551. #endif
  552. // Burn & Ignite 'em
  553. int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ];
  554. m_bCritFire = IsCurrentAttackACrit();
  555. if ( m_bCritFire )
  556. {
  557. iDmgType |= DMG_CRITICAL;
  558. }
  559. #ifdef CLIENT_DLL
  560. if ( bWasCritical != m_bCritFire )
  561. {
  562. RestartParticleEffect();
  563. }
  564. #endif
  565. #ifdef GAME_DLL
  566. // create the flame entity
  567. int iDamagePerSec = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
  568. float flDamage = (float)iDamagePerSec * flFiringInterval;
  569. CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg );
  570. int iCritFromBehind = 0;
  571. CALL_ATTRIB_HOOK_INT( iCritFromBehind, set_flamethrower_back_crit );
  572. #ifdef STAGING_ONLY
  573. if ( ShootsNapalm() )
  574. {
  575. CTFProjectile_Napalm::Create( pOwner, this );
  576. }
  577. else
  578. #endif // STAGING_ONLY
  579. {
  580. CTFFlameEntity::Create( GetFlameOriginPos(), pOwner->EyeAngles(), this, tf_flamethrower_velocity.GetFloat(), iDmgType, flDamage, iCritFromBehind == 1 );
  581. }
  582. // Pyros can become invis in some game modes. Hitting fire normally handles this,
  583. // but in the case of flamethrowers it's likely that stealth will be applied while
  584. // the fire button is down, so we have to call into RemoveInvisibility here, too.
  585. if ( pOwner->m_Shared.IsStealthed() )
  586. {
  587. pOwner->RemoveInvisibility();
  588. }
  589. #endif
  590. }
  591. #ifdef GAME_DLL
  592. // 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
  593. // per frame, depending on how constants are tuned, so keep an accumulator so we can expend fractional amounts of ammo per shot.)
  594. // 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
  595. // and cause ammo pickup indicators to appear
  596. float flAmmoPerSecond = TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK;
  597. CALL_ATTRIB_HOOK_FLOAT( flAmmoPerSecond, mult_flame_ammopersec );
  598. m_flAmmoUseRemainder += flAmmoPerSecond * flFiringInterval;
  599. // take the integer portion of the ammo use accumulator and subtract it from player's ammo count; any fractional amount of ammo use
  600. // remains and will get used in the next shot
  601. int iAmmoToSubtract = (int) m_flAmmoUseRemainder;
  602. if ( iAmmoToSubtract > 0 )
  603. {
  604. pOwner->RemoveAmmo( iAmmoToSubtract, m_iPrimaryAmmoType );
  605. m_flAmmoUseRemainder -= iAmmoToSubtract;
  606. // round to 2 digits of precision
  607. m_flAmmoUseRemainder = (float) ( (int) (m_flAmmoUseRemainder * 100) ) / 100.0f;
  608. }
  609. #endif
  610. m_flNextPrimaryAttack = gpGlobals->curtime + flFiringInterval;
  611. m_flTimeWeaponIdle = gpGlobals->curtime + flFiringInterval;
  612. #if !defined (CLIENT_DLL)
  613. lagcompensation->FinishLagCompensation( pOwner );
  614. // PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp
  615. // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically
  616. if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() )
  617. {
  618. g_pPasstimeLogic->GetBall()->FinishLagCompensation( pOwner );
  619. }
  620. #endif
  621. }
  622. //-----------------------------------------------------------------------------
  623. // Purpose:
  624. //-----------------------------------------------------------------------------
  625. float AirBurstDamageForce( const Vector &size, float damage, float scale )
  626. {
  627. float force = damage * ((48 * 48 * 82.0) / (size.x * size.y * size.z)) * scale;
  628. if ( force > 1000.0)
  629. {
  630. force = 1000.0;
  631. }
  632. return force;
  633. }
  634. //-----------------------------------------------------------------------------
  635. // Purpose:
  636. //-----------------------------------------------------------------------------
  637. bool CTFFlameThrower::SupportsAirBlastFunction( EFlameThrowerAirblastFunction eFunction ) const
  638. {
  639. int iSupportedAirBlastFunctions = 0;
  640. CALL_ATTRIB_HOOK_INT( iSupportedAirBlastFunctions, airblast_functionality_flags );
  641. // If we don't have this attribute specified, or it is set to the value 0, we interpret
  642. // that as "I can do everything!".
  643. if ( iSupportedAirBlastFunctions == 0 )
  644. {
  645. // They can do everything unless airblast is disabled, in which case they can do nothing
  646. return CanAirBlast();
  647. }
  648. return (iSupportedAirBlastFunctions & eFunction) != 0;
  649. }
  650. //-----------------------------------------------------------------------------
  651. // Purpose:
  652. //-----------------------------------------------------------------------------
  653. void CTFFlameThrower::FireAirBlast( int iAmmoPerShot )
  654. {
  655. CTFPlayer *pOwner = GetTFPlayerOwner();
  656. if ( !pOwner )
  657. return;
  658. m_bFiredSecondary = true;
  659. #ifdef CLIENT_DLL
  660. // Stop the flame if we're currently firing
  661. StopFlame( false );
  662. #endif
  663. SetWeaponState( FT_STATE_SECONDARY );
  664. #ifdef GAME_DLL
  665. SendWeaponAnim( ACT_VM_SECONDARYATTACK );
  666. pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY );
  667. int nDash = 0;
  668. CALL_ATTRIB_HOOK_INT( nDash, airblast_dashes );
  669. if ( !nDash )
  670. {
  671. DeflectProjectiles();
  672. }
  673. else
  674. {
  675. #ifdef STAGING_ONLY
  676. Vector vDashDir;
  677. AngleVectors( pOwner->EyeAngles() + QAngle( 0.0f, 180.0f, 0.0f ), &vDashDir );
  678. #else
  679. Vector vDashDir = pOwner->GetAbsVelocity();
  680. if ( !pOwner->GetGroundEntity() || vDashDir.Length() == 0.0f )
  681. {
  682. AngleVectors( pOwner->EyeAngles(), &vDashDir );
  683. }
  684. #endif
  685. vDashDir.z = 0.0f;
  686. VectorNormalize( vDashDir );
  687. Vector vCenter = pOwner->WorldSpaceCenter();
  688. Vector vSize = GetDeflectionSize();
  689. DeflectPlayer( pOwner, pOwner, vDashDir, vCenter, vSize );
  690. }
  691. // for charged airblast
  692. int iChargedAirblast = 0;
  693. CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
  694. if ( iChargedAirblast != 0 )
  695. {
  696. m_flChargeBeginTime = 0;
  697. }
  698. // compression blast doesn't go through the normal "weapon fired" code path
  699. TheNextBots().OnWeaponFired( pOwner, this );
  700. #endif
  701. #ifdef CLIENT_DLL
  702. if ( prediction->IsFirstTimePredicted() == true )
  703. {
  704. StartFlame();
  705. }
  706. #endif
  707. float fAirblastRefireTimeScale = 1.0f;
  708. CALL_ATTRIB_HOOK_FLOAT( fAirblastRefireTimeScale, mult_airblast_refire_time );
  709. if ( fAirblastRefireTimeScale <= 0.0f )
  710. {
  711. fAirblastRefireTimeScale = 1.0f;
  712. }
  713. float fAirblastPrimaryRefireTimeScale = 1.0f;
  714. CALL_ATTRIB_HOOK_FLOAT( fAirblastPrimaryRefireTimeScale, mult_airblast_primary_refire_time );
  715. if ( fAirblastPrimaryRefireTimeScale <= 0.0f )
  716. {
  717. fAirblastPrimaryRefireTimeScale = 1.0f;
  718. }
  719. // Haste Powerup Rune adds multiplier to fire delay time
  720. if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
  721. {
  722. fAirblastRefireTimeScale *= 0.5f;
  723. }
  724. m_flNextSecondaryAttack = gpGlobals->curtime + (0.75f * fAirblastRefireTimeScale);
  725. m_flNextPrimaryAttack = gpGlobals->curtime + (1.0f * fAirblastRefireTimeScale * fAirblastPrimaryRefireTimeScale);
  726. m_flResetBurstEffect = gpGlobals->curtime + 0.05f;
  727. pOwner->RemoveAmmo( iAmmoPerShot, m_iPrimaryAmmoType );
  728. }
  729. float CTFFlameThrower::GetSpinUpTime( void ) const
  730. {
  731. float flSpinUpTime = 0.0f;
  732. CALL_ATTRIB_HOOK_FLOAT( flSpinUpTime, mod_flamethrower_spinup_time );
  733. return flSpinUpTime;
  734. }
  735. void CTFFlameThrower::SetWeaponState( int nWeaponState )
  736. {
  737. if ( m_iWeaponState == nWeaponState )
  738. return;
  739. CTFPlayer *pOwner = GetTFPlayerOwner();
  740. switch ( nWeaponState )
  741. {
  742. case FT_STATE_IDLE:
  743. if ( pOwner )
  744. {
  745. float flFiringForwardPull = 0.0f;
  746. CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull );
  747. if ( flFiringForwardPull )
  748. {
  749. pOwner->m_Shared.RemoveCond( TF_COND_SPEED_BOOST );
  750. }
  751. }
  752. break;
  753. case FT_STATE_STARTFIRING:
  754. if ( pOwner )
  755. {
  756. float flFiringForwardPull = 0.0f;
  757. CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull );
  758. if ( flFiringForwardPull )
  759. {
  760. pOwner->m_Shared.AddCond( TF_COND_SPEED_BOOST );
  761. }
  762. }
  763. break;
  764. }
  765. m_iWeaponState = nWeaponState;
  766. }
  767. //-----------------------------------------------------------------------------
  768. // Purpose:
  769. //-----------------------------------------------------------------------------
  770. void CTFFlameThrower::UseRage( void )
  771. {
  772. if ( !IsRageFull() )
  773. return;
  774. CTFPlayer *pPlayer = GetTFPlayerOwner();
  775. if ( !pPlayer )
  776. return;
  777. if ( !pPlayer->IsAllowedToTaunt() )
  778. return;
  779. float flNextAttack = m_flNextSecondaryAttack;
  780. #if GAME_DLL
  781. // Do a taunt so everyone has a chance to run
  782. pPlayer->Taunt( TAUNT_BASE_WEAPON );
  783. if ( pPlayer->m_Shared.IsRageDraining() )
  784. {
  785. // taunt succeeded
  786. flNextAttack = gpGlobals->curtime + 1.0f;
  787. }
  788. #else
  789. flNextAttack = gpGlobals->curtime + 1.0f;
  790. #endif
  791. m_flNextSecondaryAttack = flNextAttack;
  792. }
  793. //-----------------------------------------------------------------------------
  794. // Purpose:
  795. //-----------------------------------------------------------------------------
  796. void CTFFlameThrower::SecondaryAttack()
  797. {
  798. if ( m_flChargeBeginTime > 0 )
  799. {
  800. m_bFiredSecondary = true;
  801. return;
  802. }
  803. if ( m_flNextSecondaryAttack > gpGlobals->curtime )
  804. {
  805. #ifndef CLIENT_DLL
  806. if ( m_flResetBurstEffect <= gpGlobals->curtime )
  807. {
  808. SetWeaponState( FT_STATE_IDLE );
  809. }
  810. #endif
  811. return;
  812. }
  813. CTFPlayer *pOwner = GetTFPlayerOwner();
  814. if ( !pOwner )
  815. return;
  816. if ( pOwner->GetWaterLevel() == WL_Eyes )
  817. return;
  818. if ( !CanAttack() )
  819. {
  820. SetWeaponState( FT_STATE_IDLE );
  821. return;
  822. }
  823. int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType );
  824. // charged airblast
  825. int iChargedAirblast = 0;
  826. CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
  827. int iBuffType = 0;
  828. CALL_ATTRIB_HOOK_INT( iBuffType, set_buff_type );
  829. float flMultAmmoPerShot = 1.0f;
  830. CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost );
  831. int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot;
  832. if ( iBuffType != 0 )
  833. {
  834. UseRage();
  835. return;
  836. }
  837. if ( iAmmo < iAmmoPerShot )
  838. return;
  839. // normal air blast?
  840. if ( iChargedAirblast == 0 && CanAirBlast() )
  841. {
  842. FireAirBlast( iAmmoPerShot );
  843. return;
  844. }
  845. #ifdef CLIENT_DLL
  846. // Stop the flame if we're currently firing
  847. StopFlame( false );
  848. #endif
  849. SetWeaponState( FT_STATE_SECONDARY );
  850. #ifdef STAGING_ONLY
  851. if ( RocketPackCanActivate( iAmmoPerShot ) )
  852. {
  853. RocketPackLaunch( iAmmoPerShot );
  854. return;
  855. }
  856. #endif // STAGING_ONLY
  857. #ifdef GAME_DLL
  858. m_iWeaponMode = TF_WEAPON_SECONDARY_MODE;
  859. m_flChargeBeginTime = gpGlobals->curtime;
  860. SendWeaponAnim( ACT_VM_PULLBACK );
  861. // @todo replace with the correct one
  862. WeaponSound( SINGLE );
  863. #endif
  864. }
  865. #ifdef GAME_DLL
  866. //-----------------------------------------------------------------------------
  867. // Purpose:
  868. //-----------------------------------------------------------------------------
  869. Vector CTFFlameThrower::GetDeflectionSize()
  870. {
  871. const Vector vecBaseDeflectionSize = BaseClass::GetDeflectionSize();
  872. float fMultiplier = 1.0f;
  873. // int iChargedAirblast = 0;
  874. // CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast );
  875. // if ( iChargedAirblast != 0 )
  876. // {
  877. // fMultiplier *= RemapValClamped( ( gpGlobals->curtime - m_flChargeBeginTime ),
  878. // 0.0f,
  879. // GetChargeMaxTime(),
  880. // AIRBLAST_CHARGE_MULT_MIN,
  881. // AIRBLAST_CHARGE_MULT_MAX );
  882. // }
  883. // Allow custom attributes to scale the deflection size.
  884. CALL_ATTRIB_HOOK_FLOAT( fMultiplier, deflection_size_multiplier );
  885. return vecBaseDeflectionSize * fMultiplier;
  886. }
  887. //-----------------------------------------------------------------------------
  888. // Purpose:
  889. //-----------------------------------------------------------------------------
  890. #ifdef _DEBUG
  891. ConVar tf_pushbackscalescale( "tf_pushbackscalescale", "1.0" );
  892. ConVar tf_pushbackscalescale_vertical( "tf_pushbackscalescale_vertical", "1.0" );
  893. #endif
  894. void ExtinguishPlayer( CEconEntity *pExtinguisher, CTFPlayer *pOwner, CTFPlayer *pTarget, const char *pExtinguisherName )
  895. {
  896. pTarget->EmitSound( "TFPlayer.FlameOut" );
  897. pTarget->m_Shared.RemoveCond( TF_COND_BURNING );
  898. // we're going to limit the number of times you can be awarded bonus points to prevent exploits
  899. if ( pOwner->ShouldGetBonusPointsForExtinguishEvent( pTarget->GetUserID() ) )
  900. {
  901. CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, 10 );
  902. }
  903. CRecipientFilter involved_filter;
  904. involved_filter.AddRecipient( pOwner );
  905. involved_filter.AddRecipient( pTarget );
  906. UserMessageBegin( involved_filter, "PlayerExtinguished" );
  907. WRITE_BYTE( pOwner->entindex() );
  908. WRITE_BYTE( pTarget->entindex() );
  909. MessageEnd();
  910. IGameEvent *event = gameeventmanager->CreateEvent( "player_extinguished" );
  911. if ( event )
  912. {
  913. event->SetInt( "victim", pTarget->entindex() );
  914. event->SetInt( "healer", pOwner->entindex() );
  915. gameeventmanager->FireEvent( event, true );
  916. }
  917. // stats
  918. EconEntity_OnOwnerKillEaterEvent( pExtinguisher, pOwner, pTarget, kKillEaterEvent_BurningAllyExtinguished );
  919. 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",
  920. pOwner->GetPlayerName(), pOwner->GetUserID(), pOwner->GetNetworkIDString(), pOwner->GetTeam()->GetName(),
  921. pTarget->GetPlayerName(), pTarget->GetUserID(), pTarget->GetNetworkIDString(), pTarget->GetTeam()->GetName(),
  922. pExtinguisherName, (int)pOwner->GetAbsOrigin().x, (int)pOwner->GetAbsOrigin().y, (int)pOwner->GetAbsOrigin().z,
  923. (int)pTarget->GetAbsOrigin().x, (int)pTarget->GetAbsOrigin().y, (int)pTarget->GetAbsOrigin().z );
  924. }
  925. bool CTFFlameThrower::DeflectPlayer( CTFPlayer *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize )
  926. {
  927. if ( pTarget->GetTeamNumber() == pOwner->GetTeamNumber() && pTarget != pOwner )
  928. {
  929. if ( pTarget->m_Shared.InCond( TF_COND_BURNING ) && SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUT_OUT_TEAMMATES ) )
  930. {
  931. ExtinguishPlayer( this, pOwner, pTarget, "tf_weapon_flamethrower" );
  932. // Return health to the Pyro.
  933. // We may want to cap the amount of health per extinguish but for now lets test this
  934. int iRestoreHealthOnExtinguish = 0;
  935. CALL_ATTRIB_HOOK_INT( iRestoreHealthOnExtinguish, extinguish_restores_health );
  936. if ( iRestoreHealthOnExtinguish > 0 )
  937. {
  938. pOwner->TakeHealth( iRestoreHealthOnExtinguish, DMG_GENERIC );
  939. IGameEvent *healevent = gameeventmanager->CreateEvent( "player_healonhit" );
  940. if ( healevent )
  941. {
  942. healevent->SetInt( "amount", iRestoreHealthOnExtinguish );
  943. healevent->SetInt( "entindex", pOwner->entindex() );
  944. item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX;
  945. if ( GetAttributeContainer() && GetAttributeContainer()->GetItem() )
  946. {
  947. healingItemDef = GetAttributeContainer()->GetItem()->GetItemDefIndex();
  948. }
  949. healevent->SetInt( "weapon_def_index", healingItemDef );
  950. gameeventmanager->FireEvent( healevent );
  951. }
  952. }
  953. }
  954. return false;
  955. }
  956. if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK ) )
  957. {
  958. int iReverseBlast = 0;
  959. CALL_ATTRIB_HOOK_INT( iReverseBlast, reverse_airblast );
  960. // Against players, let's force the pyro to be actually looking at them.
  961. // We'll be a bit more laxed when it comes to aiming at rockets and grenades.
  962. Vector vecToTarget;
  963. if ( pTarget == pOwner )
  964. {
  965. vecToTarget = vecForward;
  966. }
  967. else
  968. {
  969. vecToTarget = pTarget->WorldSpaceCenter() - pOwner->WorldSpaceCenter();
  970. VectorNormalize( vecToTarget );
  971. }
  972. // Quick Fix Uber is immune
  973. if ( pTarget->m_Shared.InCond( TF_COND_MEGAHEAL ))
  974. return false;
  975. // 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.
  976. float flDot = DotProduct( vecForward, vecToTarget );
  977. float flAirblastConeScale = 0.2f;
  978. CALL_ATTRIB_HOOK_FLOAT( flAirblastConeScale, mult_airblast_cone_scale );
  979. float flAirblastConeThreshold = Clamp(1.0f - flAirblastConeScale, 0.0f, 1.0f);
  980. if (flDot < flAirblastConeThreshold)
  981. {
  982. return false;
  983. }
  984. if ( pTarget != pOwner )
  985. {
  986. pTarget->SetAbsVelocity( vec3_origin );
  987. if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__STUN ) )
  988. {
  989. if ( !pTarget->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) )
  990. {
  991. pTarget->m_Shared.StunPlayer( tf_player_movement_stun_time.GetFloat(), 1.f, TF_STUN_MOVEMENT, pOwner );
  992. }
  993. }
  994. if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__VIEW_PUNCH ) )
  995. {
  996. pTarget->ApplyPunchImpulseX( RandomInt( 10, 15 ) );
  997. }
  998. }
  999. pTarget->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:0,victim:1" );
  1000. float flForce = AirBurstDamageForce( pTarget->WorldAlignSize(), 60, 6.f );
  1001. CALL_ATTRIB_HOOK_FLOAT( flForce, airblast_pushback_scale );
  1002. #ifdef _DEBUG
  1003. Vector vecForce = vecToTarget * flForce * tf_pushbackscalescale.GetFloat();
  1004. #else
  1005. Vector vecForce = vecToTarget * flForce;
  1006. #endif
  1007. if ( iReverseBlast )
  1008. {
  1009. vecForce = -vecForce;
  1010. }
  1011. float flVerticalPushbackScale = tf_flamethrower_burst_zvelocity.GetFloat();
  1012. if ( iReverseBlast )
  1013. {
  1014. // Don't give quite so big a vertical kick if we're sucking rather than blowing...
  1015. flVerticalPushbackScale *= 0.75f;
  1016. }
  1017. #ifdef STAGING_ONLY
  1018. if ( !( pTarget == pOwner && pOwner->GetGroundEntity() ) )
  1019. #endif
  1020. {
  1021. CALL_ATTRIB_HOOK_FLOAT( flVerticalPushbackScale, airblast_vertical_pushback_scale );
  1022. }
  1023. #ifdef _DEBUG
  1024. vecForce.z += flVerticalPushbackScale * tf_pushbackscalescale_vertical.GetFloat();
  1025. /*
  1026. // Kyle says: this will force players off the ground for at least one frame.
  1027. // This is disabled on purpose right now to match previous flamethrower functionality.
  1028. if ( pTarget->GetFlags() & FL_ONGROUND )
  1029. {
  1030. vecForce.z += 268.3281572999747f;
  1031. }
  1032. */
  1033. #else
  1034. vecForce.z += flVerticalPushbackScale;
  1035. #endif
  1036. // Apply AirBlastImpulse
  1037. pTarget->ApplyAirBlastImpulse( vecForce );
  1038. // Make sure we get credit for the airblast if the target falls to its death
  1039. pTarget->m_AchievementData.AddDamagerToHistory( pOwner );
  1040. SendObjectDeflectedEvent( pOwner, pTarget, TF_WEAPON_NONE, pTarget ); // TF_WEAPON_NONE means the player got pushed
  1041. // If the target is charging, stop the charge and keep the charge meter where it is.
  1042. pTarget->m_Shared.InterruptCharge();
  1043. // Track for achievements
  1044. pTarget->m_AchievementData.AddPusherToHistory( pOwner );
  1045. // Give bonus points whenever a pyro pushes high-value targets back
  1046. if ( TFGameRules() && ( pTarget->IsMiniBoss() || pTarget->m_Shared.IsInvulnerable() ) )
  1047. {
  1048. int nAmount = pTarget->IsMiniBoss() ? 10 : 5;
  1049. CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, nAmount );
  1050. }
  1051. return true;
  1052. }
  1053. return false;
  1054. }
  1055. //-----------------------------------------------------------------------------
  1056. // Purpose:
  1057. //-----------------------------------------------------------------------------
  1058. void CTFFlameThrower::PlayDeflectionSound( bool bPlayer )
  1059. {
  1060. if ( bPlayer )
  1061. {
  1062. EmitSound( "TFPlayer.AirBlastImpact" );
  1063. }
  1064. }
  1065. //-----------------------------------------------------------------------------
  1066. // Purpose:
  1067. //-----------------------------------------------------------------------------
  1068. bool CTFFlameThrower::DeflectEntity( CBaseEntity *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize )
  1069. {
  1070. Assert( pTarget );
  1071. Assert( pOwner );
  1072. if ( !SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_REFLECT_PROJECTILES ) )
  1073. return false;
  1074. // can't deflect things on our own team
  1075. // except the passtime ball when in passtime mode
  1076. if ( (pTarget->GetTeamNumber() == pOwner->GetTeamNumber())
  1077. && !(g_pPasstimeLogic && (g_pPasstimeLogic->GetBall() == pTarget)) )
  1078. {
  1079. return false;
  1080. }
  1081. // Grab the owner of the projectile *before* we reflect it.
  1082. CTFPlayer *pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pTarget );
  1083. if ( !pTFPlayerVictim )
  1084. {
  1085. pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pTarget->GetOwnerEntity() );
  1086. }
  1087. if ( !pTFPlayerVictim )
  1088. {
  1089. // We can't use OwnerEntity for grenades, because then the owner can't shoot them with his hitscan weapons (due to collide rules)
  1090. // Thrower is used to store the person who threw the grenade, for damage purposes.
  1091. CBaseGrenade *pBaseGrenade = dynamic_cast< CBaseGrenade*>( pTarget );
  1092. if ( pBaseGrenade )
  1093. {
  1094. pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pBaseGrenade->GetThrower() );
  1095. }
  1096. }
  1097. if ( !pTFPlayerVictim )
  1098. {
  1099. // Is the OwnerEntity() a base object, like a sentry gun shooting rockets at us?
  1100. if ( pTarget->GetOwnerEntity() && pTarget->GetOwnerEntity()->IsBaseObject() )
  1101. {
  1102. CBaseObject *pObj = dynamic_cast<CBaseObject *>( pTarget->GetOwnerEntity() );
  1103. if ( pObj )
  1104. {
  1105. pTFPlayerVictim = dynamic_cast<CTFPlayer *>( pObj->GetOwner() );
  1106. }
  1107. }
  1108. }
  1109. bool bDeflected = BaseClass::DeflectEntity( pTarget, pOwner, vecForward, vecCenter, vecSize );
  1110. if ( bDeflected )
  1111. {
  1112. pTarget->EmitSound( "Weapon_FlameThrower.AirBurstAttackDeflect" );
  1113. EconEntity_OnOwnerKillEaterEvent( this, pOwner, pTFPlayerVictim, kKillEaterEvent_ProjectileReflect );
  1114. }
  1115. return bDeflected;
  1116. }
  1117. #endif
  1118. //-----------------------------------------------------------------------------
  1119. // Purpose:
  1120. //-----------------------------------------------------------------------------
  1121. bool CTFFlameThrower::Lower( void )
  1122. {
  1123. if ( BaseClass::Lower() )
  1124. {
  1125. // If we were firing, stop
  1126. if ( m_iWeaponState > FT_STATE_IDLE )
  1127. {
  1128. SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
  1129. SetWeaponState( FT_STATE_IDLE );
  1130. }
  1131. return true;
  1132. }
  1133. return false;
  1134. }
  1135. //-----------------------------------------------------------------------------
  1136. // Purpose: Returns the position of the tip of the muzzle at it appears visually
  1137. //-----------------------------------------------------------------------------
  1138. Vector CTFFlameThrower::GetVisualMuzzlePos()
  1139. {
  1140. return GetMuzzlePosHelper( true );
  1141. }
  1142. //-----------------------------------------------------------------------------
  1143. // Purpose: Returns the position at which to spawn flame damage entities
  1144. //-----------------------------------------------------------------------------
  1145. Vector CTFFlameThrower::GetFlameOriginPos()
  1146. {
  1147. return GetMuzzlePosHelper( false );
  1148. }
  1149. #ifdef CLIENT_DLL
  1150. //-----------------------------------------------------------------------------
  1151. // Purpose:
  1152. //-----------------------------------------------------------------------------
  1153. float CTFFlameThrower::GetFlameHitRatio( void )
  1154. {
  1155. // Safety net to avoid divide by zero
  1156. if ( m_iActiveFlames == 0 )
  1157. return 0.1f;
  1158. float flRatio = ( ( (float)m_iDamagingFlames ) / ( (float)m_iActiveFlames ) );
  1159. //Msg( "Act: %d Dmg: %d\n", m_iActiveFlames, m_iDamagingFlames );
  1160. return flRatio;
  1161. }
  1162. #endif
  1163. //-----------------------------------------------------------------------------
  1164. // Purpose:
  1165. //-----------------------------------------------------------------------------
  1166. void CTFFlameThrower::IncrementFlameDamageCount( void )
  1167. {
  1168. m_iDamagingFlames++;
  1169. }
  1170. //-----------------------------------------------------------------------------
  1171. // Purpose:
  1172. //-----------------------------------------------------------------------------
  1173. void CTFFlameThrower::DecrementFlameDamageCount( void )
  1174. {
  1175. if ( m_iDamagingFlames <= 0 )
  1176. return;
  1177. m_iDamagingFlames--;
  1178. }
  1179. //-----------------------------------------------------------------------------
  1180. // Purpose:
  1181. //-----------------------------------------------------------------------------
  1182. void CTFFlameThrower::IncrementActiveFlameCount( void )
  1183. {
  1184. m_iActiveFlames++;
  1185. }
  1186. //-----------------------------------------------------------------------------
  1187. // Purpose:
  1188. //-----------------------------------------------------------------------------
  1189. void CTFFlameThrower::DecrementActiveFlameCount( void )
  1190. {
  1191. if ( m_iActiveFlames <= 0 )
  1192. return;
  1193. m_iActiveFlames--;
  1194. }
  1195. //-----------------------------------------------------------------------------
  1196. // Purpose:
  1197. //-----------------------------------------------------------------------------
  1198. void CTFFlameThrower::ResetFlameHitCount( void )
  1199. {
  1200. m_iDamagingFlames = 0;
  1201. m_iActiveFlames = 0;
  1202. }
  1203. //-----------------------------------------------------------------------------
  1204. // Purpose: UI Progress
  1205. //-----------------------------------------------------------------------------
  1206. float CTFFlameThrower::GetProgress( void )
  1207. {
  1208. CTFPlayer *pPlayer = GetTFPlayerOwner();
  1209. if ( !pPlayer )
  1210. return 0.f;
  1211. return pPlayer->m_Shared.GetRageMeter() / 100.0f;
  1212. }
  1213. //-----------------------------------------------------------------------------
  1214. // Purpose: UI Progress (same as GetProgress() without the division by 100.0f)
  1215. //-----------------------------------------------------------------------------
  1216. bool CTFFlameThrower::IsRageFull( void )
  1217. {
  1218. CTFPlayer *pPlayer = GetTFPlayerOwner();
  1219. if ( !pPlayer )
  1220. return false;
  1221. return ( pPlayer->m_Shared.GetRageMeter() >= 100.0f );
  1222. }
  1223. //-----------------------------------------------------------------------------
  1224. // Purpose:
  1225. //-----------------------------------------------------------------------------
  1226. bool CTFFlameThrower::EffectMeterShouldFlash( void )
  1227. {
  1228. CTFPlayer *pPlayer = GetTFPlayerOwner();
  1229. if ( !pPlayer )
  1230. return false;
  1231. if ( pPlayer && (IsRageFull() || pPlayer->m_Shared.IsRageDraining()) )
  1232. return true;
  1233. else
  1234. return false;
  1235. }
  1236. //-----------------------------------------------------------------------------
  1237. // Purpose: Returns the position of the tip of the muzzle
  1238. //-----------------------------------------------------------------------------
  1239. Vector CTFFlameThrower::GetMuzzlePosHelper( bool bVisualPos )
  1240. {
  1241. Vector vecMuzzlePos;
  1242. CTFPlayer *pOwner = GetTFPlayerOwner();
  1243. if ( pOwner )
  1244. {
  1245. Vector vecForward, vecRight, vecUp;
  1246. AngleVectors( pOwner->GetAbsAngles(), &vecForward, &vecRight, &vecUp );
  1247. vecMuzzlePos = pOwner->Weapon_ShootPosition();
  1248. vecMuzzlePos += vecRight * TF_FLAMETHROWER_MUZZLEPOS_RIGHT;
  1249. // if asking for visual position of muzzle, include the forward component
  1250. if ( bVisualPos )
  1251. {
  1252. vecMuzzlePos += vecForward * TF_FLAMETHROWER_MUZZLEPOS_FORWARD;
  1253. }
  1254. }
  1255. return vecMuzzlePos;
  1256. }
  1257. void CTFFlameThrower::CalculateHalloweenSpell( void )
  1258. {
  1259. m_bHasHalloweenSpell.Set( false );
  1260. if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
  1261. {
  1262. int iHalloweenSpell = 0;
  1263. CALL_ATTRIB_HOOK_INT_ON_OTHER( this, iHalloweenSpell, halloween_green_flames );
  1264. m_bHasHalloweenSpell.Set( iHalloweenSpell > 0 );
  1265. }
  1266. }
  1267. bool CTFFlameThrower::Deploy( void )
  1268. {
  1269. #if defined( CLIENT_DLL )
  1270. StartPilotLight();
  1271. m_flFlameHitRatio = 0;
  1272. m_flPrevFlameHitRatio = -1;
  1273. m_flChargeBeginTime = 0;
  1274. m_bEffectsThinking = true;
  1275. SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" );
  1276. StopFullCritEffect();
  1277. #endif // CLIENT_DLL
  1278. CalculateHalloweenSpell();
  1279. return BaseClass::Deploy();
  1280. }
  1281. //-----------------------------------------------------------------------------
  1282. // Purpose:
  1283. //-----------------------------------------------------------------------------
  1284. void CTFFlameThrower::FireGameEvent( IGameEvent *event )
  1285. {
  1286. if ( FStrEq( event->GetName(), "recalculate_holidays" ) )
  1287. {
  1288. CalculateHalloweenSpell();
  1289. }
  1290. }
  1291. #if defined( CLIENT_DLL )
  1292. //-----------------------------------------------------------------------------
  1293. // Purpose:
  1294. //-----------------------------------------------------------------------------
  1295. void CTFFlameThrower::OnDataChanged(DataUpdateType_t updateType)
  1296. {
  1297. BaseClass::OnDataChanged(updateType);
  1298. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1299. C_TFPlayer *pPlayerOwner = GetTFPlayerOwner();
  1300. //
  1301. bool bLocalPlayerAmmo = true;
  1302. if ( pPlayerOwner == pLocalPlayer )
  1303. {
  1304. bLocalPlayerAmmo = GetPlayerOwner()->GetAmmoCount( m_iPrimaryAmmoType ) > 0;
  1305. }
  1306. if ( IsCarrierAlive() && ( WeaponState() == WEAPON_IS_ACTIVE ) && bLocalPlayerAmmo == true )
  1307. {
  1308. if ( m_iWeaponState > FT_STATE_IDLE )
  1309. {
  1310. if ( ( m_iWeaponState == FT_STATE_SECONDARY && GetPlayerOwner() != C_BasePlayer::GetLocalPlayer() ) || m_iWeaponState != FT_STATE_SECONDARY )
  1311. {
  1312. StartFlame();
  1313. #ifdef STAGING_ONLY
  1314. if ( ShootsNapalm() )
  1315. {
  1316. RestartParticleEffect();
  1317. }
  1318. #endif // STAGING_ONLY
  1319. }
  1320. }
  1321. else
  1322. {
  1323. StartPilotLight();
  1324. }
  1325. }
  1326. else
  1327. {
  1328. StopFlame();
  1329. StopPilotLight();
  1330. StopFullCritEffect();
  1331. m_bEffectsThinking = false;
  1332. }
  1333. if ( pPlayerOwner == pLocalPlayer )
  1334. {
  1335. if ( m_pFiringLoop )
  1336. {
  1337. m_flFlameHitRatio = GetFlameHitRatio();
  1338. m_flFlameHitRatio = RemapValClamped( m_flFlameHitRatio, 0.0f, 1.0f, 1.0f, 100.f );
  1339. //Msg ( "%f\n", m_flFlameHitRatio );
  1340. if ( m_flFlameHitRatio != m_flPrevFlameHitRatio )
  1341. {
  1342. m_flPrevFlameHitRatio = m_flFlameHitRatio;
  1343. CLocalPlayerFilter filter;
  1344. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1345. // We play accent sounds based on accuracy
  1346. if ( m_flFlameHitRatio >= TF_FLAMETHROWER_HITACCURACY_HIGH )
  1347. {
  1348. controller.SoundChangePitch( m_pFiringLoop, 140, 0.1 );
  1349. m_szAccuracySound = "Weapon_FlameThrower.FireHitHard";
  1350. }
  1351. else
  1352. {
  1353. controller.SoundChangePitch( m_pFiringLoop, 100, 0.1 );
  1354. // If our accuracy is too low
  1355. if ( m_pFiringAccuracyLoop )
  1356. {
  1357. controller.SoundDestroy( m_pFiringAccuracyLoop );
  1358. m_pFiringAccuracyLoop = NULL;
  1359. }
  1360. return;
  1361. }
  1362. // Only start a new sound if there's been a change
  1363. if ( !m_pFiringAccuracyLoop )
  1364. {
  1365. m_pFiringAccuracyLoop = controller.SoundCreate( filter, entindex(), m_szAccuracySound );
  1366. controller.Play( m_pFiringAccuracyLoop, 1.0, 100 );
  1367. }
  1368. }
  1369. }
  1370. else if ( m_pFiringAccuracyLoop )
  1371. {
  1372. CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringAccuracyLoop );
  1373. m_pFiringAccuracyLoop = NULL;
  1374. }
  1375. if ( GetBuffType() > 0 )
  1376. {
  1377. if ( !m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() >= 100.0f )
  1378. {
  1379. m_bFullRageEffect = true;
  1380. m_MmmmphEffect.StartEffects( FullCritChargedEffectName() );
  1381. }
  1382. else if ( m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() < 100.0f )
  1383. {
  1384. StopFullCritEffect();
  1385. m_MmmmphEffect.StopEffects();
  1386. }
  1387. }
  1388. }
  1389. }
  1390. //-----------------------------------------------------------------------------
  1391. // Purpose:
  1392. //-----------------------------------------------------------------------------
  1393. void CTFFlameThrower::UpdateOnRemove( void )
  1394. {
  1395. m_FlameEffects.StopEffects();
  1396. m_MmmmphEffect.StopEffects();
  1397. StopPilotLight();
  1398. StopFullCritEffect();
  1399. m_bEffectsThinking = false;
  1400. BaseClass::UpdateOnRemove();
  1401. }
  1402. //-----------------------------------------------------------------------------
  1403. // Purpose:
  1404. //-----------------------------------------------------------------------------
  1405. void CTFFlameThrower::SetDormant( bool bDormant )
  1406. {
  1407. // If I'm going from active to dormant and I'm carried by another player, stop our firing sound.
  1408. if ( !IsCarriedByLocalPlayer() )
  1409. {
  1410. if ( !IsDormant() && bDormant )
  1411. {
  1412. StopFlame();
  1413. StopPilotLight();
  1414. StopFullCritEffect();
  1415. m_bEffectsThinking = false;
  1416. }
  1417. }
  1418. // Deliberately skip base combat weapon to avoid being holstered
  1419. C_BaseEntity::SetDormant( bDormant );
  1420. }
  1421. int CTFFlameThrower::GetWorldModelIndex( void )
  1422. {
  1423. int iParticleEffectIndex = 0;
  1424. CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
  1425. // Pyro bubble wand support.
  1426. if ( iParticleEffectIndex == 3 )
  1427. {
  1428. CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
  1429. if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && pPlayer->m_Shared.GetTauntIndex() == TAUNT_BASE_WEAPON )
  1430. {
  1431. // While we are taunting, replace our normal world model with the bubble wand.
  1432. m_iWorldModelIndex = modelinfo->GetModelIndex( TF_WEAPON_BUBBLE_WAND_MODEL );
  1433. return m_iWorldModelIndex;
  1434. }
  1435. }
  1436. return BaseClass::GetWorldModelIndex();
  1437. }
  1438. //-----------------------------------------------------------------------------
  1439. // Purpose:
  1440. //-----------------------------------------------------------------------------
  1441. void CTFFlameThrower::StartFlame()
  1442. {
  1443. if ( m_iWeaponState == FT_STATE_SECONDARY )
  1444. {
  1445. GetAppropriateWorldOrViewModel()->ParticleProp()->Create( "pyro_blast", PATTACH_POINT_FOLLOW, "muzzle" );
  1446. CLocalPlayerFilter filter;
  1447. const char *shootsound = GetShootSound( WPN_DOUBLE );
  1448. EmitSound( filter, entindex(), shootsound );
  1449. return;
  1450. }
  1451. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1452. // normally, crossfade between start sound & firing loop in 3.5 sec
  1453. float flCrossfadeTime = 3.5;
  1454. if ( m_pFiringLoop && ( m_bCritFire != m_bFiringLoopCritical ) )
  1455. {
  1456. // If we're firing and changing between critical & noncritical, just need to change the firing loop.
  1457. // Set crossfade time to zero so we skip the start sound and go to the loop immediately.
  1458. flCrossfadeTime = 0;
  1459. StopFlame( true );
  1460. }
  1461. StopPilotLight();
  1462. if ( !m_pFiringStartSound && !m_pFiringLoop )
  1463. {
  1464. // NVNT if the local player is owning this weapon, process the start event
  1465. if ( C_BasePlayer::GetLocalPlayer() == GetOwner() && haptics )
  1466. haptics->ProcessHapticEvent(2,"Weapons","flamer_start");
  1467. RestartParticleEffect();
  1468. CLocalPlayerFilter filter;
  1469. // Play the fire start sound
  1470. const char *shootsound = GetShootSound( SINGLE );
  1471. if ( flCrossfadeTime > 0.0 )
  1472. {
  1473. // play the firing start sound and fade it out
  1474. m_pFiringStartSound = controller.SoundCreate( filter, entindex(), shootsound );
  1475. controller.Play( m_pFiringStartSound, 1.0, 100 );
  1476. controller.SoundChangeVolume( m_pFiringStartSound, 0.0, flCrossfadeTime );
  1477. }
  1478. // Start the fire sound loop and fade it in
  1479. if ( m_bCritFire )
  1480. {
  1481. shootsound = GetShootSound( BURST );
  1482. }
  1483. else
  1484. {
  1485. shootsound = GetShootSound( SPECIAL1 );
  1486. }
  1487. m_pFiringLoop = controller.SoundCreate( filter, entindex(), shootsound );
  1488. m_bFiringLoopCritical = m_bCritFire;
  1489. // play the firing loop sound and fade it in
  1490. if ( flCrossfadeTime > 0.0 )
  1491. {
  1492. controller.Play( m_pFiringLoop, 0.0, 100 );
  1493. controller.SoundChangeVolume( m_pFiringLoop, 1.0, flCrossfadeTime );
  1494. }
  1495. else
  1496. {
  1497. controller.Play( m_pFiringLoop, 1.0, 100 );
  1498. }
  1499. }
  1500. // check our "hit" sound
  1501. if ( m_bHitTarget != m_bFiringHitTarget )
  1502. {
  1503. if ( m_bHitTarget == false )
  1504. {
  1505. StopHitSound();
  1506. }
  1507. else
  1508. {
  1509. char *pchFireHitSound = "Weapon_FlameThrower.FireHit";
  1510. int iParticleEffectIndex = 0;
  1511. CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
  1512. if ( iParticleEffectIndex == 3 )
  1513. {
  1514. pchFireHitSound = "Weapon_Rainblower.FireHit";
  1515. }
  1516. CLocalPlayerFilter filter;
  1517. m_pFiringHitLoop = controller.SoundCreate( filter, entindex(), pchFireHitSound );
  1518. controller.Play( m_pFiringHitLoop, 1.0, 100 );
  1519. }
  1520. m_bFiringHitTarget = m_bHitTarget;
  1521. }
  1522. }
  1523. //-----------------------------------------------------------------------------
  1524. // Purpose:
  1525. //-----------------------------------------------------------------------------
  1526. void CTFFlameThrower::StopHitSound()
  1527. {
  1528. if ( m_pFiringHitLoop )
  1529. {
  1530. CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringHitLoop );
  1531. m_pFiringHitLoop = NULL;
  1532. }
  1533. m_bHitTarget = m_bFiringHitTarget = false;
  1534. }
  1535. //-----------------------------------------------------------------------------
  1536. // Purpose:
  1537. //-----------------------------------------------------------------------------
  1538. void CTFFlameThrower::StopFlame( bool bAbrupt /* = false */ )
  1539. {
  1540. if ( ( m_pFiringLoop || m_pFiringStartSound ) && !bAbrupt )
  1541. {
  1542. // play a quick wind-down poof when the flame stops
  1543. CLocalPlayerFilter filter;
  1544. const char *shootsound = GetShootSound( SPECIAL3 );
  1545. EmitSound( filter, entindex(), shootsound );
  1546. }
  1547. if ( m_pFiringLoop )
  1548. {
  1549. CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringLoop );
  1550. m_pFiringLoop = NULL;
  1551. }
  1552. if ( m_pFiringStartSound )
  1553. {
  1554. CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringStartSound );
  1555. m_pFiringStartSound = NULL;
  1556. }
  1557. if ( m_FlameEffects.StopEffects() )
  1558. {
  1559. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1560. if ( pLocalPlayer && pLocalPlayer == GetOwner() )
  1561. {
  1562. // NVNT local player is finished firing. send the stop event.
  1563. if ( haptics )
  1564. haptics->ProcessHapticEvent(2,"Weapons","flamer_stop");
  1565. }
  1566. }
  1567. if ( !bAbrupt )
  1568. {
  1569. StopHitSound();
  1570. }
  1571. m_iParticleWaterLevel = -1;
  1572. }
  1573. //-----------------------------------------------------------------------------
  1574. // Purpose:
  1575. //-----------------------------------------------------------------------------
  1576. void CTFFlameThrower::StartPilotLight()
  1577. {
  1578. if ( !m_pPilotLightSound )
  1579. {
  1580. StopFlame();
  1581. // Create the looping pilot light sound
  1582. const char *pilotlightsound = GetShootSound( SPECIAL2 );
  1583. CLocalPlayerFilter filter;
  1584. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1585. m_pPilotLightSound = controller.SoundCreate( filter, entindex(), pilotlightsound );
  1586. controller.Play( m_pPilotLightSound, 1.0, 100 );
  1587. }
  1588. }
  1589. //-----------------------------------------------------------------------------
  1590. // Purpose:
  1591. //-----------------------------------------------------------------------------
  1592. void CTFFlameThrower::StopPilotLight()
  1593. {
  1594. if ( m_pPilotLightSound )
  1595. {
  1596. CSoundEnvelopeController::GetController().SoundDestroy( m_pPilotLightSound );
  1597. m_pPilotLightSound = NULL;
  1598. }
  1599. }
  1600. void CTFFlameThrower::StopFullCritEffect()
  1601. {
  1602. m_bFullRageEffect = false;
  1603. m_MmmmphEffect.StopEffects();
  1604. }
  1605. //-----------------------------------------------------------------------------
  1606. // Purpose:
  1607. //-----------------------------------------------------------------------------
  1608. void CTFFlameThrower::RestartParticleEffect( void )
  1609. {
  1610. CTFPlayer *pOwner = GetTFPlayerOwner();
  1611. if ( !pOwner )
  1612. return;
  1613. if ( m_iWeaponState != FT_STATE_FIRING && m_iWeaponState != FT_STATE_STARTFIRING )
  1614. {
  1615. return;
  1616. }
  1617. m_iParticleWaterLevel = pOwner->GetWaterLevel();
  1618. bool bIsFirstPersonView = IsFirstPersonView();
  1619. // Start the appropriate particle effect
  1620. const char *pszParticleEffect;
  1621. if ( pOwner->GetWaterLevel() == WL_Eyes )
  1622. {
  1623. pszParticleEffect = "flamethrower_underwater";
  1624. }
  1625. else
  1626. {
  1627. if ( m_bCritFire )
  1628. {
  1629. pszParticleEffect = FlameCritEffectName( bIsFirstPersonView );
  1630. }
  1631. else
  1632. {
  1633. pszParticleEffect = FlameEffectName( bIsFirstPersonView );
  1634. }
  1635. }
  1636. m_FlameEffects.StartEffects( pszParticleEffect );
  1637. }
  1638. //-----------------------------------------------------------------------------
  1639. // Purpose:
  1640. //-----------------------------------------------------------------------------
  1641. const char* CTFFlameThrower::FlameEffectName( bool bIsFirstPersonView )
  1642. {
  1643. CTFPlayer *pOwner = GetTFPlayerOwner();
  1644. if ( !pOwner )
  1645. return NULL;
  1646. #ifdef STAGING_ONLY
  1647. if ( ShootsNapalm() )
  1648. {
  1649. return "muzzle_bignasty";
  1650. }
  1651. #endif // STAGING_ONLY
  1652. // Halloween Spell
  1653. if ( m_bHasHalloweenSpell )
  1654. {
  1655. return "flamethrower_halloween";
  1656. }
  1657. int iParticleEffectIndex = 0;
  1658. CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
  1659. switch ( iParticleEffectIndex )
  1660. {
  1661. case 1: return "drg_phlo_stream";
  1662. case 2: return "flamethrower_giant_mvm";
  1663. case 3: return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" );
  1664. default: return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_blue" : "flamethrower" );
  1665. }
  1666. }
  1667. //-----------------------------------------------------------------------------
  1668. // Purpose:
  1669. //-----------------------------------------------------------------------------
  1670. const char* CTFFlameThrower::FlameCritEffectName( bool bIsFirstPersonView )
  1671. {
  1672. CTFPlayer *pOwner = GetTFPlayerOwner();
  1673. if ( !pOwner )
  1674. return NULL;
  1675. #ifdef STAGING_ONLY
  1676. if ( ShootsNapalm() )
  1677. {
  1678. return "muzzle_bignasty";
  1679. }
  1680. #endif // STAGING_ONLY
  1681. // Halloween Spell
  1682. if ( m_bHasHalloweenSpell )
  1683. {
  1684. return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_halloween_crit_blue" : "flamethrower_halloween_crit_red" );
  1685. }
  1686. int iParticleEffectIndex = 0;
  1687. CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode );
  1688. switch ( iParticleEffectIndex )
  1689. {
  1690. case 1: return "drg_phlo_stream_crit";
  1691. case 2: return "flamethrower_crit_giant_mvm";
  1692. case 3: return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" );
  1693. default: return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_crit_blue" : "flamethrower_crit_red" );
  1694. }
  1695. }
  1696. //-----------------------------------------------------------------------------
  1697. // Purpose:
  1698. //-----------------------------------------------------------------------------
  1699. const char* CTFFlameThrower::FullCritChargedEffectName( void )
  1700. {
  1701. switch( GetTeamNumber() )
  1702. {
  1703. case TF_TEAM_BLUE: return "medicgun_invulnstatus_fullcharge_blue";
  1704. case TF_TEAM_RED: return "medicgun_invulnstatus_fullcharge_red";
  1705. default: return "";
  1706. }
  1707. }
  1708. //-----------------------------------------------------------------------------
  1709. // Purpose:
  1710. //-----------------------------------------------------------------------------
  1711. void CTFFlameThrower::ClientEffectsThink( void )
  1712. {
  1713. CTFPlayer *pPlayer = GetTFPlayerOwner();
  1714. if ( !pPlayer )
  1715. return;
  1716. if ( !pPlayer->IsLocalPlayer() )
  1717. return;
  1718. if ( !pPlayer->GetViewModel() )
  1719. return;
  1720. if ( !m_bEffectsThinking )
  1721. return;
  1722. float flRageInverse = 1.f;
  1723. if ( GetBuffType() > 0 )
  1724. {
  1725. flRageInverse = 1.0f - ( pPlayer->m_Shared.GetRageMeter() / 100.0f );
  1726. if ( flRageInverse < 1.0f )
  1727. {
  1728. // We have some rage, let's spark!
  1729. ParticleProp()->Init( this );
  1730. CNewParticleEffect* pEffect = ParticleProp()->Create( "drg_bison_idle", PATTACH_POINT_FOLLOW, "muzzle" );
  1731. if ( pEffect )
  1732. {
  1733. pEffect->SetControlPoint( CUSTOM_COLOR_CP1, GetParticleColor( 1 ) );
  1734. pEffect->SetControlPoint( CUSTOM_COLOR_CP2, GetParticleColor( 2 ) );
  1735. }
  1736. }
  1737. }
  1738. SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime + 0.1f + RandomFloat( 1.0f, 5.0f ) * flRageInverse, "EFFECTS_THINK" );
  1739. }
  1740. void CTFFlameThrower::FlameEffect_t::StartEffects( const char* pszEffectName )
  1741. {
  1742. // Stop any old flame effects
  1743. StopEffects();
  1744. // Figure out which weapon this flame effect is to be attached to. Store this for
  1745. // later so we know which weapon to deactivate the effect on
  1746. m_hEffectWeapon = m_pOwner->GetWeaponForEffect();
  1747. if( m_hEffectWeapon )
  1748. {
  1749. CParticleProperty* pParticleProp = m_hEffectWeapon->ParticleProp();
  1750. if( pParticleProp )
  1751. {
  1752. // Flame on
  1753. m_pFlameEffect = pParticleProp->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
  1754. }
  1755. }
  1756. }
  1757. bool CTFFlameThrower::FlameEffect_t::StopEffects()
  1758. {
  1759. bool bStopped = false;
  1760. // Stop any old flame effects
  1761. if ( m_pFlameEffect && m_hEffectWeapon )
  1762. {
  1763. m_hEffectWeapon->ParticleProp()->StopEmission( m_pFlameEffect );
  1764. bStopped = true;
  1765. }
  1766. m_pFlameEffect = NULL;
  1767. m_hEffectWeapon = NULL;
  1768. return bStopped;
  1769. }
  1770. #else
  1771. //-----------------------------------------------------------------------------
  1772. // Purpose:
  1773. //-----------------------------------------------------------------------------
  1774. void CTFFlameThrower::HitTargetThink( void )
  1775. {
  1776. if ( ( m_flTimeToStopHitSound > 0 ) && ( m_flTimeToStopHitSound < gpGlobals->curtime ) )
  1777. {
  1778. m_bHitTarget = false;
  1779. m_flTimeToStopHitSound = 0;
  1780. SetContextThink( NULL, 0, s_pszFlameThrowerHitTargetThink );
  1781. return;
  1782. }
  1783. SetNextThink( gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink );
  1784. }
  1785. //-----------------------------------------------------------------------------
  1786. // Purpose:
  1787. //-----------------------------------------------------------------------------
  1788. void CTFFlameThrower::SetHitTarget( void )
  1789. {
  1790. if ( m_iWeaponState > FT_STATE_IDLE )
  1791. {
  1792. m_bHitTarget = true;
  1793. m_flTimeToStopHitSound = gpGlobals->curtime + 0.2;
  1794. // Start the hit target thinking
  1795. SetContextThink( &CTFFlameThrower::HitTargetThink, gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink );
  1796. }
  1797. }
  1798. #endif
  1799. #ifdef STAGING_ONLY
  1800. //-----------------------------------------------------------------------------
  1801. // Purpose:
  1802. //-----------------------------------------------------------------------------
  1803. bool CTFFlameThrower::RocketPackCanActivate( int nAmmoCost )
  1804. {
  1805. CTFPlayer *pOwner = GetTFPlayerOwner();
  1806. if ( !pOwner )
  1807. return false;
  1808. int nRocketPack = 0;
  1809. CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, nRocketPack, rocket_pack );
  1810. if ( !nRocketPack )
  1811. return false;
  1812. if ( pOwner->m_Shared.IsLoser() )
  1813. return false;
  1814. if ( pOwner->m_Shared.InCond( TF_COND_STUNNED ) )
  1815. return false;
  1816. if ( pOwner->IsTaunting() )
  1817. return false;
  1818. // if ( pOwner->m_Shared.GetChargeMeter() < 100.f )
  1819. // return false;
  1820. if ( pOwner->GetAmmoCount( TF_AMMO_PRIMARY ) < nAmmoCost )
  1821. return false;
  1822. return true;
  1823. }
  1824. //-----------------------------------------------------------------------------
  1825. // Purpose:
  1826. //-----------------------------------------------------------------------------
  1827. bool CTFFlameThrower::RocketPackLaunch( int nAmmoCost )
  1828. {
  1829. CTFPlayer *pOwner = GetTFPlayerOwner();
  1830. if ( !pOwner )
  1831. return false;
  1832. #ifdef CLIENT_DLL
  1833. StopFlame( false );
  1834. #endif // CLIENT_DLL
  1835. #ifdef GAME_DLL
  1836. // Launch
  1837. if ( !pOwner->m_Shared.InCond( TF_COND_ROCKETPACK ) )
  1838. {
  1839. pOwner->m_Shared.AddCond( TF_COND_ROCKETPACK );
  1840. pOwner->m_Shared.StunPlayer( 0.5f, 1.0f, TF_STUN_MOVEMENT );
  1841. }
  1842. Vector vecDir;
  1843. pOwner->EyeVectors( &vecDir );
  1844. pOwner->SetAbsVelocity( vec3_origin );
  1845. Vector vecFlightDir = -vecDir;
  1846. VectorNormalize( vecFlightDir );
  1847. float flForce = 450.f;
  1848. const float flPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 1.8f; // Greater force while airborne
  1849. const float flVertPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 0.25f; // Less vertical force while airborne
  1850. Vector vecForce = vecFlightDir * -flForce * flPushScale;
  1851. vecForce.z += 1.f * flForce * flVertPushScale;
  1852. pOwner->RemoveFlag( FL_ONGROUND );
  1853. pOwner->ApplyAbsVelocityImpulse( vecForce );
  1854. m_flNextSecondaryAttack = gpGlobals->curtime + 0.75f;
  1855. m_flNextPrimaryAttack = gpGlobals->curtime + 1.f;
  1856. m_flResetBurstEffect = gpGlobals->curtime + 0.05f;
  1857. m_bFiredSecondary = true;
  1858. m_flChargeBeginTime = 0;
  1859. pOwner->RemoveAmmo( nAmmoCost, m_iPrimaryAmmoType );
  1860. pOwner->EmitSound( "Equipment.RocketPack_Activate" );
  1861. #endif // GAME_DLL
  1862. #ifdef CLIENT_DLL
  1863. if ( prediction->IsFirstTimePredicted() == true )
  1864. {
  1865. StartFlame();
  1866. }
  1867. #endif // CLIENT_DLL
  1868. return true;
  1869. }
  1870. //-----------------------------------------------------------------------------
  1871. // Purpose:
  1872. //-----------------------------------------------------------------------------
  1873. bool CTFFlameThrower::ShootsNapalm( void )
  1874. {
  1875. int iNapalm = 0;
  1876. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iNapalm, mod_flamethrower_napalm );
  1877. return ( iNapalm > 0 );
  1878. }
  1879. #endif // STAGING_ONLY
  1880. IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameRocket, DT_TFFlameRocket )
  1881. BEGIN_NETWORK_TABLE( CTFFlameRocket, DT_TFFlameRocket )
  1882. END_NETWORK_TABLE()
  1883. #ifdef GAME_DLL
  1884. LINK_ENTITY_TO_CLASS( tf_flame, CTFFlameEntity );
  1885. IMPLEMENT_AUTO_LIST( ITFFlameEntityAutoList );
  1886. //-----------------------------------------------------------------------------
  1887. // Purpose:
  1888. //-----------------------------------------------------------------------------
  1889. CTFFlameEntity::CTFFlameEntity()
  1890. {}
  1891. //-----------------------------------------------------------------------------
  1892. // Purpose: Spawns this entity
  1893. //-----------------------------------------------------------------------------
  1894. void CTFFlameEntity::Spawn( void )
  1895. {
  1896. BaseClass::Spawn();
  1897. // don't collide with anything, we do our own collision detection in our think method
  1898. SetSolid( SOLID_NONE );
  1899. SetSolidFlags( FSOLID_NOT_SOLID );
  1900. SetCollisionGroup( COLLISION_GROUP_NONE );
  1901. // move noclip: update position from velocity, that's it
  1902. SetMoveType( MOVETYPE_NOCLIP, MOVECOLLIDE_DEFAULT );
  1903. AddEFlags( EFL_NO_WATER_VELOCITY_CHANGE );
  1904. float iBoxSize = tf_flamethrower_boxsize.GetFloat();
  1905. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), iBoxSize, mult_flame_size );
  1906. UTIL_SetSize( this, -Vector( iBoxSize, iBoxSize, iBoxSize ), Vector( iBoxSize, iBoxSize, iBoxSize ) );
  1907. // Setup attributes.
  1908. m_takedamage = DAMAGE_NO;
  1909. m_vecInitialPos = GetAbsOrigin();
  1910. m_vecPrevPos = m_vecInitialPos;
  1911. // Track total active flame entities
  1912. m_hFlameThrower = dynamic_cast< CTFFlameThrower* >( GetOwnerEntity() );
  1913. if ( m_hFlameThrower )
  1914. {
  1915. m_hFlameThrower->IncrementActiveFlameCount();
  1916. m_bBurnedEnemy = false;
  1917. float flFlameLife = tf_flamethrower_flametime.GetFloat();
  1918. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), flFlameLife, mult_flame_life );
  1919. m_flTimeRemove = gpGlobals->curtime + ( flFlameLife * random->RandomFloat( 0.9f, 1.1f ) );
  1920. }
  1921. else
  1922. {
  1923. m_flTimeRemove = gpGlobals->curtime + 3.f;
  1924. }
  1925. // Setup the think function.
  1926. SetThink( &CTFFlameEntity::FlameThink );
  1927. SetNextThink( gpGlobals->curtime );
  1928. }
  1929. //-----------------------------------------------------------------------------
  1930. // Purpose: Creates an instance of this entity
  1931. //-----------------------------------------------------------------------------
  1932. CTFFlameEntity *CTFFlameEntity::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flSpeed, int iDmgType, float flDmgAmount, bool bAlwaysCritFromBehind, bool bRandomize )
  1933. {
  1934. CTFFlameEntity *pFlame = static_cast<CTFFlameEntity*>( CBaseEntity::Create( "tf_flame", vecOrigin, vecAngles, pOwner ) );
  1935. if ( !pFlame )
  1936. return NULL;
  1937. // Initialize the owner.
  1938. pFlame->SetOwnerEntity( pOwner );
  1939. if ( pOwner->GetOwnerEntity() )
  1940. pFlame->m_hAttacker = pOwner->GetOwnerEntity();
  1941. else
  1942. pFlame->m_hAttacker = pOwner;
  1943. CBaseEntity *pAttacker = (CBaseEntity *) pFlame->m_hAttacker;
  1944. if ( pAttacker )
  1945. {
  1946. pFlame->m_iAttackerTeam = pAttacker->GetTeamNumber();
  1947. }
  1948. // Set team.
  1949. pFlame->ChangeTeam( pOwner->GetTeamNumber() );
  1950. pFlame->m_iDmgType = iDmgType;
  1951. pFlame->m_flDmgAmount = flDmgAmount;
  1952. // Setup the initial velocity.
  1953. Vector vecForward, vecRight, vecUp;
  1954. AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
  1955. float flFlameLifeMult = 1.0f;
  1956. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, flFlameLifeMult, mult_flame_life );
  1957. float velocity = flFlameLifeMult * flSpeed;
  1958. pFlame->m_vecBaseVelocity = vecForward * velocity;
  1959. float iFlameSizeMult = 1.0f;
  1960. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, iFlameSizeMult, mult_flame_size );
  1961. if ( bRandomize )
  1962. {
  1963. pFlame->m_vecBaseVelocity += RandomVector( -velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat(), velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat() );
  1964. }
  1965. if ( pOwner->GetOwnerEntity() )
  1966. {
  1967. pFlame->m_vecAttackerVelocity = pOwner->GetOwnerEntity()->GetAbsVelocity();
  1968. }
  1969. pFlame->SetAbsVelocity( pFlame->m_vecBaseVelocity );
  1970. // Setup the initial angles.
  1971. pFlame->SetAbsAngles( vecAngles );
  1972. pFlame->SetCritFromBehind( bAlwaysCritFromBehind );
  1973. return pFlame;
  1974. }
  1975. //-----------------------------------------------------------------------------
  1976. class CFlameEntityEnum : public IEntityEnumerator
  1977. {
  1978. public:
  1979. CFlameEntityEnum( CBaseEntity *pShooter )
  1980. {
  1981. m_pShooter = pShooter;
  1982. }
  1983. virtual bool EnumEntity( IHandleEntity *pHandleEntity )
  1984. {
  1985. CBaseEntity *pEnt = static_cast<CBaseEntity*>( pHandleEntity );
  1986. // Ignore collisions with the shooter
  1987. if ( pEnt == m_pShooter )
  1988. return true;
  1989. if ( pEnt->IsPlayer() && pEnt->IsAlive() )
  1990. {
  1991. m_Targets.AddToTail( pEnt );
  1992. }
  1993. else if ( pEnt->MyNextBotPointer() && pEnt->IsAlive() )
  1994. {
  1995. // add non-player bots
  1996. m_Targets.AddToTail( pEnt );
  1997. }
  1998. else if ( pEnt->IsBaseObject() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() )
  1999. {
  2000. // only add enemy objects
  2001. m_Targets.AddToTail( pEnt );
  2002. }
  2003. else if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() && FClassnameIs( pEnt, "tf_robot_destruction_robot" ) )
  2004. {
  2005. // only add enemy robots
  2006. m_Targets.AddToTail( pEnt );
  2007. }
  2008. else if ( FClassnameIs( pEnt, "func_breakable" ) || FClassnameIs( pEnt, "tf_pumpkin_bomb" ) || FClassnameIs( pEnt, "tf_merasmus_trick_or_treat_prop" ) )
  2009. {
  2010. m_Targets.AddToTail( pEnt );
  2011. }
  2012. return true;
  2013. }
  2014. const CUtlVector< CBaseEntity* >& GetTargets() { return m_Targets; }
  2015. public:
  2016. Ray_t *m_pRay;
  2017. CBaseEntity *m_pShooter;
  2018. CUtlVector< CBaseEntity* > m_Targets;
  2019. };
  2020. //-----------------------------------------------------------------------------
  2021. // Purpose: Think method
  2022. //-----------------------------------------------------------------------------
  2023. void CTFFlameEntity::FlameThink( void )
  2024. {
  2025. TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 )
  2026. // if we've expired, remove ourselves
  2027. if ( gpGlobals->curtime >= m_flTimeRemove )
  2028. {
  2029. RemoveFlame();
  2030. return;
  2031. }
  2032. else
  2033. {
  2034. // Always think, if we haven't died due to our timeout.
  2035. SetNextThink( gpGlobals->curtime );
  2036. }
  2037. // Did we move? should we check collision?
  2038. if ( GetAbsOrigin() != m_vecPrevPos )
  2039. {
  2040. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s Collision", __FUNCTION__ );
  2041. CTFPlayer *pAttacker = dynamic_cast<CTFPlayer *>( (CBaseEntity *) m_hAttacker );
  2042. if ( !pAttacker )
  2043. return;
  2044. // Create a ray for flame entity to trace
  2045. Ray_t rayWorld;
  2046. rayWorld.Init( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );
  2047. // check against world first
  2048. // if we collide with world, just destroy the flame
  2049. trace_t trWorld;
  2050. UTIL_TraceRay( rayWorld, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &trWorld );
  2051. bool bHitWorld = trWorld.startsolid || trWorld.fraction < 1.f;
  2052. // update the ray
  2053. Ray_t rayEnt;
  2054. rayEnt.Init( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );
  2055. // burn all entities that we should collide with
  2056. CFlameEntityEnum eFlameEnum( pAttacker );
  2057. enginetrace->EnumerateEntities( rayEnt, false, &eFlameEnum );
  2058. bool bHitSomething = false;
  2059. FOR_EACH_VEC( eFlameEnum.GetTargets(), i )
  2060. {
  2061. CBaseEntity *pEnt = eFlameEnum.GetTargets()[i];
  2062. // skip ent that's already burnt by this flame
  2063. int iIndex = m_hEntitiesBurnt.Find( pEnt );
  2064. if ( iIndex != m_hEntitiesBurnt.InvalidIndex() )
  2065. continue;
  2066. // if we're removing the flame this frame from hitting world, check if we hit this ent before hitting the world
  2067. if ( bHitWorld )
  2068. {
  2069. trace_t trEnt;
  2070. enginetrace->ClipRayToEntity( rayWorld, MASK_SOLID | CONTENTS_HITBOX, pEnt, &trEnt );
  2071. // hit world before this ent, skip it
  2072. if ( trEnt.fraction >= trWorld.fraction )
  2073. continue;
  2074. }
  2075. // burn them all!
  2076. if ( pEnt->IsPlayer() && pEnt->InSameTeam( pAttacker ) )
  2077. {
  2078. OnCollideWithTeammate( ToTFPlayer( pEnt ) );
  2079. }
  2080. else
  2081. {
  2082. OnCollide( pEnt );
  2083. }
  2084. bHitSomething = true;
  2085. }
  2086. // now, let's see if the flame visual could have actually hit this player. Trace backward from the
  2087. // point of impact to where the flame was fired, see if we hit anything.
  2088. if ( bHitSomething && tf_debug_flamethrower.GetBool() )
  2089. {
  2090. NDebugOverlay::SweptBox( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 255, 0, 100, 5.0 );
  2091. NDebugOverlay::EntityBounds( this, 255, 255, 0, 100, 5.0 );
  2092. }
  2093. // remove the flame if it hits the world
  2094. if ( bHitWorld )
  2095. {
  2096. if ( tf_debug_flamethrower.GetInt() )
  2097. {
  2098. NDebugOverlay::SweptBox( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 0, 0, 100, 3.0 );
  2099. }
  2100. RemoveFlame();
  2101. }
  2102. }
  2103. // Reduce our base velocity by the air drag constant
  2104. m_vecBaseVelocity *= GetFlameDrag();
  2105. // Add our float upward velocity
  2106. Vector vecVelocity = m_vecBaseVelocity + Vector( 0, 0, GetFlameFloat() ) + m_vecAttackerVelocity;
  2107. // Update our velocity
  2108. SetAbsVelocity( vecVelocity );
  2109. // Render debug visualization if convar on
  2110. if ( tf_debug_flamethrower.GetInt() )
  2111. {
  2112. if ( m_hEntitiesBurnt.Count() > 0 )
  2113. {
  2114. int val = ( (int) ( gpGlobals->curtime * 10 ) ) % 255;
  2115. NDebugOverlay::EntityBounds(this, val, 255, val, 0 ,0 );
  2116. }
  2117. else
  2118. {
  2119. NDebugOverlay::EntityBounds(this, 0, 100, 255, 0 ,0) ;
  2120. }
  2121. }
  2122. m_vecPrevPos = GetAbsOrigin();
  2123. }
  2124. //-----------------------------------------------------------------------------
  2125. // Purpose:
  2126. //-----------------------------------------------------------------------------
  2127. void CTFFlameEntity::SetHitTarget( void )
  2128. {
  2129. if ( !m_hFlameThrower )
  2130. return;
  2131. m_hFlameThrower->SetHitTarget();
  2132. }
  2133. //-----------------------------------------------------------------------------
  2134. // Purpose:
  2135. //-----------------------------------------------------------------------------
  2136. void CTFFlameEntity::RemoveFlame()
  2137. {
  2138. UpdateFlameThrowerHitRatio();
  2139. UTIL_Remove( this );
  2140. }
  2141. //-----------------------------------------------------------------------------
  2142. // Purpose: Called when we've collided with another entity
  2143. //-----------------------------------------------------------------------------
  2144. void CTFFlameEntity::OnCollide( CBaseEntity *pOther )
  2145. {
  2146. int nContents = UTIL_PointContents( GetAbsOrigin() );
  2147. if ( (nContents & MASK_WATER) )
  2148. {
  2149. RemoveFlame();
  2150. return;
  2151. }
  2152. // remember that we've burnt this player
  2153. m_hEntitiesBurnt.AddToTail( pOther );
  2154. float flDistance = GetAbsOrigin().DistTo( m_vecInitialPos );
  2155. float flDamage = m_flDmgAmount * RemapValClamped( flDistance, tf_flamethrower_maxdamagedist.GetFloat()/2, tf_flamethrower_maxdamagedist.GetFloat(), 1.0f, 0.70f );
  2156. flDamage = MAX( flDamage, 1.0 );
  2157. if ( tf_debug_flamethrower.GetInt() )
  2158. {
  2159. Msg( "Flame touch dmg: %.1f\n", flDamage );
  2160. }
  2161. CBaseEntity *pAttacker = m_hAttacker;
  2162. if ( !pAttacker )
  2163. return;
  2164. SetHitTarget();
  2165. int iDamageType = m_iDmgType;
  2166. if ( pOther && pOther->IsPlayer() )
  2167. {
  2168. CTFPlayer *pVictim = ToTFPlayer( pOther );
  2169. if ( IsBehindTarget( pOther ) )
  2170. {
  2171. if ( m_bCritFromBehind == true )
  2172. {
  2173. iDamageType |= DMG_CRITICAL;
  2174. }
  2175. if ( pVictim )
  2176. {
  2177. pVictim->HandleAchievement_Pyro_BurnFromBehind( ToTFPlayer( pAttacker ) );
  2178. }
  2179. }
  2180. // Pyro-specific
  2181. if ( pAttacker->IsPlayer() && pVictim )
  2182. {
  2183. CTFPlayer *pPlayerAttacker = ToTFPlayer( pAttacker );
  2184. if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_PYRO ) )
  2185. {
  2186. // burn the victim while taunting?
  2187. if ( pVictim->m_Shared.InCond( TF_COND_TAUNTING ) )
  2188. {
  2189. static CSchemaItemDefHandle flipTaunt( "Flippin' Awesome Taunt" );
  2190. // if I'm the one being flipped, and getting lit on fire
  2191. if ( !pVictim->IsTauntInitiator() && pVictim->GetTauntEconItemView() && pVictim->GetTauntEconItemView()->GetItemDefinition() == flipTaunt )
  2192. {
  2193. pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_IGNITE_PLAYER_BEING_FLIPPED );
  2194. }
  2195. }
  2196. pVictim->m_Shared.AddCond( TF_COND_HEALING_DEBUFF, 2.f, pAttacker );
  2197. }
  2198. }
  2199. }
  2200. CTakeDamageInfo info( GetOwnerEntity(), pAttacker, GetOwnerEntity(), flDamage, iDamageType, TF_DMG_CUSTOM_BURNING );
  2201. info.SetReportedPosition( pAttacker->GetAbsOrigin() );
  2202. if ( info.GetDamageType() & DMG_CRITICAL )
  2203. {
  2204. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  2205. }
  2206. // terrible hack for flames hitting the Merasmus props to get the particle effect in the correct position
  2207. if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) )
  2208. {
  2209. info.SetDamagePosition( GetAbsOrigin() );
  2210. }
  2211. // Track hits for the Flamethrower, which is used to change the weapon sound based on hit ratio
  2212. if ( m_hFlameThrower )
  2213. {
  2214. m_bBurnedEnemy = true;
  2215. m_hFlameThrower->IncrementFlameDamageCount();
  2216. }
  2217. // We collided with pOther, so try to find a place on their surface to show blood
  2218. trace_t pTrace;
  2219. UTIL_TraceLine( WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_SOLID|CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &pTrace );
  2220. pOther->DispatchTraceAttack( info, GetAbsVelocity(), &pTrace );
  2221. ApplyMultiDamage();
  2222. }
  2223. //-----------------------------------------------------------------------------
  2224. // Purpose:
  2225. //-----------------------------------------------------------------------------
  2226. void CTFFlameEntity::OnCollideWithTeammate( CTFPlayer *pPlayer )
  2227. {
  2228. // Only care about Snipers
  2229. if ( !pPlayer->IsPlayerClass(TF_CLASS_SNIPER) )
  2230. return;
  2231. int iIndex = m_hEntitiesBurnt.Find( pPlayer );
  2232. if ( iIndex != m_hEntitiesBurnt.InvalidIndex() )
  2233. return;
  2234. m_hEntitiesBurnt.AddToTail( pPlayer );
  2235. // Does he have the bow?
  2236. CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon();
  2237. if ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_COMPOUND_BOW )
  2238. {
  2239. CTFCompoundBow *pBow = static_cast<CTFCompoundBow*>( pWpn );
  2240. pBow->SetArrowAlight( true );
  2241. }
  2242. }
  2243. //-----------------------------------------------------------------------------
  2244. // Purpose:
  2245. //-----------------------------------------------------------------------------
  2246. bool CTFFlameEntity::IsBehindTarget( CBaseEntity *pTarget )
  2247. {
  2248. return ( DotProductToTarget( pTarget ) > 0.8 );
  2249. }
  2250. //-----------------------------------------------------------------------------
  2251. // Purpose: Utility to calculate dot product between facing angles of flame and target
  2252. //-----------------------------------------------------------------------------
  2253. float CTFFlameEntity::DotProductToTarget( CBaseEntity *pTarget )
  2254. {
  2255. Assert( pTarget );
  2256. // Get the forward view vector of the target, ignore Z
  2257. Vector vecVictimForward;
  2258. AngleVectors( pTarget->EyeAngles(), &vecVictimForward, NULL, NULL );
  2259. vecVictimForward.z = 0.0f;
  2260. vecVictimForward.NormalizeInPlace();
  2261. Vector vecTraveling = m_vecBaseVelocity;
  2262. vecTraveling.z = 0.0f;
  2263. vecTraveling.NormalizeInPlace();
  2264. return DotProduct( vecVictimForward, vecTraveling );
  2265. }
  2266. //-----------------------------------------------------------------------------
  2267. // Purpose:
  2268. //-----------------------------------------------------------------------------
  2269. void CTFFlameEntity::UpdateFlameThrowerHitRatio( void )
  2270. {
  2271. if ( !m_hFlameThrower )
  2272. return;
  2273. if ( m_bBurnedEnemy )
  2274. {
  2275. m_hFlameThrower->DecrementFlameDamageCount();
  2276. }
  2277. m_hFlameThrower->DecrementActiveFlameCount();
  2278. }
  2279. //-----------------------------------------------------------------------------
  2280. // Purpose:
  2281. //-----------------------------------------------------------------------------
  2282. float CTFFlameEntity::GetFlameFloat( void )
  2283. {
  2284. return tf_flamethrower_float.GetFloat();
  2285. }
  2286. //-----------------------------------------------------------------------------
  2287. // Purpose:
  2288. //-----------------------------------------------------------------------------
  2289. float CTFFlameEntity::GetFlameDrag( void )
  2290. {
  2291. return tf_flamethrower_drag.GetFloat();
  2292. }
  2293. #endif // GAME_DLL
  2294. #ifdef STAGING_ONLY
  2295. //-----------------------------------------------------------------------------
  2296. // Purpose: Napalm
  2297. //-----------------------------------------------------------------------------
  2298. #ifdef GAME_DLL
  2299. #define NAPALM_THINK_CONTEXT "CTFMedigunShield_ShieldThink"
  2300. #endif // GAME_DLL
  2301. LINK_ENTITY_TO_CLASS( tf_projectile_napalm, CTFProjectile_Napalm );
  2302. IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Napalm, DT_TFProjectile_Napalm )
  2303. BEGIN_NETWORK_TABLE( CTFProjectile_Napalm, DT_TFProjectile_Napalm )
  2304. #ifdef GAME_DLL
  2305. SendPropEHandle( SENDINFO( m_hFlameThrower ) ),
  2306. #else
  2307. RecvPropEHandle( RECVINFO( m_hFlameThrower ) ),
  2308. #endif
  2309. END_NETWORK_TABLE()
  2310. BEGIN_PREDICTION_DATA( CTFProjectile_Napalm )
  2311. END_PREDICTION_DATA()
  2312. // Data
  2313. BEGIN_DATADESC( CTFProjectile_Napalm )
  2314. #ifdef GAME_DLL
  2315. DEFINE_THINKFUNC( NapalmThink ),
  2316. #endif // GAME_DLL
  2317. END_DATADESC()
  2318. //-----------------------------------------------------------------------------
  2319. // Purpose:
  2320. //-----------------------------------------------------------------------------
  2321. CTFProjectile_Napalm::CTFProjectile_Napalm()
  2322. {
  2323. #ifdef GAME_DLL
  2324. m_flRemoveTime = 0.f;
  2325. m_nHitCount = 0;
  2326. m_flLastBurnTime = 0.f;
  2327. #endif // GAME_DLL
  2328. }
  2329. //-----------------------------------------------------------------------------
  2330. // Purpose:
  2331. //-----------------------------------------------------------------------------
  2332. CTFProjectile_Napalm::~CTFProjectile_Napalm()
  2333. {
  2334. }
  2335. //-----------------------------------------------------------------------------
  2336. // Purpose:
  2337. //-----------------------------------------------------------------------------
  2338. void CTFProjectile_Napalm::Precache()
  2339. {
  2340. // PrecacheModel( TF_MODEL_NAPALM );
  2341. PrecacheParticleSystem( "burninggibs" );
  2342. PrecacheParticleSystem( "flaming_arrow" );
  2343. PrecacheScriptSound( "Player.PlasmaDamage" );
  2344. BaseClass::Precache();
  2345. }
  2346. //-----------------------------------------------------------------------------
  2347. // Purpose:
  2348. //-----------------------------------------------------------------------------
  2349. void CTFProjectile_Napalm::Spawn()
  2350. {
  2351. Precache();
  2352. #ifdef GAME_DLL
  2353. m_flRemoveTime = gpGlobals->curtime + 2.2f;
  2354. SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime, NAPALM_THINK_CONTEXT );
  2355. #endif // GAME_DLL
  2356. BaseClass::Spawn();
  2357. #ifdef GAME_DLL
  2358. SetDetonateTimerLength( FLT_MAX );
  2359. #endif // GAME_DLL
  2360. }
  2361. #ifdef GAME_DLL
  2362. //-----------------------------------------------------------------------------
  2363. // Purpose:
  2364. //-----------------------------------------------------------------------------
  2365. CTFProjectile_Napalm *CTFProjectile_Napalm::Create( CBaseCombatCharacter *pOwner, CTFFlameThrower *pLauncher )
  2366. {
  2367. if ( pOwner )
  2368. {
  2369. Vector vecForward;
  2370. AngleVectors( pOwner->EyeAngles(), &vecForward, NULL, NULL );
  2371. CTFProjectile_Napalm *pProjectile = static_cast< CTFProjectile_Napalm* >( CBaseEntity::Create( "tf_projectile_napalm", pLauncher->GetVisualMuzzlePos(), pOwner->EyeAngles() ) );
  2372. if ( pProjectile )
  2373. {
  2374. pProjectile->ChangeTeam( pOwner->GetTeamNumber() );
  2375. // Setup the initial velocity.
  2376. float flVelocity = 1100.f;
  2377. pProjectile->m_vecBaseVelocity = vecForward * flVelocity;
  2378. pProjectile->m_vecBaseVelocity += RandomVector( -flVelocity * tf_flamethrower_vecrand.GetFloat(), flVelocity * tf_flamethrower_vecrand.GetFloat() );
  2379. pProjectile->InitGrenade( pProjectile->m_vecBaseVelocity, vec3_origin, pOwner, pLauncher->GetTFWpnData() );
  2380. pProjectile->m_hFlameThrower = pLauncher;
  2381. return pProjectile;
  2382. }
  2383. }
  2384. return NULL;
  2385. }
  2386. //-----------------------------------------------------------------------------
  2387. // Purpose:
  2388. //-----------------------------------------------------------------------------
  2389. int CTFProjectile_Napalm::UpdateTransmitState()
  2390. {
  2391. return SetTransmitState( FL_EDICT_PVSCHECK );
  2392. }
  2393. //-----------------------------------------------------------------------------
  2394. // Purpose:
  2395. //-----------------------------------------------------------------------------
  2396. void CTFProjectile_Napalm::NapalmThink( void )
  2397. {
  2398. if ( gpGlobals->curtime > m_flRemoveTime )
  2399. {
  2400. SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
  2401. SetTouch( NULL );
  2402. return;
  2403. }
  2404. SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime + 0.1f, NAPALM_THINK_CONTEXT );
  2405. }
  2406. //-----------------------------------------------------------------------------
  2407. // Purpose:
  2408. //-----------------------------------------------------------------------------
  2409. void CTFProjectile_Napalm::Explode( trace_t *pTrace, int bitsDamageType )
  2410. {
  2411. if ( !m_nHitCount )
  2412. {
  2413. SetModelName( NULL_STRING );
  2414. AddSolidFlags( FSOLID_TRIGGER );
  2415. m_takedamage = DAMAGE_NO;
  2416. // Pull out of the wall a bit.
  2417. if ( pTrace->fraction != 1.f )
  2418. {
  2419. SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) );
  2420. }
  2421. CTFPlayer *pThrower = ToTFPlayer( GetThrower() );
  2422. if ( pThrower )
  2423. {
  2424. const Vector& vecOrigin = GetAbsOrigin();
  2425. // Any effects from the initial explosion
  2426. if ( InitialExplodeEffects( pThrower, pTrace ) )
  2427. {
  2428. // Particle
  2429. if ( GetImpactEffect() )
  2430. {
  2431. CPVSFilter filter( vecOrigin );
  2432. // Stick effect on the player
  2433. CBaseEntity *pEnt = pTrace->m_pEnt;
  2434. if ( pEnt && pEnt->IsPlayer() && !pThrower->InSameTeam( pEnt ) )
  2435. {
  2436. // TE_TFParticleEffect( filter, 0.f, GetImpactEffect(), pEnt->GetAbsOrigin(), vec3_angle, pEnt, PATTACH_ABSORIGIN_FOLLOW );
  2437. m_flRemoveTime = gpGlobals->curtime;
  2438. }
  2439. // World
  2440. else
  2441. {
  2442. TE_TFParticleEffect( filter, 0.0, GetImpactEffect(), vecOrigin, vec3_angle );
  2443. }
  2444. }
  2445. // Sounds
  2446. // EmitSound( "Player.PlasmaDamage" );
  2447. // Treat this trace exactly like radius damage
  2448. CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE );
  2449. // Burn players in impact range
  2450. CBaseEntity *pListOfEntities[32];
  2451. int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC );
  2452. for ( int i = 0; i < iEntities; ++i )
  2453. {
  2454. if ( pThrower->InSameTeam( pListOfEntities[i] ) )
  2455. continue;
  2456. CBaseCombatCharacter *pBaseCombatCharacter = NULL;
  2457. CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] );
  2458. if ( !pPlayer )
  2459. {
  2460. pBaseCombatCharacter = dynamic_cast< CBaseCombatCharacter* >( pListOfEntities[i] );
  2461. }
  2462. else
  2463. {
  2464. pBaseCombatCharacter = pPlayer;
  2465. }
  2466. if ( !pBaseCombatCharacter || !pBaseCombatCharacter->IsAlive() )
  2467. continue;
  2468. // Do a quick trace to see if there's any geometry in the way.
  2469. trace_t pImpactTrace;
  2470. UTIL_TraceLine( GetAbsOrigin(), pBaseCombatCharacter->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &pImpactTrace );
  2471. if ( pImpactTrace.DidHitWorld() )
  2472. continue;
  2473. // Effects on the individual players
  2474. ExplodeEffectOnTarget( pThrower, pPlayer, pBaseCombatCharacter );
  2475. }
  2476. ApplyBlastDamage( pThrower, vecOrigin );
  2477. }
  2478. }
  2479. AddEffects( EF_NODRAW );
  2480. SetAbsVelocity( vec3_origin );
  2481. SetMoveType( MOVETYPE_NONE );
  2482. }
  2483. m_nHitCount++;
  2484. }
  2485. //-----------------------------------------------------------------------------
  2486. // Purpose:
  2487. //-----------------------------------------------------------------------------
  2488. void CTFProjectile_Napalm::PipebombTouch( CBaseEntity *pOther )
  2489. {
  2490. if ( gpGlobals->curtime - m_flLastBurnTime < 0.15f )
  2491. return;
  2492. if ( InSameTeam( pOther ) )
  2493. return;
  2494. if ( !m_hFlameThrower || !m_hFlameThrower->GetOwnerEntity() )
  2495. return;
  2496. CTakeDamageInfo info;
  2497. info.SetAttacker( m_hFlameThrower->GetOwnerEntity() );
  2498. info.SetInflictor( m_hFlameThrower );
  2499. info.SetWeapon( m_hFlameThrower );
  2500. info.SetDamage( 2.f );
  2501. info.SetDamageCustom( GetCustomDamageType() );
  2502. info.SetDamagePosition( GetAbsOrigin() );
  2503. info.SetDamageType( DMG_BURN );
  2504. pOther->TakeDamage( info );
  2505. m_flLastBurnTime = gpGlobals->curtime;
  2506. BaseClass::PipebombTouch( pOther );
  2507. }
  2508. //-----------------------------------------------------------------------------
  2509. // Purpose: Radius damage
  2510. //-----------------------------------------------------------------------------
  2511. void CTFProjectile_Napalm::ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin )
  2512. {
  2513. CTakeDamageInfo info;
  2514. info.SetAttacker( pThrower );
  2515. info.SetInflictor( this );
  2516. info.SetWeapon( m_hFlameThrower );
  2517. info.SetDamage( 25.f );
  2518. info.SetDamageCustom( GetCustomDamageType() );
  2519. info.SetDamagePosition( vecOrigin );
  2520. info.SetDamageType( DMG_BURN );
  2521. CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, 100.f, pThrower );
  2522. TFGameRules()->RadiusDamage( radiusinfo );
  2523. }
  2524. //-----------------------------------------------------------------------------
  2525. // Purpose:
  2526. //-----------------------------------------------------------------------------
  2527. bool CTFProjectile_Napalm::InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace )
  2528. {
  2529. // Added Particle
  2530. Vector vecOrigin = GetAbsOrigin();
  2531. // Particle
  2532. CPVSFilter filter( vecOrigin );
  2533. TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, TF_WEAPON_FLAMETHROWER, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX );
  2534. return true;
  2535. }
  2536. //-----------------------------------------------------------------------------
  2537. // Purpose: Direct hit
  2538. //-----------------------------------------------------------------------------
  2539. void CTFProjectile_Napalm::ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget )
  2540. {
  2541. if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() )
  2542. return;
  2543. if ( pTarget )
  2544. {
  2545. if ( pTarget->m_Shared.IsInvulnerable() )
  2546. return;
  2547. if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
  2548. return;
  2549. }
  2550. Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin();
  2551. VectorNormalize( vecDir );
  2552. const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
  2553. trace_t *pNewTrace = const_cast<trace_t*>( pTrace );
  2554. CBaseEntity *pInflictor = GetLauncher();
  2555. CTakeDamageInfo info;
  2556. info.SetAttacker( pThrower );
  2557. info.SetInflictor( this );
  2558. info.SetWeapon( pInflictor );
  2559. info.SetDamage( 50 );
  2560. info.SetDamageCustom( GetCustomDamageType() );
  2561. info.SetDamagePosition( GetAbsOrigin() );
  2562. info.SetDamageType( DMG_IGNITE );
  2563. // Hurt 'em.
  2564. Vector dir;
  2565. AngleVectors( GetAbsAngles(), &dir );
  2566. pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace );
  2567. ApplyMultiDamage();
  2568. }
  2569. //-----------------------------------------------------------------------------
  2570. // Purpose:
  2571. //-----------------------------------------------------------------------------
  2572. const char *CTFProjectile_Napalm::GetImpactEffect( void )
  2573. {
  2574. return "burninggibs";
  2575. }
  2576. //-----------------------------------------------------------------------------
  2577. // Purpose:
  2578. //-----------------------------------------------------------------------------
  2579. void CTFProjectile_Napalm::SetCustomPipebombModel( void )
  2580. {
  2581. SetModel( "models/weapons/w_models/w_flaregun_shell.mdl" );
  2582. }
  2583. #endif // GAME_DLL
  2584. #ifdef CLIENT_DLL
  2585. //-----------------------------------------------------------------------------
  2586. // Purpose:
  2587. //-----------------------------------------------------------------------------
  2588. void CTFProjectile_Napalm::OnDataChanged( DataUpdateType_t updateType )
  2589. {
  2590. BaseClass::OnDataChanged( updateType );
  2591. }
  2592. //-----------------------------------------------------------------------------
  2593. // Purpose:
  2594. //-----------------------------------------------------------------------------
  2595. const char *CTFProjectile_Napalm::GetTrailParticleName( void )
  2596. {
  2597. if ( GetTeamNumber() == TF_TEAM_BLUE )
  2598. {
  2599. return "flaming_arrow";
  2600. }
  2601. else
  2602. {
  2603. return "flaming_arrow";
  2604. }
  2605. }
  2606. #endif // CLIENT_DLL
  2607. #endif // STAGING_ONLY