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.

1190 lines
26 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // TODO:
  6. // Take advantage of new NPC fields like GetEnemy() and get rid of that OFFSET() stuff
  7. // Revisit enemy validation stuff, maybe it's not necessary with the newest NPC code
  8. //
  9. // $NoKeywords: $
  10. //=============================================================================//
  11. #include "cbase.h"
  12. #include "Sprite.h"
  13. #include "basecombatweapon.h"
  14. #include "ai_basenpc.h"
  15. #include "AI_Senses.h"
  16. #include "AI_Memory.h"
  17. #include "gamerules.h"
  18. #include "ammodef.h"
  19. #include "ndebugoverlay.h"
  20. #include "IEffects.h"
  21. #include "vstdlib/random.h"
  22. #include "engine/IEngineSound.h"
  23. class CSprite;
  24. #define TURRET_RANGE (100 * 12)
  25. #define TURRET_SPREAD VECTOR_CONE_5DEGREES
  26. #define TURRET_TURNRATE 360 // max angles per second
  27. #define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target
  28. #define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target
  29. #define TURRET_MACHINE_VOLUME 0.5
  30. #define TURRET_BC_YAW "aim_yaw"
  31. #define TURRET_BC_PITCH "aim_pitch"
  32. #define TURRET_ORIENTATION_FLOOR 0
  33. #define TURRET_ORIENTATION_CEILING 1
  34. //=========================================================
  35. // private activities
  36. //=========================================================
  37. int ACT_TURRET_OPEN;
  38. int ACT_TURRET_CLOSE;
  39. int ACT_TURRET_OPEN_IDLE;
  40. int ACT_TURRET_CLOSED_IDLE;
  41. int ACT_TURRET_FIRE;
  42. int ACT_TURRET_RELOAD;
  43. // ===============================================
  44. // Private spawn flags (must be above (1<<15))
  45. // ===============================================
  46. #define SF_NPC_TURRET_AUTOACTIVATE 0x00000020
  47. #define SF_NPC_TURRET_STARTINACTIVE 0x00000040
  48. extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud
  49. ConVar sk_miniturret_health( "sk_miniturret_health","0");
  50. ConVar sk_sentry_health( "sk_sentry_health","0");
  51. ConVar sk_turret_health( "sk_turret_health","0");
  52. class CBaseTurret : public CAI_BaseNPC
  53. {
  54. DECLARE_CLASS( CBaseTurret, CAI_BaseNPC );
  55. public:
  56. void Spawn(void);
  57. virtual void Precache(void);
  58. bool KeyValue( const char *szKeyName, const char *szValue );
  59. //void TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
  60. virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
  61. virtual int OnTakeDamage( const CTakeDamageInfo &info );
  62. virtual Class_T Classify(void);
  63. int BloodColor( void ) { return DONT_BLEED; }
  64. bool Event_Gibbed( void ) { return FALSE; } // UNDONE: Throw turret gibs?
  65. Vector EyeOffset( Activity nActivity );
  66. Vector EyePosition( void );
  67. // Inputs
  68. void InputToggle( inputdata_t &inputdata );
  69. // Think functions
  70. void ActiveThink(void);
  71. void SearchThink(void);
  72. void AutoSearchThink(void);
  73. void TurretDeath(void);
  74. void Deploy(void);
  75. void Retire(void);
  76. void Initialize(void);
  77. virtual void Ping(void);
  78. virtual void EyeOn(void);
  79. virtual void EyeOff(void);
  80. DECLARE_DATADESC();
  81. // other functions
  82. int MoveTurret(void);
  83. virtual void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy) { };
  84. CSprite *m_pEyeGlow;
  85. int m_eyeBrightness;
  86. int m_iDeployHeight;
  87. int m_iRetractHeight;
  88. int m_iMinPitch;
  89. int m_iBaseTurnRate; // angles per second
  90. float m_fTurnRate; // actual turn rate
  91. int m_iOn;
  92. int m_fBeserk; // Sometimes this bitch will just freak out
  93. int m_iAutoStart; // true if the turret auto deploys when a target
  94. // enters its range
  95. Vector m_vecLastSight; // Last seen position
  96. float m_flLastSight; // Last time we saw a target
  97. float m_flMaxWait; // Max time to search w/o a target
  98. // movement
  99. float m_flStartYaw;
  100. QAngle m_vecGoalAngles;
  101. int m_iAmmoType;
  102. float m_flPingTime; // Time until the next ping, used when searching
  103. float m_flDamageTime; // Time we last took damage.
  104. COutputEvent m_OnDeploy;
  105. COutputEvent m_OnRetire;
  106. // external
  107. //COutputEvent m_OnDamaged;
  108. //COutputEvent m_OnDeath;
  109. //COutputEvent m_OnHalfHealth;
  110. //COutputEHANDLE m_OnFoundEnemy;
  111. //COutputEvent m_OnLostEnemyLOS;
  112. //COutputEvent m_OnLostEnemy;
  113. //COutputEHANDLE m_OnFoundPlayer;
  114. //COutputEvent m_OnLostPlayerLOS;
  115. //COutputEvent m_OnLostPlayer;
  116. //COutputEvent m_OnHearWorld;
  117. //COutputEvent m_OnHearPlayer;
  118. //COutputEvent m_OnHearCombat;
  119. };
  120. BEGIN_DATADESC( CBaseTurret )
  121. DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ),
  122. DEFINE_FIELD( m_eyeBrightness, FIELD_INTEGER ),
  123. DEFINE_FIELD( m_iDeployHeight, FIELD_INTEGER ),
  124. DEFINE_FIELD( m_iRetractHeight, FIELD_INTEGER ),
  125. DEFINE_FIELD( m_iMinPitch, FIELD_INTEGER ),
  126. DEFINE_FIELD( m_iBaseTurnRate, FIELD_INTEGER ),
  127. DEFINE_FIELD( m_fTurnRate, FIELD_FLOAT ),
  128. DEFINE_FIELD( m_iOn, FIELD_INTEGER ),
  129. DEFINE_FIELD( m_fBeserk, FIELD_INTEGER ),
  130. DEFINE_FIELD( m_iAutoStart, FIELD_INTEGER ),
  131. DEFINE_FIELD( m_vecLastSight, FIELD_POSITION_VECTOR ),
  132. DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
  133. DEFINE_FIELD( m_flMaxWait, FIELD_FLOAT ),
  134. DEFINE_FIELD( m_flStartYaw, FIELD_FLOAT ),
  135. DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
  136. DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
  137. DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
  138. DEFINE_FIELD( m_flDamageTime, FIELD_TIME ),
  139. // Function pointers
  140. //DEFINE_USEFUNC( TurretUse ),
  141. DEFINE_THINKFUNC( ActiveThink ),
  142. DEFINE_THINKFUNC( SearchThink ),
  143. DEFINE_THINKFUNC( AutoSearchThink ),
  144. DEFINE_THINKFUNC( TurretDeath ),
  145. DEFINE_THINKFUNC( Deploy ),
  146. DEFINE_THINKFUNC( Retire ),
  147. DEFINE_THINKFUNC( Initialize ),
  148. // Inputs
  149. DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
  150. // Outputs
  151. DEFINE_OUTPUT(m_OnDeploy, "OnDeploy"),
  152. DEFINE_OUTPUT(m_OnRetire, "OnRetire"),
  153. END_DATADESC()
  154. //------------------------------------------------------------------------------
  155. // Purpose :
  156. // Input :
  157. // Output :
  158. //------------------------------------------------------------------------------
  159. Vector CBaseTurret::EyeOffset( Activity nActivity )
  160. {
  161. return Vector( 0, 0, -20 );
  162. }
  163. Vector CBaseTurret::EyePosition( void )
  164. {
  165. Vector vecOrigin;
  166. QAngle vecAngles;
  167. GetAttachment( "eyes", vecOrigin, vecAngles );
  168. return vecOrigin;
  169. }
  170. bool CBaseTurret::KeyValue( const char *szKeyName, const char *szValue )
  171. {
  172. if (FStrEq(szKeyName, "maxsleep"))
  173. {
  174. m_flMaxWait = atof(szValue);
  175. }
  176. else if (FStrEq(szKeyName, "turnrate"))
  177. {
  178. m_iBaseTurnRate = atoi(szValue);
  179. }
  180. else if (FStrEq(szKeyName, "style") ||
  181. FStrEq(szKeyName, "height") ||
  182. FStrEq(szKeyName, "value1") ||
  183. FStrEq(szKeyName, "value2") ||
  184. FStrEq(szKeyName, "value3"))
  185. {
  186. }
  187. else
  188. return BaseClass::KeyValue( szKeyName, szValue );
  189. return true;
  190. }
  191. void CBaseTurret::Spawn()
  192. {
  193. Precache( );
  194. SetNextThink( gpGlobals->curtime + 1 );
  195. SetMoveType( MOVETYPE_FLY );
  196. m_nSequence = 0;
  197. m_flCycle = 0;
  198. SetSolid( SOLID_SLIDEBOX );
  199. m_takedamage = DAMAGE_YES;
  200. AddFlag( FL_AIMTARGET );
  201. m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("SMG1");
  202. AddFlag( FL_NPC );
  203. if (( m_spawnflags & SF_NPC_TURRET_AUTOACTIVATE ) && !( m_spawnflags & SF_NPC_TURRET_STARTINACTIVE ))
  204. {
  205. m_iAutoStart = true;
  206. }
  207. ResetSequenceInfo( );
  208. SetPoseParameter( TURRET_BC_YAW, 0 );
  209. SetPoseParameter( TURRET_BC_PITCH, 0 );
  210. // Activities
  211. ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_OPEN );
  212. ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_CLOSE );
  213. ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_CLOSED_IDLE );
  214. ADD_CUSTOM_ACTIVITY( CBase`matTurret, ACT_TURRET_OPEN_IDLE );
  215. ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_FIRE );
  216. ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_RELOAD );
  217. }
  218. void CBaseTurret::Precache( )
  219. {
  220. BaseClass::Precache();
  221. PrecacheScriptSound( "NPC_Turret.Ping" );
  222. PrecacheScriptSound( "NPC_Turret.Deploy" );
  223. PrecacheScriptSound( "NPC_Turret.Retire" );
  224. PrecacheScriptSound( "NPC_Turret.Alert" );
  225. PrecacheScriptSound( "NPC_Turret.Die" );
  226. }
  227. void CBaseTurret::Initialize(void)
  228. {
  229. m_iOn = 0;
  230. m_fBeserk = 0;
  231. SetPoseParameter( TURRET_BC_YAW, 0 );
  232. SetPoseParameter( TURRET_BC_PITCH, 0 );
  233. if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE;
  234. if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT;
  235. m_vecGoalAngles = GetAngles();
  236. if (m_iAutoStart)
  237. {
  238. m_flLastSight = gpGlobals->curtime + m_flMaxWait;
  239. SetThink(AutoSearchThink);
  240. SetNextThink( gpGlobals->curtime + .1 );
  241. }
  242. else
  243. SetThink(SUB_DoNothing);
  244. }
  245. //-----------------------------------------------------------------------------
  246. // Purpose: Input handler for toggling the turret on/off.
  247. //-----------------------------------------------------------------------------
  248. void CBaseTurret::InputToggle( inputdata_t &inputdata )
  249. {
  250. //if ( !ShouldToggle( useType, m_iOn ) )
  251. // return;
  252. if (m_iOn)
  253. {
  254. SetEnemy( NULL );
  255. SetNextThink( gpGlobals->curtime + 0.1f );
  256. m_iAutoStart = FALSE;// switching off a turret disables autostart
  257. //!!!! this should spin down first!!BUGBUG
  258. SetThink(Retire);
  259. }
  260. else
  261. {
  262. SetNextThink( gpGlobals->curtime + 0.1f ); // turn on delay
  263. // if the turret is flagged as an autoactivate turret, re-enable its ability open self.
  264. if ( m_spawnflags & SF_NPC_TURRET_AUTOACTIVATE )
  265. {
  266. m_iAutoStart = TRUE;
  267. }
  268. SetThink(Deploy);
  269. }
  270. }
  271. void CBaseTurret::Ping( void )
  272. {
  273. // make the pinging noise every second while searching
  274. if (m_flPingTime == 0)
  275. m_flPingTime = gpGlobals->curtime + 1;
  276. else if (m_flPingTime <= gpGlobals->curtime)
  277. {
  278. m_flPingTime = gpGlobals->curtime + 1;
  279. EmitSound( "NPC_Turret.Ping" );
  280. EyeOn( );
  281. }
  282. else if (m_eyeBrightness > 0)
  283. {
  284. EyeOff( );
  285. }
  286. }
  287. void CBaseTurret::EyeOn( )
  288. {
  289. if (m_pEyeGlow)
  290. {
  291. if (m_eyeBrightness != 255)
  292. {
  293. m_eyeBrightness = 255;
  294. }
  295. m_pEyeGlow->SetBrightness( m_eyeBrightness );
  296. }
  297. }
  298. void CBaseTurret::EyeOff( )
  299. {
  300. if (m_pEyeGlow)
  301. {
  302. if (m_eyeBrightness > 0)
  303. {
  304. m_eyeBrightness = MAX( 0, m_eyeBrightness - 30 );
  305. m_pEyeGlow->SetBrightness( m_eyeBrightness );
  306. }
  307. }
  308. }
  309. void CBaseTurret::ActiveThink(void)
  310. {
  311. int fAttack = 0;
  312. Vector vecDirToEnemy;
  313. SetNextThink( gpGlobals->curtime + 0.1f );
  314. StudioFrameAdvance( );
  315. if ((!m_iOn) || (GetEnemy() == NULL))
  316. {
  317. SetEnemy( NULL );
  318. m_flLastSight = gpGlobals->curtime + m_flMaxWait;
  319. SetThink(SearchThink);
  320. return;
  321. }
  322. // if it's dead, look for something new
  323. if ( !GetEnemy()->IsAlive() )
  324. {
  325. if (!m_flLastSight)
  326. {
  327. m_flLastSight = gpGlobals->curtime + 0.5; // continue-shooting timeout
  328. }
  329. else
  330. {
  331. if (gpGlobals->curtime > m_flLastSight)
  332. {
  333. SetEnemy( NULL );
  334. m_flLastSight = gpGlobals->curtime + m_flMaxWait;
  335. SetThink(SearchThink);
  336. return;
  337. }
  338. }
  339. }
  340. Vector vecMid = EyePosition( );
  341. Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid);
  342. // g_pEffects->Sparks( vecMid );
  343. // g_pEffects->Sparks( vecMidEnemy );
  344. // Look for our current enemy
  345. //int fEnemyVisible = FBoxVisible( this, GetEnemy(), vecMidEnemy );
  346. int fEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() );
  347. vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy
  348. // NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.1 );
  349. float flDistToEnemy = vecDirToEnemy.Length();
  350. QAngle vecAnglesToEnemy;
  351. VectorNormalize( vecDirToEnemy );
  352. VectorAngles( vecDirToEnemy, vecAnglesToEnemy );
  353. // Current enmey is not visible.
  354. if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE))
  355. {
  356. // DevMsg( "lost you\n" );
  357. if (!m_flLastSight)
  358. {
  359. m_flLastSight = gpGlobals->curtime + 0.5;
  360. }
  361. else
  362. {
  363. // Should we look for a new target?
  364. if (gpGlobals->curtime > m_flLastSight)
  365. {
  366. ClearEnemyMemory();
  367. SetEnemy( NULL );
  368. m_flLastSight = gpGlobals->curtime + m_flMaxWait;
  369. SetThink(SearchThink);
  370. return;
  371. }
  372. }
  373. fEnemyVisible = 0;
  374. }
  375. else
  376. {
  377. m_vecLastSight = vecMidEnemy;
  378. }
  379. Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight;
  380. VectorNormalize( vecLOS );
  381. Vector vecMuzzle, vecMuzzleDir;
  382. QAngle vecMuzzleAng;
  383. GetAttachment( "eyes", vecMuzzle, vecMuzzleAng );
  384. AngleVectors( vecMuzzleAng, &vecMuzzleDir );
  385. // Is the Gun looking at the target
  386. if (DotProduct(vecLOS, vecMuzzleDir) <= 0.9848) // 10 degree slop
  387. {
  388. fAttack = FALSE;
  389. }
  390. else
  391. {
  392. fAttack = TRUE;
  393. }
  394. // fire the gun
  395. if (fAttack || m_fBeserk)
  396. {
  397. m_Activity = ACT_RESET;
  398. SetActivity( (Activity)ACT_TURRET_FIRE );
  399. Shoot(vecMuzzle, vecMuzzleDir );
  400. }
  401. else
  402. {
  403. SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );
  404. }
  405. //move the gun
  406. if (m_fBeserk)
  407. {
  408. // DevMsg( "berserk" );
  409. if (random->RandomInt(0,9) == 0)
  410. {
  411. m_vecGoalAngles.y = random->RandomFloat(-180,180);
  412. m_vecGoalAngles.x = random->RandomFloat(-90,90);
  413. OnTakeDamage( CTakeDamageInfo( this, this, 1, DMG_GENERIC ) ); // don't beserk forever
  414. return;
  415. }
  416. }
  417. else if (fEnemyVisible)
  418. {
  419. // DevMsg( "->[%.2f]\n", vec.x);
  420. m_vecGoalAngles.y = vecAnglesToEnemy.y;
  421. m_vecGoalAngles.x = vecAnglesToEnemy.x;
  422. }
  423. MoveTurret();
  424. }
  425. void CBaseTurret::Deploy(void)
  426. {
  427. SetNextThink( gpGlobals->curtime + 0.1f );
  428. StudioFrameAdvance( );
  429. if ( m_Activity != ACT_TURRET_OPEN )
  430. {
  431. m_iOn = 1;
  432. SetActivity( (Activity)ACT_TURRET_OPEN );
  433. EmitSound( "NPC_Turret.Deploy" );
  434. m_OnDeploy.FireOutput(NULL, this);
  435. }
  436. if (m_fSequenceFinished)
  437. {
  438. Vector curmins, curmaxs;
  439. curmins = WorldAlignMins();
  440. curmaxs = WorldAlignMaxs();
  441. curmaxs.z = m_iDeployHeight;
  442. curmins.z = -m_iDeployHeight;
  443. SetCollisionBounds( curmins, curmaxs );
  444. Relink();
  445. SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );
  446. m_flPlaybackRate = 0;
  447. SetThink(SearchThink);
  448. }
  449. m_flLastSight = gpGlobals->curtime + m_flMaxWait;
  450. }
  451. void CBaseTurret::Retire(void)
  452. {
  453. // make the turret level
  454. m_vecGoalAngles = GetAngles( );
  455. SetNextThink( gpGlobals->curtime + 0.1f );
  456. StudioFrameAdvance( );
  457. EyeOff( );
  458. if ( m_Activity != ACT_TURRET_CLOSE )
  459. {
  460. SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );
  461. if (!MoveTurret())
  462. {
  463. SetActivity( (Activity)ACT_TURRET_CLOSE );
  464. EmitSound( "NPC_Turret.Retire" );
  465. m_OnRetire.FireOutput(NULL, this);
  466. }
  467. }
  468. else if (m_fSequenceFinished)
  469. {
  470. m_iOn = 0;
  471. m_flLastSight = 0;
  472. SetActivity( (Activity)ACT_TURRET_CLOSED_IDLE );
  473. Vector curmins, curmaxs;
  474. curmins = WorldAlignMins();
  475. curmaxs = WorldAlignMaxs();
  476. curmaxs.z = m_iRetractHeight;
  477. curmins.z = -m_iRetractHeight;
  478. SetCollisionBounds( curmins, curmaxs );
  479. Relink();
  480. if (m_iAutoStart)
  481. {
  482. SetThink(AutoSearchThink);
  483. SetNextThink( gpGlobals->curtime + .1 );
  484. }
  485. else
  486. {
  487. SetThink(SUB_DoNothing);
  488. }
  489. }
  490. }
  491. //
  492. // This search function will sit with the turret deployed and look for a new target.
  493. // After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will
  494. // retact.
  495. //
  496. void CBaseTurret::SearchThink(void)
  497. {
  498. // ensure rethink
  499. SetActivity( (Activity)ACT_TURRET_OPEN_IDLE );
  500. StudioFrameAdvance( );
  501. SetNextThink( gpGlobals->curtime + 0.1f );
  502. Ping( );
  503. // If we have a target and we're still healthy
  504. if (GetEnemy() != NULL)
  505. {
  506. if (!GetEnemy()->IsAlive() )
  507. SetEnemy( NULL );// Dead enemy forces a search for new one
  508. }
  509. // Acquire Target
  510. if (GetEnemy() == NULL)
  511. {
  512. GetSenses()->Look(TURRET_RANGE);
  513. SetEnemy( BestEnemy() );
  514. }
  515. // If we've found a target, spin up the barrel and start to attack
  516. if (GetEnemy() != NULL)
  517. {
  518. m_flLastSight = 0;
  519. SetThink(ActiveThink);
  520. }
  521. else
  522. {
  523. // Are we out of time, do we need to retract?
  524. if (gpGlobals->curtime > m_flLastSight)
  525. {
  526. //Before we retrace, make sure that we are spun down.
  527. m_flLastSight = 0;
  528. SetThink(Retire);
  529. }
  530. // generic hunt for new victims
  531. m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_iBaseTurnRate);
  532. if (m_vecGoalAngles.y >= 360)
  533. m_vecGoalAngles.y -= 360;
  534. MoveTurret();
  535. }
  536. }
  537. //
  538. // This think function will deploy the turret when something comes into range. This is for
  539. // automatically activated turrets.
  540. //
  541. void CBaseTurret::AutoSearchThink(void)
  542. {
  543. // ensure rethink
  544. StudioFrameAdvance( );
  545. SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.2, 0.3 ) );
  546. // If we have a target and we're still healthy
  547. if (GetEnemy() != NULL)
  548. {
  549. if (!GetEnemy()->IsAlive() )
  550. SetEnemy( NULL );// Dead enemy forces a search for new one
  551. }
  552. // Acquire Target
  553. if (GetEnemy() == NULL)
  554. {
  555. GetSenses()->Look( TURRET_RANGE );
  556. SetEnemy( BestEnemy() );
  557. }
  558. if (GetEnemy() != NULL)
  559. {
  560. SetThink(Deploy);
  561. EmitSound( "NPC_Turret.Alert" );
  562. }
  563. }
  564. void CBaseTurret :: TurretDeath( void )
  565. {
  566. StudioFrameAdvance( );
  567. SetNextThink( gpGlobals->curtime + 0.1f );
  568. if (m_lifeState != LIFE_DEAD)
  569. {
  570. m_lifeState = LIFE_DEAD;
  571. EmitSound( "NPC_Turret.Die" );
  572. SetActivity( (Activity)ACT_TURRET_CLOSE );
  573. EyeOn( );
  574. }
  575. EyeOff( );
  576. if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime)
  577. {
  578. // lots of smoke
  579. Vector pos;
  580. CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );
  581. pos.z = CollisionProp()->GetCollisionOrigin().z;
  582. CBroadcastRecipientFilter filter;
  583. te->Smoke( filter, 0.0, &pos,
  584. g_sModelIndexSmoke,
  585. 2.5,
  586. 10 );
  587. }
  588. if (m_flDamageTime + random->RandomFloat( 0, 5 ) > gpGlobals->curtime)
  589. {
  590. Vector vecSrc;
  591. CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecSrc );
  592. g_pEffects->Sparks( vecSrc );
  593. }
  594. if (m_fSequenceFinished && !MoveTurret( ) && m_flDamageTime + 5 < gpGlobals->curtime)
  595. {
  596. m_flPlaybackRate = 0;
  597. SetThink( NULL );
  598. }
  599. }
  600. void CBaseTurret::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr )
  601. {
  602. CTakeDamageInfo info = inputInfo;
  603. if ( ptr->hitgroup == 10 )
  604. {
  605. // hit armor
  606. if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) )
  607. {
  608. g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) );
  609. m_flDamageTime = gpGlobals->curtime;
  610. }
  611. info.SetDamage( 0.1 );// don't hurt the NPC much, but allow bits_COND_LIGHT_DAMAGE to be generated
  612. }
  613. if ( !m_takedamage )
  614. return;
  615. AddMultiDamage( info, this );
  616. }
  617. int CBaseTurret::OnTakeDamage( const CTakeDamageInfo &inputInfo )
  618. {
  619. if ( !m_takedamage )
  620. return 0;
  621. CTakeDamageInfo info = inputInfo;
  622. if (!m_iOn)
  623. info.ScaleDamage( 0.1f );
  624. m_iHealth -= info.GetDamage();
  625. if (m_iHealth <= 0)
  626. {
  627. m_iHealth = 0;
  628. m_takedamage = DAMAGE_NO;
  629. m_flDamageTime = gpGlobals->curtime;
  630. RemoveFlag( FL_NPC ); // why are they set in the first place???
  631. SetThink(TurretDeath);
  632. m_OnDamaged.FireOutput( info.GetInflictor(), this );
  633. SetNextThink( gpGlobals->curtime + 0.1f );
  634. return 0;
  635. }
  636. if (m_iHealth <= 10)
  637. {
  638. if (m_iOn && (1 || random->RandomInt(0, 0x7FFF) > 800))
  639. {
  640. m_fBeserk = 1;
  641. SetThink(SearchThink);
  642. }
  643. }
  644. return 1;
  645. }
  646. int CBaseTurret::MoveTurret(void)
  647. {
  648. bool bDidMove = false;
  649. int iPose;
  650. matrix3x4_t localToWorld;
  651. GetAttachment( LookupAttachment( "eyes" ), localToWorld );
  652. Vector vecGoalDir;
  653. AngleVectors( m_vecGoalAngles, &vecGoalDir );
  654. Vector vecGoalLocalDir;
  655. VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir );
  656. QAngle vecGoalLocalAngles;
  657. VectorAngles( vecGoalLocalDir, vecGoalLocalAngles );
  658. float flDiff;
  659. QAngle vecNewAngles;
  660. // update pitch
  661. flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1 * m_iBaseTurnRate ) );
  662. iPose = LookupPoseParameter( TURRET_BC_PITCH );
  663. SetPoseParameter( iPose, GetPoseParameter( iPose ) + flDiff / 1.5 );
  664. if (fabs(flDiff) > 0.1)
  665. {
  666. bDidMove = true;
  667. }
  668. // update yaw, with acceleration
  669. #if 0
  670. float flDist = AngleNormalize( vecGoalLocalAngles.y );
  671. float flNewDist;
  672. float flNewTurnRate;
  673. ChangeDistance( 0.1, flDist, 0.0, m_fTurnRate, m_iBaseTurnRate, m_iBaseTurnRate * 4, flNewDist, flNewTurnRate );
  674. m_fTurnRate = flNewTurnRate;
  675. flDiff = flDist - flNewDist;
  676. #else
  677. flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1 * m_iBaseTurnRate ) );
  678. #endif
  679. iPose = LookupPoseParameter( TURRET_BC_YAW );
  680. SetPoseParameter( iPose, GetPoseParameter( iPose ) + flDiff / 1.5 );
  681. if (fabs(flDiff) > 0.1)
  682. {
  683. bDidMove = true;
  684. }
  685. if (bDidMove)
  686. {
  687. // DevMsg( "(%.2f, %.2f)\n", AngleNormalize( vecGoalLocalAngles.x ), AngleNormalize( vecGoalLocalAngles.y ) );
  688. }
  689. return bDidMove;
  690. }
  691. //
  692. // ID as a machine
  693. //
  694. Class_T CBaseTurret::Classify ( void )
  695. {
  696. if (m_iOn || m_iAutoStart)
  697. {
  698. return CLASS_MILITARY;
  699. }
  700. return CLASS_MILITARY;
  701. }
  702. //////////////////////////////////////////////////////////////////////////////////////////////////////
  703. class CCeilingTurret : public CBaseTurret
  704. {
  705. DECLARE_CLASS( CCeilingTurret, CBaseTurret );
  706. public:
  707. void Spawn(void);
  708. void Precache(void);
  709. // other functions
  710. void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy );
  711. };
  712. #define TURRET_GLOW_SPRITE "sprites/glow01.vmt"
  713. LINK_ENTITY_TO_CLASS( npc_turret_ceiling, CCeilingTurret );
  714. void CCeilingTurret::Spawn()
  715. {
  716. Precache( );
  717. SetModel( "models/combine_turrets/ceiling_turret.mdl" );
  718. BaseClass::Spawn( );
  719. m_iHealth = sk_turret_health.GetFloat();
  720. m_HackedGunPos = Vector( 0, 0, 12.75 );
  721. AngleVectors( GetAngles(), NULL, NULL, &m_vecViewOffset );
  722. m_vecViewOffset = m_vecViewOffset * Vector( 0, 0, -64 );
  723. m_flFieldOfView = VIEW_FIELD_FULL;
  724. m_iRetractHeight = 16;
  725. m_iDeployHeight = 32;
  726. m_iMinPitch = -45;
  727. UTIL_SetSize(this, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight));
  728. SetThink(Initialize);
  729. m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, GetOrigin(), FALSE );
  730. m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation );
  731. m_pEyeGlow->SetAttachment( this, 2 );
  732. m_eyeBrightness = 0;
  733. SetNextThink( gpGlobals->curtime + 0.3; );
  734. }
  735. void CCeilingTurret::Precache()
  736. {
  737. PrecacheModel( "models/combine_turrets/ceiling_turret.mdl");
  738. PrecacheModel( TURRET_GLOW_SPRITE );
  739. PrecacheScriptSound( "CeilingTurret.Shoot" );
  740. BaseClass::Precache();
  741. }
  742. void CCeilingTurret::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy)
  743. {
  744. //NDebugOverlay::Line( vecSrc, vecSrc + vecDirToEnemy * 512, 0, 255, 255, false, 0.1 );
  745. FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
  746. EmitSound( "CeilingTurret.Shoot" );
  747. DoMuzzleFlash();
  748. }
  749. //////////////////////////////////////////////////////////////////////////////////////////////////////
  750. #if 0
  751. class CMiniTurret : public CBaseTurret
  752. {
  753. DECLARE_CLASS( CMiniTurret, CBaseTurret );
  754. public:
  755. void Spawn( );
  756. void Precache(void);
  757. // other functions
  758. void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy);
  759. };
  760. LINK_ENTITY_TO_CLASS( npc_miniturret, CMiniTurret );
  761. void CMiniTurret::Spawn()
  762. {
  763. Precache( );
  764. SetModel( "models/miniturret.mdl" );
  765. m_iHealth = sk_miniturret_health.GetFloat();
  766. m_HackedGunPos = Vector( 0, 0, 12.75 );
  767. m_vecViewOffset.z = 12.75;
  768. m_flFieldOfView = VIEW_FIELD_NARROW;
  769. CBaseTurret::Spawn( );
  770. m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("Pistol");
  771. m_iRetractHeight = 16;
  772. m_iDeployHeight = 32;
  773. m_iMinPitch = -45;
  774. UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
  775. SetThink(Initialize);
  776. SetNextThink( gpGlobals->curtime + 0.3; );
  777. }
  778. void CMiniTurret::Precache()
  779. {
  780. PrecacheModel ("models/miniturret.mdl");
  781. PrecacheScriptSound( "MiniTurret.Shoot" );
  782. BaseClass::Precache();
  783. }
  784. void CMiniTurret::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy)
  785. {
  786. FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
  787. EmitSound( "MiniTurret.Shoot" );
  788. DoMuzzleFlash();
  789. }
  790. #endif
  791. //=========================================================
  792. // Sentry gun - smallest turret, placed near grunt entrenchments
  793. //=========================================================
  794. class CSentry : public CBaseTurret
  795. {
  796. DECLARE_CLASS( CSentry, CBaseTurret );
  797. public:
  798. void Spawn( );
  799. void Precache(void);
  800. // other functions
  801. void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy);
  802. int OnTakeDamage( const CTakeDamageInfo &info );
  803. void SentryTouch( CBaseEntity *pOther );
  804. void SentryDeath( void );
  805. protected:
  806. DECLARE_DATADESC();
  807. };
  808. BEGIN_DATADESC( CSentry )
  809. // Function pointers
  810. DEFINE_ENTITYFUNC( SentryTouch ),
  811. DEFINE_THINKFUNC( SentryDeath ),
  812. END_DATADESC()
  813. LINK_ENTITY_TO_CLASS( NPC_sentry, CSentry );
  814. void CSentry::Precache()
  815. {
  816. PrecacheModel ("models/sentry.mdl");
  817. PrecacheScriptSound( "Sentry.Shoot" );
  818. PrecacheScriptSound( "Sentry.Die" );
  819. BaseClass::Precache();
  820. }
  821. void CSentry::Spawn()
  822. {
  823. Precache( );
  824. SetModel( "models/sentry.mdl" );
  825. m_iHealth = sk_sentry_health.GetFloat();
  826. m_HackedGunPos = Vector( 0, 0, 48 );
  827. m_vecViewOffset.z = 48;
  828. m_flMaxWait = 1E6;
  829. CBaseTurret::Spawn();
  830. m_iRetractHeight = 64;
  831. m_iDeployHeight = 64;
  832. m_iMinPitch = -60;
  833. UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
  834. SetTouch(SentryTouch);
  835. SetThink(Initialize);
  836. SetNextThink( gpGlobals->curtime + 0.3; );
  837. }
  838. void CSentry::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy)
  839. {
  840. FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
  841. EmitSound( "Sentry.Shoot" );
  842. DoMuzzleFlash();
  843. }
  844. int CSentry::OnTakeDamage( const CTakeDamageInfo &info )
  845. {
  846. if ( !m_takedamage )
  847. return 0;
  848. if (!m_iOn)
  849. {
  850. SetThink( Deploy );
  851. SetNextThink( gpGlobals->curtime + 0.1f );
  852. }
  853. m_iHealth -= info.GetDamage();
  854. if (m_iHealth <= 0)
  855. {
  856. m_iHealth = 0;
  857. m_takedamage = DAMAGE_NO;
  858. m_flDamageTime = gpGlobals->curtime;
  859. RemoveFlag( FL_NPC ); // why are they set in the first place???
  860. SetThink(SentryDeath);
  861. m_OnDamaged.FireOutput( info.GetInflictor(), this );
  862. SetNextThink( gpGlobals->curtime + 0.1f );
  863. return 0;
  864. }
  865. return 1;
  866. }
  867. void CSentry::SentryTouch( CBaseEntity *pOther )
  868. {
  869. if ( pOther && (pOther->IsPlayer() || (pOther->GetFlags() & FL_NPC)) )
  870. {
  871. OnTakeDamage( CTakeDamageInfo( pOther, pOther, 0, 0 ) );
  872. }
  873. }
  874. void CSentry :: SentryDeath( void )
  875. {
  876. StudioFrameAdvance( );
  877. SetNextThink( gpGlobals->curtime + 0.1f );
  878. if (m_lifeState != LIFE_DEAD)
  879. {
  880. m_lifeState = LIFE_DEAD;
  881. EmitSound( "Sentry.Die" );
  882. SetPoseParameter( TURRET_BC_YAW, 0 );
  883. SetPoseParameter( TURRET_BC_PITCH, 0 );
  884. SetActivity( (Activity)ACT_TURRET_CLOSE );
  885. SetSolid( SOLID_NOT );
  886. QAngle angles = GetAngles();
  887. angles.y = UTIL_AngleMod( GetAngles().y + random->RandomInt( 0, 2 ) * 120 );
  888. SetAngles( angles );
  889. EyeOn( );
  890. }
  891. EyeOff( );
  892. Vector vecSrc;
  893. QAngle vecAng;
  894. GetAttachment( "eyes", vecSrc, vecAng );
  895. if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime)
  896. {
  897. // lots of smoke
  898. Vector pos = vecSrc + Vector( random->RandomFloat( -16, 16 ),
  899. random->RandomFloat( -16, 16 ),
  900. -32 );
  901. CBroadcastRecipientFilter filter;
  902. te->Smoke( filter, 0.0, &pos,
  903. g_sModelIndexSmoke,
  904. 1.5,
  905. 8 );
  906. }
  907. if (m_flDamageTime + random->RandomFloat( 0, 8 ) > gpGlobals->curtime)
  908. {
  909. g_pEffects->Sparks( vecSrc );
  910. }
  911. if (m_fSequenceFinished && m_flDamageTime + 5 < gpGlobals->curtime)
  912. {
  913. m_flPlaybackRate = 0;
  914. SetThink( NULL );
  915. }
  916. }