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.

914 lines
26 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Slowly damages the object it's attached to
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_player.h"
  9. #include "tf_team.h"
  10. #include "tf_gamerules.h"
  11. #include "tf_obj.h"
  12. #include "tf_obj_sentrygun.h"
  13. #include "tf_obj_sapper.h"
  14. #include "ndebugoverlay.h"
  15. #include "tf_gamestats.h"
  16. #include "tf_obj_teleporter.h"
  17. #include "tf_weapon_builder.h"
  18. #include "tf_fx.h"
  19. #include "bot/tf_bot.h"
  20. ConVar tf_mvm_notice_sapped_squadmates_delay( "tf_mvm_notice_sapped_squadmates_delay", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "How long it takes for a squad leader to notice his squadmate was sapped" );
  21. // ------------------------------------------------------------------------ //
  22. #define SAPPER_MINS Vector(0, 0, 0)
  23. #define SAPPER_MAXS Vector(1, 1, 1)
  24. const char * g_sapperModel = "models/buildables/sapper_placed.mdl";
  25. const char * g_sapperPlacementModel = "models/buildables/sapper_placement.mdl";
  26. BEGIN_DATADESC( CObjectSapper )
  27. DEFINE_THINKFUNC( SapperThink ),
  28. END_DATADESC()
  29. IMPLEMENT_SERVERCLASS_ST(CObjectSapper, DT_ObjectSapper)
  30. END_SEND_TABLE();
  31. LINK_ENTITY_TO_CLASS(obj_attachment_sapper, CObjectSapper);
  32. PRECACHE_REGISTER(obj_attachment_sapper);
  33. ConVar obj_sapper_amount( "obj_sapper_amount", "25", FCVAR_NONE, "Amount of health inflicted by a Sapper object per second" );
  34. #define SAPPER_THINK_CONTEXT "SapperThink"
  35. #define SAPPER_REMOVE_DISABLE_TIME 0.5f
  36. //-----------------------------------------------------------------------------
  37. // Purpose:
  38. //-----------------------------------------------------------------------------
  39. CObjectSapper::CObjectSapper()
  40. {
  41. m_szPlacementModel[ 0 ] = '\0';
  42. m_szSapperModel[ 0 ] = '\0';
  43. szSapperSound[ 0 ] = '\0';
  44. m_iHealth = GetBaseHealth();
  45. SetMaxHealth( m_iHealth );
  46. m_flSelfDestructTime = 0;
  47. UseClientSideAnimation();
  48. }
  49. //-----------------------------------------------------------------------------
  50. // Purpose:
  51. //-----------------------------------------------------------------------------
  52. void CObjectSapper::UpdateOnRemove()
  53. {
  54. StopSound( "Weapon_Sapper.Timer" );
  55. StopSound( "Weapon_sd_sapper.Timer" );
  56. StopSound( "Weapon_p2rec.Timer" );
  57. #ifdef STAGING_ONLY
  58. StopSound( "WeaponDynamiteSapper.TickTock" );
  59. StopSound( "WeaponDynamiteSapper.BellRing" );
  60. #endif
  61. if( GetBuilder() )
  62. {
  63. GetBuilder()->OnSapperFinished( m_flSapperStartTime );
  64. }
  65. BaseClass::UpdateOnRemove();
  66. }
  67. //-----------------------------------------------------------------------------
  68. // Purpose:
  69. //-----------------------------------------------------------------------------
  70. void CObjectSapper::Spawn()
  71. {
  72. SetModel( GetSapperModelName( SAPPER_MODEL_PLACEMENT ) );
  73. m_takedamage = DAMAGE_YES;
  74. m_iHealth = GetBaseHealth();
  75. SetType( OBJ_ATTACHMENT_SAPPER );
  76. BaseClass::Spawn();
  77. Vector mins = SAPPER_MINS;
  78. Vector maxs = SAPPER_MAXS;
  79. CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &mins, &maxs );
  80. int nFlags = m_fObjectFlags | OF_ALLOW_REPEAT_PLACEMENT;
  81. // Don't allow repeat placement as a human spy in MvM
  82. if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() &&
  83. GetBuilder() && !GetBuilder()->IsBot() )
  84. {
  85. nFlags &= ~( OF_ALLOW_REPEAT_PLACEMENT );
  86. }
  87. m_fObjectFlags.Set( nFlags );
  88. SetSolid( SOLID_NONE );
  89. #ifdef STAGING_ONLY
  90. m_bIsRinging = false;
  91. #endif
  92. }
  93. //-----------------------------------------------------------------------------
  94. // Purpose:
  95. //-----------------------------------------------------------------------------
  96. void CObjectSapper::Precache()
  97. {
  98. Precache( "c_sapper.mdl" ); // Precache the placed and placement models for the sappers
  99. #ifdef STAGING_ONLY
  100. Precache( "c_sd_sapper.mdl" );
  101. #else
  102. Precache( "w_sd_sapper.mdl" );
  103. #endif
  104. Precache( "c_p2rec.mdl" );
  105. Precache( "c_sapper_xmas.mdl" );
  106. Precache( "c_breadmonster_sapper.mdl" );
  107. PrecacheScriptSound( "Weapon_Sapper.Plant" );
  108. PrecacheScriptSound( "Weapon_Sapper.Timer" );
  109. PrecacheScriptSound( "Weapon_sd_sapper.Timer" );
  110. PrecacheScriptSound( "Weapon_p2rec.Timer" );
  111. #ifdef STAGING_ONLY
  112. PrecacheScriptSound( "WeaponDynamiteSapper.TickTock" );
  113. PrecacheScriptSound( "WeaponDynamiteSapper.BellRing" );
  114. #endif
  115. // Precache the Wheatley Sapper sounds
  116. PrecacheScriptSound( "PSap.null" );
  117. PrecacheScriptSound( "Psap.Attached" );
  118. PrecacheScriptSound( "Psap.AttachedPW" );
  119. PrecacheScriptSound( "PSap.Damage" );
  120. PrecacheScriptSound( "PSap.Death" );
  121. PrecacheScriptSound( "PSap.DeathLong" );
  122. PrecacheScriptSound( "PSap.Deploy" );
  123. PrecacheScriptSound( "PSap.DeployAgain" );
  124. PrecacheScriptSound( "PSap.DeployIntro" );
  125. PrecacheScriptSound( "PSap.Hacked" );
  126. PrecacheScriptSound( "Psap.HackedFollowup" );
  127. PrecacheScriptSound( "Psap.HackedLoud" );
  128. PrecacheScriptSound( "PSap.Hacking" );
  129. PrecacheScriptSound( "PSap.HackingPW" );
  130. PrecacheScriptSound( "PSap.HackingShort" );
  131. PrecacheScriptSound( "PSap.Holster" );
  132. PrecacheScriptSound( "PSap.HolsterFast" );
  133. PrecacheScriptSound( "Psap.Idle" );
  134. PrecacheScriptSound( "Psap.IdleHack02" );
  135. PrecacheScriptSound( "Psap.IdleHarmless02" );
  136. PrecacheScriptSound( "PSap.IdleIntro01" );
  137. PrecacheScriptSound( "PSap.IdleIntro02" );
  138. PrecacheScriptSound( "PSap.IdleIntro03" );
  139. PrecacheScriptSound( "PSap.IdleIntro04" );
  140. PrecacheScriptSound( "PSap.IdleKnife02" );
  141. PrecacheScriptSound( "PSap.IdleKnife03" );
  142. PrecacheScriptSound( "PSap.Sneak" );
  143. BaseClass::Precache();
  144. }
  145. void CObjectSapper::Precache( const char *pchBaseModel )
  146. {
  147. m_szPlacementModel[ 0 ] = '\0';
  148. m_szSapperModel[ 0 ] = '\0';
  149. int iModelIndex;
  150. iModelIndex = PrecacheModel( GetSapperModelName( SAPPER_MODEL_PLACED, pchBaseModel ) );
  151. PrecacheGibsForModel( iModelIndex );
  152. PrecacheModel( GetSapperModelName( SAPPER_MODEL_PLACEMENT, pchBaseModel ) );
  153. m_szPlacementModel[ 0 ] = '\0';
  154. m_szSapperModel[ 0 ] = '\0';
  155. }
  156. //-----------------------------------------------------------------------------
  157. // Purpose:
  158. //-----------------------------------------------------------------------------
  159. void CObjectSapper::FinishedBuilding( void )
  160. {
  161. BaseClass::FinishedBuilding();
  162. CBaseEntity *pEntity = m_hBuiltOnEntity.Get();
  163. if ( pEntity )
  164. {
  165. if ( GetParentObject() )
  166. {
  167. GetParentObject()->OnAddSapper();
  168. CBaseObject *pObject = dynamic_cast<CBaseObject *>( m_hBuiltOnEntity.Get() );
  169. if ( pObject )
  170. {
  171. if ( GetBuilder() && pObject->GetBuilder() )
  172. {
  173. IGameEvent * event = gameeventmanager->CreateEvent( "player_sapped_object" );
  174. if ( event )
  175. {
  176. event->SetInt( "userid", GetBuilder()->GetUserID() );
  177. event->SetInt( "ownerid", pObject->GetBuilder()->GetUserID() );
  178. event->SetInt( "object", pObject->ObjectType() );
  179. event->SetInt( "sapperid", entindex() );
  180. gameeventmanager->FireEvent( event );
  181. }
  182. }
  183. }
  184. }
  185. }
  186. if( GetBuilder() )
  187. {
  188. m_flSapperStartTime = gpGlobals->curtime;
  189. GetBuilder()->OnSapperStarted( m_flSapperStartTime );
  190. }
  191. EmitSound( "Weapon_Sapper.Plant" );
  192. EmitSound( GetSapperSoundName() ); // start looping "Weapon_Sapper.Timer", killed when we die
  193. m_flSapperDamageAccumulator = 0;
  194. m_flLastThinkTime = gpGlobals->curtime;
  195. m_flLastHealthLeachTime = gpGlobals->curtime;
  196. SetContextThink( &CObjectSapper::SapperThink, gpGlobals->curtime + 0.1, SAPPER_THINK_CONTEXT );
  197. }
  198. //-----------------------------------------------------------------------------
  199. // Purpose: Change our model based on the object we are attaching to
  200. //-----------------------------------------------------------------------------
  201. void CObjectSapper::SetupAttachedVersion( void )
  202. {
  203. if ( !IsParentValid() )
  204. return;
  205. if ( IsPlacing() )
  206. {
  207. CBaseEntity *pEntity = m_hBuiltOnEntity.Get();
  208. if ( pEntity )
  209. {
  210. SetModel( GetSapperModelName( SAPPER_MODEL_PLACEMENT ) );
  211. }
  212. }
  213. BaseClass::SetupAttachedVersion();
  214. }
  215. //-----------------------------------------------------------------------------
  216. // Purpose:
  217. //-----------------------------------------------------------------------------
  218. void CObjectSapper::OnGoActive( void )
  219. {
  220. if ( !IsParentValid() )
  221. return;
  222. // set new model
  223. CBaseEntity *pEntity = m_hBuiltOnEntity.Get();
  224. m_flSelfDestructTime = 0;
  225. CTFPlayer *pBuilder = ToTFPlayer( GetBuilder() );
  226. if ( pEntity )
  227. {
  228. SetModel( GetSapperModelName( SAPPER_MODEL_PLACED ) );
  229. if ( pEntity->IsPlayer() ) // Sapped bot in MvM mode, or player in bountymode
  230. {
  231. float flTime = 4.f;
  232. if ( pBuilder )
  233. {
  234. int iRoboSapper = 0;
  235. CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, iRoboSapper, robo_sapper );
  236. CTFPlayer *pTFParent = ToTFPlayer( GetParentEntity() );
  237. if ( pTFParent && pTFParent->IsAlive() )
  238. {
  239. int nRadius = 200;
  240. switch( iRoboSapper )
  241. {
  242. case 2:
  243. flTime = 5.5f;
  244. nRadius = 225;
  245. break;
  246. case 3:
  247. flTime = 7.f;
  248. nRadius = 250;
  249. break;
  250. default:
  251. break;
  252. }
  253. // Unlimited, single-target version of the RoboSapper
  254. if ( GetObjectMode() == MODE_SAPPER_ANTI_ROBOT )
  255. {
  256. nRadius = 0;
  257. }
  258. ApplyRoboSapper( pTFParent, flTime, nRadius );
  259. }
  260. }
  261. m_flSelfDestructTime = gpGlobals->curtime + flTime;
  262. }
  263. #ifdef STAGING_ONLY
  264. //if ( pBuilder )
  265. //{
  266. // float flExplodeOnTimer = 0;
  267. // CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flExplodeOnTimer, sapper_explodes_on_timer )
  268. // {
  269. // if ( flExplodeOnTimer != 0 )
  270. // {
  271. // // timer is based on health of the object
  272. // // Sappers normally do 25dps
  273. // //float flTimer = pEntity->GetMaxHealth() * 0.04f;
  274. // //m_flSelfDestructTime = gpGlobals->curtime + flExplodeOnTimer;
  275. // }
  276. // }
  277. //}
  278. #endif
  279. }
  280. UTIL_SetSize( this, SAPPER_MINS, SAPPER_MAXS );
  281. SetSolid( SOLID_NONE );
  282. BaseClass::OnGoActive();
  283. }
  284. //-----------------------------------------------------------------------------
  285. // Purpose:
  286. //-----------------------------------------------------------------------------
  287. bool CObjectSapper::IsParentValid( void )
  288. {
  289. bool bValid = false;
  290. CBaseEntity *pEntity = m_hBuiltOnEntity.Get();
  291. if ( pEntity )
  292. {
  293. if ( pEntity->IsPlayer() ) // sapped bot in MvM mode
  294. {
  295. bValid = true;
  296. }
  297. else
  298. {
  299. CBaseObject *pObject = dynamic_cast<CBaseObject *>( pEntity );
  300. if ( pObject )
  301. {
  302. bValid = true;
  303. }
  304. }
  305. }
  306. if ( !bValid )
  307. {
  308. DestroyObject();
  309. }
  310. return bValid;
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Purpose:
  314. //-----------------------------------------------------------------------------
  315. void CObjectSapper::DetachObjectFromObject( void )
  316. {
  317. CBaseObject *pParent = GetParentObject();
  318. if ( pParent )
  319. {
  320. pParent->OnRemoveSapper();
  321. #ifdef STAGING_ONLY
  322. CTFPlayer *pBuilder = GetBuilder();
  323. if ( pBuilder && pParent->GetHealth() < 0 )
  324. {
  325. // Attr on Det
  326. float flExplodeOnTimer = 0;
  327. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flExplodeOnTimer, sapper_explodes_on_det );
  328. if ( flExplodeOnTimer )
  329. {
  330. float flDamage = pParent->GetMaxHealth() * 1.5;
  331. Vector vecOrigin = GetAbsOrigin();
  332. // Use the building as the det position
  333. CTakeDamageInfo detInfo;
  334. detInfo.SetDamage( flDamage );
  335. detInfo.SetAttacker( this );
  336. detInfo.SetInflictor( this );
  337. detInfo.SetDamageType( DMG_BLAST );
  338. // Generate Large Radius Damage
  339. float flRadius = 200.0f;
  340. CTFRadiusDamageInfo radiusinfo( &detInfo, vecOrigin, flRadius, NULL, flRadius );
  341. TFGameRules()->RadiusDamage( radiusinfo );
  342. DispatchParticleEffect( "explosionTrail_seeds_mvm", vecOrigin, GetAbsAngles() );
  343. }
  344. }
  345. #endif
  346. }
  347. BaseClass::DetachObjectFromObject();
  348. }
  349. //-----------------------------------------------------------------------------
  350. const char* CObjectSapper::GetSapperModelName( SapperModel_t nModel, const char *pchModelName /*= NULL */)
  351. {
  352. // Check to see if we have model names generated, if not we must generate
  353. if ( m_szPlacementModel[0] == '\0' || m_szSapperModel[0] == '\0' )
  354. {
  355. if ( !pchModelName )
  356. {
  357. if ( GetBuilder() )
  358. {
  359. CTFWeaponBuilder *pWeapon = dynamic_cast< CTFWeaponBuilder* >( GetBuilder()->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING ) );
  360. if ( pWeapon )
  361. {
  362. pchModelName = pWeapon->GetWorldModel();
  363. }
  364. }
  365. }
  366. if ( !pchModelName )
  367. {
  368. if ( nModel >= SAPPER_MODEL_PLACEMENT )
  369. return g_sapperPlacementModel;
  370. return g_sapperModel;
  371. }
  372. // Generate Models
  373. // Name base
  374. char szModelName[ _MAX_PATH ];
  375. V_FileBase( pchModelName, szModelName, sizeof( szModelName ) );
  376. pchModelName = szModelName + 2;
  377. #ifdef STAGING_ONLY
  378. if (!V_strcmp(pchModelName, "sd_sapper"))
  379. {
  380. V_snprintf(m_szPlacementModel, sizeof(m_szPlacementModel), "models/workshop_partner/buildables/%s%s", pchModelName, "_placement.mdl");
  381. V_snprintf(m_szSapperModel, sizeof(m_szSapperModel), "models/workshop_partner/buildables/%s%s", pchModelName, "_placed.mdl");
  382. }
  383. else
  384. #endif
  385. {
  386. V_snprintf(m_szPlacementModel, sizeof(m_szPlacementModel), "models/buildables/%s%s", pchModelName, "_placement.mdl");
  387. V_snprintf(m_szSapperModel, sizeof(m_szSapperModel), "models/buildables/%s%s", pchModelName, "_placed.mdl");
  388. }
  389. }
  390. if ( nModel >= SAPPER_MODEL_PLACEMENT )
  391. {
  392. return m_szPlacementModel;
  393. }
  394. return m_szSapperModel;
  395. }
  396. //-----------------------------------------------------------------------------
  397. const char* CObjectSapper::GetSapperSoundName( void )
  398. {
  399. if ( szSapperSound[ 0 ] == '\0' )
  400. {
  401. const char *pchModelName = NULL;
  402. if ( GetBuilder() )
  403. {
  404. CTFWeaponBuilder *pWeapon = dynamic_cast< CTFWeaponBuilder* >( GetBuilder()->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING ) );
  405. if ( pWeapon )
  406. {
  407. pchModelName = pWeapon->GetWorldModel();
  408. }
  409. }
  410. #ifdef STAGING_ONLY
  411. // // Attr on Det
  412. float flExplodeOnTimer = 0;
  413. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flExplodeOnTimer, sapper_explodes_on_det );
  414. if ( flExplodeOnTimer )
  415. {
  416. EmitSound( "Weapon_Sapper.Timer" );
  417. return "WeaponDynamiteSapper.TickTock";
  418. }
  419. #endif
  420. if ( !pchModelName )
  421. {
  422. return "Weapon_Sapper.Timer";
  423. }
  424. char szModelName[ _MAX_PATH ];
  425. V_FileBase( pchModelName, szModelName, sizeof( szModelName ) );
  426. pchModelName = szModelName + 2;
  427. V_snprintf( szSapperSound, sizeof( szSapperSound ), "Weapon_%s.Timer", pchModelName );
  428. }
  429. return szSapperSound;
  430. }
  431. //-----------------------------------------------------------------------------
  432. // Purpose: Slowly destroy the object I'm attached to
  433. //-----------------------------------------------------------------------------
  434. void CObjectSapper::SapperThink( void )
  435. {
  436. if ( !GetTeam() )
  437. return;
  438. bool bThink = true;
  439. CBaseEntity *pEntity = m_hBuiltOnEntity.Get();
  440. if ( pEntity )
  441. {
  442. if ( pEntity->IsPlayer() ) // sapping bots in MvM mode
  443. {
  444. bool bDestroy = false;
  445. CTFPlayer *pTFOwner = ToTFPlayer( m_hBuiltOnEntity.Get() );
  446. CTFPlayer *pBuilder = GetBuilder();
  447. if ( !pBuilder || !pTFOwner || ( pTFOwner && !pTFOwner->IsAlive() ) )
  448. {
  449. bDestroy = true;
  450. }
  451. #ifdef STAGING_ONLY
  452. /*if ( gpGlobals->curtime >= m_flSelfDestructTime )
  453. {
  454. bDestroy = true;
  455. Explode();
  456. }*/
  457. #else
  458. if ( gpGlobals->curtime >= m_flSelfDestructTime )
  459. {
  460. bDestroy = true;
  461. Explode();
  462. }
  463. #endif
  464. if ( bDestroy )
  465. {
  466. DestroyObject();
  467. bThink = false;
  468. return;
  469. }
  470. }
  471. else
  472. {
  473. CBaseObject *pObject = GetParentObject();
  474. if ( !pObject )
  475. {
  476. DestroyObject();
  477. bThink = false;
  478. return;
  479. }
  480. // Don't bring objects back from the dead
  481. if ( !pObject->IsAlive() || pObject->IsDying() )
  482. return;
  483. CTFPlayer *pBuilder = GetBuilder();
  484. // how much damage to give this think?
  485. float flTimeSinceLastThink = gpGlobals->curtime - m_flLastThinkTime;
  486. float flDamageToGive = ( flTimeSinceLastThink ) * obj_sapper_amount.GetFloat();
  487. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flDamageToGive, mult_sapper_damage );
  488. // add to accumulator
  489. m_flSapperDamageAccumulator += flDamageToGive;
  490. int iDamage = (int)m_flSapperDamageAccumulator;
  491. m_flSapperDamageAccumulator -= iDamage;
  492. // sapper building damage added to health of Vampire Powerup carrier
  493. if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
  494. {
  495. CTFPlayer *pTFOwner = ToTFPlayer( GetOwner() );
  496. if ( pTFOwner && pTFOwner->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE )
  497. {
  498. pTFOwner->TakeHealth( flDamageToGive, DMG_GENERIC );
  499. }
  500. }
  501. int iCustomDamage = 0;
  502. if ( GetReversesBuildingConstructionSpeed() != 0.0f )
  503. {
  504. iCustomDamage = TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH;
  505. }
  506. CTakeDamageInfo info;
  507. info.SetDamage( iDamage );
  508. info.SetAttacker( this );
  509. info.SetInflictor( this );
  510. info.SetDamageType( DMG_CRUSH );
  511. info.SetDamageCustom( iCustomDamage );
  512. pObject->TakeDamage( info );
  513. if ( gpGlobals->curtime - m_flLastHealthLeachTime > 1.0f )
  514. {
  515. m_flLastHealthLeachTime = gpGlobals->curtime;
  516. float flHealOwnerPerSecond = 0.0f;
  517. CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, flHealOwnerPerSecond, sapper_damage_leaches_health );
  518. if ( flHealOwnerPerSecond )
  519. {
  520. CTFPlayer *pSpyOwner = GetOwner();
  521. if ( pSpyOwner && pSpyOwner->IsAlive() )
  522. {
  523. pSpyOwner->TakeHealth( flHealOwnerPerSecond, DMG_IGNORE_MAXHEALTH );
  524. pSpyOwner->m_Shared.HealthKitPickupEffects( flHealOwnerPerSecond );
  525. }
  526. }
  527. }
  528. #ifdef STAGING_ONLY
  529. if ( !m_bIsRinging && pObject->GetHealth() < 60.0f )
  530. {
  531. int iDetonate = 0;
  532. CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, iDetonate, sapper_explodes_on_det );
  533. if ( iDetonate )
  534. {
  535. EmitSound( "WeaponDynamiteSapper.BellRing" );
  536. m_bIsRinging = true;
  537. }
  538. }
  539. //float flExplodeOnTimer = 0;
  540. //CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flExplodeOnTimer, sapper_explodes_on_det );
  541. ////if ( flExplodeOnTimer != 0 && m_flSelfDestructTime < gpGlobals->curtime )
  542. //if ( flExplodeOnTimer )
  543. //{
  544. // float flDamage = pObject->GetMaxHealth() * 1.5;
  545. // Explode();
  546. // DestroyObject();
  547. // Vector vecOrigin = GetAbsOrigin();
  548. // // Use the building as the det position
  549. // CTakeDamageInfo detInfo;
  550. // detInfo.SetDamage( flDamage );
  551. // detInfo.SetAttacker( this );
  552. // detInfo.SetInflictor( this );
  553. // detInfo.SetDamageType( DMG_BLAST );
  554. // // Destroy the building by doubly applying damage
  555. // pObject->TakeDamage( detInfo );
  556. // // Generate Large Radius Damage
  557. // float flRadius = 200.0f; // same as pipebomb launcher
  558. // CTFRadiusDamageInfo radiusinfo( &detInfo, vecOrigin, flRadius, NULL, flRadius );
  559. // TFGameRules()->RadiusDamage( radiusinfo );
  560. // DispatchParticleEffect( "explosionTrail_seeds_mvm", vecOrigin, GetAbsAngles() );
  561. //}
  562. #endif
  563. }
  564. }
  565. if ( bThink )
  566. {
  567. SetNextThink( gpGlobals->curtime + 0.1f, SAPPER_THINK_CONTEXT );
  568. }
  569. m_flLastThinkTime = gpGlobals->curtime;
  570. }
  571. //-----------------------------------------------------------------------------
  572. // Purpose:
  573. //-----------------------------------------------------------------------------
  574. int CObjectSapper::OnTakeDamage( const CTakeDamageInfo &info )
  575. {
  576. if ( info.GetDamageCustom() != TF_DMG_WRENCH_FIX )
  577. {
  578. // See if the weapon has a "I damage sappers" attribute on it
  579. int iDmgSappers = 0;
  580. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(info.GetWeapon());
  581. if ( pWeapon )
  582. {
  583. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iDmgSappers, set_dmg_apply_to_sapper );
  584. }
  585. if ( iDmgSappers == 0 )
  586. return 0;
  587. }
  588. // Is the damage from something other than another sapper? (which might be on our matching teleporter)
  589. if ( !( info.GetDamageType() & DMG_FROM_OTHER_SAPPER ) )
  590. {
  591. if ( GetParentObject() )
  592. {
  593. CTakeDamageInfo localDamageInfo = info;
  594. localDamageInfo.AddDamageType( DMG_FROM_OTHER_SAPPER );
  595. // If there's a matching teleporter with a sapper then have that sapper take damage, too.
  596. CObjectTeleporter *pParentTeleporter = dynamic_cast< CObjectTeleporter * >( GetParentObject() );
  597. if ( pParentTeleporter )
  598. {
  599. // GetMatchingTeleporter is set when a matching teleporter is ACTIVE
  600. // if we don't find the cache matching teleporter, try to find with a more expensive FindMatch func
  601. CObjectTeleporter *pMatchingTeleporter = pParentTeleporter->GetMatchingTeleporter() ? pParentTeleporter->GetMatchingTeleporter() : pParentTeleporter->FindMatch();
  602. if ( pMatchingTeleporter && pMatchingTeleporter->HasSapper() )
  603. {
  604. // Do damage to any attached buildings
  605. IHasBuildPoints *pBPInterface = dynamic_cast< IHasBuildPoints * >( pMatchingTeleporter );
  606. int iNumObjects = pBPInterface->GetNumObjectsOnMe();
  607. for ( int iPoint = 0 ; iPoint < iNumObjects ; iPoint++ )
  608. {
  609. CBaseObject *pObject = pMatchingTeleporter->GetBuildPointObject( iPoint );
  610. if ( pObject && pObject->IsHostileUpgrade() )
  611. {
  612. pObject->TakeDamage( localDamageInfo );
  613. }
  614. }
  615. }
  616. }
  617. }
  618. }
  619. return BaseClass::OnTakeDamage( info );
  620. }
  621. //-----------------------------------------------------------------------------
  622. // Purpose:
  623. //-----------------------------------------------------------------------------
  624. void CObjectSapper::Killed( const CTakeDamageInfo &info )
  625. {
  626. CBaseEntity *pInflictor = info.GetInflictor();
  627. CBaseEntity *pKiller = info.GetAttacker();
  628. CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, this ) );
  629. // We don't own the building we removed the sapper from
  630. if ( pScorer && GetParentObject() && GetParentObject()->GetOwner() != pScorer )
  631. {
  632. // Give a bonus point for it
  633. if ( TFGameRules()->GameModeUsesUpgrades() )
  634. {
  635. CTF_GameStats.Event_PlayerAwardBonusPoints( pScorer, this, 10 );
  636. }
  637. if ( pScorer->IsPlayerClass( TF_CLASS_ENGINEER ) )
  638. {
  639. pScorer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DESTROY_SAPPERS, 1 );
  640. }
  641. }
  642. // Optional: if a weapon was used to destroy this sapper, we give the weapon an opportunity
  643. // to adjust its stats.
  644. {
  645. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>( info.GetWeapon() );
  646. if ( pWeapon )
  647. {
  648. EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( info.GetWeapon() ), // econ entity
  649. pWeapon->GetTFPlayerOwner(), // scorer
  650. GetOwner(), // victim
  651. kKillEaterEvent_SapperDestroyed );
  652. }
  653. }
  654. CBaseObject *pParent = GetParentObject();
  655. if ( pParent )
  656. {
  657. pParent->SetPlasmaDisabled( SAPPER_REMOVE_DISABLE_TIME );
  658. }
  659. BaseClass::Killed( info );
  660. }
  661. int CObjectSapper::GetBaseHealth( void )
  662. {
  663. float flSapperHealth = SAPPER_MAX_HEALTH;
  664. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flSapperHealth, mult_sapper_health );
  665. return flSapperHealth;
  666. }
  667. //-----------------------------------------------------------------------------
  668. // Purpose: Search for players to apply RoboSapper effects to
  669. //-----------------------------------------------------------------------------
  670. void CObjectSapper::ApplyRoboSapper( CTFPlayer *pTarget, float flDuration, int nRadius /*= 200*/ )
  671. {
  672. // Apply effects to primary target
  673. if ( IsValidRoboSapperTarget( pTarget ) )
  674. {
  675. ApplyRoboSapperEffects( pTarget, flDuration );
  676. }
  677. // If we have a radius, search it for valid targets
  678. if ( nRadius )
  679. {
  680. int iCount = 0;
  681. for ( int i = 1; i < gpGlobals->maxClients; i++ )
  682. {
  683. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  684. if ( !pPlayer )
  685. continue;
  686. // Ignore the primary target (handled above)
  687. if ( pPlayer == pTarget )
  688. continue;
  689. // Same team, alive, etc
  690. if ( !IsValidRoboSapperTarget( pPlayer ) )
  691. continue;
  692. // Range check from pTarget
  693. Vector vecDist = pPlayer->GetAbsOrigin() - GetAbsOrigin();
  694. if ( vecDist.LengthSqr() > nRadius * nRadius )
  695. continue;
  696. // Ignore bots we can't see
  697. trace_t trace;
  698. UTIL_TraceLine( pPlayer->WorldSpaceCenter(), WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace );
  699. if ( trace.fraction < 1.0f )
  700. continue;
  701. // Apply
  702. if ( ApplyRoboSapperEffects( pPlayer, flDuration ) )
  703. iCount++;
  704. }
  705. // ACHIEVEMENT_TF_MVM_SPY_SAP_ROBOTS
  706. if ( iCount >= 10 )
  707. {
  708. CTFPlayer *pBuilder = ToTFPlayer( GetBuilder() );
  709. if ( pBuilder && TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  710. {
  711. pBuilder->AwardAchievement( ACHIEVEMENT_TF_MVM_SPY_SAP_ROBOTS );
  712. }
  713. }
  714. Vector vecOrigin = GetAbsOrigin();
  715. CPVSFilter filter( vecOrigin );
  716. TE_TFParticleEffect( filter, 0.f, "Explosion_ShockWave_01", vecOrigin, vec3_angle );
  717. }
  718. }
  719. //-----------------------------------------------------------------------------
  720. // Purpose: Applies effects of the RoboSapper to pTarget for flDuration
  721. //-----------------------------------------------------------------------------
  722. bool CObjectSapper::ApplyRoboSapperEffects( CTFPlayer *pTarget, float flDuration )
  723. {
  724. if ( !pTarget )
  725. return false;
  726. int iStunFlags = TF_STUN_MOVEMENT | TF_STUN_CONTROLS | TF_STUN_NO_EFFECTS;
  727. // Giants and players can't be fully incapacitated - only slowed
  728. CTFBot *pTFBot = static_cast<CTFBot *>( pTarget );
  729. if ( ( pTFBot && pTFBot->IsMiniBoss() ) || !pTFBot )
  730. {
  731. iStunFlags = TF_STUN_MOVEMENT;
  732. }
  733. pTarget->m_Shared.StunPlayer( flDuration, 0.85f, iStunFlags, GetBuilder() );
  734. pTarget->m_Shared.AddCond( TF_COND_SAPPED, flDuration, GetBuilder() );
  735. return true;
  736. }
  737. //-----------------------------------------------------------------------------
  738. // Purpose: Valid player to apply RoboSapper effects to?
  739. //-----------------------------------------------------------------------------
  740. bool CObjectSapper::IsValidRoboSapperTarget( CTFPlayer *pTarget )
  741. {
  742. if ( !pTarget )
  743. return false;
  744. if ( !pTarget->IsAlive() )
  745. return false;
  746. if ( GetBuilder() && GetBuilder()->GetTeamNumber() == pTarget->GetTeam()->GetTeamNumber() )
  747. return false;
  748. if ( pTarget->m_Shared.IsInvulnerable() )
  749. return false;
  750. if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) )
  751. return false;
  752. if ( pTarget->m_Shared.InCond( TF_COND_SAPPED ) )
  753. return false;
  754. if ( pTarget->m_Shared.InCond( TF_COND_REPROGRAMMED ) )
  755. return false;
  756. return true;
  757. }
  758. float CObjectSapper::GetReversesBuildingConstructionSpeed( void )
  759. {
  760. float flReverseSpeed = 0.0f;
  761. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flReverseSpeed, sapper_degenerates_buildings );
  762. return flReverseSpeed;
  763. }