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.

2330 lines
67 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "npc_turret_floor.h"
  9. #include "ai_senses.h"
  10. #include "ai_memory.h"
  11. #include "engine/IEngineSound.h"
  12. #include "ammodef.h"
  13. #include "hl2/hl2_player.h"
  14. #include "soundenvelope.h"
  15. #include "physics_saverestore.h"
  16. #include "IEffects.h"
  17. #include "basehlcombatweapon_shared.h"
  18. #include "phys_controller.h"
  19. #include "ai_interactions.h"
  20. #include "Sprite.h"
  21. #include "beam_shared.h"
  22. #include "props.h"
  23. #include "particle_parse.h"
  24. #ifdef PORTAL
  25. #include "prop_portal_shared.h"
  26. #include "portal_util_shared.h"
  27. #endif
  28. // memdbgon must be the last include file in a .cpp file!!!
  29. #include "tier0/memdbgon.h"
  30. const char *GetMassEquivalent(float flMass);
  31. #define DISABLE_SHOT 0
  32. //Debug visualization
  33. ConVar g_debug_turret( "g_debug_turret", "0" );
  34. extern ConVar physcannon_tracelength;
  35. // Interactions
  36. int g_interactionTurretStillStanding = 0;
  37. float CNPC_FloorTurret::fMaxTipControllerVelocity = 300.0f * 300.0f;
  38. float CNPC_FloorTurret::fMaxTipControllerAngularVelocity = 90.0f * 90.0f;
  39. #define LASER_BEAM_SPRITE "effects/laser1.vmt"
  40. #define FLOOR_TURRET_MODEL "models/combine_turrets/floor_turret.mdl"
  41. #define FLOOR_TURRET_MODEL_CITIZEN "models/combine_turrets/citizen_turret.mdl"
  42. #define FLOOR_TURRET_GLOW_SPRITE "sprites/glow1.vmt"
  43. // #define FLOOR_TURRET_BC_YAW "aim_yaw"
  44. // #define FLOOR_TURRET_BC_PITCH "aim_pitch"
  45. #define FLOOR_TURRET_RANGE 1200
  46. #define FLOOR_TURRET_MAX_WAIT 5
  47. #define FLOOR_TURRET_SHORT_WAIT 2.0 // Used for FAST_RETIRE spawnflag
  48. #define FLOOR_TURRET_PING_TIME 1.0f //LPB!!
  49. #define FLOOR_TURRET_VOICE_PITCH_LOW 45
  50. #define FLOOR_TURRET_VOICE_PITCH_HIGH 100
  51. //Aiming variables
  52. #define FLOOR_TURRET_MAX_NOHARM_PERIOD 0.0f
  53. #define FLOOR_TURRET_MAX_GRACE_PERIOD 3.0f
  54. //Activities
  55. int ACT_FLOOR_TURRET_OPEN;
  56. int ACT_FLOOR_TURRET_CLOSE;
  57. int ACT_FLOOR_TURRET_OPEN_IDLE;
  58. int ACT_FLOOR_TURRET_CLOSED_IDLE;
  59. int ACT_FLOOR_TURRET_FIRE;
  60. //Datatable
  61. BEGIN_DATADESC( CNPC_FloorTurret )
  62. DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
  63. DEFINE_FIELD( m_bAutoStart, FIELD_BOOLEAN ),
  64. DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
  65. DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ),
  66. DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
  67. DEFINE_FIELD( m_bNoAlarmSounds, FIELD_BOOLEAN ),
  68. DEFINE_FIELD( m_flShotTime, FIELD_TIME ),
  69. DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
  70. DEFINE_FIELD( m_flThrashTime, FIELD_TIME ),
  71. DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
  72. DEFINE_FIELD( m_flNextActivateSoundTime, FIELD_TIME ),
  73. DEFINE_FIELD( m_bCarriedByPlayer, FIELD_BOOLEAN ),
  74. DEFINE_FIELD( m_bUseCarryAngles, FIELD_BOOLEAN ),
  75. DEFINE_FIELD( m_flPlayerDropTime, FIELD_TIME ),
  76. DEFINE_FIELD( m_hLastNPCToKickMe, FIELD_EHANDLE ),
  77. DEFINE_FIELD( m_flKnockOverFailedTime, FIELD_TIME ),
  78. DEFINE_FIELD( m_flDestructStartTime, FIELD_TIME ),
  79. DEFINE_FIELD( m_hFizzleEffect, FIELD_EHANDLE ),
  80. DEFINE_FIELD( m_vecGoalAngles,FIELD_VECTOR ),
  81. DEFINE_FIELD( m_iEyeAttachment, FIELD_INTEGER ),
  82. DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ),
  83. DEFINE_FIELD( m_iEyeState, FIELD_INTEGER ),
  84. DEFINE_FIELD( m_hEyeGlow, FIELD_EHANDLE ),
  85. DEFINE_FIELD( m_pMotionController,FIELD_EHANDLE),
  86. DEFINE_FIELD( m_vecEnemyLKP, FIELD_VECTOR ),
  87. DEFINE_FIELD( m_hLaser, FIELD_EHANDLE ),
  88. DEFINE_FIELD( m_bSelfDestructing, FIELD_BOOLEAN ),
  89. DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
  90. DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
  91. DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN ),
  92. DEFINE_KEYFIELD( m_iKeySkin, FIELD_INTEGER, "SkinNumber" ),
  93. DEFINE_THINKFUNC( Retire ),
  94. DEFINE_THINKFUNC( Deploy ),
  95. DEFINE_THINKFUNC( ActiveThink ),
  96. DEFINE_THINKFUNC( SearchThink ),
  97. DEFINE_THINKFUNC( AutoSearchThink ),
  98. DEFINE_THINKFUNC( TippedThink ),
  99. DEFINE_THINKFUNC( InactiveThink ),
  100. DEFINE_THINKFUNC( SuppressThink ),
  101. DEFINE_THINKFUNC( DisabledThink ),
  102. DEFINE_THINKFUNC( SelfDestructThink ),
  103. DEFINE_THINKFUNC( BreakThink ),
  104. DEFINE_USEFUNC( ToggleUse ),
  105. // Inputs
  106. DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
  107. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  108. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  109. DEFINE_INPUTFUNC( FIELD_VOID, "DepleteAmmo", InputDepleteAmmo ),
  110. DEFINE_INPUTFUNC( FIELD_VOID, "RestoreAmmo", InputRestoreAmmo ),
  111. DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
  112. DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ),
  113. DEFINE_OUTPUT( m_OnRetire, "OnRetire" ),
  114. DEFINE_OUTPUT( m_OnTipped, "OnTipped" ),
  115. DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),
  116. DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
  117. DEFINE_BASENPCINTERACTABLE_DATADESC(),
  118. // DEFINE_FIELD( m_ShotSounds, FIELD_SHORT ),
  119. END_DATADESC()
  120. LINK_ENTITY_TO_CLASS( npc_turret_floor, CNPC_FloorTurret );
  121. //-----------------------------------------------------------------------------
  122. // Constructor
  123. //-----------------------------------------------------------------------------
  124. CNPC_FloorTurret::CNPC_FloorTurret( void ) :
  125. m_bActive( false ),
  126. m_hEyeGlow( NULL ),
  127. m_hLaser( NULL ),
  128. m_iAmmoType( -1 ),
  129. m_bAutoStart( false ),
  130. m_flPingTime( 0.0f ),
  131. m_flNextActivateSoundTime( 0.0f ),
  132. m_bCarriedByPlayer( false ),
  133. m_bUseCarryAngles( false ),
  134. m_flPlayerDropTime( 0.0f ),
  135. m_flShotTime( 0.0f ),
  136. m_flLastSight( 0.0f ),
  137. m_bBlinkState( false ),
  138. m_flThrashTime( 0.0f ),
  139. m_pMotionController( NULL ),
  140. m_bEnabled( false ),
  141. m_bSelfDestructing( false )
  142. {
  143. m_vecGoalAngles.Init();
  144. m_vecEnemyLKP = vec3_invalid;
  145. }
  146. //-----------------------------------------------------------------------------
  147. // Purpose:
  148. //-----------------------------------------------------------------------------
  149. Class_T CNPC_FloorTurret::Classify( void )
  150. {
  151. if ( m_bEnabled )
  152. {
  153. // Hacked or friendly turrets don't attack players
  154. if( m_bHackedByAlyx || IsCitizenTurret() )
  155. return CLASS_PLAYER_ALLY;
  156. return CLASS_COMBINE;
  157. }
  158. return CLASS_NONE;
  159. }
  160. //-----------------------------------------------------------------------------
  161. // Purpose:
  162. //-----------------------------------------------------------------------------
  163. void CNPC_FloorTurret::UpdateOnRemove( void )
  164. {
  165. if ( m_pMotionController != NULL )
  166. {
  167. UTIL_Remove( m_pMotionController );
  168. m_pMotionController = NULL;
  169. }
  170. if ( m_hLaser != NULL )
  171. {
  172. UTIL_Remove( m_hLaser );
  173. m_hLaser = NULL;
  174. }
  175. if ( m_hEyeGlow != NULL )
  176. {
  177. UTIL_Remove( m_hEyeGlow );
  178. m_hEyeGlow = NULL;
  179. }
  180. BaseClass::UpdateOnRemove();
  181. }
  182. //-----------------------------------------------------------------------------
  183. // Purpose: Precache
  184. //-----------------------------------------------------------------------------
  185. void CNPC_FloorTurret::Precache( void )
  186. {
  187. const char *pModelName = STRING( GetModelName() );
  188. pModelName = ( pModelName && pModelName[ 0 ] != '\0' ) ? pModelName : FLOOR_TURRET_MODEL;
  189. PrecacheModel( pModelName );
  190. PrecacheModel( FLOOR_TURRET_GLOW_SPRITE );
  191. PropBreakablePrecacheAll( MAKE_STRING( pModelName ) );
  192. if ( IsCitizenTurret() )
  193. {
  194. PrecacheModel( LASER_BEAM_SPRITE );
  195. PrecacheScriptSound( "NPC_FloorTurret.AlarmPing");
  196. }
  197. // Activities
  198. ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_OPEN );
  199. ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_CLOSE );
  200. ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_CLOSED_IDLE );
  201. ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_OPEN_IDLE );
  202. ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_FIRE );
  203. PrecacheScriptSound( "NPC_FloorTurret.Retire" );
  204. PrecacheScriptSound( "NPC_FloorTurret.Deploy" );
  205. PrecacheScriptSound( "NPC_FloorTurret.Move" );
  206. PrecacheScriptSound( "NPC_Combine.WeaponBash" );
  207. PrecacheScriptSound( "NPC_FloorTurret.Activate" );
  208. PrecacheScriptSound( "NPC_FloorTurret.Alert" );
  209. m_ShotSounds = PrecacheScriptSound( "NPC_FloorTurret.ShotSounds" );
  210. PrecacheScriptSound( "NPC_FloorTurret.Die" );
  211. PrecacheScriptSound( "NPC_FloorTurret.Retract");
  212. PrecacheScriptSound( "NPC_FloorTurret.Alarm");
  213. PrecacheScriptSound( "NPC_FloorTurret.Ping");
  214. PrecacheScriptSound( "NPC_FloorTurret.DryFire");
  215. PrecacheScriptSound( "NPC_FloorTurret.Destruct" );
  216. #ifdef HL2_EPISODIC
  217. PrecacheParticleSystem( "explosion_turret_break" );
  218. #endif // HL2_EPISODIC
  219. BaseClass::Precache();
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose: Spawn the entity
  223. //-----------------------------------------------------------------------------
  224. void CNPC_FloorTurret::Spawn( void )
  225. {
  226. Precache();
  227. const char *pModelName = STRING( GetModelName() );
  228. SetModel( ( pModelName && pModelName[ 0 ] != '\0' ) ? pModelName : FLOOR_TURRET_MODEL );
  229. // If we're a citizen turret, we use a different skin
  230. if ( IsCitizenTurret() )
  231. {
  232. if (m_iKeySkin == 0)
  233. { // select a "random" skin -- rather than being truly random, use a static variable
  234. // to cycle through them evenly. The static won't be saved across save/load, but
  235. // frankly I don't care so much about that.
  236. // m_nSkin = random->RandomInt( 1, 4 );
  237. static unsigned int nextSkin = 0;
  238. m_nSkin = nextSkin + 1;
  239. // add one mod 4
  240. nextSkin = (nextSkin + 1) & 0x03;
  241. }
  242. else
  243. { // at least make sure that it's in the right range
  244. m_nSkin = clamp(m_iKeySkin,1,4);
  245. }
  246. }
  247. BaseClass::Spawn();
  248. SetBlocksLOS( false );
  249. m_HackedGunPos = Vector( 0, 0, 12.75 );
  250. SetViewOffset( EyeOffset( ACT_IDLE ) );
  251. m_flFieldOfView = 0.4f; // 60 degrees
  252. m_takedamage = DAMAGE_EVENTS_ONLY;
  253. m_iHealth = 100;
  254. m_iMaxHealth = 100;
  255. AddEFlags( EFL_NO_DISSOLVE );
  256. SetPoseParameter( m_poseAim_Yaw, 0 );
  257. SetPoseParameter( m_poseAim_Pitch, 0 );
  258. m_iAmmoType = GetAmmoDef()->Index( "PISTOL" );
  259. m_iMuzzleAttachment = LookupAttachment( "eyes" );
  260. m_iEyeAttachment = LookupAttachment( "light" );
  261. // FIXME: Do we ever need m_bAutoStart? (Sawyer)
  262. m_spawnflags |= SF_FLOOR_TURRET_AUTOACTIVATE;
  263. //Set our autostart state
  264. m_bAutoStart = !!( m_spawnflags & SF_FLOOR_TURRET_AUTOACTIVATE );
  265. m_bEnabled = ( ( m_spawnflags & SF_FLOOR_TURRET_STARTINACTIVE ) == false );
  266. //Do we start active?
  267. if ( m_bAutoStart && m_bEnabled )
  268. {
  269. SetThink( &CNPC_FloorTurret::AutoSearchThink );
  270. SetEyeState( TURRET_EYE_DORMANT );
  271. }
  272. else
  273. {
  274. SetThink( &CNPC_FloorTurret::DisabledThink );
  275. SetEyeState( TURRET_EYE_DISABLED );
  276. }
  277. // Start
  278. if ( OnSide() )
  279. {
  280. SetThink( &CNPC_FloorTurret::DisabledThink );
  281. SetEyeState( TURRET_EYE_DISABLED );
  282. }
  283. //Stagger our starting times
  284. SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ) );
  285. SetUse( &CNPC_FloorTurret::ToggleUse );
  286. // Don't allow us to skip animation setup because our attachments are critical to us!
  287. SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
  288. CreateVPhysics();
  289. SetState(NPC_STATE_IDLE);
  290. }
  291. //-----------------------------------------------------------------------------
  292. // Purpose:
  293. //-----------------------------------------------------------------------------
  294. void CNPC_FloorTurret::Activate( void )
  295. {
  296. BaseClass::Activate();
  297. // Force the eye state to the current state so that our glows are recreated after transitions
  298. SetEyeState( m_iEyeState );
  299. if ( !m_pMotionController )
  300. {
  301. // Create the motion controller
  302. m_pMotionController = CTurretTipController::CreateTipController( this );
  303. // Enable the controller
  304. if ( m_pMotionController != NULL )
  305. {
  306. m_pMotionController->Enable();
  307. }
  308. }
  309. }
  310. //-----------------------------------------------------------------------------
  311. bool CNPC_FloorTurret::CreateVPhysics( void )
  312. {
  313. //Spawn our physics hull
  314. if ( VPhysicsInitNormal( SOLID_VPHYSICS, 0, false ) == NULL )
  315. {
  316. DevMsg( "npc_turret_floor unable to spawn physics object!\n" );
  317. }
  318. return true;
  319. }
  320. //-----------------------------------------------------------------------------
  321. // Purpose: Retract and stop attacking
  322. //-----------------------------------------------------------------------------
  323. void CNPC_FloorTurret::Retire( void )
  324. {
  325. if ( PreThink( TURRET_RETIRING ) )
  326. return;
  327. //Level out the turret
  328. m_vecGoalAngles = GetAbsAngles();
  329. SetNextThink( gpGlobals->curtime + 0.05f );
  330. //Set ourselves to close
  331. if ( GetActivity() != ACT_FLOOR_TURRET_CLOSE )
  332. {
  333. //Set our visible state to dormant
  334. SetEyeState( TURRET_EYE_DORMANT );
  335. SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
  336. //If we're done moving to our desired facing, close up
  337. if ( UpdateFacing() == false )
  338. {
  339. SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSE );
  340. EmitSound( "NPC_FloorTurret.Retire" );
  341. //Notify of the retraction
  342. m_OnRetire.FireOutput( NULL, this );
  343. }
  344. }
  345. else if ( IsActivityFinished() )
  346. {
  347. m_bActive = false;
  348. m_flLastSight = 0;
  349. SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSED_IDLE );
  350. //Go back to auto searching
  351. if ( m_bAutoStart )
  352. {
  353. SetThink( &CNPC_FloorTurret::AutoSearchThink );
  354. SetNextThink( gpGlobals->curtime + 0.05f );
  355. }
  356. else
  357. {
  358. //Set our visible state to dormant
  359. SetEyeState( TURRET_EYE_DISABLED );
  360. SetThink( &CNPC_FloorTurret::DisabledThink );
  361. }
  362. }
  363. }
  364. //-----------------------------------------------------------------------------
  365. // Purpose: Deploy and start attacking
  366. //-----------------------------------------------------------------------------
  367. void CNPC_FloorTurret::Deploy( void )
  368. {
  369. if ( PreThink( TURRET_DEPLOYING ) )
  370. return;
  371. m_vecGoalAngles = GetAbsAngles();
  372. SetNextThink( gpGlobals->curtime + 0.05f );
  373. //Show we've seen a target
  374. SetEyeState( TURRET_EYE_SEE_TARGET );
  375. //Open if we're not already
  376. if ( GetActivity() != ACT_FLOOR_TURRET_OPEN )
  377. {
  378. m_bActive = true;
  379. SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN );
  380. EmitSound( "NPC_FloorTurret.Deploy" );
  381. //Notify we're deploying
  382. m_OnDeploy.FireOutput( NULL, this );
  383. }
  384. //If we're done, then start searching
  385. if ( IsActivityFinished() )
  386. {
  387. SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
  388. m_flShotTime = gpGlobals->curtime + 1.0f;
  389. m_flPlaybackRate = 0;
  390. SetThink( &CNPC_FloorTurret::SearchThink );
  391. EmitSound( "NPC_FloorTurret.Move" );
  392. }
  393. m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
  394. }
  395. //-----------------------------------------------------------------------------
  396. // Purpose:
  397. //-----------------------------------------------------------------------------
  398. void CNPC_FloorTurret::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  399. {
  400. m_hPhysicsAttacker = pPhysGunUser;
  401. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  402. // Drop our mass a lot so that we can be moved easily with +USE
  403. if ( reason != PUNTED_BY_CANNON )
  404. {
  405. Assert( VPhysicsGetObject() );
  406. m_bCarriedByPlayer = true;
  407. m_OnPhysGunPickup.FireOutput( this, this );
  408. // We want to use preferred carry angles if we're not nicely upright
  409. Vector vecToTurret = pPhysGunUser->GetAbsOrigin() - GetAbsOrigin();
  410. vecToTurret.z = 0;
  411. VectorNormalize( vecToTurret );
  412. // We want to use preferred carry angles if we're not nicely upright
  413. Vector forward, up;
  414. GetVectors( &forward, NULL, &up );
  415. bool bUpright = DotProduct( up, Vector(0,0,1) ) > 0.9f;
  416. bool bBehind = DotProduct( vecToTurret, forward ) < 0.85f;
  417. // Correct our angles only if we're not upright or we're mostly behind the turret
  418. if ( hl2_episodic.GetBool() )
  419. {
  420. m_bUseCarryAngles = ( bUpright == false || bBehind );
  421. }
  422. else
  423. {
  424. m_bUseCarryAngles = ( bUpright == false );
  425. }
  426. }
  427. // Clear out our last NPC to kick me, because it makes no sense now
  428. m_hLastNPCToKickMe = NULL;
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose:
  432. //-----------------------------------------------------------------------------
  433. void CNPC_FloorTurret::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  434. {
  435. m_hPhysicsAttacker = pPhysGunUser;
  436. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  437. m_bCarriedByPlayer = false;
  438. m_bUseCarryAngles = false;
  439. m_OnPhysGunDrop.FireOutput( this, this );
  440. // If this is a friendly turret, remember that it was just dropped
  441. if ( IRelationType( pPhysGunUser ) != D_HT )
  442. {
  443. m_flPlayerDropTime = gpGlobals->curtime + 2.0;
  444. }
  445. // Restore our mass to the original value
  446. Assert( VPhysicsGetObject() );
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose: Whether this should return carry angles
  450. // Output : Returns true on success, false on failure.
  451. //-----------------------------------------------------------------------------
  452. bool CNPC_FloorTurret::HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer )
  453. {
  454. // Don't use preferred angles on enemy turrets
  455. if ( IRelationType( pPlayer ) == D_HT )
  456. return false;
  457. return m_bUseCarryAngles;
  458. }
  459. //-----------------------------------------------------------------------------
  460. // Purpose:
  461. //-----------------------------------------------------------------------------
  462. bool CNPC_FloorTurret::OnAttemptPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  463. {
  464. // Prevent players pulling enemy turrets from afar if they're in front of the turret
  465. if ( reason == PICKED_UP_BY_CANNON && IRelationType( pPhysGunUser ) == D_HT )
  466. {
  467. Vector vecForward;
  468. GetVectors( &vecForward, NULL, NULL );
  469. Vector vecForce = (pPhysGunUser->GetAbsOrigin() - GetAbsOrigin());
  470. float flDistance = VectorNormalize( vecForce );
  471. // If it's over the physcannon tracelength, we're pulling it
  472. if ( flDistance > physcannon_tracelength.GetFloat() )
  473. {
  474. float flDot = DotProduct( vecForward, vecForce );
  475. if ( flDot > 0.5 )
  476. return false;
  477. }
  478. }
  479. return true;
  480. }
  481. //-----------------------------------------------------------------------------
  482. // Purpose:
  483. //-----------------------------------------------------------------------------
  484. bool CNPC_FloorTurret::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *sourceEnt)
  485. {
  486. if ( interactionType == g_interactionCombineBash )
  487. {
  488. // We've been bashed by a combine soldier. Remember who it was, if we haven't got an active kicker
  489. if ( !m_hLastNPCToKickMe )
  490. {
  491. m_hLastNPCToKickMe = sourceEnt;
  492. m_flKnockOverFailedTime = gpGlobals->curtime + 3.0;
  493. }
  494. // Get knocked away
  495. Vector forward, up;
  496. AngleVectors( sourceEnt->GetLocalAngles(), &forward, NULL, &up );
  497. ApplyAbsVelocityImpulse( forward * 100 + up * 50 );
  498. CTakeDamageInfo info( sourceEnt, sourceEnt, 30, DMG_CLUB );
  499. CalculateMeleeDamageForce( &info, forward, GetAbsOrigin() );
  500. TakeDamage( info );
  501. EmitSound( "NPC_Combine.WeaponBash" );
  502. return true;
  503. }
  504. return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
  505. }
  506. //-----------------------------------------------------------------------------
  507. // Purpose: Returns the speed at which the turret can face a target
  508. //-----------------------------------------------------------------------------
  509. float CNPC_FloorTurret::MaxYawSpeed( void )
  510. {
  511. //TODO: Scale by difficulty?
  512. return 360.0f;
  513. }
  514. //-----------------------------------------------------------------------------
  515. // Purpose: Return true if this turret was recently dropped by a player
  516. // Output : Returns true on success, false on failure.
  517. //-----------------------------------------------------------------------------
  518. bool CNPC_FloorTurret::WasJustDroppedByPlayer( void )
  519. {
  520. if ( m_flPlayerDropTime > gpGlobals->curtime )
  521. return true;
  522. return false;
  523. }
  524. //-----------------------------------------------------------------------------
  525. // Purpose: Causes the turret to face its desired angles
  526. //-----------------------------------------------------------------------------
  527. bool CNPC_FloorTurret::UpdateFacing( void )
  528. {
  529. bool bMoved = false;
  530. UpdateMuzzleMatrix();
  531. Vector vecGoalDir;
  532. AngleVectors( m_vecGoalAngles, &vecGoalDir );
  533. Vector vecGoalLocalDir;
  534. VectorIRotate( vecGoalDir, m_muzzleToWorld, vecGoalLocalDir );
  535. if ( g_debug_turret.GetBool() )
  536. {
  537. Vector vecMuzzle, vecMuzzleDir;
  538. MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
  539. MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
  540. NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 );
  541. NDebugOverlay::Cross3D( vecMuzzle+(vecMuzzleDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 );
  542. NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecMuzzleDir*256), 255, 255, 0, false, 0.05 );
  543. NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 );
  544. NDebugOverlay::Cross3D( vecMuzzle+(vecGoalDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 );
  545. NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecGoalDir*256), 255, 0, 0, false, 0.05 );
  546. }
  547. QAngle vecGoalLocalAngles;
  548. VectorAngles( vecGoalLocalDir, vecGoalLocalAngles );
  549. // Update pitch
  550. float flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.05f * MaxYawSpeed() ) );
  551. SetPoseParameter( m_poseAim_Pitch, GetPoseParameter( m_poseAim_Pitch ) + ( flDiff / 1.5f ) );
  552. if ( fabs( flDiff ) > 0.1f )
  553. {
  554. bMoved = true;
  555. }
  556. // Update yaw
  557. flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.05f * MaxYawSpeed() ) );
  558. SetPoseParameter( m_poseAim_Yaw, GetPoseParameter( m_poseAim_Yaw ) + ( flDiff / 1.5f ) );
  559. if ( fabs( flDiff ) > 0.1f )
  560. {
  561. bMoved = true;
  562. }
  563. // You're going to make decisions based on this info. So bump the bone cache after you calculate everything
  564. InvalidateBoneCache();
  565. return bMoved;
  566. }
  567. void CNPC_FloorTurret::DryFire( void )
  568. {
  569. EmitSound( "NPC_FloorTurret.DryFire");
  570. EmitSound( "NPC_FloorTurret.Activate" );
  571. if ( RandomFloat( 0, 1 ) > 0.5 )
  572. {
  573. m_flShotTime = gpGlobals->curtime + random->RandomFloat( 1, 2.5 );
  574. }
  575. else
  576. {
  577. m_flShotTime = gpGlobals->curtime;
  578. }
  579. }
  580. //-----------------------------------------------------------------------------
  581. // Purpose: Turret will continue to fire on a target's position when it loses sight of it
  582. //-----------------------------------------------------------------------------
  583. void CNPC_FloorTurret::SuppressThink( void )
  584. {
  585. //Allow descended classes a chance to do something before the think function
  586. if ( PreThink( TURRET_SUPPRESSING ) )
  587. return;
  588. //Update our think time
  589. SetNextThink( gpGlobals->curtime + 0.1f );
  590. // Look for a new enemy
  591. HackFindEnemy();
  592. //If we've acquired an enemy, start firing at it
  593. if ( !GetEnemy() )
  594. {
  595. SetThink( &CNPC_FloorTurret::ActiveThink );
  596. return;
  597. }
  598. //See if we're done suppressing
  599. if ( gpGlobals->curtime > m_flLastSight )
  600. {
  601. // Should we look for a new target?
  602. ClearEnemyMemory();
  603. SetEnemy( NULL );
  604. SetThink( &CNPC_FloorTurret::SearchThink );
  605. m_vecGoalAngles = GetAbsAngles();
  606. SpinDown();
  607. if ( m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE )
  608. {
  609. // Retire quickly in this case. (The case where we saw the player, but he hid again).
  610. m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT;
  611. }
  612. else
  613. {
  614. m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
  615. }
  616. return;
  617. }
  618. //Get our shot positions
  619. Vector vecMid = EyePosition();
  620. Vector vecMidEnemy = m_vecEnemyLKP;
  621. //Calculate dir and dist to enemy
  622. Vector vecDirToEnemy = vecMidEnemy - vecMid;
  623. //We want to look at the enemy's eyes so we don't jitter
  624. Vector vecDirToEnemyEyes = vecMidEnemy - vecMid;
  625. VectorNormalize( vecDirToEnemyEyes );
  626. QAngle vecAnglesToEnemy;
  627. VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
  628. //Draw debug info
  629. if ( g_debug_turret.GetBool() )
  630. {
  631. NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
  632. NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
  633. NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f );
  634. }
  635. if ( m_flShotTime < gpGlobals->curtime && m_vecEnemyLKP != vec3_invalid )
  636. {
  637. Vector vecMuzzle, vecMuzzleDir;
  638. UpdateMuzzleMatrix();
  639. MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
  640. MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
  641. //Fire the gun
  642. if ( DotProduct( vecDirToEnemy, vecMuzzleDir ) >= 0.9848 ) // 10 degree slop
  643. {
  644. if( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
  645. {
  646. DryFire();
  647. }
  648. else
  649. {
  650. ResetActivity();
  651. SetActivity( (Activity) ACT_FLOOR_TURRET_FIRE );
  652. //Fire the weapon
  653. #if !DISABLE_SHOT
  654. Shoot( vecMuzzle, vecMuzzleDir );
  655. #endif
  656. }
  657. }
  658. }
  659. else
  660. {
  661. SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
  662. }
  663. //If we can see our enemy, face it
  664. m_vecGoalAngles.y = vecAnglesToEnemy.y;
  665. m_vecGoalAngles.x = vecAnglesToEnemy.x;
  666. //Turn to face
  667. UpdateFacing();
  668. }
  669. //-----------------------------------------------------------------------------
  670. // Purpose: Allows the turret to fire on targets if they're visible
  671. //-----------------------------------------------------------------------------
  672. void CNPC_FloorTurret::ActiveThink( void )
  673. {
  674. //Allow descended classes a chance to do something before the think function
  675. if ( PreThink( TURRET_ACTIVE ) )
  676. return;
  677. HackFindEnemy();
  678. //Update our think time
  679. SetNextThink( gpGlobals->curtime + 0.1f );
  680. //If we've become inactive, go back to searching
  681. if ( ( m_bActive == false ) || ( GetEnemy() == NULL ) )
  682. {
  683. SetEnemy( NULL );
  684. m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
  685. SetThink( &CNPC_FloorTurret::SearchThink );
  686. m_vecGoalAngles = GetAbsAngles();
  687. return;
  688. }
  689. //Get our shot positions
  690. Vector vecMid = EyePosition();
  691. Vector vecMidEnemy = GetEnemy()->BodyTarget( vecMid );
  692. // Store off our last seen location so we can suppress it later
  693. m_vecEnemyLKP = vecMidEnemy;
  694. //Look for our current enemy
  695. bool bEnemyInFOV = FInViewCone( GetEnemy() );
  696. bool bEnemyVisible = FVisible( GetEnemy() ) && GetEnemy()->IsAlive();
  697. // Robin: This is a hack to get around the fact that the muzzle for the turret
  698. // is outside it's vcollide. This means that if it leans against a thin wall,
  699. // the muzzle can be on the other side of the wall, where it's then able to see
  700. // and shoot at targets. This check ensures that nothing has come between the
  701. // center of the turret and the muzzle.
  702. if ( bEnemyVisible )
  703. {
  704. trace_t tr;
  705. Vector vecCenter;
  706. CollisionProp()->CollisionToWorldSpace( Vector(0,0,52), &vecCenter );
  707. UTIL_TraceLine( vecCenter, vecMid, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  708. if ( tr.fraction != 1.0 )
  709. {
  710. bEnemyVisible = false;
  711. }
  712. }
  713. //Calculate dir and dist to enemy
  714. Vector vecDirToEnemy = vecMidEnemy - vecMid;
  715. float flDistToEnemy = VectorNormalize( vecDirToEnemy );
  716. //Draw debug info
  717. if ( g_debug_turret.GetBool() )
  718. {
  719. NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
  720. NDebugOverlay::Cross3D( GetEnemy()->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
  721. NDebugOverlay::Line( vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05 );
  722. NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
  723. NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
  724. NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f );
  725. }
  726. //See if they're past our FOV of attack
  727. if ( bEnemyInFOV == false )
  728. {
  729. // Should we look for a new target?
  730. ClearEnemyMemory();
  731. SetEnemy( NULL );
  732. if ( m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE )
  733. {
  734. // Retire quickly in this case. (The case where we saw the player, but he hid again).
  735. m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT;
  736. }
  737. else
  738. {
  739. m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
  740. }
  741. SetThink( &CNPC_FloorTurret::SearchThink );
  742. m_vecGoalAngles = GetAbsAngles();
  743. SpinDown();
  744. return;
  745. }
  746. //Current enemy is not visible
  747. if ( ( bEnemyVisible == false ) || ( flDistToEnemy > FLOOR_TURRET_RANGE ))
  748. {
  749. m_flLastSight = gpGlobals->curtime + 2.0f;
  750. ClearEnemyMemory();
  751. SetEnemy( NULL );
  752. SetThink( &CNPC_FloorTurret::SuppressThink );
  753. return;
  754. }
  755. if ( g_debug_turret.GetBool() )
  756. {
  757. Vector vecMuzzle, vecMuzzleDir;
  758. UpdateMuzzleMatrix();
  759. MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
  760. MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
  761. // Visualize vertical firing ranges
  762. for ( int i = 0; i < 4; i++ )
  763. {
  764. QAngle angMaxDownPitch = GetAbsAngles();
  765. switch( i )
  766. {
  767. case 0: angMaxDownPitch.x -= 15; break;
  768. case 1: angMaxDownPitch.x += 15; break;
  769. case 2: angMaxDownPitch.x -= 25; break;
  770. case 3: angMaxDownPitch.x += 25; break;
  771. default:
  772. break;
  773. }
  774. Vector vecMaxDownPitch;
  775. AngleVectors( angMaxDownPitch, &vecMaxDownPitch );
  776. NDebugOverlay::Line( vecMuzzle, vecMuzzle + (vecMaxDownPitch*256), 255, 255, 255, false, 0.1 );
  777. }
  778. }
  779. if ( m_flShotTime < gpGlobals->curtime )
  780. {
  781. Vector vecMuzzle, vecMuzzleDir;
  782. UpdateMuzzleMatrix();
  783. MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
  784. MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
  785. Vector2D vecDirToEnemy2D = vecDirToEnemy.AsVector2D();
  786. Vector2D vecMuzzleDir2D = vecMuzzleDir.AsVector2D();
  787. bool bCanShoot = true;
  788. float minCos3d = DOT_10DEGREE; // 10 degrees slop
  789. if ( flDistToEnemy < 60.0 )
  790. {
  791. vecDirToEnemy2D.NormalizeInPlace();
  792. vecMuzzleDir2D.NormalizeInPlace();
  793. bCanShoot = ( vecDirToEnemy2D.Dot(vecMuzzleDir2D) >= DOT_10DEGREE );
  794. minCos3d = 0.7071; // 45 degrees
  795. }
  796. //Fire the gun
  797. if ( bCanShoot ) // 10 degree slop XY
  798. {
  799. float dot3d = DotProduct( vecDirToEnemy, vecMuzzleDir );
  800. if( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
  801. {
  802. DryFire();
  803. }
  804. else
  805. {
  806. if ( dot3d >= minCos3d )
  807. {
  808. ResetActivity();
  809. SetActivity( (Activity) ACT_FLOOR_TURRET_FIRE );
  810. //Fire the weapon
  811. #if !DISABLE_SHOT
  812. Shoot( vecMuzzle, vecMuzzleDir, (dot3d < DOT_10DEGREE) );
  813. #endif
  814. }
  815. }
  816. }
  817. }
  818. else
  819. {
  820. SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
  821. }
  822. //If we can see our enemy, face it
  823. if ( bEnemyVisible )
  824. {
  825. //We want to look at the enemy's eyes so we don't jitter
  826. Vector vecDirToEnemyEyes = GetEnemy()->WorldSpaceCenter() - vecMid;
  827. VectorNormalize( vecDirToEnemyEyes );
  828. QAngle vecAnglesToEnemy;
  829. VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
  830. m_vecGoalAngles.y = vecAnglesToEnemy.y;
  831. m_vecGoalAngles.x = vecAnglesToEnemy.x;
  832. }
  833. //Turn to face
  834. UpdateFacing();
  835. }
  836. //-----------------------------------------------------------------------------
  837. // Purpose: Target doesn't exist or has eluded us, so search for one
  838. //-----------------------------------------------------------------------------
  839. void CNPC_FloorTurret::SearchThink( void )
  840. {
  841. //Allow descended classes a chance to do something before the think function
  842. if ( PreThink( TURRET_SEARCHING ) )
  843. return;
  844. SetNextThink( gpGlobals->curtime + 0.05f );
  845. SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
  846. //If our enemy has died, pick a new enemy
  847. if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) )
  848. {
  849. SetEnemy( NULL );
  850. }
  851. //Acquire the target
  852. if ( GetEnemy() == NULL )
  853. {
  854. HackFindEnemy();
  855. }
  856. //If we've found a target, spin up the barrel and start to attack
  857. if ( GetEnemy() != NULL )
  858. {
  859. //Give players a grace period
  860. if ( GetEnemy()->IsPlayer() )
  861. {
  862. m_flShotTime = gpGlobals->curtime + 0.5f;
  863. }
  864. else
  865. {
  866. m_flShotTime = gpGlobals->curtime + 0.1f;
  867. }
  868. m_flLastSight = 0;
  869. SetThink( &CNPC_FloorTurret::ActiveThink );
  870. SetEyeState( TURRET_EYE_SEE_TARGET );
  871. SpinUp();
  872. if ( gpGlobals->curtime > m_flNextActivateSoundTime )
  873. {
  874. EmitSound( "NPC_FloorTurret.Activate" );
  875. m_flNextActivateSoundTime = gpGlobals->curtime + 3.0;
  876. }
  877. return;
  878. }
  879. //Are we out of time and need to retract?
  880. if ( gpGlobals->curtime > m_flLastSight )
  881. {
  882. //Before we retrace, make sure that we are spun down.
  883. m_flLastSight = 0;
  884. SetThink( &CNPC_FloorTurret::Retire );
  885. return;
  886. }
  887. //Display that we're scanning
  888. m_vecGoalAngles.x = GetAbsAngles().x + ( sin( gpGlobals->curtime * 1.0f ) * 15.0f );
  889. m_vecGoalAngles.y = GetAbsAngles().y + ( sin( gpGlobals->curtime * 2.0f ) * 60.0f );
  890. //Turn and ping
  891. UpdateFacing();
  892. Ping();
  893. }
  894. //-----------------------------------------------------------------------------
  895. // Purpose: Watch for a target to wander into our view
  896. //-----------------------------------------------------------------------------
  897. void CNPC_FloorTurret::AutoSearchThink( void )
  898. {
  899. //Allow descended classes a chance to do something before the think function
  900. if ( PreThink( TURRET_AUTO_SEARCHING ) )
  901. return;
  902. //Spread out our thinking
  903. SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.2f, 0.4f ) );
  904. //If the enemy is dead, find a new one
  905. if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) )
  906. {
  907. SetEnemy( NULL );
  908. }
  909. //Acquire Target
  910. if ( GetEnemy() == NULL )
  911. {
  912. HackFindEnemy();
  913. }
  914. //Deploy if we've got an active target
  915. if ( GetEnemy() != NULL )
  916. {
  917. SetThink( &CNPC_FloorTurret::Deploy );
  918. if ( !m_bNoAlarmSounds )
  919. {
  920. EmitSound( "NPC_FloorTurret.Alert" );
  921. }
  922. }
  923. }
  924. //-----------------------------------------------------------------------------
  925. // Purpose: Fire!
  926. //-----------------------------------------------------------------------------
  927. void CNPC_FloorTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy, bool bStrict )
  928. {
  929. FireBulletsInfo_t info;
  930. if ( !bStrict && GetEnemy() != NULL )
  931. {
  932. Vector vecDir = GetActualShootTrajectory( vecSrc );
  933. info.m_vecSrc = vecSrc;
  934. info.m_vecDirShooting = vecDir;
  935. info.m_iTracerFreq = 1;
  936. info.m_iShots = 1;
  937. info.m_pAttacker = this;
  938. info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
  939. info.m_flDistance = MAX_COORD_RANGE;
  940. info.m_iAmmoType = m_iAmmoType;
  941. }
  942. else
  943. {
  944. info.m_vecSrc = vecSrc;
  945. info.m_vecDirShooting = vecDirToEnemy;
  946. info.m_iTracerFreq = 1;
  947. info.m_iShots = 1;
  948. info.m_pAttacker = this;
  949. info.m_vecSpread = GetAttackSpread( NULL, GetEnemy() );
  950. info.m_flDistance = MAX_COORD_RANGE;
  951. info.m_iAmmoType = m_iAmmoType;
  952. }
  953. FireBullets( info );
  954. EmitSound( "NPC_FloorTurret.ShotSounds", m_ShotSounds );
  955. DoMuzzleFlash();
  956. }
  957. //-----------------------------------------------------------------------------
  958. // Purpose:
  959. // Input : *pEnemy -
  960. // Output : Returns true on success, false on failure.
  961. //-----------------------------------------------------------------------------
  962. bool CNPC_FloorTurret::IsValidEnemy( CBaseEntity *pEnemy )
  963. {
  964. if ( m_NPCState == NPC_STATE_DEAD )
  965. return false;
  966. // Don't shoot at other turrets.
  967. if ( pEnemy->m_iClassname == m_iClassname )
  968. return false;
  969. // If our eye is stuck in something, don't shoot
  970. if ( UTIL_PointContents(EyePosition()) & MASK_SHOT )
  971. return false;
  972. // Turrets have limited vertical aim capability
  973. // - Can only aim +-15 degrees, + the 10 degree slop they're allowed.
  974. Vector vEnemyPos = pEnemy->EyePosition();
  975. #ifdef PORTAL
  976. if ( !FInViewCone( pEnemy ) || !FVisible( pEnemy ) )
  977. {
  978. CProp_Portal *pPortal = FInViewConeThroughPortal( pEnemy );
  979. if ( pPortal )
  980. {
  981. // Translate our target across the portal
  982. UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vEnemyPos, vEnemyPos );
  983. }
  984. }
  985. #endif
  986. Vector los = ( vEnemyPos - EyePosition() );
  987. QAngle angleToTarget;
  988. VectorAngles( los, angleToTarget );
  989. float flZDiff = fabs( AngleNormalize( angleToTarget.x - GetAbsAngles().x) );
  990. if ( flZDiff > 28.0f && los.LengthSqr() > 4096.0f )
  991. return false;
  992. return BaseClass::IsValidEnemy( pEnemy );
  993. }
  994. //-----------------------------------------------------------------------------
  995. // Purpose:
  996. // Input : *pEnemy -
  997. // Output : Returns true on success, false on failure.
  998. //-----------------------------------------------------------------------------
  999. bool CNPC_FloorTurret::CanBeAnEnemyOf( CBaseEntity *pEnemy )
  1000. {
  1001. // If we're out of ammo, make friendly companions ignore us
  1002. if ( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
  1003. {
  1004. if ( pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL )
  1005. return false;
  1006. }
  1007. // If we're on the side, we're never anyone's enemy
  1008. if ( OnSide() )
  1009. return false;
  1010. return BaseClass::CanBeAnEnemyOf( pEnemy );
  1011. }
  1012. //-----------------------------------------------------------------------------
  1013. // Purpose: The turret has been tipped over and will thrash for awhile
  1014. //-----------------------------------------------------------------------------
  1015. void CNPC_FloorTurret::TippedThink( void )
  1016. {
  1017. // Update our PVS state
  1018. CheckPVSCondition();
  1019. //Animate
  1020. StudioFrameAdvance();
  1021. SetNextThink( gpGlobals->curtime + 0.05f );
  1022. SetEnemy( NULL );
  1023. // If we're not on side anymore, stop thrashing
  1024. if ( !OnSide() )
  1025. {
  1026. ReturnToLife();
  1027. return;
  1028. }
  1029. //See if we should continue to thrash
  1030. if ( gpGlobals->curtime < m_flThrashTime )
  1031. {
  1032. if ( m_flShotTime < gpGlobals->curtime )
  1033. {
  1034. if( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
  1035. {
  1036. DryFire();
  1037. }
  1038. else if ( IsCitizenTurret() == false ) // Citizen turrets don't wildly fire
  1039. {
  1040. Vector vecMuzzle, vecMuzzleDir;
  1041. UpdateMuzzleMatrix();
  1042. MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
  1043. MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
  1044. ResetActivity();
  1045. SetActivity( (Activity) ACT_FLOOR_TURRET_FIRE );
  1046. #if !DISABLE_SHOT
  1047. Shoot( vecMuzzle, vecMuzzleDir );
  1048. #endif
  1049. }
  1050. m_flShotTime = gpGlobals->curtime + 0.05f;
  1051. }
  1052. m_vecGoalAngles.x = GetAbsAngles().x + random->RandomFloat( -60, 60 );
  1053. m_vecGoalAngles.y = GetAbsAngles().y + random->RandomFloat( -60, 60 );
  1054. UpdateFacing();
  1055. }
  1056. else
  1057. {
  1058. //Face forward
  1059. m_vecGoalAngles = GetAbsAngles();
  1060. //Set ourselves to close
  1061. if ( GetActivity() != ACT_FLOOR_TURRET_CLOSE )
  1062. {
  1063. SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
  1064. //If we're done moving to our desired facing, close up
  1065. if ( UpdateFacing() == false )
  1066. {
  1067. //Make any last death noises and anims
  1068. EmitSound( "NPC_FloorTurret.Die" );
  1069. SpinDown();
  1070. SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSE );
  1071. EmitSound( "NPC_FloorTurret.Retract" );
  1072. CTakeDamageInfo info;
  1073. info.SetDamage( 1 );
  1074. info.SetDamageType( DMG_CRUSH );
  1075. Event_Killed( info );
  1076. }
  1077. }
  1078. else if ( IsActivityFinished() )
  1079. {
  1080. m_bActive = false;
  1081. m_flLastSight = 0;
  1082. SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSED_IDLE );
  1083. // Don't need to store last NPC anymore, because I've been knocked over
  1084. if ( m_hLastNPCToKickMe )
  1085. {
  1086. m_hLastNPCToKickMe = NULL;
  1087. m_flKnockOverFailedTime = 0;
  1088. }
  1089. //Try to look straight
  1090. if ( UpdateFacing() == false )
  1091. {
  1092. m_OnTipped.FireOutput( this, this );
  1093. SetEyeState( TURRET_EYE_DEAD );
  1094. SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
  1095. // Start thinking slowly to see if we're ever set upright somehow
  1096. SetThink( &CNPC_FloorTurret::InactiveThink );
  1097. SetNextThink( gpGlobals->curtime + 1.0f );
  1098. }
  1099. }
  1100. }
  1101. }
  1102. //-----------------------------------------------------------------------------
  1103. // Purpose: This turret is dead. See if it ever becomes upright again, and if
  1104. // so, become active again.
  1105. //-----------------------------------------------------------------------------
  1106. void CNPC_FloorTurret::InactiveThink( void )
  1107. {
  1108. // Update our PVS state
  1109. CheckPVSCondition();
  1110. // Wake up if we're not on our side
  1111. if ( !OnSide() && m_bEnabled )
  1112. {
  1113. ReturnToLife();
  1114. return;
  1115. }
  1116. if ( IsCitizenTurret() )
  1117. {
  1118. // Blink if we have ammo or our current blink is "on" and we need to turn it off again
  1119. if ( HasSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO ) == false || m_bBlinkState )
  1120. {
  1121. // If we're on our side, ping and complain to the player
  1122. if ( m_bBlinkState == false )
  1123. {
  1124. // Ping when the light is going to come back on
  1125. EmitSound( "NPC_FloorTurret.AlarmPing" );
  1126. }
  1127. SetEyeState( TURRET_EYE_ALARM );
  1128. SetNextThink( gpGlobals->curtime + 0.25f );
  1129. }
  1130. }
  1131. else
  1132. {
  1133. SetNextThink( gpGlobals->curtime + 1.0f );
  1134. }
  1135. }
  1136. //-----------------------------------------------------------------------------
  1137. // Purpose:
  1138. //-----------------------------------------------------------------------------
  1139. void CNPC_FloorTurret::ReturnToLife( void )
  1140. {
  1141. m_flThrashTime = 0;
  1142. // Enable the tip controller
  1143. m_pMotionController->Enable( true );
  1144. // Return to life
  1145. SetState( NPC_STATE_IDLE );
  1146. m_lifeState = LIFE_ALIVE;
  1147. SetCollisionGroup( COLLISION_GROUP_NONE );
  1148. // Become active again
  1149. Enable();
  1150. }
  1151. //-----------------------------------------------------------------------------
  1152. // Purpose: The turret is not doing anything at all
  1153. //-----------------------------------------------------------------------------
  1154. void CNPC_FloorTurret::DisabledThink( void )
  1155. {
  1156. SetNextThink( gpGlobals->curtime + 0.5 );
  1157. if ( OnSide() )
  1158. {
  1159. m_OnTipped.FireOutput( this, this );
  1160. SetEyeState( TURRET_EYE_DEAD );
  1161. SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  1162. SetThink( NULL );
  1163. }
  1164. }
  1165. //-----------------------------------------------------------------------------
  1166. // Purpose: The turret doesn't run base AI properly, which is a bad decision.
  1167. // As a result, it has to manually find enemies.
  1168. //-----------------------------------------------------------------------------
  1169. void CNPC_FloorTurret::HackFindEnemy( void )
  1170. {
  1171. // We have to refresh our memories before finding enemies, so
  1172. // dead enemies are cleared out before new ones are added.
  1173. GetEnemies()->RefreshMemories();
  1174. GetSenses()->Look( FLOOR_TURRET_RANGE );
  1175. SetEnemy( BestEnemy() );
  1176. }
  1177. //-----------------------------------------------------------------------------
  1178. // Purpose: Determines whether the turret is upright enough to function
  1179. // Output : Returns true if the turret is tipped over
  1180. //-----------------------------------------------------------------------------
  1181. inline bool CNPC_FloorTurret::OnSide( void )
  1182. {
  1183. Vector up;
  1184. GetVectors( NULL, NULL, &up );
  1185. return ( DotProduct( up, Vector(0,0,1) ) < 0.5f );
  1186. }
  1187. //-----------------------------------------------------------------------------
  1188. // Purpose: Allows a generic think function before the others are called
  1189. // Input : state - which state the turret is currently in
  1190. //-----------------------------------------------------------------------------
  1191. bool CNPC_FloorTurret::PreThink( turretState_e state )
  1192. {
  1193. // Hack to disable turrets when ai is disabled
  1194. if ( CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI )
  1195. {
  1196. // Push our think out into the future
  1197. SetNextThink( gpGlobals->curtime + 0.1f );
  1198. return true;
  1199. }
  1200. CheckPVSCondition();
  1201. //Animate
  1202. StudioFrameAdvance();
  1203. // We're gonna blow up, so don't interrupt us
  1204. if ( state == TURRET_SELF_DESTRUCTING )
  1205. return false;
  1206. //See if we've tipped, but only do this if we're not being carried
  1207. if ( !IsBeingCarriedByPlayer() )
  1208. {
  1209. if ( OnSide() == false )
  1210. {
  1211. // If I still haven't fallen over after an NPC has tried to knock me down, let them know
  1212. if ( m_hLastNPCToKickMe && m_flKnockOverFailedTime < gpGlobals->curtime )
  1213. {
  1214. m_hLastNPCToKickMe->DispatchInteraction( g_interactionTurretStillStanding, NULL, this );
  1215. m_hLastNPCToKickMe = NULL;
  1216. }
  1217. //Debug visualization
  1218. if ( g_debug_turret.GetBool() )
  1219. {
  1220. Vector up;
  1221. GetVectors( NULL, NULL, &up );
  1222. NDebugOverlay::Line( GetAbsOrigin()+(up*32), GetAbsOrigin()+(up*128), 0, 255, 0, false, 2.0f );
  1223. NDebugOverlay::Cross3D( GetAbsOrigin()+(up*32), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 2.0f );
  1224. NDebugOverlay::Cross3D( GetAbsOrigin()+(up*128), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 2.0f );
  1225. }
  1226. }
  1227. else
  1228. {
  1229. if ( HasSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO ) == false )
  1230. {
  1231. //Thrash around for a bit
  1232. m_flThrashTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 2.5f );
  1233. SetNextThink( gpGlobals->curtime + 0.05f );
  1234. SetThink( &CNPC_FloorTurret::TippedThink );
  1235. SetEyeState( TURRET_EYE_SEE_TARGET );
  1236. SpinUp();
  1237. if ( !m_bNoAlarmSounds )
  1238. {
  1239. EmitSound( "NPC_FloorTurret.Alarm" );
  1240. }
  1241. }
  1242. else
  1243. {
  1244. // Take away the laser
  1245. UTIL_Remove( m_hLaser );
  1246. m_hLaser = NULL;
  1247. // Become inactive
  1248. SetThink( &CNPC_FloorTurret::InactiveThink );
  1249. SetEyeState( TURRET_EYE_DEAD );
  1250. }
  1251. //Stop being targetted
  1252. SetState( NPC_STATE_DEAD );
  1253. m_lifeState = LIFE_DEAD;
  1254. //Disable the tip controller
  1255. m_pMotionController->Enable( false );
  1256. //Debug visualization
  1257. if ( g_debug_turret.GetBool() )
  1258. {
  1259. Vector up;
  1260. GetVectors( NULL, NULL, &up );
  1261. NDebugOverlay::Line( GetAbsOrigin()+(up*32), GetAbsOrigin()+(up*128), 255, 0, 0, false, 2.0f );
  1262. NDebugOverlay::Cross3D( GetAbsOrigin()+(up*32), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 2.0f );
  1263. NDebugOverlay::Cross3D( GetAbsOrigin()+(up*128), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 2.0f );
  1264. }
  1265. //Interrupt current think function
  1266. return true;
  1267. }
  1268. }
  1269. //Do not interrupt current think function
  1270. return false;
  1271. }
  1272. //-----------------------------------------------------------------------------
  1273. // Purpose: Sets the state of the glowing eye attached to the turret
  1274. // Input : state - state the eye should be in
  1275. //-----------------------------------------------------------------------------
  1276. void CNPC_FloorTurret::SetEyeState( eyeState_t state )
  1277. {
  1278. // Must have a valid eye to affect
  1279. if ( !m_hEyeGlow )
  1280. {
  1281. // Create our eye sprite
  1282. m_hEyeGlow = CSprite::SpriteCreate( FLOOR_TURRET_GLOW_SPRITE, GetLocalOrigin(), false );
  1283. if ( !m_hEyeGlow )
  1284. return;
  1285. m_hEyeGlow->SetTransparency( kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation );
  1286. m_hEyeGlow->SetAttachment( this, m_iEyeAttachment );
  1287. }
  1288. // Add the laser if it doesn't already exist
  1289. if ( IsCitizenTurret() && HasSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO ) == false && m_hLaser == NULL )
  1290. {
  1291. m_hLaser = CBeam::BeamCreate( LASER_BEAM_SPRITE, 1.0f );
  1292. if ( m_hLaser == NULL )
  1293. return;
  1294. m_hLaser->EntsInit( this, this );
  1295. m_hLaser->FollowEntity( this );
  1296. m_hLaser->SetStartAttachment( LookupAttachment( "laser_start" ) );
  1297. m_hLaser->SetEndAttachment( LookupAttachment( "laser_end" ) );
  1298. m_hLaser->SetNoise( 0 );
  1299. m_hLaser->SetColor( 255, 0, 0 );
  1300. m_hLaser->SetScrollRate( 0 );
  1301. m_hLaser->SetWidth( 1.0f );
  1302. m_hLaser->SetEndWidth( 1.0f );
  1303. m_hLaser->SetBrightness( 160 );
  1304. m_hLaser->SetBeamFlags( SF_BEAM_SHADEIN );
  1305. }
  1306. m_iEyeState = state;
  1307. //Set the state
  1308. switch( state )
  1309. {
  1310. default:
  1311. case TURRET_EYE_SEE_TARGET: //Fade in and scale up
  1312. m_hEyeGlow->SetColor( 255, 0, 0 );
  1313. m_hEyeGlow->SetBrightness( 164, 0.1f );
  1314. m_hEyeGlow->SetScale( 0.4f, 0.1f );
  1315. break;
  1316. case TURRET_EYE_SEEKING_TARGET: //Ping-pongs
  1317. //Toggle our state
  1318. m_bBlinkState = !m_bBlinkState;
  1319. m_hEyeGlow->SetColor( 255, 128, 0 );
  1320. if ( m_bBlinkState )
  1321. {
  1322. //Fade up and scale up
  1323. m_hEyeGlow->SetScale( 0.25f, 0.1f );
  1324. m_hEyeGlow->SetBrightness( 164, 0.1f );
  1325. }
  1326. else
  1327. {
  1328. //Fade down and scale down
  1329. m_hEyeGlow->SetScale( 0.2f, 0.1f );
  1330. m_hEyeGlow->SetBrightness( 64, 0.1f );
  1331. }
  1332. break;
  1333. case TURRET_EYE_DORMANT: //Fade out and scale down
  1334. m_hEyeGlow->SetColor( 0, 255, 0 );
  1335. m_hEyeGlow->SetScale( 0.1f, 0.5f );
  1336. m_hEyeGlow->SetBrightness( 64, 0.5f );
  1337. break;
  1338. case TURRET_EYE_DEAD: //Fade out slowly
  1339. m_hEyeGlow->SetColor( 255, 0, 0 );
  1340. m_hEyeGlow->SetScale( 0.1f, 3.0f );
  1341. m_hEyeGlow->SetBrightness( 0, 3.0f );
  1342. break;
  1343. case TURRET_EYE_DISABLED:
  1344. m_hEyeGlow->SetColor( 0, 255, 0 );
  1345. m_hEyeGlow->SetScale( 0.1f, 1.0f );
  1346. m_hEyeGlow->SetBrightness( 0, 1.0f );
  1347. break;
  1348. case TURRET_EYE_ALARM:
  1349. {
  1350. //Toggle our state
  1351. m_bBlinkState = !m_bBlinkState;
  1352. m_hEyeGlow->SetColor( 255, 0, 0 );
  1353. if ( m_bBlinkState )
  1354. {
  1355. //Fade up and scale up
  1356. m_hEyeGlow->SetScale( 0.75f, 0.05f );
  1357. m_hEyeGlow->SetBrightness( 192, 0.05f );
  1358. }
  1359. else
  1360. {
  1361. //Fade down and scale down
  1362. m_hEyeGlow->SetScale( 0.25f, 0.25f );
  1363. m_hEyeGlow->SetBrightness( 64, 0.25f );
  1364. }
  1365. }
  1366. break;
  1367. }
  1368. }
  1369. //-----------------------------------------------------------------------------
  1370. // Purpose: Make a pinging noise so the player knows where we are
  1371. //-----------------------------------------------------------------------------
  1372. void CNPC_FloorTurret::Ping( void )
  1373. {
  1374. //See if it's time to ping again
  1375. if ( m_flPingTime > gpGlobals->curtime )
  1376. return;
  1377. //Ping!
  1378. EmitSound( "NPC_FloorTurret.Ping" );
  1379. SetEyeState( TURRET_EYE_SEEKING_TARGET );
  1380. m_flPingTime = gpGlobals->curtime + FLOOR_TURRET_PING_TIME;
  1381. }
  1382. //-----------------------------------------------------------------------------
  1383. // Purpose: Toggle the turret's state
  1384. //-----------------------------------------------------------------------------
  1385. void CNPC_FloorTurret::Toggle( void )
  1386. {
  1387. //This turret is on its side, it can't function
  1388. if ( OnSide() || ( IsAlive() == false ) )
  1389. return;
  1390. //Toggle the state
  1391. if ( m_bEnabled )
  1392. {
  1393. Disable();
  1394. }
  1395. else
  1396. {
  1397. Enable();
  1398. }
  1399. }
  1400. //-----------------------------------------------------------------------------
  1401. // Purpose: Enable the turret and deploy
  1402. //-----------------------------------------------------------------------------
  1403. void CNPC_FloorTurret::Enable( void )
  1404. {
  1405. // Don't interrupt blowing up!
  1406. if ( m_bSelfDestructing )
  1407. return;
  1408. // Always allow us to come back to life, even if not right now
  1409. m_bEnabled = true;
  1410. //This turret is on its side, it can't function
  1411. if ( OnSide() || ( IsAlive() == false ) )
  1412. return;
  1413. // if the turret is flagged as an autoactivate turret, re-enable its ability open self.
  1414. if ( m_spawnflags & SF_FLOOR_TURRET_AUTOACTIVATE )
  1415. {
  1416. m_bAutoStart = true;
  1417. }
  1418. SetThink( &CNPC_FloorTurret::Deploy );
  1419. SetNextThink( gpGlobals->curtime + 0.05f );
  1420. }
  1421. //-----------------------------------------------------------------------------
  1422. // Purpose: Retire the turret until enabled again
  1423. //-----------------------------------------------------------------------------
  1424. void CNPC_FloorTurret::Disable( void )
  1425. {
  1426. //This turret is on its side, it can't function
  1427. if ( OnSide() || ( IsAlive() == false ) || m_bSelfDestructing )
  1428. return;
  1429. if ( m_bEnabled )
  1430. {
  1431. m_bEnabled = false;
  1432. m_bAutoStart = false;
  1433. SetEnemy( NULL );
  1434. SetThink( &CNPC_FloorTurret::Retire );
  1435. SetNextThink( gpGlobals->curtime + 0.1f );
  1436. }
  1437. else
  1438. SetThink( &CNPC_FloorTurret::DisabledThink );
  1439. }
  1440. //-----------------------------------------------------------------------------
  1441. // Purpose: Toggle the turret's state via input function
  1442. //-----------------------------------------------------------------------------
  1443. void CNPC_FloorTurret::InputToggle( inputdata_t &inputdata )
  1444. {
  1445. Toggle();
  1446. }
  1447. //-----------------------------------------------------------------------------
  1448. // Purpose:
  1449. //-----------------------------------------------------------------------------
  1450. void CNPC_FloorTurret::InputEnable( inputdata_t &inputdata )
  1451. {
  1452. Enable();
  1453. }
  1454. //-----------------------------------------------------------------------------
  1455. // Purpose:
  1456. //-----------------------------------------------------------------------------
  1457. void CNPC_FloorTurret::InputDisable( inputdata_t &inputdata )
  1458. {
  1459. Disable();
  1460. }
  1461. //-----------------------------------------------------------------------------
  1462. // Purpose: Stops the turret from firing live rounds (still attempts to though)
  1463. //-----------------------------------------------------------------------------
  1464. void CNPC_FloorTurret::InputDepleteAmmo( inputdata_t &inputdata )
  1465. {
  1466. AddSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO );
  1467. }
  1468. //-----------------------------------------------------------------------------
  1469. // Purpose: Allows the turret to fire live rounds again
  1470. //-----------------------------------------------------------------------------
  1471. void CNPC_FloorTurret::InputRestoreAmmo( inputdata_t &inputdata )
  1472. {
  1473. RemoveSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO );
  1474. }
  1475. //-----------------------------------------------------------------------------
  1476. // Purpose: Allow players and npc's to turn the turret on and off
  1477. //-----------------------------------------------------------------------------
  1478. void CNPC_FloorTurret::ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  1479. {
  1480. switch( useType )
  1481. {
  1482. case USE_OFF:
  1483. Disable();
  1484. break;
  1485. case USE_ON:
  1486. Enable();
  1487. break;
  1488. case USE_SET:
  1489. break;
  1490. case USE_TOGGLE:
  1491. Toggle( );
  1492. break;
  1493. }
  1494. }
  1495. //-----------------------------------------------------------------------------
  1496. // Purpose: Reduce physics forces from the front
  1497. //-----------------------------------------------------------------------------
  1498. int CNPC_FloorTurret::VPhysicsTakeDamage( const CTakeDamageInfo &info )
  1499. {
  1500. bool bShouldIgnoreFromFront = false;
  1501. // Ignore crossbow bolts hitting us from the front
  1502. bShouldIgnoreFromFront = ( info.GetDamageType() & DMG_BULLET ) != 0;
  1503. // Ignore bullets from the front
  1504. if ( !bShouldIgnoreFromFront )
  1505. {
  1506. bShouldIgnoreFromFront = FClassnameIs( info.GetInflictor(), "crossbow_bolt" );
  1507. }
  1508. // Did it hit us on the front?
  1509. if ( bShouldIgnoreFromFront )
  1510. {
  1511. Vector vecForward;
  1512. GetVectors( &vecForward, NULL, NULL );
  1513. Vector vecForce = info.GetDamageForce();
  1514. VectorNormalize( vecForce );
  1515. float flDot = DotProduct( vecForward, vecForce );
  1516. if ( flDot < -0.85 )
  1517. return 0;
  1518. }
  1519. return BaseClass::VPhysicsTakeDamage( info );
  1520. }
  1521. //-----------------------------------------------------------------------------
  1522. // Purpose:
  1523. // Input : &info -
  1524. //-----------------------------------------------------------------------------
  1525. int CNPC_FloorTurret::OnTakeDamage( const CTakeDamageInfo &info )
  1526. {
  1527. CTakeDamageInfo newInfo = info;
  1528. if ( info.GetDamageType() & (DMG_SLASH|DMG_CLUB) )
  1529. {
  1530. // Take extra force from melee hits
  1531. newInfo.ScaleDamageForce( 2.0f );
  1532. // Disable our upright controller for some time
  1533. if ( m_pMotionController != NULL )
  1534. {
  1535. m_pMotionController->Suspend( 2.0f );
  1536. }
  1537. }
  1538. else if ( info.GetDamageType() & DMG_BLAST )
  1539. {
  1540. newInfo.ScaleDamageForce( 2.0f );
  1541. }
  1542. else if ( (info.GetDamageType() & DMG_BULLET) && !(info.GetDamageType() & DMG_BUCKSHOT) )
  1543. {
  1544. // Bullets, but not buckshot, do extra push
  1545. newInfo.ScaleDamageForce( 2.5f );
  1546. }
  1547. // Manually apply vphysics because AI_BaseNPC takedamage doesn't call back to CBaseEntity OnTakeDamage
  1548. VPhysicsTakeDamage( newInfo );
  1549. // Bump up our search time
  1550. m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
  1551. // Start looking around in anger if we were idle
  1552. if ( IsAlive() && m_bEnabled && m_bAutoStart && GetActivity() == ACT_FLOOR_TURRET_CLOSED_IDLE && m_bSelfDestructing == false )
  1553. {
  1554. SetThink( &CNPC_FloorTurret::Deploy );
  1555. }
  1556. return BaseClass::OnTakeDamage( newInfo );
  1557. }
  1558. //-----------------------------------------------------------------------------
  1559. // Purpose:
  1560. //-----------------------------------------------------------------------------
  1561. void CNPC_FloorTurret::SpinUp( void )
  1562. {
  1563. }
  1564. #define FLOOR_TURRET_MIN_SPIN_DOWN 1.0f
  1565. //-----------------------------------------------------------------------------
  1566. // Purpose:
  1567. // Output : const QAngle
  1568. //-----------------------------------------------------------------------------
  1569. QAngle CNPC_FloorTurret::PreferredCarryAngles( void )
  1570. {
  1571. // FIXME: Embed this into the class
  1572. static QAngle g_prefAngles;
  1573. Vector vecUserForward;
  1574. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  1575. pPlayer->EyeVectors( &vecUserForward );
  1576. // If we're looking up, then face directly forward
  1577. if ( vecUserForward.z >= 0.0f )
  1578. return vec3_angle;
  1579. // Otherwise, stay "upright"
  1580. g_prefAngles.Init();
  1581. g_prefAngles.x = -pPlayer->EyeAngles().x;
  1582. return g_prefAngles;
  1583. }
  1584. //-----------------------------------------------------------------------------
  1585. // Purpose:
  1586. //-----------------------------------------------------------------------------
  1587. void CNPC_FloorTurret::SpinDown( void )
  1588. {
  1589. }
  1590. //-----------------------------------------------------------------------------
  1591. // Purpose:
  1592. // Input : *pVictim -
  1593. // Output : float
  1594. //-----------------------------------------------------------------------------
  1595. float CNPC_FloorTurret::GetAttackDamageScale( CBaseEntity *pVictim )
  1596. {
  1597. CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
  1598. // Do extra damage to antlions & combine
  1599. if ( pBCC )
  1600. {
  1601. if ( pBCC->Classify() == CLASS_ANTLION )
  1602. return 2.0;
  1603. if ( pBCC->Classify() == CLASS_COMBINE )
  1604. return 2.0;
  1605. }
  1606. return BaseClass::GetAttackDamageScale( pVictim );
  1607. }
  1608. //-----------------------------------------------------------------------------
  1609. // Purpose:
  1610. //-----------------------------------------------------------------------------
  1611. Vector CNPC_FloorTurret::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
  1612. {
  1613. WeaponProficiency_t weaponProficiency = WEAPON_PROFICIENCY_AVERAGE;
  1614. // Switch our weapon proficiency based upon our target
  1615. if ( pTarget )
  1616. {
  1617. if ( pTarget->Classify() == CLASS_PLAYER || pTarget->Classify() == CLASS_ANTLION || pTarget->Classify() == CLASS_ZOMBIE )
  1618. {
  1619. // Make me much more accurate
  1620. weaponProficiency = WEAPON_PROFICIENCY_PERFECT;
  1621. }
  1622. else if ( pTarget->Classify() == CLASS_COMBINE )
  1623. {
  1624. // Make me more accurate
  1625. weaponProficiency = WEAPON_PROFICIENCY_VERY_GOOD;
  1626. }
  1627. }
  1628. return VECTOR_CONE_10DEGREES * ((CBaseHLCombatWeapon::GetDefaultProficiencyValues())[ weaponProficiency ].spreadscale);
  1629. }
  1630. //------------------------------------------------------------------------------
  1631. // Do we have a physics attacker?
  1632. //------------------------------------------------------------------------------
  1633. CBasePlayer *CNPC_FloorTurret::HasPhysicsAttacker( float dt )
  1634. {
  1635. // If the player is holding me now, or I've been recently thrown
  1636. // then return a pointer to that player
  1637. if ( IsHeldByPhyscannon( ) || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) )
  1638. {
  1639. return m_hPhysicsAttacker;
  1640. }
  1641. return NULL;
  1642. }
  1643. //-----------------------------------------------------------------------------
  1644. // Purpose: Draw any debug text overlays
  1645. // Output : Current text offset from the top
  1646. //-----------------------------------------------------------------------------
  1647. int CNPC_FloorTurret::DrawDebugTextOverlays( void )
  1648. {
  1649. int text_offset = BaseClass::DrawDebugTextOverlays();
  1650. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  1651. {
  1652. if (VPhysicsGetObject())
  1653. {
  1654. char tempstr[512];
  1655. Q_snprintf(tempstr, sizeof(tempstr),"Mass: %.2f kg / %.2f lb (%s)", VPhysicsGetObject()->GetMass(), kg2lbs(VPhysicsGetObject()->GetMass()), GetMassEquivalent(VPhysicsGetObject()->GetMass()));
  1656. EntityText( text_offset, tempstr, 0);
  1657. text_offset++;
  1658. }
  1659. }
  1660. return text_offset;
  1661. }
  1662. void CNPC_FloorTurret::UpdateMuzzleMatrix()
  1663. {
  1664. if ( gpGlobals->tickcount != m_muzzleToWorldTick )
  1665. {
  1666. m_muzzleToWorldTick = gpGlobals->tickcount;
  1667. GetAttachment( m_iMuzzleAttachment, m_muzzleToWorld );
  1668. }
  1669. }
  1670. //-----------------------------------------------------------------------------
  1671. // Purpose: We override this code because otherwise we start to move into the
  1672. // tricky realm of player avoidance. Since we don't go through the
  1673. // normal NPC thinking but we ARE an NPC (...) we miss a bunch of
  1674. // book keeping. This means we can become invisible and then never
  1675. // reappear.
  1676. //-----------------------------------------------------------------------------
  1677. void CNPC_FloorTurret::PlayerPenetratingVPhysics( void )
  1678. {
  1679. // We don't care!
  1680. }
  1681. #define SELF_DESTRUCT_DURATION 4.0f
  1682. #define SELF_DESTRUCT_BEEP_MIN_DELAY 0.1f
  1683. #define SELF_DESTRUCT_BEEP_MAX_DELAY 0.75f
  1684. #define SELF_DESTRUCT_BEEP_MIN_PITCH 100.0f
  1685. #define SELF_DESTRUCT_BEEP_MAX_PITCH 225.0f
  1686. //-----------------------------------------------------------------------------
  1687. // Purpose:
  1688. //-----------------------------------------------------------------------------
  1689. void CNPC_FloorTurret::BreakThink( void )
  1690. {
  1691. Vector vecUp;
  1692. GetVectors( NULL, NULL, &vecUp );
  1693. Vector vecOrigin = WorldSpaceCenter() + ( vecUp * 12.0f );
  1694. // Our effect
  1695. #ifdef HL2_EPISODIC
  1696. DispatchParticleEffect( "explosion_turret_break", vecOrigin, GetAbsAngles() );
  1697. #endif // HL2_EPISODIC
  1698. // K-boom
  1699. RadiusDamage( CTakeDamageInfo( this, this, 15.0f, DMG_BLAST ), vecOrigin, (10*12), CLASS_NONE, this );
  1700. EmitSound( "NPC_FloorTurret.Destruct" );
  1701. breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), vec3_origin, RandomAngularImpulse( -800.0f, 800.0f ) );
  1702. params.impactEnergyScale = 1.0f;
  1703. params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
  1704. // no damage/damage force? set a burst of 100 for some movement
  1705. params.defBurstScale = 100;
  1706. PropBreakableCreateAll( GetModelIndex(), VPhysicsGetObject(), params, this, -1, true );
  1707. // Throw out some small chunks too obscure the explosion even more
  1708. CPVSFilter filter( vecOrigin );
  1709. for ( int i = 0; i < 4; i++ )
  1710. {
  1711. Vector gibVelocity = RandomVector(-100,100);
  1712. int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
  1713. te->BreakModel( filter, 0.0, vecOrigin, GetAbsAngles(), Vector(40,40,40), gibVelocity, iModelIndex, 150, 4, 2.5, BREAK_METAL );
  1714. }
  1715. // We're done!
  1716. UTIL_Remove( this );
  1717. }
  1718. //-----------------------------------------------------------------------------
  1719. // Purpose: The countdown to destruction!
  1720. //-----------------------------------------------------------------------------
  1721. void CNPC_FloorTurret::SelfDestructThink( void )
  1722. {
  1723. // Continue to animate
  1724. PreThink( TURRET_SELF_DESTRUCTING );
  1725. // If we're done, explode
  1726. if ( ( gpGlobals->curtime - m_flDestructStartTime ) >= SELF_DESTRUCT_DURATION )
  1727. {
  1728. SetThink( &CNPC_FloorTurret::BreakThink );
  1729. SetNextThink( gpGlobals->curtime + 0.1f );
  1730. UTIL_Remove( m_hFizzleEffect );
  1731. m_hFizzleEffect = NULL;
  1732. return;
  1733. }
  1734. // Find out where we are in the cycle of our destruction
  1735. float flDestructPerc = clamp( ( gpGlobals->curtime - m_flDestructStartTime ) / SELF_DESTRUCT_DURATION, 0.0f, 1.0f );
  1736. // Figure out when our next beep should occur
  1737. float flBeepTime = SELF_DESTRUCT_BEEP_MAX_DELAY + ( ( SELF_DESTRUCT_BEEP_MIN_DELAY - SELF_DESTRUCT_BEEP_MAX_DELAY ) * flDestructPerc );
  1738. // If it's time to beep again, do so
  1739. if ( gpGlobals->curtime > ( m_flPingTime + flBeepTime ) )
  1740. {
  1741. // Figure out what our beep pitch will be
  1742. float flBeepPitch = SELF_DESTRUCT_BEEP_MIN_PITCH + ( ( SELF_DESTRUCT_BEEP_MAX_PITCH - SELF_DESTRUCT_BEEP_MIN_PITCH ) * flDestructPerc );
  1743. StopSound( "NPC_FloorTurret.AlarmPing" );
  1744. // Play the beep
  1745. CPASAttenuationFilter filter( this, "NPC_FloorTurret.AlarmPing" );
  1746. EmitSound_t params;
  1747. params.m_pSoundName = "NPC_FloorTurret.AlarmPing";
  1748. params.m_nPitch = floor( flBeepPitch );
  1749. params.m_nFlags = SND_CHANGE_PITCH;
  1750. EmitSound( filter, entindex(), params );
  1751. // Flash our eye
  1752. SetEyeState( TURRET_EYE_ALARM );
  1753. // Save this as the last time we pinged
  1754. m_flPingTime = gpGlobals->curtime;
  1755. // Randomly twitch
  1756. m_vecGoalAngles.x = GetAbsAngles().x + random->RandomFloat( -60*flDestructPerc, 60*flDestructPerc );
  1757. m_vecGoalAngles.y = GetAbsAngles().y + random->RandomFloat( -60*flDestructPerc, 60*flDestructPerc );
  1758. }
  1759. UpdateFacing();
  1760. // Think again!
  1761. SetNextThink( gpGlobals->curtime + 0.05f );
  1762. }
  1763. //-----------------------------------------------------------------------------
  1764. // Purpose: Make us explode
  1765. //-----------------------------------------------------------------------------
  1766. void CNPC_FloorTurret::InputSelfDestruct( inputdata_t &inputdata )
  1767. {
  1768. // Ka-boom!
  1769. m_flDestructStartTime = gpGlobals->curtime;
  1770. m_flPingTime = gpGlobals->curtime;
  1771. m_bSelfDestructing = true;
  1772. SetThink( &CNPC_FloorTurret::SelfDestructThink );
  1773. SetNextThink( gpGlobals->curtime + 0.1f );
  1774. // Create the dust effect in place
  1775. m_hFizzleEffect = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
  1776. if ( m_hFizzleEffect != NULL )
  1777. {
  1778. Vector vecUp;
  1779. GetVectors( NULL, NULL, &vecUp );
  1780. // Setup our basic parameters
  1781. m_hFizzleEffect->KeyValue( "start_active", "1" );
  1782. m_hFizzleEffect->KeyValue( "effect_name", "explosion_turret_fizzle" );
  1783. m_hFizzleEffect->SetParent( this );
  1784. m_hFizzleEffect->SetAbsOrigin( WorldSpaceCenter() + ( vecUp * 12.0f ) );
  1785. DispatchSpawn( m_hFizzleEffect );
  1786. m_hFizzleEffect->Activate();
  1787. }
  1788. }
  1789. //
  1790. // Tip controller
  1791. //
  1792. LINK_ENTITY_TO_CLASS( floorturret_tipcontroller, CTurretTipController );
  1793. //---------------------------------------------------------
  1794. // Save/Restore
  1795. //---------------------------------------------------------
  1796. BEGIN_DATADESC( CTurretTipController )
  1797. DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
  1798. DEFINE_FIELD( m_flSuspendTime, FIELD_TIME ),
  1799. DEFINE_FIELD( m_worldGoalAxis, FIELD_VECTOR ),
  1800. DEFINE_FIELD( m_localTestAxis, FIELD_VECTOR ),
  1801. DEFINE_PHYSPTR( m_pController ),
  1802. DEFINE_FIELD( m_angularLimit, FIELD_FLOAT ),
  1803. DEFINE_FIELD( m_pParentTurret, FIELD_CLASSPTR ),
  1804. END_DATADESC()
  1805. CTurretTipController::~CTurretTipController()
  1806. {
  1807. if ( m_pController )
  1808. {
  1809. physenv->DestroyMotionController( m_pController );
  1810. m_pController = NULL;
  1811. }
  1812. }
  1813. //-----------------------------------------------------------------------------
  1814. // Purpose:
  1815. //-----------------------------------------------------------------------------
  1816. void CTurretTipController::Spawn( void )
  1817. {
  1818. m_bEnabled = true;
  1819. // align the object's local Z axis
  1820. m_localTestAxis.Init( 0, 0, 1 );
  1821. // with the world's Z axis
  1822. m_worldGoalAxis.Init( 0, 0, 1 );
  1823. // recover from up to 25 degrees / sec angular velocity
  1824. m_angularLimit = 25;
  1825. m_flSuspendTime = 0;
  1826. SetMoveType( MOVETYPE_NONE );
  1827. }
  1828. //-----------------------------------------------------------------------------
  1829. // Purpose:
  1830. //-----------------------------------------------------------------------------
  1831. void CTurretTipController::Activate( void )
  1832. {
  1833. BaseClass::Activate();
  1834. if ( m_pParentTurret == NULL )
  1835. {
  1836. UTIL_Remove(this);
  1837. return;
  1838. }
  1839. IPhysicsObject *pPhys = m_pParentTurret->VPhysicsGetObject();
  1840. if ( pPhys == NULL )
  1841. {
  1842. UTIL_Remove(this);
  1843. return;
  1844. }
  1845. //Setup the motion controller
  1846. if ( !m_pController )
  1847. {
  1848. m_pController = physenv->CreateMotionController( (IMotionEvent *)this );
  1849. m_pController->AttachObject( pPhys, true );
  1850. }
  1851. else
  1852. {
  1853. m_pController->SetEventHandler( this );
  1854. }
  1855. }
  1856. //-----------------------------------------------------------------------------
  1857. // Purpose: Actual simulation for tip controller
  1858. //-----------------------------------------------------------------------------
  1859. IMotionEvent::simresult_e CTurretTipController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
  1860. {
  1861. if ( Enabled() == false )
  1862. return SIM_NOTHING;
  1863. // Don't simulate if we're being carried by the player
  1864. if ( m_pParentTurret->IsBeingCarriedByPlayer() )
  1865. return SIM_NOTHING;
  1866. float flAngularLimit = m_angularLimit;
  1867. // If we were just dropped by a friendly player, stabilise better
  1868. if ( m_pParentTurret->WasJustDroppedByPlayer() )
  1869. {
  1870. // Increase the controller strength a little
  1871. flAngularLimit += 20;
  1872. }
  1873. else
  1874. {
  1875. // If the turret has some vertical velocity, don't simulate
  1876. Vector vecVelocity;
  1877. AngularImpulse angImpulse;
  1878. pObject->GetVelocity( &vecVelocity, &angImpulse );
  1879. if ( (vecVelocity.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerVelocity) || (angImpulse.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerAngularVelocity) )
  1880. return SIM_NOTHING;
  1881. }
  1882. linear.Init();
  1883. AngularImpulse angVel;
  1884. pObject->GetVelocity( NULL, &angVel );
  1885. matrix3x4_t matrix;
  1886. // get the object's local to world transform
  1887. pObject->GetPositionMatrix( &matrix );
  1888. // Get the alignment axis in object space
  1889. Vector currentLocalTargetAxis;
  1890. VectorIRotate( m_worldGoalAxis, matrix, currentLocalTargetAxis );
  1891. float invDeltaTime = (1/deltaTime);
  1892. angular = ComputeRotSpeedToAlignAxes( m_localTestAxis, currentLocalTargetAxis, angVel, 1.0, invDeltaTime * invDeltaTime, flAngularLimit * invDeltaTime );
  1893. return SIM_LOCAL_ACCELERATION;
  1894. }
  1895. //-----------------------------------------------------------------------------
  1896. // Purpose:
  1897. //-----------------------------------------------------------------------------
  1898. void CTurretTipController::Enable( bool state )
  1899. {
  1900. m_bEnabled = state;
  1901. }
  1902. //-----------------------------------------------------------------------------
  1903. // Purpose:
  1904. // Input : time -
  1905. //-----------------------------------------------------------------------------
  1906. void CTurretTipController::Suspend( float time )
  1907. {
  1908. m_flSuspendTime = gpGlobals->curtime + time;
  1909. }
  1910. float CTurretTipController::SuspendedTill( void )
  1911. {
  1912. return m_flSuspendTime;
  1913. }
  1914. //-----------------------------------------------------------------------------
  1915. // Purpose:
  1916. // Output : Returns true on success, false on failure.
  1917. //-----------------------------------------------------------------------------
  1918. bool CTurretTipController::Enabled( void )
  1919. {
  1920. if ( m_flSuspendTime > gpGlobals->curtime )
  1921. return false;
  1922. return m_bEnabled;
  1923. }
  1924. //-----------------------------------------------------------------------------
  1925. //
  1926. // Schedules
  1927. //
  1928. //-----------------------------------------------------------------------------
  1929. AI_BEGIN_CUSTOM_NPC( npc_turret_floor, CNPC_FloorTurret )
  1930. DECLARE_INTERACTION( g_interactionTurretStillStanding );
  1931. AI_END_CUSTOM_NPC()