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.

1402 lines
39 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: TF Pipebomb Grenade.
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "tf_weaponbase.h"
  8. #include "tf_gamerules.h"
  9. #include "npcevent.h"
  10. #include "engine/IEngineSound.h"
  11. #include "tf_weapon_grenade_pipebomb.h"
  12. #include "tf_weapon_pipebomblauncher.h"
  13. #include "tf_weapon_grenadelauncher.h"
  14. // Client specific.
  15. #ifdef CLIENT_DLL
  16. #include "c_tf_player.h"
  17. #include "IEffects.h"
  18. #include "materialsystem/imaterialvar.h"
  19. #include "functionproxy.h"
  20. // Server specific.
  21. #else
  22. #include "tf_player.h"
  23. #include "items.h"
  24. #include "tf_weaponbase_grenadeproj.h"
  25. #include "soundent.h"
  26. #include "KeyValues.h"
  27. #include "IEffects.h"
  28. #include "props.h"
  29. #include "func_respawnroom.h"
  30. #include "tf_ammo_pack.h"
  31. #include "takedamageinfo.h"
  32. #include "tf_team.h"
  33. #include "physics_collisionevent.h"
  34. #ifdef TF_RAID_MODE
  35. #include "player_vs_environment/boss_alpha/boss_alpha.h"
  36. #endif // TF_RAID_MODE
  37. #include "tf_weapon_medigun.h"
  38. #endif
  39. #define TF_WEAPON_PIPEBOMB_TIMER 3.0f //Seconds
  40. #define TF_WEAPON_PIPEBOMB_GRAVITY 0.5f
  41. #define TF_WEAPON_PIPEBOMB_FRICTION 0.8f
  42. #define TF_WEAPON_PIPEBOMB_ELASTICITY 0.45f
  43. #define TF_WEAPON_PIPEBOMB_TIMER_DMG_REDUCTION 0.6
  44. extern ConVar tf_grenadelauncher_max_chargetime;
  45. ConVar tf_grenadelauncher_chargescale( "tf_grenadelauncher_chargescale", "1.0", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  46. ConVar tf_grenadelauncher_livetime( "tf_grenadelauncher_livetime", "0.8", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  47. extern ConVar tf_sticky_radius_ramp_time;
  48. extern ConVar tf_sticky_airdet_radius;
  49. #ifndef CLIENT_DLL
  50. ConVar tf_grenadelauncher_min_contact_speed( "tf_grenadelauncher_min_contact_speed", "100", FCVAR_DEVELOPMENTONLY );
  51. extern ConVar tf_obj_gib_velocity_min;
  52. extern ConVar tf_obj_gib_velocity_max;
  53. extern ConVar tf_obj_gib_maxspeed;
  54. #endif
  55. IMPLEMENT_NETWORKCLASS_ALIASED( TFGrenadePipebombProjectile, DT_TFProjectile_Pipebomb )
  56. BEGIN_NETWORK_TABLE( CTFGrenadePipebombProjectile, DT_TFProjectile_Pipebomb )
  57. #ifdef CLIENT_DLL
  58. RecvPropInt( RECVINFO( m_bTouched ) ),
  59. RecvPropInt( RECVINFO( m_iType ) ),
  60. RecvPropEHandle( RECVINFO( m_hLauncher ) ),
  61. RecvPropBool( RECVINFO( m_bDefensiveBomb ) ),
  62. #else
  63. SendPropBool( SENDINFO( m_bTouched ) ),
  64. SendPropInt( SENDINFO( m_iType ), 3 ),
  65. SendPropEHandle( SENDINFO( m_hLauncher ) ),
  66. SendPropBool( SENDINFO( m_bDefensiveBomb ) ),
  67. #endif
  68. END_NETWORK_TABLE()
  69. #ifdef GAME_DLL
  70. static string_t s_iszTrainName;
  71. #endif
  72. //-----------------------------------------------------------------------------
  73. // Purpose:
  74. // Input : -
  75. //-----------------------------------------------------------------------------
  76. CTFGrenadePipebombProjectile::CTFGrenadePipebombProjectile()
  77. {
  78. m_bTouched = false;
  79. m_flChargeTime = 0.0f;
  80. m_bDetonateOnPulse = false;
  81. #ifdef GAME_DLL
  82. s_iszTrainName = AllocPooledString( "models/props_vehicles/train_enginecar.mdl" );
  83. m_flDeflectedTime = 0.0f;
  84. m_bWallShatter = false;
  85. m_bDefensiveBomb = false;
  86. m_bSendPlayerDestroyedEvent = true;
  87. m_bCanTakeDamage = true;
  88. #else
  89. pEffectTrail = NULL;
  90. pEffectCrit = NULL;
  91. m_iCachedDeflect = 0;
  92. m_bHighlight = false;
  93. #endif
  94. }
  95. //-----------------------------------------------------------------------------
  96. // Purpose:
  97. // Input : -
  98. //-----------------------------------------------------------------------------
  99. CTFGrenadePipebombProjectile::~CTFGrenadePipebombProjectile()
  100. {
  101. #ifdef CLIENT_DLL
  102. if ( pEffectTrail )
  103. {
  104. ParticleProp()->StopEmission( pEffectTrail );
  105. }
  106. if ( pEffectCrit )
  107. {
  108. ParticleProp()->StopEmission( pEffectCrit );
  109. }
  110. if ( m_pGlowEffect )
  111. {
  112. delete m_pGlowEffect;
  113. m_pGlowEffect = NULL;
  114. }
  115. #endif
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Purpose:
  119. //-----------------------------------------------------------------------------
  120. int CTFGrenadePipebombProjectile::GetWeaponID( void ) const
  121. {
  122. if ( m_iType == TF_GL_MODE_CANNONBALL )
  123. {
  124. return TF_WEAPON_CANNON;
  125. }
  126. return ( HasStickyEffects() ? TF_WEAPON_GRENADE_PIPEBOMB : TF_WEAPON_GRENADE_DEMOMAN );
  127. }
  128. //-----------------------------------------------------------------------------
  129. // Purpose:
  130. //-----------------------------------------------------------------------------
  131. int CTFGrenadePipebombProjectile::GetDamageType( void )
  132. {
  133. int iDmgType = BaseClass::GetDamageType();
  134. // If we're a pipebomb, we do distance based damage falloff for just the first few seconds of our life
  135. if ( m_iType == TF_GL_MODE_REMOTE_DETONATE )
  136. {
  137. if ( gpGlobals->curtime - m_flCreationTime < 5.0 )
  138. {
  139. iDmgType |= DMG_USEDISTANCEMOD;
  140. }
  141. }
  142. return iDmgType;
  143. }
  144. //-----------------------------------------------------------------------------
  145. // Purpose:
  146. //-----------------------------------------------------------------------------
  147. bool CTFGrenadePipebombProjectile::ShouldMiniCritOnReflect() const
  148. {
  149. return GetType() == TF_GL_MODE_REGULAR;
  150. }
  151. //-----------------------------------------------------------------------------
  152. // Purpose:
  153. //-----------------------------------------------------------------------------
  154. void CTFGrenadePipebombProjectile::UpdateOnRemove( void )
  155. {
  156. // Tell our launcher that we were removed
  157. CTFPipebombLauncher *pLauncher = dynamic_cast<CTFPipebombLauncher*>( m_hLauncher.Get() );
  158. if ( pLauncher )
  159. {
  160. pLauncher->DeathNotice( this );
  161. }
  162. BaseClass::UpdateOnRemove();
  163. }
  164. #ifdef CLIENT_DLL
  165. //=============================================================================
  166. //
  167. // TF Pipebomb Grenade Projectile functions (Client specific).
  168. //
  169. //-----------------------------------------------------------------------------
  170. // Purpose:
  171. // Output : const char
  172. //-----------------------------------------------------------------------------
  173. const char *CTFGrenadePipebombProjectile::GetTrailParticleName( void )
  174. {
  175. int iTeamNumber = GetTeamNumber();
  176. if ( GetDeflected() && m_iType != TF_GL_MODE_REMOTE_DETONATE )
  177. {
  178. CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() );
  179. if ( pOwner )
  180. {
  181. iTeamNumber = pOwner->GetTeamNumber();
  182. }
  183. }
  184. if ( HasStickyEffects() )
  185. {
  186. if ( iTeamNumber == TF_TEAM_BLUE )
  187. {
  188. return "stickybombtrail_blue";
  189. }
  190. else
  191. {
  192. return "stickybombtrail_red";
  193. }
  194. }
  195. else
  196. {
  197. if ( iTeamNumber == TF_TEAM_BLUE )
  198. {
  199. return "pipebombtrail_blue";
  200. }
  201. else
  202. {
  203. return "pipebombtrail_red";
  204. }
  205. }
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose:
  209. // Input : updateType -
  210. //-----------------------------------------------------------------------------
  211. void CTFGrenadePipebombProjectile::OnDataChanged(DataUpdateType_t updateType)
  212. {
  213. BaseClass::OnDataChanged( updateType );
  214. if ( updateType == DATA_UPDATE_CREATED )
  215. {
  216. m_flCreationTime = gpGlobals->curtime;
  217. m_bPulsed = false;
  218. CTFPipebombLauncher *pLauncher = dynamic_cast<CTFPipebombLauncher*>( m_hLauncher.Get() );
  219. if ( pLauncher )
  220. {
  221. pLauncher->AddPipeBomb( this );
  222. }
  223. if ( m_bDefensiveBomb && C_BasePlayer::GetLocalPlayer() == GetThrower() )
  224. {
  225. if ( GetTeamNumber() == TF_TEAM_RED )
  226. {
  227. m_pGlowEffect = new CGlowObject( this, Vector( 150, 0, 0 ), 1.0, true );
  228. }
  229. else
  230. {
  231. m_pGlowEffect = new CGlowObject( this, Vector( 0, 0, 150 ), 1.0, true );
  232. }
  233. }
  234. CreateTrailParticles();
  235. }
  236. else if ( m_bTouched )
  237. {
  238. //ParticleProp()->StopEmission();
  239. }
  240. if ( m_iCachedDeflect != GetDeflected() )
  241. {
  242. CreateTrailParticles();
  243. }
  244. m_iCachedDeflect = GetDeflected();
  245. }
  246. void CTFGrenadePipebombProjectile::CreateTrailParticles( void )
  247. {
  248. if ( pEffectTrail )
  249. {
  250. ParticleProp()->StopEmission( pEffectTrail );
  251. }
  252. if ( pEffectCrit )
  253. {
  254. ParticleProp()->StopEmission( pEffectCrit );
  255. }
  256. pEffectTrail = ParticleProp()->Create( GetTrailParticleName(), PATTACH_ABSORIGIN_FOLLOW );
  257. int iTeamNumber = GetTeamNumber();
  258. if ( GetDeflected() && m_iType != TF_GL_MODE_REMOTE_DETONATE )
  259. {
  260. CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() );
  261. if ( pOwner )
  262. {
  263. iTeamNumber = pOwner->GetTeamNumber();
  264. }
  265. }
  266. if ( m_bCritical )
  267. {
  268. switch( iTeamNumber )
  269. {
  270. case TF_TEAM_BLUE:
  271. if ( HasStickyEffects() )
  272. {
  273. pEffectCrit = ParticleProp()->Create( "critical_grenade_blue", PATTACH_ABSORIGIN_FOLLOW );
  274. }
  275. else
  276. {
  277. pEffectCrit = ParticleProp()->Create( "critical_pipe_blue", PATTACH_ABSORIGIN_FOLLOW );
  278. }
  279. break;
  280. case TF_TEAM_RED:
  281. if ( HasStickyEffects() )
  282. {
  283. pEffectCrit = ParticleProp()->Create( "critical_grenade_red", PATTACH_ABSORIGIN_FOLLOW );
  284. }
  285. else
  286. {
  287. pEffectCrit = ParticleProp()->Create( "critical_pipe_red", PATTACH_ABSORIGIN_FOLLOW );
  288. }
  289. break;
  290. default:
  291. break;
  292. }
  293. }
  294. }
  295. extern ConVar tf_grenadelauncher_livetime;
  296. void CTFGrenadePipebombProjectile::Simulate( void )
  297. {
  298. BaseClass::Simulate();
  299. if ( !HasStickyEffects() )
  300. return;
  301. if ( m_bPulsed == false )
  302. {
  303. if ( (gpGlobals->curtime - m_flCreationTime) >= GetLiveTime() )
  304. {
  305. if ( GetTeamNumber() == TF_TEAM_RED )
  306. {
  307. ParticleProp()->Create( "stickybomb_pulse_red", PATTACH_ABSORIGIN_FOLLOW );
  308. }
  309. else
  310. {
  311. ParticleProp()->Create( "stickybomb_pulse_blue", PATTACH_ABSORIGIN_FOLLOW );
  312. }
  313. m_bPulsed = true;
  314. if ( m_bDetonateOnPulse )
  315. {
  316. Detonate();
  317. }
  318. }
  319. }
  320. }
  321. //------------------------------------------------------------------------------
  322. // Purpose: Don't draw if we haven't yet gone past our original spawn point
  323. // Input : flags -
  324. //-----------------------------------------------------------------------------
  325. int CTFGrenadePipebombProjectile::DrawModel( int flags )
  326. {
  327. if ( gpGlobals->curtime < ( m_flCreationTime + 0.1 ) )
  328. return 0;
  329. return BaseClass::DrawModel( flags );
  330. }
  331. #else
  332. //=============================================================================
  333. //
  334. // TF Pipebomb Grenade Projectile functions (Server specific).
  335. //
  336. #define TF_WEAPON_PIPEGRENADE_MODEL "models/weapons/w_models/w_grenade_grenadelauncher.mdl"
  337. #define TF_WEAPON_CANNONBALL_MODEL "models/weapons/w_models/w_cannonball.mdl"
  338. #define TF_WEAPON_PIPEBOMB_MODEL "models/weapons/w_models/w_stickybomb.mdl"
  339. #define TF_WEAPON_PIPEBOMB2_MODEL "models/weapons/w_models/w_stickybomb2.mdl"
  340. #define TF_WEAPON_PIPEBOMBD_MODEL "models/weapons/w_models/w_stickybomb_d.mdl"
  341. #define TF_WEAPON_PIPEBOMB_BOUNCE_SOUND "Weapon_Grenade_Pipebomb.Bounce"
  342. #define TF_WEAPON_CANNON_IMPACT_SOUND "Weapon_LooseCannon.BallImpact"
  343. #define TF_WEAPON_GRENADE_DETONATE_TIME 2.0f
  344. #define TF_WEAPON_GRENADE_XBOX_DAMAGE 112
  345. BEGIN_DATADESC( CTFGrenadePipebombProjectile )
  346. END_DATADESC()
  347. LINK_ENTITY_TO_CLASS( tf_projectile_pipe_remote, CTFGrenadePipebombProjectile );
  348. PRECACHE_WEAPON_REGISTER( tf_projectile_pipe_remote );
  349. LINK_ENTITY_TO_CLASS( tf_projectile_pipe, CTFGrenadePipebombProjectile );
  350. PRECACHE_WEAPON_REGISTER( tf_projectile_pipe );
  351. //-----------------------------------------------------------------------------
  352. // Purpose:
  353. //-----------------------------------------------------------------------------
  354. const char* CTFGrenadePipebombProjectile::GetPipebombClass( int iPipeBombType )
  355. {
  356. switch ( iPipeBombType )
  357. {
  358. case TF_GL_MODE_REGULAR:
  359. return "tf_projectile_pipe";
  360. case TF_GL_MODE_REMOTE_DETONATE:
  361. case TF_GL_MODE_REMOTE_DETONATE_PRACTICE:
  362. return "tf_projectile_pipe_remote";
  363. default:
  364. return "tf_projectile_pipe";
  365. }
  366. }
  367. //-----------------------------------------------------------------------------
  368. // Purpose:
  369. //-----------------------------------------------------------------------------
  370. CTFGrenadePipebombProjectile* CTFGrenadePipebombProjectile::Create( const Vector &position, const QAngle &angles,
  371. const Vector &velocity, const AngularImpulse &angVelocity,
  372. CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo,
  373. int iPipeBombType, float flMultDmg )
  374. {
  375. // Translate a projectile type into a pipebomb type.
  376. int iPipeBombDetonateType;
  377. switch ( iPipeBombType )
  378. {
  379. case TF_PROJECTILE_PIPEBOMB_REMOTE:
  380. {
  381. iPipeBombDetonateType = TF_GL_MODE_REMOTE_DETONATE;
  382. }
  383. break;
  384. case TF_PROJECTILE_PIPEBOMB_PRACTICE:
  385. {
  386. iPipeBombDetonateType = TF_GL_MODE_REMOTE_DETONATE_PRACTICE;
  387. }
  388. break;
  389. case TF_PROJECTILE_CANNONBALL:
  390. {
  391. iPipeBombDetonateType = TF_GL_MODE_CANNONBALL;
  392. }
  393. break;
  394. default:
  395. iPipeBombDetonateType = TF_GL_MODE_REGULAR;
  396. }
  397. const char* pszBombClass = GetPipebombClass( iPipeBombDetonateType );
  398. CTFGrenadePipebombProjectile *pGrenade = static_cast<CTFGrenadePipebombProjectile*>( CBaseEntity::CreateNoSpawn( pszBombClass, position, angles, pOwner ) );
  399. if ( pGrenade )
  400. {
  401. // Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly
  402. pGrenade->SetPipebombMode( iPipeBombDetonateType );
  403. DispatchSpawn( pGrenade );
  404. pGrenade->InitGrenade( velocity, angVelocity, pOwner, weaponInfo );
  405. pGrenade->SetDamage( pGrenade->GetDamage() * flMultDmg );
  406. pGrenade->SetFullDamage( pGrenade->GetDamage() );
  407. if ( pGrenade->m_iType != TF_GL_MODE_REMOTE_DETONATE )
  408. {
  409. // Some hackery here. Reduce the damage, so that if we explode on timeout,
  410. // we'll do less damage. If we explode on contact, we'll restore this to full damage.
  411. pGrenade->SetDamage( pGrenade->GetDamage() * TF_WEAPON_PIPEBOMB_TIMER_DMG_REDUCTION );
  412. }
  413. pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
  414. if ( pOwner )
  415. {
  416. pGrenade->SetTruceValidForEnt( pOwner->IsTruceValidForEnt() );
  417. }
  418. }
  419. return pGrenade;
  420. }
  421. //-----------------------------------------------------------------------------
  422. // Purpose:
  423. //-----------------------------------------------------------------------------
  424. void CTFGrenadePipebombProjectile::Spawn()
  425. {
  426. if ( HasStickyEffects() )
  427. {
  428. // Set this to max, so effectively they do not self-implode.
  429. if ( m_iType == TF_GL_MODE_REMOTE_DETONATE_PRACTICE )
  430. {
  431. SetModel( TF_WEAPON_PIPEBOMB2_MODEL );
  432. }
  433. else
  434. {
  435. SetModel( TF_WEAPON_PIPEBOMB_MODEL );
  436. }
  437. SetDetonateTimerLength( FLT_MAX );
  438. SetContextThink( &CTFGrenadePipebombProjectile::PreArmThink, gpGlobals->curtime + 0.001f, "PRE_ARM_THINK" ); // Next frame.
  439. SetTouch( &CTFGrenadePipebombProjectile::StickybombTouch );
  440. }
  441. else
  442. {
  443. if ( m_iType == TF_GL_MODE_CANNONBALL )
  444. {
  445. SetModel( TF_WEAPON_CANNONBALL_MODEL );
  446. }
  447. else
  448. {
  449. SetModel( TF_WEAPON_PIPEGRENADE_MODEL );
  450. }
  451. SetDetonateTimerLength( TF_WEAPON_GRENADE_DETONATE_TIME );
  452. SetTouch( &CTFGrenadePipebombProjectile::PipebombTouch );
  453. }
  454. SetCustomPipebombModel();
  455. BaseClass::Spawn();
  456. m_bTouched = false;
  457. m_flCreationTime = gpGlobals->curtime;
  458. // We want to get touch functions called so we can damage enemy players
  459. AddSolidFlags( FSOLID_TRIGGER );
  460. m_flMinSleepTime = 0;
  461. AddFlag( FL_GRENADE );
  462. }
  463. //-----------------------------------------------------------------------------
  464. // Purpose:
  465. //-----------------------------------------------------------------------------
  466. void CTFGrenadePipebombProjectile::Precache()
  467. {
  468. int iModel = PrecacheModel( TF_WEAPON_PIPEBOMB_MODEL );
  469. PrecacheGibsForModel( iModel );
  470. iModel = PrecacheModel( TF_WEAPON_PIPEBOMB2_MODEL );
  471. PrecacheGibsForModel( iModel );
  472. iModel = PrecacheModel( TF_WEAPON_PIPEBOMBD_MODEL );
  473. PrecacheGibsForModel( iModel );
  474. iModel = PrecacheModel( TF_WEAPON_PIPEGRENADE_MODEL );
  475. PrecacheGibsForModel( iModel );
  476. iModel = PrecacheModel( TF_WEAPON_CANNONBALL_MODEL );
  477. PrecacheGibsForModel( iModel );
  478. // Must add All custom Models here
  479. iModel = PrecacheModel( "models/workshop/weapons/c_models/c_kingmaker_sticky/w_kingmaker_stickybomb.mdl" );
  480. iModel = PrecacheModel( "models/workshop/weapons/c_models/c_quadball/w_quadball_grenade.mdl" );
  481. PrecacheParticleSystem( "stickybombtrail_blue" );
  482. PrecacheParticleSystem( "stickybombtrail_red" );
  483. PrecacheScriptSound( TF_WEAPON_PIPEBOMB_BOUNCE_SOUND );
  484. PrecacheScriptSound( TF_WEAPON_CANNON_IMPACT_SOUND );
  485. BaseClass::Precache();
  486. }
  487. //-----------------------------------------------------------------------------
  488. // Purpose:
  489. //-----------------------------------------------------------------------------
  490. void CTFGrenadePipebombProjectile::SetPipebombMode( int iPipebombMode /* = TF_GL_MODE_REGULAR */ )
  491. {
  492. m_iType.Set( iPipebombMode );
  493. }
  494. //-----------------------------------------------------------------------------
  495. // Purpose:
  496. //-----------------------------------------------------------------------------
  497. void CTFGrenadePipebombProjectile::BounceSound( void )
  498. {
  499. EmitSound( TF_WEAPON_PIPEBOMB_BOUNCE_SOUND );
  500. }
  501. //-----------------------------------------------------------------------------
  502. // Purpose:
  503. //-----------------------------------------------------------------------------
  504. void CTFGrenadePipebombProjectile::Detonate()
  505. {
  506. if ( gpGlobals->curtime > m_flDetonateTime )
  507. {
  508. if ( GetLauncher() )
  509. {
  510. float flFizzle = 0;
  511. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(), flFizzle, stickybomb_fizzle_time );
  512. if ( flFizzle )
  513. {
  514. Fizzle();
  515. }
  516. }
  517. }
  518. if ( m_bFizzle )
  519. {
  520. g_pEffects->Sparks( GetAbsOrigin(), 1, 2 );
  521. Destroy( false );
  522. if ( HasStickyEffects() )
  523. {
  524. CreatePipebombGibs();
  525. }
  526. return;
  527. }
  528. BaseClass::Detonate();
  529. }
  530. //-----------------------------------------------------------------------------
  531. // Purpose:
  532. //-----------------------------------------------------------------------------
  533. bool CTFGrenadePipebombProjectile::DetonateStickies()
  534. {
  535. if ( !GetLauncher() )
  536. return false;
  537. bool bDetonateSticky = false;
  538. Vector vecOrigin = GetAbsOrigin();
  539. const int maxEntities = 64;
  540. CBaseEntity *pObjects[ maxEntities ];
  541. int count = UTIL_EntitiesInSphere( pObjects, maxEntities, vecOrigin, GetDamageRadius(), FL_GRENADE );
  542. int iStickiesRemoved = 0;
  543. trace_t tr;
  544. for ( int i = 0; i < count; i++ )
  545. {
  546. if ( pObjects[i]->GetTeamNumber() == GetLauncher()->GetTeamNumber() )
  547. continue;
  548. CTFGrenadePipebombProjectile *pGrenade = dynamic_cast < CTFGrenadePipebombProjectile*> ( pObjects[i] );
  549. if ( !pGrenade )
  550. continue;
  551. if ( pGrenade->m_iType != TF_GL_MODE_REMOTE_DETONATE )
  552. continue;
  553. if ( pGrenade->m_bFizzle )
  554. continue;
  555. UTIL_TraceLine( vecOrigin, pGrenade->GetAbsOrigin(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  556. if ( tr.fraction < 1.0 )
  557. continue; // No line of sight to the bomb.
  558. pGrenade->Fizzle();
  559. pGrenade->Detonate();
  560. iStickiesRemoved++;
  561. bDetonateSticky = true;
  562. }
  563. CTFPlayer *pOwner = ToTFPlayer( GetThrower() );
  564. if ( iStickiesRemoved && pOwner )
  565. {
  566. pOwner->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_DESTROY_X_STICKYBOMBS, iStickiesRemoved );
  567. }
  568. return bDetonateSticky;
  569. }
  570. //-----------------------------------------------------------------------------
  571. // Purpose:
  572. //-----------------------------------------------------------------------------
  573. void CTFGrenadePipebombProjectile::CreatePipebombGibs( void )
  574. {
  575. CPVSFilter filter( GetAbsOrigin() );
  576. UserMessageBegin( filter, "CheapBreakModel" );
  577. WRITE_SHORT( GetModelIndex() );
  578. WRITE_VEC3COORD( GetAbsOrigin() );
  579. MessageEnd();
  580. }
  581. //-----------------------------------------------------------------------------
  582. // Purpose:
  583. //-----------------------------------------------------------------------------
  584. void CTFGrenadePipebombProjectile::Fizzle( void )
  585. {
  586. m_bFizzle = true;
  587. }
  588. //-----------------------------------------------------------------------------
  589. // Purpose:
  590. //-----------------------------------------------------------------------------
  591. void CTFGrenadePipebombProjectile::StickybombTouch( CBaseEntity *pOther )
  592. {
  593. #ifdef GAME_DLL
  594. #ifdef TF_RAID_MODE
  595. if ( TFGameRules()->IsRaidMode() )
  596. {
  597. if ( dynamic_cast< CBossAlpha * >( pOther ) != NULL )
  598. {
  599. // stickies stick to the boss
  600. m_bTouched = true;
  601. VPhysicsGetObject()->EnableMotion( false );
  602. SetParent( pOther );
  603. }
  604. }
  605. #endif
  606. #endif
  607. }
  608. //-----------------------------------------------------------------------------
  609. // Purpose:
  610. //-----------------------------------------------------------------------------
  611. void CTFGrenadePipebombProjectile::PipebombTouch( CBaseEntity *pOther )
  612. {
  613. if ( pOther == GetThrower() )
  614. return;
  615. // Verify a correct "other."
  616. if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
  617. return;
  618. // Handle hitting skybox (disappear).
  619. trace_t pTrace;
  620. Vector velDir = GetAbsVelocity();
  621. VectorNormalize( velDir );
  622. Vector vOrigin = GetAbsOrigin();
  623. Vector vecSpot = vOrigin - velDir * 32;
  624. UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );
  625. if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY )
  626. {
  627. UTIL_Remove( this );
  628. return;
  629. }
  630. // PASSTIME always explode when it hits the ball
  631. // fixme find a non-strcmp way to do this
  632. if ( !V_strcmp( pOther->GetClassname(), "passtime_ball" ) )
  633. {
  634. Explode( &pTrace, GetDamageType() );
  635. return;
  636. }
  637. //If we already touched a surface then we're not exploding on contact anymore.
  638. if ( m_bTouched == true )
  639. return;
  640. bool bExploded = false;
  641. // Blow up if we hit an enemy we can damage
  642. if ( pOther->GetTeamNumber() && pOther->GetTeamNumber() != GetTeamNumber() && pOther->m_takedamage != DAMAGE_NO )
  643. {
  644. // Check to see if this is a respawn room.
  645. if ( !pOther->IsPlayer() )
  646. {
  647. CFuncRespawnRoom *pRespawnRoom = dynamic_cast<CFuncRespawnRoom*>( pOther );
  648. if ( pRespawnRoom )
  649. {
  650. if ( !pRespawnRoom->PointIsWithin( vOrigin ) )
  651. return;
  652. }
  653. }
  654. if ( m_iType == TF_GL_MODE_CANNONBALL )
  655. {
  656. // Damage the player to push them back
  657. CBaseEntity *pAttacker = GetThrower();
  658. if ( pAttacker && ( pOther->IsPlayer() || pOther->IsBaseObject() ) )
  659. {
  660. // check if we already penetrate through this victim
  661. if ( !m_penetratedEntities.HasElement( pOther ) )
  662. {
  663. // Impact damage scales with distance
  664. float flDistanceSq = (pOther->GetAbsOrigin() - pAttacker->GetAbsOrigin()).LengthSqr();
  665. float flImpactDamage = RemapValClamped( flDistanceSq, 512 * 512, 1024 * 1024, 50, 25 );
  666. CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vOrigin, flImpactDamage, GetDamageType(), TF_DMG_CUSTOM_CANNONBALL_PUSH );
  667. pOther->TakeDamage( info );
  668. CTFPlayer *pVictim = ToTFPlayer( pOther );
  669. if ( pVictim )
  670. {
  671. // apply airblast - Apply stun if they are effectively grounded so we can knock them up
  672. if ( !pVictim->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) )
  673. {
  674. pVictim->m_Shared.StunPlayer( 0.5, 1.f, TF_STUN_MOVEMENT, ToTFPlayer( pAttacker ) );
  675. }
  676. Vector vecToTarget = pVictim->WorldSpaceCenter() - pAttacker->WorldSpaceCenter();
  677. VectorNormalize( vecToTarget );
  678. vecToTarget *= 400;
  679. vecToTarget.z += 350; // Mimic Flamethrower AirBlast
  680. pVictim->ApplyAirBlastImpulse( vecToTarget );
  681. }
  682. m_penetratedEntities.AddToTail( pOther );
  683. EmitSound( TF_WEAPON_CANNON_IMPACT_SOUND );
  684. // Add this guy to our donk list. If this grenade explodes and hits anyone on our launcher's
  685. // donk list, they get minicrit
  686. CTFGrenadeLauncher* pLauncher = dynamic_cast<CTFGrenadeLauncher*>( GetLauncher() );
  687. if( pLauncher )
  688. {
  689. pLauncher->AddDonkVictim( pOther );
  690. }
  691. }
  692. return;
  693. }
  694. }
  695. // Save this entity as enemy, they will take 100% damage.
  696. m_hEnemy = pOther;
  697. // Restore damage. See comment in CTFGrenadePipebombProjectile::Create() above to understand this.
  698. m_flDamage = m_flFullDamage;
  699. Explode( &pTrace, GetDamageType() );
  700. bExploded = true;
  701. }
  702. // Train hack!
  703. if ( !bExploded && pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) )
  704. {
  705. Explode( &pTrace, GetDamageType() );
  706. bExploded = true;
  707. }
  708. // Explode on contact with a Boss, too
  709. if ( !bExploded && TFGameRules()->GetActiveBoss() && pOther == TFGameRules()->GetActiveBoss() )
  710. {
  711. Explode( &pTrace, GetDamageType() );
  712. bExploded = true;
  713. }
  714. }
  715. //-----------------------------------------------------------------------------
  716. // Purpose:
  717. //-----------------------------------------------------------------------------
  718. extern bool PropDynamic_CollidesWithGrenades( CBaseEntity* pBaseEntity );
  719. void CTFGrenadePipebombProjectile::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  720. {
  721. BaseClass::VPhysicsCollision( index, pEvent );
  722. int otherIndex = !index;
  723. CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
  724. if ( !pHitEntity )
  725. return;
  726. if ( m_bWallShatter )
  727. {
  728. Fizzle();
  729. Detonate();
  730. return;
  731. }
  732. if ( m_iType == TF_GL_MODE_REGULAR || m_iType == TF_GL_MODE_CANNONBALL )
  733. {
  734. if ( PropDynamic_CollidesWithGrenades( pHitEntity) )
  735. {
  736. if ( m_bTouched == false )
  737. {
  738. SetThink( &CTFGrenadePipebombProjectile::Detonate );
  739. SetNextThink( gpGlobals->curtime );
  740. }
  741. }
  742. // Blow up if we hit an enemy we can damage
  743. else if ( pHitEntity->GetTeamNumber() && pHitEntity->GetTeamNumber() != GetTeamNumber() && pHitEntity->m_takedamage != DAMAGE_NO )
  744. {
  745. SetThink( &CTFGrenadePipebombProjectile::Detonate );
  746. SetNextThink( gpGlobals->curtime );
  747. }
  748. if ( m_bTouched == false )
  749. {
  750. SetDamage( GetDamageScaleOnWorldContact() * GetDamage() );
  751. int iNoBounce = 0;
  752. if ( GetLauncher() )
  753. {
  754. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetLauncher(), iNoBounce, grenade_no_bounce )
  755. if ( iNoBounce )
  756. {
  757. Vector velocity;
  758. AngularImpulse angularVelocity;
  759. VPhysicsGetObject()->GetVelocity( &velocity, &angularVelocity );
  760. velocity *= 0.1f;
  761. VPhysicsGetObject()->SetVelocity( &velocity, &angularVelocity );
  762. }
  763. }
  764. }
  765. m_bTouched = true;
  766. return;
  767. }
  768. // Handle hitting skybox (disappear).
  769. surfacedata_t *pprops = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] );
  770. if ( pprops->game.material == 'X' )
  771. {
  772. // uncomment to destroy grenade upon hitting sky brush
  773. //SetThink( &CTFGrenadePipebombProjectile::SUB_Remove );
  774. //SetNextThink( gpGlobals->curtime );
  775. return;
  776. }
  777. bool bIsDynamicProp = ( NULL != dynamic_cast<CDynamicProp *>( pHitEntity ) );
  778. // Temp: Don't stick to the saw blades in sawmill.
  779. // We should make the saws their own entity type for networking.
  780. if ( FStrEq( pHitEntity->m_iParent.ToCStr(), "sawmovelinear01" ) ||
  781. FStrEq( pHitEntity->m_iParent.ToCStr(), "sawmovelinear02" ) ||
  782. PropDynamic_CollidesWithGrenades( pHitEntity) )
  783. {
  784. bIsDynamicProp = false;
  785. }
  786. // Pipebombs stick to the world when they touch it
  787. if ( pHitEntity && ( pHitEntity->IsWorld() || bIsDynamicProp ) && gpGlobals->curtime > m_flMinSleepTime )
  788. {
  789. m_bTouched = true;
  790. g_PostSimulationQueue.QueueCall( VPhysicsGetObject(), &IPhysicsObject::EnableMotion, false );
  791. // Save impact data for explosions.
  792. m_bUseImpactNormal = true;
  793. pEvent->pInternalData->GetSurfaceNormal( m_vecImpactNormal );
  794. m_vecImpactNormal.Negate();
  795. m_flTouchedTime = gpGlobals->curtime;
  796. float flFizzle = 0;
  797. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(), flFizzle, stickybomb_fizzle_time );
  798. if ( flFizzle > 0 )
  799. {
  800. SetDetonateTimerLength( flFizzle );
  801. }
  802. }
  803. }
  804. ConVar tf_grenade_forcefrom_bullet( "tf_grenade_forcefrom_bullet", "2.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  805. ConVar tf_grenade_forcefrom_buckshot( "tf_grenade_forcefrom_buckshot", "0.75", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  806. ConVar tf_grenade_forcefrom_blast( "tf_grenade_forcefrom_blast", "0.15", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  807. ConVar tf_grenade_force_sleeptime( "tf_grenade_force_sleeptime", "1.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); // How long after being shot will we re-stick to the world.
  808. ConVar tf_pipebomb_force_to_move( "tf_pipebomb_force_to_move", "1500.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  809. ConVar tf_pipebomb_deflect_reset_time( "tf_pipebomb_deflect_reset_time", "10.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  810. //-----------------------------------------------------------------------------
  811. // Purpose: If we are shot after being stuck to the world, move a bit
  812. //-----------------------------------------------------------------------------
  813. int CTFGrenadePipebombProjectile::OnTakeDamage( const CTakeDamageInfo &info )
  814. {
  815. if ( !info.GetAttacker() )
  816. {
  817. Assert( !info.GetAttacker() );
  818. return 0;
  819. }
  820. bool bSameTeam = ( info.GetAttacker()->GetTeamNumber() == GetTeamNumber() );
  821. if ( !bSameTeam && CanTakeDamage() )
  822. {
  823. if ( m_bTouched && HasStickyEffects() && ( info.GetDamageType() & (DMG_BULLET|DMG_BUCKSHOT|DMG_BLAST|DMG_SONIC|DMG_MELEE) ) )
  824. {
  825. Vector vecForce = info.GetDamageForce();
  826. bool bBreakPipes = false;
  827. if ( info.GetDamageType() & (DMG_BULLET|DMG_MELEE) )
  828. {
  829. vecForce *= tf_grenade_forcefrom_bullet.GetFloat();
  830. bBreakPipes = true;
  831. }
  832. if ( info.GetDamageType() & DMG_SONIC )
  833. {
  834. vecForce *= tf_grenade_forcefrom_bullet.GetFloat();
  835. }
  836. else if ( info.GetDamageType() & DMG_BUCKSHOT )
  837. {
  838. vecForce *= tf_grenade_forcefrom_buckshot.GetFloat();
  839. bBreakPipes = true;
  840. }
  841. else if ( info.GetDamageType() & DMG_BLAST )
  842. {
  843. vecForce *= tf_grenade_forcefrom_blast.GetFloat();
  844. }
  845. if ( bBreakPipes == true )
  846. {
  847. // we might get multiple calls for the same pipe when shooting it with a shotgun,
  848. // so make sure it only sends the player_destroyed_pipebomb event once
  849. if ( m_bSendPlayerDestroyedEvent )
  850. {
  851. if ( info.GetAttacker()->IsPlayer() )
  852. {
  853. CTFPlayer *pPlayer = ToTFPlayer( info.GetAttacker() );
  854. if ( pPlayer )
  855. {
  856. IGameEvent * event = gameeventmanager->CreateEvent( "player_destroyed_pipebomb" );
  857. if ( event )
  858. {
  859. event->SetInt( "userid", pPlayer->GetUserID() );
  860. gameeventmanager->FireEvent( event );
  861. }
  862. if ( pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) )
  863. {
  864. // If we are near a building, award achievement progress.
  865. CTFTeam *pTeam = pPlayer->GetTFTeam();
  866. if ( pTeam )
  867. {
  868. for ( int i=0; i<pTeam->GetNumObjects(); i++ )
  869. {
  870. CBaseObject *pObject = pTeam->GetObject(i);
  871. if ( pObject && pObject->GetAbsOrigin().DistTo( GetAbsOrigin() ) < 100 &&
  872. pObject->ObjectType() != OBJ_ATTACHMENT_SAPPER )
  873. {
  874. pPlayer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DESTROY_STICKIES, 1 );
  875. break; // Only one award per sticky.
  876. }
  877. }
  878. }
  879. }
  880. }
  881. m_bSendPlayerDestroyedEvent = false;
  882. }
  883. }
  884. Fizzle();
  885. Detonate();
  886. }
  887. // If the force is sufficient, detach & move the pipebomb
  888. float flForce = tf_pipebomb_force_to_move.GetFloat();
  889. if ( vecForce.LengthSqr() > (flForce*flForce) )
  890. {
  891. if ( VPhysicsGetObject() )
  892. {
  893. VPhysicsGetObject()->EnableMotion( true );
  894. }
  895. CTakeDamageInfo newInfo = info;
  896. newInfo.SetDamageForce( vecForce );
  897. VPhysicsTakeDamage( newInfo );
  898. // The pipebomb will re-stick to the ground after this time expires
  899. m_flMinSleepTime = gpGlobals->curtime + tf_grenade_force_sleeptime.GetFloat();
  900. m_bTouched = false;
  901. // It has moved the data is no longer valid.
  902. m_bUseImpactNormal = false;
  903. m_vecImpactNormal.Init();
  904. return 1;
  905. }
  906. }
  907. }
  908. return 0;
  909. }
  910. void CTFGrenadePipebombProjectile::IncrementDeflected( void )
  911. {
  912. BaseClass::IncrementDeflected();
  913. if ( GetDeflected() && HasStickyEffects() )
  914. {
  915. m_flDeflectedTime = gpGlobals->curtime + tf_pipebomb_deflect_reset_time.GetFloat();
  916. }
  917. int iTeamNumber = GetTeamNumber();
  918. CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() );
  919. if ( pOwner )
  920. {
  921. iTeamNumber = pOwner->GetTeamNumber();
  922. }
  923. if ( !HasStickyEffects() )
  924. {
  925. m_nSkin = ( iTeamNumber == TF_TEAM_BLUE ) ? 1 : 0;
  926. }
  927. }
  928. void CTFGrenadePipebombProjectile::DetonateThink( void )
  929. {
  930. BaseClass::DetonateThink();
  931. if ( m_flDeflectedTime <= gpGlobals->curtime && HasStickyEffects() )
  932. {
  933. ResetDeflected();
  934. SetDeflectOwner( NULL );
  935. }
  936. // If we received our crit via a medic, make sure they still exist.
  937. if ( m_CritMedics.Count() )
  938. {
  939. if ( TFGameRules() && ( TFGameRules()->InSetup() || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) )
  940. {
  941. bool bRemove = true;
  942. FOR_EACH_VEC( m_CritMedics, i )
  943. {
  944. if ( m_CritMedics[i] && m_CritMedics[i]->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC )
  945. {
  946. bRemove = false;
  947. break;
  948. }
  949. }
  950. // No medic(s)
  951. if ( bRemove )
  952. {
  953. Fizzle();
  954. Detonate();
  955. return;
  956. }
  957. }
  958. else
  959. {
  960. // Clear the vector when the game starts
  961. m_CritMedics.RemoveAll();
  962. }
  963. }
  964. }
  965. void CTFGrenadePipebombProjectile::PreArmThink( void )
  966. {
  967. SetContextThink( &CTFGrenadePipebombProjectile::ArmThink, gpGlobals->curtime + GetLiveTime(), "ARM_THINK" );
  968. }
  969. void CTFGrenadePipebombProjectile::ArmThink( void )
  970. {
  971. // When between waves in MvM, players sometimes switch to medic just so demos can place crit stickies,
  972. // and then switch back. This code removes the sticky if the medic switches ( in DetonateThink() )
  973. if ( IsCritical() && HasStickyEffects() && TFGameRules() && ( TFGameRules()->InSetup() || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) )
  974. {
  975. CTFPlayer *pOwner = ToTFPlayer( GetThrower() );
  976. if ( pOwner && pOwner->m_Shared.InCond( TF_COND_CRITBOOSTED ) && !pOwner->m_Shared.InCond( TF_COND_CRITBOOSTED_USER_BUFF ) )
  977. {
  978. // Find the medic(s)
  979. for ( int i = 0; i < pOwner->m_Shared.GetNumHealers(); i++ )
  980. {
  981. CTFPlayer *pMedic = ToTFPlayer( pOwner->m_Shared.GetHealerByIndex( i ) );
  982. if ( !pMedic )
  983. continue;
  984. CWeaponMedigun *pMedigun = dynamic_cast <CWeaponMedigun*>( pMedic->GetActiveTFWeapon() );
  985. if ( pMedigun && pMedigun->IsReleasingCharge() && pMedigun->GetChargeType() == MEDIGUN_CHARGE_CRITICALBOOST )
  986. {
  987. m_CritMedics.AddToTail( pMedic );
  988. }
  989. }
  990. // We didn't find the medic. What provided TF_COND_CRITBOOSTED?
  991. Assert( m_CritMedics.Count() );
  992. }
  993. }
  994. if ( m_bDetonateOnPulse )
  995. {
  996. Detonate();
  997. }
  998. }
  999. #endif
  1000. //------------------------------------------------------------------------------
  1001. // Purpose:
  1002. //-----------------------------------------------------------------------------
  1003. float CTFGrenadePipebombProjectile::GetLiveTime( void )
  1004. {
  1005. float flLiveTime = tf_grenadelauncher_livetime.GetFloat();
  1006. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(), flLiveTime, sticky_arm_time );
  1007. if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
  1008. {
  1009. CTFPlayer *pOwner = ToTFPlayer( GetThrower() );
  1010. if ( pOwner )
  1011. {
  1012. if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
  1013. {
  1014. flLiveTime *= 0.5f;
  1015. }
  1016. else if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_KING || pOwner->m_Shared.InCond( TF_COND_KING_BUFFED ) )
  1017. {
  1018. flLiveTime *= 0.75f;
  1019. }
  1020. }
  1021. }
  1022. return flLiveTime;
  1023. }
  1024. #ifdef GAME_DLL
  1025. //-----------------------------------------------------------------------------
  1026. // Purpose: Grenade was deflected.
  1027. //-----------------------------------------------------------------------------
  1028. void CTFGrenadePipebombProjectile::Deflected( CBaseEntity *pDeflectedBy, Vector& vecDir )
  1029. {
  1030. CTFPlayer *pTFDeflector = ToTFPlayer( pDeflectedBy );
  1031. if ( !pTFDeflector )
  1032. return;
  1033. CTFPlayer* pOldOwner = NULL;
  1034. if ( HasStickyEffects() )
  1035. {
  1036. CTakeDamageInfo info;
  1037. float flForceMultiplier = 1.0f;
  1038. ITFChargeUpWeapon *pWeapon = dynamic_cast<ITFChargeUpWeapon*>( pTFDeflector->GetActiveWeapon() );
  1039. if ( pWeapon )
  1040. {
  1041. flForceMultiplier = RemapValClamped( ( gpGlobals->curtime - pWeapon->GetChargeBeginTime() ),
  1042. 0.0f,
  1043. pWeapon->GetChargeMaxTime(),
  1044. 1.0f,
  1045. 2.0f );
  1046. }
  1047. Vector vecForce = vecDir * flForceMultiplier * -CTFWeaponBase::DeflectionForce( WorldAlignSize(), 90, 12.0f );
  1048. pOldOwner = ToTFPlayer( GetThrower() );
  1049. info.SetAttacker( pDeflectedBy );
  1050. info.SetDamageForce( vecForce );
  1051. info.SetDamageType( DMG_SONIC );
  1052. info.SetWeapon( pTFDeflector->GetActiveTFWeapon() );
  1053. OnTakeDamage( info );
  1054. }
  1055. else
  1056. {
  1057. ChangeTeam( pTFDeflector->GetTeamNumber() );
  1058. SetLauncher( pTFDeflector->GetActiveWeapon() );
  1059. pOldOwner = ToTFPlayer( GetThrower() );
  1060. SetThrower( pTFDeflector );
  1061. if ( pTFDeflector->m_Shared.IsCritBoosted() )
  1062. {
  1063. SetCritical( true );
  1064. }
  1065. }
  1066. if ( pOldOwner )
  1067. {
  1068. pOldOwner->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:1,victim:1" );
  1069. }
  1070. CTFWeaponBase::SendObjectDeflectedEvent( pTFDeflector, pOldOwner, GetWeaponID(), this );
  1071. SetDeflectOwner( pTFDeflector );
  1072. IncrementDeflected();
  1073. }
  1074. #endif
  1075. #ifdef CLIENT_DLL
  1076. //-----------------------------------------------------------------------------
  1077. // Purpose: Highlight FX
  1078. //-----------------------------------------------------------------------------
  1079. class CProxyStickybombGlowColor : public CResultProxy
  1080. {
  1081. public:
  1082. void OnBind( void *pC_BaseEntity )
  1083. {
  1084. Assert( m_pResult );
  1085. if ( !pC_BaseEntity )
  1086. {
  1087. m_pResult->SetVecValue( 1, 1, 1 );
  1088. return;
  1089. }
  1090. CTFGrenadePipebombProjectile *pGrenade = NULL;
  1091. C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
  1092. if ( !pEntity )
  1093. {
  1094. m_pResult->SetVecValue( 1, 1, 1 );
  1095. return;
  1096. }
  1097. // default to [1 1 1]
  1098. Vector vResult = Vector( 1, 1, 1 );
  1099. pGrenade = dynamic_cast<CTFGrenadePipebombProjectile*>( pEntity );
  1100. if ( pGrenade )
  1101. {
  1102. if ( pGrenade->IsHighlighted() )
  1103. {
  1104. int iTeamNumber = pGrenade->GetTeamNumber();
  1105. if ( iTeamNumber == TF_TEAM_RED )
  1106. {
  1107. vResult = Vector ( 100.f, 0.f, 0.f );
  1108. if ( pGrenade->m_pGlowEffect )
  1109. {
  1110. pGrenade->m_pGlowEffect->SetColor( Vector( 250, 0, 0 ) );
  1111. }
  1112. }
  1113. else
  1114. {
  1115. vResult = Vector ( 0.f, 0.f, 100.f );
  1116. if ( pGrenade->m_pGlowEffect )
  1117. {
  1118. pGrenade->m_pGlowEffect->SetColor( Vector( 0, 0, 250 ) );
  1119. }
  1120. }
  1121. }
  1122. else
  1123. {
  1124. int iTeamNumber = pGrenade->GetTeamNumber();
  1125. if ( iTeamNumber == TF_TEAM_RED )
  1126. {
  1127. if ( pGrenade->m_pGlowEffect )
  1128. {
  1129. pGrenade->m_pGlowEffect->SetColor( Vector( 200, 100, 100 ) );
  1130. }
  1131. }
  1132. else
  1133. {
  1134. if ( pGrenade->m_pGlowEffect )
  1135. {
  1136. pGrenade->m_pGlowEffect->SetColor( Vector( 100, 100, 200 ) );
  1137. }
  1138. }
  1139. }
  1140. }
  1141. m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z );
  1142. }
  1143. };
  1144. EXPOSE_INTERFACE( CProxyStickybombGlowColor, IMaterialProxy, "StickybombGlowColor" IMATERIAL_PROXY_INTERFACE_VERSION );
  1145. #endif
  1146. //-----------------------------------------------------------------------------
  1147. // Purpose:
  1148. //-----------------------------------------------------------------------------
  1149. #if GAME_DLL
  1150. int CTFGrenadePipebombProjectile::GetDamageCustom()
  1151. {
  1152. if ( m_iType == TF_GL_MODE_REMOTE_DETONATE )
  1153. {
  1154. if ( !m_bTouched )
  1155. {
  1156. return TF_DMG_CUSTOM_AIR_STICKY_BURST;
  1157. }
  1158. else if ( m_bDefensiveBomb )
  1159. {
  1160. return TF_DMG_CUSTOM_DEFENSIVE_STICKY;
  1161. }
  1162. else
  1163. {
  1164. return TF_DMG_CUSTOM_STANDARD_STICKY;
  1165. }
  1166. }
  1167. else if ( m_iType == TF_GL_MODE_REMOTE_DETONATE_PRACTICE )
  1168. {
  1169. return TF_DMG_CUSTOM_PRACTICE_STICKY;
  1170. }
  1171. return BaseClass::GetDamageCustom();
  1172. }
  1173. float CTFGrenadePipebombProjectile::GetDamageScaleOnWorldContact()
  1174. {
  1175. float flGrenadeDamageScaleOnWorldContact = 1.f;
  1176. if ( GetLauncher() )
  1177. {
  1178. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(),flGrenadeDamageScaleOnWorldContact, grenade_damage_reduction_on_world_contact );
  1179. }
  1180. return flGrenadeDamageScaleOnWorldContact;
  1181. }
  1182. #endif
  1183. //-----------------------------------------------------------------------------
  1184. float CTFGrenadePipebombProjectile::GetDamageRadius()
  1185. {
  1186. float flRadiusMod = 1.0f;
  1187. #ifdef GAME_DLL
  1188. // winbomb prevention.
  1189. // Air Det
  1190. if ( m_iType == TF_GL_MODE_REMOTE_DETONATE )
  1191. {
  1192. if ( m_bTouched == false )
  1193. {
  1194. float flArmTime = tf_grenadelauncher_livetime.GetFloat();
  1195. flRadiusMod *= RemapValClamped( gpGlobals->curtime - m_flCreationTime, flArmTime, flArmTime + tf_sticky_radius_ramp_time.GetFloat(), tf_sticky_airdet_radius.GetFloat(), 1.0 );
  1196. }
  1197. }
  1198. #endif // GAME_DLL
  1199. return BaseClass::GetDamageRadius() * flRadiusMod;
  1200. }