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.

3496 lines
91 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "ai_default.h"
  9. #include "ai_basenpc.h"
  10. #include "ammodef.h"
  11. #include "ai_task.h"
  12. #include "ai_schedule.h"
  13. #include "ai_node.h"
  14. #include "ai_hull.h"
  15. #include "ai_memory.h"
  16. #include "ai_senses.h"
  17. #include "beam_shared.h"
  18. #include "game.h"
  19. #include "npcevent.h"
  20. #include "entitylist.h"
  21. #include "activitylist.h"
  22. #include "soundent.h"
  23. #include "gib.h"
  24. #include "ndebugoverlay.h"
  25. #include "smoke_trail.h"
  26. #include "weapon_rpg.h"
  27. #include "player.h"
  28. #include "mathlib/mathlib.h"
  29. #include "vstdlib/random.h"
  30. #include "engine/IEngineSound.h"
  31. #include "IEffects.h"
  32. #include "effect_color_tables.h"
  33. #include "npc_rollermine.h"
  34. #include "eventqueue.h"
  35. #include "effect_dispatch_data.h"
  36. #include "te_effect_dispatch.h"
  37. #include "collisionutils.h"
  38. // memdbgon must be the last include file in a .cpp file!!!
  39. #include "tier0/memdbgon.h"
  40. extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint);
  41. ConVar bulletSpeed( "bulletspeed", "6000" );
  42. ConVar sniperLines( "showsniperlines", "0" );
  43. ConVar sniperviewdist("sniperviewdist", "35" );
  44. ConVar showsniperdist("showsniperdist", "0" );
  45. ConVar sniperspeak( "sniperspeak", "0" );
  46. ConVar sniper_xbox_delay( "sniper_xbox_delay", "1" );
  47. // Moved to HL2_SharedGameRules because these are referenced by shared AmmoDef functions
  48. extern ConVar sk_dmg_sniper_penetrate_plr;
  49. extern ConVar sk_dmg_sniper_penetrate_npc;
  50. // No model, impervious to damage.
  51. #define SF_SNIPER_HIDDEN (1 << 16)
  52. #define SF_SNIPER_VIEWCONE (1 << 17) ///< when set, sniper only sees in a small cone around the laser.
  53. #define SF_SNIPER_NOCORPSE (1 << 18) ///< when set, no corpse
  54. #define SF_SNIPER_STARTDISABLED (1 << 19)
  55. #define SF_SNIPER_FAST (1 << 20) ///< This is faster-shooting sniper. Paint time is decreased 25%. Bullet speed increases 150%.
  56. #define SF_SNIPER_NOSWEEP (1 << 21) ///< This sniper doesn't sweep to the target or use decoys.
  57. // If the last time I fired at someone was between 0 and this many seconds, draw
  58. // a bead on them much faster. (use subsequent paint time)
  59. #define SNIPER_FASTER_ATTACK_PERIOD 3.0f
  60. // These numbers determine the interval between shots. They used to be constants,
  61. // but are now keyfields. HL2 backwards compatibility was maintained by supplying
  62. // default values in the constructor.
  63. #if 0
  64. // How long to aim at someone before shooting them.
  65. #define SNIPER_PAINT_ENEMY_TIME 1.0f
  66. // ...plus this
  67. #define SNIPER_PAINT_NPC_TIME_NOISE 0.75f
  68. #else
  69. // How long to aim at someone before shooting them.
  70. #define SNIPER_DEFAULT_PAINT_ENEMY_TIME 1.0f
  71. // ...plus this
  72. #define SNIPER_DEFAULT_PAINT_NPC_TIME_NOISE 0.75f
  73. #endif
  74. #define SNIPER_SUBSEQUENT_PAINT_TIME ( ( IsXbox() ) ? 1.0f : 0.4f )
  75. #define SNIPER_FOG_PAINT_ENEMY_TIME 0.25f
  76. #define SNIPER_PAINT_DECOY_TIME 2.0f
  77. #define SNIPER_PAINT_FRUSTRATED_TIME 1.0f
  78. #define SNIPER_QUICKAIM_TIME 0.2f
  79. #define SNIPER_PAINT_NO_SHOT_TIME 0.7f
  80. #define SNIPER_DECOY_MAX_MASS 200.0f
  81. // #def'ing this will turn on heaps of sniper debug messages.
  82. #undef SNIPER_DEBUG
  83. // Target protection
  84. #define SNIPER_PROTECTION_MINDIST (1024.0*1024.0) // Distance around protect target that sniper does priority modification in
  85. #define SNIPER_PROTECTION_PRIORITYCAP 100.0 // Max addition to priority of an enemy right next to the protect target, falls to 0 at SNIPER_PROTECTION_MINDIST.
  86. //---------------------------------------------------------
  87. // Like an infotarget, but shares a spawnflag that has
  88. // relevance to the sniper.
  89. //---------------------------------------------------------
  90. #define SF_SNIPERTARGET_SHOOTME 1
  91. #define SF_SNIPERTARGET_NOINTERRUPT 2
  92. #define SF_SNIPERTARGET_SNAPSHOT 4
  93. #define SF_SNIPERTARGET_RESUME 8
  94. #define SF_SNIPERTARGET_SNAPTO 16
  95. #define SF_SNIPERTARGET_FOCUS 32
  96. #define SNIPER_DECOY_RADIUS 256
  97. #define SNIPER_NUM_DECOYS 5
  98. #define NUM_OLDDECOYS 5
  99. #define NUM_PENETRATIONS 3
  100. #define PENETRATION_THICKNESS 5
  101. #define SNIPER_MAX_GROUP_TARGETS 16
  102. //=========================================================
  103. //=========================================================
  104. class CSniperTarget : public CPointEntity
  105. {
  106. DECLARE_DATADESC();
  107. public:
  108. DECLARE_CLASS( CSniperTarget, CPointEntity );
  109. bool KeyValue( const char *szKeyName, const char *szValue );
  110. string_t m_iszGroupName;
  111. };
  112. //---------------------------------------------------------
  113. // Save/Restore
  114. //---------------------------------------------------------
  115. BEGIN_DATADESC( CSniperTarget )
  116. DEFINE_FIELD( m_iszGroupName, FIELD_STRING ),
  117. END_DATADESC()
  118. //=========================================================
  119. //=========================================================
  120. class CSniperBullet : public CBaseEntity
  121. {
  122. public:
  123. DECLARE_CLASS( CSniperBullet, CBaseEntity );
  124. CSniperBullet( void ) { Init(); }
  125. Vector m_vecDir;
  126. Vector m_vecStart;
  127. Vector m_vecEnd;
  128. float m_flLastThink;
  129. float m_SoundTime;
  130. int m_AmmoType;
  131. int m_PenetratedAmmoType;
  132. float m_Speed;
  133. bool m_bDirectShot;
  134. void Precache( void );
  135. bool IsActive( void ) { return m_fActive; }
  136. bool Start( const Vector &vecOrigin, const Vector &vecTarget, CBaseEntity *pOwner, bool bDirectShot );
  137. void Stop( void );
  138. void BulletThink( void );
  139. void Init( void );
  140. DECLARE_DATADESC();
  141. private:
  142. // Only one shot per sniper at a time. If a bullet hasn't
  143. // hit, the shooter must wait.
  144. bool m_fActive;
  145. // This tracks how many times this single bullet has
  146. // struck. This is for penetration, so the bullet can
  147. // go through things.
  148. int m_iImpacts;
  149. };
  150. //=========================================================
  151. //=========================================================
  152. class CProtoSniper : public CAI_BaseNPC
  153. {
  154. DECLARE_CLASS( CProtoSniper, CAI_BaseNPC );
  155. public:
  156. CProtoSniper( void );
  157. void Precache( void );
  158. void Spawn( void );
  159. Class_T Classify( void );
  160. float MaxYawSpeed( void );
  161. Vector EyePosition( void );
  162. void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
  163. bool IsLaserOn( void ) { return m_pBeam != NULL; }
  164. void Event_Killed( const CTakeDamageInfo &info );
  165. void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info );
  166. void UpdateOnRemove( void );
  167. int OnTakeDamage_Alive( const CTakeDamageInfo &info );
  168. bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions) {return true;}
  169. int IRelationPriority( CBaseEntity *pTarget );
  170. bool IsFastSniper() { return HasSpawnFlags(SF_SNIPER_FAST); }
  171. bool QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC = false );
  172. virtual bool FInViewCone( CBaseEntity *pEntity );
  173. void StartTask( const Task_t *pTask );
  174. void RunTask( const Task_t *pTask );
  175. int RangeAttack1Conditions ( float flDot, float flDist );
  176. bool FireBullet( const Vector &vecTarget, bool bDirectShot );
  177. float GetBulletSpeed();
  178. Vector DesiredBodyTarget( CBaseEntity *pTarget );
  179. Vector LeadTarget( CBaseEntity *pTarget );
  180. CBaseEntity *PickDeadPlayerTarget();
  181. virtual int SelectSchedule( void );
  182. virtual int TranslateSchedule( int scheduleType );
  183. bool KeyValue( const char *szKeyName, const char *szValue );
  184. void PrescheduleThink( void );
  185. static const char *pAttackSounds[];
  186. bool FCanCheckAttacks ( void );
  187. bool FindDecoyObject( void );
  188. void ScopeGlint();
  189. int GetSoundInterests( void );
  190. void OnListened();
  191. Vector GetBulletOrigin( void );
  192. virtual int Restore( IRestore &restore );
  193. virtual void OnScheduleChange( void );
  194. bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
  195. bool ShouldNotDistanceCull() { return true; }
  196. int DrawDebugTextOverlays();
  197. void NotifyShotMissedTarget();
  198. private:
  199. bool ShouldSnapShot( void );
  200. void ClearTargetGroup( void );
  201. float GetPositionParameter( float flTime, bool fLinear );
  202. void GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress );
  203. bool IsSweepingRandomly( void ) { return m_iNumGroupTargets > 0; }
  204. void ClearOldDecoys( void );
  205. void AddOldDecoy( CBaseEntity *pDecoy );
  206. bool HasOldDecoy( CBaseEntity *pDecoy );
  207. bool FindFrustratedShot( float flNoise );
  208. bool VerifyShot( CBaseEntity *pTarget );
  209. void SetSweepTarget( const char *pszTarget );
  210. // Inputs
  211. void InputEnableSniper( inputdata_t &inputdata );
  212. void InputDisableSniper( inputdata_t &inputdata );
  213. void InputSetDecoyRadius( inputdata_t &inputdata );
  214. void InputSweepTarget( inputdata_t &inputdata );
  215. void InputSweepTargetHighestPriority( inputdata_t &inputdata );
  216. void InputSweepGroupRandomly( inputdata_t &inputdata );
  217. void InputStopSweeping( inputdata_t &inputdata );
  218. void InputProtectTarget( inputdata_t &inputdata );
  219. #if HL2_EPISODIC
  220. void InputSetPaintInterval( inputdata_t &inputdata );
  221. void InputSetPaintIntervalVariance( inputdata_t &inputdata );
  222. #endif
  223. void LaserOff( void );
  224. void LaserOn( const Vector &vecTarget, const Vector &vecDeviance );
  225. void PaintTarget( const Vector &vecTarget, float flPaintTime );
  226. bool IsPlayerAllySniper();
  227. private:
  228. /// This is the variable from which m_flPaintTime gets set.
  229. /// How long to aim at someone before shooting them.
  230. float m_flKeyfieldPaintTime;
  231. /// A random number from 0 to this is added to m_flKeyfieldPaintTime
  232. /// to yield m_flPaintTime's initial delay.
  233. float m_flKeyfieldPaintTimeNoise;
  234. // This keeps track of the last spot the laser painted. For
  235. // continuous sweeping that changes direction.
  236. Vector m_vecPaintCursor;
  237. float m_flPaintTime;
  238. bool m_fWeaponLoaded;
  239. bool m_fEnabled;
  240. bool m_fIsPatient;
  241. float m_flPatience;
  242. int m_iMisses;
  243. EHANDLE m_hDecoyObject;
  244. EHANDLE m_hSweepTarget;
  245. Vector m_vecDecoyObjectTarget;
  246. Vector m_vecFrustratedTarget;
  247. Vector m_vecPaintStart; // used to track where a sweep starts for the purpose of interpolating.
  248. float m_flFrustration;
  249. float m_flThinkInterval;
  250. float m_flDecoyRadius;
  251. CBeam *m_pBeam;
  252. bool m_fSnapShot;
  253. int m_iNumGroupTargets;
  254. CBaseEntity *m_pGroupTarget[ SNIPER_MAX_GROUP_TARGETS ];
  255. bool m_bSweepHighestPriority; // My hack :[ (sjb)
  256. int m_iBeamBrightness;
  257. // bullet stopping energy shield effect.
  258. float m_flShieldDist;
  259. float m_flShieldRadius;
  260. float m_flTimeLastAttackedPlayer;
  261. // Protection
  262. EHANDLE m_hProtectTarget; // Entity that this sniper is supposed to protect
  263. float m_flDangerEnemyDistance; // Distance to the enemy nearest the protect target
  264. // Have I warned the target that I'm pointing my laser at them?
  265. bool m_bWarnedTargetEntity;
  266. float m_flTimeLastShotMissed;
  267. bool m_bKilledPlayer;
  268. bool m_bShootZombiesInChest; ///< if true, do not try to shoot zombies in the headcrab
  269. COutputEvent m_OnShotFired;
  270. DEFINE_CUSTOM_AI;
  271. DECLARE_DATADESC();
  272. };
  273. //=========================================================
  274. //=========================================================
  275. // NOTES about the Sniper:
  276. //
  277. // PATIENCE:
  278. // The concept of "patience" is simply a restriction placed
  279. // on how close a target has to be to the sniper before the
  280. // sniper will take his first shot at the target. This
  281. // distance is referred to as "patience" is set by the `
  282. // designer in Worldcraft. The sniper won't attack unless
  283. // the target enters this radius. Once the sniper takes
  284. // this first shot, he will not return to a patient state.
  285. // He will then shoot at any/all targets to which there is
  286. // a clear shot, regardless of distance. (sjb)
  287. //
  288. //
  289. // TODO: Sniper accumulates frustration while reloading.
  290. // probably should subtract reload time from frustration.
  291. //=========================================================
  292. //=========================================================
  293. //=========================================================
  294. //=========================================================
  295. short sFlashSprite;
  296. short sHaloSprite;
  297. //=========================================================
  298. //=========================================================
  299. BEGIN_DATADESC( CProtoSniper )
  300. DEFINE_FIELD( m_fWeaponLoaded, FIELD_BOOLEAN ),
  301. DEFINE_FIELD( m_fEnabled, FIELD_BOOLEAN ),
  302. DEFINE_FIELD( m_fIsPatient, FIELD_BOOLEAN ),
  303. DEFINE_FIELD( m_flPatience, FIELD_FLOAT ),
  304. DEFINE_FIELD( m_iMisses, FIELD_INTEGER ),
  305. DEFINE_FIELD( m_hDecoyObject, FIELD_EHANDLE ),
  306. DEFINE_FIELD( m_hSweepTarget, FIELD_EHANDLE ),
  307. DEFINE_FIELD( m_vecDecoyObjectTarget, FIELD_VECTOR ),
  308. DEFINE_FIELD( m_vecFrustratedTarget, FIELD_VECTOR ),
  309. DEFINE_FIELD( m_vecPaintStart, FIELD_VECTOR ),
  310. DEFINE_FIELD( m_flPaintTime, FIELD_TIME ),
  311. DEFINE_FIELD( m_vecPaintCursor, FIELD_VECTOR ),
  312. DEFINE_FIELD( m_flFrustration, FIELD_TIME ),
  313. DEFINE_FIELD( m_flThinkInterval, FIELD_FLOAT ),
  314. DEFINE_FIELD( m_flDecoyRadius, FIELD_FLOAT ),
  315. DEFINE_FIELD( m_pBeam, FIELD_CLASSPTR ),
  316. DEFINE_FIELD( m_fSnapShot, FIELD_BOOLEAN ),
  317. DEFINE_FIELD( m_iNumGroupTargets, FIELD_INTEGER ),
  318. DEFINE_ARRAY( m_pGroupTarget, FIELD_CLASSPTR, SNIPER_MAX_GROUP_TARGETS ),
  319. DEFINE_KEYFIELD( m_iBeamBrightness, FIELD_INTEGER, "beambrightness" ),
  320. DEFINE_KEYFIELD(m_flShieldDist, FIELD_FLOAT, "shielddistance" ),
  321. DEFINE_KEYFIELD(m_flShieldRadius, FIELD_FLOAT, "shieldradius" ),
  322. DEFINE_KEYFIELD(m_bShootZombiesInChest, FIELD_BOOLEAN, "shootZombiesInChest" ),
  323. DEFINE_KEYFIELD(m_flKeyfieldPaintTime, FIELD_FLOAT, "PaintInterval" ),
  324. DEFINE_KEYFIELD(m_flKeyfieldPaintTimeNoise, FIELD_FLOAT, "PaintIntervalVariance" ),
  325. DEFINE_FIELD( m_flTimeLastAttackedPlayer, FIELD_TIME ),
  326. DEFINE_FIELD( m_hProtectTarget, FIELD_EHANDLE ),
  327. DEFINE_FIELD( m_flDangerEnemyDistance, FIELD_FLOAT ),
  328. DEFINE_FIELD( m_bSweepHighestPriority, FIELD_BOOLEAN ),
  329. DEFINE_FIELD( m_bWarnedTargetEntity, FIELD_BOOLEAN ),
  330. DEFINE_FIELD( m_flTimeLastShotMissed, FIELD_TIME ),
  331. // Inputs
  332. DEFINE_INPUTFUNC( FIELD_VOID, "EnableSniper", InputEnableSniper ),
  333. DEFINE_INPUTFUNC( FIELD_VOID, "DisableSniper", InputDisableSniper ),
  334. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDecoyRadius", InputSetDecoyRadius ),
  335. DEFINE_INPUTFUNC( FIELD_STRING, "SweepTarget", InputSweepTarget ),
  336. DEFINE_INPUTFUNC( FIELD_STRING, "SweepTargetHighestPriority", InputSweepTargetHighestPriority ),
  337. DEFINE_INPUTFUNC( FIELD_STRING, "SweepGroupRandomly", InputSweepGroupRandomly ),
  338. DEFINE_INPUTFUNC( FIELD_STRING, "StopSweeping", InputStopSweeping ),
  339. DEFINE_INPUTFUNC( FIELD_STRING, "ProtectTarget", InputProtectTarget ),
  340. #if HL2_EPISODIC
  341. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPaintInterval", InputSetPaintInterval ),
  342. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPaintIntervalVariance", InputSetPaintIntervalVariance ),
  343. #endif
  344. // Outputs
  345. DEFINE_OUTPUT( m_OnShotFired, "OnShotFired" ),
  346. END_DATADESC()
  347. //=========================================================
  348. //=========================================================
  349. BEGIN_DATADESC( CSniperBullet )
  350. DEFINE_FIELD( m_SoundTime, FIELD_TIME ),
  351. DEFINE_FIELD( m_AmmoType, FIELD_INTEGER ),
  352. DEFINE_FIELD( m_PenetratedAmmoType, FIELD_INTEGER ),
  353. DEFINE_FIELD( m_fActive, FIELD_BOOLEAN ),
  354. DEFINE_FIELD( m_iImpacts, FIELD_INTEGER ),
  355. DEFINE_FIELD( m_vecOrigin, FIELD_VECTOR ),
  356. DEFINE_FIELD( m_vecDir, FIELD_VECTOR ),
  357. DEFINE_FIELD( m_flLastThink, FIELD_TIME ),
  358. DEFINE_FIELD( m_Speed, FIELD_FLOAT ),
  359. DEFINE_FIELD( m_bDirectShot, FIELD_BOOLEAN ),
  360. DEFINE_FIELD( m_vecStart, FIELD_VECTOR ),
  361. DEFINE_FIELD( m_vecEnd, FIELD_VECTOR ),
  362. DEFINE_THINKFUNC( BulletThink ),
  363. END_DATADESC()
  364. //=========================================================
  365. // Private conditions
  366. //=========================================================
  367. enum Sniper_Conds
  368. {
  369. COND_SNIPER_CANATTACKDECOY = LAST_SHARED_CONDITION,
  370. COND_SNIPER_SUPPRESSED,
  371. COND_SNIPER_ENABLED,
  372. COND_SNIPER_DISABLED,
  373. COND_SNIPER_FRUSTRATED,
  374. COND_SNIPER_SWEEP_TARGET,
  375. COND_SNIPER_NO_SHOT,
  376. };
  377. //=========================================================
  378. // schedules
  379. //=========================================================
  380. enum
  381. {
  382. SCHED_PSNIPER_SCAN = LAST_SHARED_SCHEDULE,
  383. SCHED_PSNIPER_CAMP,
  384. SCHED_PSNIPER_ATTACK,
  385. SCHED_PSNIPER_RELOAD,
  386. SCHED_PSNIPER_ATTACKDECOY,
  387. SCHED_PSNIPER_SUPPRESSED,
  388. SCHED_PSNIPER_DISABLEDWAIT,
  389. SCHED_PSNIPER_FRUSTRATED_ATTACK,
  390. SCHED_PSNIPER_SWEEP_TARGET,
  391. SCHED_PSNIPER_SWEEP_TARGET_NOINTERRUPT,
  392. SCHED_PSNIPER_SNAPATTACK,
  393. SCHED_PSNIPER_NO_CLEAR_SHOT,
  394. SCHED_PSNIPER_PLAYER_DEAD,
  395. };
  396. //=========================================================
  397. // tasks
  398. //=========================================================
  399. enum
  400. {
  401. TASK_SNIPER_FRUSTRATED_ATTACK = LAST_SHARED_TASK,
  402. TASK_SNIPER_PAINT_ENEMY,
  403. TASK_SNIPER_PAINT_DECOY,
  404. TASK_SNIPER_PAINT_FRUSTRATED,
  405. TASK_SNIPER_PAINT_SWEEP_TARGET,
  406. TASK_SNIPER_ATTACK_CURSOR,
  407. TASK_SNIPER_PAINT_NO_SHOT,
  408. TASK_SNIPER_PLAYER_DEAD,
  409. };
  410. CProtoSniper::CProtoSniper( void ) : m_flKeyfieldPaintTime(SNIPER_DEFAULT_PAINT_ENEMY_TIME),
  411. m_flKeyfieldPaintTimeNoise(SNIPER_DEFAULT_PAINT_NPC_TIME_NOISE)
  412. {
  413. #ifdef _DEBUG
  414. m_vecPaintCursor.Init();
  415. m_vecDecoyObjectTarget.Init();
  416. m_vecFrustratedTarget.Init();
  417. m_vecPaintStart.Init();
  418. #endif
  419. m_iMisses = 0;
  420. m_flDecoyRadius = SNIPER_DECOY_RADIUS;
  421. m_fSnapShot = false;
  422. m_iBeamBrightness = 100;
  423. }
  424. //-----------------------------------------------------------------------------
  425. //-----------------------------------------------------------------------------
  426. bool CProtoSniper::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
  427. {
  428. Disposition_t disp = IRelationType(pEntity);
  429. if( disp != D_HT )
  430. {
  431. // Don't bother with anything I wouldn't shoot.
  432. return false;
  433. }
  434. if( !FInViewCone(pEntity) )
  435. {
  436. // Yes, this does call FInViewCone twice a frame for all entities checked for
  437. // visibility, but doing this allows us to cut out a bunch of traces that would
  438. // be done by VerifyShot for entities that aren't even in our viewcone.
  439. return false;
  440. }
  441. if( VerifyShot( pEntity ) )
  442. {
  443. return BaseClass::QuerySeeEntity(pEntity, bOnlyHateOrFearIfNPC);
  444. }
  445. return false;
  446. }
  447. //-----------------------------------------------------------------------------
  448. //-----------------------------------------------------------------------------
  449. bool CProtoSniper::FInViewCone ( CBaseEntity *pEntity )
  450. {
  451. if( pEntity->GetFlags() & FL_CLIENT )
  452. {
  453. CBasePlayer *pPlayer;
  454. pPlayer = ToBasePlayer( pEntity );
  455. if( m_spawnflags & SF_SNIPER_VIEWCONE )
  456. {
  457. // See how close this spot is to the laser.
  458. Vector vecEyes;
  459. Vector vecLOS;
  460. float flDist;
  461. Vector vecNearestPoint;
  462. vecEyes = EyePosition();
  463. vecLOS = m_vecPaintCursor - vecEyes;
  464. VectorNormalize(vecLOS);
  465. vecNearestPoint = PointOnLineNearestPoint( EyePosition(), EyePosition() + vecLOS * 8192, pPlayer->EyePosition() );
  466. flDist = ( pPlayer->EyePosition() - vecNearestPoint ).Length();
  467. if( showsniperdist.GetFloat() != 0 )
  468. {
  469. Msg( "Dist from beam: %f\n", flDist );
  470. }
  471. if( flDist <= sniperviewdist.GetFloat() )
  472. {
  473. return true;
  474. }
  475. return false;
  476. }
  477. }
  478. return BaseClass::FInViewCone( pEntity->EyePosition() );
  479. }
  480. //-----------------------------------------------------------------------------
  481. //-----------------------------------------------------------------------------
  482. void CProtoSniper::LaserOff( void )
  483. {
  484. if( m_pBeam )
  485. {
  486. UTIL_Remove( m_pBeam);
  487. m_pBeam = NULL;
  488. }
  489. SetNextThink( gpGlobals->curtime + 0.1f );
  490. }
  491. //-----------------------------------------------------------------------------
  492. //-----------------------------------------------------------------------------
  493. #define LASER_LEAD_DIST 64
  494. void CProtoSniper::LaserOn( const Vector &vecTarget, const Vector &vecDeviance )
  495. {
  496. if (!m_pBeam)
  497. {
  498. m_pBeam = CBeam::BeamCreate( "effects/bluelaser1.vmt", 1.0f );
  499. m_pBeam->SetColor( 0, 100, 255 );
  500. }
  501. else
  502. {
  503. // Beam seems to be on.
  504. //return;
  505. }
  506. // Don't aim right at the guy right now.
  507. Vector vecInitialAim;
  508. if( vecDeviance == vec3_origin )
  509. {
  510. // Start the aim where it last left off!
  511. vecInitialAim = m_vecPaintCursor;
  512. }
  513. else
  514. {
  515. vecInitialAim = vecTarget;
  516. }
  517. vecInitialAim.x += random->RandomFloat( -vecDeviance.x, vecDeviance.x );
  518. vecInitialAim.y += random->RandomFloat( -vecDeviance.y, vecDeviance.y );
  519. vecInitialAim.z += random->RandomFloat( -vecDeviance.z, vecDeviance.z );
  520. // The beam is backwards, sortof. The endpoint is the sniper. This is
  521. // so that the beam can be tapered to very thin where it emits from the sniper.
  522. m_pBeam->PointsInit( vecInitialAim, GetBulletOrigin() );
  523. m_pBeam->SetBrightness( 255 );
  524. m_pBeam->SetNoise( 0 );
  525. m_pBeam->SetWidth( 1.0f );
  526. m_pBeam->SetEndWidth( 0 );
  527. m_pBeam->SetScrollRate( 0 );
  528. m_pBeam->SetFadeLength( 0 );
  529. m_pBeam->SetHaloTexture( sHaloSprite );
  530. m_pBeam->SetHaloScale( 4.0f );
  531. m_vecPaintStart = vecInitialAim;
  532. // Think faster whilst painting. Higher resolution on the
  533. // beam movement.
  534. SetNextThink( gpGlobals->curtime + 0.02 );
  535. }
  536. //-----------------------------------------------------------------------------
  537. // Crikey!
  538. //-----------------------------------------------------------------------------
  539. float CProtoSniper::GetPositionParameter( float flTime, bool fLinear )
  540. {
  541. float flElapsedTime;
  542. float flTimeParameter;
  543. flElapsedTime = flTime - (GetWaitFinishTime() - gpGlobals->curtime);
  544. flTimeParameter = ( flElapsedTime / flTime );
  545. if( fLinear )
  546. {
  547. return flTimeParameter;
  548. }
  549. else
  550. {
  551. return (1 + sin( (M_PI * flTimeParameter) - (M_PI / 2) ) ) / 2;
  552. }
  553. }
  554. //-----------------------------------------------------------------------------
  555. //-----------------------------------------------------------------------------
  556. void CProtoSniper::GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress )
  557. {
  558. #if 0
  559. Vector vecDelta;
  560. vecDelta = vecGoal - vecStart;
  561. float flDist = VectorNormalize( vecDelta );
  562. vecDelta = vecStart + vecDelta * (flDist * flParameter);
  563. vecDelta = (vecDelta - GetBulletOrigin() ).Normalize();
  564. *pProgress = vecDelta;
  565. #else
  566. // Quaternions
  567. Vector vecIdealDir;
  568. QAngle vecIdealAngles;
  569. QAngle vecCurrentAngles;
  570. Vector vecCurrentDir;
  571. Vector vecBulletOrigin = GetBulletOrigin();
  572. // vecIdealDir is where the gun should be aimed when the painting
  573. // time is up. This can be approximate. This is only for drawing the
  574. // laser, not actually aiming the weapon. A large discrepancy will look
  575. // bad, though.
  576. vecIdealDir = vecGoal - vecBulletOrigin;
  577. VectorNormalize(vecIdealDir);
  578. // Now turn vecIdealDir into angles!
  579. VectorAngles( vecIdealDir, vecIdealAngles );
  580. // This is the vector of the beam's current aim.
  581. vecCurrentDir = vecStart - vecBulletOrigin;
  582. VectorNormalize(vecCurrentDir);
  583. // Turn this to angles, too.
  584. VectorAngles( vecCurrentDir, vecCurrentAngles );
  585. Quaternion idealQuat;
  586. Quaternion currentQuat;
  587. Quaternion aimQuat;
  588. AngleQuaternion( vecIdealAngles, idealQuat );
  589. AngleQuaternion( vecCurrentAngles, currentQuat );
  590. QuaternionSlerp( currentQuat, idealQuat, flParameter, aimQuat );
  591. QuaternionAngles( aimQuat, vecCurrentAngles );
  592. // Rebuild the current aim vector.
  593. AngleVectors( vecCurrentAngles, &vecCurrentDir );
  594. *pProgress = vecCurrentDir;
  595. #endif
  596. }
  597. //-----------------------------------------------------------------------------
  598. // Sweep the laser sight towards the point where the gun should be aimed
  599. //-----------------------------------------------------------------------------
  600. void CProtoSniper::PaintTarget( const Vector &vecTarget, float flPaintTime )
  601. {
  602. Vector vecCurrentDir;
  603. Vector vecStart;
  604. // vecStart is the barrel of the gun (or the laser sight)
  605. vecStart = GetBulletOrigin();
  606. float P;
  607. // keep painttime from hitting 0 exactly.
  608. flPaintTime = MAX( flPaintTime, 0.000001f );
  609. P = GetPositionParameter( flPaintTime, false );
  610. // Vital allies are sharper about avoiding the sniper.
  611. if( P > 0.25f && GetEnemy() && GetEnemy()->IsNPC() && HasCondition(COND_SEE_ENEMY) && !m_bWarnedTargetEntity )
  612. {
  613. m_bWarnedTargetEntity = true;
  614. if( GetEnemy()->Classify() == CLASS_PLAYER_ALLY_VITAL && GetEnemy()->MyNPCPointer()->FVisible(this) )
  615. {
  616. CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_REACT_TO_SOURCE, GetEnemy()->EarPosition(), 16, 1.0f, this );
  617. }
  618. }
  619. GetPaintAim( m_vecPaintStart, vecTarget, clamp(P,0.0f,1.0f), &vecCurrentDir );
  620. #if 1
  621. #define THRESHOLD 0.8f
  622. float flNoiseScale;
  623. if ( P >= THRESHOLD )
  624. {
  625. flNoiseScale = 1 - (1 / (1 - THRESHOLD)) * ( P - THRESHOLD );
  626. }
  627. else if ( P <= 1 - THRESHOLD )
  628. {
  629. flNoiseScale = P / (1 - THRESHOLD);
  630. }
  631. else
  632. {
  633. flNoiseScale = 1;
  634. }
  635. // mult by P
  636. vecCurrentDir.x += flNoiseScale * ( sin( 3 * M_PI * gpGlobals->curtime ) * 0.0006 );
  637. vecCurrentDir.y += flNoiseScale * ( sin( 2 * M_PI * gpGlobals->curtime + 0.5 * M_PI ) * 0.0006 );
  638. vecCurrentDir.z += flNoiseScale * ( sin( 1.5 * M_PI * gpGlobals->curtime + M_PI ) * 0.0006 );
  639. #endif
  640. trace_t tr;
  641. UTIL_TraceLine( vecStart, vecStart + vecCurrentDir * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  642. m_pBeam->SetStartPos( tr.endpos );
  643. m_pBeam->RelinkBeam();
  644. m_vecPaintCursor = tr.endpos;
  645. }
  646. //-----------------------------------------------------------------------------
  647. //-----------------------------------------------------------------------------
  648. bool CProtoSniper::IsPlayerAllySniper()
  649. {
  650. CBaseEntity *pPlayer = AI_GetSinglePlayer();
  651. return IRelationType( pPlayer ) == D_LI;
  652. }
  653. //-----------------------------------------------------------------------------
  654. //-----------------------------------------------------------------------------
  655. void CProtoSniper::InputSetDecoyRadius( inputdata_t &inputdata )
  656. {
  657. m_flDecoyRadius = (float)inputdata.value.Int();
  658. }
  659. //-----------------------------------------------------------------------------
  660. //-----------------------------------------------------------------------------
  661. void CProtoSniper::OnScheduleChange( void )
  662. {
  663. LaserOff();
  664. BaseClass::OnScheduleChange();
  665. }
  666. //-----------------------------------------------------------------------------
  667. //-----------------------------------------------------------------------------
  668. bool CProtoSniper::KeyValue( const char *szKeyName, const char *szValue )
  669. {
  670. if (FStrEq(szKeyName, "radius"))
  671. {
  672. m_flPatience = atof(szValue);
  673. // If the designer specifies a patience radius of 0, the
  674. // sniper won't have any patience at all. The sniper will
  675. // shoot at the first target it sees regardless of distance.
  676. if( m_flPatience == 0.0 )
  677. {
  678. m_fIsPatient = false;
  679. }
  680. else
  681. {
  682. m_fIsPatient = true;
  683. }
  684. return true;
  685. }
  686. else if( FStrEq(szKeyName, "misses") )
  687. {
  688. m_iMisses = atoi( szValue );
  689. return true;
  690. }
  691. else
  692. {
  693. return BaseClass::KeyValue( szKeyName, szValue );
  694. }
  695. }
  696. LINK_ENTITY_TO_CLASS( npc_sniper, CProtoSniper );
  697. LINK_ENTITY_TO_CLASS( proto_sniper, CProtoSniper );
  698. LINK_ENTITY_TO_CLASS( sniperbullet, CSniperBullet );
  699. //-----------------------------------------------------------------------------
  700. // Purpose:
  701. //
  702. //
  703. //-----------------------------------------------------------------------------
  704. void CProtoSniper::Precache( void )
  705. {
  706. PrecacheModel("models/combine_soldier.mdl");
  707. sHaloSprite = PrecacheModel("sprites/light_glow03.vmt");
  708. sFlashSprite = PrecacheModel( "sprites/muzzleflash1.vmt" );
  709. PrecacheModel("effects/bluelaser1.vmt");
  710. UTIL_PrecacheOther( "sniperbullet" );
  711. PrecacheScriptSound( "NPC_Sniper.Die" );
  712. PrecacheScriptSound( "NPC_Sniper.TargetDestroyed" );
  713. PrecacheScriptSound( "NPC_Sniper.HearDanger");
  714. PrecacheScriptSound( "NPC_Sniper.FireBullet" );
  715. PrecacheScriptSound( "NPC_Sniper.Reload" );
  716. PrecacheScriptSound( "NPC_Sniper.SonicBoom" );
  717. BaseClass::Precache();
  718. }
  719. //-----------------------------------------------------------------------------
  720. // Purpose:
  721. //
  722. //
  723. //-----------------------------------------------------------------------------
  724. void CProtoSniper::Spawn( void )
  725. {
  726. Precache();
  727. /// HACK:
  728. SetModel( "models/combine_soldier.mdl" );
  729. //m_hBullet = (CSniperBullet *)Create( "sniperbullet", GetBulletOrigin(), GetLocalAngles(), NULL );
  730. //Assert( m_hBullet != NULL );
  731. SetHullType( HULL_HUMAN );
  732. SetHullSizeNormal();
  733. UTIL_SetSize( this, Vector( -16, -16 , 0 ), Vector( 16, 16, 64 ) );
  734. SetSolid( SOLID_BBOX );
  735. AddSolidFlags( FSOLID_NOT_STANDABLE );
  736. SetMoveType( MOVETYPE_FLY );
  737. m_bloodColor = DONT_BLEED;
  738. m_iHealth = 10;
  739. m_flFieldOfView = 0.2;
  740. m_NPCState = NPC_STATE_NONE;
  741. if( HasSpawnFlags( SF_SNIPER_STARTDISABLED ) )
  742. {
  743. m_fEnabled = false;
  744. }
  745. else
  746. {
  747. m_fEnabled = true;
  748. }
  749. CapabilitiesClear();
  750. CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 );
  751. CapabilitiesAdd( bits_CAP_SIMPLE_RADIUS_DAMAGE );
  752. m_HackedGunPos = Vector ( 0, 0, 0 );
  753. m_spawnflags |= SF_NPC_LONG_RANGE;
  754. m_spawnflags |= SF_NPC_ALWAYSTHINK;
  755. m_pBeam = NULL;
  756. m_bSweepHighestPriority = false;
  757. ClearOldDecoys();
  758. NPCInit();
  759. if( m_spawnflags & SF_SNIPER_HIDDEN )
  760. {
  761. AddEffects( EF_NODRAW );
  762. AddSolidFlags( FSOLID_NOT_SOLID );
  763. }
  764. // Point the cursor straight ahead so that the sniper's
  765. // first sweep of the laser doesn't look weird.
  766. Vector vecForward;
  767. AngleVectors( GetLocalAngles(), &vecForward );
  768. m_vecPaintCursor = GetBulletOrigin() + vecForward * 1024;
  769. m_fWeaponLoaded = true;
  770. //m_debugOverlays |= OVERLAY_TEXT_BIT;
  771. // none!
  772. GetEnemies()->SetFreeKnowledgeDuration( 0.0 );
  773. m_flTimeLastAttackedPlayer = 0.0f;
  774. m_bWarnedTargetEntity = false;
  775. m_bKilledPlayer = false;
  776. }
  777. //-----------------------------------------------------------------------------
  778. //-----------------------------------------------------------------------------
  779. void CProtoSniper::SetSweepTarget( const char *pszTarget )
  780. {
  781. CBaseEntity *pTarget;
  782. // In case the sniper was sweeping a random set of targets when asked to sweep a normal chain.
  783. ClearTargetGroup();
  784. pTarget = gEntList.FindEntityByName( NULL, pszTarget );
  785. if( !pTarget )
  786. {
  787. DevMsg( "**Sniper %s cannot find sweep target %s\n", GetClassname(), pszTarget );
  788. m_hSweepTarget = NULL;
  789. return;
  790. }
  791. m_hSweepTarget = pTarget;
  792. }
  793. //-----------------------------------------------------------------------------
  794. // Purpose: Forces an idle sniper to paint the specified target.
  795. //-----------------------------------------------------------------------------
  796. void CProtoSniper::InputSweepTarget( inputdata_t &inputdata )
  797. {
  798. SetSweepTarget( inputdata.value.String() );
  799. }
  800. //-----------------------------------------------------------------------------
  801. //-----------------------------------------------------------------------------
  802. void CProtoSniper::InputSweepTargetHighestPriority( inputdata_t &inputdata )
  803. {
  804. SetSweepTarget( inputdata.value.String() );
  805. m_bSweepHighestPriority = true;
  806. if( GetCurSchedule() && stricmp( GetCurSchedule()->GetName(), "SCHED_PSNIPER_RELOAD" ) )
  807. {
  808. // If you're doing anything except reloading, stop and do this.
  809. ClearSchedule( "Told to sweep target via input" );
  810. }
  811. }
  812. //-----------------------------------------------------------------------------
  813. //-----------------------------------------------------------------------------
  814. void CProtoSniper::ClearTargetGroup( void )
  815. {
  816. int i;
  817. for( i = 0 ; i < SNIPER_MAX_GROUP_TARGETS ; i++ )
  818. {
  819. m_pGroupTarget[ i ] = NULL;
  820. }
  821. m_iNumGroupTargets = 0;
  822. }
  823. //-----------------------------------------------------------------------------
  824. // Purpose: Similar to SweepTarget, but forces the sniper to sweep targets
  825. // in a group (bound by groupname) randomly until interrupted.
  826. //-----------------------------------------------------------------------------
  827. void CProtoSniper::InputSweepGroupRandomly( inputdata_t &inputdata )
  828. {
  829. ClearTargetGroup();
  830. CBaseEntity *pEnt;
  831. // PERFORMANCE
  832. // Go through the whole ent list? This could hurt. (sjb)
  833. // Gary: Yes, this sucks. :)
  834. pEnt = gEntList.FirstEnt();
  835. do
  836. {
  837. CSniperTarget *pTarget;
  838. pTarget = dynamic_cast<CSniperTarget*>(pEnt);
  839. // If the pointer is null, this isn't a sniper target.
  840. if( pTarget )
  841. {
  842. if( !strcmp( inputdata.value.String(), STRING( pTarget->m_iszGroupName ) ) )
  843. {
  844. m_pGroupTarget[ m_iNumGroupTargets ] = pTarget;
  845. m_iNumGroupTargets++;
  846. }
  847. }
  848. pEnt = gEntList.NextEnt( pEnt );
  849. } while( pEnt );
  850. m_hSweepTarget = m_pGroupTarget[ random->RandomInt( 0, m_iNumGroupTargets - 1 ) ];
  851. }
  852. //-----------------------------------------------------------------------------
  853. //-----------------------------------------------------------------------------
  854. void CProtoSniper::InputStopSweeping( inputdata_t &inputdata )
  855. {
  856. m_hSweepTarget = NULL;
  857. ClearSchedule( "Told to stop sweeping via input" );
  858. }
  859. //-----------------------------------------------------------------------------
  860. // Purpose:
  861. // Input : &inputdata -
  862. //-----------------------------------------------------------------------------
  863. void CProtoSniper::InputProtectTarget( inputdata_t &inputdata )
  864. {
  865. m_hProtectTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller );
  866. if ( !m_hProtectTarget )
  867. {
  868. DevMsg( "Sniper %s cannot find protect target %s\n", GetClassname(), inputdata.value.String() );
  869. return;
  870. }
  871. m_flDangerEnemyDistance = 0;
  872. }
  873. #if HL2_EPISODIC
  874. //-----------------------------------------------------------------------------
  875. //-----------------------------------------------------------------------------
  876. void CProtoSniper::InputSetPaintInterval( inputdata_t &inputdata )
  877. {
  878. m_flKeyfieldPaintTime = inputdata.value.Float();
  879. }
  880. //-----------------------------------------------------------------------------
  881. //-----------------------------------------------------------------------------
  882. void CProtoSniper::InputSetPaintIntervalVariance( inputdata_t &inputdata )
  883. {
  884. m_flKeyfieldPaintTimeNoise = inputdata.value.Float();
  885. }
  886. #endif
  887. //-----------------------------------------------------------------------------
  888. // Purpose:
  889. // Input : *pTarget -
  890. // Output : int
  891. //-----------------------------------------------------------------------------
  892. int CProtoSniper::IRelationPriority( CBaseEntity *pTarget )
  893. {
  894. int priority = BaseClass::IRelationPriority( pTarget );
  895. // If we have a target to protect, increase priority on targets closer to it
  896. if ( m_hProtectTarget )
  897. {
  898. float flDistance = (pTarget->GetAbsOrigin() - m_hProtectTarget->GetAbsOrigin()).LengthSqr();
  899. if ( flDistance <= SNIPER_PROTECTION_MINDIST )
  900. {
  901. float flBonus = (1.0 - (flDistance / SNIPER_PROTECTION_MINDIST)) * SNIPER_PROTECTION_PRIORITYCAP;
  902. priority += flBonus;
  903. if ( m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
  904. {
  905. NDebugOverlay::Text( pTarget->GetAbsOrigin() + Vector(0,0,16), UTIL_VarArgs("P: %d (b %f)!", priority, flBonus), false, 0.1 );
  906. }
  907. }
  908. }
  909. return priority;
  910. }
  911. //-----------------------------------------------------------------------------
  912. // Purpose:
  913. //
  914. //
  915. // Output :
  916. //-----------------------------------------------------------------------------
  917. Class_T CProtoSniper::Classify( void )
  918. {
  919. if( m_fEnabled )
  920. {
  921. return CLASS_PROTOSNIPER;
  922. }
  923. else
  924. {
  925. return CLASS_NONE;
  926. }
  927. }
  928. //-----------------------------------------------------------------------------
  929. //-----------------------------------------------------------------------------
  930. Vector CProtoSniper::GetBulletOrigin( void )
  931. {
  932. if( m_spawnflags & SF_SNIPER_HIDDEN )
  933. {
  934. return GetAbsOrigin();
  935. }
  936. else
  937. {
  938. Vector vecForward;
  939. AngleVectors( GetLocalAngles(), &vecForward );
  940. return WorldSpaceCenter() + vecForward * 20;
  941. }
  942. }
  943. //-----------------------------------------------------------------------------
  944. //-----------------------------------------------------------------------------
  945. void CProtoSniper::ClearOldDecoys( void )
  946. {
  947. #if 0
  948. int i;
  949. for( i = 0 ; i < NUM_OLDDECOYS ; i++ )
  950. {
  951. m_pOldDecoys[ i ] = NULL;
  952. }
  953. m_iOldDecoySlot = 0;
  954. #endif
  955. }
  956. //-----------------------------------------------------------------------------
  957. //-----------------------------------------------------------------------------
  958. bool CProtoSniper::HasOldDecoy( CBaseEntity *pDecoy )
  959. {
  960. #if 0
  961. int i;
  962. for( i = 0 ; i < NUM_OLDDECOYS ; i++ )
  963. {
  964. if( m_pOldDecoys[ i ] == pDecoy )
  965. {
  966. return true;
  967. }
  968. }
  969. #endif
  970. return false;
  971. }
  972. //-----------------------------------------------------------------------------
  973. // The list of old decoys is just a circular list. We put decoys that we've
  974. // already fired at in this list. When they've been pushed off the list by others,
  975. // then they are valid targets again.
  976. //-----------------------------------------------------------------------------
  977. void CProtoSniper::AddOldDecoy( CBaseEntity *pDecoy )
  978. {
  979. #if 0
  980. m_pOldDecoys[ m_iOldDecoySlot ] = pDecoy;
  981. m_iOldDecoySlot++;
  982. if( m_iOldDecoySlot == NUM_OLDDECOYS )
  983. {
  984. m_iOldDecoySlot = 0;
  985. }
  986. #endif
  987. }
  988. //-----------------------------------------------------------------------------
  989. // Purpose: Only blast damage can hurt a sniper.
  990. //
  991. //
  992. // Output :
  993. //-----------------------------------------------------------------------------
  994. #define SNIPER_MAX_INFLICTOR_DIST 15.0f * 12.0f // 15 feet.
  995. int CProtoSniper::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  996. {
  997. if( !m_fEnabled )
  998. {
  999. // As good as not existing.
  1000. return 0;
  1001. }
  1002. if( !info.GetInflictor() )
  1003. return 0;
  1004. CTakeDamageInfo newInfo = info;
  1005. // Allow SetHealth() & npc_kill inputs to hurt the sniper
  1006. if ( info.GetDamageType() == DMG_GENERIC && info.GetInflictor() == this )
  1007. return CAI_BaseNPC::OnTakeDamage_Alive( newInfo );
  1008. if( !(info.GetDamageType() & (DMG_BLAST|DMG_BURN) ) )
  1009. {
  1010. // Only blasts and burning hurt
  1011. return 0;
  1012. }
  1013. if( (info.GetDamageType() & DMG_BLAST) && info.GetDamage() < m_iHealth )
  1014. {
  1015. // Only blasts powerful enough to kill hurt
  1016. return 0;
  1017. }
  1018. float flDist = GetAbsOrigin().DistTo( info.GetInflictor()->GetAbsOrigin() );
  1019. if( flDist > SNIPER_MAX_INFLICTOR_DIST )
  1020. {
  1021. // Sniper only takes damage from explosives that are nearby. This makes a sniper
  1022. // susceptible to a grenade that lands in his nest, but not to a large explosion
  1023. // that goes off elsewhere and just happens to be able to trace into the sniper's
  1024. // nest.
  1025. return 0;
  1026. }
  1027. if( info.GetDamageType() & DMG_BURN )
  1028. {
  1029. newInfo.SetDamage( m_iHealth );
  1030. }
  1031. return CAI_BaseNPC::OnTakeDamage_Alive( newInfo );
  1032. }
  1033. //-----------------------------------------------------------------------------
  1034. // Purpose: When a sniper is killed, we launch a fake ragdoll corpse as if the
  1035. // sniper was blasted out of his nest.
  1036. //
  1037. //
  1038. // Output :
  1039. //-----------------------------------------------------------------------------
  1040. void CProtoSniper::Event_Killed( const CTakeDamageInfo &info )
  1041. {
  1042. if( !(m_spawnflags & SF_SNIPER_NOCORPSE) )
  1043. {
  1044. Vector vecForward;
  1045. float flForce = random->RandomFloat( 500, 700 ) * 10;
  1046. AngleVectors( GetLocalAngles(), &vecForward );
  1047. float flFadeTime = 0.0;
  1048. if( HasSpawnFlags( SF_NPC_FADE_CORPSE ) )
  1049. {
  1050. flFadeTime = 5.0;
  1051. }
  1052. CBaseEntity *pGib;
  1053. bool bShouldIgnite = IsOnFire() || hl2_episodic.GetBool();
  1054. pGib = CreateRagGib( "models/combine_soldier.mdl", GetLocalOrigin(), GetLocalAngles(), (vecForward * flForce) + Vector(0, 0, 600), flFadeTime, bShouldIgnite );
  1055. }
  1056. m_OnDeath.FireOutput( info.GetAttacker(), this );
  1057. // Tell my killer that he got me!
  1058. if( info.GetAttacker() )
  1059. {
  1060. info.GetAttacker()->Event_KilledOther(this, info);
  1061. g_EventQueue.AddEvent( info.GetAttacker(), "KilledNPC", 0.3, this, this );
  1062. }
  1063. LaserOff();
  1064. EmitSound( "NPC_Sniper.Die" );
  1065. UTIL_Remove( this );
  1066. }
  1067. //---------------------------------------------------------
  1068. //---------------------------------------------------------
  1069. void CProtoSniper::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info )
  1070. {
  1071. if( pVictim && pVictim->IsPlayer() )
  1072. {
  1073. m_bKilledPlayer = true;
  1074. }
  1075. }
  1076. //---------------------------------------------------------
  1077. //---------------------------------------------------------
  1078. void CProtoSniper::UpdateOnRemove( void )
  1079. {
  1080. LaserOff();
  1081. BaseClass::UpdateOnRemove();
  1082. }
  1083. //---------------------------------------------------------
  1084. //---------------------------------------------------------
  1085. int CProtoSniper::SelectSchedule ( void )
  1086. {
  1087. if( HasCondition(COND_ENEMY_DEAD) && sniperspeak.GetBool() )
  1088. {
  1089. EmitSound( "NPC_Sniper.TargetDestroyed" );
  1090. }
  1091. if( !m_fWeaponLoaded )
  1092. {
  1093. // Reload is absolute priority.
  1094. return SCHED_RELOAD;
  1095. }
  1096. if( !AI_GetSinglePlayer()->IsAlive() && m_bKilledPlayer )
  1097. {
  1098. if( HasCondition(COND_IN_PVS) )
  1099. {
  1100. return SCHED_PSNIPER_PLAYER_DEAD;
  1101. }
  1102. }
  1103. if( HasCondition( COND_HEAR_DANGER ) )
  1104. {
  1105. // Next priority is to be suppressed!
  1106. ScopeGlint();
  1107. CSound *pSound = GetBestSound();
  1108. if( pSound && pSound->IsSoundType( SOUND_DANGER ) && BaseClass::FVisible( pSound->GetSoundReactOrigin() ) )
  1109. {
  1110. // The sniper will scream if the sound of a grenade about to detonate is heard.
  1111. // If this COND_HEAR_DANGER is due to the sound really being SOUND_DANGER_SNIPERONLY,
  1112. // the sniper keeps quiet, because the player's grenade might miss the mark.
  1113. // Make sure the sound is visible, otherwise the sniper will scream at a grenade that
  1114. // probably won't harm him.
  1115. // Also, don't play the sound effect if we're an ally.
  1116. if ( IsPlayerAllySniper() == false )
  1117. {
  1118. EmitSound( "NPC_Sniper.HearDanger" );
  1119. }
  1120. }
  1121. return SCHED_PSNIPER_SUPPRESSED;
  1122. }
  1123. // OK. If you fall through all the cases above, but you're DISABLED,
  1124. // play the schedule that waits a little while and tries again.
  1125. if( !m_fEnabled )
  1126. {
  1127. return SCHED_PSNIPER_DISABLEDWAIT;
  1128. }
  1129. if( HasCondition( COND_SNIPER_SWEEP_TARGET ) )
  1130. {
  1131. // Sweep a target. Scripted by level designers!
  1132. if( ( m_hSweepTarget && m_hSweepTarget->HasSpawnFlags( SF_SNIPERTARGET_NOINTERRUPT ) ) || m_bSweepHighestPriority )
  1133. {
  1134. return SCHED_PSNIPER_SWEEP_TARGET_NOINTERRUPT;
  1135. }
  1136. else
  1137. {
  1138. return SCHED_PSNIPER_SWEEP_TARGET;
  1139. }
  1140. }
  1141. if( GetEnemy() == NULL || HasCondition( COND_ENEMY_DEAD ) )
  1142. {
  1143. // Look for an enemy.
  1144. SetEnemy( NULL );
  1145. return SCHED_PSNIPER_SCAN;
  1146. }
  1147. if( HasCondition( COND_SNIPER_FRUSTRATED ) )
  1148. {
  1149. return SCHED_PSNIPER_FRUSTRATED_ATTACK;
  1150. }
  1151. if( HasCondition( COND_SNIPER_CANATTACKDECOY ) )
  1152. {
  1153. return SCHED_RANGE_ATTACK2;
  1154. }
  1155. if( HasCondition( COND_SNIPER_NO_SHOT ) )
  1156. {
  1157. return SCHED_PSNIPER_NO_CLEAR_SHOT;
  1158. }
  1159. if( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  1160. {
  1161. // shoot!
  1162. return SCHED_RANGE_ATTACK1;
  1163. }
  1164. else
  1165. {
  1166. // Camp on this target
  1167. return SCHED_PSNIPER_CAMP;
  1168. }
  1169. }
  1170. //---------------------------------------------------------
  1171. //---------------------------------------------------------
  1172. int CProtoSniper::GetSoundInterests( void )
  1173. {
  1174. // Suppress when you hear danger sound
  1175. if( m_fEnabled )
  1176. {
  1177. return SOUND_DANGER | SOUND_DANGER_SNIPERONLY;
  1178. }
  1179. return SOUND_NONE;
  1180. }
  1181. //---------------------------------------------------------
  1182. //---------------------------------------------------------
  1183. void CProtoSniper::OnListened()
  1184. {
  1185. BaseClass::OnListened();
  1186. AISoundIter_t iter;
  1187. Vector forward;
  1188. GetVectors( &forward, NULL, NULL );
  1189. CSound *pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
  1190. while ( pCurrentSound )
  1191. {
  1192. // the npc cares about this sound, and it's close enough to hear.
  1193. if ( pCurrentSound->FIsSound() )
  1194. {
  1195. // this is an audible sound.
  1196. if( pCurrentSound->SoundTypeNoContext() == SOUND_DANGER_SNIPERONLY )
  1197. {
  1198. SetCondition( COND_HEAR_DANGER );
  1199. }
  1200. #if 0
  1201. if( pCurrentSound->IsSoundType( SOUND_BULLET_IMPACT ) )
  1202. {
  1203. // Clip this bullet to the shield.
  1204. if( pCurrentSound->m_hOwner )
  1205. {
  1206. Ray_t ray;
  1207. cplane_t plane;
  1208. ray.Init( pCurrentSound->m_hOwner->EyePosition(), pCurrentSound->GetSoundOrigin(), Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) );
  1209. plane.normal = forward;
  1210. plane.type = PLANE_ANYX;
  1211. plane.dist = DotProduct( plane.normal, WorldSpaceCenter() + forward * m_flShieldDist );
  1212. plane.signbits = SignbitsForPlane(&plane);
  1213. float fraction = IntersectRayWithPlane( ray, plane );
  1214. Vector vecImpactPoint = ray.m_Start + ray.m_Delta * fraction;
  1215. float flDist = (vecImpactPoint - (WorldSpaceCenter() + forward * m_flShieldDist)).LengthSqr();
  1216. if( flDist <= (m_flShieldRadius * m_flShieldRadius) )
  1217. {
  1218. CEffectData data;
  1219. data.m_vOrigin = vecImpactPoint;
  1220. data.m_vNormal = vec3_origin;
  1221. data.m_vAngles = vec3_angle;
  1222. data.m_nColor = COMMAND_POINT_YELLOW;
  1223. DispatchEffect( "CommandPointer", data );
  1224. }
  1225. }
  1226. }
  1227. #endif
  1228. }
  1229. pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
  1230. }
  1231. }
  1232. //---------------------------------------------------------
  1233. //---------------------------------------------------------
  1234. bool CProtoSniper::FCanCheckAttacks ( void )
  1235. {
  1236. return true;
  1237. }
  1238. //---------------------------------------------------------
  1239. //---------------------------------------------------------
  1240. bool CProtoSniper::FindDecoyObject( void )
  1241. {
  1242. #define SEARCH_DEPTH 50
  1243. CBaseEntity *pDecoys[ SNIPER_NUM_DECOYS ];
  1244. CBaseEntity *pList[ SEARCH_DEPTH ];
  1245. CBaseEntity *pCurrent;
  1246. int count;
  1247. int i;
  1248. Vector vecTarget = GetEnemy()->WorldSpaceCenter();
  1249. Vector vecDelta;
  1250. m_hDecoyObject = NULL;
  1251. for( i = 0 ; i < SNIPER_NUM_DECOYS ; i++ )
  1252. {
  1253. pDecoys[ i ] = NULL;
  1254. }
  1255. vecDelta.x = m_flDecoyRadius;
  1256. vecDelta.y = m_flDecoyRadius;
  1257. vecDelta.z = m_flDecoyRadius;
  1258. count = UTIL_EntitiesInBox( pList, SEARCH_DEPTH, vecTarget - vecDelta, vecTarget + vecDelta, 0 );
  1259. // Now we have the list of entities near the target.
  1260. // Dig through that list and build the list of decoys.
  1261. int iIterator = 0;
  1262. for( i = 0 ; i < count ; i++ )
  1263. {
  1264. pCurrent = pList[ i ];
  1265. if( FClassnameIs( pCurrent, "func_breakable" ) || FClassnameIs( pCurrent, "prop_physics" ) || FClassnameIs( pCurrent, "func_physbox" ) )
  1266. {
  1267. if( !pCurrent->VPhysicsGetObject() )
  1268. continue;
  1269. if( pCurrent->VPhysicsGetObject()->GetMass() > SNIPER_DECOY_MAX_MASS )
  1270. {
  1271. // Skip this very heavy object. Probably a car or dumpster.
  1272. continue;
  1273. }
  1274. if( pCurrent->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  1275. {
  1276. // Ah! If the player is holding something, try to shoot it!
  1277. if( FVisible( pCurrent ) )
  1278. {
  1279. m_hDecoyObject = pCurrent;
  1280. m_vecDecoyObjectTarget = pCurrent->WorldSpaceCenter();
  1281. return true;
  1282. }
  1283. }
  1284. // This item meets criteria for a decoy object to shoot at.
  1285. // But have we shot at this item recently? If we HAVE, don't add it.
  1286. #if 0
  1287. if( !HasOldDecoy( pCurrent ) )
  1288. #endif
  1289. {
  1290. pDecoys[ iIterator ] = pCurrent;
  1291. if( iIterator == SNIPER_NUM_DECOYS - 1 )
  1292. {
  1293. break;
  1294. }
  1295. else
  1296. {
  1297. iIterator++;
  1298. }
  1299. }
  1300. }
  1301. }
  1302. if( iIterator == 0 )
  1303. {
  1304. return false;
  1305. }
  1306. // try 4 times to pick a random object from the list
  1307. // and trace to it. If the trace goes off, that's the object!
  1308. for( i = 0 ; i < 4 ; i++ )
  1309. {
  1310. CBaseEntity *pProspect;
  1311. trace_t tr;
  1312. // Pick one of the decoys at random.
  1313. pProspect = pDecoys[ random->RandomInt( 0, iIterator - 1 ) ];
  1314. Vector vecDecoyTarget;
  1315. Vector vecDirToDecoy;
  1316. Vector vecBulletOrigin;
  1317. vecBulletOrigin = GetBulletOrigin();
  1318. pProspect->CollisionProp()->RandomPointInBounds( Vector( .1, .1, .1 ), Vector( .6, .6, .6 ), &vecDecoyTarget );
  1319. // When trying to trace to an object using its absmin + some fraction of its size, it's best
  1320. // to lengthen the trace a little beyond the object's bounding box in case it's a more complex
  1321. // object, or not axially aligned.
  1322. vecDirToDecoy = vecDecoyTarget - vecBulletOrigin;
  1323. VectorNormalize(vecDirToDecoy);
  1324. // Right now, tracing with MASK_BLOCKLOS and checking the fraction as well as the object the trace
  1325. // has hit makes it possible for the decoy behavior to shoot through glass.
  1326. UTIL_TraceLine( vecBulletOrigin, vecDecoyTarget + vecDirToDecoy * 32,
  1327. MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr);
  1328. if( tr.m_pEnt == pProspect || tr.fraction == 1.0 )
  1329. {
  1330. // Great! A shot will hit this object.
  1331. m_hDecoyObject = pProspect;
  1332. m_vecDecoyObjectTarget = tr.endpos;
  1333. // Throw some noise in, don't always hit the center.
  1334. Vector vecNoise;
  1335. pProspect->CollisionProp()->RandomPointInBounds( Vector( 0.25, 0.25, 0.25 ), Vector( 0.75, 0.75, 0.75 ), &vecNoise );
  1336. m_vecDecoyObjectTarget += vecNoise - pProspect->GetAbsOrigin();
  1337. return true;
  1338. }
  1339. }
  1340. return false;
  1341. }
  1342. //---------------------------------------------------------
  1343. //---------------------------------------------------------
  1344. #define SNIPER_SNAP_SHOT_VELOCITY 125
  1345. bool CProtoSniper::ShouldSnapShot( void )
  1346. {
  1347. if( GetEnemy()->IsPlayer() )
  1348. {
  1349. if( GetEnemy()->GetSmoothedVelocity().Length() >= SNIPER_SNAP_SHOT_VELOCITY )
  1350. {
  1351. return true;
  1352. }
  1353. else
  1354. {
  1355. return false;
  1356. }
  1357. }
  1358. // Right now, always snapshot at NPC's
  1359. return true;
  1360. }
  1361. //---------------------------------------------------------
  1362. //---------------------------------------------------------
  1363. bool CProtoSniper::VerifyShot( CBaseEntity *pTarget )
  1364. {
  1365. trace_t tr;
  1366. Vector vecTarget = DesiredBodyTarget( pTarget );
  1367. UTIL_TraceLine( GetBulletOrigin(), vecTarget, MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
  1368. if( tr.fraction != 1.0 )
  1369. {
  1370. if( pTarget->IsPlayer() )
  1371. {
  1372. // if the target is the player, do another trace to see if we can shoot his eyeposition. This should help
  1373. // improve sniper responsiveness in cases where the player is hiding his chest from the sniper with his
  1374. // head in full view.
  1375. UTIL_TraceLine( GetBulletOrigin(), pTarget->EyePosition(), MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
  1376. if( tr.fraction == 1.0 )
  1377. {
  1378. return true;
  1379. }
  1380. }
  1381. // Trace hit something.
  1382. if( tr.m_pEnt )
  1383. {
  1384. if( tr.m_pEnt->m_takedamage == DAMAGE_YES )
  1385. {
  1386. // Just shoot it if I can hurt it. Probably a breakable or glass pane.
  1387. return true;
  1388. }
  1389. }
  1390. return false;
  1391. }
  1392. else
  1393. {
  1394. return true;
  1395. }
  1396. }
  1397. //---------------------------------------------------------
  1398. //---------------------------------------------------------
  1399. int CProtoSniper::RangeAttack1Conditions ( float flDot, float flDist )
  1400. {
  1401. float fFrustration;
  1402. fFrustration = gpGlobals->curtime - m_flFrustration;
  1403. //Msg( "Frustration: %f\n", fFrustration );
  1404. if( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_ENEMY_OCCLUDED ) )
  1405. {
  1406. if( VerifyShot( GetEnemy() ) )
  1407. {
  1408. // Can see the enemy, have a clear shot to his midsection
  1409. ClearCondition( COND_SNIPER_NO_SHOT );
  1410. }
  1411. else
  1412. {
  1413. // Can see the enemy, but can't take a shot at his midsection
  1414. SetCondition( COND_SNIPER_NO_SHOT );
  1415. return COND_NONE;
  1416. }
  1417. if( m_fIsPatient )
  1418. {
  1419. // This sniper has a clear shot at the target, but can not take
  1420. // the shot if he is being patient and the target is outside
  1421. // of the patience radius.
  1422. float flDist;
  1423. flDist = ( GetLocalOrigin() - GetEnemy()->GetLocalOrigin() ).Length2D();
  1424. if( flDist <= m_flPatience )
  1425. {
  1426. // This target is close enough to attack!
  1427. return COND_CAN_RANGE_ATTACK1;
  1428. }
  1429. else
  1430. {
  1431. // Be patient...
  1432. return COND_NONE;
  1433. }
  1434. }
  1435. else
  1436. {
  1437. // Not being patient. Clear for attack.
  1438. return COND_CAN_RANGE_ATTACK1;
  1439. }
  1440. }
  1441. if( fFrustration >= 2 && !m_fIsPatient )
  1442. {
  1443. if( !(m_spawnflags & SF_SNIPER_NOSWEEP) && !m_hDecoyObject && FindDecoyObject() )
  1444. {
  1445. // If I don't have a decoy, try to find one and shoot it.
  1446. return COND_SNIPER_CANATTACKDECOY;
  1447. }
  1448. if( fFrustration >= 2.5 )
  1449. {
  1450. // Otherwise, just fire somewhere near the hiding enemy.
  1451. return COND_SNIPER_FRUSTRATED;
  1452. }
  1453. }
  1454. return COND_NONE;
  1455. }
  1456. //---------------------------------------------------------
  1457. //---------------------------------------------------------
  1458. int CProtoSniper::TranslateSchedule( int scheduleType )
  1459. {
  1460. switch( scheduleType )
  1461. {
  1462. case SCHED_RANGE_ATTACK1:
  1463. if( m_hSweepTarget != NULL && m_fSnapShot && ShouldSnapShot() )
  1464. {
  1465. return SCHED_PSNIPER_SNAPATTACK;
  1466. }
  1467. return SCHED_PSNIPER_ATTACK;
  1468. break;
  1469. case SCHED_RANGE_ATTACK2:
  1470. return SCHED_PSNIPER_ATTACKDECOY;
  1471. break;
  1472. case SCHED_RELOAD:
  1473. return SCHED_PSNIPER_RELOAD;
  1474. break;
  1475. }
  1476. return BaseClass::TranslateSchedule( scheduleType );
  1477. }
  1478. //---------------------------------------------------------
  1479. //---------------------------------------------------------
  1480. void CProtoSniper::ScopeGlint()
  1481. {
  1482. CEffectData data;
  1483. data.m_vOrigin = GetAbsOrigin();
  1484. data.m_vNormal = vec3_origin;
  1485. data.m_vAngles = vec3_angle;
  1486. data.m_nColor = COMMAND_POINT_BLUE;
  1487. DispatchEffect( "CommandPointer", data );
  1488. }
  1489. //---------------------------------------------------------
  1490. // This starts the bullet state machine. The actual effects
  1491. // of the bullet will happen later. This function schedules
  1492. // those effects.
  1493. //
  1494. // fDirectShot indicates whether the bullet is a "direct shot"
  1495. // that is - fired with the intent that it will strike the
  1496. // enemy. Otherwise, the bullet is intended to strike a
  1497. // decoy object or nothing at all in particular.
  1498. //---------------------------------------------------------
  1499. bool CProtoSniper::FireBullet( const Vector &vecTarget, bool bDirectShot )
  1500. {
  1501. CSniperBullet *pBullet;
  1502. Vector vecBulletOrigin;
  1503. vecBulletOrigin = GetBulletOrigin();
  1504. pBullet = (CSniperBullet *)Create( "sniperbullet", GetBulletOrigin(), GetLocalAngles(), NULL );
  1505. Assert( pBullet != NULL );
  1506. if( !pBullet->Start( vecBulletOrigin, vecTarget, this, bDirectShot ) )
  1507. {
  1508. // Bullet must still be active.
  1509. return false;
  1510. }
  1511. pBullet->SetOwnerEntity( this );
  1512. CPASAttenuationFilter filternoatten( this, ATTN_NONE );
  1513. EmitSound( filternoatten, entindex(), "NPC_Sniper.FireBullet" );
  1514. CPVSFilter filter( vecBulletOrigin );
  1515. te->Sprite( filter, 0.0, &vecBulletOrigin, sFlashSprite, 0.3, 255 );
  1516. // force a reload when we're done
  1517. m_fWeaponLoaded = false;
  1518. // Once the sniper takes a shot, turn the patience off!
  1519. m_fIsPatient = false;
  1520. // Alleviate frustration, too!
  1521. m_flFrustration = gpGlobals->curtime;
  1522. // This may have been a snap shot.
  1523. // Don't allow subsequent snap shots.
  1524. m_fSnapShot = false;
  1525. // Return to normal priority
  1526. m_bSweepHighestPriority = false;
  1527. // Sniper had to be aiming here to fire here.
  1528. // Make it the cursor.
  1529. m_vecPaintCursor = vecTarget;
  1530. m_hDecoyObject.Set( NULL );
  1531. m_OnShotFired.FireOutput( GetEnemy(), this );
  1532. return true;
  1533. }
  1534. //---------------------------------------------------------
  1535. //---------------------------------------------------------
  1536. float CProtoSniper::GetBulletSpeed()
  1537. {
  1538. float speed = bulletSpeed.GetFloat();
  1539. if( IsFastSniper() )
  1540. {
  1541. speed *= 2.5f;
  1542. }
  1543. return speed;
  1544. }
  1545. //---------------------------------------------------------
  1546. //---------------------------------------------------------
  1547. void CProtoSniper::StartTask( const Task_t *pTask )
  1548. {
  1549. switch( pTask->iTask )
  1550. {
  1551. case TASK_SNIPER_PLAYER_DEAD:
  1552. {
  1553. m_hSweepTarget = AI_GetSinglePlayer();
  1554. SetWait( 4.0f );
  1555. LaserOn( m_hSweepTarget->GetAbsOrigin(), vec3_origin );
  1556. }
  1557. break;
  1558. case TASK_SNIPER_ATTACK_CURSOR:
  1559. break;
  1560. case TASK_RANGE_ATTACK1:
  1561. // Start task does nothing here.
  1562. // We fall through to RunTask() which will keep trying to take
  1563. // the shot until the weapon is ready to fire. In some rare cases,
  1564. // the weapon may be ready to fire before the single bullet allocated
  1565. // to the sniper has hit its target.
  1566. break;
  1567. case TASK_RANGE_ATTACK2:
  1568. // Don't call up to base class, it will try to set the activity.
  1569. break;
  1570. case TASK_SNIPER_PAINT_SWEEP_TARGET:
  1571. if ( !m_hSweepTarget.Get() )
  1572. {
  1573. TaskFail( FAIL_NO_TARGET );
  1574. return;
  1575. }
  1576. SetWait( m_hSweepTarget->m_flSpeed );
  1577. // Snap directly to this target if this spawnflag is set.
  1578. // Otherwise, sweep from wherever the cursor was.
  1579. if( m_hSweepTarget->HasSpawnFlags( SF_SNIPERTARGET_SNAPTO ) )
  1580. {
  1581. m_vecPaintCursor = m_hSweepTarget->GetLocalOrigin();
  1582. }
  1583. LaserOn( m_hSweepTarget->GetLocalOrigin(), vec3_origin );
  1584. break;
  1585. case TASK_SNIPER_PAINT_ENEMY:
  1586. // Everytime we start to paint an enemy, this is reset to false.
  1587. m_bWarnedTargetEntity = false;
  1588. // If the sniper has a sweep target, clear it, unless it's flagged to resume
  1589. if( m_hSweepTarget != NULL )
  1590. {
  1591. if ( !m_hSweepTarget->HasSpawnFlags( SF_SNIPERTARGET_RESUME) )
  1592. {
  1593. ClearTargetGroup();
  1594. m_hSweepTarget = NULL;
  1595. }
  1596. }
  1597. if( m_spawnflags & SF_SNIPER_VIEWCONE )
  1598. {
  1599. SetWait( SNIPER_FOG_PAINT_ENEMY_TIME );
  1600. // Just turn it on where it is.
  1601. LaserOn( m_vecPaintCursor, vec3_origin );
  1602. }
  1603. else
  1604. {
  1605. if( GetEnemy()->IsPlayer() )
  1606. {
  1607. float delay = 0;
  1608. #ifdef _XBOX
  1609. delay += sniper_xbox_delay.GetFloat();
  1610. #endif
  1611. if( gpGlobals->curtime - m_flTimeLastAttackedPlayer <= SNIPER_FASTER_ATTACK_PERIOD )
  1612. {
  1613. SetWait( SNIPER_SUBSEQUENT_PAINT_TIME + delay );
  1614. m_flPaintTime = SNIPER_SUBSEQUENT_PAINT_TIME + delay;
  1615. }
  1616. else
  1617. {
  1618. SetWait( m_flKeyfieldPaintTime + delay );
  1619. m_flPaintTime = m_flKeyfieldPaintTime + delay;
  1620. }
  1621. }
  1622. else
  1623. {
  1624. m_flPaintTime = m_flKeyfieldPaintTimeNoise > 0 ?
  1625. m_flKeyfieldPaintTime + random->RandomFloat( 0, m_flKeyfieldPaintTimeNoise ) :
  1626. m_flKeyfieldPaintTime
  1627. ;
  1628. if( IsFastSniper() )
  1629. {
  1630. // Get the shot off a little faster.
  1631. m_flPaintTime *= 0.75f;
  1632. }
  1633. SetWait( m_flPaintTime );
  1634. }
  1635. Vector vecCursor;
  1636. if ( m_spawnflags & SF_SNIPER_NOSWEEP )
  1637. {
  1638. LaserOn( m_vecPaintCursor, vec3_origin );
  1639. }
  1640. else
  1641. {
  1642. // Try to start the laser where the player can't miss seeing it!
  1643. AngleVectors( GetEnemy()->GetLocalAngles(), &vecCursor );
  1644. vecCursor = vecCursor * 300;
  1645. vecCursor += GetEnemy()->EyePosition();
  1646. LaserOn( vecCursor, Vector( 16, 16, 16 ) );
  1647. }
  1648. }
  1649. // Scope glints if shooting at player.
  1650. if( GetEnemy()->IsPlayer() )
  1651. {
  1652. ScopeGlint();
  1653. }
  1654. break;
  1655. case TASK_SNIPER_PAINT_NO_SHOT:
  1656. SetWait( SNIPER_PAINT_NO_SHOT_TIME );
  1657. if( FindFrustratedShot( pTask->flTaskData ) )
  1658. {
  1659. LaserOff();
  1660. LaserOn( m_vecFrustratedTarget, vec3_origin );
  1661. }
  1662. else
  1663. {
  1664. TaskFail( "Frustrated shot with no enemy" );
  1665. }
  1666. break;
  1667. case TASK_SNIPER_PAINT_FRUSTRATED:
  1668. m_flPaintTime = SNIPER_PAINT_FRUSTRATED_TIME + random->RandomFloat( 0, SNIPER_PAINT_FRUSTRATED_TIME );
  1669. SetWait( m_flPaintTime );
  1670. if( FindFrustratedShot( pTask->flTaskData ) )
  1671. {
  1672. LaserOff();
  1673. LaserOn( m_vecFrustratedTarget, vec3_origin );
  1674. }
  1675. else
  1676. {
  1677. TaskFail( "Frustrated shot with no enemy" );
  1678. }
  1679. break;
  1680. case TASK_SNIPER_PAINT_DECOY:
  1681. SetWait( pTask->flTaskData );
  1682. LaserOn( m_vecDecoyObjectTarget, Vector( 64, 64, 64 ) );
  1683. break;
  1684. case TASK_RELOAD:
  1685. {
  1686. CPASAttenuationFilter filter( this );
  1687. EmitSound( filter, entindex(), "NPC_Sniper.Reload" );
  1688. m_fWeaponLoaded = true;
  1689. TaskComplete();
  1690. }
  1691. break;
  1692. case TASK_SNIPER_FRUSTRATED_ATTACK:
  1693. //FindFrustratedShot();
  1694. break;
  1695. default:
  1696. BaseClass::StartTask( pTask );
  1697. break;
  1698. }
  1699. }
  1700. //---------------------------------------------------------
  1701. //---------------------------------------------------------
  1702. void CProtoSniper::RunTask( const Task_t *pTask )
  1703. {
  1704. switch( pTask->iTask )
  1705. {
  1706. case TASK_SNIPER_PLAYER_DEAD:
  1707. if( IsWaitFinished() )
  1708. {
  1709. m_hSweepTarget = PickDeadPlayerTarget();
  1710. m_vecPaintStart = m_vecPaintCursor;
  1711. SetWait( 4.0f );
  1712. }
  1713. else
  1714. {
  1715. PaintTarget( m_hSweepTarget->GetAbsOrigin(), 4.0f );
  1716. }
  1717. break;
  1718. case TASK_SNIPER_ATTACK_CURSOR:
  1719. if( FireBullet( m_vecPaintCursor, true ) )
  1720. {
  1721. TaskComplete();
  1722. }
  1723. break;
  1724. case TASK_RANGE_ATTACK1:
  1725. // Fire at enemy.
  1726. if( FireBullet( LeadTarget( GetEnemy() ), true ) )
  1727. {
  1728. // Msg("Firing at %s\n",GetEnemy()->GetEntityName().ToCStr());
  1729. if( GetEnemy() && GetEnemy()->IsPlayer() )
  1730. {
  1731. m_flTimeLastAttackedPlayer = gpGlobals->curtime;
  1732. }
  1733. TaskComplete();
  1734. }
  1735. else
  1736. {
  1737. // Msg("Firebullet %s is false\n",GetEnemy()->GetEntityName().ToCStr());
  1738. }
  1739. break;
  1740. case TASK_SNIPER_FRUSTRATED_ATTACK:
  1741. if( FireBullet( m_vecFrustratedTarget, false ) )
  1742. {
  1743. TaskComplete();
  1744. }
  1745. break;
  1746. case TASK_SNIPER_PAINT_SWEEP_TARGET:
  1747. if ( !m_hSweepTarget.Get() )
  1748. {
  1749. TaskFail( FAIL_NO_TARGET );
  1750. return;
  1751. }
  1752. if( IsWaitFinished() )
  1753. {
  1754. // Time up! Paint the next target in the chain, or stop.
  1755. CBaseEntity *pNext;
  1756. pNext = gEntList.FindEntityByName( NULL, m_hSweepTarget->m_target );
  1757. if ( m_hSweepTarget->HasSpawnFlags( SF_SNIPERTARGET_SHOOTME ) )
  1758. {
  1759. FireBullet( m_hSweepTarget->GetLocalOrigin(), false );
  1760. TaskComplete(); // Force a reload.
  1761. }
  1762. if( pNext || IsSweepingRandomly() )
  1763. {
  1764. // Bump the timer up, update the cursor, paint the new target!
  1765. // This is done regardless of whether we just fired at the current target.
  1766. m_vecPaintCursor = m_hSweepTarget->GetLocalOrigin();
  1767. if( IsSweepingRandomly() )
  1768. {
  1769. // If sweeping randomly, just pick another target.
  1770. CBaseEntity *pOldTarget;
  1771. pOldTarget = m_hSweepTarget;
  1772. // Pick another target in the group. Don't shoot at the one we just shot at.
  1773. if( m_iNumGroupTargets > 1 )
  1774. {
  1775. do
  1776. {
  1777. m_hSweepTarget = m_pGroupTarget[ random->RandomInt( 0, m_iNumGroupTargets - 1 ) ];
  1778. } while( m_hSweepTarget == pOldTarget );
  1779. }
  1780. }
  1781. else
  1782. {
  1783. // If not, go with the next target in the chain.
  1784. m_hSweepTarget = pNext;
  1785. }
  1786. m_vecPaintStart = m_vecPaintCursor;
  1787. SetWait( m_hSweepTarget->m_flSpeed );
  1788. }
  1789. else
  1790. {
  1791. m_hSweepTarget = NULL;
  1792. LaserOff();
  1793. TaskComplete();
  1794. }
  1795. #if 0
  1796. NDebugOverlay::Line(GetBulletOrigin(), m_hSweepTarget->GetLocalOrigin(), 0,255,0, true, 20 );
  1797. #endif
  1798. }
  1799. else
  1800. {
  1801. if ( m_hSweepTarget->HasSpawnFlags( SF_SNIPERTARGET_SNAPSHOT ) )
  1802. {
  1803. m_fSnapShot = true;
  1804. }
  1805. PaintTarget( m_hSweepTarget->GetAbsOrigin(), m_hSweepTarget->m_flSpeed );
  1806. }
  1807. break;
  1808. case TASK_SNIPER_PAINT_ENEMY:
  1809. if( IsWaitFinished() )
  1810. {
  1811. TaskComplete();
  1812. }
  1813. PaintTarget( LeadTarget( GetEnemy() ), m_flPaintTime );
  1814. break;
  1815. case TASK_SNIPER_PAINT_DECOY:
  1816. if( IsWaitFinished() )
  1817. {
  1818. TaskComplete();
  1819. }
  1820. PaintTarget( m_vecDecoyObjectTarget, pTask->flTaskData );
  1821. break;
  1822. case TASK_SNIPER_PAINT_NO_SHOT:
  1823. if( IsWaitFinished() )
  1824. {
  1825. //HACKHACK(sjb)
  1826. // This condition should be turned off
  1827. // by a task.
  1828. ClearCondition( COND_SNIPER_NO_SHOT );
  1829. TaskComplete();
  1830. }
  1831. PaintTarget( m_vecFrustratedTarget, SNIPER_PAINT_NO_SHOT_TIME );
  1832. break;
  1833. case TASK_SNIPER_PAINT_FRUSTRATED:
  1834. if( IsWaitFinished() )
  1835. {
  1836. TaskComplete();
  1837. }
  1838. PaintTarget( m_vecFrustratedTarget, m_flPaintTime );
  1839. break;
  1840. case TASK_RANGE_ATTACK2:
  1841. // Fire at decoy
  1842. if( m_hDecoyObject == NULL )
  1843. {
  1844. TaskFail("sniper: bad decoy");
  1845. break;
  1846. }
  1847. if( FireBullet( m_vecDecoyObjectTarget, false ) )
  1848. {
  1849. //Msg( "Fired at decoy\n" );
  1850. AddOldDecoy( m_hDecoyObject );
  1851. TaskComplete();
  1852. }
  1853. break;
  1854. default:
  1855. BaseClass::RunTask( pTask );
  1856. break;
  1857. }
  1858. }
  1859. //-----------------------------------------------------------------------------
  1860. // The sniper throws away the circular list of old decoys when we restore.
  1861. //-----------------------------------------------------------------------------
  1862. int CProtoSniper::Restore( IRestore &restore )
  1863. {
  1864. ClearOldDecoys();
  1865. return BaseClass::Restore( restore );
  1866. }
  1867. //-----------------------------------------------------------------------------
  1868. // Purpose:
  1869. //
  1870. //
  1871. //-----------------------------------------------------------------------------
  1872. float CProtoSniper::MaxYawSpeed( void )
  1873. {
  1874. return 60;
  1875. }
  1876. //---------------------------------------------------------
  1877. //---------------------------------------------------------
  1878. void CProtoSniper::PrescheduleThink( void )
  1879. {
  1880. BaseClass::PrescheduleThink();
  1881. // If a sweep target is set, keep asking the AI to sweep the target
  1882. if( m_hSweepTarget != NULL )
  1883. {
  1884. if( m_bSweepHighestPriority || (!HasCondition( COND_CAN_RANGE_ATTACK1 ) && !HasCondition( COND_SNIPER_NO_SHOT ) ) )
  1885. {
  1886. SetCondition( COND_SNIPER_SWEEP_TARGET );
  1887. }
  1888. }
  1889. else
  1890. {
  1891. ClearCondition( COND_SNIPER_SWEEP_TARGET );
  1892. }
  1893. // Think faster if the beam is on, this gives the beam higher resolution.
  1894. if( m_pBeam )
  1895. {
  1896. SetNextThink( gpGlobals->curtime + 0.03 );
  1897. }
  1898. else
  1899. {
  1900. SetNextThink( gpGlobals->curtime + 0.1f );
  1901. }
  1902. // If the enemy has just stepped into view, or we've acquired a new enemy,
  1903. // Record the last time we've seen the enemy as right now.
  1904. //
  1905. // If the enemy has been out of sight for a full second, mark him eluded.
  1906. if( GetEnemy() != NULL )
  1907. {
  1908. if( gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ) > 30 )
  1909. {
  1910. // Stop pestering enemies after 30 seconds of frustration.
  1911. GetEnemies()->ClearMemory( GetEnemy() );
  1912. SetEnemy(NULL);
  1913. }
  1914. }
  1915. // Suppress at the sound of danger. Incoming missiles, for example.
  1916. if( HasCondition( COND_HEAR_DANGER ) )
  1917. {
  1918. SetCondition( COND_SNIPER_SUPPRESSED );
  1919. }
  1920. }
  1921. //---------------------------------------------------------
  1922. //---------------------------------------------------------
  1923. Vector CProtoSniper::EyePosition( void )
  1924. {
  1925. if( m_spawnflags & SF_SNIPER_HIDDEN )
  1926. {
  1927. return GetLocalOrigin();
  1928. }
  1929. else
  1930. {
  1931. return BaseClass::EyePosition();
  1932. }
  1933. }
  1934. //---------------------------------------------------------
  1935. //---------------------------------------------------------
  1936. Vector CProtoSniper::DesiredBodyTarget( CBaseEntity *pTarget )
  1937. {
  1938. // By default, aim for the center
  1939. Vector vecTarget = pTarget->WorldSpaceCenter();
  1940. float flTimeSinceLastMiss = gpGlobals->curtime - m_flTimeLastShotMissed;
  1941. if( pTarget->GetFlags() & FL_CLIENT )
  1942. {
  1943. if( !BaseClass::FVisible( vecTarget ) )
  1944. {
  1945. // go to the player's eyes if his center is concealed.
  1946. // Bump up an inch so the player's not looking straight down a beam.
  1947. vecTarget = pTarget->EyePosition() + Vector( 0, 0, 1 );
  1948. }
  1949. }
  1950. else
  1951. {
  1952. if( pTarget->Classify() == CLASS_HEADCRAB )
  1953. {
  1954. // Headcrabs are tiny inside their boxes.
  1955. vecTarget = pTarget->GetAbsOrigin();
  1956. vecTarget.z += 4.0;
  1957. }
  1958. else if( !m_bShootZombiesInChest && pTarget->Classify() == CLASS_ZOMBIE )
  1959. {
  1960. if( flTimeSinceLastMiss > 0.0f && flTimeSinceLastMiss < 4.0f && hl2_episodic.GetBool() )
  1961. {
  1962. vecTarget = pTarget->BodyTarget( GetBulletOrigin(), false );
  1963. }
  1964. else
  1965. {
  1966. // Shoot zombies in the headcrab
  1967. vecTarget = pTarget->HeadTarget( GetBulletOrigin() );
  1968. }
  1969. }
  1970. else if( pTarget->Classify() == CLASS_ANTLION )
  1971. {
  1972. // Shoot about a few inches above the origin. This makes it easy to hit antlions
  1973. // even if they are on their backs.
  1974. vecTarget = pTarget->GetAbsOrigin();
  1975. vecTarget.z += 18.0f;
  1976. }
  1977. else if( pTarget->Classify() == CLASS_EARTH_FAUNA )
  1978. {
  1979. // Shoot birds in the center
  1980. }
  1981. else
  1982. {
  1983. // Shoot NPCs in the chest
  1984. vecTarget.z += 8.0f;
  1985. }
  1986. }
  1987. return vecTarget;
  1988. }
  1989. //---------------------------------------------------------
  1990. //---------------------------------------------------------
  1991. Vector CProtoSniper::LeadTarget( CBaseEntity *pTarget )
  1992. {
  1993. float targetTime;
  1994. float targetDist;
  1995. //float adjustedShotDist;
  1996. //float actualShotDist;
  1997. Vector vecAdjustedShot;
  1998. Vector vecTarget;
  1999. trace_t tr;
  2000. /*
  2001. NDebugOverlay::EntityBounds(pTarget,
  2002. 255,255,0,96,0.1f);
  2003. */
  2004. if( sniperLines.GetBool() )
  2005. {
  2006. Msg("Sniper %s is targeting %s\n", GetDebugName(), pTarget ? pTarget->GetDebugName() : "nobody" );
  2007. }
  2008. if( pTarget == NULL )
  2009. {
  2010. // no target
  2011. return vec3_origin;
  2012. }
  2013. // Get target
  2014. vecTarget = DesiredBodyTarget( pTarget );
  2015. // Get bullet time to target
  2016. targetDist = (vecTarget - GetBulletOrigin() ).Length();
  2017. targetTime = targetDist / GetBulletSpeed();
  2018. // project target's velocity over that time.
  2019. Vector vecVelocity = vec3_origin;
  2020. if( pTarget->IsPlayer() || pTarget->Classify() == CLASS_MISSILE )
  2021. {
  2022. // This target is a client, who has an actual velocity.
  2023. vecVelocity = pTarget->GetSmoothedVelocity();
  2024. // Slow the vertical velocity down a lot, or the sniper will
  2025. // lead a jumping player by firing several feet above his head.
  2026. // THIS may affect the sniper hitting a player that's ascending/descending
  2027. // ladders. If so, we'll have to check for the player's ladder flag.
  2028. if( pTarget->GetFlags() & FL_CLIENT )
  2029. {
  2030. vecVelocity.z *= 0.25;
  2031. }
  2032. }
  2033. else
  2034. {
  2035. if( pTarget->MyNPCPointer() && pTarget->MyNPCPointer()->GetNavType() == NAV_FLY )
  2036. {
  2037. // Take a flying monster's velocity directly.
  2038. vecVelocity = pTarget->GetAbsVelocity();
  2039. }
  2040. else
  2041. {
  2042. // Have to build a velocity vector using the character's current groundspeed.
  2043. CBaseAnimating *pAnimating;
  2044. pAnimating = (CBaseAnimating *)pTarget;
  2045. Assert( pAnimating != NULL );
  2046. QAngle vecAngle;
  2047. vecAngle.y = pAnimating->GetSequenceMoveYaw( pAnimating->GetSequence() );
  2048. vecAngle.x = 0;
  2049. vecAngle.z = 0;
  2050. vecAngle.y += pTarget->GetLocalAngles().y;
  2051. AngleVectors( vecAngle, &vecVelocity );
  2052. vecVelocity = vecVelocity * pAnimating->m_flGroundSpeed;
  2053. }
  2054. }
  2055. if( m_iMisses > 0 && !FClassnameIs( pTarget, "npc_bullseye" ) )
  2056. {
  2057. // I'm supposed to miss this shot, so aim above the target's head.
  2058. // BUT DON'T miss bullseyes, and don't count the shot.
  2059. vecAdjustedShot = vecTarget;
  2060. vecAdjustedShot.z += 16;
  2061. m_iMisses--;
  2062. // NDebugOverlay::Cross3D(vecAdjustedShot,12.0f,255,0,0,false,1);
  2063. return vecAdjustedShot;
  2064. }
  2065. vecAdjustedShot = vecTarget + ( vecVelocity * targetTime );
  2066. // if the adjusted shot falls well short of the target, take the straight shot.
  2067. // it's not very interesting for the bullet to hit something far away from the
  2068. // target. (for instance, if a sign or ledge or something is between the player
  2069. // and the sniper, and the sniper would hit this object if he tries to lead the player)
  2070. // NDebugOverlay::Cross3D(vecAdjustedShot,12.0f,5,255,0,false,1);
  2071. if( sniperLines.GetFloat() == 1.0f )
  2072. {
  2073. Vector vecBulletOrigin;
  2074. vecBulletOrigin = GetBulletOrigin();
  2075. CPVSFilter filter( GetLocalOrigin() );
  2076. te->ShowLine( filter, 0.0, &vecBulletOrigin, &vecAdjustedShot );
  2077. }
  2078. /*
  2079. UTIL_TraceLine( vecBulletOrigin, vecAdjustedShot, MASK_SHOT, this, &tr );
  2080. actualShotDist = (tr.endpos - vecBulletOrigin ).Length();
  2081. adjustedShotDist = ( vecAdjustedShot - vecBulletOrigin ).Length();
  2082. /////////////////////////////////////////////
  2083. // the shot taken should hit within 10% of the sniper's distance to projected target.
  2084. // else, shoot straight. (there's some object in the way of the adjusted shot)
  2085. /////////////////////////////////////////////
  2086. if( actualShotDist <= adjustedShotDist * 0.9 )
  2087. {
  2088. vecAdjustedShot = vecTarget;
  2089. }
  2090. */
  2091. return vecAdjustedShot;
  2092. }
  2093. //---------------------------------------------------------
  2094. // Sniper killed the player. Pick the player's body or something
  2095. // nearby to point the laser at, so that the player can get
  2096. // a fix on the sniper's location.
  2097. //---------------------------------------------------------
  2098. CBaseEntity *CProtoSniper::PickDeadPlayerTarget()
  2099. {
  2100. const int iSearchSize = 32;
  2101. CBaseEntity *pTarget = AI_GetSinglePlayer();
  2102. CBaseEntity *pEntities[ iSearchSize ];
  2103. int iNumEntities = UTIL_EntitiesInSphere( pEntities, iSearchSize, AI_GetSinglePlayer()->GetAbsOrigin(), 180.0f, 0 );
  2104. // Not very robust, but doesn't need to be. Randomly select a nearby object in the list that isn't an NPC.
  2105. if( iNumEntities > 0 )
  2106. {
  2107. int i;
  2108. // Try a few times to randomly select a target.
  2109. for( i = 0 ; i < 10 ; i++ )
  2110. {
  2111. CBaseEntity *pCandidate = pEntities[ random->RandomInt(0, iNumEntities - 1) ];
  2112. if( !pCandidate->IsNPC() && FInViewCone(pCandidate) )
  2113. {
  2114. return pCandidate;
  2115. }
  2116. }
  2117. }
  2118. // Fall through to accept the player as a target.
  2119. return pTarget;
  2120. }
  2121. //---------------------------------------------------------
  2122. //---------------------------------------------------------
  2123. void CProtoSniper::InputEnableSniper( inputdata_t &inputdata )
  2124. {
  2125. ClearCondition( COND_SNIPER_DISABLED );
  2126. SetCondition( COND_SNIPER_ENABLED );
  2127. m_fEnabled = true;
  2128. }
  2129. //---------------------------------------------------------
  2130. //---------------------------------------------------------
  2131. void CProtoSniper::InputDisableSniper( inputdata_t &inputdata )
  2132. {
  2133. ClearCondition( COND_SNIPER_ENABLED );
  2134. SetCondition( COND_SNIPER_DISABLED );
  2135. m_fEnabled = false;
  2136. }
  2137. //---------------------------------------------------------
  2138. //---------------------------------------------------------
  2139. bool CProtoSniper::FindFrustratedShot( float flNoise )
  2140. {
  2141. Vector vecForward;
  2142. Vector vecStart;
  2143. Vector vecAimAt;
  2144. Vector vecAim;
  2145. if( !GetEnemy() )
  2146. {
  2147. return false;
  2148. }
  2149. // Just pick a spot somewhere around the target.
  2150. // Try a handful of times to pick a spot that guarantees the
  2151. // target will see the laser.
  2152. #define MAX_TRIES 15
  2153. for( int i = 0 ; i < MAX_TRIES ; i++ )
  2154. {
  2155. Vector vecSpot = GetEnemyLKP();
  2156. vecSpot.x += random->RandomFloat( -64, 64 );
  2157. vecSpot.y += random->RandomFloat( -64, 64 );
  2158. vecSpot.z += random->RandomFloat( -40, 40 );
  2159. // Help move the frustrated spot off the target's BBOX in X/Y space.
  2160. if( vecSpot.x < 0 )
  2161. vecSpot.x -= 32;
  2162. else
  2163. vecSpot.x += 32;
  2164. if( vecSpot.y < 0 )
  2165. vecSpot.y -= 32;
  2166. else
  2167. vecSpot.y += 32;
  2168. Vector vecSrc, vecDir;
  2169. vecSrc = GetAbsOrigin();
  2170. vecDir = vecSpot - vecSrc;
  2171. VectorNormalize( vecDir );
  2172. if( GetEnemy()->FVisible( vecSpot ) || i == MAX_TRIES - 1 )
  2173. {
  2174. trace_t tr;
  2175. AI_TraceLine(vecSrc, vecSrc + vecDir * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
  2176. if( !GetEnemy()->FVisible( tr.endpos ) )
  2177. {
  2178. // Dont accept this point unless we are out of tries!
  2179. if( i != MAX_TRIES - 1 )
  2180. {
  2181. continue;
  2182. }
  2183. }
  2184. m_vecFrustratedTarget = tr.endpos;
  2185. break;
  2186. }
  2187. }
  2188. #if 0
  2189. NDebugOverlay::Line(vecStart, tr.endpos, 0,255,0, true, 20 );
  2190. #endif
  2191. return true;
  2192. }
  2193. //---------------------------------------------------------
  2194. // See all NPC's easily.
  2195. //
  2196. // Only see the player if you can trace to both of his
  2197. // eyeballs. That is, allow the player to peek around corners.
  2198. // This is a little more expensive than the base class' check!
  2199. //---------------------------------------------------------
  2200. #define SNIPER_EYE_DIST 0.75
  2201. #define SNIPER_TARGET_VERTICAL_OFFSET Vector( 0, 0, 5 );
  2202. bool CProtoSniper::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
  2203. {
  2204. if( m_spawnflags & SF_SNIPER_VIEWCONE )
  2205. {
  2206. // Viewcone snipers are blind with their laser off.
  2207. if( !IsLaserOn() )
  2208. {
  2209. return false;
  2210. }
  2211. }
  2212. if( !pEntity->IsPlayer() )
  2213. {
  2214. // NPC
  2215. return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
  2216. }
  2217. if ( pEntity->GetFlags() & FL_NOTARGET )
  2218. {
  2219. return false;
  2220. }
  2221. Vector vecVerticalOffset;
  2222. Vector vecRight;
  2223. Vector vecEye;
  2224. trace_t tr;
  2225. if( fabs( GetAbsOrigin().z - pEntity->WorldSpaceCenter().z ) <= 120.f )
  2226. {
  2227. // If the player is around the same elevation, look straight at his eyes.
  2228. // At the same elevation, the vertical peeking allowance makes it too easy
  2229. // for a player to dispatch the sniper from cover.
  2230. vecVerticalOffset = vec3_origin;
  2231. }
  2232. else
  2233. {
  2234. // Otherwise, look at a spot below his eyes. This allows the player to back away
  2235. // from his cover a bit and have a peek at the sniper without being detected.
  2236. vecVerticalOffset = SNIPER_TARGET_VERTICAL_OFFSET;
  2237. }
  2238. AngleVectors( pEntity->GetLocalAngles(), NULL, &vecRight, NULL );
  2239. vecEye = vecRight * SNIPER_EYE_DIST - vecVerticalOffset;
  2240. UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr );
  2241. #if 0
  2242. NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
  2243. #endif
  2244. bool fCheckFailed = false;
  2245. if( tr.fraction != 1.0 )
  2246. {
  2247. fCheckFailed = true;
  2248. }
  2249. // Don't check the other eye if the first eye failed.
  2250. if( !fCheckFailed )
  2251. {
  2252. vecEye = -vecRight * SNIPER_EYE_DIST - vecVerticalOffset;
  2253. UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr );
  2254. #if 0
  2255. NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
  2256. #endif
  2257. if( tr.fraction != 1.0 )
  2258. {
  2259. fCheckFailed = true;
  2260. }
  2261. }
  2262. if( !fCheckFailed )
  2263. {
  2264. // Can see the player.
  2265. return true;
  2266. }
  2267. // Now, if the check failed, see if the player is ducking and has recently
  2268. // fired a muzzleflash. If yes, see if you'd be able to see the player if
  2269. // they were standing in their current position instead of ducking. Since
  2270. // the sniper doesn't have a clear shot in this situation, he will harrass
  2271. // near the player.
  2272. CBasePlayer *pPlayer;
  2273. pPlayer = ToBasePlayer( pEntity );
  2274. if( (pPlayer->GetFlags() & FL_DUCKING) && pPlayer->MuzzleFlashTime() > gpGlobals->curtime )
  2275. {
  2276. vecEye = pPlayer->EyePosition() + Vector( 0, 0, 32 );
  2277. UTIL_TraceLine( EyePosition(), vecEye, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr );
  2278. if( tr.fraction != 1.0 )
  2279. {
  2280. // Everything failed.
  2281. if (ppBlocker)
  2282. {
  2283. *ppBlocker = tr.m_pEnt;
  2284. }
  2285. return false;
  2286. }
  2287. else
  2288. {
  2289. // Fake being able to see the player.
  2290. return true;
  2291. }
  2292. }
  2293. if (ppBlocker)
  2294. {
  2295. *ppBlocker = tr.m_pEnt;
  2296. }
  2297. return false;
  2298. }
  2299. //-----------------------------------------------------------------------------
  2300. // Purpose: Draw any debug text overlays
  2301. // Output : Returns the current text offset from the top
  2302. //-----------------------------------------------------------------------------
  2303. int CProtoSniper::DrawDebugTextOverlays()
  2304. {
  2305. int text_offset = 0;
  2306. // ---------------------
  2307. // Print Baseclass text
  2308. // ---------------------
  2309. text_offset = BaseClass::DrawDebugTextOverlays();
  2310. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  2311. {
  2312. char tempstr[512];
  2313. CSniperTarget *pTarget = NULL;
  2314. if ( m_iNumGroupTargets > 0 )
  2315. {
  2316. pTarget = dynamic_cast<CSniperTarget *>(m_pGroupTarget[0]);
  2317. }
  2318. Q_snprintf( tempstr, sizeof( tempstr ), "Sweep group (count): %s (%d)", pTarget != NULL ? STRING( pTarget->m_iszGroupName ) : "<None>", m_iNumGroupTargets );
  2319. EntityText( text_offset, tempstr, 0 );
  2320. text_offset++;
  2321. for ( int i = 0; i < m_iNumGroupTargets; i++ )
  2322. {
  2323. if ( m_pGroupTarget[i] != NULL )
  2324. {
  2325. NDebugOverlay::VertArrow( EyePosition(), m_pGroupTarget[i]->GetAbsOrigin(), 8, 0, 255, 0, 0, true, 0);
  2326. }
  2327. }
  2328. }
  2329. return text_offset;
  2330. }
  2331. //-----------------------------------------------------------------------------
  2332. // Inform the sniper that a bullet missed its intended target. We don't know
  2333. // which bullet or which target.
  2334. //-----------------------------------------------------------------------------
  2335. void CProtoSniper::NotifyShotMissedTarget()
  2336. {
  2337. m_flTimeLastShotMissed = gpGlobals->curtime;
  2338. // In episodic, aim at the (easier to hit at distance or high speed) centers
  2339. // of the bodies of NPC targets. This change makes Alyx sniper less likely to
  2340. // miss zombie and zombines over and over because of the large amount of head movement
  2341. // in these NPCs' walk and run animations.
  2342. }
  2343. //-----------------------------------------------------------------------------
  2344. //
  2345. // Schedules
  2346. //
  2347. //-----------------------------------------------------------------------------
  2348. AI_BEGIN_CUSTOM_NPC( proto_sniper, CProtoSniper )
  2349. DECLARE_CONDITION( COND_SNIPER_CANATTACKDECOY );
  2350. DECLARE_CONDITION( COND_SNIPER_SUPPRESSED );
  2351. DECLARE_CONDITION( COND_SNIPER_ENABLED );
  2352. DECLARE_CONDITION( COND_SNIPER_DISABLED );
  2353. DECLARE_CONDITION( COND_SNIPER_FRUSTRATED );
  2354. DECLARE_CONDITION( COND_SNIPER_SWEEP_TARGET );
  2355. DECLARE_CONDITION( COND_SNIPER_NO_SHOT );
  2356. DECLARE_TASK( TASK_SNIPER_FRUSTRATED_ATTACK );
  2357. DECLARE_TASK( TASK_SNIPER_PAINT_ENEMY );
  2358. DECLARE_TASK( TASK_SNIPER_PAINT_DECOY );
  2359. DECLARE_TASK( TASK_SNIPER_PAINT_FRUSTRATED );
  2360. DECLARE_TASK( TASK_SNIPER_PAINT_SWEEP_TARGET );
  2361. DECLARE_TASK( TASK_SNIPER_ATTACK_CURSOR );
  2362. DECLARE_TASK( TASK_SNIPER_PAINT_NO_SHOT );
  2363. DECLARE_TASK( TASK_SNIPER_PLAYER_DEAD );
  2364. //=========================================================
  2365. // SCAN
  2366. //=========================================================
  2367. DEFINE_SCHEDULE
  2368. (
  2369. SCHED_PSNIPER_SCAN,
  2370. " Tasks"
  2371. " TASK_WAIT_INDEFINITE 0"
  2372. " "
  2373. " Interrupts"
  2374. " COND_NEW_ENEMY"
  2375. " COND_ENEMY_DEAD"
  2376. " COND_HEAR_DANGER"
  2377. " COND_SNIPER_DISABLED"
  2378. " COND_SNIPER_SWEEP_TARGET"
  2379. )
  2380. //=========================================================
  2381. // CAMP
  2382. //=========================================================
  2383. DEFINE_SCHEDULE
  2384. (
  2385. SCHED_PSNIPER_CAMP,
  2386. " Tasks"
  2387. " TASK_WAIT_INDEFINITE 0"
  2388. " "
  2389. " Interrupts"
  2390. " COND_NEW_ENEMY"
  2391. " COND_ENEMY_DEAD"
  2392. " COND_CAN_RANGE_ATTACK1"
  2393. " COND_SNIPER_CANATTACKDECOY"
  2394. " COND_SNIPER_SUPPRESSED"
  2395. " COND_HEAR_DANGER"
  2396. " COND_SNIPER_DISABLED"
  2397. " COND_SNIPER_FRUSTRATED"
  2398. " COND_SNIPER_SWEEP_TARGET"
  2399. )
  2400. //=========================================================
  2401. // ATTACK
  2402. //=========================================================
  2403. DEFINE_SCHEDULE
  2404. (
  2405. SCHED_PSNIPER_ATTACK,
  2406. " Tasks"
  2407. " TASK_SNIPER_PAINT_ENEMY 0"
  2408. " TASK_RANGE_ATTACK1 0"
  2409. " "
  2410. " Interrupts"
  2411. " COND_ENEMY_OCCLUDED"
  2412. " COND_ENEMY_DEAD"
  2413. " COND_HEAR_DANGER"
  2414. " COND_SNIPER_DISABLED"
  2415. )
  2416. //=========================================================
  2417. // ATTACK
  2418. //=========================================================
  2419. DEFINE_SCHEDULE
  2420. (
  2421. SCHED_PSNIPER_SNAPATTACK,
  2422. " Tasks"
  2423. " TASK_SNIPER_ATTACK_CURSOR 0"
  2424. " "
  2425. " Interrupts"
  2426. " COND_ENEMY_OCCLUDED"
  2427. " COND_ENEMY_DEAD"
  2428. " COND_NEW_ENEMY"
  2429. " COND_HEAR_DANGER"
  2430. " COND_SNIPER_DISABLED"
  2431. )
  2432. //=========================================================
  2433. // RELOAD
  2434. //=========================================================
  2435. DEFINE_SCHEDULE
  2436. (
  2437. SCHED_PSNIPER_RELOAD,
  2438. " Tasks"
  2439. " TASK_RELOAD 0"
  2440. " TASK_WAIT 1.0"
  2441. " "
  2442. " Interrupts"
  2443. " COND_HEAR_DANGER"
  2444. )
  2445. //=========================================================
  2446. // Attack decoy
  2447. //=========================================================
  2448. DEFINE_SCHEDULE
  2449. (
  2450. SCHED_PSNIPER_ATTACKDECOY,
  2451. " Tasks"
  2452. " TASK_SNIPER_PAINT_DECOY 2.0"
  2453. " TASK_RANGE_ATTACK2 0"
  2454. " "
  2455. " Interrupts"
  2456. " COND_NEW_ENEMY"
  2457. " COND_ENEMY_DEAD"
  2458. " COND_HEAR_DANGER"
  2459. " COND_CAN_RANGE_ATTACK1"
  2460. " COND_SNIPER_DISABLED"
  2461. " COND_SNIPER_SWEEP_TARGET"
  2462. )
  2463. //=========================================================
  2464. //=========================================================
  2465. DEFINE_SCHEDULE
  2466. (
  2467. SCHED_PSNIPER_SUPPRESSED,
  2468. " Tasks"
  2469. " TASK_WAIT 2.0"
  2470. " "
  2471. " Interrupts"
  2472. )
  2473. //=========================================================
  2474. // Sniper is allowed to process a couple conditions while
  2475. // disabled, but mostly he waits until he's enabled.
  2476. //=========================================================
  2477. DEFINE_SCHEDULE
  2478. (
  2479. SCHED_PSNIPER_DISABLEDWAIT,
  2480. " Tasks"
  2481. " TASK_WAIT 0.5"
  2482. " "
  2483. " Interrupts"
  2484. " COND_SNIPER_ENABLED"
  2485. " COND_NEW_ENEMY"
  2486. " COND_ENEMY_DEAD"
  2487. )
  2488. //=========================================================
  2489. //=========================================================
  2490. DEFINE_SCHEDULE
  2491. (
  2492. SCHED_PSNIPER_FRUSTRATED_ATTACK,
  2493. " Tasks"
  2494. " TASK_WAIT 2.0"
  2495. " TASK_SNIPER_PAINT_FRUSTRATED 0.05"
  2496. " TASK_SNIPER_PAINT_FRUSTRATED 0.025"
  2497. " TASK_SNIPER_PAINT_FRUSTRATED 0.0"
  2498. " TASK_SNIPER_FRUSTRATED_ATTACK 0.0"
  2499. " "
  2500. " Interrupts"
  2501. " COND_NEW_ENEMY"
  2502. " COND_ENEMY_DEAD"
  2503. " COND_SNIPER_DISABLED"
  2504. " COND_CAN_RANGE_ATTACK1"
  2505. " COND_SEE_ENEMY"
  2506. " COND_HEAR_DANGER"
  2507. " COND_SNIPER_SWEEP_TARGET"
  2508. )
  2509. //=========================================================
  2510. //=========================================================
  2511. DEFINE_SCHEDULE
  2512. (
  2513. SCHED_PSNIPER_SWEEP_TARGET,
  2514. " Tasks"
  2515. " TASK_SNIPER_PAINT_SWEEP_TARGET 0.0"
  2516. " "
  2517. " Interrupts"
  2518. " COND_NEW_ENEMY"
  2519. " COND_SNIPER_DISABLED"
  2520. " COND_CAN_RANGE_ATTACK1"
  2521. " COND_HEAR_DANGER"
  2522. " COND_SNIPER_NO_SHOT"
  2523. )
  2524. //=========================================================
  2525. //=========================================================
  2526. DEFINE_SCHEDULE
  2527. (
  2528. SCHED_PSNIPER_SWEEP_TARGET_NOINTERRUPT,
  2529. " Tasks"
  2530. " TASK_SNIPER_PAINT_SWEEP_TARGET 0.0"
  2531. " "
  2532. " Interrupts"
  2533. " COND_SNIPER_DISABLED"
  2534. )
  2535. //=========================================================
  2536. //=========================================================
  2537. DEFINE_SCHEDULE
  2538. (
  2539. SCHED_PSNIPER_NO_CLEAR_SHOT,
  2540. " Tasks"
  2541. " TASK_SNIPER_PAINT_NO_SHOT 0.0"
  2542. " TASK_SNIPER_PAINT_NO_SHOT 0.075"
  2543. " TASK_SNIPER_PAINT_NO_SHOT 0.05"
  2544. " TASK_SNIPER_PAINT_NO_SHOT 0.0"
  2545. " "
  2546. " Interrupts"
  2547. " COND_NEW_ENEMY"
  2548. " COND_ENEMY_DEAD"
  2549. " COND_SNIPER_DISABLED"
  2550. " COND_CAN_RANGE_ATTACK1"
  2551. " COND_HEAR_DANGER"
  2552. )
  2553. //=========================================================
  2554. //=========================================================
  2555. DEFINE_SCHEDULE
  2556. (
  2557. SCHED_PSNIPER_PLAYER_DEAD,
  2558. " Tasks"
  2559. " TASK_SNIPER_PLAYER_DEAD 0"
  2560. " "
  2561. " Interrupts"
  2562. )
  2563. AI_END_CUSTOM_NPC()
  2564. //-----------------------------------------------------------------------------
  2565. //
  2566. // Sniper Bullet
  2567. //
  2568. //-----------------------------------------------------------------------------
  2569. //---------------------------------------------------------
  2570. //---------------------------------------------------------
  2571. void CSniperBullet::Precache()
  2572. {
  2573. }
  2574. //---------------------------------------------------------
  2575. //---------------------------------------------------------
  2576. void CSniperBullet::BulletThink( void )
  2577. {
  2578. // Set the bullet up to think again.
  2579. SetNextThink( gpGlobals->curtime + 0.05 );
  2580. if( !GetOwnerEntity() )
  2581. {
  2582. // Owner died!
  2583. Stop();
  2584. return;
  2585. }
  2586. if( gpGlobals->curtime >= m_SoundTime )
  2587. {
  2588. // See if it's time to make the sonic boom.
  2589. CPASAttenuationFilter filter( this, ATTN_NONE );
  2590. EmitSound( filter, entindex(), "NPC_Sniper.SonicBoom" );
  2591. if( GetOwnerEntity() )
  2592. {
  2593. CAI_BaseNPC *pSniper;
  2594. CAI_BaseNPC *pEnemyNPC;
  2595. pSniper = GetOwnerEntity()->MyNPCPointer();
  2596. if( pSniper && pSniper->GetEnemy() )
  2597. {
  2598. pEnemyNPC = pSniper->GetEnemy()->MyNPCPointer();
  2599. // Warn my enemy if they can see the sniper.
  2600. if( pEnemyNPC && GetOwnerEntity() && pEnemyNPC->FVisible( GetOwnerEntity()->WorldSpaceCenter() ) )
  2601. {
  2602. CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_FROM_SNIPER, pSniper->GetEnemy()->EarPosition(), 16, 1.0f, GetOwnerEntity() );
  2603. }
  2604. }
  2605. }
  2606. // No way the bullet will live this long.
  2607. m_SoundTime = 1e9;
  2608. }
  2609. // Trace this timeslice of the bullet.
  2610. Vector vecStart;
  2611. Vector vecEnd;
  2612. float flInterval;
  2613. flInterval = gpGlobals->curtime - GetLastThink();
  2614. vecStart = GetAbsOrigin();
  2615. vecEnd = vecStart + ( m_vecDir * (m_Speed * flInterval) );
  2616. float flDist = (vecStart - vecEnd).Length();
  2617. //Msg(".");
  2618. trace_t tr;
  2619. AI_TraceLine( vecStart, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  2620. if( tr.fraction != 1.0 )
  2621. {
  2622. // This slice of bullet will hit something.
  2623. GetOwnerEntity()->FireBullets( 1, vecStart, m_vecDir, vec3_origin, flDist, m_AmmoType, 0 );
  2624. m_iImpacts++;
  2625. #ifdef HL2_EPISODIC
  2626. if( tr.m_pEnt->IsNPC() || m_iImpacts == NUM_PENETRATIONS )
  2627. #else
  2628. if( tr.m_pEnt->m_takedamage == DAMAGE_YES || m_iImpacts == NUM_PENETRATIONS )
  2629. #endif//HL2_EPISODIC
  2630. {
  2631. // Bullet stops when it hits an NPC, or when it has penetrated enough times.
  2632. if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() )
  2633. {
  2634. if( tr.m_pEnt->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
  2635. {
  2636. Pickup_ForcePlayerToDropThisObject(tr.m_pEnt);
  2637. }
  2638. }
  2639. Stop();
  2640. return;
  2641. }
  2642. else
  2643. {
  2644. #define STEP_SIZE 2
  2645. #define NUM_STEPS 6
  2646. // Try to slide a 'cursor' through the object that was hit.
  2647. Vector vecCursor = tr.endpos;
  2648. for( int i = 0 ; i < NUM_STEPS ; i++ )
  2649. {
  2650. //Msg("-");
  2651. vecCursor += m_vecDir * STEP_SIZE;
  2652. if( UTIL_PointContents( vecCursor ) != CONTENTS_SOLID )
  2653. {
  2654. // Passed out of a solid!
  2655. SetAbsOrigin( vecCursor );
  2656. // Fire another tracer.
  2657. AI_TraceLine( vecCursor, vecCursor + m_vecDir * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  2658. UTIL_Tracer( vecCursor, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, m_Speed, true, "StriderTracer" );
  2659. return;
  2660. }
  2661. }
  2662. // Bullet also stops when it fails to exit material after penetrating this far.
  2663. //Msg("#\n");
  2664. if( m_bDirectShot )
  2665. {
  2666. CProtoSniper *pSniper = dynamic_cast<CProtoSniper*>(GetOwnerEntity());
  2667. if( pSniper )
  2668. {
  2669. pSniper->NotifyShotMissedTarget();
  2670. }
  2671. }
  2672. Stop();
  2673. return;
  2674. }
  2675. }
  2676. else
  2677. {
  2678. SetAbsOrigin( vecEnd );
  2679. }
  2680. }
  2681. //=========================================================
  2682. //=========================================================
  2683. bool CSniperBullet::Start( const Vector &vecOrigin, const Vector &vecTarget, CBaseEntity *pOwner, bool bDirectShot )
  2684. {
  2685. m_flLastThink = gpGlobals->curtime;
  2686. if( m_AmmoType == -1 )
  2687. {
  2688. // This guy doesn't have a REAL weapon, per say, but he does fire
  2689. // sniper rounds. Since there's no weapon to index the ammo type,
  2690. // do it manually here.
  2691. m_AmmoType = GetAmmoDef()->Index("SniperRound");
  2692. // This is the bullet that is used for all subsequent FireBullets() calls after the first
  2693. // call penetrates a surface and keeps going.
  2694. m_PenetratedAmmoType = GetAmmoDef()->Index("SniperPenetratedRound");
  2695. }
  2696. if( m_fActive )
  2697. {
  2698. return false;
  2699. }
  2700. SetOwnerEntity( pOwner );
  2701. UTIL_SetOrigin( this, vecOrigin );
  2702. m_vecDir = vecTarget - vecOrigin;
  2703. VectorNormalize( m_vecDir );
  2704. // Set speed;
  2705. CProtoSniper *pSniper = dynamic_cast<CProtoSniper*>(pOwner);
  2706. if( pSniper )
  2707. {
  2708. m_Speed = pSniper->GetBulletSpeed();
  2709. }
  2710. else
  2711. {
  2712. m_Speed = bulletSpeed.GetFloat();
  2713. }
  2714. // Start the tracer here, and tell it to end at the end of the last trace
  2715. // the trace comes from the loop above that does penetration.
  2716. trace_t tr;
  2717. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + m_vecDir * 8192, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  2718. UTIL_Tracer( vecOrigin, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, m_Speed, true, "StriderTracer" );
  2719. float flElapsedTime = ( (tr.startpos - tr.endpos).Length() / m_Speed );
  2720. m_SoundTime = gpGlobals->curtime + flElapsedTime * 0.5;
  2721. SetThink( &CSniperBullet::BulletThink );
  2722. SetNextThink( gpGlobals->curtime );
  2723. m_fActive = true;
  2724. m_bDirectShot = bDirectShot;
  2725. return true;
  2726. /*
  2727. int i;
  2728. // Try to find all of the things the bullet can go through along the way.
  2729. //-------------------------------
  2730. //-------------------------------
  2731. m_vecDir = vecTarget - vecOrigin;
  2732. VectorNormalize( m_vecDir );
  2733. trace_t tr;
  2734. // Elapsed time counts how long the bullet is in motion through this simulation.
  2735. float flElapsedTime = 0;
  2736. for( i = 0 ; i < NUM_PENETRATIONS ; i++ )
  2737. {
  2738. // Trace to the target.
  2739. UTIL_TraceLine( GetAbsOrigin(), vecTarget, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  2740. flShotDist = (tr.endpos - GetAbsOrigin()).Length();
  2741. // Record the two endpoints of the segment and the time at which this bullet hits,
  2742. // and the time at which it's supposed to hit its mark.
  2743. m_ImpactTime[ i ] = flElapsedTime + ( flShotDist / GetBulletSpeed() );
  2744. m_vecStart[ i ] = tr.startpos;
  2745. m_vecEnd[ i ] = tr.endpos;
  2746. // The elapsed time is now pushed forward by how long it takes the bullet
  2747. // to travel through this segment.
  2748. flElapsedTime += ( flShotDist / GetBulletSpeed() );
  2749. // Never let gpGlobals->curtime get added to the elapsed time!
  2750. m_ImpactTime[ i ] += gpGlobals->curtime;
  2751. CBaseEntity *pEnt;
  2752. pEnt = tr.m_pEnt;
  2753. if( !pEnt ||
  2754. pEnt->MyNPCPointer() ||
  2755. UTIL_DistApprox2D( tr.endpos, vecTarget ) <= 4 ||
  2756. FClassnameIs( pEnt, "prop_physics" ) )
  2757. {
  2758. // If we're close to the target, assume the shot is going to hit
  2759. // the target and stop penetrating.
  2760. //
  2761. // If we're going to hit an NPC, stop penetrating.
  2762. //
  2763. // If we hit a physics prop, stop penetrating.
  2764. //
  2765. // Otherwise, keep looping.
  2766. break;
  2767. }
  2768. // We're going to try to penetrate whatever the bullet has hit.
  2769. // Push through the object by the penetration distance, then trace back.
  2770. Vector vecCursor;
  2771. vecCursor = tr.endpos;
  2772. vecCursor += m_vecDir * PENETRATION_THICKNESS;
  2773. UTIL_TraceLine( vecCursor, vecCursor + m_vecDir * -2, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  2774. #if 1
  2775. if( tr.startsolid )
  2776. {
  2777. // The cursor is inside the solid. Solid is too thick to penetrate.
  2778. #ifdef SNIPER_DEBUG
  2779. Msg( "SNIPER STARTSOLID\n" );
  2780. #endif
  2781. break;
  2782. }
  2783. #endif
  2784. // Now put the bullet at this point and continue.
  2785. UTIL_SetOrigin( this, vecCursor );
  2786. }
  2787. //-------------------------------
  2788. //-------------------------------
  2789. */
  2790. /*
  2791. #ifdef SNIPER_DEBUG
  2792. Msg( "PENETRATING %d items", i );
  2793. #endif // SNIPER_DEBUG
  2794. #ifdef SNIPER_DEBUG
  2795. Msg( "Dist: %f Travel Time: %f\n", flShotDist, m_ImpactTime );
  2796. #endif // SNIPER_DEBUG
  2797. */
  2798. }
  2799. //---------------------------------------------------------
  2800. //---------------------------------------------------------
  2801. void CSniperBullet::Init( void )
  2802. {
  2803. #ifdef SNIPER_DEBUG
  2804. Msg( "Bullet stopped\n" );
  2805. #endif // SNIPER_DEBUG
  2806. m_fActive = false;
  2807. m_vecDir.Init();
  2808. m_AmmoType = -1;
  2809. m_SoundTime = 1e9;
  2810. m_iImpacts = 0;
  2811. }
  2812. //---------------------------------------------------------
  2813. //---------------------------------------------------------
  2814. void CSniperBullet::Stop( void )
  2815. {
  2816. // The bullet doesn't retire immediately because it still has a sound
  2817. // in the world that is relying on the bullet's position as a react origin.
  2818. // So stick around for another second or so.
  2819. SetThink( &CBaseEntity::SUB_Remove );
  2820. SetNextThink( gpGlobals->curtime + 1.0 );
  2821. }
  2822. //---------------------------------------------------------
  2823. //---------------------------------------------------------
  2824. bool CSniperTarget::KeyValue( const char *szKeyName, const char *szValue )
  2825. {
  2826. if (FStrEq(szKeyName, "groupname"))
  2827. {
  2828. m_iszGroupName = AllocPooledString( szValue );
  2829. return true;
  2830. }
  2831. else
  2832. {
  2833. return CPointEntity::KeyValue( szKeyName, szValue );
  2834. }
  2835. }
  2836. LINK_ENTITY_TO_CLASS( info_snipertarget, CSniperTarget );