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.

643 lines
17 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_weapon_pipebomblauncher.h"
  8. #include "tf_fx_shared.h"
  9. #include "tf_weapon_grenade_pipebomb.h"
  10. #include "in_buttons.h"
  11. #include "datacache/imdlcache.h"
  12. #include "tf_gamerules.h"
  13. // Client specific.
  14. #ifdef CLIENT_DLL
  15. #include "c_tf_player.h"
  16. #include <vgui_controls/Panel.h>
  17. #include <vgui/ISurface.h>
  18. #include "prediction.h"
  19. #include "c_tf_gamestats.h"
  20. // Server specific.
  21. #else
  22. #include "tf_player.h"
  23. #include "tf_gamestats.h"
  24. #endif
  25. #define TF_PIPEBOMB_HIGHLIGHT 1
  26. #define TF_PIPEBOMB_DETONATE 2
  27. #define TF_WEAPON_PIPEBOMBD_MODEL "models/weapons/w_models/w_stickybomb_d.mdl"
  28. #define TF_WEAPON_PIPEBOMB_LAUNCHER_CHARGE_SOUND "Weapon_StickyBombLauncher.ChargeUp"
  29. //=============================================================================
  30. //
  31. // Weapon Pipebomb Launcher tables.
  32. //
  33. IMPLEMENT_NETWORKCLASS_ALIASED( TFPipebombLauncher, DT_WeaponPipebombLauncher )
  34. BEGIN_NETWORK_TABLE_NOBASE( CTFPipebombLauncher, DT_PipebombLauncherLocalData )
  35. #ifdef CLIENT_DLL
  36. RecvPropInt( RECVINFO( m_iPipebombCount ) ),
  37. RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ),
  38. #else
  39. SendPropInt( SENDINFO( m_iPipebombCount ), 5, SPROP_UNSIGNED ),
  40. SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
  41. #endif
  42. END_NETWORK_TABLE()
  43. BEGIN_NETWORK_TABLE( CTFPipebombLauncher, DT_WeaponPipebombLauncher )
  44. #ifdef CLIENT_DLL
  45. RecvPropDataTable( "PipebombLauncherLocalData", 0, 0, &REFERENCE_RECV_TABLE( DT_PipebombLauncherLocalData ) ),
  46. #else
  47. SendPropDataTable( "PipebombLauncherLocalData", 0, &REFERENCE_SEND_TABLE( DT_PipebombLauncherLocalData ), SendProxy_SendLocalWeaponDataTable ),
  48. #endif
  49. END_NETWORK_TABLE()
  50. #ifdef CLIENT_DLL
  51. BEGIN_PREDICTION_DATA( CTFPipebombLauncher )
  52. DEFINE_FIELD( m_flChargeBeginTime, FIELD_FLOAT )
  53. END_PREDICTION_DATA()
  54. #endif
  55. LINK_ENTITY_TO_CLASS( tf_weapon_pipebomblauncher, CTFPipebombLauncher );
  56. PRECACHE_WEAPON_REGISTER( tf_weapon_pipebomblauncher );
  57. // Server specific.
  58. #ifndef CLIENT_DLL
  59. BEGIN_DATADESC( CTFPipebombLauncher )
  60. END_DATADESC()
  61. #endif
  62. //=============================================================================
  63. //
  64. // Weapon Pipebomb Launcher functions.
  65. //
  66. //-----------------------------------------------------------------------------
  67. // Purpose:
  68. // Input : -
  69. //-----------------------------------------------------------------------------
  70. CTFPipebombLauncher::CTFPipebombLauncher()
  71. {
  72. m_bReloadsSingly = true;
  73. m_flLastDenySoundTime = 0.0f;
  74. m_bNoAutoRelease = false;
  75. m_bWantsToShoot = false;
  76. #ifdef CLIENT_DLL
  77. m_flNextBombCheckTime = 0;
  78. m_bBombThinking = false;
  79. #endif
  80. }
  81. //-----------------------------------------------------------------------------
  82. // Purpose:
  83. // Input : -
  84. //-----------------------------------------------------------------------------
  85. CTFPipebombLauncher::~CTFPipebombLauncher()
  86. {
  87. }
  88. //-----------------------------------------------------------------------------
  89. // Purpose:
  90. //-----------------------------------------------------------------------------
  91. void CTFPipebombLauncher::Spawn( void )
  92. {
  93. m_iAltFireHint = HINT_ALTFIRE_PIPEBOMBLAUNCHER;
  94. BaseClass::Spawn();
  95. }
  96. //-----------------------------------------------------------------------------
  97. // Purpose: Reset the charge when we holster
  98. //-----------------------------------------------------------------------------
  99. bool CTFPipebombLauncher::Holster( CBaseCombatWeapon *pSwitchingTo )
  100. {
  101. m_flChargeBeginTime = 0;
  102. return BaseClass::Holster( pSwitchingTo );
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose: Reset the charge when we deploy
  106. //-----------------------------------------------------------------------------
  107. bool CTFPipebombLauncher::Deploy( void )
  108. {
  109. m_flChargeBeginTime = 0;
  110. return BaseClass::Deploy();
  111. }
  112. //-----------------------------------------------------------------------------
  113. // Purpose:
  114. //-----------------------------------------------------------------------------
  115. void CTFPipebombLauncher::WeaponReset( void )
  116. {
  117. BaseClass::WeaponReset();
  118. #ifndef CLIENT_DLL
  119. DetonateRemotePipebombs( true );
  120. #endif
  121. m_flChargeBeginTime = 0.0f;
  122. }
  123. //-----------------------------------------------------------------------------
  124. // Purpose:
  125. //-----------------------------------------------------------------------------
  126. void CTFPipebombLauncher::ItemPostFrame( void )
  127. {
  128. BaseClass::ItemPostFrame();
  129. if ( m_flChargeBeginTime > 0 )
  130. {
  131. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
  132. if ( !pPlayer )
  133. return;
  134. // If we're not holding down the attack button, launch our grenade
  135. if ( m_iClip1 > 0 && !(pPlayer->m_nButtons & IN_ATTACK) && (pPlayer->m_afButtonReleased & IN_ATTACK) )
  136. {
  137. LaunchGrenade();
  138. }
  139. else if ( !m_bNoAutoRelease )
  140. {
  141. float flTotalChargeTime = gpGlobals->curtime - m_flChargeBeginTime;
  142. if ( flTotalChargeTime >= GetChargeForceReleaseTime() )
  143. {
  144. ForceLaunchGrenade();
  145. }
  146. }
  147. }
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Purpose:
  151. //-----------------------------------------------------------------------------
  152. void CTFPipebombLauncher::PrimaryAttack( void )
  153. {
  154. // Check for ammunition.
  155. if ( m_iClip1 <= 0 && m_iClip1 != -1 )
  156. return;
  157. // Are we capable of firing again?
  158. if ( m_flNextPrimaryAttack > gpGlobals->curtime )
  159. return;
  160. if ( !CanAttack() )
  161. {
  162. m_flChargeBeginTime = 0;
  163. return;
  164. }
  165. if ( m_flChargeBeginTime <= 0 )
  166. {
  167. // Set the weapon mode.
  168. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
  169. // save that we had the attack button down
  170. m_flChargeBeginTime = gpGlobals->curtime;
  171. SendWeaponAnim( ACT_VM_PULLBACK );
  172. #ifdef CLIENT_DLL
  173. EmitSound( TF_WEAPON_PIPEBOMB_LAUNCHER_CHARGE_SOUND );
  174. #endif // CLIENT_DLL
  175. }
  176. else
  177. {
  178. float flTotalChargeTime = gpGlobals->curtime - m_flChargeBeginTime;
  179. if ( flTotalChargeTime >= GetChargeMaxTime() )
  180. {
  181. LaunchGrenade();
  182. }
  183. }
  184. #ifdef CLIENT_DLL
  185. if ( GetDetonateMode() == TF_DETONATE_MODE_DOT && !m_bBombThinking )
  186. {
  187. m_bBombThinking = true;
  188. SetContextThink( &CTFPipebombLauncher::BombHighlightThink, gpGlobals->curtime + 0.1f, "BOMB_HIGHLIGHT_THINK" );
  189. }
  190. #endif
  191. }
  192. //-----------------------------------------------------------------------------
  193. // Purpose:
  194. //-----------------------------------------------------------------------------
  195. #ifdef CLIENT_DLL
  196. void CTFPipebombLauncher::BombHighlightThink( void )
  197. {
  198. ModifyPipebombsInView( TF_PIPEBOMB_HIGHLIGHT );
  199. if ( GetOwner() )
  200. {
  201. SetContextThink( &CTFPipebombLauncher::BombHighlightThink, gpGlobals->curtime + 0.1f, "BOMB_HIGHLIGHT_THINK" );
  202. }
  203. }
  204. #endif
  205. //-----------------------------------------------------------------------------
  206. // Purpose:
  207. //-----------------------------------------------------------------------------
  208. void CTFPipebombLauncher::WeaponIdle( void )
  209. {
  210. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
  211. if ( !pPlayer )
  212. return;
  213. if ( m_flChargeBeginTime > 0 && m_iClip1 > 0 && (pPlayer->m_afButtonReleased & IN_ATTACK) )
  214. {
  215. if ( m_iClip1 > 0 )
  216. {
  217. m_bWantsToShoot = true;
  218. }
  219. }
  220. if ( m_bWantsToShoot )
  221. {
  222. LaunchGrenade();
  223. }
  224. else
  225. {
  226. BaseClass::WeaponIdle();
  227. }
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Purpose:
  231. //-----------------------------------------------------------------------------
  232. void CTFPipebombLauncher::LaunchGrenade( void )
  233. {
  234. // Get the player owning the weapon.
  235. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
  236. if ( !pPlayer )
  237. return;
  238. m_bWantsToShoot = false;
  239. CalcIsAttackCritical();
  240. SendWeaponAnim( ACT_VM_PRIMARYATTACK );
  241. pPlayer->SetAnimation( PLAYER_ATTACK1 );
  242. pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
  243. CTFGrenadePipebombProjectile *pProjectile = static_cast<CTFGrenadePipebombProjectile*>( FireProjectile( pPlayer ) );
  244. if ( pProjectile )
  245. {
  246. // Save the charge time to scale the detonation timer.
  247. pProjectile->SetChargeTime( gpGlobals->curtime - m_flChargeBeginTime );
  248. #ifdef GAME_DLL
  249. if ( GetDetonateMode() == TF_DETONATE_MODE_AIR )
  250. {
  251. pProjectile->m_bWallShatter = true;
  252. }
  253. else if ( GetDetonateMode() == TF_DETONATE_MODE_DOT )
  254. {
  255. pProjectile->m_bDefensiveBomb = true;
  256. pProjectile->SetModel( TF_WEAPON_PIPEBOMBD_MODEL );
  257. }
  258. float flChargeDmg = 1.0f;
  259. CALL_ATTRIB_HOOK_FLOAT( flChargeDmg, stickybomb_charge_damage_increase );
  260. if ( flChargeDmg != 1.0f )
  261. {
  262. float flDamage = pProjectile->GetDamage();
  263. flDamage += flDamage * ( flChargeDmg - 1.0f ) * GetCurrentCharge();
  264. pProjectile->SetDamage( flDamage );
  265. }
  266. #endif // GAME_DLL
  267. }
  268. #ifdef CLIENT_DLL
  269. C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
  270. StopSound( TF_WEAPON_PIPEBOMB_LAUNCHER_CHARGE_SOUND );
  271. #else
  272. pPlayer->SpeakWeaponFire();
  273. CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
  274. #endif
  275. // Set next attack times.
  276. float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay );
  277. m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
  278. m_flLastDenySoundTime = gpGlobals->curtime;
  279. SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() );
  280. // Check the reload mode and behave appropriately.
  281. if ( m_bReloadsSingly )
  282. {
  283. m_iReloadMode.Set( TF_RELOAD_START );
  284. }
  285. m_flChargeBeginTime = 0;
  286. if ( TFGameRules()->GameModeUsesUpgrades() )
  287. {
  288. PlayUpgradedShootSound( "Weapon_Upgrade.DamageBonus" );
  289. }
  290. }
  291. float CTFPipebombLauncher::GetProjectileSpeed( void )
  292. {
  293. float flForwardSpeed = RemapValClamped( ( gpGlobals->curtime - m_flChargeBeginTime ),
  294. 0.0f,
  295. GetChargeMaxTime(),
  296. TF_PIPEBOMB_MIN_CHARGE_VEL,
  297. TF_PIPEBOMB_MAX_CHARGE_VEL );
  298. return flForwardSpeed;
  299. }
  300. void CTFPipebombLauncher::AddPipeBomb( CTFGrenadePipebombProjectile *pBomb )
  301. {
  302. PipebombHandle hHandle;
  303. hHandle = pBomb;
  304. m_Pipebombs.AddToTail( hHandle );
  305. }
  306. //-----------------------------------------------------------------------------
  307. // Purpose: Add pipebombs to our list as they're fired
  308. //-----------------------------------------------------------------------------
  309. CBaseEntity *CTFPipebombLauncher::FireProjectile( CTFPlayer *pPlayer )
  310. {
  311. CBaseEntity *pProjectile = BaseClass::FireProjectile( pPlayer );
  312. if ( pProjectile )
  313. {
  314. #ifdef GAME_DLL
  315. // If we've gone over the max pipebomb count, detonate the oldest
  316. int nMaxPipebombs = TF_WEAPON_PIPEBOMB_COUNT;
  317. CALL_ATTRIB_HOOK_INT( nMaxPipebombs, add_max_pipebombs );
  318. if ( m_Pipebombs.Count() >= nMaxPipebombs )
  319. {
  320. CTFGrenadePipebombProjectile *pTemp = m_Pipebombs[0];
  321. if ( pTemp )
  322. {
  323. pTemp->SetTimer( gpGlobals->curtime ); // explode NOW
  324. }
  325. m_Pipebombs.Remove(0);
  326. }
  327. CTFGrenadePipebombProjectile *pPipebomb = (CTFGrenadePipebombProjectile*)pProjectile;
  328. PipebombHandle hHandle;
  329. hHandle = pPipebomb;
  330. m_Pipebombs.AddToTail( hHandle );
  331. m_iPipebombCount = m_Pipebombs.Count();
  332. #endif
  333. }
  334. return pProjectile;
  335. }
  336. //-----------------------------------------------------------------------------
  337. // Purpose: Detonate this demoman's pipebombs if secondary fire is down.
  338. //-----------------------------------------------------------------------------
  339. void CTFPipebombLauncher::ItemBusyFrame( void )
  340. {
  341. #ifdef GAME_DLL
  342. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  343. if ( pOwner && pOwner->m_nButtons & IN_ATTACK2 )
  344. {
  345. // We need to do this to catch the case of player trying to detonate
  346. // pipebombs while in the middle of reloading.
  347. SecondaryAttack();
  348. }
  349. #endif
  350. BaseClass::ItemBusyFrame();
  351. }
  352. //-----------------------------------------------------------------------------
  353. // Purpose: Detonate active pipebombs
  354. //-----------------------------------------------------------------------------
  355. void CTFPipebombLauncher::SecondaryAttack( void )
  356. {
  357. if ( !CanAttack() )
  358. return;
  359. if ( m_iPipebombCount )
  360. {
  361. // Get a valid player.
  362. CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
  363. if ( !pPlayer )
  364. return;
  365. //If one or more pipebombs failed to detonate then play a sound.
  366. if ( DetonateRemotePipebombs( false ) == true )
  367. {
  368. if ( m_flLastDenySoundTime <= gpGlobals->curtime )
  369. {
  370. // Deny!
  371. m_flLastDenySoundTime = gpGlobals->curtime + 1;
  372. WeaponSound( SPECIAL2 );
  373. return;
  374. }
  375. }
  376. else
  377. {
  378. // Play a detonate sound.
  379. WeaponSound( SPECIAL3 );
  380. #ifdef GAME_DLL
  381. IGameEvent *pDetEvent = gameeventmanager->CreateEvent( "demoman_det_stickies" );
  382. if ( pDetEvent )
  383. {
  384. pDetEvent->SetInt( "player", pPlayer->entindex() );
  385. // Send the event
  386. gameeventmanager->FireEvent( pDetEvent );
  387. }
  388. #endif
  389. }
  390. }
  391. }
  392. //=============================================================================
  393. //
  394. // Server specific functions.
  395. //
  396. #ifdef GAME_DLL
  397. //-----------------------------------------------------------------------------
  398. // Purpose:
  399. //-----------------------------------------------------------------------------
  400. void CTFPipebombLauncher::UpdateOnRemove(void)
  401. {
  402. // If we just died, we want to fizzle our pipebombs.
  403. // If the player switched classes, our pipebombs have already been removed.
  404. DetonateRemotePipebombs( true );
  405. BaseClass::UpdateOnRemove();
  406. }
  407. //-----------------------------------------------------------------------------
  408. // Purpose:
  409. //-----------------------------------------------------------------------------
  410. void CTFPipebombLauncher::ApplyPostHitEffects( const CTakeDamageInfo &inputInfo, CTFPlayer *pPlayer )
  411. {
  412. if ( !GetTFPlayerOwner() )
  413. return;
  414. if ( pPlayer->m_Shared.GetWeaponKnockbackID() == -1 )
  415. {
  416. pPlayer->m_Shared.SetWeaponKnockbackID( GetTFPlayerOwner()->GetUserID() );
  417. }
  418. }
  419. #endif
  420. //-----------------------------------------------------------------------------
  421. // Purpose: If a pipebomb has been removed, remove it from our list
  422. //-----------------------------------------------------------------------------
  423. void CTFPipebombLauncher::DeathNotice( CBaseEntity *pVictim )
  424. {
  425. Assert( dynamic_cast<CTFGrenadePipebombProjectile*>(pVictim) );
  426. PipebombHandle hHandle;
  427. hHandle = (CTFGrenadePipebombProjectile*)pVictim;
  428. m_Pipebombs.FindAndRemove( hHandle );
  429. m_iPipebombCount = m_Pipebombs.Count();
  430. }
  431. //-----------------------------------------------------------------------------
  432. // Purpose: Remove *with* explosions
  433. //-----------------------------------------------------------------------------
  434. bool CTFPipebombLauncher::DetonateRemotePipebombs( bool bFizzle )
  435. {
  436. if ( GetDetonateMode() == TF_DETONATE_MODE_DOT && !bFizzle )
  437. {
  438. return ModifyPipebombsInView( TF_PIPEBOMB_DETONATE );
  439. }
  440. bool bFailedToDetonate = false;
  441. int count = m_Pipebombs.Count();
  442. for ( int i = 0; i < count; i++ )
  443. {
  444. CTFGrenadePipebombProjectile *pTemp = m_Pipebombs[i];
  445. if ( pTemp )
  446. {
  447. //This guy will die soon enough.
  448. if ( pTemp->IsEffectActive( EF_NODRAW ) )
  449. continue;
  450. #ifdef GAME_DLL
  451. if ( bFizzle )
  452. {
  453. pTemp->Fizzle();
  454. }
  455. #endif
  456. if ( bFizzle == false )
  457. {
  458. if ( ( gpGlobals->curtime - pTemp->m_flCreationTime ) < pTemp->GetLiveTime() )
  459. {
  460. if ( pTemp->GetLiveTime() <= 0.5f )
  461. {
  462. pTemp->SetDetonateOnPulse( true );
  463. }
  464. bFailedToDetonate = true;
  465. continue;
  466. }
  467. }
  468. #ifdef GAME_DLL
  469. if ( CanDestroyStickies() )
  470. {
  471. pTemp->DetonateStickies();
  472. }
  473. pTemp->Detonate();
  474. #endif
  475. }
  476. }
  477. return bFailedToDetonate;
  478. }
  479. //-----------------------------------------------------------------------------
  480. // Purpose:
  481. //-----------------------------------------------------------------------------
  482. bool CTFPipebombLauncher::ModifyPipebombsInView( int iEffect )
  483. {
  484. CTFPlayer* pPlayer = ToTFPlayer( GetOwner() );
  485. if ( !pPlayer )
  486. return true;
  487. // Dot product from the view angle to determine which bombs to detonate.
  488. bool bFailedToDetonate = true;
  489. int count = m_Pipebombs.Count();
  490. for ( int i=0; i<count; ++i )
  491. {
  492. CTFGrenadePipebombProjectile *pTemp = m_Pipebombs[i];
  493. if ( !pTemp || pTemp->IsEffectActive( EF_NODRAW ) )
  494. continue;
  495. Vector vecToTarget;
  496. vecToTarget = pTemp->WorldSpaceCenter() - pPlayer->EyePosition();
  497. vecToTarget.NormalizeInPlace();
  498. Vector vecPlayerForward;
  499. AngleVectors( pPlayer->EyeAngles(), &vecPlayerForward, NULL, NULL );
  500. vecPlayerForward.NormalizeInPlace();
  501. bool bArmed = ( ( gpGlobals->curtime - pTemp->m_flCreationTime ) > pTemp->GetLiveTime() );
  502. float flDist = pPlayer->GetAbsOrigin().DistTo( pTemp->GetAbsOrigin() );
  503. float flDot = DotProduct( vecToTarget, vecPlayerForward );
  504. // Detonate sticky bombs directly under the crosshair or under our feet (to allow sticky jumping)
  505. if ( flDot > 0.975f || flDist < pTemp->GetDamageRadius() )
  506. {
  507. switch ( iEffect )
  508. {
  509. case TF_PIPEBOMB_HIGHLIGHT:
  510. #ifdef CLIENT_DLL
  511. pTemp->SetHighlight( true );
  512. #endif
  513. break;
  514. case TF_PIPEBOMB_DETONATE:
  515. if ( bArmed )
  516. {
  517. bFailedToDetonate = false;
  518. #ifdef GAME_DLL
  519. if ( CanDestroyStickies() )
  520. {
  521. pTemp->DetonateStickies();
  522. }
  523. #endif
  524. pTemp->Detonate();
  525. }
  526. break;
  527. }
  528. }
  529. else if ( iEffect == TF_PIPEBOMB_HIGHLIGHT )
  530. {
  531. #ifdef CLIENT_DLL
  532. pTemp->SetHighlight( false );
  533. #endif
  534. }
  535. }
  536. return bFailedToDetonate;
  537. }
  538. //-----------------------------------------------------------------------------
  539. // Purpose:
  540. //-----------------------------------------------------------------------------
  541. bool CTFPipebombLauncher::Reload( void )
  542. {
  543. if ( m_flChargeBeginTime > 0 )
  544. return false;
  545. return BaseClass::Reload();
  546. }