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.

1412 lines
41 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_senses.h"
  10. #include "ai_memory.h"
  11. #include "engine/IEngineSound.h"
  12. #include "Sprite.h"
  13. #include "IEffects.h"
  14. #include "prop_portal_shared.h"
  15. #include "te.h"
  16. #include "te_effect_dispatch.h"
  17. #include "soundenvelope.h" // for looping sound effects
  18. #include "portal_gamerules.h" // for difficulty settings
  19. #include "weapon_rpg.h"
  20. #include "explode.h"
  21. #include "smoke_trail.h" // smoke trailers on the rocket
  22. #include "physics_bone_follower.h" // For bone follower manager
  23. #include "physicsshadowclone.h" // For translating hit entities shadow clones to real ent
  24. //#include "ndebugoverlay.h"
  25. // memdbgon must be the last include file in a .cpp file!!!
  26. #include "tier0/memdbgon.h"
  27. #define ROCKET_TURRET_RANGE 8192
  28. #define ROCKET_TURRET_EMITER_OFFSET 0.0
  29. #define ROCKET_TURRET_THINK_RATE 0.05
  30. #define ROCKET_TURRET_DEATH_EFFECT_TIME 1.5f
  31. #define ROCKET_TURRET_LOCKON_TIME 2.0f
  32. #define ROCKET_TURRET_HALF_LOCKON_TIME 1.0f
  33. #define ROCKET_TURRET_QUARTER_LOCKON_TIME 0.5f
  34. #define ROCKET_TURRET_ROCKET_FIRE_COOLDOWN_TIME 4.0f
  35. // For search thinks
  36. #define MAX_DIVERGENCE_X 30.0f
  37. #define MAX_DIVERGENCE_Y 15.0f
  38. #define ROCKET_TURRET_DECAL_NAME "decals/scorchfade"
  39. #define ROCKET_TURRET_MODEL_NAME "models/props_bts/rocket_sentry.mdl"
  40. #define ROCKET_TURRET_PROJECTILE_NAME "models/props_bts/rocket.mdl"
  41. #define ROCKET_TURRET_SOUND_LOCKING "NPC_RocketTurret.LockingBeep"
  42. #define ROCKET_TURRET_SOUND_LOCKED "NPC_FloorTurret.LockedBeep"
  43. #define ROCKET_PROJECTILE_FIRE_SOUND "NPC_FloorTurret.RocketFire"
  44. #define ROCKET_PROJECTILE_LOOPING_SOUND "NPC_FloorTurret.RocketFlyLoop"
  45. #define ROCKET_PROJECTILE_DEFAULT_LIFE 20.0
  46. //Spawnflags
  47. #define SF_ROCKET_TURRET_START_INACTIVE 0x00000001
  48. // These bones have physics shadows
  49. const char *pRocketTurretFollowerBoneNames[] =
  50. {
  51. "Root",
  52. "Base",
  53. "Arm_1",
  54. "Arm_2",
  55. "Arm_3",
  56. "Arm_4",
  57. "Rot_LR",
  58. "Rot_UD",
  59. "Gun_casing",
  60. "Gun_Barrel_01",
  61. "gun_barrel_02",
  62. "loader",
  63. "missle_01",
  64. "missle_02",
  65. "panel",
  66. };
  67. class CNPC_RocketTurret : public CAI_BaseNPC
  68. {
  69. DECLARE_CLASS( CNPC_RocketTurret, CAI_BaseNPC );
  70. DECLARE_SERVERCLASS();
  71. DECLARE_DATADESC();
  72. public:
  73. CNPC_RocketTurret( void );
  74. ~CNPC_RocketTurret( void );
  75. void Precache( void );
  76. void Spawn( void );
  77. virtual void Activate( void );
  78. virtual ITraceFilter* GetBeamTraceFilter( void );
  79. void UpdateOnRemove( void );
  80. bool CreateVPhysics( void );
  81. // Think functions
  82. void SearchThink( void ); // Lost Target, spaz out
  83. void FollowThink( void ); // Found target, chase it
  84. void LockingThink( void ); // Charge up effects
  85. void FiringThink( void ); // Currently has rocket out
  86. void DyingThink( void ); // Overloading, blowing up
  87. void DeathThink( void ); // Destroyed, sparking
  88. void OpeningThink ( void ); // Finish open/close animation before using pose params
  89. void ClosingThink ( void );
  90. // Inputs
  91. void InputToggle( inputdata_t &inputdata );
  92. void InputEnable( inputdata_t &inputdata );
  93. void InputDisable( inputdata_t &inputdata );
  94. void InputSetTarget( inputdata_t &inputdata );
  95. void InputDestroy( inputdata_t &inputdata );
  96. void RocketDied( void ); // After rocket hits something and self-destructs (or times out)
  97. Class_T Classify( void )
  98. {
  99. if( m_bEnabled )
  100. return CLASS_COMBINE;
  101. return CLASS_NONE;
  102. }
  103. bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
  104. Vector EyeOffset( Activity nActivity )
  105. {
  106. return vec3_origin;
  107. }
  108. Vector EyePosition( void )
  109. {
  110. Vector vMuzzlePos;
  111. GetAttachment( m_iMuzzleAttachment, vMuzzlePos, NULL, NULL, NULL );
  112. return vMuzzlePos;
  113. }
  114. protected:
  115. bool PreThink( void );
  116. void Toggle( void );
  117. void Enable( void );
  118. void Disable( void );
  119. void SetTarget( CBaseEntity* pTarget );
  120. void Destroy ( void );
  121. float UpdateFacing( void );
  122. void UpdateAimPoint( void );
  123. void FireRocket( void );
  124. void UpdateSkin( int nSkin );
  125. void UpdateMuzzleMatrix ( void );
  126. bool TestLOS( const Vector& vAimPoint );
  127. bool TestPortalsForLOS( Vector* pOutVec, bool bConsiderNonPortalAimPoint );
  128. bool FindAimPointThroughPortal( const CProp_Portal* pPortal, Vector* pVecOut );
  129. void SyncPoseToAimAngles ( void );
  130. void LaserOn ( void );
  131. void LaserOff ( void );
  132. bool m_bEnabled;
  133. bool m_bHasSightOfEnemy;
  134. QAngle m_vecGoalAngles;
  135. CNetworkVar( QAngle, m_vecCurrentAngles );
  136. QAngle m_vecAnglesToEnemy;
  137. enum
  138. {
  139. ROCKET_SKIN_IDLE=0,
  140. ROCKET_SKIN_LOCKING,
  141. ROCKET_SKIN_LOCKED,
  142. ROCKET_SKIN_COUNT,
  143. };
  144. Vector m_vecDirToEnemy;
  145. float m_flDistToEnemy;
  146. float m_flTimeSpentDying;
  147. float m_flTimeLocking; // Period spent locking on to target
  148. float m_flTimeLastFired; // Cooldown time between attacks
  149. float m_flTimeSpentPaused; // for search think's movements
  150. float m_flPauseLength;
  151. float m_flTotalDivergenceX;
  152. float m_flTotalDivergenceY;
  153. matrix3x4_t m_muzzleToWorld;
  154. int m_muzzleToWorldTick;
  155. int m_iPosePitch;
  156. int m_iPoseYaw;
  157. // Contained Bone Follower manager
  158. CBoneFollowerManager m_BoneFollowerManager;
  159. // Model indices for effects
  160. CNetworkVar( int, m_iLaserState );
  161. CNetworkVar( int, m_nSiteHalo );
  162. // Target indicator sprite info
  163. int m_iMuzzleAttachment;
  164. int m_iLightAttachment;
  165. COutputEvent m_OnFoundTarget;
  166. COutputEvent m_OnLostTarget;
  167. CTraceFilterSkipTwoEntities m_filterBeams;
  168. EHANDLE m_hCurRocket;
  169. };
  170. //Datatable
  171. BEGIN_DATADESC( CNPC_RocketTurret )
  172. DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
  173. DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
  174. DEFINE_FIELD( m_vecCurrentAngles, FIELD_VECTOR ),
  175. DEFINE_FIELD( m_bHasSightOfEnemy, FIELD_BOOLEAN ),
  176. DEFINE_FIELD( m_vecAnglesToEnemy, FIELD_VECTOR ),
  177. DEFINE_FIELD( m_vecDirToEnemy, FIELD_VECTOR ),
  178. DEFINE_FIELD( m_flDistToEnemy, FIELD_FLOAT ),
  179. DEFINE_FIELD( m_flTimeSpentDying, FIELD_FLOAT ),
  180. DEFINE_FIELD( m_flTimeLocking, FIELD_FLOAT ),
  181. DEFINE_FIELD( m_flTimeLastFired, FIELD_FLOAT ),
  182. DEFINE_FIELD( m_iLaserState, FIELD_INTEGER ),
  183. DEFINE_FIELD( m_nSiteHalo, FIELD_INTEGER ),
  184. DEFINE_FIELD( m_flTimeSpentPaused, FIELD_FLOAT ),
  185. DEFINE_FIELD( m_flPauseLength, FIELD_FLOAT ),
  186. DEFINE_FIELD( m_flTotalDivergenceX, FIELD_FLOAT ),
  187. DEFINE_FIELD( m_flTotalDivergenceY, FIELD_FLOAT ),
  188. DEFINE_FIELD( m_iPosePitch, FIELD_INTEGER ),
  189. DEFINE_FIELD( m_iPoseYaw, FIELD_INTEGER ),
  190. DEFINE_FIELD( m_hCurRocket, FIELD_EHANDLE ),
  191. DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ),
  192. DEFINE_FIELD( m_iLightAttachment, FIELD_INTEGER ),
  193. DEFINE_FIELD( m_muzzleToWorldTick, FIELD_INTEGER ),
  194. DEFINE_FIELD( m_muzzleToWorld, FIELD_MATRIX3X4_WORLDSPACE ),
  195. // DEFINE_FIELD( m_filterBeams, CTraceFilterSkipTwoEntities ),
  196. DEFINE_EMBEDDED( m_BoneFollowerManager ),
  197. DEFINE_THINKFUNC( SearchThink ),
  198. DEFINE_THINKFUNC( FollowThink ),
  199. DEFINE_THINKFUNC( LockingThink ),
  200. DEFINE_THINKFUNC( FiringThink ),
  201. DEFINE_THINKFUNC( DyingThink ),
  202. DEFINE_THINKFUNC( DeathThink ),
  203. DEFINE_THINKFUNC( OpeningThink ),
  204. DEFINE_THINKFUNC( ClosingThink ),
  205. // Inputs
  206. DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
  207. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  208. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  209. DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ),
  210. DEFINE_INPUTFUNC( FIELD_VOID, "Destroy", InputDestroy ),
  211. DEFINE_OUTPUT( m_OnFoundTarget, "OnFoundTarget" ),
  212. DEFINE_OUTPUT( m_OnLostTarget, "OnLostTarget" ),
  213. END_DATADESC()
  214. IMPLEMENT_SERVERCLASS_ST(CNPC_RocketTurret, DT_NPC_RocketTurret)
  215. SendPropInt( SENDINFO( m_iLaserState ), 2 ),
  216. SendPropInt( SENDINFO( m_nSiteHalo ) ),
  217. SendPropVector( SENDINFO( m_vecCurrentAngles ) ),
  218. END_SEND_TABLE()
  219. LINK_ENTITY_TO_CLASS( npc_rocket_turret, CNPC_RocketTurret );
  220. // Projectile class for this weapon, a rocket
  221. class CRocket_Turret_Projectile : public CMissile
  222. {
  223. DECLARE_CLASS( CRocket_Turret_Projectile, CMissile );
  224. DECLARE_DATADESC();
  225. public:
  226. void Precache( void );
  227. void Spawn( void );
  228. virtual void NotifyLauncherOnDeath( void );
  229. virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
  230. virtual void SetLauncher( EHANDLE hLauncher );
  231. virtual void CreateSmokeTrail( void ); // overloaded from base
  232. virtual void MissileTouch( CBaseEntity *pOther );
  233. EHANDLE m_hLauncher;
  234. CSoundPatch *m_pAmbientSound;
  235. protected:
  236. virtual void DoExplosion( void );
  237. virtual void CreateSounds( void );
  238. virtual void StopLoopingSounds ( void );
  239. };
  240. BEGIN_DATADESC( CRocket_Turret_Projectile )
  241. DEFINE_FIELD( m_hLauncher, FIELD_EHANDLE ),
  242. DEFINE_FUNCTION( MissileTouch ),
  243. DEFINE_SOUNDPATCH( m_pAmbientSound ),
  244. END_DATADESC()
  245. LINK_ENTITY_TO_CLASS( rocket_turret_projectile, CRocket_Turret_Projectile );
  246. //-----------------------------------------------------------------------------
  247. // Constructor
  248. //-----------------------------------------------------------------------------
  249. CNPC_RocketTurret::CNPC_RocketTurret( void )
  250. : m_filterBeams( NULL, NULL, COLLISION_GROUP_DEBRIS )
  251. {
  252. m_bEnabled = false;
  253. m_bHasSightOfEnemy = false;
  254. m_vecGoalAngles.Init();
  255. m_vecAnglesToEnemy.Init();
  256. m_vecDirToEnemy.Init();
  257. m_flTimeLastFired = m_flTimeLocking = m_flDistToEnemy = m_flTimeSpentDying = 0.0f;
  258. m_iLightAttachment = m_iMuzzleAttachment = m_nSiteHalo = 0;
  259. m_flTimeSpentPaused = m_flPauseLength = m_flTotalDivergenceX = m_flTotalDivergenceY = 0.0f;
  260. m_hCurRocket = NULL;
  261. }
  262. CNPC_RocketTurret::~CNPC_RocketTurret( void )
  263. {
  264. }
  265. //-----------------------------------------------------------------------------
  266. // Purpose: Precache
  267. //-----------------------------------------------------------------------------
  268. void CNPC_RocketTurret::Precache( void )
  269. {
  270. PrecacheModel("effects/bluelaser1.vmt");
  271. m_nSiteHalo = PrecacheModel("sprites/light_glow03.vmt");
  272. PrecacheScriptSound ( ROCKET_TURRET_SOUND_LOCKING );
  273. PrecacheScriptSound ( ROCKET_TURRET_SOUND_LOCKED );
  274. PrecacheScriptSound ( ROCKET_PROJECTILE_FIRE_SOUND );
  275. PrecacheScriptSound ( ROCKET_PROJECTILE_LOOPING_SOUND );
  276. UTIL_PrecacheDecal( ROCKET_TURRET_DECAL_NAME );
  277. PrecacheModel( ROCKET_TURRET_MODEL_NAME );
  278. PrecacheModel ( ROCKET_TURRET_PROJECTILE_NAME );
  279. BaseClass::Precache();
  280. }
  281. //-----------------------------------------------------------------------------
  282. // Purpose: the entity
  283. //-----------------------------------------------------------------------------
  284. void CNPC_RocketTurret::Spawn( void )
  285. {
  286. Precache();
  287. BaseClass::Spawn();
  288. SetViewOffset( vec3_origin );
  289. AddEFlags( EFL_NO_DISSOLVE );
  290. SetModel( ROCKET_TURRET_MODEL_NAME );
  291. SetSolid( SOLID_VPHYSICS );
  292. m_iMuzzleAttachment = LookupAttachment ( "barrel" );
  293. m_iLightAttachment = LookupAttachment ( "eye" );
  294. m_iPosePitch = LookupPoseParameter( "aim_pitch" );
  295. m_iPoseYaw = LookupPoseParameter( "aim_yaw" );
  296. m_vecCurrentAngles = m_vecGoalAngles = GetAbsAngles();
  297. CreateVPhysics();
  298. //Set our autostart state
  299. m_bEnabled = ( ( m_spawnflags & SF_ROCKET_TURRET_START_INACTIVE ) == false );
  300. // Set Locked sprite
  301. if ( m_bEnabled )
  302. {
  303. m_iLaserState = 1;
  304. SetSequence(LookupSequence("idle"));
  305. }
  306. else
  307. {
  308. m_iLaserState = 0;
  309. SetSequence(LookupSequence("inactive"));
  310. }
  311. SetCycle(1.0f);
  312. UpdateSkin( ROCKET_SKIN_IDLE );
  313. SetPoseParameter( "aim_pitch", 0 );
  314. SetPoseParameter( "aim_yaw", -180 );
  315. if ( m_bEnabled )
  316. {
  317. SetThink( &CNPC_RocketTurret::FollowThink );
  318. }
  319. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  320. }
  321. bool CNPC_RocketTurret::CreateVPhysics( void )
  322. {
  323. m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pRocketTurretFollowerBoneNames), pRocketTurretFollowerBoneNames );
  324. BaseClass::CreateVPhysics();
  325. return true;
  326. }
  327. void CNPC_RocketTurret::Activate( void )
  328. {
  329. m_filterBeams.SetPassEntity( this );
  330. m_filterBeams.SetPassEntity2( UTIL_GetLocalPlayer() );
  331. BaseClass::Activate();
  332. }
  333. ITraceFilter* CNPC_RocketTurret::GetBeamTraceFilter( void )
  334. {
  335. return &m_filterBeams;
  336. }
  337. void CNPC_RocketTurret::UpdateOnRemove( void )
  338. {
  339. m_BoneFollowerManager.DestroyBoneFollowers();
  340. BaseClass::UpdateOnRemove();
  341. }
  342. //-----------------------------------------------------------------------------
  343. // Purpose:
  344. // Input : *pEntity -
  345. // Output : Returns true on success, false on failure.
  346. //-----------------------------------------------------------------------------
  347. bool CNPC_RocketTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
  348. {
  349. CBaseEntity *pHitEntity = NULL;
  350. if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) )
  351. return true;
  352. if (ppBlocker)
  353. {
  354. *ppBlocker = pHitEntity;
  355. }
  356. return false;
  357. }
  358. //-----------------------------------------------------------------------------
  359. // Purpose:
  360. // Output : void CNPC_RocketTurret::UpdateAimPoint
  361. //-----------------------------------------------------------------------------
  362. void CNPC_RocketTurret::UpdateAimPoint ( void )
  363. {
  364. //If we've become inactive
  365. if ( ( m_bEnabled == false ) || ( GetEnemy() == NULL ) )
  366. {
  367. SetEnemy( NULL );
  368. SetNextThink( TICK_NEVER_THINK );
  369. m_vecGoalAngles = GetAbsAngles();
  370. return;
  371. }
  372. //Get our shot positions
  373. Vector vecMid = EyePosition();
  374. Vector vecMidEnemy = GetEnemy()->GetAbsOrigin() + (GetEnemy()->WorldAlignMins() + GetEnemy()->WorldAlignMaxs()) * 0.5f;
  375. //Calculate dir and dist to enemy
  376. m_vecDirToEnemy = vecMidEnemy - vecMid;
  377. m_flDistToEnemy = VectorNormalize( m_vecDirToEnemy );
  378. VectorAngles( m_vecDirToEnemy, m_vecAnglesToEnemy );
  379. bool bEnemyVisible = false;
  380. if ( !(GetEnemy()->GetFlags() & FL_NOTARGET) )
  381. {
  382. bool bEnemyVisibleInWorld = FVisible( GetEnemy() );
  383. // Test portals in our view as possible ways to view the player
  384. bool bEnemyVisibleThroughPortal = TestPortalsForLOS( &vecMidEnemy, bEnemyVisibleInWorld );
  385. bEnemyVisible = bEnemyVisibleInWorld || bEnemyVisibleThroughPortal;
  386. }
  387. //Store off our last seen location
  388. UpdateEnemyMemory( GetEnemy(), vecMidEnemy );
  389. if ( bEnemyVisible )
  390. {
  391. m_vecDirToEnemy = vecMidEnemy - vecMid;
  392. m_flDistToEnemy = VectorNormalize( m_vecDirToEnemy );
  393. VectorAngles( m_vecDirToEnemy, m_vecAnglesToEnemy );
  394. }
  395. //Current enemy is not visible
  396. if ( ( bEnemyVisible == false ) || ( m_flDistToEnemy > ROCKET_TURRET_RANGE ) )
  397. {
  398. // Had LOS, just lost it
  399. if ( m_bHasSightOfEnemy )
  400. {
  401. m_OnLostTarget.FireOutput( GetEnemy(), this );
  402. }
  403. m_bHasSightOfEnemy = false;
  404. }
  405. //If we can see our enemy
  406. if ( bEnemyVisible )
  407. {
  408. // Had no LOS, just gained it
  409. if ( !m_bHasSightOfEnemy )
  410. {
  411. m_OnFoundTarget.FireOutput( GetEnemy(), this );
  412. }
  413. m_bHasSightOfEnemy = true;
  414. }
  415. }
  416. bool SignDiffers ( float f1, float f2 )
  417. {
  418. return !( Sign(f1) == Sign(f2) );
  419. }
  420. //-----------------------------------------------------------------------------
  421. // Purpose:
  422. //-----------------------------------------------------------------------------
  423. void CNPC_RocketTurret::SearchThink()
  424. {
  425. if ( PreThink() || GetEnemy() == NULL )
  426. return;
  427. SetSequence ( LookupSequence( "idle" ) );
  428. UpdateAimPoint();
  429. //Update our think time
  430. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  431. // Still can't see enemy, zip around frantically
  432. if ( !m_bHasSightOfEnemy )
  433. {
  434. if ( m_flTimeSpentPaused >= m_flPauseLength )
  435. {
  436. float flOffsetX = RandomFloat( -5.0f, 5.0f );
  437. float flOffsetY = RandomFloat( -5.0f, 5.0f );
  438. if ( fabs(m_flTotalDivergenceX) <= MAX_DIVERGENCE_X ||
  439. SignDiffers( m_flTotalDivergenceX, flOffsetX ) )
  440. {
  441. m_flTotalDivergenceX += flOffsetX;
  442. m_vecGoalAngles.x += flOffsetX;
  443. }
  444. if ( fabs(m_flTotalDivergenceY) <= MAX_DIVERGENCE_Y ||
  445. SignDiffers( m_flTotalDivergenceY, flOffsetY ) )
  446. {
  447. m_flTotalDivergenceY += flOffsetY;
  448. m_vecGoalAngles.y += flOffsetY;
  449. }
  450. // Reset pause timer
  451. m_flTimeSpentPaused = 0.0f;
  452. m_flPauseLength = RandomFloat( 0.3f, 2.5f );
  453. }
  454. m_flTimeSpentPaused += ROCKET_TURRET_THINK_RATE;
  455. }
  456. else
  457. {
  458. // Found target, go back to following it
  459. SetThink( &CNPC_RocketTurret::FollowThink );
  460. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  461. }
  462. // Move beam towards goal angles
  463. UpdateFacing();
  464. }
  465. //-----------------------------------------------------------------------------
  466. // Purpose: Allows the turret to fire on targets if they're visible
  467. //-----------------------------------------------------------------------------
  468. void CNPC_RocketTurret::FollowThink( void )
  469. {
  470. // Default to player as enemy
  471. if ( GetEnemy() == NULL )
  472. {
  473. SetEnemy( UTIL_GetLocalPlayer() );
  474. }
  475. SetSequence ( LookupSequence( "idle" ) );
  476. //Allow descended classes a chance to do something before the think function
  477. if ( PreThink() || GetEnemy() == NULL )
  478. {
  479. return;
  480. }
  481. //Update our think time
  482. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  483. UpdateAimPoint();
  484. m_vecGoalAngles = m_vecAnglesToEnemy;
  485. // Chase enemy
  486. if ( !m_bHasSightOfEnemy )
  487. {
  488. // Aim at the last known location
  489. m_vecGoalAngles = m_vecCurrentAngles;
  490. // Lost sight, move to search think
  491. SetThink( &CNPC_RocketTurret::SearchThink );
  492. }
  493. //Turn to face
  494. UpdateFacing();
  495. // If our facing direction hits our enemy, fire the beam
  496. Ray_t rayDmg;
  497. Vector vForward;
  498. AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
  499. Vector vEndPoint = EyePosition() + vForward*ROCKET_TURRET_RANGE;
  500. rayDmg.Init( EyePosition(), vEndPoint );
  501. rayDmg.m_IsRay = true;
  502. trace_t traceDmg;
  503. // This version reorients through portals
  504. CTraceFilterSimple subfilter( this, COLLISION_GROUP_NONE );
  505. CTraceFilterTranslateClones filter ( &subfilter );
  506. float flRequiredParameter = 2.0f;
  507. CProp_Portal* pFirstPortal = UTIL_Portal_FirstAlongRay( rayDmg, flRequiredParameter );
  508. UTIL_Portal_TraceRay_Bullets( pFirstPortal, rayDmg, MASK_VISIBLE_AND_NPCS, &filter, &traceDmg, false );
  509. if ( traceDmg.m_pEnt )
  510. {
  511. // This thing we're hurting is our enemy
  512. if ( traceDmg.m_pEnt == GetEnemy() )
  513. {
  514. // If we're past the cooldown time, fire another rocket
  515. if ( (gpGlobals->curtime - m_flTimeLastFired) > ROCKET_TURRET_ROCKET_FIRE_COOLDOWN_TIME )
  516. {
  517. SetThink( &CNPC_RocketTurret::LockingThink );
  518. }
  519. }
  520. }
  521. }
  522. //-----------------------------------------------------------------------------
  523. // Purpose: Charge up, prepare to fire and give player time to dodge
  524. //-----------------------------------------------------------------------------
  525. void CNPC_RocketTurret::LockingThink( void )
  526. {
  527. //Allow descended classes a chance to do something before the think function
  528. if ( PreThink() )
  529. return;
  530. //Turn to face
  531. UpdateFacing();
  532. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  533. if ( m_flTimeLocking == 0.0f )
  534. {
  535. // Play lockon sound
  536. EmitSound ( ROCKET_TURRET_SOUND_LOCKING );
  537. EmitSound ( ROCKET_TURRET_SOUND_LOCKING, gpGlobals->curtime + ROCKET_TURRET_QUARTER_LOCKON_TIME );
  538. EmitSound ( ROCKET_TURRET_SOUND_LOCKED, gpGlobals->curtime + ROCKET_TURRET_HALF_LOCKON_TIME );
  539. ResetSequence(LookupSequence("load"));
  540. // Change lockon sprite
  541. UpdateSkin( ROCKET_SKIN_LOCKING );
  542. }
  543. m_flTimeLocking += ROCKET_TURRET_THINK_RATE;
  544. if ( m_flTimeLocking > ROCKET_TURRET_LOCKON_TIME )
  545. {
  546. // Set Locked sprite to 'rocket out' color
  547. UpdateSkin( ROCKET_SKIN_LOCKED );
  548. FireRocket();
  549. SetThink ( &CNPC_RocketTurret::FiringThink );
  550. m_flTimeLocking = 0.0f;
  551. }
  552. }
  553. //-----------------------------------------------------------------------------
  554. // Purpose: Charge up, deal damage along our facing direction.
  555. //-----------------------------------------------------------------------------
  556. void CNPC_RocketTurret::FiringThink( void )
  557. {
  558. //Allow descended classes a chance to do something before the think function
  559. if ( PreThink() )
  560. return;
  561. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  562. CRocket_Turret_Projectile* pRocket = dynamic_cast<CRocket_Turret_Projectile*>(m_hCurRocket.Get());
  563. if ( pRocket )
  564. {
  565. // If this rocket has been out too long, detonate it and launch a new one
  566. if ( (gpGlobals->curtime - m_flTimeLastFired) > ROCKET_PROJECTILE_DEFAULT_LIFE )
  567. {
  568. pRocket->ShotDown();
  569. m_flTimeLastFired = gpGlobals->curtime;
  570. SetThink( &CNPC_RocketTurret::FollowThink );
  571. }
  572. }
  573. else
  574. {
  575. // Set Locked sprite
  576. UpdateSkin( ROCKET_SKIN_IDLE );
  577. // Rocket dead, or never created. Revert to follow think
  578. m_flTimeLastFired = gpGlobals->curtime;
  579. SetThink( &CNPC_RocketTurret::FollowThink );
  580. }
  581. }
  582. void CNPC_RocketTurret::FireRocket ( void )
  583. {
  584. UTIL_Remove( m_hCurRocket );
  585. CRocket_Turret_Projectile *pRocket = (CRocket_Turret_Projectile *) CBaseEntity::Create( "rocket_turret_projectile", EyePosition(), m_vecCurrentAngles, this );
  586. if ( !pRocket )
  587. return;
  588. m_hCurRocket = pRocket;
  589. Vector vForward;
  590. AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
  591. m_flTimeLastFired = gpGlobals->curtime;
  592. EmitSound ( ROCKET_PROJECTILE_FIRE_SOUND );
  593. ResetSequence(LookupSequence("fire"));
  594. pRocket->SetThink( NULL );
  595. pRocket->SetMoveType( MOVETYPE_FLY );
  596. pRocket->CreateSmokeTrail();
  597. pRocket->SetModel( ROCKET_TURRET_PROJECTILE_NAME );
  598. UTIL_SetSize( pRocket, vec3_origin, vec3_origin );
  599. pRocket->SetAbsVelocity( vForward * 550 );
  600. pRocket->SetLauncher ( this );
  601. }
  602. void CNPC_RocketTurret::UpdateSkin( int nSkin )
  603. {
  604. m_nSkin = nSkin;
  605. }
  606. //-----------------------------------------------------------------------------
  607. // Purpose: Rocket destructed, resume search behavior
  608. //-----------------------------------------------------------------------------
  609. void CNPC_RocketTurret::RocketDied( void )
  610. {
  611. // Set Locked sprite
  612. UpdateSkin( ROCKET_SKIN_IDLE );
  613. // Rocket dead, return to follow think
  614. m_flTimeLastFired = gpGlobals->curtime;
  615. SetThink( &CNPC_RocketTurret::FollowThink );
  616. }
  617. //-----------------------------------------------------------------------------
  618. // Purpose: Show this rocket turret has overloaded with effects and noise for a period of time
  619. //-----------------------------------------------------------------------------
  620. void CNPC_RocketTurret::DyingThink( void )
  621. {
  622. // Make the beam graphics freak out a bit
  623. m_iLaserState = 2;
  624. UpdateSkin( ROCKET_SKIN_IDLE );
  625. // If we've freaked out for long enough, be dead
  626. if ( m_flTimeSpentDying > ROCKET_TURRET_DEATH_EFFECT_TIME )
  627. {
  628. Vector vForward;
  629. AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
  630. g_pEffects->EnergySplash( EyePosition(), vForward, true );
  631. m_OnDeath.FireOutput( this, this );
  632. SetThink( &CNPC_RocketTurret::DeathThink );
  633. SetNextThink( gpGlobals->curtime + 1.0f );
  634. m_flTimeSpentDying = 0.0f;
  635. }
  636. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  637. m_flTimeSpentDying += ROCKET_TURRET_THINK_RATE;
  638. }
  639. //-----------------------------------------------------------------------------
  640. // Purpose: Sparks and fizzes to show it's broken.
  641. //-----------------------------------------------------------------------------
  642. void CNPC_RocketTurret::DeathThink( void )
  643. {
  644. Vector vForward;
  645. AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL );
  646. m_iLaserState = 0;
  647. SetEnemy( NULL );
  648. g_pEffects->Sparks( EyePosition(), 1, 1, &vForward );
  649. g_pEffects->Smoke( EyePosition(), 0, 6.0f, 20 );
  650. SetNextThink( gpGlobals->curtime + RandomFloat( 2.0f, 8.0f ) );
  651. }
  652. void CNPC_RocketTurret::UpdateMuzzleMatrix()
  653. {
  654. if ( gpGlobals->tickcount != m_muzzleToWorldTick )
  655. {
  656. m_muzzleToWorldTick = gpGlobals->tickcount;
  657. GetAttachment( m_iMuzzleAttachment, m_muzzleToWorld );
  658. }
  659. }
  660. //-----------------------------------------------------------------------------
  661. // Purpose: Avoid aiming/drawing beams while opening and closing
  662. // Input : -
  663. //-----------------------------------------------------------------------------
  664. void CNPC_RocketTurret::OpeningThink()
  665. {
  666. StudioFrameAdvance();
  667. // Require these poses for this animation
  668. QAngle vecNeutralAngles ( 0, 90, 0 );
  669. m_vecGoalAngles = m_vecCurrentAngles = vecNeutralAngles;
  670. SyncPoseToAimAngles();
  671. // Start following player after we're fully opened
  672. float flCurProgress = GetCycle();
  673. if ( flCurProgress >= 0.99f )
  674. {
  675. LaserOn();
  676. SetThink( &CNPC_RocketTurret::FollowThink );
  677. }
  678. SetNextThink( gpGlobals->curtime + 0.1f );
  679. }
  680. //-----------------------------------------------------------------------------
  681. // Purpose: Avoid aiming/drawing beams while opening and closing
  682. // Input : -
  683. //-----------------------------------------------------------------------------
  684. void CNPC_RocketTurret::ClosingThink()
  685. {
  686. LaserOff();
  687. // Require these poses for this animation
  688. QAngle vecNeutralAngles ( 0, 90, 0 );
  689. m_vecGoalAngles = vecNeutralAngles;
  690. // Once we're within 10 degrees of the neutral pose, start close animation.
  691. if ( UpdateFacing() <= 10.0f )
  692. {
  693. StudioFrameAdvance();
  694. }
  695. SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE );
  696. // Start following player after we're fully opened
  697. float flCurProgress = GetCycle();
  698. if ( flCurProgress >= 0.99f )
  699. {
  700. SetThink( NULL );
  701. SetNextThink( TICK_NEVER_THINK );
  702. }
  703. }
  704. //-----------------------------------------------------------------------------
  705. // Purpose:
  706. // Output : void SyncPoseToAimAngles
  707. //-----------------------------------------------------------------------------
  708. void CNPC_RocketTurret::SyncPoseToAimAngles ( void )
  709. {
  710. QAngle localAngles = TransformAnglesToLocalSpace( m_vecCurrentAngles.Get(), EntityToWorldTransform() );
  711. // Update pitch
  712. SetPoseParameter( m_iPosePitch, localAngles.x );
  713. // Update yaw -- NOTE: This yaw movement is screwy for this model, we must invert the yaw delta and also skew an extra 90 deg to
  714. // get the 'forward face' of the turret to match up with the look direction. If the model and it's pose parameters change, this will be wrong.
  715. SetPoseParameter( m_iPoseYaw, AngleNormalize( -localAngles.y - 90 ) );
  716. InvalidateBoneCache();
  717. }
  718. //-----------------------------------------------------------------------------
  719. // Purpose: Causes the turret to face its desired angles
  720. // Returns distance current and goal angles the angles in degrees.
  721. //-----------------------------------------------------------------------------
  722. float CNPC_RocketTurret::UpdateFacing( void )
  723. {
  724. Quaternion qtCurrent ( m_vecCurrentAngles.Get() );
  725. Quaternion qtGoal ( m_vecGoalAngles );
  726. Quaternion qtOut;
  727. float flDiff = QuaternionAngleDiff( qtCurrent, qtGoal );
  728. // 1/10th degree is all the granularity we need, gives rocket player hit box width accuracy at 18k game units.
  729. if ( flDiff < 0.1 )
  730. return flDiff;
  731. // Slerp 5% of the way to goal (distance dependant speed, but torque minimial and no euler wrapping issues).
  732. QuaternionSlerp( qtCurrent, qtGoal, 0.05, qtOut );
  733. QAngle vNewAngles;
  734. QuaternionAngles( qtOut, vNewAngles );
  735. m_vecCurrentAngles = vNewAngles;
  736. SyncPoseToAimAngles();
  737. return flDiff;
  738. }
  739. //-----------------------------------------------------------------------------
  740. // Purpose: Tests if this prop's front point will have direct line of sight to it's target entity once the pose parameters are set to face it
  741. // Input : vAimPoint - The point to aim at
  742. // Output : Returns true if target is in direct line of sight, false otherwise.
  743. //-----------------------------------------------------------------------------
  744. bool CNPC_RocketTurret::TestLOS( const Vector& vAimPoint )
  745. {
  746. // Snap to face (for accurate traces)
  747. QAngle vecOldAngles = m_vecCurrentAngles.m_Value;
  748. Vector vecToAimPoint = vAimPoint - EyePosition();
  749. VectorAngles( vecToAimPoint, m_vecCurrentAngles.m_Value );
  750. SyncPoseToAimAngles();
  751. Vector vFaceOrigin = EyePosition();
  752. trace_t trTarget;
  753. Ray_t ray;
  754. ray.Init( vFaceOrigin, vAimPoint );
  755. ray.m_IsRay = true;
  756. // This aim point does hit target, now make sure there are no blocking objects in the way
  757. CTraceFilterSimple filter ( this, COLLISION_GROUP_NONE );
  758. UTIL_Portal_TraceRay( ray, MASK_VISIBLE_AND_NPCS, &filter, &trTarget, false );
  759. // Set model back to current facing
  760. m_vecCurrentAngles = vecOldAngles;
  761. SyncPoseToAimAngles();
  762. return ( trTarget.m_pEnt == GetEnemy() );
  763. }
  764. //-----------------------------------------------------------------------------
  765. // Purpose: Tests all portals in the turret's vis for possible routes to see it's target point
  766. // Input : pOutVec - The location to aim at in order to hit the target ent, choosing least rotation if multiple
  767. // bConsiderNonPortalAimPoint - Output in pOutVec the non portal (direct) aimpoint if it requires the least rotation
  768. // Output : Returns true on success, false on failure.
  769. //-----------------------------------------------------------------------------
  770. bool CNPC_RocketTurret::TestPortalsForLOS( Vector* pOutVec, bool bConsiderNonPortalAimPoint = false )
  771. {
  772. // Aim at the target through the world
  773. CBaseEntity* pTarget = GetEnemy();
  774. if ( !pTarget )
  775. {
  776. return false;
  777. }
  778. Vector vAimPoint = pTarget->GetAbsOrigin() + (pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs()) * 0.5f;
  779. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  780. if( iPortalCount == 0 )
  781. {
  782. *pOutVec = vAimPoint;
  783. return false;
  784. }
  785. Vector vCurAim;
  786. AngleVectors( m_vecCurrentAngles.m_Value, &vCurAim );
  787. vCurAim.NormalizeInPlace();
  788. CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
  789. Vector *portalAimPoints = (Vector *)stackalloc( sizeof( Vector ) * iPortalCount );
  790. bool *bUsable = (bool *)stackalloc( sizeof( bool ) * iPortalCount );
  791. float *fPortalDot = (float *)stackalloc( sizeof( float ) * iPortalCount );
  792. // Test through any active portals: This may be a shorter distance to the target
  793. for( int i = 0; i != iPortalCount; ++i )
  794. {
  795. CProp_Portal *pTempPortal = pPortals[i];
  796. if( !pTempPortal->m_bActivated ||
  797. (pTempPortal->m_hLinkedPortal.Get() == NULL) )
  798. {
  799. //portalAimPoints[i] = vec3_invalid;
  800. bUsable[i] = false;
  801. continue;
  802. }
  803. bUsable[i] = FindAimPointThroughPortal( pPortals[ i ], &portalAimPoints[ i ] );
  804. if ( 1 )
  805. {
  806. QAngle goalAngles;
  807. Vector vecToEnemy = portalAimPoints[ i ] - EyePosition();
  808. vecToEnemy.NormalizeInPlace();
  809. // This value is for choosing the easiest aim point for the turret to see through.
  810. // 'Easiest' is the least rotation needed.
  811. fPortalDot[i] = DotProduct( vecToEnemy, vCurAim );
  812. }
  813. }
  814. int iCountPortalsThatSeeTarget = 0;
  815. float fHighestDot = -1.0;
  816. if ( bConsiderNonPortalAimPoint )
  817. {
  818. QAngle enemyRotToFace;
  819. Vector vecToEnemy = vAimPoint - EyePosition();
  820. vecToEnemy.NormalizeInPlace();
  821. fHighestDot = DotProduct( vecToEnemy, vCurAim );
  822. }
  823. // Compare aim points, use the closest aim point which has direct LOS
  824. for( int i = 0; i != iPortalCount; ++i )
  825. {
  826. if( bUsable[i] )
  827. {
  828. // This aim point has direct LOS
  829. if ( TestLOS( portalAimPoints[ i ] ) && fHighestDot < fPortalDot[ i ] )
  830. {
  831. *pOutVec = portalAimPoints[ i ];
  832. fHighestDot = fPortalDot[ i ];
  833. ++iCountPortalsThatSeeTarget;
  834. }
  835. }
  836. }
  837. return (iCountPortalsThatSeeTarget != 0);
  838. }
  839. //-----------------------------------------------------------------------------
  840. // Purpose: Find the center of the target entity as seen through the specified portal
  841. // Input : pPortal - The portal to look through
  842. // Output : Vector& output point in world space where the target *appears* to be as seen through the portal
  843. //-----------------------------------------------------------------------------
  844. bool CNPC_RocketTurret::FindAimPointThroughPortal( const CProp_Portal* pPortal, Vector* pVecOut )
  845. {
  846. if ( pPortal && pPortal->m_bActivated )
  847. {
  848. CProp_Portal* pLinked = pPortal->m_hLinkedPortal.Get();
  849. CBaseEntity* pTarget = GetEnemy();
  850. // Require that the portal is facing towards the beam to test through it
  851. Vector vRocketToPortal, vPortalForward;
  852. VectorSubtract ( pPortal->GetAbsOrigin(), EyePosition(), vRocketToPortal );
  853. pPortal->GetVectors( &vPortalForward, NULL, NULL);
  854. float fDot = DotProduct( vRocketToPortal, vPortalForward );
  855. // Portal must be facing the turret, and have a linked partner
  856. if ( fDot < 0.0f && pLinked && pLinked->m_bActivated && pTarget )
  857. {
  858. VMatrix matToPortalView = pLinked->m_matrixThisToLinked;
  859. Vector vTargetAimPoint = pTarget->GetAbsOrigin() + (pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs()) * 0.5f;
  860. *pVecOut = matToPortalView * vTargetAimPoint;
  861. return true;
  862. }
  863. }
  864. // Bad portal pointer, not linked, no target or otherwise failed
  865. return false;
  866. }
  867. void CNPC_RocketTurret::LaserOn( void )
  868. {
  869. // Set Locked sprite
  870. m_iLaserState = 1;
  871. }
  872. void CNPC_RocketTurret::LaserOff( void )
  873. {
  874. // Set Locked sprite;
  875. m_iLaserState = 0;
  876. }
  877. //-----------------------------------------------------------------------------
  878. // Purpose: Allows a generic think function before the others are called
  879. // Input : state - which state the turret is currently in
  880. //-----------------------------------------------------------------------------
  881. bool CNPC_RocketTurret::PreThink( void )
  882. {
  883. StudioFrameAdvance();
  884. CheckPVSCondition();
  885. //Do not interrupt current think function
  886. return false;
  887. }
  888. //-----------------------------------------------------------------------------
  889. // Purpose: Toggle the turret's state
  890. //-----------------------------------------------------------------------------
  891. void CNPC_RocketTurret::Toggle( void )
  892. {
  893. //Toggle the state
  894. if ( m_bEnabled )
  895. {
  896. Disable();
  897. }
  898. else
  899. {
  900. Enable();
  901. }
  902. }
  903. //-----------------------------------------------------------------------------
  904. // Purpose: Enable the turret and deploy
  905. //-----------------------------------------------------------------------------
  906. void CNPC_RocketTurret::Enable( void )
  907. {
  908. if ( m_bEnabled )
  909. return;
  910. m_bEnabled = true;
  911. ResetSequence( LookupSequence("open") );
  912. SetThink( &CNPC_RocketTurret::OpeningThink );
  913. SetNextThink( gpGlobals->curtime + 0.05 );
  914. }
  915. //-----------------------------------------------------------------------------
  916. // Purpose: Retire the turret until enabled again
  917. //-----------------------------------------------------------------------------
  918. void CNPC_RocketTurret::Disable( void )
  919. {
  920. if ( !m_bEnabled )
  921. return;
  922. UpdateSkin( ROCKET_SKIN_IDLE );
  923. m_bEnabled = false;
  924. ResetSequence(LookupSequence("close"));
  925. SetThink( &CNPC_RocketTurret::ClosingThink );
  926. SetNextThink( gpGlobals->curtime + 0.05 );
  927. SetEnemy( NULL );
  928. }
  929. //-----------------------------------------------------------------------------
  930. // Purpose: Sets the enemy of this rocket
  931. // Input : pTarget - the enemy to set
  932. //-----------------------------------------------------------------------------
  933. void CNPC_RocketTurret::SetTarget( CBaseEntity* pTarget )
  934. {
  935. SetEnemy( pTarget );
  936. }
  937. //-----------------------------------------------------------------------------
  938. // Purpose: Explode and set death think
  939. //-----------------------------------------------------------------------------
  940. void CNPC_RocketTurret::Destroy( void )
  941. {
  942. SetThink( &CNPC_RocketTurret::DyingThink );
  943. SetNextThink( gpGlobals->curtime + 0.1f );
  944. }
  945. //-----------------------------------------------------------------------------
  946. // Purpose:
  947. //-----------------------------------------------------------------------------
  948. void CNPC_RocketTurret::InputToggle( inputdata_t &inputdata )
  949. {
  950. Toggle();
  951. }
  952. //-----------------------------------------------------------------------------
  953. // Purpose:
  954. //-----------------------------------------------------------------------------
  955. void CNPC_RocketTurret::InputEnable( inputdata_t &inputdata )
  956. {
  957. Enable();
  958. }
  959. //-----------------------------------------------------------------------------
  960. // Purpose:
  961. //-----------------------------------------------------------------------------
  962. void CNPC_RocketTurret::InputDisable( inputdata_t &inputdata )
  963. {
  964. Disable();
  965. }
  966. void CNPC_RocketTurret::InputSetTarget( inputdata_t &inputdata )
  967. {
  968. CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, NULL );
  969. SetTarget( pTarget );
  970. }
  971. //-----------------------------------------------------------------------------
  972. // Purpose: Plays some 'death' effects and sets the destroy think
  973. // Input : &inputdata -
  974. //-----------------------------------------------------------------------------
  975. void CNPC_RocketTurret::InputDestroy( inputdata_t &inputdata )
  976. {
  977. Destroy();
  978. }
  979. //-----------------------------------------------------------------------------
  980. // Projectile methods
  981. //-----------------------------------------------------------------------------
  982. void CRocket_Turret_Projectile::Spawn( void )
  983. {
  984. Precache();
  985. BaseClass::Spawn();
  986. SetTouch ( &CRocket_Turret_Projectile::MissileTouch );
  987. CreateSounds();
  988. }
  989. //-----------------------------------------------------------------------------
  990. // Purpose:
  991. // Input : *pOther -
  992. //-----------------------------------------------------------------------------
  993. void CRocket_Turret_Projectile::MissileTouch( CBaseEntity *pOther )
  994. {
  995. Assert( pOther );
  996. Vector vVel = GetAbsVelocity();
  997. // Touched a launcher, and is heading towards that launcher
  998. if ( FClassnameIs( pOther, "npc_rocket_turret" ) )
  999. {
  1000. Dissolve( NULL, gpGlobals->curtime + 0.1f, false, ENTITY_DISSOLVE_NORMAL );
  1001. Vector vBounceVel = Vector( -vVel.x, -vVel.y, 200 );
  1002. SetAbsVelocity ( vBounceVel * 0.1f );
  1003. QAngle vBounceAngles;
  1004. VectorAngles( vBounceVel, vBounceAngles );
  1005. SetAbsAngles ( vBounceAngles );
  1006. SetLocalAngularVelocity ( QAngle ( 180, 90, 45 ) );
  1007. UTIL_Remove ( m_hRocketTrail );
  1008. SetSolid ( SOLID_NONE );
  1009. if( m_hRocketTrail )
  1010. {
  1011. m_hRocketTrail->SetLifetime(0.1f);
  1012. m_hRocketTrail = NULL;
  1013. }
  1014. return;
  1015. }
  1016. // Don't touch triggers (but DO hit weapons)
  1017. if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON )
  1018. return;
  1019. Explode();
  1020. }
  1021. void CRocket_Turret_Projectile::Precache( void )
  1022. {
  1023. BaseClass::Precache();
  1024. PrecacheScriptSound( ROCKET_PROJECTILE_LOOPING_SOUND );
  1025. }
  1026. void CRocket_Turret_Projectile::NotifyLauncherOnDeath( void )
  1027. {
  1028. CNPC_RocketTurret* pLauncher = (CNPC_RocketTurret*)m_hLauncher.Get();
  1029. if ( pLauncher )
  1030. {
  1031. pLauncher->RocketDied();
  1032. }
  1033. }
  1034. // When teleported (usually by portal)
  1035. void CRocket_Turret_Projectile::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
  1036. {
  1037. // On teleport, we record a pointer to the portal we are arriving at
  1038. if ( eventType == NOTIFY_EVENT_TELEPORT )
  1039. {
  1040. // HACK: Clearing the owner allows collisions with launcher.
  1041. // Players have had trouble realizing a launcher's own rockets don't kill it
  1042. // because they didn't ever collide. We do this after a portal teleport so it avoids self-collisions on launch.
  1043. SetOwnerEntity( NULL );
  1044. // Restart smoke trail
  1045. UTIL_Remove( m_hRocketTrail );
  1046. m_hRocketTrail = NULL; // This shouldn't leak cause the pointer has been handed to the delete list
  1047. CreateSmokeTrail();
  1048. }
  1049. }
  1050. void CRocket_Turret_Projectile::SetLauncher ( EHANDLE hLauncher )
  1051. {
  1052. m_hLauncher = hLauncher;
  1053. }
  1054. void CRocket_Turret_Projectile::DoExplosion( void )
  1055. {
  1056. NotifyLauncherOnDeath();
  1057. StopLoopingSounds();
  1058. // Explode
  1059. ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), 200, 25,
  1060. SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 100.0f, this);
  1061. // Hackish: Knock turrets in the area
  1062. CBaseEntity* pTurretIter = NULL;
  1063. while ( (pTurretIter = gEntList.FindEntityByClassnameWithin( pTurretIter, "npc_portal_turret_floor", GetAbsOrigin(), 128 )) != NULL )
  1064. {
  1065. CTakeDamageInfo info( this, this, 200, DMG_BLAST );
  1066. info.SetDamagePosition( GetAbsOrigin() );
  1067. CalculateExplosiveDamageForce( &info, (pTurretIter->GetAbsOrigin() - GetAbsOrigin()), GetAbsOrigin() );
  1068. pTurretIter->VPhysicsTakeDamage( info );
  1069. }
  1070. }
  1071. void CRocket_Turret_Projectile::CreateSounds()
  1072. {
  1073. if (!m_pAmbientSound)
  1074. {
  1075. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1076. CPASAttenuationFilter filter( this );
  1077. m_pAmbientSound = controller.SoundCreate( filter, entindex(), ROCKET_PROJECTILE_LOOPING_SOUND );
  1078. controller.Play( m_pAmbientSound, 1.0, 100 );
  1079. }
  1080. }
  1081. void CRocket_Turret_Projectile::StopLoopingSounds()
  1082. {
  1083. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1084. controller.SoundDestroy( m_pAmbientSound );
  1085. m_pAmbientSound = NULL;
  1086. BaseClass::StopLoopingSounds();
  1087. }
  1088. void CRocket_Turret_Projectile::CreateSmokeTrail( void )
  1089. {
  1090. if ( m_hRocketTrail )
  1091. return;
  1092. // Smoke trail.
  1093. if ( (m_hRocketTrail = RocketTrail::CreateRocketTrail()) != NULL )
  1094. {
  1095. m_hRocketTrail->m_Opacity = 0.2f;
  1096. m_hRocketTrail->m_SpawnRate = 100;
  1097. m_hRocketTrail->m_ParticleLifetime = 0.8f;
  1098. m_hRocketTrail->m_StartColor.Init( 0.65f, 0.65f , 0.65f );
  1099. m_hRocketTrail->m_EndColor.Init( 0.0, 0.0, 0.0 );
  1100. m_hRocketTrail->m_StartSize = 8;
  1101. m_hRocketTrail->m_EndSize = 32;
  1102. m_hRocketTrail->m_SpawnRadius = 4;
  1103. m_hRocketTrail->m_MinSpeed = 2;
  1104. m_hRocketTrail->m_MaxSpeed = 16;
  1105. m_hRocketTrail->SetLifetime( 999 );
  1106. m_hRocketTrail->FollowEntity( this, "0" );
  1107. }
  1108. }
  1109. static void fire_rocket_projectile_f( void )
  1110. {
  1111. CBasePlayer *pPlayer = (CBasePlayer *)UTIL_GetCommandClient();
  1112. Vector ptEyes, vForward;
  1113. QAngle vLookAng;
  1114. ptEyes = pPlayer->EyePosition();
  1115. pPlayer->EyeVectors( &vForward );
  1116. vLookAng = pPlayer->EyeAngles();
  1117. CRocket_Turret_Projectile *pRocket = (CRocket_Turret_Projectile *) CBaseEntity::Create( "rocket_turret_projectile", ptEyes, vLookAng, pPlayer );
  1118. if ( !pRocket )
  1119. return;
  1120. pRocket->SetThink( NULL );
  1121. pRocket->SetMoveType( MOVETYPE_FLY );
  1122. pRocket->SetModel( ROCKET_TURRET_PROJECTILE_NAME );
  1123. UTIL_SetSize( pRocket, vec3_origin, vec3_origin );
  1124. pRocket->CreateSmokeTrail();
  1125. pRocket->SetAbsVelocity( vForward * 550 );
  1126. pRocket->SetLauncher ( NULL );
  1127. }
  1128. ConCommand fire_rocket_projectile( "fire_rocket_projectile", fire_rocket_projectile_f, "Fires a rocket turret projectile from the player's eyes for testing.", FCVAR_CHEAT );