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.

704 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Combine gun turret that emerges from a trapdoor in the ground.
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "npc_turret_ground.h"
  8. #include "ai_default.h"
  9. #include "ai_task.h"
  10. #include "ai_schedule.h"
  11. #include "ai_hull.h"
  12. #include "ai_senses.h"
  13. #include "ai_memory.h"
  14. #include "soundent.h"
  15. #include "game.h"
  16. #include "vstdlib/random.h"
  17. #include "engine/IEngineSound.h"
  18. #include "npcevent.h"
  19. #include "IEffects.h"
  20. #include "ammodef.h"
  21. #include "beam_shared.h"
  22. #include "explode.h"
  23. #include "te_effect_dispatch.h"
  24. #define GROUNDTURRET_BEAM_SPRITE "materials/effects/bluelaser2.vmt"
  25. #define GROUNDTURRET_VIEWCONE 60.0f // (degrees)
  26. #define GROUNDTURRET_RETIRE_TIME 7.0f
  27. ConVar ai_newgroundturret ( "ai_newgroundturret", "0" );
  28. // memdbgon must be the last include file in a .cpp file!!!
  29. #include "tier0/memdbgon.h"
  30. LINK_ENTITY_TO_CLASS( npc_turret_ground, CNPC_GroundTurret );
  31. //---------------------------------------------------------
  32. // Save/Restore
  33. //---------------------------------------------------------
  34. BEGIN_DATADESC( CNPC_GroundTurret )
  35. DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
  36. DEFINE_FIELD( m_pSmoke, FIELD_CLASSPTR ),
  37. DEFINE_FIELD( m_vecSpread, FIELD_VECTOR ),
  38. DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
  39. DEFINE_FIELD( m_flTimeNextShoot, FIELD_TIME ),
  40. DEFINE_FIELD( m_flTimeLastSawEnemy, FIELD_TIME ),
  41. DEFINE_FIELD( m_iDeathSparks, FIELD_INTEGER ),
  42. DEFINE_FIELD( m_bHasExploded, FIELD_BOOLEAN ),
  43. DEFINE_FIELD( m_flSensingDist, FIELD_FLOAT ),
  44. DEFINE_FIELD( m_flTimeNextPing, FIELD_TIME ),
  45. DEFINE_FIELD( m_bSeeEnemy, FIELD_BOOLEAN ),
  46. DEFINE_FIELD( m_vecClosedPos, FIELD_POSITION_VECTOR ),
  47. DEFINE_FIELD( m_vecLightOffset, FIELD_POSITION_VECTOR ),
  48. DEFINE_THINKFUNC( DeathEffects ),
  49. DEFINE_OUTPUT( m_OnAreaClear, "OnAreaClear" ),
  50. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  51. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  52. // DEFINE_FIELD( m_ShotSounds, FIELD_SHORT ),
  53. END_DATADESC()
  54. //-----------------------------------------------------------------------------
  55. // Purpose:
  56. //-----------------------------------------------------------------------------
  57. void CNPC_GroundTurret::Precache( void )
  58. {
  59. PrecacheModel( GROUNDTURRET_BEAM_SPRITE );
  60. PrecacheModel( "models/combine_turrets/ground_turret.mdl" );
  61. PrecacheScriptSound( "NPC_CeilingTurret.Deploy" );
  62. m_ShotSounds = PrecacheScriptSound( "NPC_FloorTurret.ShotSounds" );
  63. PrecacheScriptSound( "NPC_FloorTurret.Die" );
  64. PrecacheScriptSound( "NPC_FloorTurret.Ping" );
  65. PrecacheScriptSound( "DoSpark" );
  66. BaseClass::Precache();
  67. }
  68. //-----------------------------------------------------------------------------
  69. // Purpose:
  70. //-----------------------------------------------------------------------------
  71. void CNPC_GroundTurret::Spawn( void )
  72. {
  73. Precache();
  74. UTIL_SetModel( this, "models/combine_turrets/ground_turret.mdl" );
  75. SetNavType( NAV_FLY );
  76. SetSolid( SOLID_VPHYSICS );
  77. SetBloodColor( DONT_BLEED );
  78. m_iHealth = 125;
  79. m_flFieldOfView = cos( ((GROUNDTURRET_VIEWCONE / 2.0f) * M_PI / 180.0f) );
  80. m_NPCState = NPC_STATE_NONE;
  81. m_vecSpread.x = 0.5;
  82. m_vecSpread.y = 0.5;
  83. m_vecSpread.z = 0.5;
  84. CapabilitiesClear();
  85. AddEFlags( EFL_NO_DISSOLVE );
  86. NPCInit();
  87. CapabilitiesAdd( bits_CAP_SIMPLE_RADIUS_DAMAGE );
  88. m_iAmmoType = GetAmmoDef()->Index( "PISTOL" );
  89. m_pSmoke = NULL;
  90. m_bHasExploded = false;
  91. m_bEnabled = false;
  92. if( ai_newgroundturret.GetBool() )
  93. {
  94. m_flSensingDist = 384;
  95. SetDistLook( m_flSensingDist );
  96. }
  97. else
  98. {
  99. m_flSensingDist = 2048;
  100. }
  101. if( !GetParent() )
  102. {
  103. DevMsg("ERROR! npc_ground_turret with no parent!\n");
  104. UTIL_Remove(this);
  105. return;
  106. }
  107. m_flTimeNextShoot = gpGlobals->curtime;
  108. m_flTimeNextPing = gpGlobals->curtime;
  109. m_vecClosedPos = GetAbsOrigin();
  110. StudioFrameAdvance();
  111. Vector vecPos;
  112. GetAttachment( "eyes", vecPos );
  113. SetViewOffset( vecPos - GetAbsOrigin() );
  114. GetAttachment( "light", vecPos );
  115. m_vecLightOffset = vecPos - GetAbsOrigin();
  116. }
  117. //-----------------------------------------------------------------------------
  118. //-----------------------------------------------------------------------------
  119. bool CNPC_GroundTurret::CreateVPhysics( void )
  120. {
  121. //Spawn our physics hull
  122. if ( !VPhysicsInitStatic() )
  123. {
  124. DevMsg( "npc_turret_ground unable to spawn physics object!\n" );
  125. }
  126. return true;
  127. }
  128. //-----------------------------------------------------------------------------
  129. //-----------------------------------------------------------------------------
  130. void CNPC_GroundTurret::PrescheduleThink()
  131. {
  132. if( UTIL_FindClientInPVS(edict()) )
  133. {
  134. SetNextThink( gpGlobals->curtime + 0.03f );
  135. }
  136. else
  137. {
  138. SetNextThink( gpGlobals->curtime + 0.1f );
  139. }
  140. }
  141. //-----------------------------------------------------------------------------
  142. // Purpose:
  143. // Output :
  144. //-----------------------------------------------------------------------------
  145. Class_T CNPC_GroundTurret::Classify( void )
  146. {
  147. if( !IsOpen() )
  148. {
  149. // NPC's should disregard me if I'm closed.
  150. return CLASS_NONE;
  151. }
  152. else
  153. {
  154. return CLASS_COMBINE;
  155. }
  156. }
  157. //---------------------------------------------------------
  158. //---------------------------------------------------------
  159. void CNPC_GroundTurret::PostNPCInit()
  160. {
  161. BaseClass::PostNPCInit();
  162. }
  163. //---------------------------------------------------------
  164. //---------------------------------------------------------
  165. int CNPC_GroundTurret::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  166. {
  167. if( !info.GetInflictor() )
  168. {
  169. return 0;
  170. }
  171. // Only take damage from self (kill input from my bullseye) or missiles.
  172. if( info.GetInflictor() != this && info.GetInflictor()->Classify() != CLASS_MISSILE )
  173. {
  174. return 0;
  175. }
  176. CTakeDamageInfo infoCopy = info;
  177. if( info.GetInflictor() == this )
  178. {
  179. // Taking damage from myself, make sure it's fatal.
  180. infoCopy.SetDamage( GetHealth() );
  181. infoCopy.SetDamageType( DMG_REMOVENORAGDOLL | DMG_GENERIC );
  182. }
  183. return BaseClass::OnTakeDamage_Alive( infoCopy );
  184. }
  185. //---------------------------------------------------------
  186. //---------------------------------------------------------
  187. void CNPC_GroundTurret::Event_Killed( const CTakeDamageInfo &info )
  188. {
  189. BaseClass::Event_Killed( info );
  190. if ( m_pSmoke != NULL )
  191. return;
  192. m_pSmoke = SmokeTrail::CreateSmokeTrail();
  193. if ( m_pSmoke )
  194. {
  195. m_pSmoke->m_SpawnRate = 18;
  196. m_pSmoke->m_ParticleLifetime = 3.0;
  197. m_pSmoke->m_StartSize = 8;
  198. m_pSmoke->m_EndSize = 32;
  199. m_pSmoke->m_SpawnRadius = 16;
  200. m_pSmoke->m_MinSpeed = 8;
  201. m_pSmoke->m_MaxSpeed = 32;
  202. m_pSmoke->m_Opacity = 0.6;
  203. m_pSmoke->m_StartColor.Init( 0.25f, 0.25f, 0.25f );
  204. m_pSmoke->m_EndColor.Init( 0, 0, 0 );
  205. m_pSmoke->SetLifetime( 30.0f );
  206. m_pSmoke->FollowEntity( this );
  207. }
  208. m_iDeathSparks = random->RandomInt( 6, 12 );
  209. SetThink( &CNPC_GroundTurret::DeathEffects );
  210. SetNextThink( gpGlobals->curtime + 1.5f );
  211. }
  212. //---------------------------------------------------------
  213. //---------------------------------------------------------
  214. void CNPC_GroundTurret::DeathEffects()
  215. {
  216. if( !m_bHasExploded )
  217. {
  218. //ExplosionCreate( GetAbsOrigin(), QAngle( 0, 0, 1 ), this, 150, 150, false );
  219. CTakeDamageInfo info;
  220. DeathSound( info );
  221. m_bHasExploded = true;
  222. SetNextThink( gpGlobals->curtime + 0.5 );
  223. }
  224. else
  225. {
  226. // Sparks
  227. EmitSound( "DoSpark" );
  228. m_iDeathSparks--;
  229. if( m_iDeathSparks == 0 )
  230. {
  231. SetThink(NULL);
  232. return;
  233. }
  234. SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.5, 2.5 ) );
  235. }
  236. }
  237. //---------------------------------------------------------
  238. //---------------------------------------------------------
  239. void CNPC_GroundTurret::DeathSound( const CTakeDamageInfo &info )
  240. {
  241. EmitSound("NPC_FloorTurret.Die");
  242. }
  243. //---------------------------------------------------------
  244. //---------------------------------------------------------
  245. void CNPC_GroundTurret::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
  246. {
  247. #if 1
  248. //BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
  249. UTIL_Tracer( vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, 5000, true, "AR2Tracer" );
  250. #else
  251. CBeam *pBeam;
  252. int width = 2;
  253. pBeam = CBeam::BeamCreate( GROUNDTURRET_BEAM_SPRITE, width );
  254. if ( !pBeam )
  255. return;
  256. pBeam->SetStartPos( vecTracerSrc );
  257. pBeam->SetEndPos( tr.endpos );
  258. pBeam->SetWidth( width );
  259. pBeam->SetEndWidth( width / 4.0f );
  260. pBeam->SetBrightness( 100 );
  261. pBeam->SetColor( 0, 145+random->RandomInt( -16, 16 ), 255 );
  262. pBeam->RelinkBeam();
  263. pBeam->LiveForTime( random->RandomFloat( 0.2f, 0.5f ) );
  264. #endif
  265. }
  266. //---------------------------------------------------------
  267. //---------------------------------------------------------
  268. void CNPC_GroundTurret::GatherConditions()
  269. {
  270. if( !IsEnabled() )
  271. {
  272. return;
  273. }
  274. if( !IsOpen() && !UTIL_FindClientInPVS( edict() ) )
  275. {
  276. return;
  277. }
  278. // Throw away old enemies so the turret can retire
  279. AIEnemiesIter_t iter;
  280. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
  281. {
  282. if( pEMemory->timeLastSeen < gpGlobals->curtime - GROUNDTURRET_RETIRE_TIME )
  283. {
  284. pEMemory->hEnemy = NULL;
  285. }
  286. }
  287. BaseClass::GatherConditions();
  288. if( GetEnemy() && HasCondition(COND_SEE_ENEMY) )
  289. {
  290. m_flTimeLastSawEnemy = gpGlobals->curtime;
  291. }
  292. else
  293. {
  294. if( gpGlobals->curtime - m_flTimeLastSawEnemy >= GROUNDTURRET_RETIRE_TIME )
  295. {
  296. m_OnAreaClear.FireOutput(this, this);
  297. m_flTimeLastSawEnemy = FLT_MAX;
  298. return;
  299. }
  300. }
  301. if( HasCondition( COND_SEE_ENEMY ) )
  302. {
  303. m_bSeeEnemy = true;
  304. }
  305. else
  306. {
  307. m_bSeeEnemy = false;
  308. }
  309. if( GetEnemy() && m_bSeeEnemy && IsEnabled() )
  310. {
  311. if( m_flTimeNextShoot < gpGlobals->curtime )
  312. {
  313. Shoot();
  314. }
  315. }
  316. }
  317. //---------------------------------------------------------
  318. //---------------------------------------------------------
  319. Vector CNPC_GroundTurret::EyePosition()
  320. {
  321. if( ai_newgroundturret.GetBool() )
  322. {
  323. return GetAbsOrigin() + Vector( 0, 0, 6 );
  324. }
  325. return GetAbsOrigin() + GetViewOffset();
  326. }
  327. //---------------------------------------------------------
  328. //---------------------------------------------------------
  329. bool CNPC_GroundTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
  330. {
  331. if ( BaseClass::FVisible( pEntity, traceMask, ppBlocker ) )
  332. return true;
  333. if ( ( pEntity->GetAbsOrigin().AsVector2D() - GetAbsOrigin().AsVector2D() ).LengthSqr() < Square(10*12) &&
  334. FInViewCone( pEntity->GetAbsOrigin() ) &&
  335. BaseClass::FVisible( pEntity->GetAbsOrigin() + Vector( 0, 0, 1 ), traceMask, ppBlocker ) )
  336. return true;
  337. return false;
  338. }
  339. //---------------------------------------------------------
  340. //---------------------------------------------------------
  341. bool CNPC_GroundTurret::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC)
  342. {
  343. float flDist;
  344. flDist = (pEntity->GetAbsOrigin() - EyePosition()).Length2DSqr();
  345. if( flDist <= m_flSensingDist * m_flSensingDist )
  346. {
  347. return BaseClass::QuerySeeEntity(pEntity, bOnlyHateOrFearIfNPC);
  348. }
  349. return false;
  350. }
  351. //---------------------------------------------------------
  352. //---------------------------------------------------------
  353. bool CNPC_GroundTurret::IsEnabled()
  354. {
  355. if( ai_newgroundturret.GetBool() )
  356. {
  357. return true;
  358. }
  359. return m_bEnabled;
  360. }
  361. //---------------------------------------------------------
  362. //---------------------------------------------------------
  363. bool CNPC_GroundTurret::IsOpen()
  364. {
  365. // The method is hacky but in the end, this does actually give
  366. // us a pretty good idea if the turret is open or closed.
  367. return( fabs(GetAbsOrigin().z - m_vecClosedPos.z ) > 1.0f );
  368. }
  369. //---------------------------------------------------------
  370. //---------------------------------------------------------
  371. void CNPC_GroundTurret::StartTask( const Task_t *pTask )
  372. {
  373. switch( pTask->iTask )
  374. {
  375. case TASK_GROUNDTURRET_SCAN:
  376. Scan();
  377. break;
  378. default:
  379. BaseClass::StartTask( pTask );
  380. break;
  381. }
  382. }
  383. //---------------------------------------------------------
  384. //---------------------------------------------------------
  385. void CNPC_GroundTurret::RunTask( const Task_t *pTask )
  386. {
  387. switch( pTask->iTask )
  388. {
  389. case TASK_GROUNDTURRET_SCAN:
  390. Scan();
  391. break;
  392. default:
  393. BaseClass::RunTask( pTask );
  394. break;
  395. }
  396. }
  397. //---------------------------------------------------------
  398. //---------------------------------------------------------
  399. int CNPC_GroundTurret::SelectSchedule( void )
  400. {
  401. return SCHED_GROUND_TURRET_IDLE;
  402. }
  403. //---------------------------------------------------------
  404. //---------------------------------------------------------
  405. int CNPC_GroundTurret::TranslateSchedule( int scheduleType )
  406. {
  407. switch( scheduleType )
  408. {
  409. case SCHED_IDLE_STAND:
  410. return SCHED_GROUND_TURRET_IDLE;
  411. break;
  412. }
  413. return BaseClass::TranslateSchedule( scheduleType );
  414. }
  415. //-----------------------------------------------------------------------------
  416. // Purpose: Override base class activiites
  417. // Input :
  418. // Output :
  419. //-----------------------------------------------------------------------------
  420. Activity CNPC_GroundTurret::NPC_TranslateActivity( Activity activity )
  421. {
  422. return ACT_IDLE;
  423. }
  424. //-----------------------------------------------------------------------------
  425. //-----------------------------------------------------------------------------
  426. void CNPC_GroundTurret::Shoot()
  427. {
  428. FireBulletsInfo_t info;
  429. Vector vecSrc = EyePosition();
  430. Vector vecDir;
  431. GetVectors( &vecDir, NULL, NULL );
  432. for( int i = 0 ; i < 1 ; i++ )
  433. {
  434. info.m_vecSrc = vecSrc;
  435. if( i > 0 || !GetEnemy()->IsPlayer() )
  436. {
  437. // Subsequent shots or shots at non-players random
  438. GetVectors( &info.m_vecDirShooting, NULL, NULL );
  439. info.m_vecSpread = m_vecSpread;
  440. }
  441. else
  442. {
  443. // First shot is at the enemy.
  444. info.m_vecDirShooting = GetActualShootTrajectory( vecSrc );
  445. info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
  446. }
  447. info.m_iTracerFreq = 1;
  448. info.m_iShots = 1;
  449. info.m_pAttacker = this;
  450. info.m_flDistance = MAX_COORD_RANGE;
  451. info.m_iAmmoType = m_iAmmoType;
  452. FireBullets( info );
  453. }
  454. // Do the AR2 muzzle flash
  455. CEffectData data;
  456. data.m_nEntIndex = entindex();
  457. data.m_nAttachmentIndex = LookupAttachment( "eyes" );
  458. data.m_flScale = 1.0f;
  459. data.m_fFlags = MUZZLEFLASH_COMBINE;
  460. DispatchEffect( "MuzzleFlash", data );
  461. EmitSound( "NPC_FloorTurret.ShotSounds", m_ShotSounds );
  462. if( IsX360() )
  463. {
  464. m_flTimeNextShoot = gpGlobals->curtime + 0.2;
  465. }
  466. else
  467. {
  468. m_flTimeNextShoot = gpGlobals->curtime + 0.09;
  469. }
  470. }
  471. //-----------------------------------------------------------------------------
  472. //-----------------------------------------------------------------------------
  473. void CNPC_GroundTurret::ProjectBeam( const Vector &vecStart, const Vector &vecDir, int width, int brightness, float duration )
  474. {
  475. CBeam *pBeam;
  476. pBeam = CBeam::BeamCreate( GROUNDTURRET_BEAM_SPRITE, width );
  477. if ( !pBeam )
  478. return;
  479. trace_t tr;
  480. AI_TraceLine( vecStart, vecStart + vecDir * m_flSensingDist, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  481. pBeam->SetStartPos( tr.endpos );
  482. pBeam->SetEndPos( tr.startpos );
  483. pBeam->SetWidth( width );
  484. pBeam->SetEndWidth( 0.1 );
  485. pBeam->SetFadeLength( 16 );
  486. pBeam->SetBrightness( brightness );
  487. pBeam->SetColor( 0, 145+random->RandomInt( -16, 16 ), 255 );
  488. pBeam->RelinkBeam();
  489. pBeam->LiveForTime( duration );
  490. }
  491. //-----------------------------------------------------------------------------
  492. //-----------------------------------------------------------------------------
  493. void CNPC_GroundTurret::Scan()
  494. {
  495. if( m_bSeeEnemy )
  496. {
  497. // Using a bool for this check because the condition gets wiped out by changing schedules.
  498. return;
  499. }
  500. if( IsOpeningOrClosing() )
  501. {
  502. // Moving.
  503. return;
  504. }
  505. if( !IsOpen() )
  506. {
  507. // Closed
  508. return;
  509. }
  510. if( !UTIL_FindClientInPVS(edict()) )
  511. {
  512. return;
  513. }
  514. if( gpGlobals->curtime >= m_flTimeNextPing )
  515. {
  516. EmitSound( "NPC_FloorTurret.Ping" );
  517. m_flTimeNextPing = gpGlobals->curtime + 1.0f;
  518. }
  519. QAngle scanAngle;
  520. Vector forward;
  521. Vector vecEye = GetAbsOrigin() + m_vecLightOffset;
  522. // Draw the outer extents
  523. scanAngle = GetAbsAngles();
  524. scanAngle.y += (GROUNDTURRET_VIEWCONE / 2.0f);
  525. AngleVectors( scanAngle, &forward, NULL, NULL );
  526. ProjectBeam( vecEye, forward, 1, 30, 0.1 );
  527. scanAngle = GetAbsAngles();
  528. scanAngle.y -= (GROUNDTURRET_VIEWCONE / 2.0f);
  529. AngleVectors( scanAngle, &forward, NULL, NULL );
  530. ProjectBeam( vecEye, forward, 1, 30, 0.1 );
  531. // Draw a sweeping beam
  532. scanAngle = GetAbsAngles();
  533. scanAngle.y += (GROUNDTURRET_VIEWCONE / 2.0f) * sin( gpGlobals->curtime * 3.0f );
  534. AngleVectors( scanAngle, &forward, NULL, NULL );
  535. ProjectBeam( vecEye, forward, 1, 30, 0.3 );
  536. }
  537. //-----------------------------------------------------------------------------
  538. //-----------------------------------------------------------------------------
  539. void CNPC_GroundTurret::InputEnable( inputdata_t &inputdata )
  540. {
  541. m_bEnabled = true;
  542. // Because the turret might not ever ACQUIRE an enemy, we need to arrange to
  543. // retire after a few seconds.
  544. m_flTimeLastSawEnemy = gpGlobals->curtime;
  545. }
  546. //-----------------------------------------------------------------------------
  547. //-----------------------------------------------------------------------------
  548. void CNPC_GroundTurret::InputDisable( inputdata_t &inputdata )
  549. {
  550. m_bEnabled = false;
  551. }
  552. //-----------------------------------------------------------------------------
  553. //
  554. // Schedules
  555. //
  556. //-----------------------------------------------------------------------------
  557. AI_BEGIN_CUSTOM_NPC( npc_groundturret, CNPC_GroundTurret )
  558. DECLARE_TASK( TASK_GROUNDTURRET_SCAN );
  559. DEFINE_SCHEDULE
  560. (
  561. SCHED_GROUND_TURRET_IDLE,
  562. " Tasks "
  563. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  564. " TASK_GROUNDTURRET_SCAN 0"
  565. ""
  566. " Interrupts "
  567. " COND_NEW_ENEMY"
  568. " COND_SEE_ENEMY"
  569. " COND_LOST_ENEMY"
  570. )
  571. DEFINE_SCHEDULE
  572. (
  573. SCHED_GROUND_TURRET_ATTACK,
  574. " Tasks "
  575. " TASK_WAIT_INDEFINITE 0"
  576. ""
  577. " Interrupts "
  578. " COND_NEW_ENEMY"
  579. " COND_LOST_ENEMY"
  580. " COND_SEE_ENEMY"
  581. )
  582. AI_END_CUSTOM_NPC()