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.

1847 lines
53 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "npcevent.h"
  8. #include "ai_basenpc_physicsflyer.h"
  9. #include "weapon_physcannon.h"
  10. #include "hl2_player.h"
  11. #include "npc_scanner.h"
  12. #include "IEffects.h"
  13. #include "explode.h"
  14. #include "ai_route.h"
  15. // memdbgon must be the last include file in a .cpp file!!!
  16. #include "tier0/memdbgon.h"
  17. ConVar g_debug_basescanner( "g_debug_basescanner", "0", FCVAR_CHEAT );
  18. BEGIN_DATADESC( CNPC_BaseScanner )
  19. DEFINE_EMBEDDED( m_KilledInfo ),
  20. DEFINE_SOUNDPATCH( m_pEngineSound ),
  21. DEFINE_FIELD( m_flFlyNoiseBase, FIELD_FLOAT ),
  22. DEFINE_FIELD( m_flEngineStallTime, FIELD_TIME ),
  23. DEFINE_FIELD( m_fNextFlySoundTime, FIELD_TIME ),
  24. DEFINE_FIELD( m_nFlyMode, FIELD_INTEGER ),
  25. DEFINE_FIELD( m_vecDiveBombDirection, FIELD_VECTOR ),
  26. DEFINE_FIELD( m_flDiveBombRollForce, FIELD_FLOAT ),
  27. // Physics Influence
  28. DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
  29. DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
  30. DEFINE_FIELD( m_flGoalOverrideDistance, FIELD_FLOAT ),
  31. DEFINE_FIELD( m_flAttackNearDist, FIELD_FLOAT ),
  32. DEFINE_FIELD( m_flAttackFarDist, FIELD_FLOAT ),
  33. DEFINE_FIELD( m_flAttackRange, FIELD_FLOAT ),
  34. DEFINE_FIELD( m_nPoseTail, FIELD_INTEGER ),
  35. DEFINE_FIELD( m_nPoseDynamo, FIELD_INTEGER ),
  36. DEFINE_FIELD( m_nPoseFlare, FIELD_INTEGER ),
  37. DEFINE_FIELD( m_nPoseFaceVert, FIELD_INTEGER ),
  38. DEFINE_FIELD( m_nPoseFaceHoriz, FIELD_INTEGER ),
  39. // DEFINE_FIELD( m_bHasSpoken, FIELD_BOOLEAN ),
  40. DEFINE_FIELD( m_pSmokeTrail, FIELD_CLASSPTR ),
  41. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistanceOverride", InputSetDistanceOverride ),
  42. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFlightSpeed", InputSetFlightSpeed ),
  43. DEFINE_THINKFUNC( DiveBombSoundThink ),
  44. END_DATADESC()
  45. ConVar sk_scanner_dmg_dive( "sk_scanner_dmg_dive","0");
  46. //-----------------------------------------------------------------------------
  47. // Think contexts
  48. //-----------------------------------------------------------------------------
  49. static const char *s_pDiveBombSoundThinkContext = "DiveBombSoundThinkContext";
  50. //-----------------------------------------------------------------------------
  51. // Purpose:
  52. //-----------------------------------------------------------------------------
  53. CNPC_BaseScanner::CNPC_BaseScanner()
  54. {
  55. #ifdef _DEBUG
  56. m_vCurrentBanking.Init();
  57. #endif
  58. m_pEngineSound = NULL;
  59. m_bHasSpoken = false;
  60. m_flAttackNearDist = SCANNER_ATTACK_NEAR_DIST;
  61. m_flAttackFarDist = SCANNER_ATTACK_FAR_DIST;
  62. m_flAttackRange = SCANNER_ATTACK_RANGE;
  63. }
  64. //-----------------------------------------------------------------------------
  65. // Purpose:
  66. //-----------------------------------------------------------------------------
  67. void CNPC_BaseScanner::Spawn(void)
  68. {
  69. #ifdef _XBOX
  70. // Always fade the corpse
  71. AddSpawnFlags( SF_NPC_FADE_CORPSE );
  72. AddEffects( EF_NOSHADOW );
  73. #endif // _XBOX
  74. SetHullType( HULL_TINY_CENTERED );
  75. SetHullSizeNormal();
  76. SetSolid( SOLID_BBOX );
  77. AddSolidFlags( FSOLID_NOT_STANDABLE );
  78. SetMoveType( MOVETYPE_VPHYSICS );
  79. m_bloodColor = DONT_BLEED;
  80. SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin.
  81. m_flFieldOfView = 0.2;
  82. m_NPCState = NPC_STATE_NONE;
  83. SetNavType( NAV_FLY );
  84. AddFlag( FL_FLY );
  85. // This entity cannot be dissolved by the combine balls,
  86. // nor does it get killed by the mega physcannon.
  87. AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL );
  88. m_flGoalOverrideDistance = 0.0f;
  89. m_nFlyMode = SCANNER_FLY_PATROL;
  90. AngleVectors( GetLocalAngles(), &m_vCurrentBanking );
  91. m_fHeadYaw = 0;
  92. m_pSmokeTrail = NULL;
  93. SetCurrentVelocity( vec3_origin );
  94. // Noise modifier
  95. Vector bobAmount;
  96. bobAmount.x = random->RandomFloat( -2.0f, 2.0f );
  97. bobAmount.y = random->RandomFloat( -2.0f, 2.0f );
  98. bobAmount.z = random->RandomFloat( 2.0f, 4.0f );
  99. if ( random->RandomInt( 0, 1 ) )
  100. {
  101. bobAmount.z *= -1.0f;
  102. }
  103. SetNoiseMod( bobAmount );
  104. // set flight speed
  105. m_flSpeed = GetMaxSpeed();
  106. // --------------------------------------------
  107. CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_SKIP_NAV_GROUND_CHECK );
  108. NPCInit();
  109. m_flFlyNoiseBase = random->RandomFloat( 0, M_PI );
  110. m_flNextAttack = gpGlobals->curtime;
  111. }
  112. //-----------------------------------------------------------------------------
  113. //
  114. //-----------------------------------------------------------------------------
  115. void CNPC_BaseScanner::UpdateEfficiency( bool bInPVS )
  116. {
  117. SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
  118. SetMoveEfficiency( AIME_NORMAL );
  119. }
  120. //-----------------------------------------------------------------------------
  121. //-----------------------------------------------------------------------------
  122. float CNPC_BaseScanner::GetAutoAimRadius()
  123. {
  124. if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
  125. {
  126. return 24.0f;
  127. }
  128. return 12.0f;
  129. }
  130. //-----------------------------------------------------------------------------
  131. // Purpose: Called just before we are deleted.
  132. //-----------------------------------------------------------------------------
  133. void CNPC_BaseScanner::UpdateOnRemove( void )
  134. {
  135. // Stop combat loops if I'm alive. If I'm dead, the die sound will already have stopped it.
  136. if ( IsAlive() && m_bHasSpoken )
  137. {
  138. SentenceStop();
  139. }
  140. BaseClass::UpdateOnRemove();
  141. }
  142. //-----------------------------------------------------------------------------
  143. // Purpose: Gets the appropriate next schedule based on current condition
  144. // bits.
  145. //-----------------------------------------------------------------------------
  146. int CNPC_BaseScanner::SelectSchedule(void)
  147. {
  148. // ----------------------------------------------------
  149. // If I'm dead, go into a dive bomb
  150. // ----------------------------------------------------
  151. if ( m_iHealth <= 0 )
  152. {
  153. m_flSpeed = SCANNER_MAX_DIVE_BOMB_SPEED;
  154. return SCHED_SCANNER_ATTACK_DIVEBOMB;
  155. }
  156. // -------------------------------
  157. // If I'm in a script sequence
  158. // -------------------------------
  159. if ( m_NPCState == NPC_STATE_SCRIPT )
  160. return(BaseClass::SelectSchedule());
  161. // -------------------------------
  162. // Flinch
  163. // -------------------------------
  164. if ( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) )
  165. {
  166. if ( IsHeldByPhyscannon( ) )
  167. return SCHED_SMALL_FLINCH;
  168. if ( m_NPCState == NPC_STATE_IDLE )
  169. return SCHED_SMALL_FLINCH;
  170. if ( m_NPCState == NPC_STATE_ALERT )
  171. {
  172. if ( m_iHealth < ( 3 * m_iMaxHealth / 4 ))
  173. return SCHED_TAKE_COVER_FROM_ORIGIN;
  174. if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
  175. return SCHED_SMALL_FLINCH;
  176. }
  177. else
  178. {
  179. if ( random->RandomInt( 0, 10 ) < 4 )
  180. return SCHED_SMALL_FLINCH;
  181. }
  182. }
  183. // I'm being held by the physcannon... struggle!
  184. if ( IsHeldByPhyscannon( ) )
  185. return SCHED_SCANNER_HELD_BY_PHYSCANNON;
  186. // ----------------------------------------------------------
  187. // If I have an enemy
  188. // ----------------------------------------------------------
  189. if ( GetEnemy() != NULL && GetEnemy()->IsAlive() )
  190. {
  191. // Patrol if the enemy has vanished
  192. if ( HasCondition( COND_LOST_ENEMY ) )
  193. return SCHED_SCANNER_PATROL;
  194. // Chase via route if we're directly blocked
  195. if ( HasCondition( COND_SCANNER_FLY_BLOCKED ) )
  196. return SCHED_SCANNER_CHASE_ENEMY;
  197. // Attack if it's time
  198. if ( gpGlobals->curtime >= m_flNextAttack )
  199. {
  200. if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
  201. return SCHED_SCANNER_ATTACK;
  202. }
  203. // Otherwise fly in low for attack
  204. return SCHED_SCANNER_ATTACK_HOVER;
  205. }
  206. // Default to patrolling around
  207. return SCHED_SCANNER_PATROL;
  208. }
  209. //-----------------------------------------------------------------------------
  210. // Purpose:
  211. //-----------------------------------------------------------------------------
  212. void CNPC_BaseScanner::OnScheduleChange( void )
  213. {
  214. m_flSpeed = GetMaxSpeed();
  215. BaseClass::OnScheduleChange();
  216. }
  217. //-----------------------------------------------------------------------------
  218. // Purpose: For innate melee attack
  219. //-----------------------------------------------------------------------------
  220. int CNPC_BaseScanner::MeleeAttack1Conditions( float flDot, float flDist )
  221. {
  222. if (GetEnemy() == NULL)
  223. {
  224. return COND_NONE;
  225. }
  226. // Check too far to attack with 2D distance
  227. float vEnemyDist2D = (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length2D();
  228. if (m_flNextAttack > gpGlobals->curtime)
  229. {
  230. return COND_NONE;
  231. }
  232. else if (vEnemyDist2D > m_flAttackRange)
  233. {
  234. return COND_TOO_FAR_TO_ATTACK;
  235. }
  236. else if (flDot < 0.7)
  237. {
  238. return COND_NOT_FACING_ATTACK;
  239. }
  240. return COND_CAN_MELEE_ATTACK1;
  241. }
  242. //-----------------------------------------------------------------------------
  243. // Purpose:
  244. // Input : eOldState -
  245. // eNewState -
  246. //-----------------------------------------------------------------------------
  247. void CNPC_BaseScanner::OnStateChange( NPC_STATE eOldState, NPC_STATE eNewState )
  248. {
  249. if (( eNewState == NPC_STATE_ALERT ) || ( eNewState == NPC_STATE_COMBAT ))
  250. {
  251. SetPoseParameter(m_nPoseFlare, 1.0f);
  252. }
  253. else
  254. {
  255. SetPoseParameter(m_nPoseFlare, 0);
  256. }
  257. }
  258. //-----------------------------------------------------------------------------
  259. // Purpose:
  260. // Input : pTask -
  261. //-----------------------------------------------------------------------------
  262. void CNPC_BaseScanner::StartTask( const Task_t *pTask )
  263. {
  264. switch (pTask->iTask)
  265. {
  266. case TASK_SCANNER_SET_FLY_PATROL:
  267. {
  268. // Fly in patrol mode and clear any
  269. // remaining target entity
  270. m_nFlyMode = SCANNER_FLY_PATROL;
  271. TaskComplete();
  272. break;
  273. }
  274. case TASK_SCANNER_SET_FLY_CHASE:
  275. {
  276. m_nFlyMode = SCANNER_FLY_CHASE;
  277. TaskComplete();
  278. break;
  279. }
  280. case TASK_SCANNER_SET_FLY_ATTACK:
  281. {
  282. m_nFlyMode = SCANNER_FLY_ATTACK;
  283. TaskComplete();
  284. break;
  285. }
  286. case TASK_SCANNER_SET_FLY_DIVE:
  287. {
  288. // Pick a direction to divebomb.
  289. if ( GetEnemy() != NULL )
  290. {
  291. // Fly towards my enemy
  292. Vector vEnemyPos = GetEnemyLKP();
  293. m_vecDiveBombDirection = vEnemyPos - GetLocalOrigin();
  294. }
  295. else
  296. {
  297. // Pick a random forward and down direction.
  298. Vector forward;
  299. GetVectors( &forward, NULL, NULL );
  300. m_vecDiveBombDirection = forward + Vector( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( -20, -10 ) );
  301. }
  302. VectorNormalize( m_vecDiveBombDirection );
  303. // Calculate a roll force.
  304. m_flDiveBombRollForce = random->RandomFloat( 20.0, 420.0 );
  305. if ( random->RandomInt( 0, 1 ) )
  306. {
  307. m_flDiveBombRollForce *= -1;
  308. }
  309. DiveBombSoundThink();
  310. m_nFlyMode = SCANNER_FLY_DIVE;
  311. TaskComplete();
  312. break;
  313. }
  314. default:
  315. BaseClass::StartTask(pTask);
  316. break;
  317. }
  318. }
  319. //------------------------------------------------------------------------------
  320. // Purpose: Override to split in two when attacked
  321. //------------------------------------------------------------------------------
  322. int CNPC_BaseScanner::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  323. {
  324. // Start smoking when we're nearly dead
  325. if ( m_iHealth < ( m_iMaxHealth - ( m_iMaxHealth / 4 ) ) )
  326. {
  327. StartSmokeTrail();
  328. }
  329. return (BaseClass::OnTakeDamage_Alive( info ));
  330. }
  331. //------------------------------------------------------------------------------
  332. // Purpose: Override to split in two when attacked
  333. //------------------------------------------------------------------------------
  334. int CNPC_BaseScanner::OnTakeDamage_Dying( const CTakeDamageInfo &info )
  335. {
  336. // do the damage
  337. m_iHealth -= info.GetDamage();
  338. if ( m_iHealth < -40 )
  339. {
  340. Gib();
  341. return 1;
  342. }
  343. return VPhysicsTakeDamage( info );
  344. }
  345. //-----------------------------------------------------------------------------
  346. // Purpose:
  347. //-----------------------------------------------------------------------------
  348. void CNPC_BaseScanner::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  349. {
  350. if ( info.GetDamageType() & DMG_BULLET)
  351. {
  352. g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal);
  353. }
  354. BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
  355. }
  356. //-----------------------------------------------------------------------------
  357. // Take damage from being thrown by a physcannon
  358. //-----------------------------------------------------------------------------
  359. #define SCANNER_SMASH_SPEED 250.0 // How fast a scanner must slam into something to take full damage
  360. void CNPC_BaseScanner::TakeDamageFromPhyscannon( CBasePlayer *pPlayer )
  361. {
  362. CTakeDamageInfo info;
  363. info.SetDamageType( DMG_GENERIC );
  364. info.SetInflictor( this );
  365. info.SetAttacker( pPlayer );
  366. info.SetDamagePosition( GetAbsOrigin() );
  367. info.SetDamageForce( Vector( 1.0, 1.0, 1.0 ) );
  368. // Convert velocity into damage.
  369. Vector vel;
  370. VPhysicsGetObject()->GetVelocity( &vel, NULL );
  371. float flSpeed = vel.Length();
  372. float flFactor = flSpeed / SCANNER_SMASH_SPEED;
  373. // Clamp. Don't inflict negative damage or massive damage!
  374. flFactor = clamp( flFactor, 0.0f, 2.0f );
  375. float flDamage = m_iMaxHealth * flFactor;
  376. #if 0
  377. Msg("Doing %f damage for %f speed!\n", flDamage, flSpeed );
  378. #endif
  379. info.SetDamage( flDamage );
  380. TakeDamage( info );
  381. }
  382. //-----------------------------------------------------------------------------
  383. // Take damage from physics impacts
  384. //-----------------------------------------------------------------------------
  385. void CNPC_BaseScanner::TakeDamageFromPhysicsImpact( int index, gamevcollisionevent_t *pEvent )
  386. {
  387. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  388. // NOTE: Augment the normal impact energy scale here.
  389. float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 5.0f;
  390. // Scale by the mapmaker's energyscale
  391. flDamageScale *= m_impactEnergyScale;
  392. int damageType = 0;
  393. float damage = CalculateDefaultPhysicsDamage( index, pEvent, flDamageScale, true, damageType );
  394. if ( damage == 0 )
  395. return;
  396. Vector damagePos;
  397. pEvent->pInternalData->GetContactPoint( damagePos );
  398. Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
  399. if ( damageForce == vec3_origin )
  400. {
  401. // This can happen if this entity is motion disabled, and can't move.
  402. // Use the velocity of the entity that hit us instead.
  403. damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
  404. }
  405. // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
  406. PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index );
  407. }
  408. //-----------------------------------------------------------------------------
  409. // Is the scanner being held?
  410. //-----------------------------------------------------------------------------
  411. bool CNPC_BaseScanner::IsHeldByPhyscannon( )
  412. {
  413. return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD);
  414. }
  415. //------------------------------------------------------------------------------
  416. // Physics impact
  417. //------------------------------------------------------------------------------
  418. #define SCANNER_SMASH_TIME 0.75 // How long after being thrown from a physcannon that a manhack is eligible to die from impact
  419. void CNPC_BaseScanner::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  420. {
  421. BaseClass::VPhysicsCollision( index, pEvent );
  422. // Take no impact damage while being carried.
  423. if ( IsHeldByPhyscannon( ) )
  424. return;
  425. CBasePlayer *pPlayer = HasPhysicsAttacker( SCANNER_SMASH_TIME );
  426. if( pPlayer )
  427. {
  428. TakeDamageFromPhyscannon( pPlayer );
  429. return;
  430. }
  431. // It also can take physics damage from things thrown by the player.
  432. int otherIndex = !index;
  433. CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
  434. if ( pHitEntity )
  435. {
  436. if ( pHitEntity->HasPhysicsAttacker( 0.5f ) )
  437. {
  438. // It can take physics damage from things thrown by the player.
  439. TakeDamageFromPhysicsImpact( index, pEvent );
  440. }
  441. else if ( FClassnameIs( pHitEntity, "prop_combine_ball" ) )
  442. {
  443. // It also can take physics damage from a combine ball.
  444. TakeDamageFromPhysicsImpact( index, pEvent );
  445. }
  446. }
  447. }
  448. //------------------------------------------------------------------------------
  449. // Purpose:
  450. //------------------------------------------------------------------------------
  451. void CNPC_BaseScanner::Gib( void )
  452. {
  453. if ( IsMarkedForDeletion() )
  454. return;
  455. // Sparks
  456. for ( int i = 0; i < 4; i++ )
  457. {
  458. Vector sparkPos = GetAbsOrigin();
  459. sparkPos.x += random->RandomFloat(-12,12);
  460. sparkPos.y += random->RandomFloat(-12,12);
  461. sparkPos.z += random->RandomFloat(-12,12);
  462. g_pEffects->Sparks(sparkPos);
  463. }
  464. // Light
  465. CBroadcastRecipientFilter filter;
  466. te->DynamicLight( filter, 0.0, &WorldSpaceCenter(), 255, 180, 100, 0, 100, 0.1, 0 );
  467. // Cover the gib spawn
  468. ExplosionCreate( WorldSpaceCenter(), GetAbsAngles(), this, 64, 64, false );
  469. // Turn off any smoke trail
  470. if ( m_pSmokeTrail )
  471. {
  472. m_pSmokeTrail->m_ParticleLifetime = 0;
  473. UTIL_Remove(m_pSmokeTrail);
  474. m_pSmokeTrail = NULL;
  475. }
  476. // FIXME: This is because we couldn't save/load the CTakeDamageInfo.
  477. // because it's midnight before the teamwide playtest. Real solution
  478. // is to add a datadesc to CTakeDamageInfo
  479. if ( m_KilledInfo.GetInflictor() )
  480. {
  481. BaseClass::Event_Killed( m_KilledInfo );
  482. }
  483. UTIL_Remove(this);
  484. }
  485. //-----------------------------------------------------------------------------
  486. // Purpose:
  487. // Input : *pPhysGunUser -
  488. // bPunting -
  489. //-----------------------------------------------------------------------------
  490. void CNPC_BaseScanner::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  491. {
  492. m_hPhysicsAttacker = pPhysGunUser;
  493. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  494. if ( reason == PUNTED_BY_CANNON )
  495. {
  496. // There's about to be a massive change in velocity.
  497. // Think immediately to handle changes in m_vCurrentVelocity;
  498. SetNextThink( gpGlobals->curtime + 0.01f );
  499. m_flEngineStallTime = gpGlobals->curtime + 2.0f;
  500. ScannerEmitSound( "DiveBomb" );
  501. }
  502. else
  503. {
  504. SetCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
  505. ClearCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );
  506. }
  507. }
  508. //-----------------------------------------------------------------------------
  509. // Purpose:
  510. // Input : *pPhysGunUser -
  511. //-----------------------------------------------------------------------------
  512. void CNPC_BaseScanner::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  513. {
  514. m_hPhysicsAttacker = pPhysGunUser;
  515. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  516. ClearCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
  517. SetCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );
  518. if ( Reason == LAUNCHED_BY_CANNON )
  519. {
  520. m_flEngineStallTime = gpGlobals->curtime + 2.0f;
  521. // There's about to be a massive change in velocity.
  522. // Think immediately to handle changes in m_vCurrentVelocity;
  523. SetNextThink( gpGlobals->curtime + 0.01f );
  524. ScannerEmitSound( "DiveBomb" );
  525. }
  526. }
  527. //------------------------------------------------------------------------------
  528. // Do we have a physics attacker?
  529. //------------------------------------------------------------------------------
  530. CBasePlayer *CNPC_BaseScanner::HasPhysicsAttacker( float dt )
  531. {
  532. // If the player is holding me now, or I've been recently thrown
  533. // then return a pointer to that player
  534. if ( IsHeldByPhyscannon( ) || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) )
  535. {
  536. return m_hPhysicsAttacker;
  537. }
  538. return NULL;
  539. }
  540. //------------------------------------------------------------------------------
  541. // Purpose:
  542. //------------------------------------------------------------------------------
  543. void CNPC_BaseScanner::StopLoopingSounds(void)
  544. {
  545. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  546. controller.SoundDestroy( m_pEngineSound );
  547. m_pEngineSound = NULL;
  548. BaseClass::StopLoopingSounds();
  549. }
  550. //-----------------------------------------------------------------------------
  551. // Purpose:
  552. // Input : pInflictor -
  553. // pAttacker -
  554. // flDamage -
  555. // bitsDamageType -
  556. //-----------------------------------------------------------------------------
  557. void CNPC_BaseScanner::Event_Killed( const CTakeDamageInfo &info )
  558. {
  559. // Copy off the takedamage info that killed me, since we're not going to call
  560. // up into the base class's Event_Killed() until we gib. (gibbing is ultimate death)
  561. m_KilledInfo = info;
  562. // Interrupt whatever schedule I'm on
  563. SetCondition(COND_SCHEDULE_DONE);
  564. // If I have an enemy and I'm up high, do a dive bomb (unless dissolved)
  565. if ( GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false )
  566. {
  567. Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin();
  568. if ( ( vecDelta.z > 120 ) && ( vecDelta.Length() > 360 ) )
  569. {
  570. // If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again.
  571. // This is especially bad if someone machineguns the divebombing scanner.
  572. AttackDivebomb();
  573. return;
  574. }
  575. }
  576. Gib();
  577. }
  578. //------------------------------------------------------------------------------
  579. // Purpose:
  580. //------------------------------------------------------------------------------
  581. void CNPC_BaseScanner::AttackDivebomb( void )
  582. {
  583. ScannerEmitSound( "DiveBomb" );
  584. m_takedamage = DAMAGE_NO;
  585. StartSmokeTrail();
  586. }
  587. //------------------------------------------------------------------------------
  588. // Purpose: Checks to see if we hit anything while dive bombing.
  589. //------------------------------------------------------------------------------
  590. void CNPC_BaseScanner::AttackDivebombCollide(float flInterval)
  591. {
  592. //
  593. // Trace forward to see if I hit anything
  594. //
  595. Vector checkPos = GetAbsOrigin() + (GetCurrentVelocity() * flInterval);
  596. trace_t tr;
  597. CBaseEntity* pHitEntity = NULL;
  598. AI_TraceHull( GetAbsOrigin(), checkPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  599. if (tr.m_pEnt)
  600. {
  601. pHitEntity = tr.m_pEnt;
  602. // Did I hit an entity that isn't another scanner?
  603. if (pHitEntity && pHitEntity->Classify()!=CLASS_SCANNER)
  604. {
  605. if ( !pHitEntity->ClassMatches("item_battery") )
  606. {
  607. if ( !pHitEntity->IsWorld() )
  608. {
  609. CTakeDamageInfo info( this, this, sk_scanner_dmg_dive.GetFloat(), DMG_CLUB );
  610. CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
  611. pHitEntity->TakeDamage( info );
  612. }
  613. Gib();
  614. }
  615. }
  616. }
  617. if (tr.fraction != 1.0)
  618. {
  619. // We've hit something so deflect our velocity based on the surface
  620. // norm of what we've hit
  621. if (flInterval > 0)
  622. {
  623. float moveLen = (1.0 - tr.fraction)*(GetAbsOrigin() - checkPos).Length();
  624. Vector vBounceVel = moveLen*tr.plane.normal/flInterval;
  625. // If I'm right over the ground don't push down
  626. if (vBounceVel.z < 0)
  627. {
  628. float floorZ = GetFloorZ(GetAbsOrigin());
  629. if (abs(GetAbsOrigin().z - floorZ) < 36)
  630. {
  631. vBounceVel.z = 0;
  632. }
  633. }
  634. SetCurrentVelocity( GetCurrentVelocity() + vBounceVel );
  635. }
  636. CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHitEntity );
  637. if (pBCC)
  638. {
  639. // Spawn some extra blood where we hit
  640. SpawnBlood(tr.endpos, g_vecAttackDir, pBCC->BloodColor(), sk_scanner_dmg_dive.GetFloat());
  641. }
  642. else
  643. {
  644. if (!(m_spawnflags & SF_NPC_GAG))
  645. {
  646. // <<TEMP>> need better sound here...
  647. ScannerEmitSound( "Shoot" );
  648. }
  649. // For sparks we must trace a line in the direction of the surface norm
  650. // that we hit.
  651. checkPos = GetAbsOrigin() - (tr.plane.normal * 24);
  652. AI_TraceLine( GetAbsOrigin(), checkPos,MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  653. if (tr.fraction != 1.0)
  654. {
  655. g_pEffects->Sparks( tr.endpos );
  656. CBroadcastRecipientFilter filter;
  657. te->DynamicLight( filter, 0.0,
  658. &GetAbsOrigin(), 255, 180, 100, 0, 50, 0.1, 0 );
  659. }
  660. }
  661. }
  662. }
  663. //-----------------------------------------------------------------------------
  664. // Purpose:
  665. //-----------------------------------------------------------------------------
  666. void CNPC_BaseScanner::PlayFlySound(void)
  667. {
  668. if ( IsMarkedForDeletion() )
  669. return;
  670. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  671. //Setup the sound if we're not already
  672. if ( m_pEngineSound == NULL )
  673. {
  674. // Create the sound
  675. CPASAttenuationFilter filter( this );
  676. m_pEngineSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, GetEngineSound(), ATTN_NORM );
  677. Assert(m_pEngineSound);
  678. // Start the engine sound
  679. controller.Play( m_pEngineSound, 0.0f, 100.0f );
  680. controller.SoundChangeVolume( m_pEngineSound, 1.0f, 2.0f );
  681. }
  682. float speed = GetCurrentVelocity().Length();
  683. float flVolume = 0.25f + (0.75f*(speed/GetMaxSpeed()));
  684. int iPitch = MIN( 255, 80 + (20*(speed/GetMaxSpeed())) );
  685. //Update our pitch and volume based on our speed
  686. controller.SoundChangePitch( m_pEngineSound, iPitch, 0.1f );
  687. controller.SoundChangeVolume( m_pEngineSound, flVolume, 0.1f );
  688. }
  689. //-----------------------------------------------------------------------------
  690. // Purpose:
  691. //-----------------------------------------------------------------------------
  692. void CNPC_BaseScanner::ScannerEmitSound( const char *pszSoundName )
  693. {
  694. CFmtStr snd;
  695. snd.sprintf("%s.%s", GetScannerSoundPrefix(), pszSoundName );
  696. m_bHasSpoken = true;
  697. EmitSound( snd.Access() );
  698. }
  699. //------------------------------------------------------------------------------
  700. // Purpose:
  701. //------------------------------------------------------------------------------
  702. void CNPC_BaseScanner::SpeakSentence( int sentenceType )
  703. {
  704. if (sentenceType == SCANNER_SENTENCE_ATTENTION)
  705. {
  706. ScannerEmitSound( "Attention" );
  707. }
  708. else if (sentenceType == SCANNER_SENTENCE_HANDSUP)
  709. {
  710. ScannerEmitSound( "Scan" );
  711. }
  712. else if (sentenceType == SCANNER_SENTENCE_PROCEED)
  713. {
  714. ScannerEmitSound( "Proceed" );
  715. }
  716. else if (sentenceType == SCANNER_SENTENCE_CURIOUS)
  717. {
  718. ScannerEmitSound( "Curious" );
  719. }
  720. }
  721. //-----------------------------------------------------------------------------
  722. // Purpose:
  723. //-----------------------------------------------------------------------------
  724. void CNPC_BaseScanner::InputSetFlightSpeed(inputdata_t &inputdata)
  725. {
  726. //FIXME: Currently unsupported
  727. /*
  728. m_flFlightSpeed = inputdata.value.Int();
  729. m_bFlightSpeedOverridden = (m_flFlightSpeed > 0);
  730. */
  731. }
  732. //-----------------------------------------------------------------------------
  733. // Purpose:
  734. //-----------------------------------------------------------------------------
  735. void CNPC_BaseScanner::StartSmokeTrail( void )
  736. {
  737. if ( m_pSmokeTrail != NULL )
  738. return;
  739. m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
  740. if ( m_pSmokeTrail )
  741. {
  742. m_pSmokeTrail->m_SpawnRate = 10;
  743. m_pSmokeTrail->m_ParticleLifetime = 1;
  744. m_pSmokeTrail->m_StartSize = 8;
  745. m_pSmokeTrail->m_EndSize = 50;
  746. m_pSmokeTrail->m_SpawnRadius = 10;
  747. m_pSmokeTrail->m_MinSpeed = 15;
  748. m_pSmokeTrail->m_MaxSpeed = 25;
  749. m_pSmokeTrail->m_StartColor.Init( 0.5f, 0.5f, 0.5f );
  750. m_pSmokeTrail->m_EndColor.Init( 0, 0, 0 );
  751. m_pSmokeTrail->SetLifetime( 500.0f );
  752. m_pSmokeTrail->FollowEntity( this );
  753. }
  754. }
  755. //-----------------------------------------------------------------------------
  756. // Purpose:
  757. //-----------------------------------------------------------------------------
  758. void CNPC_BaseScanner::BlendPhyscannonLaunchSpeed()
  759. {
  760. // Blend out desired velocity when launched by the physcannon
  761. if (!VPhysicsGetObject())
  762. return;
  763. if ( HasPhysicsAttacker( SCANNER_SMASH_TIME ) && !IsHeldByPhyscannon( ) )
  764. {
  765. Vector vecCurrentVelocity;
  766. VPhysicsGetObject()->GetVelocity( &vecCurrentVelocity, NULL );
  767. float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / SCANNER_SMASH_TIME;
  768. flLerpFactor = clamp( flLerpFactor, 0.0f, 1.0f );
  769. flLerpFactor = SimpleSplineRemapVal( flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f );
  770. flLerpFactor *= flLerpFactor;
  771. VectorLerp( vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity );
  772. }
  773. }
  774. //-----------------------------------------------------------------------------
  775. // Purpose:
  776. //-----------------------------------------------------------------------------
  777. void CNPC_BaseScanner::MoveExecute_Alive(float flInterval)
  778. {
  779. // Amount of noise to add to flying
  780. float noiseScale = 3.0f;
  781. // -------------------------------------------
  782. // Avoid obstacles, unless I'm dive bombing
  783. // -------------------------------------------
  784. if (m_nFlyMode != SCANNER_FLY_DIVE)
  785. {
  786. SetCurrentVelocity( GetCurrentVelocity() + VelocityToAvoidObstacles(flInterval) );
  787. }
  788. // If I am dive bombing add more noise to my flying
  789. else
  790. {
  791. AttackDivebombCollide(flInterval);
  792. noiseScale *= 4;
  793. }
  794. IPhysicsObject *pPhysics = VPhysicsGetObject();
  795. if ( pPhysics && pPhysics->IsAsleep() )
  796. {
  797. pPhysics->Wake();
  798. }
  799. // Add time-coherent noise to the current velocity so that it never looks bolted in place.
  800. AddNoiseToVelocity( noiseScale );
  801. AdjustScannerVelocity();
  802. float maxSpeed = GetEnemy() ? ( GetMaxSpeed() * 2.0f ) : GetMaxSpeed();
  803. if ( m_nFlyMode == SCANNER_FLY_DIVE )
  804. {
  805. maxSpeed = -1;
  806. }
  807. // Limit fall speed
  808. LimitSpeed( maxSpeed );
  809. // Blend out desired velocity when launched by the physcannon
  810. BlendPhyscannonLaunchSpeed();
  811. // Update what we're looking at
  812. UpdateHead( flInterval );
  813. // Control the tail based on our vertical travel
  814. float tailPerc = clamp( GetCurrentVelocity().z, -150, 250 );
  815. tailPerc = SimpleSplineRemapVal( tailPerc, -150, 250, -25, 80 );
  816. SetPoseParameter( m_nPoseTail, tailPerc );
  817. // Spin the dynamo based upon our speed
  818. float flCurrentDynamo = GetPoseParameter( m_nPoseDynamo );
  819. float speed = GetCurrentVelocity().Length();
  820. float flDynamoSpeed = (maxSpeed > 0 ? speed / maxSpeed : 1.0) * 60;
  821. flCurrentDynamo -= flDynamoSpeed;
  822. if ( flCurrentDynamo < -180.0 )
  823. {
  824. flCurrentDynamo += 360.0;
  825. }
  826. SetPoseParameter( m_nPoseDynamo, flCurrentDynamo );
  827. PlayFlySound();
  828. }
  829. //-----------------------------------------------------------------------------
  830. // Purpose: Handles movement towards the last move target.
  831. // Input : flInterval -
  832. //-----------------------------------------------------------------------------
  833. bool CNPC_BaseScanner::OverridePathMove( CBaseEntity *pMoveTarget, float flInterval )
  834. {
  835. // Save our last patrolling direction
  836. Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetAbsOrigin();
  837. // Continue on our path
  838. if ( ProgressFlyPath( flInterval, pMoveTarget, (MASK_NPCSOLID|CONTENTS_WATER), false, 64 ) == AINPP_COMPLETE )
  839. {
  840. if ( IsCurSchedule( SCHED_SCANNER_PATROL ) )
  841. {
  842. m_vLastPatrolDir = lastPatrolDir;
  843. VectorNormalize(m_vLastPatrolDir);
  844. }
  845. return true;
  846. }
  847. return false;
  848. }
  849. //-----------------------------------------------------------------------------
  850. // Purpose:
  851. // Input : flInterval -
  852. // Output : Returns true on success, false on failure.
  853. //-----------------------------------------------------------------------------
  854. bool CNPC_BaseScanner::OverrideMove( float flInterval )
  855. {
  856. // ----------------------------------------------
  857. // If dive bombing
  858. // ----------------------------------------------
  859. if (m_nFlyMode == SCANNER_FLY_DIVE)
  860. {
  861. MoveToDivebomb( flInterval );
  862. }
  863. else
  864. {
  865. Vector vMoveTargetPos(0,0,0);
  866. CBaseEntity *pMoveTarget = NULL;
  867. // The original line of code was, due to the accidental use of '|' instead of
  868. // '&', always true. Replacing with 'true' to suppress the warning without changing
  869. // the (long-standing) behavior.
  870. if ( true ) //!GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) )
  871. {
  872. // Select move target
  873. if ( GetTarget() != NULL )
  874. {
  875. pMoveTarget = GetTarget();
  876. }
  877. else if ( GetEnemy() != NULL )
  878. {
  879. pMoveTarget = GetEnemy();
  880. }
  881. // Select move target position
  882. if ( GetEnemy() != NULL )
  883. {
  884. vMoveTargetPos = GetEnemy()->GetAbsOrigin();
  885. }
  886. }
  887. else
  888. {
  889. vMoveTargetPos = GetNavigator()->GetCurWaypointPos();
  890. }
  891. ClearCondition( COND_SCANNER_FLY_CLEAR );
  892. ClearCondition( COND_SCANNER_FLY_BLOCKED );
  893. // See if we can fly there directly
  894. if ( pMoveTarget )
  895. {
  896. trace_t tr;
  897. AI_TraceHull( GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  898. float fTargetDist = (1.0f-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
  899. if ( ( tr.m_pEnt == pMoveTarget ) || ( fTargetDist < 50 ) )
  900. {
  901. if ( g_debug_basescanner.GetBool() )
  902. {
  903. NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0,255,0, true, 0);
  904. NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1);
  905. }
  906. SetCondition( COND_SCANNER_FLY_CLEAR );
  907. }
  908. else
  909. {
  910. //HANDY DEBUG TOOL
  911. if ( g_debug_basescanner.GetBool() )
  912. {
  913. NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255,0,0, true, 0);
  914. NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1);
  915. }
  916. SetCondition( COND_SCANNER_FLY_BLOCKED );
  917. }
  918. }
  919. // If I have a route, keep it updated and move toward target
  920. if ( GetNavigator()->IsGoalActive() )
  921. {
  922. if ( OverridePathMove( pMoveTarget, flInterval ) )
  923. {
  924. BlendPhyscannonLaunchSpeed();
  925. return true;
  926. }
  927. }
  928. // ----------------------------------------------
  929. // If attacking
  930. // ----------------------------------------------
  931. else if (m_nFlyMode == SCANNER_FLY_ATTACK)
  932. {
  933. MoveToAttack( flInterval );
  934. }
  935. // -----------------------------------------------------------------
  936. // If I don't have a route, just decelerate
  937. // -----------------------------------------------------------------
  938. else if (!GetNavigator()->IsGoalActive())
  939. {
  940. float myDecay = 9.5;
  941. Decelerate( flInterval, myDecay);
  942. }
  943. }
  944. MoveExecute_Alive( flInterval );
  945. return true;
  946. }
  947. //-----------------------------------------------------------------------------
  948. // Purpose:
  949. // Input : &goalPos -
  950. // &startPos -
  951. // idealRange -
  952. // idealHeight -
  953. // Output : Vector
  954. //-----------------------------------------------------------------------------
  955. Vector CNPC_BaseScanner::IdealGoalForMovement( const Vector &goalPos, const Vector &startPos, float idealRange, float idealHeightDiff )
  956. {
  957. Vector vMoveDir;
  958. if ( GetGoalDirection( &vMoveDir ) == false )
  959. {
  960. vMoveDir = ( goalPos - startPos );
  961. vMoveDir.z = 0;
  962. VectorNormalize( vMoveDir );
  963. }
  964. // Move up from the position by the desired amount
  965. Vector vIdealPos = goalPos + Vector( 0, 0, idealHeightDiff ) + ( vMoveDir * -idealRange );
  966. // Trace down and make sure we can fit here
  967. trace_t tr;
  968. AI_TraceHull( vIdealPos, vIdealPos - Vector( 0, 0, MinGroundDist() ), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  969. // Move up otherwise
  970. if ( tr.fraction < 1.0f )
  971. {
  972. vIdealPos.z += ( MinGroundDist() * ( 1.0f - tr.fraction ) );
  973. }
  974. //FIXME: We also need to make sure that we fit here at all, and if not, chose a new spot
  975. // Debug tools
  976. if ( g_debug_basescanner.GetBool() )
  977. {
  978. NDebugOverlay::Cross3D( goalPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 0, true, 0.1f );
  979. NDebugOverlay::Cross3D( startPos, -Vector(8,8,8), Vector(8,8,8), 255, 0, 255, true, 0.1f );
  980. NDebugOverlay::Cross3D( vIdealPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
  981. NDebugOverlay::Line( startPos, goalPos, 0, 255, 0, true, 0.1f );
  982. NDebugOverlay::Cross3D( goalPos + ( vMoveDir * -idealRange ), -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
  983. NDebugOverlay::Line( goalPos, goalPos + ( vMoveDir * -idealRange ), 255, 255, 0, true, 0.1f );
  984. NDebugOverlay::Line( goalPos + ( vMoveDir * -idealRange ), vIdealPos, 255, 255, 0, true, 0.1f );
  985. }
  986. return vIdealPos;
  987. }
  988. //-----------------------------------------------------------------------------
  989. // Purpose:
  990. // Input : flInterval -
  991. //-----------------------------------------------------------------------------
  992. void CNPC_BaseScanner::MoveToAttack(float flInterval)
  993. {
  994. if (GetEnemy() == NULL)
  995. return;
  996. if ( flInterval <= 0 )
  997. return;
  998. Vector vTargetPos = GetEnemyLKP();
  999. //float flDesiredDist = m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 );
  1000. Vector idealPos = IdealGoalForMovement( vTargetPos, GetAbsOrigin(), GetGoalDistance(), m_flAttackNearDist );
  1001. MoveToTarget( flInterval, idealPos );
  1002. //FIXME: Re-implement?
  1003. /*
  1004. // ---------------------------------------------------------
  1005. // Add evasion if I have taken damage recently
  1006. // ---------------------------------------------------------
  1007. if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime)
  1008. {
  1009. vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer());
  1010. }
  1011. */
  1012. }
  1013. //-----------------------------------------------------------------------------
  1014. // Purpose: Accelerates toward a given position.
  1015. // Input : flInterval - Time interval over which to move.
  1016. // vecMoveTarget - Position to move toward.
  1017. //-----------------------------------------------------------------------------
  1018. void CNPC_BaseScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget )
  1019. {
  1020. // Don't move if stalling
  1021. if ( m_flEngineStallTime > gpGlobals->curtime )
  1022. return;
  1023. // Look at our inspection target if we have one
  1024. if ( GetEnemy() != NULL )
  1025. {
  1026. // Otherwise at our enemy
  1027. TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() );
  1028. }
  1029. else
  1030. {
  1031. // Otherwise face our motion direction
  1032. TurnHeadToTarget( flInterval, vecMoveTarget );
  1033. }
  1034. // -------------------------------------
  1035. // Move towards our target
  1036. // -------------------------------------
  1037. float myAccel;
  1038. float myZAccel = 400.0f;
  1039. float myDecay = 0.15f;
  1040. Vector vecCurrentDir;
  1041. // Get the relationship between my current velocity and the way I want to be going.
  1042. vecCurrentDir = GetCurrentVelocity();
  1043. VectorNormalize( vecCurrentDir );
  1044. Vector targetDir = vecMoveTarget - GetAbsOrigin();
  1045. float flDist = VectorNormalize(targetDir);
  1046. float flDot;
  1047. flDot = DotProduct( targetDir, vecCurrentDir );
  1048. if( flDot > 0.25 )
  1049. {
  1050. // If my target is in front of me, my flight model is a bit more accurate.
  1051. myAccel = 250;
  1052. }
  1053. else
  1054. {
  1055. // Have a harder time correcting my course if I'm currently flying away from my target.
  1056. myAccel = 128;
  1057. }
  1058. if ( myAccel > flDist / flInterval )
  1059. {
  1060. myAccel = flDist / flInterval;
  1061. }
  1062. if ( myZAccel > flDist / flInterval )
  1063. {
  1064. myZAccel = flDist / flInterval;
  1065. }
  1066. MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay );
  1067. // calc relative banking targets
  1068. Vector forward, right, up;
  1069. GetVectors( &forward, &right, &up );
  1070. m_vCurrentBanking.x = targetDir.x;
  1071. m_vCurrentBanking.z = 120.0f * DotProduct( right, targetDir );
  1072. m_vCurrentBanking.y = 0;
  1073. float speedPerc = SimpleSplineRemapVal( GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f );
  1074. speedPerc = clamp( speedPerc, 0.0f, 1.0f );
  1075. m_vCurrentBanking *= speedPerc;
  1076. }
  1077. //-----------------------------------------------------------------------------
  1078. // Danger sounds.
  1079. //-----------------------------------------------------------------------------
  1080. void CNPC_BaseScanner::DiveBombSoundThink()
  1081. {
  1082. Vector vecPosition, vecVelocity;
  1083. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  1084. if ( pPhysicsObject == NULL )
  1085. return;
  1086. pPhysicsObject->GetPosition( &vecPosition, NULL );
  1087. pPhysicsObject->GetVelocity( &vecVelocity, NULL );
  1088. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  1089. if ( pPlayer )
  1090. {
  1091. Vector vecDelta;
  1092. VectorSubtract( pPlayer->GetAbsOrigin(), vecPosition, vecDelta );
  1093. VectorNormalize( vecDelta );
  1094. if ( DotProduct( vecDelta, vecVelocity ) > 0.5f )
  1095. {
  1096. Vector vecEndPoint;
  1097. VectorMA( vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint );
  1098. float flDist = CalcDistanceToLineSegment( pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint );
  1099. if ( flDist < 200.0f )
  1100. {
  1101. ScannerEmitSound( "DiveBombFlyby" );
  1102. SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 0.5f, s_pDiveBombSoundThinkContext );
  1103. return;
  1104. }
  1105. }
  1106. }
  1107. SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pDiveBombSoundThinkContext );
  1108. }
  1109. //-----------------------------------------------------------------------------
  1110. // Purpose:
  1111. // Input : flInterval -
  1112. //-----------------------------------------------------------------------------
  1113. void CNPC_BaseScanner::MoveToDivebomb(float flInterval)
  1114. {
  1115. float myAccel = 1600;
  1116. float myDecay = 0.05f; // decay current velocity to 10% in 1 second
  1117. // Fly towards my enemy
  1118. Vector vEnemyPos = GetEnemyLKP();
  1119. Vector vFlyDirection = vEnemyPos - GetLocalOrigin();
  1120. VectorNormalize( vFlyDirection );
  1121. // Set net velocity
  1122. MoveInDirection( flInterval, m_vecDiveBombDirection, myAccel, myAccel, myDecay);
  1123. // Spin out of control.
  1124. Vector forward;
  1125. VPhysicsGetObject()->LocalToWorldVector( &forward, Vector( 1.0, 0.0, 0.0 ) );
  1126. AngularImpulse torque = forward * m_flDiveBombRollForce;
  1127. VPhysicsGetObject()->ApplyTorqueCenter( torque );
  1128. // BUGBUG: why Y axis and not Z?
  1129. Vector up;
  1130. VPhysicsGetObject()->LocalToWorldVector( &up, Vector( 0.0, 1.0, 0.0 ) );
  1131. VPhysicsGetObject()->ApplyForceCenter( up * 2000 );
  1132. }
  1133. //-----------------------------------------------------------------------------
  1134. // Purpose:
  1135. //-----------------------------------------------------------------------------
  1136. bool CNPC_BaseScanner::IsEnemyPlayerInSuit()
  1137. {
  1138. if( GetEnemy() && GetEnemy()->IsPlayer() )
  1139. {
  1140. CHL2_Player *pPlayer = NULL;
  1141. pPlayer = (CHL2_Player *)GetEnemy();
  1142. if( pPlayer && pPlayer->IsSuitEquipped() )
  1143. {
  1144. return true;
  1145. }
  1146. }
  1147. return false;
  1148. }
  1149. //-----------------------------------------------------------------------------
  1150. // Purpose:
  1151. // Output : float
  1152. //-----------------------------------------------------------------------------
  1153. float CNPC_BaseScanner::GetGoalDistance( void )
  1154. {
  1155. if ( m_flGoalOverrideDistance != 0.0f )
  1156. return m_flGoalOverrideDistance;
  1157. switch ( m_nFlyMode )
  1158. {
  1159. case SCANNER_FLY_ATTACK:
  1160. {
  1161. float goalDist = ( m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 ) );
  1162. if( IsEnemyPlayerInSuit() )
  1163. {
  1164. goalDist *= 0.5;
  1165. }
  1166. return goalDist;
  1167. }
  1168. break;
  1169. }
  1170. return 128.0f;
  1171. }
  1172. //-----------------------------------------------------------------------------
  1173. // Purpose:
  1174. // Input : &vOut -
  1175. // Output : Returns true on success, false on failure.
  1176. //-----------------------------------------------------------------------------
  1177. bool CNPC_BaseScanner::GetGoalDirection( Vector *vOut )
  1178. {
  1179. CBaseEntity *pTarget = GetTarget();
  1180. if ( pTarget == NULL )
  1181. return false;
  1182. if ( FClassnameIs( pTarget, "info_hint_air" ) || FClassnameIs( pTarget, "info_target" ) )
  1183. {
  1184. AngleVectors( pTarget->GetAbsAngles(), vOut );
  1185. return true;
  1186. }
  1187. return false;
  1188. }
  1189. //-----------------------------------------------------------------------------
  1190. // Purpose:
  1191. //-----------------------------------------------------------------------------
  1192. Vector CNPC_BaseScanner::VelocityToEvade(CBaseCombatCharacter *pEnemy)
  1193. {
  1194. if (pEnemy)
  1195. {
  1196. // -----------------------------------------
  1197. // Keep out of enemy's shooting position
  1198. // -----------------------------------------
  1199. Vector vEnemyFacing = pEnemy->BodyDirection2D( );
  1200. Vector vEnemyDir = pEnemy->EyePosition() - GetLocalOrigin();
  1201. VectorNormalize(vEnemyDir);
  1202. float fDotPr = DotProduct(vEnemyFacing,vEnemyDir);
  1203. if (fDotPr < -0.9)
  1204. {
  1205. Vector vDirUp(0,0,1);
  1206. Vector vDir;
  1207. CrossProduct( vEnemyFacing, vDirUp, vDir);
  1208. Vector crossProduct;
  1209. CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
  1210. if (crossProduct.y < 0)
  1211. {
  1212. vDir = vDir * -1;
  1213. }
  1214. return (vDir);
  1215. }
  1216. else if (fDotPr < -0.85)
  1217. {
  1218. Vector vDirUp(0,0,1);
  1219. Vector vDir;
  1220. CrossProduct( vEnemyFacing, vDirUp, vDir);
  1221. Vector crossProduct;
  1222. CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
  1223. if (random->RandomInt(0,1))
  1224. {
  1225. vDir = vDir * -1;
  1226. }
  1227. return (vDir);
  1228. }
  1229. }
  1230. return vec3_origin;
  1231. }
  1232. //-----------------------------------------------------------------------------
  1233. // Purpose:
  1234. //-----------------------------------------------------------------------------
  1235. int CNPC_BaseScanner::DrawDebugTextOverlays(void)
  1236. {
  1237. int nOffset = BaseClass::DrawDebugTextOverlays();
  1238. if ( m_debugOverlays & OVERLAY_TEXT_BIT )
  1239. {
  1240. Vector vel;
  1241. GetVelocity( &vel, NULL );
  1242. char tempstr[512];
  1243. Q_snprintf( tempstr, sizeof(tempstr), "speed (max): %.2f (%.2f)", vel.Length(), m_flSpeed );
  1244. EntityText( nOffset, tempstr, 0 );
  1245. nOffset++;
  1246. }
  1247. return nOffset;
  1248. }
  1249. //-----------------------------------------------------------------------------
  1250. // Purpose:
  1251. // Output : float
  1252. //-----------------------------------------------------------------------------
  1253. float CNPC_BaseScanner::GetHeadTurnRate( void )
  1254. {
  1255. if ( GetEnemy() )
  1256. return 800.0f;
  1257. return 350.0f;
  1258. }
  1259. //-----------------------------------------------------------------------------
  1260. // Purpose:
  1261. // Output : Returns true on success, false on failure.
  1262. //-----------------------------------------------------------------------------
  1263. inline CBaseEntity *CNPC_BaseScanner::EntityToWatch( void )
  1264. {
  1265. return ( GetTarget() != NULL ) ? GetTarget() : GetEnemy(); // Okay if NULL
  1266. }
  1267. //-----------------------------------------------------------------------------
  1268. // Purpose:
  1269. // Input : flInterval -
  1270. //-----------------------------------------------------------------------------
  1271. void CNPC_BaseScanner::UpdateHead( float flInterval )
  1272. {
  1273. float yaw = GetPoseParameter( m_nPoseFaceHoriz );
  1274. float pitch = GetPoseParameter( m_nPoseFaceVert );
  1275. CBaseEntity *pTarget = EntityToWatch();
  1276. Vector vLookPos;
  1277. if ( !HasCondition( COND_IN_PVS ) || GetAttachment( "eyes", vLookPos ) == false )
  1278. {
  1279. vLookPos = EyePosition();
  1280. }
  1281. if ( pTarget != NULL )
  1282. {
  1283. Vector lookDir = pTarget->EyePosition() - vLookPos;
  1284. VectorNormalize( lookDir );
  1285. if ( DotProduct( lookDir, BodyDirection3D() ) < 0.0f )
  1286. {
  1287. SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) );
  1288. SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );
  1289. return;
  1290. }
  1291. float facingYaw = VecToYaw( BodyDirection3D() );
  1292. float yawDiff = VecToYaw( lookDir );
  1293. yawDiff = UTIL_AngleDiff( yawDiff, facingYaw + yaw );
  1294. float facingPitch = UTIL_VecToPitch( BodyDirection3D() );
  1295. float pitchDiff = UTIL_VecToPitch( lookDir );
  1296. pitchDiff = UTIL_AngleDiff( pitchDiff, facingPitch + pitch );
  1297. SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( yaw + yawDiff, yaw, 50 ) );
  1298. SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( pitch + pitchDiff, pitch, 50 ) );
  1299. }
  1300. else
  1301. {
  1302. SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( 0, yaw, 10 ) );
  1303. SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );
  1304. }
  1305. }
  1306. //-----------------------------------------------------------------------------
  1307. // Purpose:
  1308. // Input : &linear -
  1309. // &angular -
  1310. //-----------------------------------------------------------------------------
  1311. void CNPC_BaseScanner::ClampMotorForces( Vector &linear, AngularImpulse &angular )
  1312. {
  1313. // limit reaction forces
  1314. if ( m_nFlyMode != SCANNER_FLY_DIVE )
  1315. {
  1316. linear.x = clamp( linear.x, -500, 500 );
  1317. linear.y = clamp( linear.y, -500, 500 );
  1318. linear.z = clamp( linear.z, -500, 500 );
  1319. }
  1320. // If we're dive bombing, we need to drop faster than normal
  1321. if ( m_nFlyMode != SCANNER_FLY_DIVE )
  1322. {
  1323. // Add in weightlessness
  1324. linear.z += 800;
  1325. }
  1326. angular.z = clamp( angular.z, -GetHeadTurnRate(), GetHeadTurnRate() );
  1327. if ( m_nFlyMode == SCANNER_FLY_DIVE )
  1328. {
  1329. // Disable pitch and roll motors while crashing.
  1330. angular.x = 0;
  1331. angular.y = 0;
  1332. }
  1333. }
  1334. //-----------------------------------------------------------------------------
  1335. // Purpose:
  1336. // Input : &inputdata -
  1337. //-----------------------------------------------------------------------------
  1338. void CNPC_BaseScanner::InputSetDistanceOverride( inputdata_t &inputdata )
  1339. {
  1340. m_flGoalOverrideDistance = inputdata.value.Float();
  1341. }
  1342. //-----------------------------------------------------------------------------
  1343. // Purpose: Emit sounds specific to the NPC's state.
  1344. //-----------------------------------------------------------------------------
  1345. void CNPC_BaseScanner::AlertSound(void)
  1346. {
  1347. ScannerEmitSound( "Alert" );
  1348. }
  1349. //------------------------------------------------------------------------------
  1350. // Purpose:
  1351. //------------------------------------------------------------------------------
  1352. void CNPC_BaseScanner::DeathSound( const CTakeDamageInfo &info )
  1353. {
  1354. ScannerEmitSound( "Die" );
  1355. }
  1356. //-----------------------------------------------------------------------------
  1357. // Purpose: Overridden so that scanners play battle sounds while fighting.
  1358. // Output : Returns TRUE on success, FALSE on failure.
  1359. //-----------------------------------------------------------------------------
  1360. bool CNPC_BaseScanner::ShouldPlayIdleSound( void )
  1361. {
  1362. if ( HasSpawnFlags( SF_NPC_GAG ) )
  1363. return false;
  1364. if ( random->RandomInt( 0, 25 ) != 0 )
  1365. return false;
  1366. return true;
  1367. }
  1368. //-----------------------------------------------------------------------------
  1369. // Purpose: Plays sounds while idle or in combat.
  1370. //-----------------------------------------------------------------------------
  1371. void CNPC_BaseScanner::IdleSound(void)
  1372. {
  1373. if ( m_NPCState == NPC_STATE_COMBAT )
  1374. {
  1375. // dvs: the combat sounds should be related to what is happening, rather than random
  1376. ScannerEmitSound( "Combat" );
  1377. }
  1378. else
  1379. {
  1380. ScannerEmitSound( "Idle" );
  1381. }
  1382. }
  1383. //-----------------------------------------------------------------------------
  1384. // Purpose: Plays a sound when hurt.
  1385. //-----------------------------------------------------------------------------
  1386. void CNPC_BaseScanner::PainSound( const CTakeDamageInfo &info )
  1387. {
  1388. ScannerEmitSound( "Pain" );
  1389. }
  1390. //-----------------------------------------------------------------------------
  1391. // Purpose:
  1392. //-----------------------------------------------------------------------------
  1393. float CNPC_BaseScanner::GetMaxSpeed()
  1394. {
  1395. return SCANNER_MAX_SPEED;
  1396. }
  1397. //-----------------------------------------------------------------------------
  1398. //
  1399. // Schedules
  1400. //
  1401. //-----------------------------------------------------------------------------
  1402. AI_BEGIN_CUSTOM_NPC( npc_basescanner, CNPC_BaseScanner )
  1403. DECLARE_TASK( TASK_SCANNER_SET_FLY_PATROL )
  1404. DECLARE_TASK( TASK_SCANNER_SET_FLY_CHASE )
  1405. DECLARE_TASK( TASK_SCANNER_SET_FLY_ATTACK )
  1406. DECLARE_TASK( TASK_SCANNER_SET_FLY_DIVE )
  1407. DECLARE_CONDITION(COND_SCANNER_FLY_CLEAR)
  1408. DECLARE_CONDITION(COND_SCANNER_FLY_BLOCKED)
  1409. DECLARE_CONDITION(COND_SCANNER_RELEASED_FROM_PHYSCANNON)
  1410. DECLARE_CONDITION(COND_SCANNER_GRABBED_BY_PHYSCANNON)
  1411. //=========================================================
  1412. // > SCHED_SCANNER_PATROL
  1413. //=========================================================
  1414. DEFINE_SCHEDULE
  1415. (
  1416. SCHED_SCANNER_PATROL,
  1417. " Tasks"
  1418. " TASK_SCANNER_SET_FLY_PATROL 0"
  1419. " TASK_SET_TOLERANCE_DISTANCE 32"
  1420. " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
  1421. " TASK_GET_PATH_TO_RANDOM_NODE 2000"
  1422. " TASK_RUN_PATH 0"
  1423. " TASK_WAIT_FOR_MOVEMENT 0"
  1424. ""
  1425. " Interrupts"
  1426. " COND_GIVE_WAY"
  1427. " COND_NEW_ENEMY"
  1428. " COND_SEE_ENEMY"
  1429. " COND_SEE_FEAR"
  1430. " COND_HEAR_COMBAT"
  1431. " COND_HEAR_DANGER"
  1432. " COND_HEAR_PLAYER"
  1433. " COND_LIGHT_DAMAGE"
  1434. " COND_HEAVY_DAMAGE"
  1435. " COND_PROVOKED"
  1436. " COND_SCANNER_GRABBED_BY_PHYSCANNON"
  1437. )
  1438. //=========================================================
  1439. // > SCHED_SCANNER_ATTACK
  1440. //
  1441. // This task does nothing. Translate it in your derived
  1442. // class to perform your attack.
  1443. //
  1444. //=========================================================
  1445. DEFINE_SCHEDULE
  1446. (
  1447. SCHED_SCANNER_ATTACK,
  1448. " Tasks"
  1449. " TASK_SCANNER_SET_FLY_ATTACK 0"
  1450. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1451. " TASK_WAIT 0.1"
  1452. ""
  1453. " Interrupts"
  1454. " COND_TOO_FAR_TO_ATTACK"
  1455. " COND_SCANNER_FLY_BLOCKED"
  1456. " COND_NEW_ENEMY"
  1457. " COND_SCANNER_GRABBED_BY_PHYSCANNON"
  1458. )
  1459. //=========================================================
  1460. // > SCHED_SCANNER_ATTACK_HOVER
  1461. //=========================================================
  1462. DEFINE_SCHEDULE
  1463. (
  1464. SCHED_SCANNER_ATTACK_HOVER,
  1465. " Tasks"
  1466. " TASK_SCANNER_SET_FLY_ATTACK 0"
  1467. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1468. " TASK_WAIT 0.1"
  1469. ""
  1470. " Interrupts"
  1471. " COND_TOO_FAR_TO_ATTACK"
  1472. " COND_SCANNER_FLY_BLOCKED"
  1473. " COND_NEW_ENEMY"
  1474. " COND_SCANNER_GRABBED_BY_PHYSCANNON"
  1475. )
  1476. //=========================================================
  1477. // > SCHED_SCANNER_ATTACK_DIVEBOMB
  1478. //
  1479. // Only done when scanner is dead
  1480. //=========================================================
  1481. DEFINE_SCHEDULE
  1482. (
  1483. SCHED_SCANNER_ATTACK_DIVEBOMB,
  1484. " Tasks"
  1485. " TASK_SCANNER_SET_FLY_DIVE 0"
  1486. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1487. " TASK_WAIT 10"
  1488. ""
  1489. " Interrupts"
  1490. " COND_SCANNER_GRABBED_BY_PHYSCANNON"
  1491. )
  1492. //=========================================================
  1493. // > SCHED_SCANNER_CHASE_ENEMY
  1494. //
  1495. // Different interrupts than normal chase enemy.
  1496. //=========================================================
  1497. DEFINE_SCHEDULE
  1498. (
  1499. SCHED_SCANNER_CHASE_ENEMY,
  1500. " Tasks"
  1501. " TASK_SCANNER_SET_FLY_CHASE 0"
  1502. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL"
  1503. " TASK_SET_TOLERANCE_DISTANCE 120"
  1504. " TASK_GET_PATH_TO_ENEMY 0"
  1505. " TASK_RUN_PATH 0"
  1506. " TASK_WAIT_FOR_MOVEMENT 0"
  1507. ""
  1508. ""
  1509. " Interrupts"
  1510. " COND_SCANNER_FLY_CLEAR"
  1511. " COND_NEW_ENEMY"
  1512. " COND_ENEMY_DEAD"
  1513. " COND_LOST_ENEMY"
  1514. " COND_SCANNER_GRABBED_BY_PHYSCANNON"
  1515. )
  1516. //=========================================================
  1517. // > SCHED_SCANNER_CHASE_TARGET
  1518. //=========================================================
  1519. DEFINE_SCHEDULE
  1520. (
  1521. SCHED_SCANNER_CHASE_TARGET,
  1522. " Tasks"
  1523. " TASK_SCANNER_SET_FLY_CHASE 0"
  1524. " TASK_SET_TOLERANCE_DISTANCE 64"
  1525. " TASK_GET_PATH_TO_TARGET 0" //FIXME: This is wrong!
  1526. " TASK_RUN_PATH 0"
  1527. " TASK_WAIT_FOR_MOVEMENT 0"
  1528. ""
  1529. " Interrupts"
  1530. " COND_SCANNER_FLY_CLEAR"
  1531. " COND_NEW_ENEMY"
  1532. " COND_SCANNER_GRABBED_BY_PHYSCANNON"
  1533. )
  1534. //=========================================================
  1535. // > SCHED_SCANNER_FOLLOW_HOVER
  1536. //=========================================================
  1537. DEFINE_SCHEDULE
  1538. (
  1539. SCHED_SCANNER_FOLLOW_HOVER,
  1540. " Tasks"
  1541. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1542. " TASK_WAIT 0.1"
  1543. ""
  1544. " Interrupts"
  1545. " COND_SCANNER_FLY_BLOCKED"
  1546. " COND_SCANNER_GRABBED_BY_PHYSCANNON"
  1547. )
  1548. //=========================================================
  1549. // > SCHED_SCANNER_HELD_BY_PHYSCANNON
  1550. //=========================================================
  1551. DEFINE_SCHEDULE
  1552. (
  1553. SCHED_SCANNER_HELD_BY_PHYSCANNON,
  1554. " Tasks"
  1555. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1556. " TASK_WAIT 5.0"
  1557. ""
  1558. " Interrupts"
  1559. " COND_LIGHT_DAMAGE"
  1560. " COND_HEAVY_DAMAGE"
  1561. " COND_SCANNER_RELEASED_FROM_PHYSCANNON"
  1562. )
  1563. AI_END_CUSTOM_NPC()