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.

3035 lines
83 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_task.h"
  10. #include "ai_schedule.h"
  11. #include "ai_hull.h"
  12. #include "ai_squadslot.h"
  13. #include "ai_basenpc.h"
  14. #include "ai_navigator.h"
  15. #include "ai_interactions.h"
  16. #include "ndebugoverlay.h"
  17. #include "explode.h"
  18. #include "bitstring.h"
  19. #include "vstdlib/random.h"
  20. #include "engine/IEngineSound.h"
  21. #include "decals.h"
  22. #include "antlion_dust.h"
  23. #include "ai_memory.h"
  24. #include "ai_squad.h"
  25. #include "ai_senses.h"
  26. #include "beam_shared.h"
  27. #include "iservervehicle.h"
  28. #include "SoundEmitterSystem/isoundemittersystembase.h"
  29. #include "physics_saverestore.h"
  30. #include "vphysics/constraints.h"
  31. #include "vehicle_base.h"
  32. #include "eventqueue.h"
  33. #include "te_effect_dispatch.h"
  34. #include "npc_rollermine.h"
  35. #include "func_break.h"
  36. #include "soundenvelope.h"
  37. #include "mapentities.h"
  38. #include "RagdollBoogie.h"
  39. #include "physics_collisionevent.h"
  40. // memdbgon must be the last include file in a .cpp file!!!
  41. #include "tier0/memdbgon.h"
  42. #define ROLLERMINE_MAX_TORQUE_FACTOR 5
  43. extern short g_sModelIndexWExplosion;
  44. ConVar sk_rollermine_shock( "sk_rollermine_shock","0");
  45. ConVar sk_rollermine_stun_delay("sk_rollermine_stun_delay", "1");
  46. ConVar sk_rollermine_vehicle_intercept( "sk_rollermine_vehicle_intercept","1");
  47. enum
  48. {
  49. ROLLER_SKIN_REGULAR = 0,
  50. ROLLER_SKIN_FRIENDLY,
  51. ROLLER_SKIN_DETONATE,
  52. };
  53. //-----------------------------------------------------------------------------
  54. // CRollerController implementation
  55. //-----------------------------------------------------------------------------
  56. //-----------------------------------------------------------------------------
  57. // Purpose: This class only implements the IMotionEvent-specific behavior
  58. // It keeps track of the forces so they can be integrated
  59. //-----------------------------------------------------------------------------
  60. class CRollerController : public IMotionEvent
  61. {
  62. DECLARE_SIMPLE_DATADESC();
  63. public:
  64. IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
  65. AngularImpulse m_vecAngular;
  66. Vector m_vecLinear;
  67. void Off( void ) { m_fIsStopped = true; }
  68. void On( void ) { m_fIsStopped = false; }
  69. bool IsOn( void ) { return !m_fIsStopped; }
  70. private:
  71. bool m_fIsStopped;
  72. };
  73. BEGIN_SIMPLE_DATADESC( CRollerController )
  74. DEFINE_FIELD( m_vecAngular, FIELD_VECTOR ),
  75. DEFINE_FIELD( m_vecLinear, FIELD_VECTOR ),
  76. DEFINE_FIELD( m_fIsStopped, FIELD_BOOLEAN ),
  77. END_DATADESC()
  78. //-----------------------------------------------------------------------------
  79. IMotionEvent::simresult_e CRollerController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
  80. {
  81. if( m_fIsStopped )
  82. {
  83. return SIM_NOTHING;
  84. }
  85. linear = m_vecLinear;
  86. angular = m_vecAngular;
  87. return IMotionEvent::SIM_LOCAL_ACCELERATION;
  88. }
  89. //-----------------------------------------------------------------------------
  90. #define ROLLERMINE_IDLE_SEE_DIST 2048
  91. #define ROLLERMINE_NORMAL_SEE_DIST 2048
  92. #define ROLLERMINE_WAKEUP_DIST 256
  93. #define ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE 300 // See every other than vehicles upto this distance (i.e. old idle see dist)
  94. #define ROLLERMINE_SEE_VEHICLESONLY_BEYOND_NORMAL 800 // See every other than vehicles upto this distance (i.e. old normal see dist)
  95. #define ROLLERMINE_RETURN_TO_PLAYER_DIST (200*200)
  96. #define ROLLERMINE_MIN_ATTACK_DIST 1
  97. #define ROLLERMINE_MAX_ATTACK_DIST 4096
  98. #define ROLLERMINE_OPEN_THRESHOLD 256
  99. #define ROLLERMINE_VEHICLE_OPEN_THRESHOLD 400
  100. #define ROLLERMINE_VEHICLE_HOP_THRESHOLD 300
  101. #define ROLLERMINE_HOP_DELAY 2 // Don't allow hops faster than this
  102. //#define ROLLERMINE_REQUIRED_TO_EXPLODE_VEHICLE 4
  103. #define ROLLERMINE_FEAR_DISTANCE (300*300)
  104. //=========================================================
  105. // Custom schedules
  106. //=========================================================
  107. enum
  108. {
  109. SCHED_ROLLERMINE_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE,
  110. SCHED_ROLLERMINE_CHASE_ENEMY,
  111. SCHED_ROLLERMINE_BURIED_WAIT,
  112. SCHED_ROLLERMINE_BURIED_UNBURROW,
  113. SCHED_ROLLERMINE_FLEE,
  114. SCHED_ROLLERMINE_ALERT_STAND,
  115. SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES,
  116. SCHED_ROLLERMINE_PATH_TO_PLAYER,
  117. SCHED_ROLLERMINE_ROLL_TO_PLAYER,
  118. SCHED_ROLLERMINE_POWERDOWN,
  119. };
  120. //=========================================================
  121. // Custom tasks
  122. //=========================================================
  123. enum
  124. {
  125. TASK_ROLLERMINE_CHARGE_ENEMY = LAST_SHARED_TASK,
  126. TASK_ROLLERMINE_BURIED_WAIT,
  127. TASK_ROLLERMINE_UNBURROW,
  128. TASK_ROLLERMINE_GET_PATH_TO_FLEE,
  129. TASK_ROLLERMINE_NUDGE_TOWARDS_NODES,
  130. TASK_ROLLERMINE_RETURN_TO_PLAYER,
  131. TASK_ROLLERMINE_POWERDOWN,
  132. };
  133. // This are little 'sound event' flags. Set the flag after you play the
  134. // sound, and the sound will not be allowed to play until the flag is then cleared.
  135. #define ROLLERMINE_SE_CLEAR 0x00000000
  136. #define ROLLERMINE_SE_CHARGE 0x00000001
  137. #define ROLLERMINE_SE_TAUNT 0x00000002
  138. #define ROLLERMINE_SE_SHARPEN 0x00000004
  139. #define ROLLERMINE_SE_TOSSED 0x00000008
  140. enum rollingsoundstate_t { ROLL_SOUND_NOT_READY = 0, ROLL_SOUND_OFF, ROLL_SOUND_CLOSED, ROLL_SOUND_OPEN };
  141. //=========================================================
  142. //=========================================================
  143. class CNPC_RollerMine : public CNPCBaseInteractive<CAI_BaseNPC>, public CDefaultPlayerPickupVPhysics
  144. {
  145. DECLARE_CLASS( CNPC_RollerMine, CNPCBaseInteractive<CAI_BaseNPC> );
  146. DECLARE_SERVERCLASS();
  147. public:
  148. CNPC_RollerMine( void ) { m_bTurnedOn = true; m_bUniformSight = false; }
  149. ~CNPC_RollerMine( void );
  150. void Spawn( void );
  151. bool CreateVPhysics();
  152. void RunAI();
  153. void StartTask( const Task_t *pTask );
  154. void RunTask( const Task_t *pTask );
  155. void SpikeTouch( CBaseEntity *pOther );
  156. void ShockTouch( CBaseEntity *pOther );
  157. void CloseTouch( CBaseEntity *pOther );
  158. void EmbedTouch( CBaseEntity *pOther );
  159. float GetAttackDamageScale( CBaseEntity *pVictim );
  160. void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
  161. void Precache( void );
  162. void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
  163. void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
  164. void StopLoopingSounds( void );
  165. void PrescheduleThink();
  166. bool ShouldSavePhysics() { return true; }
  167. void OnRestore();
  168. void Bury( trace_t *tr );
  169. bool QuerySeeEntity(CBaseEntity *pSightEnt, bool bOnlyHateOrFearIfNPC = false );
  170. int RangeAttack1Conditions ( float flDot, float flDist );
  171. int SelectSchedule( void );
  172. int TranslateSchedule( int scheduleType );
  173. int GetHackedIdleSchedule( void );
  174. bool OverrideMove( float flInterval ) { return true; }
  175. bool IsValidEnemy( CBaseEntity *pEnemy );
  176. bool IsPlayerVehicle( CBaseEntity *pEntity );
  177. bool IsShocking() { return gpGlobals->curtime < m_flShockTime ? true : false; }
  178. void UpdateRollingSound();
  179. void UpdatePingSound();
  180. void StopRollingSound();
  181. void StopPingSound();
  182. float RollingSpeed();
  183. float GetStunDelay();
  184. void EmbedOnGroundImpact();
  185. void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
  186. void DrawDebugGeometryOverlays()
  187. {
  188. if (m_debugOverlays & OVERLAY_BBOX_BIT)
  189. {
  190. float dist = GetSenses()->GetDistLook();
  191. Vector range(dist, dist, 64);
  192. NDebugOverlay::Box( GetAbsOrigin(), -range, range, 255, 0, 0, 0, 0 );
  193. }
  194. BaseClass::DrawDebugGeometryOverlays();
  195. }
  196. // UNDONE: Put this in the qc file!
  197. Vector EyePosition()
  198. {
  199. // This takes advantage of the fact that the system knows
  200. // that the abs origin is at the center of the rollermine
  201. // and that the OBB is actually world-aligned despite the
  202. // fact that SOLID_VPHYSICS is being used
  203. Vector eye = CollisionProp()->GetCollisionOrigin();
  204. eye.z += CollisionProp()->OBBMaxs().z;
  205. return eye;
  206. }
  207. int OnTakeDamage( const CTakeDamageInfo &info );
  208. void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
  209. Class_T Classify()
  210. {
  211. if( !m_bTurnedOn )
  212. return CLASS_NONE;
  213. //About to blow up after being hacked so do damage to the player.
  214. if ( m_bHackedByAlyx && ( m_flPowerDownDetonateTime > 0.0f && m_flPowerDownDetonateTime <= gpGlobals->curtime ) )
  215. return CLASS_COMBINE;
  216. return ( m_bHeld || m_bHackedByAlyx ) ? CLASS_HACKED_ROLLERMINE : CLASS_COMBINE;
  217. }
  218. virtual bool ShouldGoToIdleState()
  219. {
  220. return gpGlobals->curtime > m_flGoIdleTime ? true : false;
  221. }
  222. virtual void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );
  223. // Vehicle interception
  224. bool EnemyInVehicle( void );
  225. float VehicleHeading( CBaseEntity *pVehicle );
  226. NPC_STATE SelectIdealState();
  227. // Vehicle sticking
  228. void StickToVehicle( CBaseEntity *pOther );
  229. void AnnounceArrivalToOthers( CBaseEntity *pOther );
  230. void UnstickFromVehicle( void );
  231. CBaseEntity *GetVehicleStuckTo( void );
  232. int CountRollersOnMyVehicle( CUtlVector<CNPC_RollerMine*> *pRollerList );
  233. void InputConstraintBroken( inputdata_t &inputdata );
  234. void InputRespondToChirp( inputdata_t &inputdata );
  235. void InputRespondToExplodeChirp( inputdata_t &inputdata );
  236. void InputJoltVehicle( inputdata_t &inputdata );
  237. void InputTurnOn( inputdata_t &inputdata );
  238. void InputTurnOff( inputdata_t &inputdata );
  239. void InputPowerdown( inputdata_t &inputdata );
  240. void PreventUnstickUntil( float flTime ) { m_flPreventUnstickUntil = flTime; }
  241. virtual unsigned int PhysicsSolidMaskForEntity( void ) const;
  242. void SetRollerSkin( void );
  243. COutputEvent m_OnPhysGunDrop;
  244. COutputEvent m_OnPhysGunPickup;
  245. protected:
  246. DEFINE_CUSTOM_AI;
  247. DECLARE_DATADESC();
  248. bool BecomePhysical();
  249. void WakeNeighbors();
  250. bool WakeupMine( CAI_BaseNPC *pNPC );
  251. void Open( void );
  252. void Close( void );
  253. void Explode( void );
  254. void PreDetonate( void );
  255. void Hop( float height );
  256. void ShockTarget( CBaseEntity *pOther );
  257. bool IsActive() { return m_flActiveTime > gpGlobals->curtime ? false : true; }
  258. // INPCInteractive Functions
  259. virtual bool CanInteractWith( CAI_BaseNPC *pUser ) { return true; }
  260. virtual bool HasBeenInteractedWith() { return m_bHackedByAlyx; }
  261. virtual void NotifyInteraction( CAI_BaseNPC *pUser );
  262. CSoundPatch *m_pRollSound;
  263. CSoundPatch *m_pPingSound;
  264. CRollerController m_RollerController;
  265. IPhysicsMotionController *m_pMotionController;
  266. float m_flSeeVehiclesOnlyBeyond;
  267. float m_flChargeTime;
  268. float m_flGoIdleTime;
  269. float m_flShockTime;
  270. float m_flForwardSpeed;
  271. int m_iSoundEventFlags;
  272. rollingsoundstate_t m_rollingSoundState;
  273. CNetworkVar( bool, m_bIsOpen );
  274. CNetworkVar( float, m_flActiveTime ); //If later than the current time, this will force the mine to be active
  275. bool m_bHeld; //Whether or not the player is holding the mine
  276. EHANDLE m_hVehicleStuckTo;
  277. float m_flPreventUnstickUntil;
  278. float m_flNextHop;
  279. bool m_bStartBuried;
  280. bool m_bBuried;
  281. bool m_bIsPrimed;
  282. bool m_wakeUp;
  283. bool m_bEmbedOnGroundImpact;
  284. CNetworkVar( bool, m_bHackedByAlyx );
  285. // Constraint used to stick us to a vehicle
  286. IPhysicsConstraint *m_pConstraint;
  287. bool m_bTurnedOn;
  288. bool m_bUniformSight;
  289. CNetworkVar( bool, m_bPowerDown );
  290. float m_flPowerDownTime;
  291. float m_flPowerDownDetonateTime;
  292. static string_t gm_iszDropshipClassname;
  293. };
  294. string_t CNPC_RollerMine::gm_iszDropshipClassname;
  295. LINK_ENTITY_TO_CLASS( npc_rollermine, CNPC_RollerMine );
  296. //-----------------------------------------------------------------------------
  297. //-----------------------------------------------------------------------------
  298. BEGIN_DATADESC( CNPC_RollerMine )
  299. DEFINE_SOUNDPATCH( m_pRollSound ),
  300. DEFINE_SOUNDPATCH( m_pPingSound ),
  301. DEFINE_EMBEDDED( m_RollerController ),
  302. DEFINE_PHYSPTR( m_pMotionController ),
  303. DEFINE_FIELD( m_flSeeVehiclesOnlyBeyond, FIELD_FLOAT ),
  304. DEFINE_FIELD( m_flActiveTime, FIELD_TIME ),
  305. DEFINE_FIELD( m_flChargeTime, FIELD_TIME ),
  306. DEFINE_FIELD( m_flGoIdleTime, FIELD_TIME ),
  307. DEFINE_FIELD( m_flShockTime, FIELD_TIME ),
  308. DEFINE_FIELD( m_flForwardSpeed, FIELD_FLOAT ),
  309. DEFINE_FIELD( m_bIsOpen, FIELD_BOOLEAN ),
  310. DEFINE_FIELD( m_bHeld, FIELD_BOOLEAN ),
  311. DEFINE_FIELD( m_hVehicleStuckTo, FIELD_EHANDLE ),
  312. DEFINE_FIELD( m_flPreventUnstickUntil, FIELD_TIME ),
  313. DEFINE_FIELD( m_flNextHop, FIELD_FLOAT ),
  314. DEFINE_FIELD( m_bIsPrimed, FIELD_BOOLEAN ),
  315. DEFINE_FIELD( m_iSoundEventFlags, FIELD_INTEGER ),
  316. DEFINE_FIELD( m_rollingSoundState, FIELD_INTEGER ),
  317. DEFINE_KEYFIELD( m_bStartBuried, FIELD_BOOLEAN, "StartBuried" ),
  318. DEFINE_FIELD( m_bBuried, FIELD_BOOLEAN ),
  319. DEFINE_FIELD( m_wakeUp, FIELD_BOOLEAN ),
  320. DEFINE_FIELD( m_bEmbedOnGroundImpact, FIELD_BOOLEAN ),
  321. DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN ),
  322. DEFINE_FIELD( m_bPowerDown, FIELD_BOOLEAN ),
  323. DEFINE_FIELD( m_flPowerDownTime, FIELD_TIME ),
  324. DEFINE_FIELD( m_flPowerDownDetonateTime, FIELD_TIME ),
  325. DEFINE_PHYSPTR( m_pConstraint ),
  326. DEFINE_FIELD( m_bTurnedOn, FIELD_BOOLEAN ),
  327. DEFINE_KEYFIELD( m_bUniformSight, FIELD_BOOLEAN, "uniformsightdist" ),
  328. DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputConstraintBroken ),
  329. DEFINE_INPUTFUNC( FIELD_VOID, "RespondToChirp", InputRespondToChirp ),
  330. DEFINE_INPUTFUNC( FIELD_VOID, "RespondToExplodeChirp", InputRespondToExplodeChirp ),
  331. DEFINE_INPUTFUNC( FIELD_VOID, "JoltVehicle", InputJoltVehicle ),
  332. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
  333. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
  334. DEFINE_INPUTFUNC( FIELD_VOID, "PowerDown", InputPowerdown ),
  335. // Function Pointers
  336. DEFINE_ENTITYFUNC( SpikeTouch ),
  337. DEFINE_ENTITYFUNC( ShockTouch ),
  338. DEFINE_ENTITYFUNC( CloseTouch ),
  339. DEFINE_ENTITYFUNC( EmbedTouch ),
  340. DEFINE_THINKFUNC( Explode ),
  341. DEFINE_THINKFUNC( PreDetonate ),
  342. DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
  343. DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),
  344. DEFINE_BASENPCINTERACTABLE_DATADESC(),
  345. END_DATADESC()
  346. IMPLEMENT_SERVERCLASS_ST( CNPC_RollerMine, DT_RollerMine )
  347. SendPropInt(SENDINFO(m_bIsOpen), 1, SPROP_UNSIGNED ),
  348. SendPropFloat(SENDINFO(m_flActiveTime), 0, SPROP_NOSCALE ),
  349. SendPropInt(SENDINFO(m_bHackedByAlyx), 1, SPROP_UNSIGNED ),
  350. SendPropInt(SENDINFO(m_bPowerDown), 1, SPROP_UNSIGNED ),
  351. END_SEND_TABLE()
  352. bool NPC_Rollermine_IsRollermine( CBaseEntity *pEntity )
  353. {
  354. CNPC_RollerMine *pRoller = dynamic_cast<CNPC_RollerMine *>(pEntity);
  355. return pRoller ? true : false;
  356. }
  357. CBaseEntity *NPC_Rollermine_DropFromPoint( const Vector &originStart, CBaseEntity *pOwner, const char *pszTemplate )
  358. {
  359. CBaseEntity *pEntity = NULL;
  360. CNPC_RollerMine *pMine = NULL;
  361. // Use the template, if we have it
  362. if ( pszTemplate && pszTemplate[0] )
  363. {
  364. MapEntity_ParseEntity( pEntity, pszTemplate, NULL );
  365. pMine = dynamic_cast<CNPC_RollerMine *>(pEntity);
  366. }
  367. else
  368. {
  369. pMine = (CNPC_RollerMine*)CreateEntityByName("npc_rollermine");
  370. }
  371. if ( pMine )
  372. {
  373. pMine->SetAbsOrigin( originStart );
  374. pMine->SetOwnerEntity( pOwner );
  375. pMine->Spawn();
  376. if ( !pszTemplate || !pszTemplate[0] )
  377. {
  378. pMine->EmbedOnGroundImpact();
  379. }
  380. }
  381. else
  382. {
  383. Warning( "NULL Ent in Rollermine Create!\n" );
  384. }
  385. return pMine;
  386. }
  387. //-----------------------------------------------------------------------------
  388. // Purpose:
  389. //-----------------------------------------------------------------------------
  390. CNPC_RollerMine::~CNPC_RollerMine( void )
  391. {
  392. if ( m_pMotionController != NULL )
  393. {
  394. physenv->DestroyMotionController( m_pMotionController );
  395. m_pMotionController = NULL;
  396. }
  397. UnstickFromVehicle();
  398. }
  399. //-----------------------------------------------------------------------------
  400. // Purpose:
  401. // Input :
  402. // Output :
  403. //-----------------------------------------------------------------------------
  404. void CNPC_RollerMine::Precache( void )
  405. {
  406. PrecacheModel( "models/roller.mdl" );
  407. PrecacheModel( "models/roller_spikes.mdl" );
  408. PrecacheModel( "sprites/bluelight1.vmt" );
  409. PrecacheModel( "sprites/rollermine_shock.vmt" );
  410. PrecacheModel( "sprites/rollermine_shock_yellow.vmt" );
  411. PrecacheScriptSound( "NPC_RollerMine.Taunt" );
  412. PrecacheScriptSound( "NPC_RollerMine.OpenSpikes" );
  413. PrecacheScriptSound( "NPC_RollerMine.Warn" );
  414. PrecacheScriptSound( "NPC_RollerMine.Shock" );
  415. PrecacheScriptSound( "NPC_RollerMine.ExplodeChirp" );
  416. PrecacheScriptSound( "NPC_RollerMine.Chirp" );
  417. PrecacheScriptSound( "NPC_RollerMine.ChirpRespond" );
  418. PrecacheScriptSound( "NPC_RollerMine.ExplodeChirpRespond" );
  419. PrecacheScriptSound( "NPC_RollerMine.JoltVehicle" );
  420. PrecacheScriptSound( "NPC_RollerMine.Tossed" );
  421. PrecacheScriptSound( "NPC_RollerMine.Hurt" );
  422. PrecacheScriptSound( "NPC_RollerMine.Roll" );
  423. PrecacheScriptSound( "NPC_RollerMine.RollWithSpikes" );
  424. PrecacheScriptSound( "NPC_RollerMine.Ping" );
  425. PrecacheScriptSound( "NPC_RollerMine.Held" );
  426. PrecacheScriptSound( "NPC_RollerMine.Reprogram" );
  427. PrecacheMaterial( "effects/rollerglow" );
  428. gm_iszDropshipClassname = AllocPooledString( "npc_combinedropship" ); // For fast string compares.
  429. #ifdef HL2_EPISODIC
  430. PrecacheScriptSound( "RagdollBoogie.Zap" );
  431. #endif
  432. BaseClass::Precache();
  433. }
  434. //-----------------------------------------------------------------------------
  435. // Purpose:
  436. // Input :
  437. // Output :
  438. //-----------------------------------------------------------------------------
  439. void CNPC_RollerMine::Spawn( void )
  440. {
  441. Precache();
  442. SetSolid( SOLID_VPHYSICS );
  443. AddSolidFlags( FSOLID_FORCE_WORLD_ALIGNED | FSOLID_NOT_STANDABLE );
  444. BaseClass::Spawn();
  445. AddEFlags( EFL_NO_DISSOLVE );
  446. CapabilitiesClear();
  447. CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SQUAD );
  448. m_pRollSound = NULL;
  449. m_bIsOpen = true;
  450. Close();
  451. m_bPowerDown = false;
  452. m_flFieldOfView = -1.0f;
  453. m_flForwardSpeed = -1200;
  454. m_bloodColor = DONT_BLEED;
  455. SetHullType(HULL_SMALL_CENTERED);
  456. SetHullSizeNormal();
  457. m_flActiveTime = 0;
  458. m_bBuried = m_bStartBuried;
  459. if ( m_bStartBuried )
  460. {
  461. trace_t tr;
  462. Bury( &tr );
  463. }
  464. NPCInit();
  465. m_takedamage = DAMAGE_EVENTS_ONLY;
  466. SetDistLook( ROLLERMINE_IDLE_SEE_DIST );
  467. if( m_bUniformSight )
  468. {
  469. m_flSeeVehiclesOnlyBeyond = ROLLERMINE_IDLE_SEE_DIST;
  470. }
  471. else
  472. {
  473. m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE;
  474. }
  475. //Suppress superfluous warnings from animation system
  476. m_flGroundSpeed = 20;
  477. m_NPCState = NPC_STATE_NONE;
  478. m_rollingSoundState = ROLL_SOUND_OFF;
  479. m_pConstraint = NULL;
  480. m_hVehicleStuckTo = NULL;
  481. m_flPreventUnstickUntil = 0;
  482. m_flNextHop = 0;
  483. m_flPowerDownDetonateTime = 0.0f;
  484. m_bPowerDown = false;
  485. m_flPowerDownTime = 0.0f;
  486. //Set their yaw speed to 0 so the motor doesn't rotate them.
  487. GetMotor()->SetYawSpeed( 0.0f );
  488. SetRollerSkin();
  489. }
  490. //-----------------------------------------------------------------------------
  491. // Set the contents types that are solid by default to this NPC
  492. //-----------------------------------------------------------------------------
  493. unsigned int CNPC_RollerMine::PhysicsSolidMaskForEntity( void ) const
  494. {
  495. if ( HasSpawnFlags( SF_ROLLERMINE_PROP_COLLISION ) )
  496. return MASK_SOLID;
  497. return MASK_NPCSOLID;
  498. }
  499. //-----------------------------------------------------------------------------
  500. // Purpose:
  501. //-----------------------------------------------------------------------------
  502. void CNPC_RollerMine::Bury( trace_t *tr )
  503. {
  504. AI_TraceHull( GetAbsOrigin() + Vector(0,0,64), GetAbsOrigin() - Vector( 0, 0, MAX_TRACE_LENGTH ), Vector(-16,-16,-16), Vector(16,16,16), MASK_NPCSOLID, this, GetCollisionGroup(), tr );
  505. //NDebugOverlay::Box( tr->startpos, Vector(-16,-16,-16), Vector(16,16,16), 255, 0, 0, 64, 10.0 );
  506. //NDebugOverlay::Box( tr->endpos, Vector(-16,-16,-16), Vector(16,16,16), 0, 255, 0, 64, 10.0 );
  507. // Move into the ground layer
  508. Vector buriedPos = tr->endpos - Vector( 0, 0, GetHullHeight() * 0.5 );
  509. Teleport( &buriedPos, NULL, &vec3_origin );
  510. SetMoveType( MOVETYPE_NONE );
  511. SetSchedule( SCHED_ROLLERMINE_BURIED_WAIT );
  512. }
  513. //-----------------------------------------------------------------------------
  514. // Purpose:
  515. //-----------------------------------------------------------------------------
  516. bool CNPC_RollerMine::WakeupMine( CAI_BaseNPC *pNPC )
  517. {
  518. if ( pNPC && pNPC->m_iClassname == m_iClassname && pNPC != this )
  519. {
  520. CNPC_RollerMine *pMine = dynamic_cast<CNPC_RollerMine *>(pNPC);
  521. if ( pMine )
  522. {
  523. if ( pMine->m_NPCState == NPC_STATE_IDLE )
  524. {
  525. pMine->m_wakeUp = false;
  526. pMine->SetIdealState( NPC_STATE_ALERT );
  527. return true;
  528. }
  529. }
  530. }
  531. return false;
  532. }
  533. //-----------------------------------------------------------------------------
  534. // Purpose:
  535. //-----------------------------------------------------------------------------
  536. void CNPC_RollerMine::WakeNeighbors()
  537. {
  538. if ( !m_wakeUp || !IsActive() )
  539. return;
  540. m_wakeUp = false;
  541. if ( m_pSquad )
  542. {
  543. AISquadIter_t iter;
  544. for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
  545. {
  546. WakeupMine( pSquadMember );
  547. }
  548. return;
  549. }
  550. CBaseEntity *entityList[64];
  551. Vector range(ROLLERMINE_WAKEUP_DIST,ROLLERMINE_WAKEUP_DIST,64);
  552. int boxCount = UTIL_EntitiesInBox( entityList, ARRAYSIZE(entityList), GetAbsOrigin()-range, GetAbsOrigin()+range, FL_NPC );
  553. //NDebugOverlay::Box( GetAbsOrigin(), -range, range, 255, 0, 0, 64, 10.0 );
  554. int wakeCount = 0;
  555. while ( boxCount > 0 )
  556. {
  557. boxCount--;
  558. CAI_BaseNPC *pNPC = entityList[boxCount]->MyNPCPointer();
  559. if ( WakeupMine( pNPC ) )
  560. {
  561. wakeCount++;
  562. if ( wakeCount >= 2 )
  563. return;
  564. }
  565. }
  566. }
  567. //-----------------------------------------------------------------------------
  568. // Purpose:
  569. //-----------------------------------------------------------------------------
  570. void CNPC_RollerMine::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
  571. {
  572. if ( NewState == NPC_STATE_IDLE )
  573. {
  574. SetDistLook( ROLLERMINE_IDLE_SEE_DIST );
  575. m_flDistTooFar = ROLLERMINE_IDLE_SEE_DIST;
  576. if( m_bUniformSight )
  577. {
  578. m_flSeeVehiclesOnlyBeyond = ROLLERMINE_IDLE_SEE_DIST;
  579. }
  580. else
  581. {
  582. m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE;
  583. }
  584. m_RollerController.m_vecAngular = vec3_origin;
  585. m_wakeUp = true;
  586. }
  587. else
  588. {
  589. if ( OldState == NPC_STATE_IDLE )
  590. {
  591. // wake the neighbors!
  592. WakeNeighbors();
  593. }
  594. SetDistLook( ROLLERMINE_NORMAL_SEE_DIST );
  595. if( m_bUniformSight )
  596. {
  597. m_flSeeVehiclesOnlyBeyond = ROLLERMINE_NORMAL_SEE_DIST;
  598. }
  599. else
  600. {
  601. m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_NORMAL;
  602. }
  603. m_flDistTooFar = ROLLERMINE_NORMAL_SEE_DIST;
  604. }
  605. BaseClass::OnStateChange( OldState, NewState );
  606. }
  607. //-----------------------------------------------------------------------------
  608. // Purpose:
  609. //-----------------------------------------------------------------------------
  610. NPC_STATE CNPC_RollerMine::SelectIdealState( void )
  611. {
  612. switch ( m_NPCState )
  613. {
  614. case NPC_STATE_COMBAT:
  615. {
  616. if ( HasCondition( COND_ENEMY_TOO_FAR ) )
  617. {
  618. ClearEnemyMemory();
  619. SetEnemy( NULL );
  620. m_flGoIdleTime = gpGlobals->curtime + 10;
  621. return NPC_STATE_ALERT;
  622. }
  623. }
  624. }
  625. return BaseClass::SelectIdealState();
  626. }
  627. //-----------------------------------------------------------------------------
  628. // Purpose:
  629. // Output : Returns true on success, false on failure.
  630. //-----------------------------------------------------------------------------
  631. bool CNPC_RollerMine::BecomePhysical( void )
  632. {
  633. VPhysicsDestroyObject();
  634. RemoveSolidFlags( FSOLID_NOT_SOLID );
  635. //Setup the physics controller on the roller
  636. IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags() , false );
  637. if ( pPhysicsObject == NULL )
  638. return false;
  639. m_pMotionController = physenv->CreateMotionController( &m_RollerController );
  640. m_pMotionController->AttachObject( pPhysicsObject, true );
  641. SetMoveType( MOVETYPE_VPHYSICS );
  642. return true;
  643. }
  644. //-----------------------------------------------------------------------------
  645. // Purpose:
  646. //-----------------------------------------------------------------------------
  647. void CNPC_RollerMine::OnRestore()
  648. {
  649. BaseClass::OnRestore();
  650. if ( m_pMotionController )
  651. {
  652. m_pMotionController->SetEventHandler( &m_RollerController );
  653. }
  654. // If we're stuck to a vehicle over a level transition, restart our jolt inputs
  655. if ( GetVehicleStuckTo() )
  656. {
  657. if ( !g_EventQueue.HasEventPending( this, "JoltVehicle" ) )
  658. {
  659. g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
  660. }
  661. }
  662. }
  663. //-----------------------------------------------------------------------------
  664. // Purpose:
  665. //-----------------------------------------------------------------------------
  666. bool CNPC_RollerMine::CreateVPhysics()
  667. {
  668. if ( m_bBuried )
  669. {
  670. VPhysicsInitStatic();
  671. return true;
  672. }
  673. else
  674. {
  675. return BecomePhysical();
  676. }
  677. }
  678. //-----------------------------------------------------------------------------
  679. //-----------------------------------------------------------------------------
  680. void CNPC_RollerMine::RunAI()
  681. {
  682. if( m_bTurnedOn )
  683. {
  684. // Scare combine if hacked by Alyx.
  685. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  686. Vector vecVelocity;
  687. if ( pPhysicsObject != NULL )
  688. {
  689. pPhysicsObject->GetVelocity( &vecVelocity, NULL );
  690. }
  691. if( !m_bHeld && vecVelocity.Length() > 64.0 )
  692. {
  693. if( m_bHackedByAlyx )
  694. {
  695. // Scare combine
  696. CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_COMBINE_ONLY | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
  697. }
  698. else
  699. {
  700. // Scare player allies
  701. CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_EXCLUDE_COMBINE | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
  702. }
  703. }
  704. BaseClass::RunAI();
  705. }
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose:
  709. // Input :
  710. // Output :
  711. //-----------------------------------------------------------------------------
  712. int CNPC_RollerMine::RangeAttack1Conditions( float flDot, float flDist )
  713. {
  714. if( HasCondition( COND_SEE_ENEMY ) == false )
  715. return COND_NONE;
  716. if ( EnemyInVehicle() )
  717. return COND_CAN_RANGE_ATTACK1;
  718. if( flDist > ROLLERMINE_MAX_ATTACK_DIST )
  719. return COND_TOO_FAR_TO_ATTACK;
  720. if (flDist < ROLLERMINE_MIN_ATTACK_DIST )
  721. return COND_TOO_CLOSE_TO_ATTACK;
  722. return COND_CAN_RANGE_ATTACK1;
  723. }
  724. //-----------------------------------------------------------------------------
  725. // Purpose:
  726. // Input :
  727. // Output :
  728. //-----------------------------------------------------------------------------
  729. int CNPC_RollerMine::SelectSchedule( void )
  730. {
  731. if ( m_bPowerDown )
  732. return SCHED_ROLLERMINE_POWERDOWN;
  733. if ( m_bBuried )
  734. {
  735. if ( HasCondition(COND_NEW_ENEMY) || HasCondition(COND_LIGHT_DAMAGE) )
  736. return SCHED_ROLLERMINE_BURIED_UNBURROW;
  737. return SCHED_ROLLERMINE_BURIED_WAIT;
  738. }
  739. //If we're held, don't try and do anything
  740. if ( ( m_bHeld ) || !IsActive() || m_hVehicleStuckTo )
  741. return SCHED_ALERT_STAND;
  742. // If we can see something we're afraid of, run from it
  743. if ( HasCondition( COND_SEE_FEAR ) )
  744. return SCHED_ROLLERMINE_FLEE;
  745. switch( m_NPCState )
  746. {
  747. case NPC_STATE_COMBAT:
  748. if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  749. return SCHED_ROLLERMINE_RANGE_ATTACK1;
  750. return SCHED_ROLLERMINE_CHASE_ENEMY;
  751. break;
  752. default:
  753. break;
  754. }
  755. // Rollermines never wait to fall to the ground
  756. ClearCondition( COND_FLOATING_OFF_GROUND );
  757. return BaseClass::SelectSchedule();
  758. }
  759. //-----------------------------------------------------------------------------
  760. // Purpose:
  761. //-----------------------------------------------------------------------------
  762. int CNPC_RollerMine::GetHackedIdleSchedule( void )
  763. {
  764. // If we've been hacked, return to the player
  765. if ( !m_bHackedByAlyx || m_bHeld )
  766. return SCHED_NONE;
  767. // Are we near the player?
  768. CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );
  769. if ( !pPlayer )
  770. return SCHED_NONE;
  771. if ( !HasCondition(COND_SEE_PLAYER) )
  772. return SCHED_ROLLERMINE_PATH_TO_PLAYER;
  773. if ( GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ) > ROLLERMINE_RETURN_TO_PLAYER_DIST )
  774. return SCHED_ROLLERMINE_ROLL_TO_PLAYER;
  775. return SCHED_NONE;
  776. }
  777. //-----------------------------------------------------------------------------
  778. //-----------------------------------------------------------------------------
  779. int CNPC_RollerMine::TranslateSchedule( int scheduleType )
  780. {
  781. switch( scheduleType )
  782. {
  783. case SCHED_IDLE_STAND:
  784. {
  785. int iSched = GetHackedIdleSchedule();
  786. if ( iSched != SCHED_NONE )
  787. return iSched;
  788. return SCHED_IDLE_STAND;
  789. }
  790. break;
  791. case SCHED_ALERT_STAND:
  792. {
  793. int iSched = GetHackedIdleSchedule();
  794. if ( iSched != SCHED_NONE )
  795. return iSched;
  796. return SCHED_ROLLERMINE_ALERT_STAND;
  797. }
  798. break;
  799. case SCHED_ROLLERMINE_RANGE_ATTACK1:
  800. if( HasCondition(COND_ENEMY_OCCLUDED) )
  801. {
  802. // Because of an unfortunate arrangement of cascading failing schedules, the rollermine
  803. // could end up here with instructions to drive towards the target, although the target is
  804. // not in sight. Nudge around randomly until we're back on the nodegraph.
  805. return SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES;
  806. }
  807. break;
  808. }
  809. return scheduleType;
  810. }
  811. #if 0
  812. #define ROLLERMINE_DETECTION_RADIUS 350
  813. //-----------------------------------------------------------------------------
  814. // Purpose:
  815. // Output : Returns true on success, false on failure.
  816. //-----------------------------------------------------------------------------
  817. bool CNPC_RollerMine::DetectedEnemyInProximity( void )
  818. {
  819. CBaseEntity *pEnt = NULL;
  820. CBaseEntity *pBestEnemy = NULL;
  821. float flBestDist = MAX_TRACE_LENGTH;
  822. while ( ( pEnt = gEntList.FindEntityInSphere( pEnt, GetAbsOrigin(), ROLLERMINE_DETECTION_RADIUS ) ) != NULL )
  823. {
  824. if ( IRelationType( pEnt ) != D_HT )
  825. continue;
  826. float distance = ( pEnt->GetAbsOrigin() - GetAbsOrigin() ).Length();
  827. if ( distance >= flBestDist )
  828. continue;
  829. pBestEnemy = pEnt;
  830. flBestDist = distance;
  831. }
  832. if ( pBestEnemy != NULL )
  833. {
  834. SetEnemy( pBestEnemy );
  835. return true;
  836. }
  837. return false;
  838. }
  839. #endif
  840. //-----------------------------------------------------------------------------
  841. // Purpose:
  842. // Input : *pSightEnt -
  843. // Output : Returns true on success, false on failure.
  844. //-----------------------------------------------------------------------------
  845. bool CNPC_RollerMine::QuerySeeEntity(CBaseEntity *pSightEnt, bool bOnlyHateOrFearIfNPC)
  846. {
  847. if ( IRelationType( pSightEnt ) == D_FR )
  848. {
  849. // Only see feared objects up close
  850. float flDist = (WorldSpaceCenter() - pSightEnt->WorldSpaceCenter()).LengthSqr();
  851. if ( flDist > ROLLERMINE_FEAR_DISTANCE )
  852. return false;
  853. }
  854. return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC);
  855. }
  856. //-----------------------------------------------------------------------------
  857. // Purpose:
  858. // Input :
  859. // Output :
  860. //-----------------------------------------------------------------------------
  861. void CNPC_RollerMine::StartTask( const Task_t *pTask )
  862. {
  863. switch( pTask->iTask )
  864. {
  865. case TASK_FACE_REASONABLE:
  866. case TASK_FACE_SAVEPOSITION:
  867. case TASK_FACE_LASTPOSITION:
  868. case TASK_FACE_TARGET:
  869. case TASK_FACE_AWAY_FROM_SAVEPOSITION:
  870. case TASK_FACE_HINTNODE:
  871. case TASK_FACE_ENEMY:
  872. case TASK_FACE_PLAYER:
  873. case TASK_FACE_PATH:
  874. case TASK_FACE_IDEAL:
  875. // This only applies to NPCs that aren't spheres with omnidirectional eyesight.
  876. TaskComplete();
  877. break;
  878. case TASK_ROLLERMINE_UNBURROW:
  879. {
  880. AddSolidFlags( FSOLID_NOT_SOLID );
  881. SetMoveType( MOVETYPE_NOCLIP );
  882. SetAbsVelocity( Vector( 0, 0, 256 ) );
  883. Open();
  884. trace_t tr;
  885. AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  886. if ( tr.fraction < 1.0f )
  887. {
  888. UTIL_CreateAntlionDust( tr.endpos + Vector(0,0,24), GetLocalAngles() );
  889. }
  890. }
  891. return;
  892. break;
  893. case TASK_ROLLERMINE_BURIED_WAIT:
  894. if ( HasCondition( COND_SEE_ENEMY ) )
  895. {
  896. TaskComplete();
  897. }
  898. break;
  899. case TASK_STOP_MOVING:
  900. //Stop turning
  901. m_RollerController.m_vecAngular = vec3_origin;
  902. TaskComplete();
  903. return;
  904. break;
  905. case TASK_WAIT_FOR_MOVEMENT:
  906. {
  907. // TASK_RUN_PATH and TASK_WALK_PATH work different on the rollermine and run until movement is done,
  908. // so movement is already complete when entering this task.
  909. TaskComplete();
  910. }
  911. break;
  912. case TASK_WALK_PATH:
  913. case TASK_RUN_PATH:
  914. {
  915. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  916. if ( pPhysicsObject == NULL )
  917. {
  918. assert(0);
  919. TaskFail("Roller lost internal physics object?");
  920. return;
  921. }
  922. pPhysicsObject->Wake();
  923. }
  924. break;
  925. case TASK_ROLLERMINE_CHARGE_ENEMY:
  926. case TASK_ROLLERMINE_RETURN_TO_PLAYER:
  927. {
  928. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  929. if ( pPhysicsObject == NULL )
  930. {
  931. assert(0);
  932. TaskFail("Roller lost internal physics object?");
  933. return;
  934. }
  935. pPhysicsObject->Wake();
  936. m_flChargeTime = gpGlobals->curtime;
  937. }
  938. break;
  939. case TASK_ROLLERMINE_GET_PATH_TO_FLEE:
  940. {
  941. // Find the nearest thing we're afraid of, and move away from it.
  942. float flNearest = ROLLERMINE_FEAR_DISTANCE;
  943. EHANDLE hNearestEnemy = NULL;
  944. AIEnemiesIter_t iter;
  945. for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) )
  946. {
  947. CBaseEntity *pEnemy = pEMemory->hEnemy;
  948. if ( !pEnemy || !pEnemy->IsAlive() )
  949. continue;
  950. if ( IRelationType( pEnemy ) != D_FR )
  951. continue;
  952. float flDist = (WorldSpaceCenter() - pEnemy->WorldSpaceCenter()).LengthSqr();
  953. if ( flDist < flNearest )
  954. {
  955. flNearest = flDist;
  956. hNearestEnemy = pEnemy;
  957. }
  958. }
  959. if ( !hNearestEnemy )
  960. {
  961. TaskFail("Couldn't find nearest feared object.");
  962. break;
  963. }
  964. GetMotor()->SetIdealYawToTarget( hNearestEnemy->WorldSpaceCenter() );
  965. ChainStartTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
  966. }
  967. break;
  968. case TASK_ROLLERMINE_NUDGE_TOWARDS_NODES:
  969. {
  970. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  971. if( pPhysicsObject )
  972. {
  973. // Try a few times to find a direction to shove ourself
  974. for( int i = 0 ; i < 4 ; i++ )
  975. {
  976. int x,y;
  977. x = random->RandomInt( -1, 1 );
  978. y = random->RandomInt( -1, 1 );
  979. Vector vecNudge(x, y, 0.0f);
  980. trace_t tr;
  981. // Try to move in a direction with a couple of feet of clearance.
  982. UTIL_TraceLine( WorldSpaceCenter(), WorldSpaceCenter() + vecNudge * 24.0f, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  983. if( tr.fraction == 1.0 )
  984. {
  985. vecNudge *= (pPhysicsObject->GetMass() * 75.0f);
  986. vecNudge += Vector(0,0,pPhysicsObject->GetMass() * 75.0f);
  987. pPhysicsObject->ApplyForceCenter( vecNudge );
  988. break;
  989. }
  990. }
  991. }
  992. TaskComplete();
  993. }
  994. break;
  995. case TASK_ROLLERMINE_POWERDOWN:
  996. break;
  997. default:
  998. BaseClass::StartTask( pTask );
  999. break;
  1000. }
  1001. }
  1002. //-----------------------------------------------------------------------------
  1003. // Purpose:
  1004. // Input :
  1005. // Output :
  1006. //-----------------------------------------------------------------------------
  1007. void CNPC_RollerMine::RunTask( const Task_t *pTask )
  1008. {
  1009. switch( pTask->iTask )
  1010. {
  1011. case TASK_ROLLERMINE_UNBURROW:
  1012. {
  1013. Vector vCenter = WorldSpaceCenter();
  1014. // Robin: HACK: Bloat the rollermine check to catch the model switch (roller.mdl->roller_spikes.mdl)
  1015. trace_t tr;
  1016. AI_TraceHull( vCenter, vCenter, Vector(-16,-16,-16), Vector(16,16,16), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
  1017. if ( tr.fraction == 1 && tr.allsolid != 1 && (tr.startsolid != 1) )
  1018. {
  1019. if ( BecomePhysical() )
  1020. {
  1021. Hop( 256 );
  1022. m_bBuried = false;
  1023. TaskComplete();
  1024. SetIdealState( NPC_STATE_ALERT );
  1025. }
  1026. }
  1027. }
  1028. return;
  1029. break;
  1030. case TASK_ROLLERMINE_BURIED_WAIT:
  1031. if ( HasCondition( COND_SEE_ENEMY ) || HasCondition( COND_LIGHT_DAMAGE ) )
  1032. {
  1033. TaskComplete();
  1034. }
  1035. break;
  1036. case TASK_ROLLERMINE_GET_PATH_TO_FLEE:
  1037. {
  1038. ChainRunTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
  1039. }
  1040. break;
  1041. case TASK_WAIT_FOR_MOVEMENT:
  1042. {
  1043. // TASK_RUN_PATH and TASK_WALK_PATH work different on the rollermine and run until movement is done,
  1044. // so movement is already complete when entering this task.
  1045. TaskComplete();
  1046. }
  1047. break;
  1048. case TASK_RUN_PATH:
  1049. case TASK_WALK_PATH:
  1050. if ( m_bHeld || m_hVehicleStuckTo )
  1051. {
  1052. TaskFail( "Player interrupted by grabbing" );
  1053. break;
  1054. }
  1055. // If we were fleeing, but we've lost sight of the thing scaring us, stop
  1056. if ( IsCurSchedule(SCHED_ROLLERMINE_FLEE) && !HasCondition( COND_SEE_FEAR ) )
  1057. {
  1058. TaskComplete();
  1059. break;
  1060. }
  1061. if ( !GetNavigator()->IsGoalActive() )
  1062. {
  1063. TaskComplete();
  1064. return;
  1065. }
  1066. // Start turning early
  1067. if( (GetLocalOrigin() - GetNavigator()->GetCurWaypointPos() ).Length() <= 64 )
  1068. {
  1069. if( GetNavigator()->CurWaypointIsGoal() )
  1070. {
  1071. // Hit the brakes a bit.
  1072. float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() );
  1073. Vector vecRight;
  1074. AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );
  1075. m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, -m_flForwardSpeed * 5 );
  1076. TaskComplete();
  1077. return;
  1078. }
  1079. GetNavigator()->AdvancePath();
  1080. }
  1081. {
  1082. float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() );
  1083. Vector vecRight;
  1084. Vector vecToPath; // points at the path
  1085. AngleVectors( QAngle( 0, yaw, 0 ), &vecToPath, &vecRight, NULL );
  1086. // figure out if the roller is turning. If so, cut the throttle a little.
  1087. float flDot;
  1088. Vector vecVelocity;
  1089. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  1090. if ( pPhysicsObject == NULL )
  1091. {
  1092. assert(0);
  1093. TaskFail("Roller lost internal physics object?");
  1094. return;
  1095. }
  1096. pPhysicsObject->GetVelocity( &vecVelocity, NULL );
  1097. VectorNormalize( vecVelocity );
  1098. vecVelocity.z = 0;
  1099. flDot = DotProduct( vecVelocity, vecToPath );
  1100. m_RollerController.m_vecAngular = vec3_origin;
  1101. if( flDot > 0.25 && flDot < 0.7 )
  1102. {
  1103. // Feed a little torque backwards into the axis perpendicular to the velocity.
  1104. // This will help get rid of momentum that would otherwise make us overshoot our goal.
  1105. Vector vecCompensate;
  1106. vecCompensate.x = vecVelocity.y;
  1107. vecCompensate.y = -vecVelocity.x;
  1108. vecCompensate.z = 0;
  1109. m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
  1110. }
  1111. if( m_bHackedByAlyx )
  1112. {
  1113. // Move faster.
  1114. m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * 2.0f );
  1115. }
  1116. else
  1117. {
  1118. m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed );
  1119. }
  1120. }
  1121. break;
  1122. case TASK_ROLLERMINE_CHARGE_ENEMY:
  1123. {
  1124. if ( !GetEnemy() )
  1125. {
  1126. TaskFail( FAIL_NO_ENEMY );
  1127. break;
  1128. }
  1129. if ( m_bHeld || m_hVehicleStuckTo )
  1130. {
  1131. TaskComplete();
  1132. break;
  1133. }
  1134. CBaseEntity *pEnemy = GetEnemy();
  1135. Vector vecTargetPosition = pEnemy->GetAbsOrigin();
  1136. // If we're chasing a vehicle, try and get ahead of it
  1137. if ( EnemyInVehicle() )
  1138. {
  1139. CBaseCombatCharacter *pCCEnemy = pEnemy->MyCombatCharacterPointer();
  1140. float flT;
  1141. // Project it's velocity and find our closest point on that line. Do it all in 2d space.
  1142. Vector vecVehicleVelocity = pCCEnemy->GetVehicleEntity()->GetSmoothedVelocity();
  1143. Vector vecProjected = vecTargetPosition + (vecVehicleVelocity * 1.0);
  1144. Vector2D vecProjected2D( vecProjected.x, vecProjected.y );
  1145. Vector2D vecTargetPosition2D( vecTargetPosition.x, vecTargetPosition.y );
  1146. Vector2D vecOrigin2D( GetAbsOrigin().x, GetAbsOrigin().y );
  1147. Vector2D vecIntercept2D;
  1148. CalcClosestPointOnLine2D( vecOrigin2D, vecTargetPosition2D, vecProjected2D, vecIntercept2D, &flT );
  1149. Vector vecIntercept( vecIntercept2D.x, vecIntercept2D.y, GetAbsOrigin().z );
  1150. //NDebugOverlay::Line( vecTargetPosition, vecProjected, 0,255,0, true, 0.1 );
  1151. // If we're ahead of the line somewhere, try to intercept
  1152. if ( flT > 0 )
  1153. {
  1154. // If it's beyond the end of the intercept line, just move towards the end of the line
  1155. if ( flT > 1 )
  1156. {
  1157. vecIntercept.x = vecProjected.x;
  1158. vecIntercept.y = vecProjected.y;
  1159. }
  1160. // If we're closer to the intercept point than to the vehicle, move towards the intercept
  1161. if ( (GetAbsOrigin() - vecTargetPosition).LengthSqr() > (GetAbsOrigin() - vecIntercept).LengthSqr() )
  1162. {
  1163. //NDebugOverlay::Box( vecIntercept, -Vector(20,20,20), Vector(20,20,20), 255,0,0, 0.1, 0.1 );
  1164. // Only use this position if it's clear
  1165. if ( enginetrace->GetPointContents( vecIntercept ) != CONTENTS_SOLID )
  1166. {
  1167. vecTargetPosition = vecIntercept;
  1168. }
  1169. }
  1170. }
  1171. //NDebugOverlay::Box( vecTargetPosition, -Vector(20,20,20), Vector(20,20,20), 255,255,255, 0.1, 0.1 );
  1172. }
  1173. float flTorqueFactor;
  1174. Vector vecToTarget = vecTargetPosition - GetLocalOrigin();
  1175. float yaw = UTIL_VecToYaw( vecToTarget );
  1176. Vector vecRight;
  1177. AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );
  1178. //NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()), 0,255,0, true, 0.1 );
  1179. float flDot;
  1180. // Figure out whether to continue the charge.
  1181. // (Have I overrun the target?)
  1182. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  1183. if ( pPhysicsObject == NULL )
  1184. {
  1185. // Assert(0);
  1186. TaskFail("Roller lost internal physics object?");
  1187. return;
  1188. }
  1189. Vector vecVelocity;
  1190. pPhysicsObject->GetVelocity( &vecVelocity, NULL );
  1191. VectorNormalize( vecVelocity );
  1192. VectorNormalize( vecToTarget );
  1193. flDot = DotProduct( vecVelocity, vecToTarget );
  1194. // more torque the longer the roller has been going.
  1195. flTorqueFactor = 1 + (gpGlobals->curtime - m_flChargeTime) * 2;
  1196. float flMaxTorque = ROLLERMINE_MAX_TORQUE_FACTOR;
  1197. // Friendly rollermines go a little slower
  1198. if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
  1199. {
  1200. flMaxTorque *= 0.75;
  1201. }
  1202. if( flTorqueFactor < 1 )
  1203. {
  1204. flTorqueFactor = 1;
  1205. }
  1206. else if( flTorqueFactor > flMaxTorque)
  1207. {
  1208. flTorqueFactor = flMaxTorque;
  1209. }
  1210. Vector vecCompensate;
  1211. vecCompensate.x = vecVelocity.y;
  1212. vecCompensate.y = -vecVelocity.x;
  1213. vecCompensate.z = 0;
  1214. VectorNormalize( vecCompensate );
  1215. m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
  1216. m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor );
  1217. // Taunt when I get closer
  1218. if( !(m_iSoundEventFlags & ROLLERMINE_SE_TAUNT) && UTIL_DistApprox( GetLocalOrigin(), vecTargetPosition ) <= 400 )
  1219. {
  1220. m_iSoundEventFlags |= ROLLERMINE_SE_TAUNT; // Don't repeat.
  1221. EmitSound( "NPC_RollerMine.Taunt" );
  1222. }
  1223. // Jump earlier when chasing a vehicle
  1224. float flThreshold = ROLLERMINE_OPEN_THRESHOLD;
  1225. if ( EnemyInVehicle() )
  1226. {
  1227. flThreshold = ROLLERMINE_VEHICLE_OPEN_THRESHOLD;
  1228. }
  1229. // Open the spikes if i'm close enough to cut the enemy!!
  1230. if( ( m_bIsOpen == false ) && ( ( UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() ) <= flThreshold ) || !IsActive() ) )
  1231. {
  1232. Open();
  1233. }
  1234. else if ( m_bIsOpen )
  1235. {
  1236. float flDistance = UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() );
  1237. if ( flDistance >= flThreshold )
  1238. {
  1239. // Otherwise close them if the enemy is getting away!
  1240. Close();
  1241. }
  1242. else if ( EnemyInVehicle() && flDistance < ROLLERMINE_VEHICLE_HOP_THRESHOLD )
  1243. {
  1244. // Keep trying to hop when we're ramming a vehicle, so we're visible to the player
  1245. if ( vecVelocity.x != 0 && vecVelocity.y != 0 && flTorqueFactor > 3 && flDot > 0.0 )
  1246. {
  1247. Hop( 300 );
  1248. }
  1249. }
  1250. }
  1251. // If we drive past, close the blades and make a new plan.
  1252. if ( !EnemyInVehicle() )
  1253. {
  1254. if( vecVelocity.x != 0 && vecVelocity.y != 0 )
  1255. {
  1256. if( gpGlobals->curtime - m_flChargeTime > 1.0 && flTorqueFactor > 1 && flDot < 0.0 )
  1257. {
  1258. if( m_bIsOpen )
  1259. {
  1260. Close();
  1261. }
  1262. TaskComplete();
  1263. }
  1264. }
  1265. }
  1266. }
  1267. break;
  1268. case TASK_ROLLERMINE_RETURN_TO_PLAYER:
  1269. {
  1270. if ( ConditionsGathered() && !HasCondition(COND_SEE_PLAYER) )
  1271. {
  1272. TaskFail( FAIL_NO_PLAYER );
  1273. return;
  1274. }
  1275. CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );
  1276. if ( !pPlayer || m_bHeld || m_hVehicleStuckTo )
  1277. {
  1278. TaskFail( FAIL_NO_TARGET );
  1279. return;
  1280. }
  1281. Vector vecTargetPosition = pPlayer->GetAbsOrigin();
  1282. float flTorqueFactor;
  1283. Vector vecToTarget = vecTargetPosition - GetLocalOrigin();
  1284. float yaw = UTIL_VecToYaw( vecToTarget );
  1285. Vector vecRight;
  1286. AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );
  1287. float flDot;
  1288. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  1289. if ( pPhysicsObject == NULL )
  1290. {
  1291. TaskFail("Roller lost internal physics object?");
  1292. return;
  1293. }
  1294. Vector vecVelocity;
  1295. pPhysicsObject->GetVelocity( &vecVelocity, NULL );
  1296. VectorNormalize( vecVelocity );
  1297. VectorNormalize( vecToTarget );
  1298. flDot = DotProduct( vecVelocity, vecToTarget );
  1299. // more torque the longer the roller has been going.
  1300. flTorqueFactor = 1 + (gpGlobals->curtime - m_flChargeTime) * 2;
  1301. float flMaxTorque = ROLLERMINE_MAX_TORQUE_FACTOR * 0.75;
  1302. if( flTorqueFactor < 1 )
  1303. {
  1304. flTorqueFactor = 1;
  1305. }
  1306. else if( flTorqueFactor > flMaxTorque)
  1307. {
  1308. flTorqueFactor = flMaxTorque;
  1309. }
  1310. Vector vecCompensate;
  1311. vecCompensate.x = vecVelocity.y;
  1312. vecCompensate.y = -vecVelocity.x;
  1313. vecCompensate.z = 0;
  1314. VectorNormalize( vecCompensate );
  1315. m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
  1316. m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor );
  1317. // Once we're near the player, slow & stop
  1318. if ( GetAbsOrigin().DistToSqr( vecTargetPosition ) < (ROLLERMINE_RETURN_TO_PLAYER_DIST*2.0) )
  1319. {
  1320. TaskComplete();
  1321. }
  1322. }
  1323. break;
  1324. case TASK_ROLLERMINE_POWERDOWN:
  1325. {
  1326. if ( m_flPowerDownTime <= gpGlobals->curtime )
  1327. {
  1328. m_flNextHop = gpGlobals->curtime;
  1329. m_flPowerDownTime = gpGlobals->curtime + RandomFloat( 0.3, 0.9 );
  1330. EmitSound( "NPC_RollerMine.Hurt" );
  1331. CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 0.5f, this );
  1332. if ( m_bIsOpen == false )
  1333. {
  1334. Open();
  1335. }
  1336. else
  1337. {
  1338. Close();
  1339. }
  1340. }
  1341. if ( m_flPowerDownDetonateTime <= gpGlobals->curtime )
  1342. {
  1343. SetThink( &CNPC_RollerMine::PreDetonate );
  1344. SetNextThink( gpGlobals->curtime + 0.5f );
  1345. }
  1346. // No TaskComplete() here, because the task will never complete. The rollermine will explode.
  1347. }
  1348. break;
  1349. default:
  1350. BaseClass::RunTask( pTask );
  1351. break;
  1352. }
  1353. }
  1354. //-----------------------------------------------------------------------------
  1355. // Purpose:
  1356. // Input :
  1357. // Output :
  1358. //-----------------------------------------------------------------------------
  1359. void CNPC_RollerMine::Open( void )
  1360. {
  1361. // Friendly rollers cannot open
  1362. if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
  1363. return;
  1364. if ( m_bIsOpen == false )
  1365. {
  1366. SetModel( "models/roller_spikes.mdl" );
  1367. SetRollerSkin();
  1368. EmitSound( "NPC_RollerMine.OpenSpikes" );
  1369. SetTouch( &CNPC_RollerMine::ShockTouch );
  1370. m_bIsOpen = true;
  1371. // Don't hop if we're constrained
  1372. if ( !m_pConstraint )
  1373. {
  1374. if ( EnemyInVehicle() )
  1375. {
  1376. Hop( 256 );
  1377. }
  1378. else if ( !GetEnemy() || GetEnemy()->Classify() != CLASS_BULLSEYE ) // Don't hop when attacking bullseyes
  1379. {
  1380. Hop( 128 );
  1381. }
  1382. }
  1383. }
  1384. }
  1385. //-----------------------------------------------------------------------------
  1386. // Purpose:
  1387. // Input :
  1388. // Output :
  1389. //-----------------------------------------------------------------------------
  1390. void CNPC_RollerMine::SetRollerSkin( void )
  1391. {
  1392. if ( m_bPowerDown == true )
  1393. {
  1394. m_nSkin = (int)ROLLER_SKIN_DETONATE;
  1395. }
  1396. else if ( m_bHackedByAlyx == true )
  1397. {
  1398. m_nSkin = (int)ROLLER_SKIN_FRIENDLY;
  1399. }
  1400. else
  1401. {
  1402. m_nSkin = (int)ROLLER_SKIN_REGULAR;
  1403. }
  1404. }
  1405. //-----------------------------------------------------------------------------
  1406. // Purpose:
  1407. // Input :
  1408. // Output :
  1409. //-----------------------------------------------------------------------------
  1410. void CNPC_RollerMine::Close( void )
  1411. {
  1412. // Not allowed to close while primed, because we're going to detonate on touch
  1413. if ( m_bIsPrimed )
  1414. return;
  1415. if ( m_bIsOpen && !IsShocking() )
  1416. {
  1417. SetModel( "models/roller.mdl" );
  1418. SetRollerSkin();
  1419. SetTouch( NULL );
  1420. m_bIsOpen = false;
  1421. m_iSoundEventFlags = ROLLERMINE_SE_CLEAR;
  1422. }
  1423. }
  1424. //-----------------------------------------------------------------------------
  1425. // Purpose:
  1426. // Input :
  1427. // Output :
  1428. //-----------------------------------------------------------------------------
  1429. void CNPC_RollerMine::SpikeTouch( CBaseEntity *pOther )
  1430. {
  1431. /*
  1432. if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
  1433. return;
  1434. if ( m_bHeld )
  1435. return;
  1436. if ( pOther->IsPlayer() )
  1437. return;
  1438. if ( pOther->m_takedamage != DAMAGE_YES )
  1439. return;
  1440. // If we just hit a breakable glass object, don't explode. We want to blow through it.
  1441. CBreakable *pBreakable = dynamic_cast<CBreakable*>(pOther);
  1442. if ( pBreakable && pBreakable->GetMaterialType() == matGlass )
  1443. return;
  1444. Explode();
  1445. EmitSound( "NPC_RollerMine.Warn" );
  1446. */
  1447. //FIXME: Either explode within certain rules, never explode, or just shock the hit victim
  1448. }
  1449. //-----------------------------------------------------------------------------
  1450. // Purpose:
  1451. //-----------------------------------------------------------------------------
  1452. void CNPC_RollerMine::CloseTouch( CBaseEntity *pOther )
  1453. {
  1454. if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
  1455. return;
  1456. if ( IsShocking() )
  1457. return;
  1458. bool bOtherIsDead = ( pOther->MyNPCPointer() && !pOther->MyNPCPointer()->IsAlive() );
  1459. bool bOtherIsNotarget = ( ( pOther->GetFlags() & FL_NOTARGET ) != 0 );
  1460. if ( !bOtherIsDead && !bOtherIsNotarget )
  1461. {
  1462. Disposition_t disp = IRelationType(pOther);
  1463. if ( (disp == D_HT || disp == D_FR) )
  1464. {
  1465. ShockTouch( pOther );
  1466. return;
  1467. }
  1468. }
  1469. Close();
  1470. }
  1471. //-----------------------------------------------------------------------------
  1472. // Purpose:
  1473. //-----------------------------------------------------------------------------
  1474. void CNPC_RollerMine::EmbedTouch( CBaseEntity *pOther )
  1475. {
  1476. if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
  1477. return;
  1478. m_bEmbedOnGroundImpact = false;
  1479. // Did we hit the world?
  1480. if ( pOther->entindex() == 0 )
  1481. {
  1482. m_bBuried = true;
  1483. trace_t tr;
  1484. Bury( &tr );
  1485. // Destroy out physics object and become static
  1486. VPhysicsDestroyObject();
  1487. CreateVPhysics();
  1488. // Drop a decal on the ground where we impacted
  1489. UTIL_DecalTrace( &tr, "Rollermine.Crater" );
  1490. // Make some dust
  1491. UTIL_CreateAntlionDust( tr.endpos, GetLocalAngles() );
  1492. }
  1493. // Don't try and embed again
  1494. SetTouch( NULL );
  1495. }
  1496. //-----------------------------------------------------------------------------
  1497. // Purpose:
  1498. //-----------------------------------------------------------------------------
  1499. bool CNPC_RollerMine::IsPlayerVehicle( CBaseEntity *pEntity )
  1500. {
  1501. IServerVehicle *pVehicle = pEntity->GetServerVehicle();
  1502. if ( pVehicle )
  1503. {
  1504. CBasePlayer *pPlayer = ToBasePlayer( pVehicle->GetPassenger() );
  1505. if ( pPlayer != NULL )
  1506. {
  1507. Disposition_t disp = IRelationType(pPlayer);
  1508. if ( disp == D_HT || disp == D_FR )
  1509. return true;
  1510. }
  1511. }
  1512. return false;
  1513. }
  1514. //-----------------------------------------------------------------------------
  1515. // Purpose:
  1516. // Input : *pVictim -
  1517. // Output : float
  1518. //-----------------------------------------------------------------------------
  1519. float CNPC_RollerMine::GetAttackDamageScale( CBaseEntity *pVictim )
  1520. {
  1521. // If we're friendly, don't damage players or player-friendly NPCs, even with collisions
  1522. if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
  1523. {
  1524. if ( pVictim->IsPlayer() )
  1525. return 0;
  1526. if ( pVictim->MyNPCPointer() )
  1527. {
  1528. // If we don't hate the player, we're immune
  1529. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  1530. if ( pPlayer && pVictim->MyNPCPointer()->IRelationType( pPlayer ) != D_HT )
  1531. return 0.0;
  1532. }
  1533. }
  1534. return BaseClass::GetAttackDamageScale( pVictim );
  1535. }
  1536. //-----------------------------------------------------------------------------
  1537. // Purpose:
  1538. // Input : *pOther -
  1539. //-----------------------------------------------------------------------------
  1540. void CNPC_RollerMine::ShockTarget( CBaseEntity *pOther )
  1541. {
  1542. CBeam *pBeam;
  1543. if( m_bHackedByAlyx )
  1544. {
  1545. pBeam = CBeam::BeamCreate( "sprites/rollermine_shock_yellow.vmt", 4 );
  1546. }
  1547. else
  1548. {
  1549. pBeam = CBeam::BeamCreate( "sprites/rollermine_shock.vmt", 4 );
  1550. }
  1551. int startAttach = -1;
  1552. CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pOther);
  1553. if ( pBeam != NULL )
  1554. {
  1555. pBeam->EntsInit( pOther, this );
  1556. if ( pAnimating && pAnimating->GetModel() )
  1557. {
  1558. startAttach = pAnimating->LookupAttachment("beam_damage" );
  1559. pBeam->SetStartAttachment( startAttach );
  1560. }
  1561. // Change this up a little for first person hits
  1562. if ( pOther->IsPlayer() )
  1563. {
  1564. pBeam->SetEndWidth( 8 );
  1565. pBeam->SetNoise( 4 );
  1566. pBeam->LiveForTime( 0.2f );
  1567. }
  1568. else
  1569. {
  1570. pBeam->SetEndWidth( 16 );
  1571. pBeam->SetNoise( 16 );
  1572. pBeam->LiveForTime( 0.5f );
  1573. }
  1574. pBeam->SetEndAttachment( 1 );
  1575. pBeam->SetWidth( 1 );
  1576. pBeam->SetBrightness( 255 );
  1577. pBeam->SetColor( 255, 255, 255 );
  1578. pBeam->RelinkBeam();
  1579. }
  1580. Vector shockPos = pOther->WorldSpaceCenter();
  1581. if ( startAttach > 0 && pAnimating )
  1582. {
  1583. pAnimating->GetAttachment( startAttach, shockPos );
  1584. }
  1585. Vector shockDir = ( GetAbsOrigin() - shockPos );
  1586. VectorNormalize( shockDir );
  1587. CPVSFilter filter( shockPos );
  1588. te->GaussExplosion( filter, 0.0f, shockPos, shockDir, 0 );
  1589. }
  1590. //-----------------------------------------------------------------------------
  1591. // Purpose:
  1592. //-----------------------------------------------------------------------------
  1593. void CNPC_RollerMine::NotifyInteraction( CAI_BaseNPC *pUser )
  1594. {
  1595. // For now, turn green so we can tell who is hacked.
  1596. m_bHackedByAlyx = true;
  1597. SetRollerSkin();
  1598. GetEnemies()->SetFreeKnowledgeDuration( 30.0f );
  1599. // Play the hax0red sound
  1600. EmitSound( "NPC_RollerMine.Reprogram" );
  1601. // Force the rollermine open here. At very least, this ensures that the
  1602. // correct, smaller bounding box is recomputed around it.
  1603. Open();
  1604. }
  1605. //-----------------------------------------------------------------------------
  1606. // Purpose:
  1607. //-----------------------------------------------------------------------------
  1608. void CNPC_RollerMine::ShockTouch( CBaseEntity *pOther )
  1609. {
  1610. if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
  1611. return;
  1612. if ( m_bHeld || m_hVehicleStuckTo || gpGlobals->curtime < m_flShockTime )
  1613. return;
  1614. // error?
  1615. Assert( !m_bIsPrimed );
  1616. Disposition_t disp = IRelationType(pOther);
  1617. // Ignore anyone that I'm friendly or neutral to.
  1618. if( disp != D_HT && disp != D_FR)
  1619. return;
  1620. IPhysicsObject *pPhysics = VPhysicsGetObject();
  1621. // Calculate a collision force
  1622. Vector impulse = WorldSpaceCenter() - pOther->WorldSpaceCenter();
  1623. impulse.z = 0;
  1624. VectorNormalize( impulse );
  1625. impulse.z = 0.75;
  1626. VectorNormalize( impulse );
  1627. impulse *= 600;
  1628. // Stun the roller
  1629. m_flActiveTime = gpGlobals->curtime + GetStunDelay();
  1630. // If we're a 'friendly' rollermine, just push the player a bit
  1631. if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
  1632. {
  1633. if ( pOther->IsPlayer() )
  1634. {
  1635. Vector vecForce = -impulse * 0.5;
  1636. pOther->ApplyAbsVelocityImpulse( vecForce );
  1637. }
  1638. return;
  1639. }
  1640. // jump up at a 30 degree angle away from the guy we hit
  1641. SetTouch( &CNPC_RollerMine::CloseTouch );
  1642. Vector vel;
  1643. pPhysics->SetVelocity( &impulse, NULL );
  1644. EmitSound( "NPC_RollerMine.Shock" );
  1645. // Do a shock effect
  1646. ShockTarget( pOther );
  1647. m_flShockTime = gpGlobals->curtime + 1.25;
  1648. // Calculate physics force
  1649. Vector out;
  1650. pOther->CollisionProp()->CalcNearestPoint( WorldSpaceCenter(), &out );
  1651. Vector vecForce = ( -impulse * pPhysics->GetMass() * 10 );
  1652. CTakeDamageInfo info( this, this, vecForce, out, sk_rollermine_shock.GetFloat(), DMG_SHOCK );
  1653. if( FClassnameIs( pOther, "npc_combine_s" ) )
  1654. {
  1655. if( pOther->GetHealth() <= (pOther->GetMaxHealth() / 2) )
  1656. {
  1657. // Instant special death for a combine soldier who has less than half health.
  1658. Vector vecDamageForce = pOther->WorldSpaceCenter() - WorldSpaceCenter();
  1659. VectorNormalize( vecDamageForce );
  1660. IPhysicsObject *pPhysics = pOther->VPhysicsGetObject();
  1661. if( pPhysics )
  1662. {
  1663. vecDamageForce *= (pPhysics->GetMass() * 200.0f);
  1664. // Slam Z component with some good, reliable upwards velocity.
  1665. vecDamageForce.z = pPhysics->GetMass() * 200.0f;
  1666. }
  1667. pOther->MyCombatCharacterPointer()->BecomeRagdollBoogie( this, vecDamageForce, 5.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL );
  1668. return;
  1669. }
  1670. else
  1671. {
  1672. info.SetDamage( pOther->GetMaxHealth()/2 );
  1673. }
  1674. }
  1675. pOther->TakeDamage( info );
  1676. // Knock players back a bit
  1677. if ( pOther->IsPlayer() )
  1678. {
  1679. vecForce = -impulse;
  1680. pOther->ApplyAbsVelocityImpulse( vecForce );
  1681. }
  1682. }
  1683. //-----------------------------------------------------------------------------
  1684. // Purpose:
  1685. //-----------------------------------------------------------------------------
  1686. void CNPC_RollerMine::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  1687. {
  1688. // Make sure we don't keep hitting the same entity
  1689. int otherIndex = !index;
  1690. CBaseEntity *pOther = pEvent->pEntities[otherIndex];
  1691. if ( pEvent->deltaCollisionTime < 0.5 && (pOther == this) )
  1692. return;
  1693. BaseClass::VPhysicsCollision( index, pEvent );
  1694. // If we've just hit a vehicle, we want to stick to it
  1695. if ( m_bHeld || m_hVehicleStuckTo || !IsPlayerVehicle( pOther ) )
  1696. {
  1697. // Are we supposed to be embedding ourselves?
  1698. if ( m_bEmbedOnGroundImpact )
  1699. {
  1700. // clear the flag so we don't queue more than once
  1701. m_bEmbedOnGroundImpact = false;
  1702. // call this when physics is done
  1703. g_PostSimulationQueue.QueueCall( this, &CNPC_RollerMine::EmbedTouch, pOther );
  1704. }
  1705. return;
  1706. }
  1707. StickToVehicle( pOther );
  1708. }
  1709. //-----------------------------------------------------------------------------
  1710. // Purpose:
  1711. // Input : *pOther -
  1712. //-----------------------------------------------------------------------------
  1713. void CNPC_RollerMine::StickToVehicle( CBaseEntity *pOther )
  1714. {
  1715. IPhysicsObject *pOtherPhysics = pOther->VPhysicsGetObject();
  1716. if ( !pOtherPhysics )
  1717. return;
  1718. // Don't stick to the wheels
  1719. if ( pOtherPhysics->GetCallbackFlags() & CALLBACK_IS_VEHICLE_WHEEL )
  1720. return;
  1721. // Destroy our constraint. This can happen if we had our constraint broken
  1722. // and we still haven't cleaned up our constraint.
  1723. UnstickFromVehicle();
  1724. // We've hit the vehicle that the player's in.
  1725. // Stick to it and slow it down.
  1726. m_hVehicleStuckTo = pOther;
  1727. IPhysicsObject *pPhysics = VPhysicsGetObject();
  1728. // Constrain us to the vehicle
  1729. constraint_fixedparams_t fixed;
  1730. fixed.Defaults();
  1731. fixed.InitWithCurrentObjectState( pOtherPhysics, pPhysics );
  1732. fixed.constraint.Defaults();
  1733. fixed.constraint.forceLimit = ImpulseScale( pPhysics->GetMass(), 200 );
  1734. fixed.constraint.torqueLimit = ImpulseScale( pPhysics->GetMass(), 800 );
  1735. m_pConstraint = physenv->CreateFixedConstraint( pOtherPhysics, pPhysics, NULL, fixed );
  1736. m_pConstraint->SetGameData( (void *)this );
  1737. // Kick the vehicle so the player knows we've arrived
  1738. Vector impulse = pOther->GetAbsOrigin() - GetAbsOrigin();
  1739. VectorNormalize( impulse );
  1740. impulse.z = -0.75;
  1741. VectorNormalize( impulse );
  1742. impulse *= 600;
  1743. Vector vecForce = impulse * pPhysics->GetMass() * 10;
  1744. pOtherPhysics->ApplyForceOffset( vecForce, GetAbsOrigin() );
  1745. // Get the velocity at the point we're sticking to
  1746. Vector vecVelocity;
  1747. pOtherPhysics->GetVelocityAtPoint( GetAbsOrigin(), &vecVelocity );
  1748. AngularImpulse angNone( 0.0f, 0.0f, 0.0f );
  1749. pPhysics->SetVelocity( &vecVelocity, &angNone );
  1750. // Make sure we're spiky
  1751. Open();
  1752. AnnounceArrivalToOthers( pOther );
  1753. // Also, jolt the vehicle sometime in the future
  1754. g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
  1755. }
  1756. //-----------------------------------------------------------------------------
  1757. // Purpose:
  1758. //-----------------------------------------------------------------------------
  1759. int CNPC_RollerMine::CountRollersOnMyVehicle( CUtlVector<CNPC_RollerMine*> *pRollerList )
  1760. {
  1761. CBaseEntity *entityList[64];
  1762. Vector range(256,256,256);
  1763. pRollerList->AddToTail( this );
  1764. int boxCount = UTIL_EntitiesInBox( entityList, ARRAYSIZE(entityList), GetAbsOrigin()-range, GetAbsOrigin()+range, FL_NPC );
  1765. for ( int i = 0; i < boxCount; i++ )
  1766. {
  1767. CAI_BaseNPC *pNPC = entityList[i]->MyNPCPointer();
  1768. if ( pNPC && pNPC->m_iClassname == m_iClassname && pNPC != this )
  1769. {
  1770. // Found another rollermine
  1771. CNPC_RollerMine *pMine = dynamic_cast<CNPC_RollerMine*>(pNPC);
  1772. Assert( pMine );
  1773. // Is he stuck to the same vehicle?
  1774. if ( pMine->GetVehicleStuckTo() == GetVehicleStuckTo() )
  1775. {
  1776. pRollerList->AddToTail( pMine );
  1777. }
  1778. }
  1779. }
  1780. return pRollerList->Count();
  1781. }
  1782. //-----------------------------------------------------------------------------
  1783. // Purpose: Tell other rollermines on the vehicle that I've just arrived
  1784. // Input : *pOther -
  1785. //-----------------------------------------------------------------------------
  1786. void CNPC_RollerMine::AnnounceArrivalToOthers( CBaseEntity *pOther )
  1787. {
  1788. // Now talk to any other rollermines stuck to the same vehicle
  1789. CUtlVector<CNPC_RollerMine*> aRollersOnVehicle;
  1790. int iRollers = CountRollersOnMyVehicle( &aRollersOnVehicle );
  1791. // Stop all rollers on the vehicle falling off due to the force of the arriving one
  1792. for ( int i = 0; i < iRollers; i++ )
  1793. {
  1794. aRollersOnVehicle[i]->PreventUnstickUntil( gpGlobals->curtime + 1 );
  1795. }
  1796. // See if we've got enough rollers on the vehicle to start being mean
  1797. /*
  1798. if ( iRollers >= ROLLERMINE_REQUIRED_TO_EXPLODE_VEHICLE )
  1799. {
  1800. // Alert the others
  1801. EmitSound( "NPC_RollerMine.ExplodeChirp" );
  1802. // Tell everyone to explode shortly
  1803. for ( int i = 0; i < iRollers; i++ )
  1804. {
  1805. variant_t emptyVariant;
  1806. g_EventQueue.AddEvent( aRollersOnVehicle[i], "RespondToExplodeChirp", RandomFloat(2,5), NULL, NULL );
  1807. }
  1808. }
  1809. else
  1810. {
  1811. */
  1812. // If there's other rollers on the vehicle, talk to them
  1813. if ( iRollers > 1 )
  1814. {
  1815. // Chirp to the others
  1816. EmitSound( "NPC_RollerMine.Chirp" );
  1817. // Tell the others to respond (skip first slot, because that's me)
  1818. for ( int i = 1; i < iRollers; i++ )
  1819. {
  1820. variant_t emptyVariant;
  1821. g_EventQueue.AddEvent( aRollersOnVehicle[i], "RespondToChirp", RandomFloat(2,3), NULL, NULL );
  1822. }
  1823. }
  1824. // }
  1825. }
  1826. //-----------------------------------------------------------------------------
  1827. // Purpose: Physics system has just told us our constraint has been broken
  1828. //-----------------------------------------------------------------------------
  1829. void CNPC_RollerMine::InputConstraintBroken( inputdata_t &inputdata )
  1830. {
  1831. // Prevent rollermines being dislodged right as they stick
  1832. if ( m_flPreventUnstickUntil > gpGlobals->curtime )
  1833. return;
  1834. // We can't delete it here safely
  1835. UnstickFromVehicle();
  1836. Close();
  1837. // dazed
  1838. m_RollerController.m_vecAngular.Init();
  1839. m_flActiveTime = gpGlobals->curtime + GetStunDelay();
  1840. }
  1841. //-----------------------------------------------------------------------------
  1842. // Purpose: Respond to another rollermine that's chirped at us
  1843. // Input : &inputdata -
  1844. //-----------------------------------------------------------------------------
  1845. void CNPC_RollerMine::InputRespondToChirp( inputdata_t &inputdata )
  1846. {
  1847. EmitSound( "NPC_RollerMine.ChirpRespond" );
  1848. }
  1849. //-----------------------------------------------------------------------------
  1850. // Purpose: Respond to another rollermine's signal to detonate
  1851. // Input : &inputdata -
  1852. //-----------------------------------------------------------------------------
  1853. void CNPC_RollerMine::InputRespondToExplodeChirp( inputdata_t &inputdata )
  1854. {
  1855. EmitSound( "NPC_RollerMine.ExplodeChirpRespond" );
  1856. Explode();
  1857. }
  1858. //-----------------------------------------------------------------------------
  1859. // Purpose: Apply a physics force to the vehicle we're in
  1860. // Input : &inputdata -
  1861. //-----------------------------------------------------------------------------
  1862. void CNPC_RollerMine::InputJoltVehicle( inputdata_t &inputdata )
  1863. {
  1864. Assert( GetVehicleStuckTo() );
  1865. // First, tell all rollers on the vehicle not to fall off
  1866. CUtlVector<CNPC_RollerMine*> aRollersOnVehicle;
  1867. int iRollers = CountRollersOnMyVehicle( &aRollersOnVehicle );
  1868. for ( int i = 0; i < iRollers; i++ )
  1869. {
  1870. aRollersOnVehicle[i]->PreventUnstickUntil( gpGlobals->curtime + 1 );
  1871. }
  1872. // Now smack the vehicle
  1873. Vector impulse = GetVehicleStuckTo()->GetAbsOrigin() - GetAbsOrigin();
  1874. VectorNormalize( impulse );
  1875. // Randomly apply a little vertical lift, to get the wheels off the ground
  1876. impulse.z = RandomFloat( 0.5, 1.0 );
  1877. VectorNormalize( impulse );
  1878. IPhysicsObject *pVehiclePhysics = GetVehicleStuckTo()->VPhysicsGetObject();
  1879. Vector vecForce = impulse * ImpulseScale( pVehiclePhysics->GetMass(), RandomFloat(150,250) );
  1880. pVehiclePhysics->ApplyForceOffset( vecForce, GetAbsOrigin() );
  1881. // Play sounds & effects
  1882. EmitSound( "NPC_RollerMine.JoltVehicle" );
  1883. // UNDONE: Good Zap effects
  1884. /*
  1885. CBeam *pBeam = CBeam::BeamCreate( "sprites/rollermine_shock.vmt", 4 );
  1886. if ( pBeam )
  1887. {
  1888. pBeam->EntsInit( GetVehicleStuckTo(), this );
  1889. CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>( GetVehicleStuckTo() );
  1890. if ( pAnimating )
  1891. {
  1892. int startAttach = pAnimating->LookupAttachment("beam_damage" );
  1893. pBeam->SetStartAttachment( startAttach );
  1894. }
  1895. pBeam->SetEndAttachment( 1 );
  1896. pBeam->SetWidth( 8 );
  1897. pBeam->SetEndWidth( 8 );
  1898. pBeam->SetBrightness( 255 );
  1899. pBeam->SetColor( 255, 255, 255 );
  1900. pBeam->LiveForTime( 0.5f );
  1901. pBeam->RelinkBeam();
  1902. pBeam->SetNoise( 30 );
  1903. }
  1904. */
  1905. ShockTarget( GetVehicleStuckTo() );
  1906. // Jolt again soon
  1907. g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
  1908. }
  1909. //-----------------------------------------------------------------------------
  1910. // Purpose:
  1911. // Input : &inputdata -
  1912. //-----------------------------------------------------------------------------
  1913. void CNPC_RollerMine::InputTurnOn( inputdata_t &inputdata )
  1914. {
  1915. m_RollerController.On();
  1916. m_bTurnedOn = true;
  1917. }
  1918. //-----------------------------------------------------------------------------
  1919. // Purpose:
  1920. // Input : &inputdata -
  1921. //-----------------------------------------------------------------------------
  1922. void CNPC_RollerMine::InputTurnOff( inputdata_t &inputdata )
  1923. {
  1924. m_RollerController.Off();
  1925. m_bTurnedOn = false;
  1926. StopLoopingSounds();
  1927. }
  1928. //-----------------------------------------------------------------------------
  1929. // Purpose:
  1930. // Input : &inputdata -
  1931. //-----------------------------------------------------------------------------
  1932. void CNPC_RollerMine::InputPowerdown( inputdata_t &inputdata )
  1933. {
  1934. m_bPowerDown = true;
  1935. m_flPowerDownTime = gpGlobals->curtime + RandomFloat( 0.1, 0.5 );
  1936. m_flPowerDownDetonateTime = m_flPowerDownTime + RandomFloat( 1.5, 4.0 );
  1937. ClearSchedule( "Received power down input" );
  1938. }
  1939. //-----------------------------------------------------------------------------
  1940. // Purpose: If we were stuck to a vehicle, remove ourselves
  1941. //-----------------------------------------------------------------------------
  1942. void CNPC_RollerMine::UnstickFromVehicle( void )
  1943. {
  1944. if ( m_pConstraint )
  1945. {
  1946. physenv->DestroyConstraint( m_pConstraint );
  1947. m_pConstraint = NULL;
  1948. }
  1949. // Cancel any pending jolt events
  1950. g_EventQueue.CancelEventOn( this, "JoltVehicle" );
  1951. m_hVehicleStuckTo = NULL;
  1952. }
  1953. //-----------------------------------------------------------------------------
  1954. // Purpose:
  1955. //-----------------------------------------------------------------------------
  1956. CBaseEntity *CNPC_RollerMine::GetVehicleStuckTo( void )
  1957. {
  1958. if ( !m_pConstraint )
  1959. return NULL;
  1960. return m_hVehicleStuckTo;
  1961. }
  1962. //-----------------------------------------------------------------------------
  1963. // Purpose:
  1964. // Input : *pPhysGunUser -
  1965. //-----------------------------------------------------------------------------
  1966. void CNPC_RollerMine::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  1967. {
  1968. // Are we just being punted?
  1969. if ( reason == PUNTED_BY_CANNON )
  1970. {
  1971. // Be stunned
  1972. m_flActiveTime = gpGlobals->curtime + GetStunDelay();
  1973. return;
  1974. }
  1975. //Stop turning
  1976. m_RollerController.m_vecAngular = vec3_origin;
  1977. UnstickFromVehicle();
  1978. m_OnPhysGunPickup.FireOutput( pPhysGunUser, this );
  1979. m_bHeld = true;
  1980. m_RollerController.Off();
  1981. EmitSound( "NPC_RollerMine.Held" );
  1982. }
  1983. //-----------------------------------------------------------------------------
  1984. // Purpose:
  1985. // Input : *pPhysGunUser -
  1986. //-----------------------------------------------------------------------------
  1987. void CNPC_RollerMine::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  1988. {
  1989. m_bHeld = false;
  1990. m_flActiveTime = gpGlobals->curtime + GetStunDelay();
  1991. m_RollerController.On();
  1992. // explode on contact if launched from the physgun
  1993. if ( Reason == LAUNCHED_BY_CANNON )
  1994. {
  1995. if ( m_bIsOpen )
  1996. {
  1997. //m_bIsPrimed = true;
  1998. SetTouch( &CNPC_RollerMine::SpikeTouch );
  1999. // enable world/prop touch too
  2000. VPhysicsGetObject()->SetCallbackFlags( VPhysicsGetObject()->GetCallbackFlags() | CALLBACK_GLOBAL_TOUCH|CALLBACK_GLOBAL_TOUCH_STATIC );
  2001. }
  2002. EmitSound( "NPC_RollerMine.Tossed" );
  2003. }
  2004. m_OnPhysGunDrop.FireOutput( pPhysGunUser, this );
  2005. }
  2006. //-----------------------------------------------------------------------------
  2007. // Purpose:
  2008. // Input : &info -
  2009. // Output : float
  2010. //-----------------------------------------------------------------------------
  2011. int CNPC_RollerMine::OnTakeDamage( const CTakeDamageInfo &info )
  2012. {
  2013. if ( !(info.GetDamageType() & DMG_BURN) )
  2014. {
  2015. if ( GetMoveType() == MOVETYPE_VPHYSICS )
  2016. {
  2017. AngularImpulse angVel;
  2018. angVel.Random( -400.0f, 400.0f );
  2019. VPhysicsGetObject()->AddVelocity( NULL, &angVel );
  2020. m_RollerController.m_vecAngular *= 0.8f;
  2021. VPhysicsTakeDamage( info );
  2022. }
  2023. SetCondition( COND_LIGHT_DAMAGE );
  2024. }
  2025. if ( info.GetDamageType() & (DMG_BURN|DMG_BLAST) )
  2026. {
  2027. if ( info.GetAttacker() && info.GetAttacker()->m_iClassname != m_iClassname )
  2028. {
  2029. SetThink( &CNPC_RollerMine::PreDetonate );
  2030. SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.5f ) );
  2031. }
  2032. else
  2033. {
  2034. // dazed
  2035. m_RollerController.m_vecAngular.Init();
  2036. m_flActiveTime = gpGlobals->curtime + GetStunDelay();
  2037. Hop( 300 );
  2038. }
  2039. }
  2040. return 0;
  2041. }
  2042. //-----------------------------------------------------------------------------
  2043. // Purpose: Causes the roller to hop into the air
  2044. //-----------------------------------------------------------------------------
  2045. void CNPC_RollerMine::Hop( float height )
  2046. {
  2047. if ( m_flNextHop > gpGlobals->curtime )
  2048. return;
  2049. if ( GetMoveType() == MOVETYPE_VPHYSICS )
  2050. {
  2051. IPhysicsObject *pPhysObj = VPhysicsGetObject();
  2052. pPhysObj->ApplyForceCenter( Vector(0,0,1) * height * pPhysObj->GetMass() );
  2053. AngularImpulse angVel;
  2054. angVel.Random( -400.0f, 400.0f );
  2055. pPhysObj->AddVelocity( NULL, &angVel );
  2056. m_flNextHop = gpGlobals->curtime + ROLLERMINE_HOP_DELAY;
  2057. }
  2058. }
  2059. //-----------------------------------------------------------------------------
  2060. // Purpose: Makes warning noise before actual explosion occurs
  2061. //-----------------------------------------------------------------------------
  2062. void CNPC_RollerMine::PreDetonate( void )
  2063. {
  2064. Hop( 300 );
  2065. SetTouch( NULL );
  2066. SetThink( &CNPC_RollerMine::Explode );
  2067. SetNextThink( gpGlobals->curtime + 0.5f );
  2068. EmitSound( "NPC_RollerMine.Hurt" );
  2069. }
  2070. //-----------------------------------------------------------------------------
  2071. // Purpose:
  2072. //-----------------------------------------------------------------------------
  2073. void CNPC_RollerMine::Explode( void )
  2074. {
  2075. m_takedamage = DAMAGE_NO;
  2076. //FIXME: Hack to make thrown mines more deadly and fun
  2077. float expDamage = m_bIsPrimed ? 100 : 25;
  2078. //If we've been hacked and we're blowing up cause we've been shut down then do moderate damage.
  2079. if ( m_bPowerDown == true )
  2080. {
  2081. expDamage = 50;
  2082. }
  2083. // Underwater explosion?
  2084. if ( UTIL_PointContents( GetAbsOrigin() ) & MASK_WATER )
  2085. {
  2086. CEffectData data;
  2087. data.m_vOrigin = WorldSpaceCenter();
  2088. data.m_flMagnitude = expDamage;
  2089. data.m_flScale = 128;
  2090. data.m_fFlags = ( SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE );
  2091. DispatchEffect( "WaterSurfaceExplosion", data );
  2092. }
  2093. else
  2094. {
  2095. ExplosionCreate( WorldSpaceCenter(), GetLocalAngles(), this, expDamage, 128, true );
  2096. }
  2097. CTakeDamageInfo info( this, this, 1, DMG_GENERIC );
  2098. Event_Killed( info );
  2099. // Remove myself a frame from now to avoid doing it in the middle of running AI
  2100. SetThink( &CNPC_RollerMine::SUB_Remove );
  2101. SetNextThink( gpGlobals->curtime );
  2102. }
  2103. const float MAX_ROLLING_SPEED = 720;
  2104. //-----------------------------------------------------------------------------
  2105. // Purpose:
  2106. //-----------------------------------------------------------------------------
  2107. float CNPC_RollerMine::RollingSpeed()
  2108. {
  2109. IPhysicsObject *pPhysics = VPhysicsGetObject();
  2110. if ( !m_hVehicleStuckTo && !m_bHeld && pPhysics && !pPhysics->IsAsleep() )
  2111. {
  2112. AngularImpulse angVel;
  2113. pPhysics->GetVelocity( NULL, &angVel );
  2114. float rollingSpeed = angVel.Length() - 90;
  2115. rollingSpeed = clamp( rollingSpeed, 1, MAX_ROLLING_SPEED );
  2116. rollingSpeed *= (1/MAX_ROLLING_SPEED);
  2117. return rollingSpeed;
  2118. }
  2119. return 0;
  2120. }
  2121. //-----------------------------------------------------------------------------
  2122. //-----------------------------------------------------------------------------
  2123. float CNPC_RollerMine::GetStunDelay()
  2124. {
  2125. if( m_bHackedByAlyx )
  2126. {
  2127. return 0.1f;
  2128. }
  2129. else
  2130. {
  2131. return sk_rollermine_stun_delay.GetFloat();
  2132. }
  2133. }
  2134. //-----------------------------------------------------------------------------
  2135. // Purpose: We've been dropped by a dropship. Embed in the ground if we land on it.
  2136. //-----------------------------------------------------------------------------
  2137. void CNPC_RollerMine::EmbedOnGroundImpact()
  2138. {
  2139. m_bEmbedOnGroundImpact = true;
  2140. SetTouch( &CNPC_RollerMine::EmbedTouch );
  2141. }
  2142. //-----------------------------------------------------------------------------
  2143. // Purpose:
  2144. //-----------------------------------------------------------------------------
  2145. void CNPC_RollerMine::PrescheduleThink()
  2146. {
  2147. // Are we underwater?
  2148. if ( UTIL_PointContents( GetAbsOrigin() ) & MASK_WATER )
  2149. {
  2150. // As soon as we're far enough underwater, detonate
  2151. Vector vecAboveMe = GetAbsOrigin() + Vector(0,0,64);
  2152. if ( UTIL_PointContents( vecAboveMe ) & MASK_WATER )
  2153. {
  2154. Explode();
  2155. return;
  2156. }
  2157. }
  2158. UpdateRollingSound();
  2159. UpdatePingSound();
  2160. BaseClass::PrescheduleThink();
  2161. }
  2162. //-----------------------------------------------------------------------------
  2163. // Purpose:
  2164. //-----------------------------------------------------------------------------
  2165. void CNPC_RollerMine::UpdateRollingSound()
  2166. {
  2167. if ( m_rollingSoundState == ROLL_SOUND_NOT_READY )
  2168. return;
  2169. rollingsoundstate_t soundState = ROLL_SOUND_OFF;
  2170. float rollingSpeed = RollingSpeed();
  2171. if ( rollingSpeed > 0 )
  2172. {
  2173. soundState = m_bIsOpen ? ROLL_SOUND_OPEN : ROLL_SOUND_CLOSED;
  2174. }
  2175. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2176. CSoundParameters params;
  2177. switch( soundState )
  2178. {
  2179. case ROLL_SOUND_CLOSED:
  2180. CBaseEntity::GetParametersForSound( "NPC_RollerMine.Roll", params, NULL );
  2181. break;
  2182. case ROLL_SOUND_OPEN:
  2183. CBaseEntity::GetParametersForSound( "NPC_RollerMine.RollWithSpikes", params, NULL );
  2184. break;
  2185. case ROLL_SOUND_OFF:
  2186. // no sound
  2187. break;
  2188. }
  2189. // start the new sound playing if necessary
  2190. if ( m_rollingSoundState != soundState )
  2191. {
  2192. StopRollingSound();
  2193. m_rollingSoundState = soundState;
  2194. if ( m_rollingSoundState == ROLL_SOUND_OFF )
  2195. return;
  2196. CPASAttenuationFilter filter( this );
  2197. m_pRollSound = controller.SoundCreate( filter, entindex(), params.channel, params.soundname, params.soundlevel );
  2198. controller.Play( m_pRollSound, params.volume, params.pitch );
  2199. m_rollingSoundState = soundState;
  2200. }
  2201. if ( m_pRollSound )
  2202. {
  2203. // for tuning
  2204. //DevMsg("SOUND: %s, VOL: %.1f\n", m_rollingSoundState == ROLL_SOUND_CLOSED ? "CLOSED" : "OPEN ", rollingSpeed );
  2205. controller.SoundChangePitch( m_pRollSound, params.pitchlow + (params.pitchhigh - params.pitchlow) * rollingSpeed, 0.1 );
  2206. controller.SoundChangeVolume( m_pRollSound, params.volume * rollingSpeed, 0.1 );
  2207. }
  2208. }
  2209. void CNPC_RollerMine::StopRollingSound()
  2210. {
  2211. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2212. controller.SoundDestroy( m_pRollSound );
  2213. m_pRollSound = NULL;
  2214. }
  2215. void CNPC_RollerMine::UpdatePingSound()
  2216. {
  2217. float pingSpeed = 0;
  2218. if ( m_bIsOpen && !IsShocking() && !m_bHeld )
  2219. {
  2220. CBaseEntity *pEnemy = GetEnemy();
  2221. if ( pEnemy )
  2222. {
  2223. pingSpeed = EnemyDistance( pEnemy );
  2224. pingSpeed = clamp( pingSpeed, 1, ROLLERMINE_OPEN_THRESHOLD );
  2225. pingSpeed *= (1.0f/ROLLERMINE_OPEN_THRESHOLD);
  2226. }
  2227. }
  2228. if ( pingSpeed > 0 )
  2229. {
  2230. pingSpeed = 1-pingSpeed;
  2231. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2232. CSoundParameters params;
  2233. CBaseEntity::GetParametersForSound( "NPC_RollerMine.Ping", params, NULL );
  2234. if ( !m_pPingSound )
  2235. {
  2236. CPASAttenuationFilter filter( this );
  2237. m_pPingSound = controller.SoundCreate( filter, entindex(), params.channel, params.soundname, params.soundlevel );
  2238. controller.Play( m_pPingSound, params.volume, 101 );
  2239. }
  2240. controller.SoundChangePitch( m_pPingSound, params.pitchlow + (params.pitchhigh - params.pitchlow) * pingSpeed, 0.1 );
  2241. controller.SoundChangeVolume( m_pPingSound, params.volume, 0.1 );
  2242. //DevMsg("PING: %.1f\n", pingSpeed );
  2243. }
  2244. else
  2245. {
  2246. StopPingSound();
  2247. }
  2248. }
  2249. void CNPC_RollerMine::StopPingSound()
  2250. {
  2251. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2252. controller.SoundDestroy( m_pPingSound );
  2253. m_pPingSound = NULL;
  2254. }
  2255. //-----------------------------------------------------------------------------
  2256. // Purpose:
  2257. //-----------------------------------------------------------------------------
  2258. void CNPC_RollerMine::StopLoopingSounds( void )
  2259. {
  2260. StopRollingSound();
  2261. StopPingSound();
  2262. BaseClass::StopLoopingSounds();
  2263. }
  2264. //-----------------------------------------------------------------------------
  2265. // Purpose:
  2266. // Input : *pEnemy -
  2267. // Output : Returns true on success, false on failure.
  2268. //-----------------------------------------------------------------------------
  2269. bool CNPC_RollerMine::IsValidEnemy( CBaseEntity *pEnemy )
  2270. {
  2271. // If the enemy's over the vehicle detection range, and it's not a player in a vehicle, ignore it
  2272. if ( pEnemy )
  2273. {
  2274. float flDistance = GetAbsOrigin().DistTo( pEnemy->GetAbsOrigin() );
  2275. if ( flDistance >= m_flSeeVehiclesOnlyBeyond )
  2276. {
  2277. // Handle vehicles
  2278. CBaseCombatCharacter *pCCEnemy = pEnemy->MyCombatCharacterPointer();
  2279. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  2280. {
  2281. // If we're buried, we only care when they're heading directly towards us
  2282. if ( m_bBuried )
  2283. return ( VehicleHeading( pCCEnemy->GetVehicle()->GetVehicleEnt() ) > DOT_20DEGREE );
  2284. // If we're not buried, chase him as long as he's not heading away from us
  2285. return ( VehicleHeading( pCCEnemy->GetVehicleEntity() ) > 0 );
  2286. }
  2287. return false;
  2288. }
  2289. // Never pick something I fear
  2290. if ( IRelationType( pEnemy ) == D_FR )
  2291. return false;
  2292. // Don't attack flying things.
  2293. if ( pEnemy->GetMoveType() == MOVETYPE_FLY )
  2294. return false;
  2295. }
  2296. return BaseClass::IsValidEnemy( pEnemy );
  2297. }
  2298. //-----------------------------------------------------------------------------
  2299. // Purpose:
  2300. //-----------------------------------------------------------------------------
  2301. bool CNPC_RollerMine::EnemyInVehicle( void )
  2302. {
  2303. // Clearly the enemy is not...
  2304. if ( GetEnemy() == NULL )
  2305. return false;
  2306. // If the target is in a vehicle, let the convar choose
  2307. CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
  2308. if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
  2309. return ( sk_rollermine_vehicle_intercept.GetBool() );
  2310. return false;
  2311. }
  2312. //-----------------------------------------------------------------------------
  2313. // Purpose:
  2314. //-----------------------------------------------------------------------------
  2315. float CNPC_RollerMine::VehicleHeading( CBaseEntity *pVehicle )
  2316. {
  2317. Vector vecVelocity = pVehicle->GetSmoothedVelocity();
  2318. float flSpeed = VectorNormalize( vecVelocity );
  2319. Vector vecToMine = GetAbsOrigin() - pVehicle->GetAbsOrigin();
  2320. VectorNormalize( vecToMine );
  2321. // If it's not moving, consider it moving towards us, but not directly
  2322. // This will enable already active rollers to chase the vehicle if it's stationary.
  2323. if ( flSpeed < 10 )
  2324. return 0.1;
  2325. return DotProduct( vecVelocity, vecToMine );
  2326. }
  2327. //-----------------------------------------------------------------------------
  2328. // Purpose:
  2329. // Input : &info -
  2330. // &vecDir -
  2331. // *ptr -
  2332. //-----------------------------------------------------------------------------
  2333. void CNPC_RollerMine::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  2334. {
  2335. if ( info.GetDamageType() & (DMG_BULLET | DMG_CLUB) )
  2336. {
  2337. CTakeDamageInfo newInfo( info );
  2338. // If we're stuck to the car, increase it even more
  2339. if ( GetVehicleStuckTo() )
  2340. {
  2341. newInfo.SetDamageForce( info.GetDamageForce() * 40 );
  2342. }
  2343. else
  2344. {
  2345. newInfo.SetDamageForce( info.GetDamageForce() * 20 );
  2346. }
  2347. BaseClass::TraceAttack( newInfo, vecDir, ptr, pAccumulator );
  2348. return;
  2349. }
  2350. BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
  2351. }
  2352. //-----------------------------------------------------------------------------
  2353. //
  2354. // Schedules
  2355. //
  2356. //-----------------------------------------------------------------------------
  2357. AI_BEGIN_CUSTOM_NPC( npc_rollermine, CNPC_RollerMine )
  2358. //Tasks
  2359. DECLARE_TASK( TASK_ROLLERMINE_CHARGE_ENEMY )
  2360. DECLARE_TASK( TASK_ROLLERMINE_BURIED_WAIT )
  2361. DECLARE_TASK( TASK_ROLLERMINE_UNBURROW )
  2362. DECLARE_TASK( TASK_ROLLERMINE_GET_PATH_TO_FLEE )
  2363. DECLARE_TASK( TASK_ROLLERMINE_NUDGE_TOWARDS_NODES )
  2364. DECLARE_TASK( TASK_ROLLERMINE_RETURN_TO_PLAYER )
  2365. DECLARE_TASK( TASK_ROLLERMINE_POWERDOWN )
  2366. //Schedules
  2367. DEFINE_SCHEDULE
  2368. (
  2369. SCHED_ROLLERMINE_BURIED_WAIT,
  2370. " Tasks"
  2371. " TASK_ROLLERMINE_BURIED_WAIT 0"
  2372. " "
  2373. " Interrupts"
  2374. " COND_NEW_ENEMY"
  2375. " COND_LIGHT_DAMAGE"
  2376. )
  2377. DEFINE_SCHEDULE
  2378. (
  2379. SCHED_ROLLERMINE_BURIED_UNBURROW,
  2380. " Tasks"
  2381. " TASK_ROLLERMINE_UNBURROW 0"
  2382. " "
  2383. " Interrupts"
  2384. )
  2385. DEFINE_SCHEDULE
  2386. (
  2387. SCHED_ROLLERMINE_RANGE_ATTACK1,
  2388. " Tasks"
  2389. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
  2390. " TASK_ROLLERMINE_CHARGE_ENEMY 0"
  2391. " "
  2392. " Interrupts"
  2393. " COND_ENEMY_DEAD"
  2394. " COND_NEW_ENEMY"
  2395. " COND_ENEMY_OCCLUDED"
  2396. " COND_ENEMY_TOO_FAR"
  2397. )
  2398. DEFINE_SCHEDULE
  2399. (
  2400. SCHED_ROLLERMINE_CHASE_ENEMY,
  2401. " Tasks"
  2402. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ROLLERMINE_RANGE_ATTACK1"
  2403. " TASK_SET_TOLERANCE_DISTANCE 24"
  2404. " TASK_GET_PATH_TO_ENEMY 0"
  2405. " TASK_RUN_PATH 0"
  2406. " TASK_WAIT_FOR_MOVEMENT 0"
  2407. " "
  2408. " Interrupts"
  2409. " COND_ENEMY_DEAD"
  2410. " COND_ENEMY_UNREACHABLE"
  2411. " COND_ENEMY_TOO_FAR"
  2412. " COND_CAN_RANGE_ATTACK1"
  2413. " COND_TASK_FAILED"
  2414. " COND_SEE_FEAR"
  2415. )
  2416. DEFINE_SCHEDULE
  2417. (
  2418. SCHED_ROLLERMINE_FLEE,
  2419. " Tasks"
  2420. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_IDLE_STAND"
  2421. " TASK_ROLLERMINE_GET_PATH_TO_FLEE 300"
  2422. " TASK_RUN_PATH 0"
  2423. " TASK_STOP_MOVING 0"
  2424. " "
  2425. " Interrupts"
  2426. " COND_NEW_ENEMY"
  2427. " COND_TASK_FAILED"
  2428. )
  2429. DEFINE_SCHEDULE
  2430. (
  2431. SCHED_ROLLERMINE_ALERT_STAND,
  2432. " Tasks"
  2433. " TASK_STOP_MOVING 0"
  2434. " TASK_FACE_REASONABLE 0"
  2435. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  2436. " TASK_WAIT 2"
  2437. ""
  2438. " Interrupts"
  2439. " COND_NEW_ENEMY"
  2440. " COND_SEE_ENEMY"
  2441. " COND_SEE_FEAR"
  2442. " COND_LIGHT_DAMAGE"
  2443. " COND_HEAVY_DAMAGE"
  2444. " COND_PROVOKED"
  2445. " COND_SMELL"
  2446. " COND_HEAR_COMBAT" // sound flags
  2447. " COND_HEAR_WORLD"
  2448. " COND_HEAR_PLAYER"
  2449. " COND_HEAR_DANGER"
  2450. " COND_HEAR_BULLET_IMPACT"
  2451. " COND_IDLE_INTERRUPT"
  2452. )
  2453. DEFINE_SCHEDULE
  2454. (
  2455. SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES,
  2456. " Tasks"
  2457. " TASK_ROLLERMINE_NUDGE_TOWARDS_NODES 0"
  2458. " TASK_WAIT 1.5"
  2459. ""
  2460. " Interrupts"
  2461. ""
  2462. )
  2463. DEFINE_SCHEDULE
  2464. (
  2465. SCHED_ROLLERMINE_PATH_TO_PLAYER,
  2466. " Tasks"
  2467. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ROLLERMINE_ALERT_STAND"
  2468. " TASK_SET_TOLERANCE_DISTANCE 200"
  2469. " TASK_GET_PATH_TO_PLAYER 0"
  2470. " TASK_RUN_PATH 0"
  2471. " TASK_WAIT_FOR_MOVEMENT 0"
  2472. ""
  2473. " Interrupts"
  2474. " COND_NEW_ENEMY"
  2475. " COND_SEE_ENEMY"
  2476. " COND_SEE_FEAR"
  2477. " COND_LIGHT_DAMAGE"
  2478. " COND_HEAVY_DAMAGE"
  2479. " COND_PROVOKED"
  2480. " COND_SMELL"
  2481. " COND_HEAR_COMBAT" // sound flags
  2482. " COND_HEAR_WORLD"
  2483. " COND_HEAR_PLAYER"
  2484. " COND_HEAR_DANGER"
  2485. " COND_HEAR_BULLET_IMPACT"
  2486. " COND_IDLE_INTERRUPT"
  2487. " COND_SEE_PLAYER"
  2488. )
  2489. DEFINE_SCHEDULE
  2490. (
  2491. SCHED_ROLLERMINE_ROLL_TO_PLAYER,
  2492. " Tasks"
  2493. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ROLLERMINE_ALERT_STAND"
  2494. " TASK_SET_TOLERANCE_DISTANCE 200"
  2495. " TASK_ROLLERMINE_RETURN_TO_PLAYER 0"
  2496. ""
  2497. " Interrupts"
  2498. " COND_NEW_ENEMY"
  2499. " COND_SEE_ENEMY"
  2500. " COND_SEE_FEAR"
  2501. " COND_LIGHT_DAMAGE"
  2502. " COND_HEAVY_DAMAGE"
  2503. " COND_PROVOKED"
  2504. " COND_SMELL"
  2505. " COND_HEAR_COMBAT" // sound flags
  2506. " COND_HEAR_WORLD"
  2507. " COND_HEAR_PLAYER"
  2508. " COND_HEAR_DANGER"
  2509. " COND_HEAR_BULLET_IMPACT"
  2510. " COND_IDLE_INTERRUPT"
  2511. )
  2512. DEFINE_SCHEDULE
  2513. (
  2514. SCHED_ROLLERMINE_POWERDOWN,
  2515. " Tasks"
  2516. " TASK_STOP_MOVING 0"
  2517. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  2518. " TASK_ROLLERMINE_POWERDOWN 0"
  2519. ""
  2520. " Interrupts"
  2521. ""
  2522. );
  2523. AI_END_CUSTOM_NPC()