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.

3428 lines
92 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "soundenvelope.h"
  8. #include "npc_manhack.h"
  9. #include "ai_default.h"
  10. #include "ai_node.h"
  11. #include "ai_navigator.h"
  12. #include "ai_pathfinder.h"
  13. #include "ai_moveprobe.h"
  14. #include "ai_memory.h"
  15. #include "ai_squad.h"
  16. #include "ai_route.h"
  17. #include "explode.h"
  18. #include "basegrenade_shared.h"
  19. #include "ndebugoverlay.h"
  20. #include "decals.h"
  21. #include "gib.h"
  22. #include "game.h"
  23. #include "ai_interactions.h"
  24. #include "IEffects.h"
  25. #include "vstdlib/random.h"
  26. #include "engine/IEngineSound.h"
  27. #include "movevars_shared.h"
  28. #include "npcevent.h"
  29. #include "props.h"
  30. #include "te_effect_dispatch.h"
  31. #include "ai_squadslot.h"
  32. #include "world.h"
  33. #include "smoke_trail.h"
  34. #include "func_break.h"
  35. #include "physics_impact_damage.h"
  36. #include "weapon_physcannon.h"
  37. #include "physics_prop_ragdoll.h"
  38. #include "soundent.h"
  39. #include "ammodef.h"
  40. // memdbgon must be the last include file in a .cpp file!!!
  41. #include "tier0/memdbgon.h"
  42. // When the engine is running and the manhack is operating under power
  43. // we don't let gravity affect him.
  44. #define MANHACK_GRAVITY 0.000
  45. #define MANHACK_GIB_COUNT 5
  46. #define MANHACK_INGORE_WATER_DIST 384
  47. // Sound stuff
  48. #define MANHACK_PITCH_DIST1 512
  49. #define MANHACK_MIN_PITCH1 (100)
  50. #define MANHACK_MAX_PITCH1 (160)
  51. #define MANHACK_WATER_PITCH1 (85)
  52. #define MANHACK_VOLUME1 0.55
  53. #define MANHACK_PITCH_DIST2 400
  54. #define MANHACK_MIN_PITCH2 (85)
  55. #define MANHACK_MAX_PITCH2 (190)
  56. #define MANHACK_WATER_PITCH2 (90)
  57. #define MANHACK_NOISEMOD_HIDE 5000
  58. #define MANHACK_BODYGROUP_BLADE 1
  59. #define MANHACK_BODYGROUP_BLUR 2
  60. #define MANHACK_BODYGROUP_OFF 0
  61. #define MANHACK_BODYGROUP_ON 1
  62. // ANIMATION EVENTS
  63. #define MANHACK_AE_START_ENGINE 50
  64. #define MANHACK_AE_DONE_UNPACKING 51
  65. #define MANHACK_AE_OPEN_BLADE 52
  66. //#define MANHACK_GLOW_SPRITE "sprites/laserdot.vmt"
  67. #define MANHACK_GLOW_SPRITE "sprites/glow1.vmt"
  68. #define MANHACK_CHARGE_MIN_DIST 200
  69. ConVar sk_manhack_health( "sk_manhack_health","0");
  70. ConVar sk_manhack_melee_dmg( "sk_manhack_melee_dmg","0");
  71. ConVar sk_manhack_v2( "sk_manhack_v2","1");
  72. extern void SpawnBlood(Vector vecSpot, const Vector &vAttackDir, int bloodColor, float flDamage);
  73. extern float GetFloorZ(const Vector &origin);
  74. //-----------------------------------------------------------------------------
  75. // Private activities.
  76. //-----------------------------------------------------------------------------
  77. Activity ACT_MANHACK_UNPACK;
  78. //-----------------------------------------------------------------------------
  79. // Manhack Conditions
  80. //-----------------------------------------------------------------------------
  81. enum ManhackConditions
  82. {
  83. COND_MANHACK_START_ATTACK = LAST_SHARED_CONDITION, // We are able to do a bombing run on the current enemy.
  84. };
  85. //-----------------------------------------------------------------------------
  86. // Manhack schedules.
  87. //-----------------------------------------------------------------------------
  88. enum ManhackSchedules
  89. {
  90. SCHED_MANHACK_ATTACK_HOVER = LAST_SHARED_SCHEDULE,
  91. SCHED_MANHACK_DEPLOY,
  92. SCHED_MANHACK_REGROUP,
  93. SCHED_MANHACK_SWARM_IDLE,
  94. SCHED_MANHACK_SWARM,
  95. SCHED_MANHACK_SWARM_FAILURE,
  96. };
  97. //-----------------------------------------------------------------------------
  98. // Manhack tasks.
  99. //-----------------------------------------------------------------------------
  100. enum ManhackTasks
  101. {
  102. TASK_MANHACK_HOVER = LAST_SHARED_TASK,
  103. TASK_MANHACK_UNPACK,
  104. TASK_MANHACK_FIND_SQUAD_CENTER,
  105. TASK_MANHACK_FIND_SQUAD_MEMBER,
  106. TASK_MANHACK_MOVEAT_SAVEPOSITION,
  107. };
  108. BEGIN_DATADESC( CNPC_Manhack )
  109. DEFINE_FIELD( m_vForceVelocity, FIELD_VECTOR),
  110. DEFINE_FIELD( m_vTargetBanking, FIELD_VECTOR),
  111. DEFINE_FIELD( m_vForceMoveTarget, FIELD_POSITION_VECTOR),
  112. DEFINE_FIELD( m_fForceMoveTime, FIELD_TIME),
  113. DEFINE_FIELD( m_vSwarmMoveTarget, FIELD_POSITION_VECTOR),
  114. DEFINE_FIELD( m_fSwarmMoveTime, FIELD_TIME),
  115. DEFINE_FIELD( m_fEnginePowerScale, FIELD_FLOAT),
  116. DEFINE_FIELD( m_flNextEngineSoundTime, FIELD_TIME),
  117. DEFINE_FIELD( m_flEngineStallTime, FIELD_TIME),
  118. DEFINE_FIELD( m_flNextBurstTime, FIELD_TIME ),
  119. DEFINE_FIELD( m_flWaterSuspendTime, FIELD_TIME),
  120. DEFINE_FIELD( m_nLastSpinSound, FIELD_INTEGER ),
  121. // Death
  122. DEFINE_FIELD( m_fSparkTime, FIELD_TIME),
  123. DEFINE_FIELD( m_fSmokeTime, FIELD_TIME),
  124. DEFINE_FIELD( m_bDirtyPitch, FIELD_BOOLEAN ),
  125. DEFINE_FIELD( m_bGib, FIELD_BOOLEAN),
  126. DEFINE_FIELD( m_bHeld, FIELD_BOOLEAN),
  127. DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN),
  128. DEFINE_FIELD( m_vecLoiterPosition, FIELD_POSITION_VECTOR),
  129. DEFINE_FIELD( m_fTimeNextLoiterPulse, FIELD_TIME),
  130. DEFINE_FIELD( m_flBumpSuppressTime, FIELD_TIME ),
  131. DEFINE_FIELD( m_bBladesActive, FIELD_BOOLEAN),
  132. DEFINE_FIELD( m_flBladeSpeed, FIELD_FLOAT),
  133. DEFINE_KEYFIELD( m_bIgnoreClipbrushes, FIELD_BOOLEAN, "ignoreclipbrushes" ),
  134. DEFINE_FIELD( m_hSmokeTrail, FIELD_EHANDLE),
  135. // DEFINE_FIELD( m_pLightGlow, FIELD_CLASSPTR ),
  136. // DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ),
  137. DEFINE_FIELD( m_iPanel1, FIELD_INTEGER ),
  138. DEFINE_FIELD( m_iPanel2, FIELD_INTEGER ),
  139. DEFINE_FIELD( m_iPanel3, FIELD_INTEGER ),
  140. DEFINE_FIELD( m_iPanel4, FIELD_INTEGER ),
  141. DEFINE_FIELD( m_nLastWaterLevel, FIELD_INTEGER ),
  142. DEFINE_FIELD( m_bDoSwarmBehavior, FIELD_BOOLEAN ),
  143. DEFINE_FIELD( m_nEnginePitch1, FIELD_INTEGER ),
  144. DEFINE_FIELD( m_flEnginePitch1Time, FIELD_TIME ),
  145. DEFINE_FIELD( m_nEnginePitch2, FIELD_INTEGER ),
  146. DEFINE_FIELD( m_flEnginePitch2Time, FIELD_TIME ),
  147. // Physics Influence
  148. DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
  149. DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
  150. DEFINE_FIELD( m_flBurstDuration, FIELD_FLOAT ),
  151. DEFINE_FIELD( m_vecBurstDirection, FIELD_VECTOR ),
  152. DEFINE_FIELD( m_bShowingHostile, FIELD_BOOLEAN ),
  153. // Function Pointers
  154. DEFINE_INPUTFUNC( FIELD_VOID, "DisableSwarm", InputDisableSwarm ),
  155. DEFINE_INPUTFUNC( FIELD_VOID, "Unpack", InputUnpack ),
  156. DEFINE_ENTITYFUNC( CrashTouch ),
  157. DEFINE_BASENPCINTERACTABLE_DATADESC(),
  158. END_DATADESC()
  159. LINK_ENTITY_TO_CLASS( npc_manhack, CNPC_Manhack );
  160. IMPLEMENT_SERVERCLASS_ST(CNPC_Manhack, DT_NPC_Manhack)
  161. SendPropIntWithMinusOneFlag (SENDINFO(m_nEnginePitch1), 8 ),
  162. SendPropFloat(SENDINFO(m_flEnginePitch1Time), 0, SPROP_NOSCALE),
  163. SendPropIntWithMinusOneFlag(SENDINFO(m_nEnginePitch2), 8 )
  164. END_SEND_TABLE()
  165. //------------------------------------------------------------------------------
  166. // Purpose :
  167. // Input :
  168. // Output :
  169. //------------------------------------------------------------------------------
  170. CNPC_Manhack::CNPC_Manhack()
  171. {
  172. #ifdef _DEBUG
  173. m_vForceMoveTarget.Init();
  174. m_vSwarmMoveTarget.Init();
  175. m_vTargetBanking.Init();
  176. m_vForceVelocity.Init();
  177. #endif
  178. m_bDirtyPitch = true;
  179. m_nLastWaterLevel = 0;
  180. m_nEnginePitch1 = -1;
  181. m_nEnginePitch2 = -1;
  182. m_flEnginePitch1Time = 0;
  183. m_flEnginePitch1Time = 0;
  184. m_bDoSwarmBehavior = true;
  185. m_flBumpSuppressTime = 0;
  186. }
  187. //------------------------------------------------------------------------------
  188. // Purpose:
  189. //------------------------------------------------------------------------------
  190. CNPC_Manhack::~CNPC_Manhack()
  191. {
  192. }
  193. //-----------------------------------------------------------------------------
  194. // Purpose: Indicates this NPC's place in the relationship table.
  195. //-----------------------------------------------------------------------------
  196. Class_T CNPC_Manhack::Classify(void)
  197. {
  198. return (m_bHeld||m_bHackedByAlyx) ? CLASS_PLAYER_ALLY : CLASS_MANHACK;
  199. }
  200. //-----------------------------------------------------------------------------
  201. // Purpose: Turns the manhack into a physics corpse when dying.
  202. //-----------------------------------------------------------------------------
  203. void CNPC_Manhack::Event_Dying(void)
  204. {
  205. DestroySmokeTrail();
  206. SetHullSizeNormal();
  207. BaseClass::Event_Dying();
  208. }
  209. //-----------------------------------------------------------------------------
  210. //-----------------------------------------------------------------------------
  211. void CNPC_Manhack::GatherConditions()
  212. {
  213. BaseClass::GatherConditions();
  214. if( IsLoitering() && GetEnemy() )
  215. {
  216. StopLoitering();
  217. }
  218. }
  219. //-----------------------------------------------------------------------------
  220. // Purpose:
  221. // Input :
  222. // Output :
  223. //-----------------------------------------------------------------------------
  224. void CNPC_Manhack::PrescheduleThink( void )
  225. {
  226. BaseClass::PrescheduleThink();
  227. UpdatePanels();
  228. if( m_flWaterSuspendTime > gpGlobals->curtime )
  229. {
  230. // Stuck in water!
  231. // Reduce engine power so that the manhack lifts out of the water slowly.
  232. m_fEnginePowerScale = 0.75;
  233. }
  234. // ----------------------------------------
  235. // Am I in water?
  236. // ----------------------------------------
  237. if ( GetWaterLevel() > 0 )
  238. {
  239. if( m_nLastWaterLevel == 0 )
  240. {
  241. Splash( WorldSpaceCenter() );
  242. }
  243. if( IsAlive() )
  244. {
  245. // If I've been out of water for 2 seconds or more, I'm eligible to be stuck in water again.
  246. if( gpGlobals->curtime - m_flWaterSuspendTime > 2.0 )
  247. {
  248. m_flWaterSuspendTime = gpGlobals->curtime + 1.0;
  249. }
  250. }
  251. }
  252. else
  253. {
  254. if( m_nLastWaterLevel != 0 )
  255. {
  256. Splash( WorldSpaceCenter() );
  257. }
  258. }
  259. m_nLastWaterLevel = GetWaterLevel();
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose:
  263. // Input :
  264. // Output :
  265. //-----------------------------------------------------------------------------
  266. void CNPC_Manhack::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  267. {
  268. g_vecAttackDir = vecDir;
  269. if ( info.GetDamageType() & DMG_BULLET)
  270. {
  271. g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal);
  272. }
  273. if ( info.GetDamageType() & DMG_CLUB )
  274. {
  275. // Clubbed!
  276. // UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10);
  277. g_pEffects->Sparks( ptr->endpos, 1, 1, &ptr->plane.normal );
  278. }
  279. BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
  280. }
  281. //-----------------------------------------------------------------------------
  282. // Purpose:
  283. //-----------------------------------------------------------------------------
  284. void CNPC_Manhack::DeathSound( const CTakeDamageInfo &info )
  285. {
  286. StopSound("NPC_Manhack.Stunned");
  287. CPASAttenuationFilter filter2( this, "NPC_Manhack.Die" );
  288. EmitSound( filter2, entindex(), "NPC_Manhack.Die" );
  289. }
  290. //-----------------------------------------------------------------------------
  291. // Purpose:
  292. // Output : Returns true on success, false on failure.
  293. //-----------------------------------------------------------------------------
  294. bool CNPC_Manhack::ShouldGib( const CTakeDamageInfo &info )
  295. {
  296. return ( m_bGib );
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Purpose:
  300. // Input :
  301. // Output :
  302. //-----------------------------------------------------------------------------
  303. void CNPC_Manhack::Event_Killed( const CTakeDamageInfo &info )
  304. {
  305. // turn off the blur!
  306. SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_OFF );
  307. // Sparks
  308. for (int i = 0; i < 3; i++)
  309. {
  310. Vector sparkPos = GetAbsOrigin();
  311. sparkPos.x += random->RandomFloat(-12,12);
  312. sparkPos.y += random->RandomFloat(-12,12);
  313. sparkPos.z += random->RandomFloat(-12,12);
  314. g_pEffects->Sparks( sparkPos, 2 );
  315. }
  316. // Light
  317. CBroadcastRecipientFilter filter;
  318. te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 255, 180, 100, 0, 100, 0.1, 0 );
  319. if ( m_nEnginePitch1 < 0 )
  320. {
  321. // Probably this manhack was killed immediately after spawning. Turn the sound
  322. // on right now so that we can pitch it up for the crash!
  323. SoundInit();
  324. }
  325. // Always gib when clubbed or blasted or crushed, or just randomly
  326. if ( ( info.GetDamageType() & (DMG_CLUB|DMG_CRUSH|DMG_BLAST) ) || ( random->RandomInt( 0, 1 ) ) )
  327. {
  328. m_bGib = true;
  329. }
  330. else
  331. {
  332. m_bGib = false;
  333. //FIXME: These don't stay with the ragdolls currently -- jdw
  334. // Long fadeout on the sprites!!
  335. KillSprites( 0.0f );
  336. }
  337. BaseClass::Event_Killed( info );
  338. }
  339. void CNPC_Manhack::HitPhysicsObject( CBaseEntity *pOther )
  340. {
  341. IPhysicsObject *pOtherPhysics = pOther->VPhysicsGetObject();
  342. Vector pos, posOther;
  343. // Put the force on the line between the manhack origin and hit object origin
  344. VPhysicsGetObject()->GetPosition( &pos, NULL );
  345. pOtherPhysics->GetPosition( &posOther, NULL );
  346. Vector dir = posOther - pos;
  347. VectorNormalize(dir);
  348. // size/2 is approx radius
  349. pos += dir * WorldAlignSize().x * 0.5;
  350. Vector cross;
  351. // UNDONE: Use actual manhack up vector so the fake blade is
  352. // in the right plane?
  353. // Get a vector in the x/y plane in the direction of blade spin (clockwise)
  354. CrossProduct( dir, Vector(0,0,1), cross );
  355. VectorNormalize( cross );
  356. // force is a 30kg object going 100 in/s
  357. pOtherPhysics->ApplyForceOffset( cross * 30 * 100, pos );
  358. }
  359. //-----------------------------------------------------------------------------
  360. // Take damage from being thrown by a physcannon
  361. //-----------------------------------------------------------------------------
  362. #define MANHACK_SMASH_SPEED 500.0 // How fast a manhack must slam into something to take full damage
  363. void CNPC_Manhack::TakeDamageFromPhyscannon( CBasePlayer *pPlayer )
  364. {
  365. CTakeDamageInfo info;
  366. info.SetDamageType( DMG_GENERIC );
  367. info.SetInflictor( this );
  368. info.SetAttacker( pPlayer );
  369. info.SetDamagePosition( GetAbsOrigin() );
  370. info.SetDamageForce( Vector( 1.0, 1.0, 1.0 ) );
  371. // Convert velocity into damage.
  372. Vector vel;
  373. VPhysicsGetObject()->GetVelocity( &vel, NULL );
  374. float flSpeed = vel.Length();
  375. float flFactor = flSpeed / MANHACK_SMASH_SPEED;
  376. // Clamp. Don't inflict negative damage or massive damage!
  377. flFactor = clamp( flFactor, 0.0f, 2.0f );
  378. float flDamage = m_iMaxHealth * flFactor;
  379. #if 0
  380. Msg("Doing %f damage for %f speed!\n", flDamage, flSpeed );
  381. #endif
  382. info.SetDamage( flDamage );
  383. TakeDamage( info );
  384. }
  385. //-----------------------------------------------------------------------------
  386. // Take damage from a vehicle; it's like a really big crowbar
  387. //-----------------------------------------------------------------------------
  388. void CNPC_Manhack::TakeDamageFromVehicle( int index, gamevcollisionevent_t *pEvent )
  389. {
  390. // Use the vehicle velocity to determine the damage
  391. int otherIndex = !index;
  392. CBaseEntity *pOther = pEvent->pEntities[otherIndex];
  393. float flSpeed = pEvent->preVelocity[ otherIndex ].Length();
  394. flSpeed = clamp( flSpeed, 300.0f, 600.0f );
  395. float flDamage = SimpleSplineRemapVal( flSpeed, 300.0f, 600.0f, 0.0f, 1.0f );
  396. if ( flDamage == 0.0f )
  397. return;
  398. flDamage *= 20.0f;
  399. Vector damagePos;
  400. pEvent->pInternalData->GetContactPoint( damagePos );
  401. Vector damageForce = 2.0f * pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
  402. if ( damageForce == vec3_origin )
  403. {
  404. // This can happen if this entity is a func_breakable, and can't move.
  405. // Use the velocity of the entity that hit us instead.
  406. damageForce = 2.0f * pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
  407. }
  408. Assert( damageForce != vec3_origin );
  409. CTakeDamageInfo dmgInfo( pOther, pOther, damageForce, damagePos, flDamage, DMG_CRUSH );
  410. TakeDamage( dmgInfo );
  411. }
  412. //-----------------------------------------------------------------------------
  413. // Take damage from combine ball
  414. //-----------------------------------------------------------------------------
  415. void CNPC_Manhack::TakeDamageFromPhysicsImpact( int index, gamevcollisionevent_t *pEvent )
  416. {
  417. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  418. // NOTE: Bypass the normal impact energy scale here.
  419. float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 1.0f;
  420. int damageType = 0;
  421. float damage = CalculateDefaultPhysicsDamage( index, pEvent, flDamageScale, true, damageType );
  422. if ( damage == 0 )
  423. return;
  424. Vector damagePos;
  425. pEvent->pInternalData->GetContactPoint( damagePos );
  426. Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
  427. if ( damageForce == vec3_origin )
  428. {
  429. // This can happen if this entity is motion disabled, and can't move.
  430. // Use the velocity of the entity that hit us instead.
  431. damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
  432. }
  433. // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
  434. PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index );
  435. }
  436. //-----------------------------------------------------------------------------
  437. // Purpose:
  438. //-----------------------------------------------------------------------------
  439. #define MANHACK_SMASH_TIME 0.35 // How long after being thrown from a physcannon that a manhack is eligible to die from impact
  440. void CNPC_Manhack::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  441. {
  442. BaseClass::VPhysicsCollision( index, pEvent );
  443. // Take no impact damage while being carried.
  444. if ( IsHeldByPhyscannon() )
  445. return;
  446. // Wake us up
  447. if ( m_spawnflags & SF_MANHACK_PACKED_UP )
  448. {
  449. SetCondition( COND_LIGHT_DAMAGE );
  450. }
  451. int otherIndex = !index;
  452. CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
  453. CBasePlayer *pPlayer = HasPhysicsAttacker( MANHACK_SMASH_TIME );
  454. if( pPlayer )
  455. {
  456. if (!pHitEntity)
  457. {
  458. TakeDamageFromPhyscannon( pPlayer );
  459. StopBurst( true );
  460. return;
  461. }
  462. // Don't take damage from NPCs or server ragdolls killed by the manhack
  463. CRagdollProp *pRagdollProp = dynamic_cast<CRagdollProp*>(pHitEntity);
  464. if (!pHitEntity->IsNPC() && (!pRagdollProp || pRagdollProp->GetKiller() != this))
  465. {
  466. TakeDamageFromPhyscannon( pPlayer );
  467. StopBurst( true );
  468. return;
  469. }
  470. }
  471. if ( pHitEntity )
  472. {
  473. // It can take physics damage if it rams into a vehicle
  474. if ( pHitEntity->GetServerVehicle() )
  475. {
  476. TakeDamageFromVehicle( index, pEvent );
  477. }
  478. else if ( pHitEntity->HasPhysicsAttacker( 0.5f ) )
  479. {
  480. // It also can take physics damage from things thrown by the player.
  481. TakeDamageFromPhysicsImpact( index, pEvent );
  482. }
  483. else if ( FClassnameIs( pHitEntity, "prop_combine_ball" ) )
  484. {
  485. // It also can take physics damage from a combine ball.
  486. TakeDamageFromPhysicsImpact( index, pEvent );
  487. }
  488. else if ( m_iHealth <= 0 )
  489. {
  490. TakeDamageFromPhysicsImpact( index, pEvent );
  491. }
  492. StopBurst( true );
  493. }
  494. }
  495. void CNPC_Manhack::VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent )
  496. {
  497. int otherIndex = !index;
  498. CBaseEntity *pOther = pEvent->pEntities[otherIndex];
  499. if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
  500. {
  501. HitPhysicsObject( pOther );
  502. }
  503. BaseClass::VPhysicsShadowCollision( index, pEvent );
  504. }
  505. //-----------------------------------------------------------------------------
  506. // Purpose: Manhack is out of control! (dying) Just explode as soon as you touch anything!
  507. // Input :
  508. // Output :
  509. //-----------------------------------------------------------------------------
  510. void CNPC_Manhack::CrashTouch( CBaseEntity *pOther )
  511. {
  512. CTakeDamageInfo info( GetWorldEntity(), GetWorldEntity(), 25, DMG_CRUSH );
  513. CorpseGib( info );
  514. }
  515. //-----------------------------------------------------------------------------
  516. // Create smoke trail!
  517. //-----------------------------------------------------------------------------
  518. void CNPC_Manhack::CreateSmokeTrail()
  519. {
  520. if ( HasSpawnFlags( SF_MANHACK_NO_DAMAGE_EFFECTS ) )
  521. return;
  522. if ( m_hSmokeTrail != NULL )
  523. return;
  524. SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail();
  525. if( !pSmokeTrail )
  526. return;
  527. pSmokeTrail->m_SpawnRate = 20;
  528. pSmokeTrail->m_ParticleLifetime = 0.5f;
  529. pSmokeTrail->m_StartSize = 8;
  530. pSmokeTrail->m_EndSize = 32;
  531. pSmokeTrail->m_SpawnRadius = 5;
  532. pSmokeTrail->m_MinSpeed = 15;
  533. pSmokeTrail->m_MaxSpeed = 25;
  534. pSmokeTrail->m_StartColor.Init( 0.4f, 0.4f, 0.4f );
  535. pSmokeTrail->m_EndColor.Init( 0, 0, 0 );
  536. pSmokeTrail->SetLifetime(-1);
  537. pSmokeTrail->FollowEntity(this);
  538. m_hSmokeTrail = pSmokeTrail;
  539. }
  540. void CNPC_Manhack::DestroySmokeTrail()
  541. {
  542. if ( m_hSmokeTrail.Get() )
  543. {
  544. UTIL_Remove( m_hSmokeTrail );
  545. m_hSmokeTrail = NULL;
  546. }
  547. }
  548. //-----------------------------------------------------------------------------
  549. // Purpose:
  550. // Input :
  551. // Output :
  552. //-----------------------------------------------------------------------------
  553. int CNPC_Manhack::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  554. {
  555. // Hafta make a copy of info cause we might need to scale damage.(sjb)
  556. CTakeDamageInfo tdInfo = info;
  557. if( tdInfo.GetAmmoType() == GetAmmoDef()->Index("SniperRound") )
  558. {
  559. // Unfortunately, this is the easiest way to stop the sniper killing manhacks in one shot.
  560. tdInfo.SetDamage( m_iMaxHealth>>1 );
  561. }
  562. if (info.GetDamageType() & DMG_PHYSGUN )
  563. {
  564. m_flBladeSpeed = 20.0;
  565. // respond to physics
  566. // FIXME: shouldn't this happen in a base class? Anyway to prevent it from happening twice?
  567. VPhysicsTakeDamage( info );
  568. // reduce damage to nothing
  569. tdInfo.SetDamage( 1.0 );
  570. StopBurst( true );
  571. }
  572. else if ( info.GetDamageType() & DMG_AIRBOAT )
  573. {
  574. // Airboat gun kills me instantly.
  575. tdInfo.SetDamage( GetHealth() );
  576. }
  577. else if (info.GetDamageType() & DMG_CLUB)
  578. {
  579. // Being hit by a club means a couple of things:
  580. //
  581. // -I'm going to be knocked away from the person that clubbed me.
  582. // if fudging this vector a little bit could help me slam into a physics object,
  583. // then make that adjustment. This is a simple heuristic. The manhack will be
  584. // directed towards the physics object that is closest to g_vecAttackDir
  585. //
  586. // -Take 150% damage from club attacks. This makes crowbar duels take two hits.
  587. tdInfo.ScaleDamage( 1.50 );
  588. #define MANHACK_PHYS_SEARCH_SIZE 64
  589. #define MANHACK_PHYSICS_SEARCH_RADIUS 128
  590. CBaseEntity *pList[ MANHACK_PHYS_SEARCH_SIZE ];
  591. Vector attackDir = info.GetDamageForce();
  592. VectorNormalize( attackDir );
  593. Vector testCenter = GetAbsOrigin() + ( attackDir * MANHACK_PHYSICS_SEARCH_RADIUS );
  594. Vector vecDelta( MANHACK_PHYSICS_SEARCH_RADIUS, MANHACK_PHYSICS_SEARCH_RADIUS, MANHACK_PHYSICS_SEARCH_RADIUS );
  595. int count = UTIL_EntitiesInBox( pList, MANHACK_PHYS_SEARCH_SIZE, testCenter - vecDelta, testCenter + vecDelta, 0 );
  596. Vector vecBestDir = g_vecAttackDir;
  597. float flBestDot = 0.90;
  598. IPhysicsObject *pPhysObj;
  599. int i;
  600. for( i = 0 ; i < count ; i++ )
  601. {
  602. pPhysObj = pList[ i ]->VPhysicsGetObject();
  603. if( !pPhysObj || pPhysObj->GetMass() > 200 )
  604. {
  605. // Not physics.
  606. continue;
  607. }
  608. Vector center = pList[ i ]->WorldSpaceCenter();
  609. Vector vecDirToObject;
  610. VectorSubtract( center, WorldSpaceCenter(), vecDirToObject );
  611. VectorNormalize( vecDirToObject );
  612. float flDot;
  613. flDot = DotProduct( g_vecAttackDir, vecDirToObject );
  614. if( flDot > flBestDot )
  615. {
  616. flBestDot = flDot;
  617. vecBestDir = vecDirToObject;
  618. }
  619. }
  620. tdInfo.SetDamageForce( vecBestDir * info.GetDamage() * 200 );
  621. // FIXME: shouldn't this happen in a base class? Anyway to prevent it from happening twice?
  622. VPhysicsTakeDamage( tdInfo );
  623. // Force us away (no more residual speed hits!)
  624. m_vForceVelocity = vecBestDir * info.GetDamage() * 0.5f;
  625. m_flBladeSpeed = 10.0;
  626. EmitSound( "NPC_Manhack.Bat" );
  627. // tdInfo.SetDamage( 1.0 );
  628. m_flEngineStallTime = gpGlobals->curtime + 0.5f;
  629. StopBurst( true );
  630. }
  631. else
  632. {
  633. m_flBladeSpeed = 20.0;
  634. Vector vecDamageDir = tdInfo.GetDamageForce();
  635. VectorNormalize( vecDamageDir );
  636. m_flEngineStallTime = gpGlobals->curtime + 0.25f;
  637. m_vForceVelocity = vecDamageDir * info.GetDamage() * 20.0f;
  638. tdInfo.SetDamageForce( tdInfo.GetDamageForce() * 20 );
  639. VPhysicsTakeDamage( info );
  640. }
  641. int nRetVal = BaseClass::OnTakeDamage_Alive( tdInfo );
  642. if ( nRetVal )
  643. {
  644. if ( m_iHealth > 0 )
  645. {
  646. if ( info.GetDamageType() & DMG_CLUB )
  647. {
  648. SetEyeState( MANHACK_EYE_STATE_STUNNED );
  649. }
  650. if ( m_iHealth <= ( m_iMaxHealth / 2 ) )
  651. {
  652. CreateSmokeTrail();
  653. }
  654. }
  655. else
  656. {
  657. DestroySmokeTrail();
  658. }
  659. }
  660. return nRetVal;
  661. }
  662. //------------------------------------------------------------------------------
  663. // Purpose:
  664. //------------------------------------------------------------------------------
  665. bool CNPC_Manhack::CorpseGib( const CTakeDamageInfo &info )
  666. {
  667. Vector vecGibVelocity;
  668. AngularImpulse vecGibAVelocity;
  669. if( info.GetDamageType() & DMG_CLUB )
  670. {
  671. // If clubbed to death, break apart before the attacker's eyes!
  672. vecGibVelocity = g_vecAttackDir * -150;
  673. vecGibAVelocity.x = random->RandomFloat( -2000, 2000 );
  674. vecGibAVelocity.y = random->RandomFloat( -2000, 2000 );
  675. vecGibAVelocity.z = random->RandomFloat( -2000, 2000 );
  676. }
  677. else
  678. {
  679. // Shower the pieces with my velocity.
  680. vecGibVelocity = GetCurrentVelocity();
  681. vecGibAVelocity.x = random->RandomFloat( -500, 500 );
  682. vecGibAVelocity.y = random->RandomFloat( -500, 500 );
  683. vecGibAVelocity.z = random->RandomFloat( -500, 500 );
  684. }
  685. PropBreakableCreateAll( GetModelIndex(), NULL, GetAbsOrigin(), GetAbsAngles(), vecGibVelocity, vecGibAVelocity, 1.0, 60, COLLISION_GROUP_DEBRIS );
  686. RemoveDeferred();
  687. KillSprites( 0.0f );
  688. return true;
  689. }
  690. //-----------------------------------------------------------------------------
  691. // Purpose: Explode the manhack if it's damaged while crashing
  692. // Input :
  693. // Output :
  694. //-----------------------------------------------------------------------------
  695. int CNPC_Manhack::OnTakeDamage_Dying( const CTakeDamageInfo &info )
  696. {
  697. // Ignore damage for the first 1 second of crashing behavior.
  698. // If we don't do this, manhacks always just explode under
  699. // sustained fire.
  700. VPhysicsTakeDamage( info );
  701. return 0;
  702. }
  703. //-----------------------------------------------------------------------------
  704. // Turn on the engine sound if we're gagged!
  705. //-----------------------------------------------------------------------------
  706. void CNPC_Manhack::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
  707. {
  708. if( m_vNoiseMod.z == MANHACK_NOISEMOD_HIDE && !(m_spawnflags & SF_NPC_WAIT_FOR_SCRIPT) && !(m_spawnflags & SF_MANHACK_PACKED_UP) )
  709. {
  710. // This manhack should get a normal noisemod now.
  711. float flNoiseMod = random->RandomFloat( 1.7, 2.3 );
  712. // Just bob up and down.
  713. SetNoiseMod( 0, 0, flNoiseMod );
  714. }
  715. if( NewState != NPC_STATE_IDLE && (m_spawnflags & SF_NPC_GAG) && (m_nEnginePitch1 < 0) )
  716. {
  717. m_spawnflags &= ~SF_NPC_GAG;
  718. SoundInit();
  719. }
  720. }
  721. //-----------------------------------------------------------------------------
  722. // Purpose:
  723. // Input : Type -
  724. //-----------------------------------------------------------------------------
  725. void CNPC_Manhack::HandleAnimEvent( animevent_t *pEvent )
  726. {
  727. Vector vecNewVelocity;
  728. switch( pEvent->event )
  729. {
  730. case MANHACK_AE_START_ENGINE:
  731. StartEye();
  732. StartEngine( true );
  733. m_spawnflags &= ~SF_MANHACK_PACKED_UP;
  734. // No bursts until fully unpacked!
  735. m_flNextBurstTime = gpGlobals->curtime + FLT_MAX;
  736. break;
  737. case MANHACK_AE_DONE_UNPACKING:
  738. m_flNextBurstTime = gpGlobals->curtime + 2.0;
  739. break;
  740. case MANHACK_AE_OPEN_BLADE:
  741. m_bBladesActive = true;
  742. break;
  743. default:
  744. BaseClass::HandleAnimEvent( pEvent );
  745. break;
  746. }
  747. }
  748. //-----------------------------------------------------------------------------
  749. // Purpose: Returns whether or not the given activity would translate to flying.
  750. //-----------------------------------------------------------------------------
  751. bool CNPC_Manhack::IsFlyingActivity( Activity baseAct )
  752. {
  753. return ((baseAct == ACT_FLY || baseAct == ACT_IDLE || baseAct == ACT_RUN || baseAct == ACT_WALK) && m_bBladesActive);
  754. }
  755. //-----------------------------------------------------------------------------
  756. // Purpose:
  757. // Input : Type -
  758. //-----------------------------------------------------------------------------
  759. Activity CNPC_Manhack::NPC_TranslateActivity( Activity baseAct )
  760. {
  761. if (IsFlyingActivity( baseAct ))
  762. {
  763. return (Activity)ACT_FLY;
  764. }
  765. return BaseClass::NPC_TranslateActivity( baseAct );
  766. }
  767. //-----------------------------------------------------------------------------
  768. // Purpose:
  769. // Input : Type -
  770. //-----------------------------------------------------------------------------
  771. int CNPC_Manhack::TranslateSchedule( int scheduleType )
  772. {
  773. // Fail-safe for deployment if packed up and interrupted
  774. if ( m_spawnflags & SF_MANHACK_PACKED_UP )
  775. {
  776. if ( scheduleType != SCHED_WAIT_FOR_SCRIPT )
  777. return SCHED_MANHACK_DEPLOY;
  778. }
  779. switch ( scheduleType )
  780. {
  781. case SCHED_MELEE_ATTACK1:
  782. {
  783. return SCHED_MANHACK_ATTACK_HOVER;
  784. break;
  785. }
  786. case SCHED_BACK_AWAY_FROM_ENEMY:
  787. {
  788. return SCHED_MANHACK_REGROUP;
  789. break;
  790. }
  791. case SCHED_CHASE_ENEMY:
  792. {
  793. // If we're waiting for our next attack opportunity, just swarm
  794. if ( m_flNextBurstTime > gpGlobals->curtime )
  795. {
  796. return SCHED_MANHACK_SWARM;
  797. }
  798. if ( !m_bDoSwarmBehavior || OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
  799. {
  800. return SCHED_CHASE_ENEMY;
  801. }
  802. else
  803. {
  804. return SCHED_MANHACK_SWARM;
  805. }
  806. }
  807. case SCHED_COMBAT_FACE:
  808. {
  809. // Don't care about facing enemy, handled automatically
  810. return TranslateSchedule( SCHED_CHASE_ENEMY );
  811. break;
  812. }
  813. case SCHED_WAKE_ANGRY:
  814. {
  815. if( m_spawnflags & SF_MANHACK_PACKED_UP )
  816. {
  817. return SCHED_MANHACK_DEPLOY;
  818. }
  819. else
  820. {
  821. return TranslateSchedule( SCHED_CHASE_ENEMY );
  822. }
  823. break;
  824. }
  825. case SCHED_IDLE_STAND:
  826. case SCHED_ALERT_STAND:
  827. case SCHED_ALERT_FACE:
  828. {
  829. if ( m_pSquad && m_bDoSwarmBehavior )
  830. {
  831. return SCHED_MANHACK_SWARM_IDLE;
  832. }
  833. else
  834. {
  835. return BaseClass::TranslateSchedule(scheduleType);
  836. }
  837. }
  838. case SCHED_CHASE_ENEMY_FAILED:
  839. {
  840. // Relentless bastard! Doesn't fail (fail not valid anyway)
  841. return TranslateSchedule( SCHED_CHASE_ENEMY );
  842. break;
  843. }
  844. }
  845. return BaseClass::TranslateSchedule(scheduleType);
  846. }
  847. #define MAX_LOITER_DIST_SQR 144 // (12 inches sqr)
  848. void CNPC_Manhack::Loiter()
  849. {
  850. //NDebugOverlay::Line( GetAbsOrigin(), m_vecLoiterPosition, 255, 255, 255, false, 0.1 );
  851. // Friendly manhack is loitering.
  852. if( !m_bHeld )
  853. {
  854. float distSqr = m_vecLoiterPosition.DistToSqr(GetAbsOrigin());
  855. if( distSqr > MAX_LOITER_DIST_SQR )
  856. {
  857. Vector vecDir = m_vecLoiterPosition - GetAbsOrigin();
  858. VectorNormalize( vecDir );
  859. // Move back to our loiter position.
  860. if( gpGlobals->curtime > m_fTimeNextLoiterPulse )
  861. {
  862. // Apply a pulse of force if allowed right now.
  863. if( distSqr > MAX_LOITER_DIST_SQR * 4.0f )
  864. {
  865. //Msg("Big Pulse\n");
  866. m_vForceVelocity = vecDir * 12.0f;
  867. }
  868. else
  869. {
  870. //Msg("Small Pulse\n");
  871. m_vForceVelocity = vecDir * 6.0f;
  872. }
  873. m_fTimeNextLoiterPulse = gpGlobals->curtime + 1.0f;
  874. }
  875. else
  876. {
  877. m_vForceVelocity = vec3_origin;
  878. }
  879. }
  880. else
  881. {
  882. // Counteract velocity to slow down.
  883. Vector velocity = GetCurrentVelocity();
  884. m_vForceVelocity = velocity * -0.5;
  885. }
  886. }
  887. }
  888. //-----------------------------------------------------------------------------
  889. // Purpose:
  890. //-----------------------------------------------------------------------------
  891. void CNPC_Manhack::MaintainGroundHeight( void )
  892. {
  893. float zSpeed = GetCurrentVelocity().z;
  894. if ( zSpeed > 32.0f )
  895. return;
  896. const float minGroundHeight = 52.0f;
  897. trace_t tr;
  898. AI_TraceHull( GetAbsOrigin(),
  899. GetAbsOrigin() - Vector( 0, 0, minGroundHeight ),
  900. GetHullMins(),
  901. GetHullMaxs(),
  902. (MASK_NPCSOLID_BRUSHONLY),
  903. this,
  904. COLLISION_GROUP_NONE,
  905. &tr );
  906. if ( tr.fraction != 1.0f )
  907. {
  908. float speedAdj = MAX( 16, (-zSpeed*0.5f) );
  909. m_vForceVelocity += Vector(0,0,1) * ( speedAdj * ( 1.0f - tr.fraction ) );
  910. }
  911. }
  912. //-----------------------------------------------------------------------------
  913. // Purpose: Handles movement towards the last move target.
  914. // Input : flInterval -
  915. //-----------------------------------------------------------------------------
  916. bool CNPC_Manhack::OverrideMove( float flInterval )
  917. {
  918. SpinBlades( flInterval );
  919. // Don't execute any move code if packed up.
  920. if( HasSpawnFlags(SF_MANHACK_PACKED_UP|SF_MANHACK_CARRIED) )
  921. return true;
  922. if( IsLoitering() )
  923. {
  924. Loiter();
  925. }
  926. else
  927. {
  928. MaintainGroundHeight();
  929. }
  930. // So cops, etc. will try to avoid them
  931. if ( !HasSpawnFlags( SF_MANHACK_NO_DANGER_SOUNDS ) && !m_bHeld )
  932. {
  933. CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 75, flInterval, this );
  934. }
  935. // -----------------------------------------------------------------
  936. // If I'm being forced to move somewhere
  937. // ------------------------------------------------------------------
  938. if (m_fForceMoveTime > gpGlobals->curtime)
  939. {
  940. MoveToTarget(flInterval, m_vForceMoveTarget);
  941. }
  942. // -----------------------------------------------------------------
  943. // If I have a route, keep it updated and move toward target
  944. // ------------------------------------------------------------------
  945. else if (GetNavigator()->IsGoalActive())
  946. {
  947. bool bReducible = GetNavigator()->GetPath()->GetCurWaypoint()->IsReducible();
  948. const float strictTolerance = 64.0;
  949. //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, 10 ), 255, 0, 0, true, 0.1);
  950. if ( ProgressFlyPath( flInterval, GetEnemy(), MoveCollisionMask(), bReducible, strictTolerance ) == AINPP_COMPLETE )
  951. return true;
  952. }
  953. // -----------------------------------------------------------------
  954. // If I'm supposed to swarm somewhere, try to go there
  955. // ------------------------------------------------------------------
  956. else if (m_fSwarmMoveTime > gpGlobals->curtime)
  957. {
  958. MoveToTarget(flInterval, m_vSwarmMoveTarget);
  959. }
  960. // -----------------------------------------------------------------
  961. // If I don't have anything better to do, just decelerate
  962. // -------------------------------------------------------------- ----
  963. else
  964. {
  965. float myDecay = 9.5;
  966. Decelerate( flInterval, myDecay );
  967. m_vTargetBanking = vec3_origin;
  968. // -------------------------------------
  969. // If I have an enemy turn to face him
  970. // -------------------------------------
  971. if (GetEnemy())
  972. {
  973. TurnHeadToTarget(flInterval, GetEnemy()->EyePosition() );
  974. }
  975. }
  976. if ( m_iHealth <= 0 )
  977. {
  978. // Crashing!!
  979. MoveExecute_Dead(flInterval);
  980. }
  981. else
  982. {
  983. // Alive!
  984. MoveExecute_Alive(flInterval);
  985. }
  986. return true;
  987. }
  988. //-----------------------------------------------------------------------------
  989. // Purpose:
  990. // Input :
  991. // Output :
  992. //-----------------------------------------------------------------------------
  993. void CNPC_Manhack::TurnHeadRandomly(float flInterval )
  994. {
  995. float desYaw = random->RandomFloat(0,360);
  996. float iRate = 0.8;
  997. // Make frame rate independent
  998. float timeToUse = flInterval;
  999. while (timeToUse > 0)
  1000. {
  1001. m_fHeadYaw = (iRate * m_fHeadYaw) + (1-iRate)*desYaw;
  1002. timeToUse = -0.1;
  1003. }
  1004. }
  1005. //-----------------------------------------------------------------------------
  1006. // Purpose:
  1007. // Input :
  1008. // Output :
  1009. //-----------------------------------------------------------------------------
  1010. void CNPC_Manhack::MoveToTarget(float flInterval, const Vector &vMoveTarget)
  1011. {
  1012. if (flInterval <= 0)
  1013. {
  1014. return;
  1015. }
  1016. // -----------------------------------------
  1017. // Don't steer if engine's have stalled
  1018. // -----------------------------------------
  1019. if ( gpGlobals->curtime < m_flEngineStallTime || m_iHealth <= 0 )
  1020. return;
  1021. if ( GetEnemy() != NULL )
  1022. {
  1023. TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() );
  1024. }
  1025. else
  1026. {
  1027. TurnHeadToTarget( flInterval, vMoveTarget );
  1028. }
  1029. // -------------------------------------
  1030. // Move towards our target
  1031. // -------------------------------------
  1032. float myAccel;
  1033. float myZAccel = 300.0f;
  1034. float myDecay = 0.3f;
  1035. Vector targetDir;
  1036. float flDist;
  1037. // If we're bursting, just head straight
  1038. if ( m_flBurstDuration > gpGlobals->curtime )
  1039. {
  1040. float zDist = 500;
  1041. // Steer towards our enemy if we're able to
  1042. if ( GetEnemy() != NULL )
  1043. {
  1044. Vector steerDir = ( GetEnemy()->EyePosition() - GetAbsOrigin() );
  1045. zDist = fabs( steerDir.z );
  1046. VectorNormalize( steerDir );
  1047. float useTime = flInterval;
  1048. while ( useTime > 0.0f )
  1049. {
  1050. m_vecBurstDirection += ( steerDir * 4.0f );
  1051. useTime -= 0.1f;
  1052. }
  1053. m_vecBurstDirection.z = steerDir.z;
  1054. VectorNormalize( m_vecBurstDirection );
  1055. }
  1056. // Debug visualizations
  1057. /*
  1058. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( targetDir * 64.0f ), 255, 0, 0, true, 2.1f );
  1059. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steerDir * 64.0f ), 0, 255, 0, true, 2.1f );
  1060. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( m_vecBurstDirection * 64.0f ), 0, 0, 255, true, 2.1f );
  1061. NDebugOverlay::Cross3D( GetAbsOrigin() , -Vector(8,8,8), Vector(8,8,8), 255, 0, 0, true, 2.1f );
  1062. */
  1063. targetDir = m_vecBurstDirection;
  1064. flDist = FLT_MAX;
  1065. myDecay = 0.3f;
  1066. #ifdef _XBOX
  1067. myAccel = 500;
  1068. #else
  1069. myAccel = 400;
  1070. #endif // _XBOX
  1071. myZAccel = MIN( 500, zDist / flInterval );
  1072. }
  1073. else
  1074. {
  1075. Vector vecCurrentDir = GetCurrentVelocity();
  1076. VectorNormalize( vecCurrentDir );
  1077. targetDir = vMoveTarget - GetAbsOrigin();
  1078. flDist = VectorNormalize( targetDir );
  1079. float flDot = DotProduct( targetDir, vecCurrentDir );
  1080. // Otherwise we should steer towards our goal
  1081. if( flDot > 0.25 )
  1082. {
  1083. // If my target is in front of me, my flight model is a bit more accurate.
  1084. myAccel = 300;
  1085. }
  1086. else
  1087. {
  1088. // Have a harder time correcting my course if I'm currently flying away from my target.
  1089. myAccel = 200;
  1090. }
  1091. }
  1092. // Clamp lateral acceleration
  1093. if ( myAccel > ( flDist / flInterval ) )
  1094. {
  1095. myAccel = flDist / flInterval;
  1096. }
  1097. /*
  1098. // Boost vertical movement
  1099. if ( targetDir.z > 0 )
  1100. {
  1101. // Z acceleration is faster when we thrust upwards.
  1102. // This is to help keep manhacks out of water.
  1103. myZAccel *= 5.0;
  1104. }
  1105. */
  1106. // Clamp vertical movement
  1107. if ( myZAccel > flDist / flInterval )
  1108. {
  1109. myZAccel = flDist / flInterval;
  1110. }
  1111. // Scale by our engine force
  1112. myAccel *= m_fEnginePowerScale;
  1113. myZAccel *= m_fEnginePowerScale;
  1114. MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay );
  1115. // calc relative banking targets
  1116. Vector forward, right;
  1117. GetVectors( &forward, &right, NULL );
  1118. m_vTargetBanking.x = 40 * DotProduct( forward, targetDir );
  1119. m_vTargetBanking.z = 40 * DotProduct( right, targetDir );
  1120. m_vTargetBanking.y = 0.0;
  1121. }
  1122. //-----------------------------------------------------------------------------
  1123. // Purpose: Ignore water if I'm close to my enemy
  1124. // Input :
  1125. // Output :
  1126. //-----------------------------------------------------------------------------
  1127. int CNPC_Manhack::MoveCollisionMask(void)
  1128. {
  1129. return MASK_NPCSOLID;
  1130. }
  1131. //-----------------------------------------------------------------------------
  1132. // Purpose: Make a splash effect
  1133. // Input :
  1134. // Output :
  1135. //-----------------------------------------------------------------------------
  1136. void CNPC_Manhack::Splash( const Vector &vecSplashPos )
  1137. {
  1138. CEffectData data;
  1139. data.m_fFlags = 0;
  1140. data.m_vOrigin = vecSplashPos;
  1141. data.m_vNormal = Vector( 0, 0, 1 );
  1142. data.m_flScale = 8.0f;
  1143. int contents = GetWaterType();
  1144. // Verify we have valid contents
  1145. if ( !( contents & (CONTENTS_SLIME|CONTENTS_WATER)))
  1146. {
  1147. // We're leaving the water so we have to reverify what it was
  1148. trace_t tr;
  1149. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 256 ), (CONTENTS_WATER|CONTENTS_SLIME), this, COLLISION_GROUP_NONE, &tr );
  1150. // Re-validate this
  1151. if ( !(tr.contents&(CONTENTS_WATER|CONTENTS_SLIME)) )
  1152. {
  1153. //NOTENOTE: We called a splash but we don't seem to be near water?
  1154. Assert( 0 );
  1155. return;
  1156. }
  1157. contents = tr.contents;
  1158. }
  1159. // Mark us if we're in slime
  1160. if ( contents & CONTENTS_SLIME )
  1161. {
  1162. data.m_fFlags |= FX_WATER_IN_SLIME;
  1163. }
  1164. DispatchEffect( "watersplash", data );
  1165. }
  1166. //-----------------------------------------------------------------------------
  1167. // Computes the slice bounce velocity
  1168. //-----------------------------------------------------------------------------
  1169. void CNPC_Manhack::ComputeSliceBounceVelocity( CBaseEntity *pHitEntity, trace_t &tr )
  1170. {
  1171. if( pHitEntity->IsAlive() && FClassnameIs( pHitEntity, "func_breakable_surf" ) )
  1172. {
  1173. // We want to see if the manhack hits a breakable pane of glass. To keep from checking
  1174. // The classname of the HitEntity on each impact, we only do this check if we hit
  1175. // something that's alive. Anyway, prevent the manhack bouncing off the pane of glass,
  1176. // since this impact will shatter the glass and let the manhack through.
  1177. return;
  1178. }
  1179. Vector vecDir;
  1180. // If the manhack isn't bouncing away from whatever he sliced, force it.
  1181. VectorSubtract( WorldSpaceCenter(), pHitEntity->WorldSpaceCenter(), vecDir );
  1182. VectorNormalize( vecDir );
  1183. vecDir *= 200;
  1184. vecDir[2] = 0.0f;
  1185. // Knock it away from us
  1186. if ( VPhysicsGetObject() != NULL )
  1187. {
  1188. VPhysicsGetObject()->ApplyForceOffset( vecDir * 4, GetAbsOrigin() );
  1189. }
  1190. // Also set our velocity
  1191. SetCurrentVelocity( vecDir );
  1192. }
  1193. //-----------------------------------------------------------------------------
  1194. // Is the manhack being held?
  1195. //-----------------------------------------------------------------------------
  1196. bool CNPC_Manhack::IsHeldByPhyscannon( )
  1197. {
  1198. return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD);
  1199. }
  1200. //-----------------------------------------------------------------------------
  1201. // Purpose: We've touched something that we can hurt. Slice it!
  1202. // Input :
  1203. // Output :
  1204. //-----------------------------------------------------------------------------
  1205. void CNPC_Manhack::Slice( CBaseEntity *pHitEntity, float flInterval, trace_t &tr )
  1206. {
  1207. // Don't hurt the player if I'm in water
  1208. if( GetWaterLevel() > 0 && pHitEntity->IsPlayer() )
  1209. return;
  1210. // Can't slice players holding it with the phys cannon
  1211. if ( IsHeldByPhyscannon() )
  1212. {
  1213. if ( pHitEntity && (pHitEntity == HasPhysicsAttacker( FLT_MAX )) )
  1214. return;
  1215. }
  1216. if ( pHitEntity->m_takedamage == DAMAGE_NO )
  1217. return;
  1218. // Damage must be scaled by flInterval so framerate independent
  1219. float flDamage = sk_manhack_melee_dmg.GetFloat() * flInterval;
  1220. if ( pHitEntity->IsPlayer() )
  1221. {
  1222. flDamage *= 2.0f;
  1223. }
  1224. // Held manhacks do more damage
  1225. if ( IsHeldByPhyscannon() )
  1226. {
  1227. // Deal 100 damage/sec
  1228. flDamage = 100.0f * flInterval;
  1229. }
  1230. else if ( pHitEntity->IsNPC() && HasPhysicsAttacker( MANHACK_SMASH_TIME ) )
  1231. {
  1232. extern ConVar sk_combine_guard_health;
  1233. // NOTE: The else here is essential.
  1234. // The physics attacker *will* be set even when the manhack is held
  1235. flDamage = sk_combine_guard_health.GetFloat(); // the highest healthed fleshy enemy
  1236. }
  1237. else if ( dynamic_cast<CBaseProp*>(pHitEntity) || dynamic_cast<CBreakable*>(pHitEntity) )
  1238. {
  1239. // If we hit a prop, we want it to break immediately
  1240. flDamage = pHitEntity->GetHealth();
  1241. }
  1242. else if ( pHitEntity->IsNPC() && IRelationType( pHitEntity ) == D_HT && FClassnameIs( pHitEntity, "npc_combine_s" ) )
  1243. {
  1244. flDamage *= 6.0f;
  1245. }
  1246. if (flDamage < 1.0f)
  1247. {
  1248. flDamage = 1.0f;
  1249. }
  1250. CTakeDamageInfo info( this, this, flDamage, DMG_SLASH );
  1251. // check for actual "ownership" of damage
  1252. CBasePlayer *pPlayer = HasPhysicsAttacker( MANHACK_SMASH_TIME );
  1253. if (pPlayer)
  1254. {
  1255. info.SetAttacker( pPlayer );
  1256. }
  1257. Vector dir = (tr.endpos - tr.startpos);
  1258. if ( dir == vec3_origin )
  1259. {
  1260. dir = tr.m_pEnt->GetAbsOrigin() - GetAbsOrigin();
  1261. }
  1262. CalculateMeleeDamageForce( &info, dir, tr.endpos );
  1263. pHitEntity->TakeDamage( info );
  1264. // Spawn some extra blood where we hit
  1265. if ( pHitEntity->BloodColor() == DONT_BLEED )
  1266. {
  1267. CEffectData data;
  1268. Vector velocity = GetCurrentVelocity();
  1269. data.m_vOrigin = tr.endpos;
  1270. data.m_vAngles = GetAbsAngles();
  1271. VectorNormalize( velocity );
  1272. data.m_vNormal = ( tr.plane.normal + velocity ) * 0.5;;
  1273. DispatchEffect( "ManhackSparks", data );
  1274. EmitSound( "NPC_Manhack.Grind" );
  1275. //TODO: What we really want to do is get a material reference and emit the proper sprayage! - jdw
  1276. }
  1277. else
  1278. {
  1279. SpawnBlood(tr.endpos, g_vecAttackDir, pHitEntity->BloodColor(), 6 );
  1280. EmitSound( "NPC_Manhack.Slice" );
  1281. }
  1282. // Pop back a little bit after hitting the player
  1283. ComputeSliceBounceVelocity( pHitEntity, tr );
  1284. // Save off when we last hit something
  1285. m_flLastDamageTime = gpGlobals->curtime;
  1286. // Reset our state and give the player time to react
  1287. StopBurst( true );
  1288. }
  1289. //-----------------------------------------------------------------------------
  1290. // Purpose: We've touched something solid. Just bump it.
  1291. // Input :
  1292. // Output :
  1293. //-----------------------------------------------------------------------------
  1294. void CNPC_Manhack::Bump( CBaseEntity *pHitEntity, float flInterval, trace_t &tr )
  1295. {
  1296. if ( !VPhysicsGetObject() )
  1297. return;
  1298. // Surpressing this behavior
  1299. if ( m_flBumpSuppressTime > gpGlobals->curtime )
  1300. return;
  1301. if ( pHitEntity->GetMoveType() == MOVETYPE_VPHYSICS && pHitEntity->Classify()!=CLASS_MANHACK )
  1302. {
  1303. HitPhysicsObject( pHitEntity );
  1304. }
  1305. // We've hit something so deflect our velocity based on the surface
  1306. // norm of what we've hit
  1307. if (flInterval > 0)
  1308. {
  1309. float moveLen = ( (GetCurrentVelocity() * flInterval) * (1.0 - tr.fraction) ).Length();
  1310. Vector moveVec = moveLen*tr.plane.normal/flInterval;
  1311. // If I'm totally dead, don't bounce me up
  1312. if (m_iHealth <=0 && moveVec.z > 0)
  1313. {
  1314. moveVec.z = 0;
  1315. }
  1316. // If I'm right over the ground don't push down
  1317. if (moveVec.z < 0)
  1318. {
  1319. float floorZ = GetFloorZ(GetAbsOrigin());
  1320. if (abs(GetAbsOrigin().z - floorZ) < 36)
  1321. {
  1322. moveVec.z = 0;
  1323. }
  1324. }
  1325. Vector myUp;
  1326. VPhysicsGetObject()->LocalToWorldVector( &myUp, Vector( 0.0, 0.0, 1.0 ) );
  1327. // plane must be something that could hit the blades
  1328. if (fabs( DotProduct( myUp, tr.plane.normal ) ) < 0.25 )
  1329. {
  1330. CEffectData data;
  1331. Vector velocity = GetCurrentVelocity();
  1332. data.m_vOrigin = tr.endpos;
  1333. data.m_vAngles = GetAbsAngles();
  1334. VectorNormalize( velocity );
  1335. data.m_vNormal = ( tr.plane.normal + velocity ) * 0.5;;
  1336. DispatchEffect( "ManhackSparks", data );
  1337. CBroadcastRecipientFilter filter;
  1338. te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 255, 180, 100, 0, 50, 0.3, 150 );
  1339. // add some spin, but only if we're not already going fast..
  1340. Vector vecVelocity;
  1341. AngularImpulse vecAngVelocity;
  1342. VPhysicsGetObject()->GetVelocity( &vecVelocity, &vecAngVelocity );
  1343. float flDot = DotProduct( myUp, vecAngVelocity );
  1344. if ( fabs(flDot) < 100 )
  1345. {
  1346. //AngularImpulse torque = myUp * (1000 - flDot * 10);
  1347. AngularImpulse torque = myUp * (1000 - flDot * 2);
  1348. VPhysicsGetObject()->ApplyTorqueCenter( torque );
  1349. }
  1350. if (!(m_spawnflags & SF_NPC_GAG))
  1351. {
  1352. EmitSound( "NPC_Manhack.Grind" );
  1353. }
  1354. // For decals and sparks we must trace a line in the direction of the surface norm
  1355. // that we hit.
  1356. trace_t decalTrace;
  1357. AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() - (tr.plane.normal * 24),MASK_SOLID, this, COLLISION_GROUP_NONE, &decalTrace );
  1358. if ( decalTrace.fraction != 1.0 )
  1359. {
  1360. // Leave decal only if colliding horizontally
  1361. if ((DotProduct(Vector(0,0,1),decalTrace.plane.normal)<0.5) && (DotProduct(Vector(0,0,-1),decalTrace.plane.normal)<0.5))
  1362. {
  1363. UTIL_DecalTrace( &decalTrace, "ManhackCut" );
  1364. }
  1365. }
  1366. }
  1367. // See if we will not have a valid surface
  1368. if ( tr.allsolid || tr.startsolid )
  1369. {
  1370. // Build a fake reflection back along our current velocity because we can't know how to reflect off
  1371. // a non-existant surface! -- jdw
  1372. Vector vecRandomDir = RandomVector( -1.0f, 1.0f );
  1373. SetCurrentVelocity( vecRandomDir * 50.0f );
  1374. m_flBumpSuppressTime = gpGlobals->curtime + 0.5f;
  1375. }
  1376. else
  1377. {
  1378. // This is a valid hit and we can deflect properly
  1379. VectorNormalize( moveVec );
  1380. float hitAngle = -DotProduct( tr.plane.normal, -moveVec );
  1381. Vector vReflection = 2.0 * tr.plane.normal * hitAngle + -moveVec;
  1382. float flSpeed = GetCurrentVelocity().Length();
  1383. SetCurrentVelocity( GetCurrentVelocity() + vReflection * flSpeed * 0.5f );
  1384. }
  1385. }
  1386. // -------------------------------------------------------------
  1387. // If I'm on a path check LOS to my next node, and fail on path
  1388. // if I don't have LOS. Note this is the only place I do this,
  1389. // so the manhack has to collide before failing on a path
  1390. // -------------------------------------------------------------
  1391. if (GetNavigator()->IsGoalActive() && !(GetNavigator()->GetPath()->CurWaypointFlags() & bits_WP_TO_PATHCORNER) )
  1392. {
  1393. AIMoveTrace_t moveTrace;
  1394. GetMoveProbe()->MoveLimit( NAV_GROUND, GetAbsOrigin(), GetNavigator()->GetCurWaypointPos(),
  1395. MoveCollisionMask(), GetEnemy(), &moveTrace );
  1396. if (IsMoveBlocked( moveTrace ) &&
  1397. !moveTrace.pObstruction->ClassMatches( GetClassname() ))
  1398. {
  1399. TaskFail(FAIL_NO_ROUTE);
  1400. GetNavigator()->ClearGoal();
  1401. return;
  1402. }
  1403. }
  1404. }
  1405. //-----------------------------------------------------------------------------
  1406. // Purpose:
  1407. // Input :
  1408. // Output :
  1409. //-----------------------------------------------------------------------------
  1410. void CNPC_Manhack::CheckCollisions(float flInterval)
  1411. {
  1412. // Trace forward to see if I hit anything. But trace forward along the
  1413. // owner's view direction if you're being carried.
  1414. Vector vecTraceDir, vecCheckPos;
  1415. VPhysicsGetObject()->GetVelocity( &vecTraceDir, NULL );
  1416. vecTraceDir *= flInterval;
  1417. if ( IsHeldByPhyscannon() )
  1418. {
  1419. CBasePlayer *pCarrier = HasPhysicsAttacker( FLT_MAX );
  1420. if ( pCarrier )
  1421. {
  1422. if ( pCarrier->CollisionProp()->CalcDistanceFromPoint( WorldSpaceCenter() ) < 30 )
  1423. {
  1424. AngleVectors( pCarrier->EyeAngles(), &vecTraceDir, NULL, NULL );
  1425. vecTraceDir *= 40.0f;
  1426. }
  1427. }
  1428. }
  1429. VectorAdd( GetAbsOrigin(), vecTraceDir, vecCheckPos );
  1430. trace_t tr;
  1431. CBaseEntity* pHitEntity = NULL;
  1432. AI_TraceHull( GetAbsOrigin(),
  1433. vecCheckPos,
  1434. GetHullMins(),
  1435. GetHullMaxs(),
  1436. MoveCollisionMask(),
  1437. this,
  1438. COLLISION_GROUP_NONE,
  1439. &tr );
  1440. if ( (tr.fraction != 1.0 || tr.startsolid) && tr.m_pEnt)
  1441. {
  1442. PhysicsMarkEntitiesAsTouching( tr.m_pEnt, tr );
  1443. pHitEntity = tr.m_pEnt;
  1444. if( m_bHeld && tr.m_pEnt->MyNPCPointer() && tr.m_pEnt->MyNPCPointer()->IsPlayerAlly() )
  1445. {
  1446. // Don't slice Alyx when she approaches to hack. We need a better solution for this!!
  1447. //Msg("Ignoring!\n");
  1448. return;
  1449. }
  1450. if ( pHitEntity != NULL &&
  1451. pHitEntity->m_takedamage == DAMAGE_YES &&
  1452. pHitEntity->Classify() != CLASS_MANHACK &&
  1453. gpGlobals->curtime > m_flWaterSuspendTime )
  1454. {
  1455. // Slice this thing
  1456. Slice( pHitEntity, flInterval, tr );
  1457. m_flBladeSpeed = 20.0;
  1458. }
  1459. else
  1460. {
  1461. // Just bump into this thing.
  1462. Bump( pHitEntity, flInterval, tr );
  1463. m_flBladeSpeed = 20.0;
  1464. }
  1465. }
  1466. }
  1467. //-----------------------------------------------------------------------------
  1468. // Purpose:
  1469. // Input :
  1470. // Output :
  1471. //-----------------------------------------------------------------------------
  1472. #define tempTIME_STEP = 0.5;
  1473. void CNPC_Manhack::PlayFlySound(void)
  1474. {
  1475. float flEnemyDist;
  1476. if( GetEnemy() )
  1477. {
  1478. flEnemyDist = (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length();
  1479. }
  1480. else
  1481. {
  1482. flEnemyDist = FLT_MAX;
  1483. }
  1484. if( m_spawnflags & SF_NPC_GAG )
  1485. {
  1486. // Quiet!
  1487. return;
  1488. }
  1489. if( m_flWaterSuspendTime > gpGlobals->curtime )
  1490. {
  1491. // Just went in water. Slow the motor!!
  1492. if( m_bDirtyPitch )
  1493. {
  1494. m_nEnginePitch1 = MANHACK_WATER_PITCH1;
  1495. m_flEnginePitch1Time = gpGlobals->curtime + 0.5f;
  1496. m_nEnginePitch2 = MANHACK_WATER_PITCH2;
  1497. m_flEnginePitch2Time = gpGlobals->curtime + 0.5f;
  1498. m_bDirtyPitch = false;
  1499. }
  1500. }
  1501. // Spin sound based on distance from enemy (unless we're crashing)
  1502. else if (GetEnemy() && IsAlive() )
  1503. {
  1504. if( flEnemyDist < MANHACK_PITCH_DIST1 )
  1505. {
  1506. // recalculate pitch.
  1507. int iPitch1, iPitch2;
  1508. float flDistFactor;
  1509. flDistFactor = MIN( 1.0, 1 - flEnemyDist / MANHACK_PITCH_DIST1 );
  1510. iPitch1 = MANHACK_MIN_PITCH1 + ( ( MANHACK_MAX_PITCH1 - MANHACK_MIN_PITCH1 ) * flDistFactor);
  1511. // NOTE: MANHACK_PITCH_DIST2 must be < MANHACK_PITCH_DIST1
  1512. flDistFactor = MIN( 1.0, 1 - flEnemyDist / MANHACK_PITCH_DIST2 );
  1513. iPitch2 = MANHACK_MIN_PITCH2 + ( ( MANHACK_MAX_PITCH2 - MANHACK_MIN_PITCH2 ) * flDistFactor);
  1514. m_nEnginePitch1 = iPitch1;
  1515. m_flEnginePitch1Time = gpGlobals->curtime + 0.1f;
  1516. m_nEnginePitch2 = iPitch2;
  1517. m_flEnginePitch2Time = gpGlobals->curtime + 0.1f;
  1518. m_bDirtyPitch = true;
  1519. }
  1520. else if( m_bDirtyPitch )
  1521. {
  1522. m_nEnginePitch1 = MANHACK_MIN_PITCH1;
  1523. m_flEnginePitch1Time = gpGlobals->curtime + 0.1f;
  1524. m_nEnginePitch2 = MANHACK_MIN_PITCH2;
  1525. m_flEnginePitch2Time = gpGlobals->curtime + 0.2f;
  1526. m_bDirtyPitch = false;
  1527. }
  1528. }
  1529. // If no enemy just play low sound
  1530. else if( IsAlive() && m_bDirtyPitch )
  1531. {
  1532. m_nEnginePitch1 = MANHACK_MIN_PITCH1;
  1533. m_flEnginePitch1Time = gpGlobals->curtime + 0.1f;
  1534. m_nEnginePitch2 = MANHACK_MIN_PITCH2;
  1535. m_flEnginePitch2Time = gpGlobals->curtime + 0.2f;
  1536. m_bDirtyPitch = false;
  1537. }
  1538. // Play special engine every once in a while
  1539. if (gpGlobals->curtime > m_flNextEngineSoundTime && flEnemyDist < 48)
  1540. {
  1541. m_flNextEngineSoundTime = gpGlobals->curtime + random->RandomFloat( 3.0, 10.0 );
  1542. EmitSound( "NPC_Manhack.EngineNoise" );
  1543. }
  1544. }
  1545. //-----------------------------------------------------------------------------
  1546. // Purpose:
  1547. // Input :
  1548. // Output :
  1549. //-----------------------------------------------------------------------------
  1550. void CNPC_Manhack::MoveExecute_Alive(float flInterval)
  1551. {
  1552. PhysicsCheckWaterTransition();
  1553. Vector vCurrentVelocity = GetCurrentVelocity();
  1554. // FIXME: move this
  1555. VPhysicsGetObject()->Wake();
  1556. if( m_fEnginePowerScale < GetMaxEnginePower() && gpGlobals->curtime > m_flWaterSuspendTime )
  1557. {
  1558. // Power is low, and we're no longer stuck in water, so bring power up.
  1559. m_fEnginePowerScale += 0.05;
  1560. }
  1561. // ----------------------------------------------------------------------------------------
  1562. // Add time-coherent noise to the current velocity so that it never looks bolted in place.
  1563. // ----------------------------------------------------------------------------------------
  1564. float noiseScale = 7.0f;
  1565. if ( (CBaseEntity*)GetEnemy() )
  1566. {
  1567. float flDist = (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length2D();
  1568. if( flDist < MANHACK_CHARGE_MIN_DIST )
  1569. {
  1570. // Less noise up close.
  1571. noiseScale = 2.0;
  1572. }
  1573. if ( IsInEffectiveTargetZone( GetEnemy() ) && flDist < MANHACK_CHARGE_MIN_DIST && gpGlobals->curtime > m_flNextBurstTime )
  1574. {
  1575. Vector vecCurrentDir = GetCurrentVelocity();
  1576. VectorNormalize( vecCurrentDir );
  1577. Vector vecToEnemy = ( GetEnemy()->EyePosition() - WorldSpaceCenter() );
  1578. VectorNormalize( vecToEnemy );
  1579. float flDot = DotProduct( vecCurrentDir, vecToEnemy );
  1580. if ( flDot > 0.75 )
  1581. {
  1582. Vector offsetDir = ( vecToEnemy - vecCurrentDir );
  1583. VectorNormalize( offsetDir );
  1584. Vector offsetSpeed = GetCurrentVelocity() * flDot;
  1585. //FIXME: This code sucks -- jdw
  1586. offsetDir.z = 0.0f;
  1587. m_vForceVelocity += ( offsetDir * ( offsetSpeed.Length2D() * 0.25f ) );
  1588. // Commit to the attack- no steering for about a second
  1589. StartBurst( vecToEnemy );
  1590. SetEyeState( MANHACK_EYE_STATE_CHARGE );
  1591. }
  1592. }
  1593. if ( gpGlobals->curtime > m_flBurstDuration )
  1594. {
  1595. ShowHostile( false );
  1596. }
  1597. }
  1598. // ----------------------------------------------------------------------------------------
  1599. // Add in any forced velocity
  1600. // ----------------------------------------------------------------------------------------
  1601. SetCurrentVelocity( vCurrentVelocity + m_vForceVelocity );
  1602. m_vForceVelocity = vec3_origin;
  1603. if( !m_bHackedByAlyx || GetEnemy() )
  1604. {
  1605. // If hacked and no enemy, don't drift!
  1606. AddNoiseToVelocity( noiseScale );
  1607. }
  1608. LimitSpeed( 200, ManhackMaxSpeed() );
  1609. if( m_flWaterSuspendTime > gpGlobals->curtime )
  1610. {
  1611. if( UTIL_PointContents( GetAbsOrigin() ) & (CONTENTS_WATER|CONTENTS_SLIME) )
  1612. {
  1613. // Ooops, we're submerged somehow. Move upwards until our origin is out of the water.
  1614. m_vCurrentVelocity.z = 20.0;
  1615. }
  1616. else
  1617. {
  1618. // Skimming the surface. Forbid any movement on Z
  1619. m_vCurrentVelocity.z = 0.0;
  1620. }
  1621. }
  1622. else if( GetWaterLevel() > 0 )
  1623. {
  1624. // Allow the manhack to lift off, but not to go deeper.
  1625. m_vCurrentVelocity.z = MAX( m_vCurrentVelocity.z, 0 );
  1626. }
  1627. CheckCollisions(flInterval);
  1628. // Blend out desired velocity when launched by the physcannon
  1629. if ( HasPhysicsAttacker( MANHACK_SMASH_TIME ) && (!IsHeldByPhyscannon()) && VPhysicsGetObject() )
  1630. {
  1631. Vector vecCurrentVelocity;
  1632. VPhysicsGetObject()->GetVelocity( &vecCurrentVelocity, NULL );
  1633. float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / MANHACK_SMASH_TIME;
  1634. flLerpFactor = clamp( flLerpFactor, 0.0f, 1.0f );
  1635. flLerpFactor = SimpleSplineRemapVal( flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f );
  1636. VectorLerp( vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity );
  1637. }
  1638. QAngle angles = GetLocalAngles();
  1639. // ------------------------------------------
  1640. // Stalling
  1641. // ------------------------------------------
  1642. if (gpGlobals->curtime < m_flEngineStallTime)
  1643. {
  1644. /*
  1645. // If I'm stalled add random noise
  1646. angles.x += -20+(random->RandomInt(-10,10));
  1647. angles.z += -20+(random->RandomInt(0,40));
  1648. TurnHeadRandomly(flInterval);
  1649. */
  1650. }
  1651. else
  1652. {
  1653. // Make frame rate independent
  1654. float iRate = 0.5;
  1655. float timeToUse = flInterval;
  1656. while (timeToUse > 0)
  1657. {
  1658. m_vCurrentBanking.x = (iRate * m_vCurrentBanking.x) + (1 - iRate)*(m_vTargetBanking.x);
  1659. m_vCurrentBanking.z = (iRate * m_vCurrentBanking.z) + (1 - iRate)*(m_vTargetBanking.z);
  1660. timeToUse = -0.1;
  1661. }
  1662. angles.x = m_vCurrentBanking.x;
  1663. angles.z = m_vCurrentBanking.z;
  1664. angles.y = 0;
  1665. #if 0
  1666. // Using our steering if we're not otherwise affecting our panels
  1667. if ( m_flEngineStallTime < gpGlobals->curtime && m_flBurstDuration < gpGlobals->curtime )
  1668. {
  1669. Vector delta( 10 * AngleDiff( m_vTargetBanking.x, m_vCurrentBanking.x ), -10 * AngleDiff( m_vTargetBanking.z, m_vCurrentBanking.z ), 0 );
  1670. //Vector delta( 3 * AngleNormalize( m_vCurrentBanking.x ), -4 * AngleNormalize( m_vCurrentBanking.z ), 0 );
  1671. VectorYawRotate( delta, -m_fHeadYaw, delta );
  1672. // DevMsg("%.0f %.0f\n", delta.x, delta.y );
  1673. SetPoseParameter( m_iPanel1, -delta.x - delta.y * 2);
  1674. SetPoseParameter( m_iPanel2, -delta.x + delta.y * 2);
  1675. SetPoseParameter( m_iPanel3, delta.x + delta.y * 2);
  1676. SetPoseParameter( m_iPanel4, delta.x - delta.y * 2);
  1677. //SetPoseParameter( m_iPanel1, -delta.x );
  1678. //SetPoseParameter( m_iPanel2, -delta.x );
  1679. //SetPoseParameter( m_iPanel3, delta.x );
  1680. //SetPoseParameter( m_iPanel4, delta.x );
  1681. }
  1682. #endif
  1683. }
  1684. // SetLocalAngles( angles );
  1685. if( m_lifeState != LIFE_DEAD )
  1686. {
  1687. PlayFlySound();
  1688. // SpinBlades( flInterval );
  1689. // WalkMove( GetCurrentVelocity() * flInterval, MASK_NPCSOLID );
  1690. }
  1691. // NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, -10 ), 0, 255, 0, true, 0.1);
  1692. }
  1693. //-----------------------------------------------------------------------------
  1694. // Purpose:
  1695. // Input :
  1696. // Output :
  1697. //-----------------------------------------------------------------------------
  1698. void CNPC_Manhack::SpinBlades(float flInterval)
  1699. {
  1700. if (!m_bBladesActive)
  1701. {
  1702. SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_OFF );
  1703. SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_OFF );
  1704. m_flBladeSpeed = 0.0;
  1705. m_flPlaybackRate = 1.0;
  1706. return;
  1707. }
  1708. if ( IsFlyingActivity( GetActivity() ) )
  1709. {
  1710. // Blades may only ramp up while the engine is running
  1711. if ( m_flEngineStallTime < gpGlobals->curtime )
  1712. {
  1713. if (m_flBladeSpeed < 10)
  1714. {
  1715. m_flBladeSpeed = m_flBladeSpeed * 2 + 1;
  1716. }
  1717. else
  1718. {
  1719. // accelerate engine
  1720. m_flBladeSpeed = m_flBladeSpeed + 80 * flInterval;
  1721. }
  1722. }
  1723. if (m_flBladeSpeed > 100)
  1724. {
  1725. m_flBladeSpeed = 100;
  1726. }
  1727. // blend through blades, blades+blur, blur
  1728. if (m_flBladeSpeed < 20)
  1729. {
  1730. SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_ON );
  1731. SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_OFF );
  1732. }
  1733. else if (m_flBladeSpeed < 40)
  1734. {
  1735. SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_ON );
  1736. SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_ON );
  1737. }
  1738. else
  1739. {
  1740. SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_OFF );
  1741. SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_ON );
  1742. }
  1743. m_flPlaybackRate = m_flBladeSpeed / 100.0;
  1744. }
  1745. else
  1746. {
  1747. m_flBladeSpeed = 0.0;
  1748. }
  1749. }
  1750. //-----------------------------------------------------------------------------
  1751. // Purpose: Smokes and sparks, exploding periodically. Eventually it goes away.
  1752. //-----------------------------------------------------------------------------
  1753. void CNPC_Manhack::MoveExecute_Dead(float flInterval)
  1754. {
  1755. if( GetWaterLevel() > 0 )
  1756. {
  1757. // No movement if sinking in water.
  1758. return;
  1759. }
  1760. // Periodically emit smoke.
  1761. if (gpGlobals->curtime > m_fSmokeTime && GetWaterLevel() == 0)
  1762. {
  1763. // UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10);
  1764. m_fSmokeTime = gpGlobals->curtime + random->RandomFloat( 0.1, 0.3);
  1765. }
  1766. // Periodically emit sparks.
  1767. if (gpGlobals->curtime > m_fSparkTime)
  1768. {
  1769. g_pEffects->Sparks( GetAbsOrigin() );
  1770. m_fSparkTime = gpGlobals->curtime + random->RandomFloat(0.1, 0.3);
  1771. }
  1772. Vector newVelocity = GetCurrentVelocity();
  1773. // accelerate faster and faster when dying
  1774. newVelocity = newVelocity + (newVelocity * 1.5 * flInterval );
  1775. // Lose lift
  1776. newVelocity.z -= 0.02*flInterval*(GetCurrentGravity());
  1777. // ----------------------------------------------------------------------------------------
  1778. // Add in any forced velocity
  1779. // ----------------------------------------------------------------------------------------
  1780. newVelocity += m_vForceVelocity;
  1781. SetCurrentVelocity( newVelocity );
  1782. m_vForceVelocity = vec3_origin;
  1783. // Lots of noise!! Out of control!
  1784. AddNoiseToVelocity( 5.0 );
  1785. // ----------------------
  1786. // Limit overall speed
  1787. // ----------------------
  1788. LimitSpeed( -1, MANHACK_MAX_SPEED * 2.0 );
  1789. QAngle angles = GetLocalAngles();
  1790. // ------------------------------------------
  1791. // If I'm dying, add random banking noise
  1792. // ------------------------------------------
  1793. angles.x += -20+(random->RandomInt(0,40));
  1794. angles.z += -20+(random->RandomInt(0,40));
  1795. CheckCollisions(flInterval);
  1796. PlayFlySound();
  1797. // SetLocalAngles( angles );
  1798. WalkMove( GetCurrentVelocity() * flInterval,MASK_NPCSOLID );
  1799. }
  1800. //-----------------------------------------------------------------------------
  1801. // Purpose:
  1802. //-----------------------------------------------------------------------------
  1803. void CNPC_Manhack::Precache(void)
  1804. {
  1805. //
  1806. // Model.
  1807. //
  1808. PrecacheModel("models/manhack.mdl");
  1809. PrecacheModel( MANHACK_GLOW_SPRITE );
  1810. PropBreakablePrecacheAll( MAKE_STRING("models/manhack.mdl") );
  1811. PrecacheScriptSound( "NPC_Manhack.Die" );
  1812. PrecacheScriptSound( "NPC_Manhack.Bat" );
  1813. PrecacheScriptSound( "NPC_Manhack.Grind" );
  1814. PrecacheScriptSound( "NPC_Manhack.Slice" );
  1815. PrecacheScriptSound( "NPC_Manhack.EngineNoise" );
  1816. PrecacheScriptSound( "NPC_Manhack.Unpack" );
  1817. PrecacheScriptSound( "NPC_Manhack.ChargeAnnounce" );
  1818. PrecacheScriptSound( "NPC_Manhack.ChargeEnd" );
  1819. PrecacheScriptSound( "NPC_Manhack.Stunned" );
  1820. // Sounds used on Client:
  1821. PrecacheScriptSound( "NPC_Manhack.EngineSound1" );
  1822. PrecacheScriptSound( "NPC_Manhack.EngineSound2" );
  1823. PrecacheScriptSound( "NPC_Manhack.BladeSound" );
  1824. BaseClass::Precache();
  1825. }
  1826. //-----------------------------------------------------------------------------
  1827. // Purpose:
  1828. // Input :
  1829. // Output :
  1830. //-----------------------------------------------------------------------------
  1831. void CNPC_Manhack::GatherEnemyConditions( CBaseEntity *pEnemy )
  1832. {
  1833. // The Manhack "regroups" when its in attack range but to
  1834. // far above or below its enemy. Set the start attack
  1835. // condition if we are far enough away from the enemy
  1836. // or at the correct height
  1837. // Don't bother with Z if the enemy is in a vehicle
  1838. float fl2DDist = 60.0f;
  1839. float flZDist = 12.0f;
  1840. if ( GetEnemy()->IsPlayer() && assert_cast< CBasePlayer * >(GetEnemy())->IsInAVehicle() )
  1841. {
  1842. flZDist = 24.0f;
  1843. }
  1844. if ((GetAbsOrigin() - pEnemy->GetAbsOrigin()).Length2D() > fl2DDist)
  1845. {
  1846. SetCondition(COND_MANHACK_START_ATTACK);
  1847. }
  1848. else
  1849. {
  1850. float targetZ = pEnemy->EyePosition().z;
  1851. if (fabs(GetAbsOrigin().z - targetZ) < flZDist)
  1852. {
  1853. SetCondition(COND_MANHACK_START_ATTACK);
  1854. }
  1855. }
  1856. BaseClass::GatherEnemyConditions(pEnemy);
  1857. }
  1858. //-----------------------------------------------------------------------------
  1859. // Purpose: For innate melee attack
  1860. // Input :
  1861. // Output :
  1862. //-----------------------------------------------------------------------------
  1863. int CNPC_Manhack::MeleeAttack1Conditions( float flDot, float flDist )
  1864. {
  1865. if ( GetEnemy() == NULL )
  1866. return COND_NONE;
  1867. //TODO: We could also decide if we want to back up here
  1868. if ( m_flNextBurstTime > gpGlobals->curtime )
  1869. return COND_NONE;
  1870. float flMaxDist = 45;
  1871. float flMinDist = 24;
  1872. bool bEnemyInVehicle = GetEnemy()->IsPlayer() && assert_cast< CBasePlayer * >(GetEnemy())->IsInAVehicle();
  1873. if ( GetEnemy()->IsPlayer() && assert_cast< CBasePlayer * >(GetEnemy())->IsInAVehicle() )
  1874. {
  1875. flMinDist = 0;
  1876. flMaxDist = 200.0f;
  1877. }
  1878. if (flDist > flMaxDist)
  1879. {
  1880. return COND_TOO_FAR_TO_ATTACK;
  1881. }
  1882. if (flDist < flMinDist)
  1883. {
  1884. return COND_TOO_CLOSE_TO_ATTACK;
  1885. }
  1886. // Check our current velocity and speed, if it's too far off, we need to settle
  1887. // Don't bother with Z if the enemy is in a vehicle
  1888. if ( bEnemyInVehicle )
  1889. {
  1890. return COND_CAN_MELEE_ATTACK1;
  1891. }
  1892. // Assume the this check is in regards to my current enemy
  1893. // for the Manhacks spetial condition
  1894. float deltaZ = GetAbsOrigin().z - GetEnemy()->EyePosition().z;
  1895. if ( (deltaZ > 12.0f) || (deltaZ < -24.0f) )
  1896. {
  1897. return COND_TOO_CLOSE_TO_ATTACK;
  1898. }
  1899. return COND_CAN_MELEE_ATTACK1;
  1900. }
  1901. //-----------------------------------------------------------------------------
  1902. // Purpose:
  1903. // Input : *pTask -
  1904. //-----------------------------------------------------------------------------
  1905. void CNPC_Manhack::RunTask( const Task_t *pTask )
  1906. {
  1907. switch ( pTask->iTask )
  1908. {
  1909. // Override this task so we go for the enemy at eye level
  1910. case TASK_MANHACK_HOVER:
  1911. {
  1912. break;
  1913. }
  1914. // If my enemy has moved significantly, update my path
  1915. case TASK_WAIT_FOR_MOVEMENT:
  1916. {
  1917. CBaseEntity *pEnemy = GetEnemy();
  1918. if (pEnemy &&
  1919. (GetCurSchedule()->GetId() == SCHED_CHASE_ENEMY) &&
  1920. GetNavigator()->IsGoalActive() )
  1921. {
  1922. Vector vecEnemyPosition;
  1923. vecEnemyPosition = pEnemy->EyePosition();
  1924. if ( GetNavigator()->GetGoalPos().DistToSqr(vecEnemyPosition) > 40 * 40 )
  1925. {
  1926. GetNavigator()->UpdateGoalPos( vecEnemyPosition );
  1927. }
  1928. }
  1929. BaseClass::RunTask(pTask);
  1930. break;
  1931. }
  1932. case TASK_MANHACK_MOVEAT_SAVEPOSITION:
  1933. {
  1934. // do the movement thingy
  1935. // NDebugOverlay::Line( GetAbsOrigin(), m_vSavePosition, 0, 255, 0, true, 0.1);
  1936. Vector dir = (m_vSavePosition - GetAbsOrigin());
  1937. float dist = VectorNormalize( dir );
  1938. float t = m_fSwarmMoveTime - gpGlobals->curtime;
  1939. if (t < 0.1)
  1940. {
  1941. if (dist > 256)
  1942. {
  1943. TaskFail( FAIL_NO_ROUTE );
  1944. }
  1945. else
  1946. {
  1947. TaskComplete();
  1948. }
  1949. }
  1950. else if (dist < 64)
  1951. {
  1952. m_vSwarmMoveTarget = GetAbsOrigin() - Vector( -dir.y, dir.x, 0 ) * 4;
  1953. }
  1954. else
  1955. {
  1956. m_vSwarmMoveTarget = GetAbsOrigin() + dir * 10;
  1957. }
  1958. break;
  1959. }
  1960. default:
  1961. {
  1962. BaseClass::RunTask(pTask);
  1963. }
  1964. }
  1965. }
  1966. //-----------------------------------------------------------------------------
  1967. // Purpose:
  1968. //-----------------------------------------------------------------------------
  1969. void CNPC_Manhack::Spawn(void)
  1970. {
  1971. Precache();
  1972. #ifdef _XBOX
  1973. // Always fade the corpse
  1974. AddSpawnFlags( SF_NPC_FADE_CORPSE );
  1975. #endif // _XBOX
  1976. SetModel( "models/manhack.mdl" );
  1977. SetHullType(HULL_TINY_CENTERED);
  1978. SetHullSizeNormal();
  1979. SetSolid( SOLID_BBOX );
  1980. AddSolidFlags( FSOLID_NOT_STANDABLE );
  1981. if ( HasSpawnFlags( SF_MANHACK_CARRIED ) )
  1982. {
  1983. AddSolidFlags( FSOLID_NOT_SOLID );
  1984. SetMoveType( MOVETYPE_NONE );
  1985. }
  1986. else
  1987. {
  1988. SetMoveType( MOVETYPE_VPHYSICS );
  1989. }
  1990. m_iHealth = sk_manhack_health.GetFloat();
  1991. SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin.
  1992. m_flFieldOfView = VIEW_FIELD_FULL;
  1993. m_NPCState = NPC_STATE_NONE;
  1994. if ( m_spawnflags & SF_MANHACK_USE_AIR_NODES)
  1995. {
  1996. SetNavType(NAV_FLY);
  1997. }
  1998. else
  1999. {
  2000. SetNavType(NAV_GROUND);
  2001. }
  2002. AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL );
  2003. AddEffects( EF_NOSHADOW );
  2004. SetBloodColor( DONT_BLEED );
  2005. SetCurrentVelocity( vec3_origin );
  2006. m_vForceVelocity.Init();
  2007. m_vCurrentBanking.Init();
  2008. m_vTargetBanking.Init();
  2009. m_flNextBurstTime = gpGlobals->curtime;
  2010. CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_MOVE_FLY | bits_CAP_SQUAD );
  2011. m_flNextEngineSoundTime = gpGlobals->curtime;
  2012. m_flWaterSuspendTime = gpGlobals->curtime;
  2013. m_flEngineStallTime = gpGlobals->curtime;
  2014. m_fForceMoveTime = gpGlobals->curtime;
  2015. m_vForceMoveTarget = vec3_origin;
  2016. m_fSwarmMoveTime = gpGlobals->curtime;
  2017. m_vSwarmMoveTarget = vec3_origin;
  2018. m_nLastSpinSound = -1;
  2019. m_fSmokeTime = 0;
  2020. m_fSparkTime = 0;
  2021. // Set the noise mod to huge numbers right now, in case this manhack starts out waiting for a script
  2022. // for instance, we don't want him to bob whilst he's waiting for a script. This allows designers
  2023. // to 'hide' manhacks in small places. (sjb)
  2024. SetNoiseMod( MANHACK_NOISEMOD_HIDE, MANHACK_NOISEMOD_HIDE, MANHACK_NOISEMOD_HIDE );
  2025. // Start out with full power!
  2026. m_fEnginePowerScale = GetMaxEnginePower();
  2027. // find panels
  2028. m_iPanel1 = LookupPoseParameter( "Panel1" );
  2029. m_iPanel2 = LookupPoseParameter( "Panel2" );
  2030. m_iPanel3 = LookupPoseParameter( "Panel3" );
  2031. m_iPanel4 = LookupPoseParameter( "Panel4" );
  2032. m_fHeadYaw = 0;
  2033. NPCInit();
  2034. // Manhacks are designed to slam into things, so don't take much damage from it!
  2035. SetImpactEnergyScale( 0.001 );
  2036. // Manhacks get 30 seconds worth of free knowledge.
  2037. GetEnemies()->SetFreeKnowledgeDuration( 30.0 );
  2038. // don't be an NPC, we want to collide with debris stuff
  2039. SetCollisionGroup( COLLISION_GROUP_NONE );
  2040. m_bHeld = false;
  2041. m_bHackedByAlyx = false;
  2042. StopLoitering();
  2043. }
  2044. //-----------------------------------------------------------------------------
  2045. // Purpose:
  2046. //-----------------------------------------------------------------------------
  2047. void CNPC_Manhack::StartEye( void )
  2048. {
  2049. //Create our Eye sprite
  2050. if ( m_pEyeGlow == NULL )
  2051. {
  2052. m_pEyeGlow = CSprite::SpriteCreate( MANHACK_GLOW_SPRITE, GetLocalOrigin(), false );
  2053. m_pEyeGlow->SetAttachment( this, LookupAttachment( "Eye" ) );
  2054. if( m_bHackedByAlyx )
  2055. {
  2056. m_pEyeGlow->SetTransparency( kRenderTransAdd, 0, 255, 0, 128, kRenderFxNoDissipation );
  2057. m_pEyeGlow->SetColor( 0, 255, 0 );
  2058. }
  2059. else
  2060. {
  2061. m_pEyeGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation );
  2062. m_pEyeGlow->SetColor( 255, 0, 0 );
  2063. }
  2064. m_pEyeGlow->SetBrightness( 164, 0.1f );
  2065. m_pEyeGlow->SetScale( 0.25f, 0.1f );
  2066. m_pEyeGlow->SetAsTemporary();
  2067. }
  2068. //Create our light sprite
  2069. if ( m_pLightGlow == NULL )
  2070. {
  2071. m_pLightGlow = CSprite::SpriteCreate( MANHACK_GLOW_SPRITE, GetLocalOrigin(), false );
  2072. m_pLightGlow->SetAttachment( this, LookupAttachment( "Light" ) );
  2073. if( m_bHackedByAlyx )
  2074. {
  2075. m_pLightGlow->SetTransparency( kRenderTransAdd, 0, 255, 0, 128, kRenderFxNoDissipation );
  2076. m_pLightGlow->SetColor( 0, 255, 0 );
  2077. }
  2078. else
  2079. {
  2080. m_pLightGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation );
  2081. m_pLightGlow->SetColor( 255, 0, 0 );
  2082. }
  2083. m_pLightGlow->SetBrightness( 164, 0.1f );
  2084. m_pLightGlow->SetScale( 0.25f, 0.1f );
  2085. m_pLightGlow->SetAsTemporary();
  2086. }
  2087. }
  2088. //-----------------------------------------------------------------------------
  2089. void CNPC_Manhack::Activate()
  2090. {
  2091. BaseClass::Activate();
  2092. if ( IsAlive() )
  2093. {
  2094. StartEye();
  2095. }
  2096. }
  2097. //-----------------------------------------------------------------------------
  2098. // Purpose: Get the engine sound started. Unless we're not supposed to have it on yet!
  2099. //-----------------------------------------------------------------------------
  2100. void CNPC_Manhack::PostNPCInit( void )
  2101. {
  2102. // SetAbsVelocity( vec3_origin );
  2103. m_bBladesActive = (m_spawnflags & (SF_MANHACK_PACKED_UP|SF_MANHACK_CARRIED)) ? false : true;
  2104. BladesInit();
  2105. }
  2106. void CNPC_Manhack::BladesInit()
  2107. {
  2108. if( !m_bBladesActive )
  2109. {
  2110. // manhack is packed up, so has no power of its own.
  2111. // don't start the engine sounds.
  2112. // make us fall a little slower than we should, for visual's sake
  2113. SetGravity( UTIL_ScaleForGravity( 400 ) );
  2114. SetActivity( ACT_IDLE );
  2115. }
  2116. else
  2117. {
  2118. bool engineSound = (m_spawnflags & SF_NPC_GAG) ? false : true;
  2119. StartEngine( engineSound );
  2120. SetActivity( ACT_FLY );
  2121. }
  2122. }
  2123. //-----------------------------------------------------------------------------
  2124. // Crank up the engine!
  2125. //-----------------------------------------------------------------------------
  2126. void CNPC_Manhack::StartEngine( bool fStartSound )
  2127. {
  2128. if( fStartSound )
  2129. {
  2130. SoundInit();
  2131. }
  2132. // Make the blade appear.
  2133. SetBodygroup( 1, 1 );
  2134. // Pop up a little if falling fast!
  2135. Vector vecVelocity;
  2136. GetVelocity( &vecVelocity, NULL );
  2137. if( ( m_spawnflags & SF_MANHACK_PACKED_UP ) && vecVelocity.z < 0 )
  2138. {
  2139. // DevMsg(" POP UP \n" );
  2140. // ApplyAbsVelocityImpulse( Vector(0,0,-vecVelocity.z*0.75) );
  2141. }
  2142. // Under powered flight now.
  2143. // SetMoveType( MOVETYPE_STEP );
  2144. // SetGravity( MANHACK_GRAVITY );
  2145. AddFlag( FL_FLY );
  2146. }
  2147. //-----------------------------------------------------------------------------
  2148. // Purpose: Start the manhack's engine sound.
  2149. //-----------------------------------------------------------------------------
  2150. void CNPC_Manhack::SoundInit( void )
  2151. {
  2152. m_nEnginePitch1 = MANHACK_MIN_PITCH1;
  2153. m_flEnginePitch1Time = gpGlobals->curtime;
  2154. m_nEnginePitch2 = MANHACK_MIN_PITCH2;
  2155. m_flEnginePitch2Time = gpGlobals->curtime;
  2156. }
  2157. //-----------------------------------------------------------------------------
  2158. // Purpose:
  2159. //-----------------------------------------------------------------------------
  2160. void CNPC_Manhack::StopLoopingSounds(void)
  2161. {
  2162. BaseClass::StopLoopingSounds();
  2163. m_nEnginePitch1 = -1;
  2164. m_flEnginePitch1Time = gpGlobals->curtime;
  2165. m_nEnginePitch2 = -1;
  2166. m_flEnginePitch2Time = gpGlobals->curtime;
  2167. }
  2168. //-----------------------------------------------------------------------------
  2169. // Purpose:
  2170. // Input : pTask -
  2171. //-----------------------------------------------------------------------------
  2172. void CNPC_Manhack::StartTask( const Task_t *pTask )
  2173. {
  2174. switch (pTask->iTask)
  2175. {
  2176. case TASK_MANHACK_UNPACK:
  2177. {
  2178. // Just play a sound for now.
  2179. EmitSound( "NPC_Manhack.Unpack" );
  2180. TaskComplete();
  2181. }
  2182. break;
  2183. case TASK_MANHACK_HOVER:
  2184. break;
  2185. case TASK_MOVE_TO_TARGET_RANGE:
  2186. case TASK_GET_PATH_TO_GOAL:
  2187. case TASK_GET_PATH_TO_ENEMY_LKP:
  2188. case TASK_GET_PATH_TO_PLAYER:
  2189. {
  2190. BaseClass::StartTask( pTask );
  2191. /*
  2192. // FIXME: why were these tasks considered bad?
  2193. _asm
  2194. {
  2195. int 3;
  2196. int 5;
  2197. }
  2198. */
  2199. }
  2200. break;
  2201. case TASK_FACE_IDEAL:
  2202. {
  2203. // this shouldn't ever happen, but if it does, don't choke
  2204. TaskComplete();
  2205. }
  2206. break;
  2207. case TASK_GET_PATH_TO_ENEMY:
  2208. {
  2209. if (IsUnreachable(GetEnemy()))
  2210. {
  2211. TaskFail(FAIL_NO_ROUTE);
  2212. return;
  2213. }
  2214. CBaseEntity *pEnemy = GetEnemy();
  2215. if ( pEnemy == NULL )
  2216. {
  2217. TaskFail(FAIL_NO_ENEMY);
  2218. return;
  2219. }
  2220. if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) )
  2221. {
  2222. TaskComplete();
  2223. }
  2224. else
  2225. {
  2226. // no way to get there =(
  2227. DevWarning( 2, "GetPathToEnemy failed!!\n" );
  2228. RememberUnreachable(GetEnemy());
  2229. TaskFail(FAIL_NO_ROUTE);
  2230. }
  2231. break;
  2232. }
  2233. break;
  2234. case TASK_GET_PATH_TO_TARGET:
  2235. // DevMsg("TARGET\n");
  2236. BaseClass::StartTask( pTask );
  2237. break;
  2238. case TASK_MANHACK_FIND_SQUAD_CENTER:
  2239. {
  2240. if (!m_pSquad)
  2241. {
  2242. m_vSavePosition = GetAbsOrigin();
  2243. TaskComplete();
  2244. break;
  2245. }
  2246. // calc center of squad
  2247. int count = 0;
  2248. m_vSavePosition = Vector( 0, 0, 0 );
  2249. // give attacking members more influence
  2250. AISquadIter_t iter;
  2251. for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
  2252. {
  2253. if (pSquadMember->HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ))
  2254. {
  2255. m_vSavePosition += pSquadMember->GetAbsOrigin() * 10;
  2256. count += 10;
  2257. }
  2258. else
  2259. {
  2260. m_vSavePosition += pSquadMember->GetAbsOrigin();
  2261. count++;
  2262. }
  2263. }
  2264. // pull towards enemy
  2265. if (GetEnemy() != NULL)
  2266. {
  2267. m_vSavePosition += GetEnemyLKP() * 4;
  2268. count += 4;
  2269. }
  2270. Assert( count != 0 );
  2271. m_vSavePosition = m_vSavePosition * (1.0 / count);
  2272. TaskComplete();
  2273. }
  2274. break;
  2275. case TASK_MANHACK_FIND_SQUAD_MEMBER:
  2276. {
  2277. if (m_pSquad)
  2278. {
  2279. CAI_BaseNPC *pSquadMember = m_pSquad->GetAnyMember();
  2280. m_vSavePosition = pSquadMember->GetAbsOrigin();
  2281. // find attacking members
  2282. AISquadIter_t iter;
  2283. for (pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
  2284. {
  2285. // are they attacking?
  2286. if (pSquadMember->HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ))
  2287. {
  2288. m_vSavePosition = pSquadMember->GetAbsOrigin();
  2289. break;
  2290. }
  2291. // do they have a goal?
  2292. if (pSquadMember->GetNavigator()->IsGoalActive())
  2293. {
  2294. m_vSavePosition = pSquadMember->GetAbsOrigin();
  2295. break;
  2296. }
  2297. }
  2298. }
  2299. else
  2300. {
  2301. m_vSavePosition = GetAbsOrigin();
  2302. }
  2303. TaskComplete();
  2304. }
  2305. break;
  2306. case TASK_MANHACK_MOVEAT_SAVEPOSITION:
  2307. {
  2308. trace_t tr;
  2309. AI_TraceLine( GetAbsOrigin(), m_vSavePosition, MASK_NPCWORLDSTATIC, this, COLLISION_GROUP_NONE, &tr );
  2310. if (tr.DidHitWorld())
  2311. {
  2312. TaskFail( FAIL_NO_ROUTE );
  2313. }
  2314. else
  2315. {
  2316. m_fSwarmMoveTime = gpGlobals->curtime + RandomFloat( pTask->flTaskData * 0.8, pTask->flTaskData * 1.2 );
  2317. }
  2318. }
  2319. break;
  2320. default:
  2321. BaseClass::StartTask(pTask);
  2322. break;
  2323. }
  2324. }
  2325. //-----------------------------------------------------------------------------
  2326. // Purpose:
  2327. //-----------------------------------------------------------------------------
  2328. void CNPC_Manhack::UpdateOnRemove( void )
  2329. {
  2330. DestroySmokeTrail();
  2331. KillSprites( 0.0 );
  2332. BaseClass::UpdateOnRemove();
  2333. }
  2334. //-----------------------------------------------------------------------------
  2335. // Purpose: This is a generic function (to be implemented by sub-classes) to
  2336. // handle specific interactions between different types of characters
  2337. // (For example the barnacle grabbing an NPC)
  2338. // Input : Constant for the type of interaction
  2339. // Output : true - if sub-class has a response for the interaction
  2340. // false - if sub-class has no response
  2341. //-----------------------------------------------------------------------------
  2342. bool CNPC_Manhack::HandleInteraction(int interactionType, void* data, CBaseCombatCharacter* sourceEnt)
  2343. {
  2344. if (interactionType == g_interactionVortigauntClaw)
  2345. {
  2346. // Freeze so vortigaunt and hit me easier
  2347. m_vForceMoveTarget.x = ((Vector *)data)->x;
  2348. m_vForceMoveTarget.y = ((Vector *)data)->y;
  2349. m_vForceMoveTarget.z = ((Vector *)data)->z;
  2350. m_fForceMoveTime = gpGlobals->curtime + 2.0;
  2351. return false;
  2352. }
  2353. return false;
  2354. }
  2355. //-----------------------------------------------------------------------------
  2356. // Purpose:
  2357. // Output : float
  2358. //-----------------------------------------------------------------------------
  2359. float CNPC_Manhack::ManhackMaxSpeed( void )
  2360. {
  2361. if( m_flWaterSuspendTime > gpGlobals->curtime )
  2362. {
  2363. // Slower in water!
  2364. return MANHACK_MAX_SPEED * 0.1;
  2365. }
  2366. if ( HasPhysicsAttacker( MANHACK_SMASH_TIME ) )
  2367. {
  2368. return MANHACK_NPC_BURST_SPEED;
  2369. }
  2370. return MANHACK_MAX_SPEED;
  2371. }
  2372. //-----------------------------------------------------------------------------
  2373. // Purpose:
  2374. // Output :
  2375. //-----------------------------------------------------------------------------
  2376. void CNPC_Manhack::ClampMotorForces( Vector &linear, AngularImpulse &angular )
  2377. {
  2378. float scale = m_flBladeSpeed / 100.0;
  2379. // Msg("%.0f %.0f %.0f\n", linear.x, linear.y, linear.z );
  2380. float fscale = 3000 * scale;
  2381. if ( m_flEngineStallTime > gpGlobals->curtime )
  2382. {
  2383. linear.x = 0.0f;
  2384. linear.y = 0.0f;
  2385. linear.z = clamp( linear.z, -fscale, fscale < 1200 ? 1200 : fscale );
  2386. }
  2387. else
  2388. {
  2389. // limit reaction forces
  2390. linear.x = clamp( linear.x, -fscale, fscale );
  2391. linear.y = clamp( linear.y, -fscale, fscale );
  2392. linear.z = clamp( linear.z, -fscale, fscale < 1200 ? 1200 : fscale );
  2393. }
  2394. angular.x *= scale;
  2395. angular.y *= scale;
  2396. angular.z *= scale;
  2397. }
  2398. //-----------------------------------------------------------------------------
  2399. // Purpose:
  2400. //-----------------------------------------------------------------------------
  2401. void CNPC_Manhack::KillSprites( float flDelay )
  2402. {
  2403. if( m_pEyeGlow )
  2404. {
  2405. m_pEyeGlow->FadeAndDie( flDelay );
  2406. m_pEyeGlow = NULL;
  2407. }
  2408. if( m_pLightGlow )
  2409. {
  2410. m_pLightGlow->FadeAndDie( flDelay );
  2411. m_pLightGlow = NULL;
  2412. }
  2413. // Re-enable for light trails
  2414. /*
  2415. if ( m_hLightTrail )
  2416. {
  2417. m_hLightTrail->FadeAndDie( flDelay );
  2418. m_hLightTrail = NULL;
  2419. }
  2420. */
  2421. }
  2422. //-----------------------------------------------------------------------------
  2423. // Purpose: Tests whether we're above the target's feet but also below their top
  2424. // Input : *pTarget - who we're testing against
  2425. //-----------------------------------------------------------------------------
  2426. bool CNPC_Manhack::IsInEffectiveTargetZone( CBaseEntity *pTarget )
  2427. {
  2428. Vector vecMaxPos, vecMinPos;
  2429. float ourHeight = WorldSpaceCenter().z;
  2430. // If the enemy is in a vehicle, we need to get those bounds
  2431. if ( pTarget && pTarget->IsPlayer() && assert_cast< CBasePlayer * >(pTarget)->IsInAVehicle() )
  2432. {
  2433. CBaseEntity *pVehicle = assert_cast< CBasePlayer * >(pTarget)->GetVehicleEntity();
  2434. pVehicle->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,1.0f), &vecMaxPos );
  2435. pVehicle->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,0.0f), &vecMinPos );
  2436. if ( ourHeight > vecMinPos.z && ourHeight < vecMaxPos.z )
  2437. return true;
  2438. return false;
  2439. }
  2440. // Get the enemies top and bottom point
  2441. pTarget->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,1.0f), &vecMaxPos );
  2442. #ifdef _XBOX
  2443. pTarget->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,0.5f), &vecMinPos ); // Only half the body is valid
  2444. #else
  2445. pTarget->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,0.0f), &vecMinPos );
  2446. #endif // _XBOX
  2447. // See if we're within that range
  2448. if ( ourHeight > vecMinPos.z && ourHeight < vecMaxPos.z )
  2449. return true;
  2450. return false;
  2451. }
  2452. //-----------------------------------------------------------------------------
  2453. // Purpose:
  2454. // Input : *pEnemy -
  2455. // &chasePosition -
  2456. // &tolerance -
  2457. //-----------------------------------------------------------------------------
  2458. void CNPC_Manhack::TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition )
  2459. {
  2460. if ( pEnemy && pEnemy->IsPlayer() && assert_cast< CBasePlayer * >(pEnemy)->IsInAVehicle() )
  2461. {
  2462. Vector vecNewPos;
  2463. CBaseEntity *pVehicle = assert_cast< CBasePlayer * >(pEnemy)->GetVehicleEntity();
  2464. pVehicle->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,0.5f), &vecNewPos );
  2465. chasePosition.z = vecNewPos.z;
  2466. }
  2467. else
  2468. {
  2469. Vector vecTarget;
  2470. pEnemy->CollisionProp()->NormalizedToCollisionSpace( Vector(0,0,0.75f), &vecTarget );
  2471. chasePosition.z += vecTarget.z;
  2472. }
  2473. }
  2474. float CNPC_Manhack::GetDefaultNavGoalTolerance()
  2475. {
  2476. return GetHullWidth();
  2477. }
  2478. //-----------------------------------------------------------------------------
  2479. // Purpose: Input that disables the manhack's swarm behavior
  2480. //-----------------------------------------------------------------------------
  2481. void CNPC_Manhack::InputDisableSwarm( inputdata_t &inputdata )
  2482. {
  2483. m_bDoSwarmBehavior = false;
  2484. }
  2485. //-----------------------------------------------------------------------------
  2486. // Purpose:
  2487. // Input : &inputdata -
  2488. //-----------------------------------------------------------------------------
  2489. void CNPC_Manhack::InputUnpack( inputdata_t &inputdata )
  2490. {
  2491. if ( HasSpawnFlags( SF_MANHACK_PACKED_UP ) == false )
  2492. return;
  2493. SetCondition( COND_LIGHT_DAMAGE );
  2494. }
  2495. //-----------------------------------------------------------------------------
  2496. // Purpose:
  2497. // Input : *pPhysGunUser -
  2498. // reason -
  2499. //-----------------------------------------------------------------------------
  2500. void CNPC_Manhack::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  2501. {
  2502. m_hPhysicsAttacker = pPhysGunUser;
  2503. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  2504. if ( reason == PUNTED_BY_CANNON )
  2505. {
  2506. StopLoitering();
  2507. m_bHeld = false;
  2508. // There's about to be a massive change in velocity.
  2509. // Think immediately so we can do our slice traces, etc.
  2510. SetNextThink( gpGlobals->curtime + 0.01f );
  2511. // Stall our engine for awhile
  2512. m_flEngineStallTime = gpGlobals->curtime + 2.0f;
  2513. SetEyeState( MANHACK_EYE_STATE_STUNNED );
  2514. }
  2515. else
  2516. {
  2517. // Suppress collisions between the manhack and the player; we're currently bumping
  2518. // almost certainly because it's not purely a physics object.
  2519. SetOwnerEntity( pPhysGunUser );
  2520. m_bHeld = true;
  2521. }
  2522. }
  2523. //-----------------------------------------------------------------------------
  2524. // Purpose:
  2525. // Input : *pPhysGunUser -
  2526. // Reason -
  2527. //-----------------------------------------------------------------------------
  2528. void CNPC_Manhack::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  2529. {
  2530. // Stop suppressing collisions between the manhack and the player
  2531. SetOwnerEntity( NULL );
  2532. m_bHeld = false;
  2533. if ( Reason == LAUNCHED_BY_CANNON )
  2534. {
  2535. m_hPhysicsAttacker = pPhysGunUser;
  2536. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  2537. // There's about to be a massive change in velocity.
  2538. // Think immediately so we can do our slice traces, etc.
  2539. SetNextThink( gpGlobals->curtime + 0.01f );
  2540. // Stall our engine for awhile
  2541. m_flEngineStallTime = gpGlobals->curtime + 2.0f;
  2542. SetEyeState( MANHACK_EYE_STATE_STUNNED );
  2543. }
  2544. else
  2545. {
  2546. if( m_bHackedByAlyx && !GetEnemy() )
  2547. {
  2548. // If a hacked manhack is released in peaceable conditions,
  2549. // just loiter, don't zip off.
  2550. StartLoitering( GetAbsOrigin() );
  2551. }
  2552. m_hPhysicsAttacker = NULL;
  2553. m_flLastPhysicsInfluenceTime = 0;
  2554. }
  2555. }
  2556. void CNPC_Manhack::StartLoitering( const Vector &vecLoiterPosition )
  2557. {
  2558. //Msg("Start Loitering\n");
  2559. m_vTargetBanking = vec3_origin;
  2560. m_vecLoiterPosition = GetAbsOrigin();
  2561. m_vForceVelocity = vec3_origin;
  2562. SetCurrentVelocity( vec3_origin );
  2563. }
  2564. CBasePlayer *CNPC_Manhack::HasPhysicsAttacker( float dt )
  2565. {
  2566. // If the player is holding me now, or I've been recently thrown
  2567. // then return a pointer to that player
  2568. if ( IsHeldByPhyscannon() || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) )
  2569. {
  2570. return m_hPhysicsAttacker;
  2571. }
  2572. return NULL;
  2573. }
  2574. //-----------------------------------------------------------------------------
  2575. // Manhacks that have been hacked by Alyx get more engine power (fly faster)
  2576. //-----------------------------------------------------------------------------
  2577. float CNPC_Manhack::GetMaxEnginePower()
  2578. {
  2579. if( m_bHackedByAlyx )
  2580. {
  2581. return 2.0f;
  2582. }
  2583. return 1.0f;
  2584. }
  2585. //-----------------------------------------------------------------------------
  2586. // Purpose:
  2587. //-----------------------------------------------------------------------------
  2588. void CNPC_Manhack::UpdatePanels( void )
  2589. {
  2590. if ( m_flEngineStallTime > gpGlobals->curtime )
  2591. {
  2592. SetPoseParameter( m_iPanel1, random->RandomFloat( 0.0f, 90.0f ) );
  2593. SetPoseParameter( m_iPanel2, random->RandomFloat( 0.0f, 90.0f ) );
  2594. SetPoseParameter( m_iPanel3, random->RandomFloat( 0.0f, 90.0f ) );
  2595. SetPoseParameter( m_iPanel4, random->RandomFloat( 0.0f, 90.0f ) );
  2596. return;
  2597. }
  2598. float panelPosition = GetPoseParameter( m_iPanel1 );
  2599. if ( m_bShowingHostile )
  2600. {
  2601. panelPosition = 90.0f;//UTIL_Approach( 90.0f, panelPosition, 90.0f );
  2602. }
  2603. else
  2604. {
  2605. panelPosition = UTIL_Approach( 0.0f, panelPosition, 25.0f );
  2606. }
  2607. //FIXME: If we're going to have all these be equal, there's no need for 4 poses..
  2608. SetPoseParameter( m_iPanel1, panelPosition );
  2609. SetPoseParameter( m_iPanel2, panelPosition );
  2610. SetPoseParameter( m_iPanel3, panelPosition );
  2611. SetPoseParameter( m_iPanel4, panelPosition );
  2612. //TODO: Make these waver randomly?
  2613. }
  2614. //-----------------------------------------------------------------------------
  2615. // Purpose:
  2616. // Input : hostile -
  2617. //-----------------------------------------------------------------------------
  2618. void CNPC_Manhack::ShowHostile( bool hostile /*= true*/)
  2619. {
  2620. if ( m_bShowingHostile == hostile )
  2621. return;
  2622. //TODO: Open the manhack panels or close them, depending on the state
  2623. m_bShowingHostile = hostile;
  2624. if ( hostile )
  2625. {
  2626. EmitSound( "NPC_Manhack.ChargeAnnounce" );
  2627. }
  2628. else
  2629. {
  2630. EmitSound( "NPC_Manhack.ChargeEnd" );
  2631. }
  2632. }
  2633. //-----------------------------------------------------------------------------
  2634. // Purpose:
  2635. //-----------------------------------------------------------------------------
  2636. void CNPC_Manhack::StartBurst( const Vector &vecDirection )
  2637. {
  2638. if ( m_flBurstDuration > gpGlobals->curtime )
  2639. return;
  2640. ShowHostile();
  2641. // Don't burst attack again for a couple seconds
  2642. m_flNextBurstTime = gpGlobals->curtime + 2.0;
  2643. m_flBurstDuration = gpGlobals->curtime + 1.0;
  2644. // Save off where we were going towards and for how long
  2645. m_vecBurstDirection = vecDirection;
  2646. }
  2647. //-----------------------------------------------------------------------------
  2648. // Purpose:
  2649. //-----------------------------------------------------------------------------
  2650. void CNPC_Manhack::StopBurst( bool bInterruptSchedule /*= false*/ )
  2651. {
  2652. if ( m_flBurstDuration < gpGlobals->curtime )
  2653. return;
  2654. ShowHostile( false );
  2655. // Stop our burst timers
  2656. m_flNextBurstTime = gpGlobals->curtime + 2.0f; //FIXME: Skill level based
  2657. m_flBurstDuration = gpGlobals->curtime - 0.1f;
  2658. if ( bInterruptSchedule )
  2659. {
  2660. // We need to rethink our current schedule
  2661. ClearSchedule( "Stopping burst" );
  2662. }
  2663. }
  2664. //-----------------------------------------------------------------------------
  2665. // Purpose:
  2666. //-----------------------------------------------------------------------------
  2667. void CNPC_Manhack::SetEyeState( int state )
  2668. {
  2669. // Make sure we're active
  2670. StartEye();
  2671. switch( state )
  2672. {
  2673. case MANHACK_EYE_STATE_STUNNED:
  2674. {
  2675. if ( m_pEyeGlow )
  2676. {
  2677. //Toggle our state
  2678. m_pEyeGlow->SetColor( 255, 128, 0 );
  2679. m_pEyeGlow->SetScale( 0.15f, 0.1f );
  2680. m_pEyeGlow->SetBrightness( 164, 0.1f );
  2681. m_pEyeGlow->m_nRenderFX = kRenderFxStrobeFast;
  2682. }
  2683. if ( m_pLightGlow )
  2684. {
  2685. m_pLightGlow->SetColor( 255, 128, 0 );
  2686. m_pLightGlow->SetScale( 0.15f, 0.1f );
  2687. m_pLightGlow->SetBrightness( 164, 0.1f );
  2688. m_pLightGlow->m_nRenderFX = kRenderFxStrobeFast;
  2689. }
  2690. EmitSound("NPC_Manhack.Stunned");
  2691. break;
  2692. }
  2693. case MANHACK_EYE_STATE_CHARGE:
  2694. {
  2695. if ( m_pEyeGlow )
  2696. {
  2697. //Toggle our state
  2698. if( m_bHackedByAlyx )
  2699. {
  2700. m_pEyeGlow->SetColor( 0, 255, 0 );
  2701. }
  2702. else
  2703. {
  2704. m_pEyeGlow->SetColor( 255, 0, 0 );
  2705. }
  2706. m_pEyeGlow->SetScale( 0.25f, 0.5f );
  2707. m_pEyeGlow->SetBrightness( 164, 0.1f );
  2708. m_pEyeGlow->m_nRenderFX = kRenderFxNone;
  2709. }
  2710. if ( m_pLightGlow )
  2711. {
  2712. if( m_bHackedByAlyx )
  2713. {
  2714. m_pLightGlow->SetColor( 0, 255, 0 );
  2715. }
  2716. else
  2717. {
  2718. m_pLightGlow->SetColor( 255, 0, 0 );
  2719. }
  2720. m_pLightGlow->SetScale( 0.25f, 0.5f );
  2721. m_pLightGlow->SetBrightness( 164, 0.1f );
  2722. m_pLightGlow->m_nRenderFX = kRenderFxNone;
  2723. }
  2724. break;
  2725. }
  2726. default:
  2727. if ( m_pEyeGlow )
  2728. m_pEyeGlow->m_nRenderFX = kRenderFxNone;
  2729. break;
  2730. }
  2731. }
  2732. unsigned int CNPC_Manhack::PhysicsSolidMaskForEntity( void ) const
  2733. {
  2734. unsigned int mask = BaseClass::PhysicsSolidMaskForEntity();
  2735. if ( m_bIgnoreClipbrushes )
  2736. {
  2737. mask &= ~CONTENTS_MONSTERCLIP;
  2738. }
  2739. return mask;
  2740. }
  2741. //-----------------------------------------------------------------------------
  2742. // Purpose:
  2743. // Output : Returns true on success, false on failure.
  2744. //-----------------------------------------------------------------------------
  2745. bool CNPC_Manhack::CreateVPhysics( void )
  2746. {
  2747. if ( HasSpawnFlags( SF_MANHACK_CARRIED ) )
  2748. return false;
  2749. return BaseClass::CreateVPhysics();
  2750. }
  2751. //-----------------------------------------------------------------------------
  2752. //
  2753. // Schedules
  2754. //
  2755. //-----------------------------------------------------------------------------
  2756. AI_BEGIN_CUSTOM_NPC( npc_manhack, CNPC_Manhack )
  2757. DECLARE_TASK( TASK_MANHACK_HOVER );
  2758. DECLARE_TASK( TASK_MANHACK_UNPACK );
  2759. DECLARE_TASK( TASK_MANHACK_FIND_SQUAD_CENTER );
  2760. DECLARE_TASK( TASK_MANHACK_FIND_SQUAD_MEMBER );
  2761. DECLARE_TASK( TASK_MANHACK_MOVEAT_SAVEPOSITION );
  2762. DECLARE_CONDITION( COND_MANHACK_START_ATTACK );
  2763. DECLARE_ACTIVITY( ACT_MANHACK_UNPACK );
  2764. //=========================================================
  2765. // > SCHED_MANHACK_ATTACK_HOVER
  2766. //=========================================================
  2767. DEFINE_SCHEDULE
  2768. (
  2769. SCHED_MANHACK_ATTACK_HOVER,
  2770. " Tasks"
  2771. " TASK_SET_ACTIVITY ACTIVITY:ACT_FLY"
  2772. " TASK_MANHACK_HOVER 0"
  2773. " "
  2774. " Interrupts"
  2775. " COND_TOO_FAR_TO_ATTACK"
  2776. " COND_TOO_CLOSE_TO_ATTACK"
  2777. " COND_NEW_ENEMY"
  2778. " COND_ENEMY_DEAD"
  2779. " COND_LIGHT_DAMAGE"
  2780. " COND_HEAVY_DAMAGE"
  2781. " COND_ENEMY_OCCLUDED"
  2782. );
  2783. //=========================================================
  2784. // > SCHED_MANHACK_ATTACK_HOVER
  2785. //=========================================================
  2786. DEFINE_SCHEDULE
  2787. (
  2788. SCHED_MANHACK_DEPLOY,
  2789. " Tasks"
  2790. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_MANHACK_UNPACK"
  2791. " TASK_SET_ACTIVITY ACTIVITY:ACT_FLY"
  2792. " "
  2793. // " Interrupts"
  2794. );
  2795. //=========================================================
  2796. // > SCHED_MANHACK_REGROUP
  2797. //=========================================================
  2798. DEFINE_SCHEDULE
  2799. (
  2800. SCHED_MANHACK_REGROUP,
  2801. " Tasks"
  2802. " TASK_STOP_MOVING 0"
  2803. " TASK_SET_TOLERANCE_DISTANCE 24"
  2804. " TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0"
  2805. " TASK_FIND_BACKAWAY_FROM_SAVEPOSITION 0"
  2806. " TASK_RUN_PATH 0"
  2807. " TASK_WAIT_FOR_MOVEMENT 0"
  2808. " "
  2809. " Interrupts"
  2810. " COND_MANHACK_START_ATTACK"
  2811. " COND_NEW_ENEMY"
  2812. " COND_CAN_MELEE_ATTACK1"
  2813. );
  2814. //=========================================================
  2815. // > SCHED_MANHACK_SWARN
  2816. //=========================================================
  2817. DEFINE_SCHEDULE
  2818. (
  2819. SCHED_MANHACK_SWARM_IDLE,
  2820. " Tasks"
  2821. " TASK_STOP_MOVING 0"
  2822. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MANHACK_SWARM_FAILURE"
  2823. " TASK_MANHACK_FIND_SQUAD_CENTER 0"
  2824. " TASK_MANHACK_MOVEAT_SAVEPOSITION 5"
  2825. " "
  2826. " Interrupts"
  2827. " COND_NEW_ENEMY"
  2828. " COND_SEE_ENEMY"
  2829. " COND_SEE_FEAR"
  2830. " COND_LIGHT_DAMAGE"
  2831. " COND_HEAVY_DAMAGE"
  2832. " COND_SMELL"
  2833. " COND_PROVOKED"
  2834. " COND_GIVE_WAY"
  2835. " COND_HEAR_PLAYER"
  2836. " COND_HEAR_DANGER"
  2837. " COND_HEAR_COMBAT"
  2838. " COND_HEAR_BULLET_IMPACT"
  2839. );
  2840. DEFINE_SCHEDULE
  2841. (
  2842. SCHED_MANHACK_SWARM,
  2843. " Tasks"
  2844. " TASK_STOP_MOVING 0"
  2845. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MANHACK_SWARM_FAILURE"
  2846. " TASK_MANHACK_FIND_SQUAD_CENTER 0"
  2847. " TASK_MANHACK_MOVEAT_SAVEPOSITION 1"
  2848. " "
  2849. " Interrupts"
  2850. " COND_NEW_ENEMY"
  2851. " COND_CAN_MELEE_ATTACK1"
  2852. " COND_LIGHT_DAMAGE"
  2853. " COND_HEAVY_DAMAGE"
  2854. );
  2855. DEFINE_SCHEDULE
  2856. (
  2857. SCHED_MANHACK_SWARM_FAILURE,
  2858. " Tasks"
  2859. " TASK_STOP_MOVING 0"
  2860. " TASK_WAIT 2"
  2861. " TASK_WAIT_RANDOM 2"
  2862. " TASK_MANHACK_FIND_SQUAD_MEMBER 0"
  2863. " TASK_GET_PATH_TO_SAVEPOSITION 0"
  2864. " TASK_WAIT_FOR_MOVEMENT 0"
  2865. " "
  2866. " Interrupts"
  2867. " COND_SEE_ENEMY"
  2868. " COND_NEW_ENEMY"
  2869. );
  2870. AI_END_CUSTOM_NPC()