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.

496 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "ai_basenpc.h"
  9. #include "ai_hull.h"
  10. #include "ai_senses.h"
  11. #include "ai_memory.h"
  12. #include "soundent.h"
  13. #include "smoke_trail.h"
  14. #include "weapon_rpg.h"
  15. #include "gib.h"
  16. #include "ndebugoverlay.h"
  17. #include "IEffects.h"
  18. #include "vstdlib/random.h"
  19. #include "engine/IEngineSound.h"
  20. #include "ammodef.h"
  21. #include "hl2_shareddefs.h"
  22. // memdbgon must be the last include file in a .cpp file!!!
  23. #include "tier0/memdbgon.h"
  24. #define MD_FULLAMMO 50
  25. #define MD_BC_YAW 0
  26. #define MD_BC_PITCH 1
  27. #define MD_AP_LGUN 2
  28. #define MD_AP_RGUN 1
  29. #define MD_GIB_COUNT 4
  30. #define MD_GIB_MODEL "models/gibs/missile_defense_gibs.mdl"
  31. #define MD_YAW_SPEED 24
  32. #define MD_PITCH_SPEED 12
  33. //=========================================================
  34. //=========================================================
  35. class CNPC_MissileDefense : public CAI_BaseNPC
  36. {
  37. DECLARE_CLASS( CNPC_MissileDefense, CAI_BaseNPC );
  38. DECLARE_DATADESC();
  39. public:
  40. CNPC_MissileDefense( void ) { };
  41. void Precache( void );
  42. void Spawn( void );
  43. Class_T Classify( void ) { return CLASS_NONE; }
  44. int GetSoundInterests( void ) { return SOUND_NONE; }
  45. float MaxYawSpeed( void ) { return 90.f; }
  46. void RunAI(void);
  47. void FireCannons( void );
  48. void AimGun( void );
  49. void EnemyShootPosition(CBaseEntity* pEnemy, Vector *vPosition);
  50. void Event_Killed( const CTakeDamageInfo &info );
  51. int OnTakeDamage_Alive( const CTakeDamageInfo &info );
  52. void Gib();
  53. void GetGunAim( Vector *vecAim );
  54. ~CNPC_MissileDefense();
  55. Vector m_vGunAng;
  56. int m_iAmmoLoaded;
  57. float m_flReloadedTime;
  58. };
  59. LINK_ENTITY_TO_CLASS( npc_missiledefense, CNPC_MissileDefense );
  60. //=========================================================
  61. //=========================================================
  62. BEGIN_DATADESC( CNPC_MissileDefense )
  63. DEFINE_FIELD( m_iAmmoLoaded, FIELD_INTEGER ),
  64. DEFINE_FIELD( m_flReloadedTime, FIELD_TIME ),
  65. DEFINE_FIELD( m_vGunAng, FIELD_VECTOR ),
  66. END_DATADESC()
  67. //---------------------------------------------------------
  68. //---------------------------------------------------------
  69. void CNPC_MissileDefense::Precache( void )
  70. {
  71. PrecacheModel("models/missile_defense.mdl");
  72. PrecacheModel(MD_GIB_MODEL);
  73. PrecacheScriptSound( "NPC_MissileDefense.Attack" );
  74. PrecacheScriptSound( "NPC_MissileDefense.Reload" );
  75. PrecacheScriptSound( "NPC_MissileDefense.Turn" );
  76. }
  77. //---------------------------------------------------------
  78. //---------------------------------------------------------
  79. void CNPC_MissileDefense::GetGunAim( Vector *vecAim )
  80. {
  81. Vector vecPos;
  82. QAngle vecAng;
  83. GetAttachment( MD_AP_LGUN, vecPos, vecAng );
  84. vecAng.x = GetLocalAngles().x + GetBoneController( MD_BC_PITCH );
  85. vecAng.z = 0;
  86. vecAng.y = GetLocalAngles().y + GetBoneController( MD_BC_YAW );
  87. Vector vecForward;
  88. AngleVectors( vecAng, &vecForward );
  89. *vecAim = vecForward;
  90. }
  91. #define NOISE 0.035f
  92. #define MD_ATTN_CANNON 0.4
  93. //------------------------------------------------------------------------------
  94. // Purpose :
  95. // Input :
  96. // Output :
  97. //------------------------------------------------------------------------------
  98. void CNPC_MissileDefense::FireCannons( void )
  99. {
  100. // ----------------------------------------------
  101. // Make sure I have an enemy
  102. // ----------------------------------------------
  103. if (GetEnemy() == NULL)
  104. {
  105. return;
  106. }
  107. // ----------------------------------------------
  108. // Make sure I have ammo
  109. // ----------------------------------------------
  110. if( m_iAmmoLoaded < 1 )
  111. {
  112. return;
  113. }
  114. // ----------------------------------------------
  115. // Make sure gun it pointing in right direction
  116. // ----------------------------------------------
  117. Vector vGunDir;
  118. GetGunAim( &vGunDir );
  119. Vector vTargetPos;
  120. EnemyShootPosition(GetEnemy(),&vTargetPos);
  121. Vector vTargetDir = vTargetPos - GetAbsOrigin();
  122. VectorNormalize( vTargetDir );
  123. float fDotPr = DotProduct( vGunDir, vTargetDir );
  124. if (fDotPr < 0.95)
  125. {
  126. return;
  127. }
  128. // ----------------------------------------------
  129. // Check line of sight
  130. // ----------------------------------------------
  131. trace_t tr;
  132. AI_TraceLine( GetEnemy()->EyePosition(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
  133. if (tr.fraction < 1.0)
  134. {
  135. return;
  136. }
  137. Vector vecRight;
  138. Vector vecDir;
  139. Vector vecCenter;
  140. AngleVectors( GetLocalAngles(), NULL, &vecRight, NULL );
  141. vecCenter = WorldSpaceCenter();
  142. if( GetEnemy() == NULL )
  143. {
  144. return;
  145. }
  146. bool fSound = false;
  147. if( random->RandomInt( 0, 3 ) == 0 )
  148. {
  149. fSound = true;
  150. }
  151. EmitSound( "NPC_MissileDefense.Attack" );
  152. Vector vecGun;
  153. QAngle vecAng;
  154. GetAttachment( MD_AP_LGUN, vecGun, vecAng );
  155. Vector vecTarget;
  156. EnemyShootPosition(GetEnemy(),&vecTarget);
  157. vecDir = vecTarget - vecCenter;
  158. VectorNormalize(vecDir);
  159. vecDir.x += random->RandomFloat( -NOISE, NOISE );
  160. vecDir.y += random->RandomFloat( -NOISE, NOISE );
  161. Vector vecStart = vecGun + vecDir * 110;
  162. Vector vecEnd = vecGun + vecDir * 4096;
  163. UTIL_Tracer( vecStart, vecEnd, 0, TRACER_DONT_USE_ATTACHMENT, 3000 + random->RandomFloat( 0, 2000 ), fSound );
  164. vecDir = vecTarget - vecCenter;
  165. VectorNormalize(vecDir);
  166. vecDir.x += random->RandomFloat( -NOISE, NOISE );
  167. vecDir.y += random->RandomFloat( -NOISE, NOISE );
  168. vecDir.z += random->RandomFloat( -NOISE, NOISE );
  169. GetAttachment( MD_AP_RGUN, vecGun, vecAng );
  170. vecStart = vecGun + vecDir * 110;
  171. vecEnd = vecGun + vecDir * 4096;
  172. UTIL_Tracer( vecStart, vecEnd, 0, TRACER_DONT_USE_ATTACHMENT, 3000 + random->RandomFloat( 0, 2000 ) );
  173. m_iAmmoLoaded -= 2;
  174. if( m_iAmmoLoaded < 1 )
  175. {
  176. // Incite a reload.
  177. EmitSound( "NPC_MissileDefense.Reload" );
  178. m_flReloadedTime = gpGlobals->curtime + 0.3;
  179. return;
  180. }
  181. // Do damage to the missile based on distance.
  182. // if < 1, make damage 0.
  183. float flDist = (GetEnemy()->GetLocalOrigin() - vecGun).Length();
  184. float flDamage;
  185. flDamage = 4000 - flDist;
  186. flDamage /= 1000.0;
  187. if( flDamage > 0 )
  188. {
  189. if( flDist <= 1500 )
  190. {
  191. flDamage *= 2;
  192. }
  193. CTakeDamageInfo info( this, this, flDamage, DMG_MISSILEDEFENSE );
  194. CalculateBulletDamageForce( &info, GetAmmoDef()->Index("SMG1"), vecDir, GetEnemy()->GetAbsOrigin() );
  195. GetEnemy()->TakeDamage( info );
  196. }
  197. }
  198. //---------------------------------------------------------
  199. //---------------------------------------------------------
  200. void CNPC_MissileDefense::Spawn( void )
  201. {
  202. Precache();
  203. SetModel( "models/missile_defense.mdl" );
  204. UTIL_SetSize( this, Vector( -36, -36 , 0 ), Vector( 36, 36, 64 ) );
  205. SetSolid( SOLID_BBOX );
  206. SetMoveType( MOVETYPE_NONE );
  207. m_takedamage = DAMAGE_YES;
  208. SetBloodColor( DONT_BLEED );
  209. m_iHealth = 10;
  210. m_flFieldOfView = 0.1;
  211. m_NPCState = NPC_STATE_NONE;
  212. CapabilitiesClear();
  213. CapabilitiesAdd ( bits_CAP_INNATE_RANGE_ATTACK1 );
  214. // Hate missiles
  215. AddClassRelationship( CLASS_MISSILE, D_HT, 5 );
  216. m_spawnflags |= SF_NPC_LONG_RANGE;
  217. m_flReloadedTime = gpGlobals->curtime;
  218. InitBoneControllers();
  219. NPCInit();
  220. SetBoneController( MD_BC_YAW, 10 );
  221. SetBoneController( MD_BC_PITCH, 0 );
  222. SetBodygroup( 1, 1 );
  223. SetBodygroup( 2, 1 );
  224. SetBodygroup( 3, 1 );
  225. SetBodygroup( 4, 1 );
  226. m_NPCState = NPC_STATE_IDLE;
  227. }
  228. //------------------------------------------------------------------------------
  229. // Purpose :
  230. // Input :
  231. // Output :
  232. //------------------------------------------------------------------------------
  233. int CNPC_MissileDefense::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  234. {
  235. // Only take blast damage
  236. if (info.GetDamageType() & DMG_BLAST )
  237. {
  238. return BaseClass::OnTakeDamage_Alive( info );
  239. }
  240. else
  241. {
  242. return 0;
  243. }
  244. }
  245. //------------------------------------------------------------------------------
  246. // Purpose :
  247. // Input :
  248. // Output :
  249. //------------------------------------------------------------------------------
  250. void CNPC_MissileDefense::Event_Killed( const CTakeDamageInfo &info )
  251. {
  252. StopSound( "NPC_MissileDefense.Turn" );
  253. Gib();
  254. }
  255. //------------------------------------------------------------------------------
  256. // Purpose :
  257. // Input :
  258. // Output :
  259. //------------------------------------------------------------------------------
  260. void CNPC_MissileDefense::Gib(void)
  261. {
  262. // Sparks
  263. for (int i = 0; i < 4; i++)
  264. {
  265. Vector sparkPos = GetAbsOrigin();
  266. sparkPos.x += random->RandomFloat(-12,12);
  267. sparkPos.y += random->RandomFloat(-12,12);
  268. sparkPos.z += random->RandomFloat(-12,12);
  269. g_pEffects->Sparks(sparkPos);
  270. }
  271. // Smoke
  272. UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10);
  273. // Light
  274. CBroadcastRecipientFilter filter;
  275. te->DynamicLight( filter, 0.0,
  276. &GetAbsOrigin(), 255, 180, 100, 0, 100, 0.1, 0 );
  277. // Remove top parts
  278. SetBodygroup( 1, 0 );
  279. SetBodygroup( 2, 0 );
  280. SetBodygroup( 3, 0 );
  281. SetBodygroup( 4, 0 );
  282. m_takedamage = 0;
  283. SetThink(NULL);
  284. // Throw manhackgibs
  285. CGib::SpawnSpecificGibs( this, MD_GIB_COUNT, 300, 500, MD_GIB_MODEL);
  286. }
  287. //------------------------------------------------------------------------------
  288. // Purpose :
  289. // Input :
  290. // Output :
  291. //------------------------------------------------------------------------------
  292. void CNPC_MissileDefense::RunAI( void )
  293. {
  294. // If my enemy is dead clear the memory and reset m_hEnemy
  295. if (GetEnemy() != NULL &&
  296. !GetEnemy()->IsAlive())
  297. {
  298. ClearEnemyMemory();
  299. SetEnemy( NULL );
  300. }
  301. if (GetEnemy() == NULL )
  302. {
  303. GetSenses()->Look( 4092 );
  304. SetEnemy( BestEnemy( ) );
  305. if (GetEnemy() != NULL)
  306. {
  307. m_iAmmoLoaded = MD_FULLAMMO;
  308. m_flReloadedTime = gpGlobals->curtime;
  309. }
  310. }
  311. if( m_iAmmoLoaded < 1 && gpGlobals->curtime > m_flReloadedTime )
  312. {
  313. m_iAmmoLoaded = MD_FULLAMMO;
  314. }
  315. AimGun();
  316. FireCannons();
  317. SetNextThink( gpGlobals->curtime + 0.05 );
  318. }
  319. //------------------------------------------------------------------------------
  320. // Purpose : Add a little prediction into my enemy aim position
  321. // Input :
  322. // Output :
  323. //------------------------------------------------------------------------------
  324. void CNPC_MissileDefense::EnemyShootPosition(CBaseEntity* pEnemy, Vector *vPosition)
  325. {
  326. // This should never happen, but just in case
  327. if (!pEnemy)
  328. {
  329. return;
  330. }
  331. *vPosition = pEnemy->GetAbsOrigin();
  332. // Add prediction but prevents us from flipping around as enemy approaches us
  333. float flDist = (pEnemy->GetAbsOrigin() - GetAbsOrigin()).Length();
  334. Vector vPredVel = pEnemy->GetSmoothedVelocity() * 0.5;
  335. if ( flDist > vPredVel.Length())
  336. {
  337. *vPosition += vPredVel;
  338. }
  339. }
  340. //------------------------------------------------------------------------------
  341. // Purpose :
  342. // Input :
  343. // Output :
  344. //------------------------------------------------------------------------------
  345. void CNPC_MissileDefense::AimGun( void )
  346. {
  347. if (GetEnemy() == NULL)
  348. {
  349. StopSound( "NPC_MissileDefense.Turn" );
  350. return;
  351. }
  352. Vector forward, right, up;
  353. AngleVectors( GetLocalAngles(), &forward, &right, &up );
  354. // Get gun attachment points
  355. Vector vBasePos;
  356. QAngle vBaseAng;
  357. GetAttachment( MD_AP_LGUN, vBasePos, vBaseAng );
  358. Vector vTargetPos;
  359. EnemyShootPosition(GetEnemy(),&vTargetPos);
  360. Vector vTargetDir = vTargetPos - vBasePos;
  361. VectorNormalize( vTargetDir );
  362. Vector vecOut;
  363. vecOut.x = DotProduct( forward, vTargetDir );
  364. vecOut.y = -DotProduct( right, vTargetDir );
  365. vecOut.z = DotProduct( up, vTargetDir );
  366. QAngle angles;
  367. VectorAngles(vecOut, angles);
  368. if (angles.y > 180)
  369. angles.y = angles.y - 360;
  370. if (angles.y < -180)
  371. angles.y = angles.y + 360;
  372. if (angles.x > 180)
  373. angles.x = angles.x - 360;
  374. if (angles.x < -180)
  375. angles.x = angles.x + 360;
  376. float flOldX = m_vGunAng.x;
  377. float flOldY = m_vGunAng.y;
  378. if (angles.x > m_vGunAng.x)
  379. m_vGunAng.x = MIN( angles.x, m_vGunAng.x + MD_PITCH_SPEED );
  380. if (angles.x < m_vGunAng.x)
  381. m_vGunAng.x = MAX( angles.x, m_vGunAng.x - MD_PITCH_SPEED );
  382. if (angles.y > m_vGunAng.y)
  383. m_vGunAng.y = MIN( angles.y, m_vGunAng.y + MD_YAW_SPEED );
  384. if (angles.y < m_vGunAng.y)
  385. m_vGunAng.y = MAX( angles.y, m_vGunAng.y - MD_YAW_SPEED );
  386. m_vGunAng.y = SetBoneController( MD_BC_YAW, m_vGunAng.y );
  387. m_vGunAng.x = SetBoneController( MD_BC_PITCH, m_vGunAng.x );
  388. if (flOldX != m_vGunAng.x || flOldY != m_vGunAng.y)
  389. {
  390. EmitSound( "NPC_MissileDefense.Turn" );
  391. }
  392. else
  393. {
  394. StopSound( "NPC_MissileDefense.Turn" );
  395. }
  396. }
  397. //------------------------------------------------------------------------------
  398. // Purpose :
  399. // Input :
  400. // Output :
  401. //------------------------------------------------------------------------------
  402. CNPC_MissileDefense::~CNPC_MissileDefense(void)
  403. {
  404. StopSound( "NPC_MissileDefense.Turn" );
  405. }