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.

1335 lines
37 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "ai_basenpc.h"
  9. #include "ammodef.h"
  10. #include "ai_memory.h"
  11. #include "weapon_rpg.h"
  12. #include "effect_color_tables.h"
  13. #include "te_effect_dispatch.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. extern const char* g_pModelNameLaser;
  17. // No model, impervious to damage.
  18. #define SF_STARTDISABLED (1 << 19)
  19. #define CANNON_PAINT_ENEMY_TIME 1.0f
  20. #define CANNON_SUBSEQUENT_PAINT_TIME 0.4f
  21. #define CANNON_PAINT_NPC_TIME_NOISE 1.0f
  22. #define NUM_ANCILLARY_BEAMS 4
  23. int gHaloTexture = 0;
  24. //-----------------------------------------------------------------------------
  25. //
  26. // Combine Cannon
  27. //
  28. //-----------------------------------------------------------------------------
  29. class CNPC_Combine_Cannon : public CAI_BaseNPC
  30. {
  31. DECLARE_CLASS( CNPC_Combine_Cannon, CAI_BaseNPC );
  32. public:
  33. CNPC_Combine_Cannon( void );
  34. virtual void Precache( void );
  35. virtual void Spawn( void );
  36. virtual Class_T Classify( void );
  37. virtual float MaxYawSpeed( void );
  38. virtual Vector EyePosition( void );
  39. virtual void UpdateOnRemove( void );
  40. virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
  41. virtual bool QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC = false );
  42. virtual void StartTask( const Task_t *pTask );
  43. virtual void RunTask( const Task_t *pTask );
  44. virtual int RangeAttack1Conditions( float flDot, float flDist );
  45. virtual int SelectSchedule( void );
  46. virtual int TranslateSchedule( int scheduleType );
  47. virtual void PrescheduleThink( void );
  48. virtual bool FCanCheckAttacks ( void );
  49. virtual int Restore( IRestore &restore );
  50. virtual void OnScheduleChange( void );
  51. virtual bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
  52. virtual bool WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions) { return true; }
  53. virtual int GetSoundInterests( void ) { return (SOUND_PLAYER|SOUND_COMBAT|SOUND_DANGER); }
  54. virtual bool ShouldNotDistanceCull( void ) { return true; }
  55. virtual void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
  56. virtual const char *GetTracerType( void ) { return "HelicopterTracer"; }
  57. private:
  58. void ScopeGlint( void );
  59. void AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn );
  60. float GetRefireTime( void ) { return 0.1f; }
  61. bool IsLaserOn( void ) { return m_pBeam != NULL; }
  62. bool FireBullet( const Vector &vecTarget, bool bDirectShot );
  63. Vector DesiredBodyTarget( CBaseEntity *pTarget );
  64. Vector LeadTarget( CBaseEntity *pTarget );
  65. Vector GetBulletOrigin( void );
  66. static const char *pAttackSounds[];
  67. void ClearTargetGroup( void );
  68. float GetWaitTimePercentage( float flTime, bool fLinear );
  69. void GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress );
  70. bool VerifyShot( CBaseEntity *pTarget );
  71. void SetSweepTarget( const char *pszTarget );
  72. // Inputs
  73. void InputEnableSniper( inputdata_t &inputdata );
  74. void InputDisableSniper( inputdata_t &inputdata );
  75. void LaserOff( void );
  76. void LaserOn( const Vector &vecTarget, const Vector &vecDeviance );
  77. void PaintTarget( const Vector &vecTarget, float flPaintTime );
  78. private:
  79. void CreateLaser( void );
  80. void CreateAncillaryBeams( void );
  81. void UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis );
  82. int m_iAmmoType;
  83. float m_flBarrageDuration;
  84. Vector m_vecPaintCursor;
  85. float m_flPaintTime;
  86. CHandle<CBeam> m_pBeam;
  87. CHandle<CBeam> m_pAncillaryBeams[NUM_ANCILLARY_BEAMS];
  88. EHANDLE m_hBarrageTarget;
  89. bool m_fEnabled;
  90. Vector m_vecPaintStart; // used to track where a sweep starts for the purpose of interpolating.
  91. float m_flTimeLastAttackedPlayer;
  92. float m_flTimeLastShotMissed;
  93. float m_flSightDist;
  94. DEFINE_CUSTOM_AI;
  95. DECLARE_DATADESC();
  96. };
  97. LINK_ENTITY_TO_CLASS( npc_combine_cannon, CNPC_Combine_Cannon );
  98. //=========================================================
  99. //=========================================================
  100. BEGIN_DATADESC( CNPC_Combine_Cannon )
  101. DEFINE_FIELD( m_fEnabled, FIELD_BOOLEAN ),
  102. DEFINE_FIELD( m_vecPaintStart, FIELD_POSITION_VECTOR ),
  103. DEFINE_FIELD( m_flPaintTime, FIELD_TIME ),
  104. DEFINE_FIELD( m_vecPaintCursor, FIELD_POSITION_VECTOR ),
  105. DEFINE_FIELD( m_pBeam, FIELD_EHANDLE ),
  106. DEFINE_FIELD( m_flTimeLastAttackedPlayer, FIELD_TIME ),
  107. DEFINE_FIELD( m_flTimeLastShotMissed, FIELD_TIME ),
  108. DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
  109. DEFINE_FIELD( m_flBarrageDuration, FIELD_TIME ),
  110. DEFINE_FIELD( m_hBarrageTarget, FIELD_EHANDLE ),
  111. DEFINE_ARRAY( m_pAncillaryBeams, FIELD_EHANDLE, NUM_ANCILLARY_BEAMS ),
  112. DEFINE_KEYFIELD( m_flSightDist, FIELD_FLOAT, "sightdist" ),
  113. // Inputs
  114. DEFINE_INPUTFUNC( FIELD_VOID, "EnableSniper", InputEnableSniper ),
  115. DEFINE_INPUTFUNC( FIELD_VOID, "DisableSniper", InputDisableSniper ),
  116. END_DATADESC()
  117. //=========================================================
  118. // Private conditions
  119. //=========================================================
  120. enum Sniper_Conds
  121. {
  122. COND_CANNON_ENABLED = LAST_SHARED_CONDITION,
  123. COND_CANNON_DISABLED,
  124. COND_CANNON_NO_SHOT,
  125. };
  126. //=========================================================
  127. // schedules
  128. //=========================================================
  129. enum
  130. {
  131. SCHED_CANNON_CAMP = LAST_SHARED_SCHEDULE,
  132. SCHED_CANNON_ATTACK,
  133. SCHED_CANNON_DISABLEDWAIT,
  134. SCHED_CANNON_SNAPATTACK,
  135. };
  136. //=========================================================
  137. // tasks
  138. //=========================================================
  139. enum
  140. {
  141. TASK_CANNON_PAINT_ENEMY = LAST_SHARED_TASK,
  142. TASK_CANNON_PAINT_DECOY,
  143. TASK_CANNON_ATTACK_CURSOR,
  144. };
  145. //-----------------------------------------------------------------------------
  146. // Constructor
  147. //-----------------------------------------------------------------------------
  148. CNPC_Combine_Cannon::CNPC_Combine_Cannon( void ) :
  149. m_pBeam( NULL ),
  150. m_hBarrageTarget( NULL )
  151. {
  152. #ifdef _DEBUG
  153. m_vecPaintCursor.Init();
  154. m_vecPaintStart.Init();
  155. #endif
  156. }
  157. //-----------------------------------------------------------------------------
  158. //-----------------------------------------------------------------------------
  159. bool CNPC_Combine_Cannon::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
  160. {
  161. Disposition_t disp = IRelationType(pEntity);
  162. if ( disp != D_HT )
  163. {
  164. // Don't bother with anything I wouldn't shoot.
  165. return false;
  166. }
  167. if ( !FInViewCone(pEntity) )
  168. {
  169. // Yes, this does call FInViewCone twice a frame for all entities checked for
  170. // visibility, but doing this allows us to cut out a bunch of traces that would
  171. // be done by VerifyShot for entities that aren't even in our viewcone.
  172. return false;
  173. }
  174. if ( VerifyShot( pEntity ) )
  175. return BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC );
  176. return false;
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose: Hide the beams
  180. //-----------------------------------------------------------------------------
  181. void CNPC_Combine_Cannon::LaserOff( void )
  182. {
  183. if ( m_pBeam != NULL )
  184. {
  185. m_pBeam->TurnOn();
  186. }
  187. for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
  188. {
  189. if ( m_pAncillaryBeams[i] == NULL )
  190. continue;
  191. m_pAncillaryBeams[i]->TurnOn();
  192. }
  193. SetNextThink( gpGlobals->curtime + 0.1f );
  194. }
  195. //-----------------------------------------------------------------------------
  196. // Purpose: Switch on the laser and point it at a direction
  197. //-----------------------------------------------------------------------------
  198. void CNPC_Combine_Cannon::LaserOn( const Vector &vecTarget, const Vector &vecDeviance )
  199. {
  200. if ( m_pBeam != NULL )
  201. {
  202. m_pBeam->TurnOff();
  203. // Don't aim right at the guy right now.
  204. Vector vecInitialAim;
  205. if( vecDeviance == vec3_origin )
  206. {
  207. // Start the aim where it last left off!
  208. vecInitialAim = m_vecPaintCursor;
  209. }
  210. else
  211. {
  212. vecInitialAim = vecTarget;
  213. }
  214. vecInitialAim.x += random->RandomFloat( -vecDeviance.x, vecDeviance.x );
  215. vecInitialAim.y += random->RandomFloat( -vecDeviance.y, vecDeviance.y );
  216. vecInitialAim.z += random->RandomFloat( -vecDeviance.z, vecDeviance.z );
  217. m_pBeam->SetStartPos( GetBulletOrigin() );
  218. m_pBeam->SetEndPos( vecInitialAim );
  219. m_vecPaintStart = vecInitialAim;
  220. }
  221. for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
  222. {
  223. if ( m_pAncillaryBeams[i] == NULL )
  224. continue;
  225. m_pAncillaryBeams[i]->TurnOff();
  226. }
  227. }
  228. //-----------------------------------------------------------------------------
  229. // Crikey!
  230. //-----------------------------------------------------------------------------
  231. float CNPC_Combine_Cannon::GetWaitTimePercentage( float flTime, bool fLinear )
  232. {
  233. float flElapsedTime;
  234. float flTimeParameter;
  235. flElapsedTime = flTime - (GetWaitFinishTime() - gpGlobals->curtime);
  236. flTimeParameter = ( flElapsedTime / flTime );
  237. if( fLinear )
  238. {
  239. return flTimeParameter;
  240. }
  241. else
  242. {
  243. return (1 + sin( (M_PI * flTimeParameter) - (M_PI / 2) ) ) / 2;
  244. }
  245. }
  246. //-----------------------------------------------------------------------------
  247. //-----------------------------------------------------------------------------
  248. void CNPC_Combine_Cannon::GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress )
  249. {
  250. // Quaternions
  251. Vector vecIdealDir;
  252. QAngle vecIdealAngles;
  253. QAngle vecCurrentAngles;
  254. Vector vecCurrentDir;
  255. Vector vecBulletOrigin = GetBulletOrigin();
  256. // vecIdealDir is where the gun should be aimed when the painting
  257. // time is up. This can be approximate. This is only for drawing the
  258. // laser, not actually aiming the weapon. A large discrepancy will look
  259. // bad, though.
  260. vecIdealDir = vecGoal - vecBulletOrigin;
  261. VectorNormalize(vecIdealDir);
  262. // Now turn vecIdealDir into angles!
  263. VectorAngles( vecIdealDir, vecIdealAngles );
  264. // This is the vector of the beam's current aim.
  265. vecCurrentDir = m_vecPaintStart - vecBulletOrigin;
  266. VectorNormalize(vecCurrentDir);
  267. // Turn this to angles, too.
  268. VectorAngles( vecCurrentDir, vecCurrentAngles );
  269. Quaternion idealQuat;
  270. Quaternion currentQuat;
  271. Quaternion aimQuat;
  272. AngleQuaternion( vecIdealAngles, idealQuat );
  273. AngleQuaternion( vecCurrentAngles, currentQuat );
  274. QuaternionSlerp( currentQuat, idealQuat, flParameter, aimQuat );
  275. QuaternionAngles( aimQuat, vecCurrentAngles );
  276. // Rebuild the current aim vector.
  277. AngleVectors( vecCurrentAngles, &vecCurrentDir );
  278. *pProgress = vecCurrentDir;
  279. }
  280. //-----------------------------------------------------------------------------
  281. // Purpose:
  282. //-----------------------------------------------------------------------------
  283. void CNPC_Combine_Cannon::CreateLaser( void )
  284. {
  285. if ( m_pBeam != NULL )
  286. return;
  287. m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
  288. m_pBeam->SetColor( 0, 100, 255 );
  289. m_pBeam->PointsInit( vec3_origin, GetBulletOrigin() );
  290. m_pBeam->SetBrightness( 255 );
  291. m_pBeam->SetNoise( 0 );
  292. m_pBeam->SetWidth( 1.0f );
  293. m_pBeam->SetEndWidth( 0 );
  294. m_pBeam->SetScrollRate( 0 );
  295. m_pBeam->SetFadeLength( 0 );
  296. m_pBeam->SetHaloTexture( gHaloTexture );
  297. m_pBeam->SetHaloScale( 16.0f );
  298. // Think faster while painting
  299. SetNextThink( gpGlobals->curtime + 0.02f );
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Purpose:
  303. //-----------------------------------------------------------------------------
  304. void CNPC_Combine_Cannon::CreateAncillaryBeams( void )
  305. {
  306. for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
  307. {
  308. if ( m_pAncillaryBeams[i] != NULL )
  309. continue;
  310. m_pAncillaryBeams[i] = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
  311. m_pAncillaryBeams[i]->SetColor( 0, 100, 255 );
  312. m_pAncillaryBeams[i]->PointsInit( vec3_origin, GetBulletOrigin() );
  313. m_pAncillaryBeams[i]->SetBrightness( 255 );
  314. m_pAncillaryBeams[i]->SetNoise( 0 );
  315. m_pAncillaryBeams[i]->SetWidth( 1.0f );
  316. m_pAncillaryBeams[i]->SetEndWidth( 0 );
  317. m_pAncillaryBeams[i]->SetScrollRate( 0 );
  318. m_pAncillaryBeams[i]->SetFadeLength( 0 );
  319. m_pAncillaryBeams[i]->SetHaloTexture( gHaloTexture );
  320. m_pAncillaryBeams[i]->SetHaloScale( 16.0f );
  321. m_pAncillaryBeams[i]->TurnOff();
  322. }
  323. }
  324. #define LINE_LENGTH 1600.0f
  325. //-----------------------------------------------------------------------------
  326. // Purpose:
  327. // Input : flConvergencePerc -
  328. // vecBasis -
  329. //-----------------------------------------------------------------------------
  330. void CNPC_Combine_Cannon::UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis )
  331. {
  332. // Multiple beams deviate from the basis direction by a certain number of degrees and "converge"
  333. // at the basis vector over a duration of time, the position in that duration expressed by
  334. // flConvergencePerc. The beams are most deviated at 0 and fully converged at 1.
  335. float flRotationOffset = (2*M_PI)/(float)NUM_ANCILLARY_BEAMS; // Degrees separating each beam, in radians
  336. float flDeviation = DEG2RAD(90) * ( 1.0f - flConvergencePerc );
  337. float flOffset;
  338. Vector vecFinal;
  339. Vector vecOffset;
  340. matrix3x4_t matRotate;
  341. QAngle vecAngles;
  342. VectorAngles( vecBasis, vecAngles );
  343. vecAngles[PITCH] += 90.0f;
  344. AngleMatrix( vecAngles, vecOrigin, matRotate );
  345. trace_t tr;
  346. float flScale = LINE_LENGTH * flDeviation;
  347. // For each beam, find its offset and trace outwards to place its endpoint
  348. for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
  349. {
  350. if ( flConvergencePerc >= 0.99f )
  351. {
  352. m_pAncillaryBeams[i]->TurnOn();
  353. continue;
  354. }
  355. m_pAncillaryBeams[i]->TurnOff();
  356. // Find the number of radians offset we are
  357. flOffset = (float) i * flRotationOffset + DEG2RAD( 30.0f );
  358. flOffset += (M_PI/8.0f) * sin( gpGlobals->curtime * 3.0f );
  359. // Construct a circle that's also offset by the line's length
  360. vecOffset.x = cos( flOffset ) * flScale;
  361. vecOffset.y = sin( flOffset ) * flScale;
  362. vecOffset.z = LINE_LENGTH;
  363. // Rotate this whole thing into the space of the basis vector
  364. VectorRotate( vecOffset, matRotate, vecFinal );
  365. VectorNormalize( vecFinal );
  366. // Trace a line down that vector to find where we'll eventually stop our line
  367. UTIL_TraceLine( vecOrigin, vecOrigin + ( vecFinal * LINE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  368. // Move the beam to that position
  369. m_pAncillaryBeams[i]->SetBrightness( 255.0f * flConvergencePerc );
  370. m_pAncillaryBeams[i]->SetEndPos( tr.startpos );
  371. m_pAncillaryBeams[i]->SetStartPos( tr.endpos );
  372. }
  373. }
  374. //-----------------------------------------------------------------------------
  375. // Sweep the laser sight towards the point where the gun should be aimed
  376. //-----------------------------------------------------------------------------
  377. void CNPC_Combine_Cannon::PaintTarget( const Vector &vecTarget, float flPaintTime )
  378. {
  379. // vecStart is the barrel of the gun (or the laser sight)
  380. Vector vecStart = GetBulletOrigin();
  381. // keep painttime from hitting 0 exactly.
  382. flPaintTime = MAX( flPaintTime, 0.000001f );
  383. // Find out where we are in the arc of the paint duration
  384. float flPaintPerc = GetWaitTimePercentage( flPaintTime, false );
  385. ScopeGlint();
  386. // Find out where along our line we're painting
  387. Vector vecCurrentDir;
  388. float flInterp = RemapValClamped( flPaintPerc, 0.0f, 0.5f, 0.0f, 1.0f );
  389. flInterp = clamp( flInterp, 0.0f, 1.0f );
  390. GetPaintAim( m_vecPaintStart, vecTarget, flInterp, &vecCurrentDir );
  391. #define THRESHOLD 0.9f
  392. float flNoiseScale;
  393. if ( flPaintPerc >= THRESHOLD )
  394. {
  395. flNoiseScale = 1 - (1 / (1 - THRESHOLD)) * ( flPaintPerc - THRESHOLD );
  396. }
  397. else if ( flPaintPerc <= 1 - THRESHOLD )
  398. {
  399. flNoiseScale = flPaintPerc / (1 - THRESHOLD);
  400. }
  401. else
  402. {
  403. flNoiseScale = 1;
  404. }
  405. // mult by P
  406. vecCurrentDir.x += flNoiseScale * ( sin( 3 * M_PI * gpGlobals->curtime ) * 0.0006 );
  407. vecCurrentDir.y += flNoiseScale * ( sin( 2 * M_PI * gpGlobals->curtime + 0.5 * M_PI ) * 0.0006 );
  408. vecCurrentDir.z += flNoiseScale * ( sin( 1.5 * M_PI * gpGlobals->curtime + M_PI ) * 0.0006 );
  409. // Find where our center is
  410. trace_t tr;
  411. UTIL_TraceLine( vecStart, vecStart + vecCurrentDir * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  412. m_vecPaintCursor = tr.endpos;
  413. // Update our beam position
  414. m_pBeam->SetEndPos( tr.startpos );
  415. m_pBeam->SetStartPos( tr.endpos );
  416. m_pBeam->SetBrightness( 255.0f * flPaintPerc );
  417. m_pBeam->RelinkBeam();
  418. // Find points around that center point and make our designators converge at that point over time
  419. UpdateAncillaryBeams( flPaintPerc, vecStart, vecCurrentDir );
  420. }
  421. //-----------------------------------------------------------------------------
  422. // Purpose:
  423. //-----------------------------------------------------------------------------
  424. void CNPC_Combine_Cannon::OnScheduleChange( void )
  425. {
  426. LaserOff();
  427. m_hBarrageTarget = NULL;
  428. BaseClass::OnScheduleChange();
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose:
  432. //-----------------------------------------------------------------------------
  433. void CNPC_Combine_Cannon::Precache( void )
  434. {
  435. PrecacheModel("models/combine_soldier.mdl");
  436. PrecacheModel("effects/bluelaser1.vmt");
  437. gHaloTexture = PrecacheModel("sprites/light_glow03.vmt");
  438. PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" );
  439. BaseClass::Precache();
  440. }
  441. //-----------------------------------------------------------------------------
  442. // Purpose:
  443. //-----------------------------------------------------------------------------
  444. void CNPC_Combine_Cannon::Spawn( void )
  445. {
  446. Precache();
  447. /// HACK:
  448. SetModel( "models/combine_soldier.mdl" );
  449. // Setup our ancillary beams but keep them hidden for now
  450. CreateLaser();
  451. CreateAncillaryBeams();
  452. m_iAmmoType = GetAmmoDef()->Index( "CombineHeavyCannon" );
  453. SetHullType( HULL_HUMAN );
  454. SetHullSizeNormal();
  455. UTIL_SetSize( this, Vector( -16, -16 , 0 ), Vector( 16, 16, 64 ) );
  456. SetSolid( SOLID_BBOX );
  457. AddSolidFlags( FSOLID_NOT_STANDABLE );
  458. SetMoveType( MOVETYPE_FLY );
  459. m_bloodColor = DONT_BLEED;
  460. m_iHealth = 10;
  461. m_flFieldOfView = DOT_45DEGREE;
  462. m_NPCState = NPC_STATE_NONE;
  463. if( HasSpawnFlags( SF_STARTDISABLED ) )
  464. {
  465. m_fEnabled = false;
  466. }
  467. else
  468. {
  469. m_fEnabled = true;
  470. }
  471. CapabilitiesClear();
  472. CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SIMPLE_RADIUS_DAMAGE );
  473. m_HackedGunPos = Vector ( 0, 0, 0 );
  474. AddSpawnFlags( SF_NPC_LONG_RANGE | SF_NPC_ALWAYSTHINK );
  475. NPCInit();
  476. // Limit our look distance
  477. SetDistLook( m_flSightDist );
  478. AddEffects( EF_NODRAW );
  479. AddSolidFlags( FSOLID_NOT_SOLID );
  480. // Point the cursor straight ahead so that the sniper's
  481. // first sweep of the laser doesn't look weird.
  482. Vector vecForward;
  483. AngleVectors( GetLocalAngles(), &vecForward );
  484. m_vecPaintCursor = GetBulletOrigin() + vecForward * 1024;
  485. // none!
  486. GetEnemies()->SetFreeKnowledgeDuration( 0.0f );
  487. GetEnemies()->SetEnemyDiscardTime( 2.0f );
  488. m_flTimeLastAttackedPlayer = 0.0f;
  489. }
  490. //-----------------------------------------------------------------------------
  491. // Purpose:
  492. //-----------------------------------------------------------------------------
  493. Class_T CNPC_Combine_Cannon::Classify( void )
  494. {
  495. if ( m_fEnabled )
  496. return CLASS_COMBINE;
  497. return CLASS_NONE;
  498. }
  499. //-----------------------------------------------------------------------------
  500. // Purpose:
  501. //-----------------------------------------------------------------------------
  502. Vector CNPC_Combine_Cannon::GetBulletOrigin( void )
  503. {
  504. return GetAbsOrigin();
  505. }
  506. //-----------------------------------------------------------------------------
  507. // Purpose: Nothing kills the cannon but entity I/O
  508. //-----------------------------------------------------------------------------
  509. int CNPC_Combine_Cannon::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  510. {
  511. // We are invulnerable to normal attacks for the moment
  512. return 0;
  513. }
  514. //---------------------------------------------------------
  515. // Purpose:
  516. //---------------------------------------------------------
  517. void CNPC_Combine_Cannon::UpdateOnRemove( void )
  518. {
  519. // Remove the main laser
  520. if ( m_pBeam != NULL )
  521. {
  522. UTIL_Remove( m_pBeam);
  523. m_pBeam = NULL;
  524. }
  525. // Remove our ancillary beams
  526. for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
  527. {
  528. if ( m_pAncillaryBeams[i] == NULL )
  529. continue;
  530. UTIL_Remove( m_pAncillaryBeams[i] );
  531. m_pAncillaryBeams[i] = NULL;
  532. }
  533. BaseClass::UpdateOnRemove();
  534. }
  535. //---------------------------------------------------------
  536. // Purpose:
  537. //---------------------------------------------------------
  538. int CNPC_Combine_Cannon::SelectSchedule ( void )
  539. {
  540. // Fire at our target
  541. if( GetEnemy() && HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  542. return SCHED_RANGE_ATTACK1;
  543. // Wait for a target
  544. // TODO: Sweep like a sniper?
  545. return SCHED_COMBAT_STAND;
  546. }
  547. //---------------------------------------------------------
  548. // Purpose:
  549. //---------------------------------------------------------
  550. bool CNPC_Combine_Cannon::FCanCheckAttacks ( void )
  551. {
  552. return true;
  553. }
  554. //---------------------------------------------------------
  555. //---------------------------------------------------------
  556. bool CNPC_Combine_Cannon::VerifyShot( CBaseEntity *pTarget )
  557. {
  558. trace_t tr;
  559. Vector vecTarget = DesiredBodyTarget( pTarget );
  560. UTIL_TraceLine( GetBulletOrigin(), vecTarget, MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
  561. if( tr.fraction != 1.0 )
  562. {
  563. if( pTarget->IsPlayer() )
  564. {
  565. // if the target is the player, do another trace to see if we can shoot his eyeposition. This should help
  566. // improve sniper responsiveness in cases where the player is hiding his chest from the sniper with his
  567. // head in full view.
  568. UTIL_TraceLine( GetBulletOrigin(), pTarget->EyePosition(), MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
  569. if( tr.fraction == 1.0 )
  570. {
  571. return true;
  572. }
  573. }
  574. // Trace hit something.
  575. if( tr.m_pEnt )
  576. {
  577. if( tr.m_pEnt->m_takedamage == DAMAGE_YES )
  578. {
  579. // Just shoot it if I can hurt it. Probably a breakable or glass pane.
  580. return true;
  581. }
  582. }
  583. return false;
  584. }
  585. else
  586. {
  587. return true;
  588. }
  589. }
  590. //---------------------------------------------------------
  591. //---------------------------------------------------------
  592. int CNPC_Combine_Cannon::RangeAttack1Conditions( float flDot, float flDist )
  593. {
  594. if ( GetNextAttack() > gpGlobals->curtime )
  595. return COND_NONE;
  596. if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_ENEMY_OCCLUDED ) )
  597. {
  598. if ( VerifyShot( GetEnemy() ) )
  599. {
  600. // Can see the enemy, have a clear shot to his midsection
  601. ClearCondition( COND_CANNON_NO_SHOT );
  602. return COND_CAN_RANGE_ATTACK1;
  603. }
  604. else
  605. {
  606. // Can see the enemy, but can't take a shot at his midsection
  607. SetCondition( COND_CANNON_NO_SHOT );
  608. return COND_NONE;
  609. }
  610. }
  611. return COND_NONE;
  612. }
  613. //---------------------------------------------------------
  614. //---------------------------------------------------------
  615. int CNPC_Combine_Cannon::TranslateSchedule( int scheduleType )
  616. {
  617. switch( scheduleType )
  618. {
  619. case SCHED_RANGE_ATTACK1:
  620. return SCHED_CANNON_ATTACK;
  621. break;
  622. }
  623. return BaseClass::TranslateSchedule( scheduleType );
  624. }
  625. //---------------------------------------------------------
  626. //---------------------------------------------------------
  627. void CNPC_Combine_Cannon::ScopeGlint( void )
  628. {
  629. CEffectData data;
  630. data.m_vOrigin = GetAbsOrigin();
  631. data.m_vNormal = vec3_origin;
  632. data.m_vAngles = vec3_angle;
  633. data.m_nColor = COMMAND_POINT_BLUE;
  634. DispatchEffect( "CommandPointer", data );
  635. }
  636. //-----------------------------------------------------------------------------
  637. // Purpose:
  638. // Input : *vecIn -
  639. //-----------------------------------------------------------------------------
  640. void CNPC_Combine_Cannon::AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn )
  641. {
  642. if ( pTarget == NULL || vecIn == NULL )
  643. return;
  644. Vector low = pTarget->WorldSpaceCenter() - ( pTarget->WorldSpaceCenter() - pTarget->GetAbsOrigin() ) * .25;
  645. Vector high = pTarget->EyePosition();
  646. Vector delta = high - low;
  647. Vector result = low + delta * 0.5;
  648. // Only take the height
  649. (*vecIn)[2] = result[2];
  650. }
  651. //---------------------------------------------------------
  652. // This starts the bullet state machine. The actual effects
  653. // of the bullet will happen later. This function schedules
  654. // those effects.
  655. //
  656. // fDirectShot indicates whether the bullet is a "direct shot"
  657. // that is - fired with the intent that it will strike the
  658. // enemy. Otherwise, the bullet is intended to strike a
  659. // decoy object or nothing at all in particular.
  660. //---------------------------------------------------------
  661. bool CNPC_Combine_Cannon::FireBullet( const Vector &vecTarget, bool bDirectShot )
  662. {
  663. Vector vecBulletOrigin = GetBulletOrigin();
  664. Vector vecDir = ( vecTarget - vecBulletOrigin );
  665. VectorNormalize( vecDir );
  666. FireBulletsInfo_t info;
  667. info.m_iShots = 1;
  668. info.m_iTracerFreq = 1.0f;
  669. info.m_vecDirShooting = vecDir;
  670. info.m_vecSrc = vecBulletOrigin;
  671. info.m_flDistance = MAX_TRACE_LENGTH;
  672. info.m_pAttacker = this;
  673. info.m_iAmmoType = m_iAmmoType;
  674. info.m_iPlayerDamage = 20.0f;
  675. info.m_vecSpread = Vector( 0.015f, 0.015f, 0.015f ); // medium cone
  676. FireBullets( info );
  677. EmitSound( "NPC_Combine_Cannon.FireBullet" );
  678. // Don't attack for a certain amount of time
  679. SetNextAttack( gpGlobals->curtime + GetRefireTime() );
  680. // Sniper had to be aiming here to fire here, so make it the cursor
  681. m_vecPaintCursor = vecTarget;
  682. LaserOff();
  683. return true;
  684. }
  685. //---------------------------------------------------------
  686. //---------------------------------------------------------
  687. void CNPC_Combine_Cannon::StartTask( const Task_t *pTask )
  688. {
  689. switch( pTask->iTask )
  690. {
  691. case TASK_CANNON_ATTACK_CURSOR:
  692. break;
  693. case TASK_RANGE_ATTACK1:
  694. // Setup the information for this barrage
  695. m_flBarrageDuration = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.5f );
  696. m_hBarrageTarget = GetEnemy();
  697. break;
  698. case TASK_CANNON_PAINT_ENEMY:
  699. {
  700. if ( GetEnemy()->IsPlayer() )
  701. {
  702. float delay = random->RandomFloat( 0.0f, 0.3f );
  703. if ( ( gpGlobals->curtime - m_flTimeLastAttackedPlayer ) < 1.0f )
  704. {
  705. SetWait( CANNON_SUBSEQUENT_PAINT_TIME );
  706. m_flPaintTime = CANNON_SUBSEQUENT_PAINT_TIME;
  707. }
  708. else
  709. {
  710. SetWait( CANNON_PAINT_ENEMY_TIME + delay );
  711. m_flPaintTime = CANNON_PAINT_ENEMY_TIME + delay;
  712. }
  713. }
  714. else
  715. {
  716. // Use a random time
  717. m_flPaintTime = CANNON_PAINT_ENEMY_TIME + random->RandomFloat( 0, CANNON_PAINT_NPC_TIME_NOISE );
  718. SetWait( m_flPaintTime );
  719. }
  720. // Try to start the laser where the player can't miss seeing it!
  721. Vector vecCursor;
  722. AngleVectors( GetEnemy()->GetLocalAngles(), &vecCursor );
  723. vecCursor *= 300;
  724. vecCursor += GetEnemy()->EyePosition();
  725. LaserOn( vecCursor, Vector( 16, 16, 16 ) );
  726. }
  727. break;
  728. default:
  729. BaseClass::StartTask( pTask );
  730. break;
  731. }
  732. }
  733. //---------------------------------------------------------
  734. //---------------------------------------------------------
  735. void CNPC_Combine_Cannon::RunTask( const Task_t *pTask )
  736. {
  737. switch( pTask->iTask )
  738. {
  739. case TASK_CANNON_ATTACK_CURSOR:
  740. if( FireBullet( m_vecPaintCursor, true ) )
  741. {
  742. TaskComplete();
  743. }
  744. break;
  745. case TASK_RANGE_ATTACK1:
  746. {
  747. // Where we're focusing our fire
  748. Vector vecTarget = ( m_hBarrageTarget == NULL ) ? m_vecPaintCursor : LeadTarget( m_hBarrageTarget );
  749. // Fire at enemy
  750. if ( FireBullet( vecTarget, true ) )
  751. {
  752. bool bPlayerIsEnemy = ( m_hBarrageTarget && m_hBarrageTarget->IsPlayer() );
  753. bool bBarrageFinished = m_flBarrageDuration < gpGlobals->curtime;
  754. bool bNoShot = ( QuerySeeEntity( m_hBarrageTarget ) == false ); // FIXME: Store this info off better
  755. bool bSeePlayer = HasCondition( COND_SEE_PLAYER );
  756. // Treat the player differently to normal NPCs
  757. if ( bPlayerIsEnemy )
  758. {
  759. // Store the last time we shot for doing an abbreviated attack telegraph
  760. m_flTimeLastAttackedPlayer = gpGlobals->curtime;
  761. // If we've got no shot and we're done with our current barrage
  762. if ( bNoShot && bBarrageFinished )
  763. {
  764. TaskComplete();
  765. }
  766. }
  767. else if ( bBarrageFinished || bSeePlayer )
  768. {
  769. // Done with the barrage or we saw the player as a better target
  770. TaskComplete();
  771. }
  772. }
  773. }
  774. break;
  775. case TASK_CANNON_PAINT_ENEMY:
  776. {
  777. // See if we're done painting our target
  778. if ( IsWaitFinished() )
  779. {
  780. TaskComplete();
  781. }
  782. // Continue to paint the target
  783. PaintTarget( LeadTarget( GetEnemy() ), m_flPaintTime );
  784. }
  785. break;
  786. default:
  787. BaseClass::RunTask( pTask );
  788. break;
  789. }
  790. }
  791. //-----------------------------------------------------------------------------
  792. // The sniper throws away the circular list of old decoys when we restore.
  793. //-----------------------------------------------------------------------------
  794. int CNPC_Combine_Cannon::Restore( IRestore &restore )
  795. {
  796. return BaseClass::Restore( restore );
  797. }
  798. //-----------------------------------------------------------------------------
  799. // Purpose:
  800. //
  801. //
  802. //-----------------------------------------------------------------------------
  803. float CNPC_Combine_Cannon::MaxYawSpeed( void )
  804. {
  805. return 60;
  806. }
  807. //---------------------------------------------------------
  808. //---------------------------------------------------------
  809. void CNPC_Combine_Cannon::PrescheduleThink( void )
  810. {
  811. BaseClass::PrescheduleThink();
  812. // NOTE: We'll deal with this on the client
  813. // Think faster if the beam is on, this gives the beam higher resolution.
  814. if( m_pBeam )
  815. {
  816. SetNextThink( gpGlobals->curtime + 0.03 );
  817. }
  818. else
  819. {
  820. SetNextThink( gpGlobals->curtime + 0.1f );
  821. }
  822. // If the enemy has just stepped into view, or we've acquired a new enemy,
  823. // Record the last time we've seen the enemy as right now.
  824. //
  825. // If the enemy has been out of sight for a full second, mark him eluded.
  826. if( GetEnemy() != NULL )
  827. {
  828. if( gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ) > 30 )
  829. {
  830. // Stop pestering enemies after 30 seconds of frustration.
  831. GetEnemies()->ClearMemory( GetEnemy() );
  832. SetEnemy(NULL);
  833. }
  834. }
  835. }
  836. //---------------------------------------------------------
  837. //---------------------------------------------------------
  838. Vector CNPC_Combine_Cannon::EyePosition( void )
  839. {
  840. return GetAbsOrigin();
  841. }
  842. //---------------------------------------------------------
  843. //---------------------------------------------------------
  844. Vector CNPC_Combine_Cannon::DesiredBodyTarget( CBaseEntity *pTarget )
  845. {
  846. // By default, aim for the center
  847. Vector vecTarget = pTarget->WorldSpaceCenter();
  848. float flTimeSinceLastMiss = gpGlobals->curtime - m_flTimeLastShotMissed;
  849. if( pTarget->GetFlags() & FL_CLIENT )
  850. {
  851. if( !BaseClass::FVisible( vecTarget ) )
  852. {
  853. // go to the player's eyes if his center is concealed.
  854. // Bump up an inch so the player's not looking straight down a beam.
  855. vecTarget = pTarget->EyePosition() + Vector( 0, 0, 1 );
  856. }
  857. }
  858. else
  859. {
  860. if( pTarget->Classify() == CLASS_HEADCRAB )
  861. {
  862. // Headcrabs are tiny inside their boxes.
  863. vecTarget = pTarget->GetAbsOrigin();
  864. vecTarget.z += 4.0;
  865. }
  866. else if( pTarget->Classify() == CLASS_ZOMBIE )
  867. {
  868. if( flTimeSinceLastMiss > 0.0f && flTimeSinceLastMiss < 4.0f && hl2_episodic.GetBool() )
  869. {
  870. vecTarget = pTarget->BodyTarget( GetBulletOrigin(), false );
  871. }
  872. else
  873. {
  874. // Shoot zombies in the headcrab
  875. vecTarget = pTarget->HeadTarget( GetBulletOrigin() );
  876. }
  877. }
  878. else if( pTarget->Classify() == CLASS_ANTLION )
  879. {
  880. // Shoot about a few inches above the origin. This makes it easy to hit antlions
  881. // even if they are on their backs.
  882. vecTarget = pTarget->GetAbsOrigin();
  883. vecTarget.z += 18.0f;
  884. }
  885. else if( pTarget->Classify() == CLASS_EARTH_FAUNA )
  886. {
  887. // Shoot birds in the center
  888. }
  889. else
  890. {
  891. // Shoot NPCs in the chest
  892. vecTarget.z += 8.0f;
  893. }
  894. }
  895. return vecTarget;
  896. }
  897. //---------------------------------------------------------
  898. //---------------------------------------------------------
  899. Vector CNPC_Combine_Cannon::LeadTarget( CBaseEntity *pTarget )
  900. {
  901. if ( pTarget != NULL )
  902. {
  903. Vector vecFuturePos;
  904. UTIL_PredictedPosition( pTarget, 0.05f, &vecFuturePos );
  905. AdjustShotPosition( pTarget, &vecFuturePos );
  906. return vecFuturePos;
  907. }
  908. return vec3_origin;
  909. }
  910. //---------------------------------------------------------
  911. //---------------------------------------------------------
  912. void CNPC_Combine_Cannon::InputEnableSniper( inputdata_t &inputdata )
  913. {
  914. ClearCondition( COND_CANNON_DISABLED );
  915. SetCondition( COND_CANNON_ENABLED );
  916. m_fEnabled = true;
  917. }
  918. //---------------------------------------------------------
  919. //---------------------------------------------------------
  920. void CNPC_Combine_Cannon::InputDisableSniper( inputdata_t &inputdata )
  921. {
  922. ClearCondition( COND_CANNON_ENABLED );
  923. SetCondition( COND_CANNON_DISABLED );
  924. m_fEnabled = false;
  925. }
  926. //---------------------------------------------------------
  927. // See all NPC's easily.
  928. //
  929. // Only see the player if you can trace to both of his
  930. // eyeballs. That is, allow the player to peek around corners.
  931. // This is a little more expensive than the base class' check!
  932. //---------------------------------------------------------
  933. #define CANNON_EYE_DIST 0.75
  934. #define CANNON_TARGET_VERTICAL_OFFSET Vector( 0, 0, 5 );
  935. bool CNPC_Combine_Cannon::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
  936. {
  937. // NPC
  938. if ( pEntity->IsPlayer() == false )
  939. return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
  940. if ( pEntity->GetFlags() & FL_NOTARGET )
  941. return false;
  942. Vector vecVerticalOffset;
  943. Vector vecRight;
  944. Vector vecEye;
  945. trace_t tr;
  946. if( fabs( GetAbsOrigin().z - pEntity->WorldSpaceCenter().z ) <= 120.f )
  947. {
  948. // If the player is around the same elevation, look straight at his eyes.
  949. // At the same elevation, the vertical peeking allowance makes it too easy
  950. // for a player to dispatch the sniper from cover.
  951. vecVerticalOffset = vec3_origin;
  952. }
  953. else
  954. {
  955. // Otherwise, look at a spot below his eyes. This allows the player to back away
  956. // from his cover a bit and have a peek at the sniper without being detected.
  957. vecVerticalOffset = CANNON_TARGET_VERTICAL_OFFSET;
  958. }
  959. AngleVectors( pEntity->GetLocalAngles(), NULL, &vecRight, NULL );
  960. vecEye = vecRight * CANNON_EYE_DIST - vecVerticalOffset;
  961. UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  962. #if 0
  963. NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
  964. #endif
  965. bool fCheckFailed = false;
  966. if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
  967. {
  968. fCheckFailed = true;
  969. }
  970. // Don't check the other eye if the first eye failed.
  971. if( !fCheckFailed )
  972. {
  973. vecEye = -vecRight * CANNON_EYE_DIST - vecVerticalOffset;
  974. UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  975. #if 0
  976. NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
  977. #endif
  978. if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
  979. {
  980. fCheckFailed = true;
  981. }
  982. }
  983. if( !fCheckFailed )
  984. {
  985. // Can see the player.
  986. return true;
  987. }
  988. // Now, if the check failed, see if the player is ducking and has recently
  989. // fired a muzzleflash. If yes, see if you'd be able to see the player if
  990. // they were standing in their current position instead of ducking. Since
  991. // the sniper doesn't have a clear shot in this situation, he will harrass
  992. // near the player.
  993. CBasePlayer *pPlayer;
  994. pPlayer = ToBasePlayer( pEntity );
  995. if( (pPlayer->GetFlags() & FL_DUCKING) && pPlayer->MuzzleFlashTime() > gpGlobals->curtime )
  996. {
  997. vecEye = pPlayer->EyePosition() + Vector( 0, 0, 32 );
  998. UTIL_TraceLine( EyePosition(), vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  999. if( tr.fraction != 1.0 )
  1000. {
  1001. // Everything failed.
  1002. if (ppBlocker)
  1003. {
  1004. *ppBlocker = tr.m_pEnt;
  1005. }
  1006. return false;
  1007. }
  1008. else
  1009. {
  1010. // Fake being able to see the player.
  1011. return true;
  1012. }
  1013. }
  1014. if (ppBlocker)
  1015. {
  1016. *ppBlocker = tr.m_pEnt;
  1017. }
  1018. return false;
  1019. }
  1020. //-----------------------------------------------------------------------------
  1021. //
  1022. // Schedules
  1023. //
  1024. //-----------------------------------------------------------------------------
  1025. AI_BEGIN_CUSTOM_NPC( npc_combine_cannon, CNPC_Combine_Cannon )
  1026. DECLARE_CONDITION( COND_CANNON_ENABLED );
  1027. DECLARE_CONDITION( COND_CANNON_DISABLED );
  1028. DECLARE_CONDITION( COND_CANNON_NO_SHOT );
  1029. DECLARE_TASK( TASK_CANNON_PAINT_ENEMY );
  1030. DECLARE_TASK( TASK_CANNON_ATTACK_CURSOR );
  1031. //=========================================================
  1032. // CAMP
  1033. //=========================================================
  1034. DEFINE_SCHEDULE
  1035. (
  1036. SCHED_CANNON_CAMP,
  1037. " Tasks"
  1038. " TASK_WAIT 1"
  1039. " "
  1040. " Interrupts"
  1041. " COND_NEW_ENEMY"
  1042. " COND_ENEMY_DEAD"
  1043. " COND_CAN_RANGE_ATTACK1"
  1044. " COND_HEAR_DANGER"
  1045. " COND_CANNON_DISABLED"
  1046. )
  1047. //=========================================================
  1048. // ATTACK
  1049. //=========================================================
  1050. DEFINE_SCHEDULE
  1051. (
  1052. SCHED_CANNON_ATTACK,
  1053. " Tasks"
  1054. " TASK_CANNON_PAINT_ENEMY 0"
  1055. " TASK_RANGE_ATTACK1 0"
  1056. " "
  1057. " Interrupts"
  1058. " COND_HEAR_DANGER"
  1059. " COND_CANNON_DISABLED"
  1060. )
  1061. //=========================================================
  1062. // ATTACK
  1063. //=========================================================
  1064. DEFINE_SCHEDULE
  1065. (
  1066. SCHED_CANNON_SNAPATTACK,
  1067. " Tasks"
  1068. " TASK_CANNON_ATTACK_CURSOR 0"
  1069. " "
  1070. " Interrupts"
  1071. " COND_ENEMY_OCCLUDED"
  1072. " COND_ENEMY_DEAD"
  1073. " COND_NEW_ENEMY"
  1074. " COND_HEAR_DANGER"
  1075. " COND_CANNON_DISABLED"
  1076. )
  1077. //=========================================================
  1078. // Sniper is allowed to process a couple conditions while
  1079. // disabled, but mostly he waits until he's enabled.
  1080. //=========================================================
  1081. DEFINE_SCHEDULE
  1082. (
  1083. SCHED_CANNON_DISABLEDWAIT,
  1084. " Tasks"
  1085. " TASK_WAIT 0.5"
  1086. " "
  1087. " Interrupts"
  1088. " COND_CANNON_ENABLED"
  1089. " COND_NEW_ENEMY"
  1090. " COND_ENEMY_DEAD"
  1091. )
  1092. AI_END_CUSTOM_NPC()