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.

774 lines
22 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
  4. // events
  5. //
  6. // $NoKeywords: $
  7. //=============================================================================//
  8. #include "cbase.h"
  9. #include "basecombatcharacter.h"
  10. #include "ai_basenpc.h"
  11. #include "decals.h"
  12. #include "IEffects.h"
  13. #include "ai_squad.h"
  14. #include "ai_utils.h"
  15. #include "ai_senses.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. #define SF_ENEMY_FINDER_CHECK_VIS (1 << 16)
  19. #define SF_ENEMY_FINDER_APC_VIS (1 << 17)
  20. #define SF_ENEMY_FINDER_SHORT_MEMORY (1 << 18)
  21. #define SF_ENEMY_FINDER_ENEMY_ALLOWED (1 << 19)
  22. ConVar ai_debug_enemyfinders( "ai_debug_enemyfinders", "0" );
  23. class CNPC_EnemyFinder : public CAI_BaseNPC
  24. {
  25. public:
  26. DECLARE_CLASS( CNPC_EnemyFinder, CAI_BaseNPC );
  27. CNPC_EnemyFinder()
  28. {
  29. m_PlayerFreePass.SetOuter( this );
  30. }
  31. void Precache( void );
  32. void Spawn( void );
  33. void StartNPC ( void );
  34. void PrescheduleThink();
  35. bool ShouldAlwaysThink();
  36. void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
  37. void GatherConditions( void );
  38. bool ShouldChooseNewEnemy();
  39. bool IsValidEnemy( CBaseEntity *pTarget );
  40. bool CanBeAnEnemyOf( CBaseEntity *pEnemy ) { return HasSpawnFlags( SF_ENEMY_FINDER_ENEMY_ALLOWED ); }
  41. bool FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker );
  42. Class_T Classify( void );
  43. bool CanBeSeenBy( CAI_BaseNPC *pNPC ) { return CanBeAnEnemyOf( pNPC ); } // allows entities to be 'invisible' to NPC senses.
  44. virtual int SelectSchedule( void );
  45. virtual void DrawDebugGeometryOverlays( void );
  46. // Input handlers.
  47. void InputTurnOn( inputdata_t &inputdata );
  48. void InputTurnOff( inputdata_t &inputdata );
  49. virtual void Wake( bool bFireOutput = true );
  50. private:
  51. int m_nStartOn;
  52. float m_flMinSearchDist;
  53. float m_flMaxSearchDist;
  54. CAI_FreePass m_PlayerFreePass;
  55. CSimpleSimTimer m_ChooseEnemyTimer;
  56. bool m_bEnemyStatus;
  57. COutputEvent m_OnLostEnemies;
  58. COutputEvent m_OnAcquireEnemies;
  59. DECLARE_DATADESC();
  60. DEFINE_CUSTOM_AI;
  61. };
  62. LINK_ENTITY_TO_CLASS( npc_enemyfinder, CNPC_EnemyFinder );
  63. //-----------------------------------------------------------------------------
  64. // Custom schedules.
  65. //-----------------------------------------------------------------------------
  66. enum
  67. {
  68. SCHED_EFINDER_SEARCH = LAST_SHARED_SCHEDULE,
  69. };
  70. IMPLEMENT_CUSTOM_AI( npc_enemyfinder, CNPC_EnemyFinder );
  71. BEGIN_DATADESC( CNPC_EnemyFinder )
  72. DEFINE_EMBEDDED( m_PlayerFreePass ),
  73. DEFINE_EMBEDDED( m_ChooseEnemyTimer ),
  74. // Inputs
  75. DEFINE_INPUT( m_nStartOn, FIELD_INTEGER, "StartOn" ),
  76. DEFINE_INPUT( m_flFieldOfView, FIELD_FLOAT, "FieldOfView" ),
  77. DEFINE_INPUT( m_flMinSearchDist, FIELD_FLOAT, "MinSearchDist" ),
  78. DEFINE_INPUT( m_flMaxSearchDist, FIELD_FLOAT, "MaxSearchDist" ),
  79. DEFINE_FIELD( m_bEnemyStatus, FIELD_BOOLEAN ),
  80. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
  81. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
  82. DEFINE_OUTPUT( m_OnLostEnemies, "OnLostEnemies"),
  83. DEFINE_OUTPUT( m_OnAcquireEnemies, "OnAcquireEnemies"),
  84. END_DATADESC()
  85. //-----------------------------------------------------------------------------
  86. // Purpose:
  87. //-----------------------------------------------------------------------------
  88. void CNPC_EnemyFinder::InitCustomSchedules( void )
  89. {
  90. INIT_CUSTOM_AI( CNPC_EnemyFinder );
  91. ADD_CUSTOM_SCHEDULE( CNPC_EnemyFinder, SCHED_EFINDER_SEARCH );
  92. AI_LOAD_SCHEDULE( CNPC_EnemyFinder, SCHED_EFINDER_SEARCH );
  93. }
  94. //-----------------------------------------------------------------------------
  95. // Purpose: Input handler for turning the enemy finder on.
  96. //-----------------------------------------------------------------------------
  97. void CNPC_EnemyFinder::InputTurnOn( inputdata_t &inputdata )
  98. {
  99. SetThink( &CNPC_EnemyFinder::CallNPCThink );
  100. SetNextThink( gpGlobals->curtime );
  101. }
  102. //-----------------------------------------------------------------------------
  103. // Purpose: Input handler for turning the enemy finder off.
  104. //-----------------------------------------------------------------------------
  105. void CNPC_EnemyFinder::InputTurnOff( inputdata_t &inputdata )
  106. {
  107. SetThink(NULL);
  108. }
  109. //-----------------------------------------------------------------------------
  110. // Purpose:
  111. //-----------------------------------------------------------------------------
  112. void CNPC_EnemyFinder::Precache( void )
  113. {
  114. PrecacheModel( "models/player.mdl" );
  115. BaseClass::Precache();
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Purpose:
  119. //
  120. //
  121. //-----------------------------------------------------------------------------
  122. void CNPC_EnemyFinder::Spawn( void )
  123. {
  124. Precache();
  125. SetModel( "models/player.mdl" );
  126. // This is a dummy model that is never used!
  127. UTIL_SetSize(this, vec3_origin, vec3_origin);
  128. SetMoveType( MOVETYPE_NONE );
  129. SetBloodColor( DONT_BLEED );
  130. SetGravity( 0.0 );
  131. m_iHealth = 1;
  132. AddFlag( FL_NPC );
  133. SetSolid( SOLID_NONE );
  134. m_bEnemyStatus = false;
  135. if (m_flFieldOfView < -1.0)
  136. {
  137. DevMsg("ERROR: EnemyFinder field of view must be between -1.0 and 1.0\n");
  138. m_flFieldOfView = 0.5;
  139. }
  140. else if (m_flFieldOfView > 1.0)
  141. {
  142. DevMsg("ERROR: EnemyFinder field of view must be between -1.0 and 1.0\n");
  143. m_flFieldOfView = 1.0;
  144. }
  145. CapabilitiesAdd ( bits_CAP_SQUAD );
  146. NPCInit();
  147. // Set this after NPCInit()
  148. m_takedamage = DAMAGE_NO;
  149. AddEffects( EF_NODRAW );
  150. m_NPCState = NPC_STATE_ALERT; // always alert
  151. SetViewOffset( vec3_origin );
  152. if ( m_flMaxSearchDist )
  153. {
  154. SetDistLook( m_flMaxSearchDist );
  155. }
  156. if ( HasSpawnFlags( SF_ENEMY_FINDER_SHORT_MEMORY ) )
  157. {
  158. GetEnemies()->SetEnemyDiscardTime( 0.2 );
  159. }
  160. }
  161. //-----------------------------------------------------------------------------
  162. // Purpose:
  163. // Output :
  164. //-----------------------------------------------------------------------------
  165. int CNPC_EnemyFinder::SelectSchedule( void )
  166. {
  167. return SCHED_EFINDER_SEARCH;
  168. }
  169. //------------------------------------------------------------------------------
  170. //
  171. //------------------------------------------------------------------------------
  172. void CNPC_EnemyFinder::Wake( bool bFireOutput )
  173. {
  174. BaseClass::Wake( bFireOutput );
  175. //Enemy finder is not allowed to become visible.
  176. AddEffects( EF_NODRAW );
  177. }
  178. //------------------------------------------------------------------------------
  179. //
  180. //------------------------------------------------------------------------------
  181. bool CNPC_EnemyFinder::FVisible( CBaseEntity *pTarget, int traceMask, CBaseEntity **ppBlocker )
  182. {
  183. float flTargetDist = GetAbsOrigin().DistTo( pTarget->GetAbsOrigin() );
  184. if ( flTargetDist < m_flMinSearchDist)
  185. return false;
  186. if ( m_flMaxSearchDist && flTargetDist > m_flMaxSearchDist)
  187. return false;
  188. if ( !FBitSet( m_spawnflags, SF_ENEMY_FINDER_CHECK_VIS) )
  189. return true;
  190. if ( !HasSpawnFlags(SF_ENEMY_FINDER_APC_VIS) )
  191. {
  192. bool bIsVisible = BaseClass::FVisible( pTarget, traceMask, ppBlocker );
  193. if ( bIsVisible && pTarget == m_PlayerFreePass.GetPassTarget() )
  194. bIsVisible = m_PlayerFreePass.ShouldAllowFVisible( bIsVisible );
  195. return bIsVisible;
  196. }
  197. // Make sure I can see the target from my position
  198. trace_t tr;
  199. // Trace from launch position to target position.
  200. // Use position above actual barral based on vertical launch speed
  201. Vector vStartPos = GetAbsOrigin();
  202. Vector vEndPos = pTarget->EyePosition();
  203. CBaseEntity *pVehicle = NULL;
  204. if ( pTarget->IsPlayer() )
  205. {
  206. CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(pTarget);
  207. pVehicle = pPlayer->GetVehicleEntity();
  208. }
  209. CTraceFilterSkipTwoEntities traceFilter( pTarget, pVehicle, COLLISION_GROUP_NONE );
  210. AI_TraceLine( vStartPos, vEndPos, MASK_SHOT, &traceFilter, &tr );
  211. if ( ppBlocker )
  212. {
  213. *ppBlocker = tr.m_pEnt;
  214. }
  215. return (tr.fraction == 1.0);
  216. }
  217. //------------------------------------------------------------------------------
  218. bool CNPC_EnemyFinder::ShouldChooseNewEnemy()
  219. {
  220. if ( m_ChooseEnemyTimer.Expired() )
  221. {
  222. m_ChooseEnemyTimer.Set( 0.3 );
  223. return true;
  224. }
  225. return false;
  226. }
  227. //------------------------------------------------------------------------------
  228. // Purpose : Override base class to check range and visibility
  229. // Input :
  230. // Output :
  231. //------------------------------------------------------------------------------
  232. bool CNPC_EnemyFinder::IsValidEnemy( CBaseEntity *pTarget )
  233. {
  234. float flTargetDist = GetAbsOrigin().DistTo( pTarget->GetAbsOrigin() );
  235. if (flTargetDist < m_flMinSearchDist)
  236. return false;
  237. if ( m_flMaxSearchDist && flTargetDist > m_flMaxSearchDist)
  238. return false;
  239. if ( !FBitSet( m_spawnflags, SF_ENEMY_FINDER_CHECK_VIS) )
  240. return true;
  241. if ( GetSenses()->DidSeeEntity( pTarget ) )
  242. return true;
  243. // Trace from launch position to target position.
  244. // Use position above actual barral based on vertical launch speed
  245. Vector vStartPos = GetAbsOrigin();
  246. Vector vEndPos = pTarget->EyePosition();
  247. // Test our line of sight to the target
  248. trace_t tr;
  249. AI_TraceLOS( vStartPos, vEndPos, this, &tr );
  250. // If the player is in a vehicle, see if we can see that instead
  251. if ( pTarget->IsPlayer() )
  252. {
  253. CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(pTarget);
  254. if ( tr.m_pEnt == pPlayer->GetVehicleEntity() )
  255. return true;
  256. }
  257. // Line must be clear
  258. if ( tr.fraction == 1.0f || tr.m_pEnt == pTarget )
  259. return true;
  260. // Otherwise we can't see anything
  261. return false;
  262. }
  263. //------------------------------------------------------------------------------
  264. // Purpose :
  265. // Input :
  266. // Output :
  267. //------------------------------------------------------------------------------
  268. void CNPC_EnemyFinder::StartNPC ( void )
  269. {
  270. AddSpawnFlags(SF_NPC_FALL_TO_GROUND); // this prevents CAI_BaseNPC from slamming the finder to
  271. // the ground just because it's not MOVETYPE_FLY
  272. BaseClass::StartNPC();
  273. if ( AI_IsSinglePlayer() && m_PlayerFreePass.GetParams().duration > 0.1 )
  274. {
  275. m_PlayerFreePass.SetPassTarget( UTIL_PlayerByIndex(1) );
  276. AI_FreePassParams_t freePassParams = m_PlayerFreePass.GetParams();
  277. freePassParams.coverDist = 120;
  278. freePassParams.peekEyeDist = 1.75;
  279. freePassParams.peekEyeDistZ = 4;
  280. m_PlayerFreePass.SetParams( freePassParams );
  281. }
  282. if (!m_nStartOn)
  283. {
  284. SetThink(NULL);
  285. }
  286. }
  287. //------------------------------------------------------------------------------
  288. void CNPC_EnemyFinder::PrescheduleThink()
  289. {
  290. BaseClass::PrescheduleThink();
  291. bool bHasEnemies = GetEnemies()->NumEnemies() > 0;
  292. if ( GetEnemies()->NumEnemies() > 0 )
  293. {
  294. //If I haven't seen my enemy in half a second then we'll assume he's gone.
  295. if ( gpGlobals->curtime - GetEnemyLastTimeSeen() >= 0.5f )
  296. {
  297. bHasEnemies = false;
  298. }
  299. }
  300. if ( m_bEnemyStatus != bHasEnemies )
  301. {
  302. if ( bHasEnemies )
  303. {
  304. m_OnAcquireEnemies.FireOutput( this, this );
  305. }
  306. else
  307. {
  308. m_OnLostEnemies.FireOutput( this, this );
  309. }
  310. m_bEnemyStatus = bHasEnemies;
  311. }
  312. if( ai_debug_enemyfinders.GetBool() )
  313. {
  314. m_debugOverlays |= OVERLAY_BBOX_BIT;
  315. if( IsInSquad() && GetSquad()->NumMembers() > 1 )
  316. {
  317. AISquadIter_t iter;
  318. CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL;
  319. while ( pSquadmate )
  320. {
  321. NDebugOverlay::Line( WorldSpaceCenter(), pSquadmate->EyePosition(), 255, 255, 0, false, 0.1f );
  322. pSquadmate = m_pSquad->GetNextMember( &iter );
  323. }
  324. }
  325. }
  326. }
  327. //------------------------------------------------------------------------------
  328. bool CNPC_EnemyFinder::ShouldAlwaysThink()
  329. {
  330. if ( BaseClass::ShouldAlwaysThink() )
  331. return true;
  332. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  333. if ( pPlayer && IRelationType( pPlayer ) == D_HT )
  334. {
  335. float playerDistSqr = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() );
  336. if ( !m_flMaxSearchDist || playerDistSqr <= Square(m_flMaxSearchDist) )
  337. {
  338. if ( !FBitSet( m_spawnflags, SF_ENEMY_FINDER_CHECK_VIS) )
  339. return true;
  340. if ( playerDistSqr <= Square( 50 * 12 ) )
  341. return true;
  342. }
  343. }
  344. return false;
  345. }
  346. //------------------------------------------------------------------------------
  347. // Purpose :
  348. // Input :
  349. // Output :
  350. //------------------------------------------------------------------------------
  351. void CNPC_EnemyFinder::GatherConditions()
  352. {
  353. // This works with old data because need to do before base class so as to not choose as enemy
  354. m_PlayerFreePass.Update();
  355. BaseClass::GatherConditions();
  356. }
  357. //-----------------------------------------------------------------------------
  358. // Purpose:
  359. //
  360. //
  361. // Output :
  362. //-----------------------------------------------------------------------------
  363. Class_T CNPC_EnemyFinder::Classify( void )
  364. {
  365. if ( GetSquad() )
  366. {
  367. AISquadIter_t iter;
  368. CAI_BaseNPC *pSquadmate = GetSquad()->GetFirstMember( &iter );
  369. while ( pSquadmate )
  370. {
  371. if ( pSquadmate != this && !pSquadmate->ClassMatches( GetClassname() ) )
  372. {
  373. return pSquadmate->Classify();
  374. }
  375. pSquadmate = GetSquad()->GetNextMember( &iter );
  376. }
  377. }
  378. return CLASS_NONE;
  379. }
  380. //-----------------------------------------------------------------------------
  381. // Purpose: Add a visualizer to the text, if turned on
  382. //-----------------------------------------------------------------------------
  383. void CNPC_EnemyFinder::DrawDebugGeometryOverlays( void )
  384. {
  385. // Turn on npc_relationships if we're displaying text
  386. int oldDebugOverlays = m_debugOverlays;
  387. if ( m_debugOverlays & OVERLAY_TEXT_BIT )
  388. {
  389. m_debugOverlays |= OVERLAY_NPC_RELATION_BIT;
  390. }
  391. // Draw our base overlays
  392. BaseClass::DrawDebugGeometryOverlays();
  393. // Restore the old values
  394. m_debugOverlays = oldDebugOverlays;
  395. }
  396. ConVar ai_ef_hate_npc_frequency( "ai_ef_hate_npc_frequency", "5" );
  397. ConVar ai_ef_hate_npc_duration( "ai_ef_hate_npc_duration", "1.5" );
  398. //-----------------------------------------------------------------------------
  399. // Derived class with a few changes that make the Combine Cannon behave the
  400. // way we want.
  401. //-----------------------------------------------------------------------------
  402. #define EF_COMBINE_CANNON_HATE_TIME_INVALID -1
  403. static CUtlVector<CBaseEntity*> s_ListEnemyfinders;
  404. class CNPC_EnemyFinderCombineCannon : public CNPC_EnemyFinder
  405. {
  406. public:
  407. DECLARE_CLASS( CNPC_EnemyFinderCombineCannon, CNPC_EnemyFinder );
  408. DECLARE_DATADESC();
  409. CNPC_EnemyFinderCombineCannon()
  410. {
  411. m_flTimeNextHateNPC = gpGlobals->curtime;
  412. m_flTimeStopHateNPC = EF_COMBINE_CANNON_HATE_TIME_INVALID;
  413. };
  414. public:
  415. void Spawn();
  416. void Activate();
  417. void UpdateOnRemove();
  418. bool FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker );
  419. bool IsValidEnemy( CBaseEntity *pTarget );
  420. void GatherConditions();
  421. void InputSetWideFOVForSeconds( inputdata_t &inputdata );
  422. public:
  423. float m_flTimeNextHateNPC;
  424. float m_flTimeStopHateNPC;
  425. float m_flOriginalFOV;
  426. float m_flTimeWideFOV; // If this is > gpGlobals->curtime, we have 180 degree viewcone.
  427. string_t m_iszSnapToEnt;
  428. };
  429. LINK_ENTITY_TO_CLASS( npc_enemyfinder_combinecannon, CNPC_EnemyFinderCombineCannon );
  430. BEGIN_DATADESC( CNPC_EnemyFinderCombineCannon )
  431. DEFINE_FIELD( m_flTimeNextHateNPC, FIELD_TIME ),
  432. DEFINE_FIELD( m_flTimeStopHateNPC, FIELD_TIME ),
  433. DEFINE_FIELD( m_flOriginalFOV, FIELD_FLOAT ),
  434. DEFINE_FIELD( m_flTimeWideFOV, FIELD_TIME ),
  435. DEFINE_KEYFIELD( m_iszSnapToEnt, FIELD_STRING, "snaptoent" ),
  436. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetWideFOVForSeconds", InputSetWideFOVForSeconds ),
  437. END_DATADESC()
  438. //-----------------------------------------------------------------------------
  439. //-----------------------------------------------------------------------------
  440. void CNPC_EnemyFinderCombineCannon::Spawn()
  441. {
  442. BaseClass::Spawn();
  443. m_flOriginalFOV = m_flFieldOfView;
  444. m_flTimeWideFOV = -1.0f;
  445. if( m_iszSnapToEnt != NULL_STRING )
  446. {
  447. CBaseEntity *pSnapToEnt = gEntList.FindEntityByName( NULL, m_iszSnapToEnt );
  448. if( pSnapToEnt != NULL )
  449. {
  450. //!!!HACKHACK - this eight-inch offset puts this enemyfinder perfectly on-bore
  451. // with the prefab for a func_tank_combinecannon
  452. UTIL_SetOrigin( this, pSnapToEnt->WorldSpaceCenter() + Vector( 0, 0, 8) );
  453. }
  454. else
  455. {
  456. DevMsg( this, "Enemyfinder %s can't snap to %s because it doesn't exist\n", GetDebugName(), STRING(m_iszSnapToEnt) );
  457. }
  458. }
  459. }
  460. //-----------------------------------------------------------------------------
  461. //-----------------------------------------------------------------------------
  462. void CNPC_EnemyFinderCombineCannon::Activate()
  463. {
  464. BaseClass::Activate();
  465. // See if I'm in the list of Combine enemyfinders
  466. // If not, add me.
  467. if( s_ListEnemyfinders.Find(this) == -1 )
  468. {
  469. s_ListEnemyfinders.AddToTail(this);
  470. }
  471. }
  472. //-----------------------------------------------------------------------------
  473. //-----------------------------------------------------------------------------
  474. void CNPC_EnemyFinderCombineCannon::UpdateOnRemove()
  475. {
  476. BaseClass::UpdateOnRemove();
  477. // See if I'm in the list of Combine enemyfinders
  478. int index = s_ListEnemyfinders.Find(this);
  479. if( index != -1 )
  480. {
  481. s_ListEnemyfinders.Remove(index);
  482. }
  483. }
  484. //-----------------------------------------------------------------------------
  485. //-----------------------------------------------------------------------------
  486. bool CNPC_EnemyFinderCombineCannon::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
  487. {
  488. #if 1
  489. CBaseEntity *pBlocker = NULL;
  490. bool result;
  491. if(ppBlocker == NULL)
  492. {
  493. // Whoever called this didn't care about the blocker, but we do.
  494. // So substitute our local pBlocker pointer and don't disturb ppBlocker
  495. result = BaseClass::FVisible( pEntity, traceMask, &pBlocker );
  496. }
  497. else
  498. {
  499. // Copy the ppBlocker to our local pBlocker pointer, but do not
  500. // disturb the ppBlocker that was passed to us.
  501. result = BaseClass::FVisible( pEntity, traceMask, ppBlocker );
  502. pBlocker = (*ppBlocker);
  503. }
  504. if(pEntity->IsPlayer() && result == false)
  505. {
  506. // IF we are trying to see the player, but we don't, examine the blocker
  507. // and see the player anyway if we can hurt the blocker.
  508. if(pBlocker != NULL)
  509. {
  510. if( pBlocker->m_takedamage >= DAMAGE_YES ) // also DAMAGE_AIM
  511. {
  512. // Anytime the line of sight is blocked by something I can hurt, I have line of sight.
  513. // This will make the func_tank_combinecannon shoot the blocking object. This will
  514. // continue until the gun bores through to the player or clears all interposing breakables
  515. // and finds its progress impeded by something truly solid. So lie, and say we CAN see the player.
  516. result = true;
  517. }
  518. }
  519. }
  520. return result;
  521. #endif
  522. }
  523. //-----------------------------------------------------------------------------
  524. // Purpose: Ignore NPC's most of the time when the player is a potential target.
  525. // Go through short periods of time where NPCs may distract me
  526. //
  527. // ALSO- ignore NPC's (focus only on the player) when I'm in
  528. // wide viewcone mode.
  529. //-----------------------------------------------------------------------------
  530. bool CNPC_EnemyFinderCombineCannon::IsValidEnemy( CBaseEntity *pTarget )
  531. {
  532. if( m_flTimeWideFOV > gpGlobals->curtime && !pTarget->IsPlayer() )
  533. {
  534. // ONLY allowed to hate the player when I'm in hyper-vigilant wide FOV mode.
  535. // This forces all guns in outland_09 to shoot at the player once any
  536. // gun has fired at the player. This allows the other guns to specifically
  537. // kill zombies most of the time, but immediately turn their attention to the
  538. // player when necessary (by ignoring everything else)
  539. return pTarget->IsPlayer();
  540. }
  541. bool bResult = BaseClass::IsValidEnemy( pTarget );
  542. if( bResult && !pTarget->IsPlayer() )
  543. {
  544. // This is a valid enemy, but we have to make sure no other enemyfinders for
  545. // combine cannons are currently attacking it.
  546. int i;
  547. for( i = 0 ; i < s_ListEnemyfinders.Count() ; i++ )
  548. {
  549. if( s_ListEnemyfinders[i] == this )
  550. continue;
  551. if( s_ListEnemyfinders[i]->GetEnemy() == pTarget )
  552. return false;// someone else is already busy with this target.
  553. }
  554. }
  555. /*
  556. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  557. int iPlayerRelationPriority = -1;
  558. if( pPlayer != NULL )
  559. {
  560. iPlayerRelationPriority = IRelationPriority(pPlayer);
  561. }
  562. if( bResult == true && pTarget->IsNPC() && pPlayer != NULL && FInViewCone( pPlayer ) )
  563. {
  564. if( HasCondition(COND_SEE_PLAYER) )
  565. {
  566. // The player is visible! Immediately ignore all NPCs as enemies.
  567. return false;
  568. }
  569. // The base class wants to call this a valid enemy. We may choose to interfere
  570. // If the player is in my viewcone. That means that my func_tank could potentially
  571. // harass the player. This means I should meter the time I spend shooting at npcs
  572. // NPCs so that I can focus on the player.
  573. if( m_flTimeStopHateNPC != EF_COMBINE_CANNON_HATE_TIME_INVALID )
  574. {
  575. // We currently hate NPC's. But is it time to stop?
  576. if( gpGlobals->curtime > m_flTimeStopHateNPC )
  577. {
  578. // Don't interfere with the result
  579. m_flTimeStopHateNPC = EF_COMBINE_CANNON_HATE_TIME_INVALID;
  580. m_flTimeNextHateNPC = gpGlobals->curtime + ai_ef_hate_npc_frequency.GetFloat();
  581. return bResult;
  582. }
  583. }
  584. else
  585. {
  586. // We do not hate NPCs at the moment. Is it time to turn it on?
  587. if( gpGlobals->curtime > m_flTimeNextHateNPC )
  588. {
  589. m_flTimeStopHateNPC = gpGlobals->curtime + ai_ef_hate_npc_duration.GetFloat();
  590. }
  591. else
  592. {
  593. // Stop harassing player to attack something else higher priority.
  594. if( IRelationPriority(pTarget) > iPlayerRelationPriority )
  595. return bResult;
  596. }
  597. // Make this enemy invalid.
  598. return false;
  599. }
  600. }
  601. */
  602. return bResult;
  603. }
  604. //-----------------------------------------------------------------------------
  605. // Purpose: Control the width of my viewcone
  606. //-----------------------------------------------------------------------------
  607. void CNPC_EnemyFinderCombineCannon::GatherConditions()
  608. {
  609. if( m_flTimeWideFOV > gpGlobals->curtime )
  610. {
  611. // I'm in a hyper-vigilant period of time where I get a 270 degree viewcone
  612. m_flFieldOfView = -0.5f;
  613. }
  614. else
  615. {
  616. m_flFieldOfView = m_flOriginalFOV;
  617. }
  618. BaseClass::GatherConditions();
  619. }
  620. //-----------------------------------------------------------------------------
  621. //-----------------------------------------------------------------------------
  622. void CNPC_EnemyFinderCombineCannon::InputSetWideFOVForSeconds( inputdata_t &inputdata )
  623. {
  624. m_flTimeWideFOV = gpGlobals->curtime + inputdata.value.Float();
  625. }
  626. //-----------------------------------------------------------------------------
  627. //
  628. // Schedules
  629. //
  630. //-----------------------------------------------------------------------------
  631. //=========================================================
  632. // > SCHED_EFINDER_SEARCH
  633. //=========================================================
  634. AI_DEFINE_SCHEDULE
  635. (
  636. SCHED_EFINDER_SEARCH,
  637. " Tasks"
  638. " TASK_WAIT_RANDOM 0.5 "
  639. " "
  640. " Interrupts"
  641. " COND_NEW_ENEMY"
  642. );