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.

1952 lines
49 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "npc_hydra.h"
  9. #include "ai_hull.h"
  10. #include "saverestore_utlvector.h"
  11. #include "physics_saverestore.h"
  12. #include "vphysics/constraints.h"
  13. #include "vcollide_parse.h"
  14. #include "ragdoll_shared.h"
  15. #include "physics_prop_ragdoll.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. //-----------------------------------------------------------------------------
  19. //
  20. // CNPC_Hydra
  21. //
  22. #define HYDRA_MAX_LENGTH 500
  23. LINK_ENTITY_TO_CLASS( npc_hydra, CNPC_Hydra );
  24. //=========================================================
  25. // Hydra activities
  26. //=========================================================
  27. int ACT_HYDRA_COWER;
  28. int ACT_HYDRA_STAB;
  29. //=========================================================
  30. // Private conditions
  31. //=========================================================
  32. //==================================================
  33. // AntlionConditions
  34. //==================================================
  35. enum
  36. {
  37. COND_HYDRA_SNAGGED = LAST_SHARED_CONDITION,
  38. COND_HYDRA_STUCK,
  39. COND_HYDRA_OVERSHOOT,
  40. COND_HYDRA_OVERSTRETCH, // longer than max distance
  41. COND_HYDRA_STRIKE, // head hit something
  42. COND_HYDRA_NOSTUCK // no segments are stuck
  43. };
  44. //=========================================================
  45. // Hydra schedules
  46. //=========================================================
  47. enum
  48. {
  49. SCHED_HYDRA_DEPLOY = LAST_SHARED_SCHEDULE,
  50. SCHED_HYDRA_RETRACT,
  51. SCHED_HYDRA_IDLE,
  52. SCHED_HYDRA_STAB, // shoot out head and try to hit object
  53. SCHED_HYDRA_PULLBACK, //
  54. SCHED_HYDRA_TIGHTEN_SLACK, // snagged on something, tighten slack up to obstacle and try again from there
  55. SCHED_HYDRA_RETREAT,
  56. SCHED_HYDRA_THROW,
  57. SCHED_HYDRA_RANGE_ATTACK
  58. };
  59. //=========================================================
  60. // Hydra tasks
  61. //=========================================================
  62. enum
  63. {
  64. TASK_HYDRA_RETRACT = LAST_SHARED_TASK,
  65. TASK_HYDRA_DEPLOY,
  66. TASK_HYDRA_GET_OBJECT,
  67. TASK_HYDRA_THROW_OBJECT,
  68. TASK_HYDRA_PREP_STAB,
  69. TASK_HYDRA_STAB,
  70. TASK_HYDRA_PULLBACK,
  71. TASK_HYDRA_SET_MAX_TENSION,
  72. TASK_HYDRA_SET_BLEND_TENSION
  73. };
  74. //---------------------------------------------------------
  75. // Custom Client entity
  76. //---------------------------------------------------------
  77. IMPLEMENT_SERVERCLASS_ST(CNPC_Hydra, DT_NPC_Hydra)
  78. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 0 ), -1, SPROP_COORD ),
  79. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 1 ), -1, SPROP_COORD ),
  80. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 2 ), -1, SPROP_COORD ),
  81. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 3 ), -1, SPROP_COORD ),
  82. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 4 ), -1, SPROP_COORD ),
  83. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 5 ), -1, SPROP_COORD ),
  84. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 6 ), -1, SPROP_COORD ),
  85. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 7 ), -1, SPROP_COORD ),
  86. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 8 ), -1, SPROP_COORD ),
  87. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 9 ), -1, SPROP_COORD ),
  88. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 10 ), -1, SPROP_COORD ),
  89. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 11 ), -1, SPROP_COORD ),
  90. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 12 ), -1, SPROP_COORD ),
  91. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 13 ), -1, SPROP_COORD ),
  92. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 14 ), -1, SPROP_COORD ),
  93. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 15 ), -1, SPROP_COORD ),
  94. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 16 ), -1, SPROP_COORD ),
  95. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 17 ), -1, SPROP_COORD ),
  96. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 18 ), -1, SPROP_COORD ),
  97. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 19 ), -1, SPROP_COORD ),
  98. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 20 ), -1, SPROP_COORD ),
  99. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 21 ), -1, SPROP_COORD ),
  100. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 22 ), -1, SPROP_COORD ),
  101. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 23 ), -1, SPROP_COORD ),
  102. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 24 ), -1, SPROP_COORD ),
  103. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 25 ), -1, SPROP_COORD ),
  104. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 26 ), -1, SPROP_COORD ),
  105. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 27 ), -1, SPROP_COORD ),
  106. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 28 ), -1, SPROP_COORD ),
  107. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 29 ), -1, SPROP_COORD ),
  108. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 30 ), -1, SPROP_COORD ),
  109. SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 31 ), -1, SPROP_COORD ),
  110. SendPropVector( SENDINFO( m_vecHeadDir ), -1, SPROP_NORMAL ),
  111. SendPropFloat( SENDINFO( m_flRelaxedLength ), 12, 0, 0.0, HYDRA_MAX_LENGTH * 1.5 ),
  112. END_SEND_TABLE()
  113. //---------------------------------------------------------
  114. // Save/Restore
  115. //---------------------------------------------------------
  116. BEGIN_DATADESC( CNPC_Hydra )
  117. DEFINE_AUTO_ARRAY( m_vecChain, FIELD_POSITION_VECTOR ),
  118. DEFINE_FIELD( m_activeChain, FIELD_INTEGER ),
  119. DEFINE_FIELD( m_bHasStuckSegments, FIELD_BOOLEAN ),
  120. DEFINE_FIELD( m_flCurrentLength, FIELD_FLOAT ),
  121. DEFINE_FIELD( m_vecHeadGoal, FIELD_POSITION_VECTOR ),
  122. DEFINE_FIELD( m_flHeadGoalInfluence, FIELD_FLOAT ),
  123. DEFINE_FIELD( m_vecHeadDir, FIELD_VECTOR ),
  124. DEFINE_FIELD( m_flRelaxedLength, FIELD_FLOAT ),
  125. DEFINE_FIELD( m_vecOutward, FIELD_VECTOR ),
  126. DEFINE_UTLVECTOR( m_body, FIELD_EMBEDDED ),
  127. DEFINE_FIELD( m_idealLength, FIELD_FLOAT ),
  128. DEFINE_FIELD( m_idealSegmentLength, FIELD_FLOAT ),
  129. DEFINE_FIELD( m_bExtendSoundActive, FIELD_BOOLEAN ),
  130. DEFINE_SOUNDPATCH( m_pExtendTentacleSound ),
  131. DEFINE_FIELD( m_seed, FIELD_FLOAT ),
  132. DEFINE_FIELD( m_vecTarget, FIELD_POSITION_VECTOR ),
  133. DEFINE_FIELD( m_vecTargetDir, FIELD_VECTOR ),
  134. DEFINE_FIELD( m_flLastAdjustmentTime, FIELD_TIME ),
  135. DEFINE_FIELD( m_flTaskStartTime, FIELD_TIME ),
  136. DEFINE_FIELD( m_flTaskEndTime, FIELD_TIME ),
  137. DEFINE_FIELD( m_flLengthTime, FIELD_TIME ),
  138. DEFINE_FIELD( m_bStabbedEntity, FIELD_BOOLEAN ),
  139. END_DATADESC()
  140. //-------------------------------------
  141. BEGIN_SIMPLE_DATADESC( HydraBone )
  142. DEFINE_FIELD( vecPos, FIELD_POSITION_VECTOR ),
  143. DEFINE_FIELD( vecDelta, FIELD_VECTOR ),
  144. DEFINE_FIELD( flIdealLength, FIELD_FLOAT ),
  145. DEFINE_FIELD( flActualLength, FIELD_FLOAT ),
  146. DEFINE_FIELD( bStuck, FIELD_BOOLEAN ),
  147. DEFINE_FIELD( bOnFire, FIELD_BOOLEAN ),
  148. DEFINE_FIELD( vecGoalPos, FIELD_POSITION_VECTOR ),
  149. DEFINE_FIELD( flGoalInfluence,FIELD_FLOAT ),
  150. END_DATADESC()
  151. //-------------------------------------
  152. static ConVar sv_hydraLength( "hydra_length", "100", FCVAR_ARCHIVE, "Hydra Length" );
  153. static ConVar sv_hydraSlack( "hydra_slack", "200", FCVAR_ARCHIVE, "Hydra Slack" );
  154. static ConVar sv_hydraSegmentLength( "hydra_segment_length", "30", FCVAR_ARCHIVE, "Hydra Slack" );
  155. static ConVar sv_hydraTest( "hydra_test", "1", FCVAR_ARCHIVE, "Hydra Slack" );
  156. static ConVar sv_hydraBendTension( "hydra_bend_tension", "0.4", FCVAR_ARCHIVE, "Hydra Slack" );
  157. static ConVar sv_hydraBendDelta( "hydra_bend_delta", "50", FCVAR_ARCHIVE, "Hydra Slack" );
  158. static ConVar sv_hydraGoalTension( "hydra_goal_tension", "0.5", FCVAR_ARCHIVE, "Hydra Slack" );
  159. static ConVar sv_hydraGoalDelta( "hydra_goal_delta", "400", FCVAR_ARCHIVE, "Hydra Slack" );
  160. static ConVar sv_hydraMomentum( "hydra_momentum", "0.5", FCVAR_ARCHIVE, "Hydra Slack" );
  161. static ConVar sv_hydraTestSpike( "sv_hydraTestSpike", "1", 0, "Hydra Test impaling code" );
  162. //-------------------------------------
  163. // Purpose: Initialize the custom schedules
  164. //-------------------------------------
  165. //-------------------------------------
  166. void CNPC_Hydra::Precache()
  167. {
  168. PrecacheModel( "models/Hydra.mdl" );
  169. UTIL_PrecacheOther( "hydra_impale" );
  170. PrecacheScriptSound( "NPC_Hydra.ExtendTentacle" );
  171. BaseClass::Precache();
  172. }
  173. void CNPC_Hydra::Activate( void )
  174. {
  175. CPASAttenuationFilter filter( this );
  176. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  177. m_pExtendTentacleSound = controller.SoundCreate( filter, entindex(), "NPC_Hydra.ExtendTentacle" );
  178. controller.Play( m_pExtendTentacleSound, 1.0, 100 );
  179. BaseClass::Activate();
  180. }
  181. //-----------------------------------------------------------------------------
  182. // Purpose: Returns this monster's place in the relationship table.
  183. //-----------------------------------------------------------------------------
  184. Class_T CNPC_Hydra::Classify( void )
  185. {
  186. return CLASS_BARNACLE;
  187. }
  188. //-------------------------------------
  189. #define HYDRA_OUTWARD_BIAS 16
  190. #define HYDRA_INWARD_BIAS 30
  191. void CNPC_Hydra::Spawn()
  192. {
  193. Precache();
  194. BaseClass::Spawn();
  195. SetModel( "models/Hydra.mdl" );
  196. SetHullType(HULL_HUMAN);
  197. SetHullSizeNormal();
  198. SetSolid( SOLID_BBOX );
  199. AddSolidFlags( FSOLID_NOT_STANDABLE );
  200. SetMoveType( MOVETYPE_STEP );
  201. SetBloodColor( BLOOD_COLOR_RED );
  202. ClearEffects();
  203. m_iHealth = 20;
  204. m_flFieldOfView = -1.0;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
  205. m_NPCState = NPC_STATE_NONE;
  206. GetVectors( NULL, NULL, &m_vecOutward );
  207. SetAbsAngles( QAngle( 0, 0, 0 ) );
  208. m_vecChain.Set( 0, GetAbsOrigin( ) - m_vecOutward * 32 );
  209. m_vecChain.Set( 1, GetAbsOrigin( ) + m_vecOutward * 16 );
  210. m_vecHeadGoal = m_vecChain[1] + m_vecOutward * HYDRA_OUTWARD_BIAS;
  211. m_vecHeadDir = Vector( 0, 0, 1 );
  212. // init bones
  213. HydraBone bone;
  214. bone.vecPos = GetAbsOrigin( ) - m_vecOutward * HYDRA_INWARD_BIAS;
  215. m_body.AddToTail( bone );
  216. bone.vecPos = m_vecChain[1];
  217. m_body.AddToTail( bone );
  218. bone.vecPos = m_vecHeadGoal;
  219. m_body.AddToTail( bone );
  220. bone.vecPos = m_vecHeadGoal + m_vecHeadDir;
  221. m_body.AddToTail( bone );
  222. m_idealSegmentLength = sv_hydraSegmentLength.GetFloat();
  223. for (int i = 2; i < CHAIN_LINKS; i++)
  224. {
  225. m_vecChain.Set( i, m_vecChain[i-1] );
  226. }
  227. m_seed = random->RandomFloat( 0.0, 2000.0 );
  228. NPCInit();
  229. m_takedamage = DAMAGE_NO;
  230. }
  231. //-------------------------------------
  232. void CNPC_Hydra::RunAI( void )
  233. {
  234. CheckLength( );
  235. AdjustLength( );
  236. BaseClass::RunAI();
  237. CalcGoalForces( );
  238. MoveBody( );
  239. int i;
  240. for (i = 1; i < CHAIN_LINKS && i < m_body.Count(); i++)
  241. {
  242. m_vecChain.Set( i, m_body[i].vecPos );
  243. #if 0
  244. if (m_body[i].bStuck)
  245. {
  246. NDebugOverlay::Box(m_body[i].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255, 0, 0, 20, .1);
  247. }
  248. else
  249. {
  250. NDebugOverlay::Box(m_body[i].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0, 255, 0, 20, .1);
  251. }
  252. NDebugOverlay::Line( m_body[i].vecPos, m_body[i].vecPos + m_body[i].vecDelta, 0, 255, 0, true, .1);
  253. NDebugOverlay::Line( m_body[i-1].vecPos, m_body[i].vecPos, 255, 255, 255, true, .1);
  254. #endif
  255. #if 0
  256. char text[128];
  257. Q_snprintf( text, sizeof( text ), "%d", i );
  258. NDebugOverlay::Text( m_body[i].vecPos, text, false, 0.1 );
  259. #endif
  260. #if 0
  261. char text[128];
  262. Q_snprintf( text, sizeof( text ), "%4.0f", (m_body[i].vecPos - m_body[i-1].vecPos).Length() * 100 / m_idealSegmentLength - 100);
  263. NDebugOverlay::Text( 0.5*(m_body[i-1].vecPos + m_body[i].vecPos), text, false, 0.1 );
  264. #endif
  265. }
  266. //NDebugOverlay::Box(m_body[i].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0, 255, 0, 20, .1);
  267. //NDebugOverlay::Box( m_vecHeadGoal, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255, 255, 0, 20, .1);
  268. for (; i < CHAIN_LINKS; i++)
  269. {
  270. m_vecChain.Set( i, m_vecChain[i-1] );
  271. }
  272. }
  273. Vector CNPC_Hydra::TestPosition( float t )
  274. {
  275. // return GetAbsOrigin( ) + Vector( sin( (m_seed + t) * 2.3 ) * 15, cos( (m_seed + t) * 2.4 ) * 150, sin( ( m_seed + t ) * 1.8 ) * 50 ) + m_vecOutward * sv_hydraLength.GetFloat();;
  276. t = (int)(t * 0.2);
  277. #if 1
  278. Vector tmp = Vector( sin( (m_seed + t) * 0.8 ) * 15, cos( (m_seed + t) * 0.9 ) * 150, sin( ( m_seed + t ) * 0.4 ) * 50 );
  279. tmp += Vector( sin( (m_seed + t) * 1.0 ) * 4, cos( (m_seed + t) * 0.9 ) * 4, sin( ( m_seed + t ) * 1.1 ) * 6 );
  280. tmp += GetAbsOrigin( ) + m_vecOutward * sv_hydraLength.GetFloat();
  281. return tmp;
  282. #else
  283. Vector tmp;
  284. tmp.Init;
  285. CBaseEntity *pPlayer = (CBaseEntity *)UTIL_GetLocalPlayer();
  286. if ( pPlayer )
  287. {
  288. tmp = pPlayer->EyePosition( );
  289. Vector delta = (tmp - GetAbsOrigin( ));
  290. if (delta.Length() > 200)
  291. {
  292. tmp = GetAbsOrigin( ) + Vector( 0, 0, 200 );
  293. }
  294. m_vecHeadDir = (pPlayer->EyePosition( ) - m_body[m_body.Count()-2].vecPos);
  295. VectorNormalize( m_vecHeadDir );
  296. }
  297. return tmp;
  298. #endif
  299. // m_vecHeadGoal = GetAbsOrigin( ) + Vector( sin( gpGlobals->curtime * 0.3 ) * 15, cos( gpGlobals->curtime * 0.4 ) * 150, sin( gpGlobals->curtime * 0.2 ) * 50 + dt );
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Purpose: Calculate the bone forces based on goal positions, bending rules, stretching rules, etc.
  303. // Input :
  304. // Output :
  305. //-----------------------------------------------------------------------------
  306. void CNPC_Hydra::CalcGoalForces( )
  307. {
  308. int i;
  309. int iFirst = 2;
  310. int iLast = m_body.Count() - 1;
  311. // keep head segment straight
  312. m_body[iLast].vecGoalPos = m_vecHeadGoal; // + m_vecHeadDir * m_body[iLast-1].flActualLength;
  313. m_body[iLast].flGoalInfluence = m_flHeadGoalInfluence;
  314. m_body[iLast-1].vecGoalPos = m_vecHeadGoal - m_vecHeadDir * m_idealSegmentLength;
  315. m_body[iLast-1].flGoalInfluence = 1.0; // m_flHeadGoalInfluence;
  316. // momentum?
  317. for (i = iFirst; i <= iLast; i++)
  318. {
  319. m_body[i].vecDelta = m_body[i].vecDelta * sv_hydraMomentum.GetFloat();
  320. }
  321. //Vector right, up;
  322. //VectorVectors( m_vecHeadDir, right, up );
  323. float flGoalSegmentLength = m_idealSegmentLength * ( m_idealLength / m_flCurrentLength);
  324. // goal forces
  325. #if 1
  326. for (i = iFirst; i <= iLast; i++)
  327. {
  328. // DevMsg("(%d) %.2f\n", i, t );
  329. float flInfluence = m_body[i].flGoalInfluence;
  330. if (flInfluence > 0)
  331. {
  332. m_body[i].flGoalInfluence = 0.0;
  333. Vector v0 = (m_body[i].vecGoalPos - m_body[i].vecPos);
  334. float length = v0.Length();
  335. if (length > sv_hydraGoalDelta.GetFloat())
  336. {
  337. v0 = v0 * sv_hydraGoalDelta.GetFloat() / length;
  338. }
  339. m_body[i].vecDelta += v0 * flInfluence * sv_hydraGoalTension.GetFloat();
  340. // NDebugOverlay::Box(m_body[i].vecGoalPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255, 255, 0, flInfluence * 255, .1);
  341. }
  342. }
  343. #endif
  344. // bending forces
  345. for (i = iFirst-1; i <= iLast - 1; i++)
  346. {
  347. // DevMsg("(%d) %.2f\n", i, t );
  348. Vector v3 = m_body[i+1].vecPos - m_body[i-1].vecPos;
  349. VectorNormalize( v3 );
  350. Vector delta;
  351. float length;
  352. //NDebugOverlay::Line( m_body[i].vecPos + v3 * flGoalSegmentLength, m_body[i].vecPos - v3 * flGoalSegmentLength, 255, 0, 0, true, .1);
  353. if (i+1 <= iLast)
  354. {
  355. // towards head
  356. delta = (m_body[i].vecPos + v3 * flGoalSegmentLength - m_body[i+1].vecPos) * sv_hydraBendTension.GetFloat();
  357. length = delta.Length();
  358. if (length > sv_hydraBendDelta.GetFloat())
  359. {
  360. delta = delta * (sv_hydraBendDelta.GetFloat() / length);
  361. }
  362. m_body[i+1].vecDelta += delta;
  363. //NDebugOverlay::Line( m_body[i+1].vecPos, m_body[i+1].vecPos + delta, 255, 0, 0, true, .1);
  364. }
  365. if (i-1 >= iFirst)
  366. {
  367. // towards tail
  368. delta = (m_body[i].vecPos - v3 * flGoalSegmentLength - m_body[i-1].vecPos) * sv_hydraBendTension.GetFloat();
  369. length = delta.Length();
  370. if (length > sv_hydraBendDelta.GetFloat())
  371. {
  372. delta = delta * (sv_hydraBendDelta.GetFloat() / length);
  373. }
  374. m_body[i-1].vecDelta += delta * 0.8;
  375. //NDebugOverlay::Line( m_body[i-1].vecPos, m_body[i-1].vecPos + delta, 255, 0, 0, true, .1);
  376. }
  377. }
  378. m_body[0].vecDelta = Vector( 0, 0, 0 );
  379. m_body[1].vecDelta = Vector( 0, 0, 0 );
  380. // normal gravity forces
  381. for (i = iFirst; i <= iLast; i++)
  382. {
  383. if (!m_body[i].bStuck)
  384. {
  385. m_body[i].vecDelta.z -= 3.84 * 0.2;
  386. }
  387. }
  388. #if 0
  389. // move delta's back toward the root
  390. for (i = iLast; i > iFirst; i--)
  391. {
  392. Vector tmp = m_body[i].vecDelta;
  393. m_body[i].vecDelta = tmp * 0.8;
  394. m_body[i-1].vecDelta += tmp * 0.2;
  395. }
  396. #endif
  397. // prevent stretching
  398. int maxChecks = m_body.Count() * 4;
  399. i = iLast;
  400. while (i > iFirst && maxChecks > 0)
  401. {
  402. bool didStretch = false;
  403. Vector stretch = (m_body[i].vecPos + m_body[i].vecDelta) - (m_body[i-1].vecPos + m_body[i-1].vecDelta);
  404. float t = VectorNormalize( stretch );
  405. if (t > flGoalSegmentLength)
  406. {
  407. float f0 = DotProduct( m_body[i].vecDelta, stretch );
  408. float f1 = DotProduct( m_body[i-1].vecDelta, stretch );
  409. if (f0 > 0 && f0 > f1)
  410. {
  411. // Vector limit = stretch * (f0 - flGoalSegmentLength);
  412. Vector limit = stretch * (t - flGoalSegmentLength);
  413. // propagate pulling back down the chain
  414. m_body[i].vecDelta -= limit * 0.5;
  415. m_body[i-1].vecDelta += limit * 0.5;
  416. didStretch = true;
  417. }
  418. }
  419. if (didStretch)
  420. {
  421. if (i < iLast)
  422. {
  423. i++;
  424. }
  425. }
  426. else
  427. {
  428. i--;
  429. }
  430. maxChecks--;
  431. }
  432. }
  433. //-----------------------------------------------------------------------------
  434. // Purpose: Move the body, check for collisions
  435. // Input :
  436. // Output :
  437. //-----------------------------------------------------------------------------
  438. void CNPC_Hydra::MoveBody( )
  439. {
  440. int i;
  441. int iFirst = 2;
  442. int iLast = m_body.Count() - 1;
  443. // clear stuck flags
  444. for (i = 0; i <= iLast; i++)
  445. {
  446. m_body[i].bStuck = false;
  447. }
  448. // try to move all the nodes
  449. for (i = iFirst; i <= iLast; i++)
  450. {
  451. trace_t tr;
  452. // check direct movement
  453. AI_TraceHull(m_body[i].vecPos, m_body[i].vecPos + m_body[i].vecDelta,
  454. Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
  455. MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
  456. Vector direct = tr.endpos;
  457. Vector delta = Vector( 0, 0, 0 );
  458. Vector slide = m_body[i].vecDelta;
  459. if (tr.fraction != 1.0)
  460. {
  461. // slow down and remove all motion in the direction of the plane
  462. direct += tr.plane.normal;
  463. Vector impactSpeed = (slide * tr.plane.normal) * tr.plane.normal;
  464. slide = (slide - impactSpeed) * 0.8;
  465. if (tr.m_pEnt)
  466. {
  467. if (i == iLast)
  468. {
  469. Stab( tr.m_pEnt, impactSpeed, tr );
  470. }
  471. else
  472. {
  473. Nudge( tr.m_pEnt, direct, impactSpeed );
  474. }
  475. }
  476. // slow down and remove all motion in the direction of the plane
  477. slide = (slide - (slide * tr.plane.normal) * tr.plane.normal) * 0.8;
  478. // try to move the remaining distance anyways
  479. AI_TraceHull(direct, direct + slide * (1 - tr.fraction),
  480. Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
  481. MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
  482. // NDebugOverlay::Line( m_body[i].vecPos, tr.endpos, 255, 255, 0, true, 1);
  483. direct = tr.endpos;
  484. m_body[i].bStuck = true;
  485. }
  486. // make sure the new segment doesn't intersect the world
  487. AI_TraceHull(direct, m_body[i-1].vecPos,
  488. Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
  489. MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
  490. if (tr.fraction == 1.0)
  491. {
  492. if (i+1 < iLast)
  493. {
  494. AI_TraceHull(direct, m_body[i+1].vecPos,
  495. Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
  496. MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
  497. }
  498. if (tr.fraction == 1.0)
  499. {
  500. m_body[i].vecPos = direct;
  501. delta = slide;
  502. }
  503. else
  504. {
  505. // FIXME: compute nudge force
  506. m_body[i].bStuck = true;
  507. //m_body[i+1].bStuck = true;
  508. }
  509. }
  510. else
  511. {
  512. // FIXME: compute nudge force
  513. m_body[i].bStuck = true;
  514. //m_body[i-1].bStuck = true;
  515. }
  516. // m_body[i-1].vecDelta += (m_body[i].vecDelta - delta) * 0.25;
  517. // m_body[i+1].vecDelta += (m_body[i].vecDelta - delta) * 0.25;
  518. m_body[i].vecDelta = delta;
  519. }
  520. }
  521. //-----------------------------------------------------------------------------
  522. // Purpose: Push physics objects around if they get hit
  523. // Input : vecContact = point in space where contact supposidly happened
  524. // vecSpeed = in/sec of contact
  525. // Output :
  526. //-----------------------------------------------------------------------------
  527. void CNPC_Hydra::Nudge( CBaseEntity *pOther, const Vector &vecContact, const Vector &vecSpeed )
  528. {
  529. if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS )
  530. {
  531. return;
  532. }
  533. IPhysicsObject *pOtherPhysics = pOther->VPhysicsGetObject();
  534. // Put the force on the line between the "contact point" and hit object origin
  535. //Vector posOther;
  536. //pOtherPhysics->GetPosition( &posOther, NULL );
  537. // force is a 30kg object going 100 in/s
  538. pOtherPhysics->ApplyForceOffset( vecSpeed * 30, vecContact );
  539. }
  540. //-----------------------------------------------------------------------------
  541. // Purpose: Push physics objects around if they get hit
  542. // Input : vecContact = point in space where contact supposidly happened
  543. // vecSpeed = in/sec of contact
  544. // Output :
  545. //-----------------------------------------------------------------------------
  546. void CNPC_Hydra::Stab( CBaseEntity *pOther, const Vector &vecSpeed, trace_t &tr )
  547. {
  548. if (pOther->m_takedamage == DAMAGE_YES && !pOther->IsPlayer())
  549. {
  550. Vector dir = vecSpeed;
  551. VectorNormalize( dir );
  552. if ( !sv_hydraTestSpike.GetInt() )
  553. {
  554. ClearMultiDamage();
  555. // FIXME: this is bogus
  556. CTakeDamageInfo info( this, this, pOther->m_iHealth+25, DMG_SLASH );
  557. CalculateMeleeDamageForce( &info, dir, tr.endpos );
  558. pOther->DispatchTraceAttack( info, dir, &tr );
  559. ApplyMultiDamage();
  560. }
  561. else
  562. {
  563. CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pOther);
  564. if ( pAnimating )
  565. {
  566. AttachStabbedEntity( pAnimating, vecSpeed * 30, tr );
  567. }
  568. }
  569. }
  570. else
  571. {
  572. Nudge( pOther, tr.endpos, vecSpeed );
  573. }
  574. }
  575. //-----------------------------------------------------------------------------
  576. // Purpose:
  577. // Input : vecContact = point in space where contact supposidly happened
  578. // vecSpeed = in/sec of contact
  579. // Output :
  580. //-----------------------------------------------------------------------------
  581. void CNPC_Hydra::Kick( CBaseEntity *pHitEntity, const Vector &vecContact, const Vector &vecSpeed )
  582. {
  583. }
  584. //-----------------------------------------------------------------------------
  585. // Purpose:
  586. // Input : vecContact = point in space where contact supposidly happened
  587. // vecSpeed = in/sec of contact
  588. // Output :
  589. //-----------------------------------------------------------------------------
  590. void CNPC_Hydra::Splash( const Vector &vecSplashPos )
  591. {
  592. }
  593. //-----------------------------------------------------------------------------
  594. // Purpose: Calculate the actual hydra length
  595. // Input :
  596. // Output :
  597. //-----------------------------------------------------------------------------
  598. void CNPC_Hydra::CheckLength( )
  599. {
  600. int i;
  601. ClearCondition( COND_HYDRA_SNAGGED );
  602. ClearCondition( COND_HYDRA_NOSTUCK );
  603. ClearCondition( COND_HYDRA_OVERSTRETCH );
  604. m_bHasStuckSegments = m_body[m_body.Count() - 1].bStuck;
  605. m_flCurrentLength = 0;
  606. for (i = 1; i < m_body.Count() - 1; i++)
  607. {
  608. float length = (m_body[i+1].vecPos - m_body[i].vecPos).Length();
  609. Assert( m_body[i+1].vecPos.IsValid( ) );
  610. Assert( m_body[i].vecPos.IsValid( ) );
  611. Assert( IsFinite( length ) );
  612. m_body[i].flActualLength = length;
  613. m_flCurrentLength += length;
  614. // check for over streatched segements
  615. if (length > m_idealSegmentLength * 3.0 && (m_body[i].bStuck || m_body[i+1].bStuck))
  616. {
  617. //NDebugOverlay::Line( m_body[i].vecPos, m_body[i+1].vecPos, 255, 0, 0, true, 1.0);
  618. SetCondition( COND_HYDRA_SNAGGED );
  619. }
  620. if (m_body[i].bStuck)
  621. {
  622. m_bHasStuckSegments = true;
  623. }
  624. }
  625. if (m_flCurrentLength > HYDRA_MAX_LENGTH) // FIXME
  626. {
  627. SetCondition( COND_HYDRA_OVERSTRETCH );
  628. }
  629. if (!m_bHasStuckSegments)
  630. {
  631. SetCondition( COND_HYDRA_NOSTUCK );
  632. }
  633. }
  634. //-----------------------------------------------------------------------------
  635. // Purpose: Grow or shrink the hydra, as needed
  636. // Input :
  637. // Output :
  638. //-----------------------------------------------------------------------------
  639. void CNPC_Hydra::AdjustLength( )
  640. {
  641. m_body[0].vecPos = m_body[1].vecPos - m_vecOutward * m_idealSegmentLength ;
  642. // DevMsg( "actual %.0f ideal %.0f relaxed %.0f\n", actualLength, m_idealLength, m_idealSegmentLength * (m_body.Count() - 3) );
  643. CalcRelaxedLength( );
  644. // "NPC_Hydra.ExtendTentacle"
  645. bool bAdjustFailed = false;
  646. bool bShouldAdjust = false;
  647. if (m_flCurrentLength < m_idealLength)
  648. {
  649. if (m_flRelaxedLength + m_idealSegmentLength * 0.5 < m_idealLength)
  650. {
  651. bShouldAdjust = true;
  652. //if (!GrowFromMostStretched( ))
  653. if (!GrowFromVirtualRoot())
  654. {
  655. bAdjustFailed = true;
  656. }
  657. }
  658. }
  659. else if (m_flCurrentLength > m_idealLength)
  660. {
  661. // if (relaxedLength > actualLength)
  662. if (m_flRelaxedLength - m_idealSegmentLength * 0.5 > m_idealLength || HasCondition( COND_HYDRA_SNAGGED ))
  663. {
  664. bShouldAdjust = true;
  665. if (!ContractFromRoot())
  666. {
  667. if (!ContractBetweenStuckSegments())
  668. {
  669. if (!ContractFromHead())
  670. {
  671. bAdjustFailed = true;
  672. }
  673. }
  674. }
  675. }
  676. else if (gpGlobals->curtime - m_flLastAdjustmentTime > 1.0)
  677. {
  678. bShouldAdjust = true;
  679. // start to panic
  680. if (!GrowFromMostStretched( ))
  681. {
  682. bAdjustFailed = true;
  683. }
  684. // SplitLongestSegment( );
  685. /*
  686. if (!ContractBetweenStuckSegments())
  687. {
  688. if (!ContractFromHead())
  689. {
  690. }
  691. }
  692. */
  693. }
  694. else
  695. {
  696. bAdjustFailed = true;
  697. }
  698. }
  699. if (!bAdjustFailed)
  700. {
  701. m_flLastAdjustmentTime = gpGlobals->curtime;
  702. if (bShouldAdjust && !m_bExtendSoundActive)
  703. {
  704. m_bExtendSoundActive = true;
  705. //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  706. //controller.SoundChangeVolume( m_pExtendTentacleSound, 1.0, 0.1 );
  707. }
  708. }
  709. else if (bShouldAdjust)
  710. {
  711. m_bExtendSoundActive = false;
  712. //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  713. //controller.SoundChangeVolume( m_pExtendTentacleSound, 0.0, 0.3 );
  714. }
  715. CalcRelaxedLength( );
  716. }
  717. //-----------------------------------------------------------------------------
  718. // Purpose: Remove nodes, starting at the end, regardless of length
  719. // Input :
  720. // Output :
  721. //-----------------------------------------------------------------------------
  722. bool CNPC_Hydra::ContractFromHead( )
  723. {
  724. if (m_body.Count() <= 2)
  725. {
  726. return false;
  727. }
  728. int iNode = m_body.Count() - 1;
  729. if (m_body[iNode].bStuck && m_body[iNode-1].flActualLength > m_idealSegmentLength * 2.0)
  730. {
  731. AddNodeBefore( iNode );
  732. iNode = m_body.Count() - 1;
  733. }
  734. if (m_body.Count() <= 3)
  735. {
  736. return false;
  737. }
  738. // always legal since no new link is being formed
  739. m_body.Remove( iNode );
  740. CalcRelaxedLength( );
  741. return true;
  742. }
  743. //-----------------------------------------------------------------------------
  744. // Purpose: Starting at the first stuck node back from the head, find a node to remove
  745. // between it and the actual root who is part of a chain that isn't too long.
  746. // Input :
  747. // Output :
  748. //-----------------------------------------------------------------------------
  749. bool CNPC_Hydra::ContractBetweenStuckSegments( )
  750. {
  751. if (m_body.Count() <= 3)
  752. return false;
  753. // first first stuck segment closest to head;
  754. int iStuckHead = VirtualRoot( );
  755. if (iStuckHead < 3)
  756. return false;
  757. // find a non stuck node with the shortest distance between its neighbors
  758. int iShortest = -1;
  759. float dist = m_idealSegmentLength * 2;
  760. int i;
  761. for (i = iStuckHead - 1; i > 2; i--)
  762. {
  763. if (!m_body[i].bStuck)
  764. {
  765. float length = (m_body[i-1].vecPos - m_body[i+1].vecPos).Length();
  766. // check segment length
  767. if (length < dist )
  768. {
  769. if (IsValidConnection( i-1, i+1 ))
  770. {
  771. dist = length;
  772. iShortest = i;
  773. }
  774. }
  775. }
  776. }
  777. if (iShortest = -1)
  778. return false;
  779. // FIXME: check for tunneling
  780. m_body.Remove( iShortest );
  781. CalcRelaxedLength( );
  782. return true;
  783. }
  784. //-----------------------------------------------------------------------------
  785. // Purpose: Try to remove segment closest to root
  786. // Input :
  787. // Output :
  788. //-----------------------------------------------------------------------------
  789. bool CNPC_Hydra::ContractFromRoot( )
  790. {
  791. if (m_body.Count() <= 3)
  792. return false;
  793. // don't contract overly long segments
  794. if (m_body[2].flActualLength > m_idealSegmentLength * 2.0)
  795. return false;
  796. if (!IsValidConnection( 1, 3 ))
  797. return false;
  798. m_body.Remove( 2 );
  799. CalcRelaxedLength( );
  800. return true;
  801. }
  802. //-----------------------------------------------------------------------------
  803. // Purpose: Find the first stuck node that's closest to the head
  804. // Input :
  805. // Output :
  806. //-----------------------------------------------------------------------------
  807. int CNPC_Hydra::VirtualRoot( )
  808. {
  809. // first first stuck segment closest to head;
  810. int iStuckHead;
  811. for (iStuckHead = m_body.Count() - 2; iStuckHead > 1; iStuckHead--)
  812. {
  813. if (m_body[iStuckHead].bStuck)
  814. {
  815. return iStuckHead;
  816. }
  817. }
  818. return 1;
  819. }
  820. //-----------------------------------------------------------------------------
  821. // Purpose: Insert a node before the given node.
  822. // Input :
  823. // Output :
  824. //-----------------------------------------------------------------------------
  825. bool CNPC_Hydra::AddNodeBefore( int iNode )
  826. {
  827. if (iNode < 1)
  828. return false;
  829. HydraBone bone;
  830. bone.vecPos = (m_body[iNode].vecPos + m_body[iNode-1].vecPos) * 0.5;
  831. bone.vecDelta = (m_body[iNode].vecDelta + m_body[iNode-1].vecDelta) * 0.5;
  832. /*
  833. // FIXME: can't do this, may be embedded in the world
  834. int i0 = (iNode>2)?iNode-2:0;
  835. int i1 = (iNode>1)?iNode-1:0;
  836. int i2 = iNode;
  837. int i3 = (iNode<m_body.Count()-1)?iNode+1:m_body.Count()-1;
  838. Catmull_Rom_Spline( m_body[i0].vecPos, m_body[i1].vecPos, m_body[i2].vecPos, m_body[i3].vecPos, 0.5, bone.vecPos );
  839. */
  840. bone.flActualLength = (m_body[iNode].vecPos - bone.vecPos).Length();
  841. bone.flIdealLength = m_idealSegmentLength;
  842. m_body[iNode-1].flActualLength = bone.flActualLength;
  843. //Vector vecGoalPos;
  844. //float flGoalInfluence;
  845. m_body.InsertBefore( iNode, bone );
  846. return true;
  847. }
  848. bool CNPC_Hydra::AddNodeAfter( int iNode )
  849. {
  850. AddNodeBefore( iNode + 1 );
  851. return false;
  852. }
  853. bool CNPC_Hydra::GrowFromVirtualRoot( )
  854. {
  855. if (m_body[1].flActualLength < m_idealSegmentLength * 0.5)
  856. return false;
  857. return AddNodeAfter( 1 );
  858. }
  859. bool CNPC_Hydra::GrowFromMostStretched( )
  860. {
  861. int iNode = VirtualRoot( );
  862. int iLongest = iNode;
  863. float dist = m_idealSegmentLength * 0.5;
  864. for (iNode; iNode < m_body.Count() - 1; iNode++)
  865. {
  866. if (m_body[iNode].flActualLength > dist)
  867. {
  868. iLongest = iNode;
  869. dist = m_body[iNode].flActualLength;
  870. }
  871. }
  872. if (m_body[iLongest].flActualLength <= dist)
  873. {
  874. return AddNodeAfter( iLongest );
  875. }
  876. return false;
  877. }
  878. void CNPC_Hydra::CalcRelaxedLength( )
  879. {
  880. m_flRelaxedLength = m_idealSegmentLength * (m_body.Count() -2) + HYDRA_OUTWARD_BIAS;
  881. }
  882. bool CNPC_Hydra::IsValidConnection( int iNode0, int iNode1 )
  883. {
  884. trace_t tr;
  885. // check to make sure new connection is valid
  886. AI_TraceHull(m_body[iNode0].vecPos, m_body[iNode1].vecPos,
  887. Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
  888. MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
  889. if (tr.fraction == 1.0)
  890. {
  891. return true;
  892. }
  893. return false;
  894. }
  895. //-------------------------------------
  896. float CNPC_Hydra::MaxYawSpeed()
  897. {
  898. return 0;
  899. if( IsMoving() )
  900. {
  901. return 20;
  902. }
  903. switch( GetActivity() )
  904. {
  905. case ACT_180_LEFT:
  906. return 30;
  907. break;
  908. case ACT_TURN_LEFT:
  909. case ACT_TURN_RIGHT:
  910. return 30;
  911. break;
  912. default:
  913. return 15;
  914. break;
  915. }
  916. }
  917. //-------------------------------------
  918. int CNPC_Hydra::TranslateSchedule( int scheduleType )
  919. {
  920. return BaseClass::TranslateSchedule( scheduleType );
  921. }
  922. //-------------------------------------
  923. void CNPC_Hydra::HandleAnimEvent( animevent_t *pEvent )
  924. {
  925. BaseClass::HandleAnimEvent( pEvent );
  926. }
  927. //-------------------------------------
  928. void CNPC_Hydra::PrescheduleThink()
  929. {
  930. BaseClass::PrescheduleThink();
  931. if ( m_bStabbedEntity )
  932. {
  933. UpdateStabbedEntity();
  934. }
  935. }
  936. //-------------------------------------
  937. int CNPC_Hydra::SelectSchedule ()
  938. {
  939. switch ( m_NPCState )
  940. {
  941. case NPC_STATE_IDLE:
  942. {
  943. SetState( NPC_STATE_ALERT );
  944. return SCHED_HYDRA_DEPLOY;
  945. }
  946. break;
  947. case NPC_STATE_ALERT:
  948. {
  949. return SCHED_HYDRA_STAB;
  950. }
  951. break;
  952. case NPC_STATE_COMBAT:
  953. {
  954. if (HasCondition( COND_HYDRA_SNAGGED ))
  955. {
  956. return SCHED_HYDRA_PULLBACK;
  957. }
  958. else if (HasCondition( COND_HYDRA_OVERSTRETCH ))
  959. {
  960. return SCHED_HYDRA_STAB;
  961. }
  962. return SCHED_HYDRA_STAB;
  963. }
  964. break;
  965. }
  966. return BaseClass::SelectSchedule();
  967. }
  968. //-------------------------------------
  969. void CNPC_Hydra::StartTask( const Task_t *pTask )
  970. {
  971. switch( pTask->iTask )
  972. {
  973. case TASK_HYDRA_DEPLOY:
  974. m_vecHeadGoal = GetAbsOrigin( ) + m_vecOutward * 100;
  975. m_idealLength = 100;
  976. m_vecHeadDir = m_vecOutward;
  977. return;
  978. case TASK_HYDRA_PREP_STAB:
  979. {
  980. m_flTaskEndTime = gpGlobals->curtime + pTask->flTaskData;
  981. // Go outward
  982. m_vecHeadGoal = GetAbsOrigin( ) + m_vecOutward * 100;
  983. SetTarget( (CBaseEntity *)UTIL_GetLocalPlayer() );
  984. if (GetEnemy())
  985. {
  986. SetTarget( GetEnemy() );
  987. }
  988. //CPASAttenuationFilter filter( this, "NPC_Hydra.Alert" );
  989. //Vector vecHead = EyePosition();
  990. //EmitSound( filter, entindex(), "NPC_Hydra.Alert", &vecHead );
  991. }
  992. return;
  993. case TASK_HYDRA_STAB:
  994. {
  995. //CPASAttenuationFilter filter( this, "NPC_Hydra.Attack" );
  996. //Vector vecHead = EyePosition();
  997. //EmitSound( filter, entindex(), "NPC_Hydra.Attack", &vecHead );
  998. m_flTaskEndTime = gpGlobals->curtime + 0.5;
  999. }
  1000. return;
  1001. case TASK_HYDRA_PULLBACK:
  1002. m_vecHeadGoal = GetAbsOrigin( ) + m_vecOutward * pTask->flTaskData;
  1003. m_idealLength = pTask->flTaskData * 1.1;
  1004. return;
  1005. default:
  1006. BaseClass::StartTask( pTask );
  1007. break;
  1008. }
  1009. }
  1010. //-------------------------------------
  1011. void CNPC_Hydra::RunTask( const Task_t *pTask )
  1012. {
  1013. switch( pTask->iTask )
  1014. {
  1015. case TASK_HYDRA_DEPLOY:
  1016. {
  1017. m_flHeadGoalInfluence = 1.0;
  1018. float dist = (EyePosition() - m_vecHeadGoal).Length();
  1019. if (dist < m_idealSegmentLength)
  1020. {
  1021. TaskComplete();
  1022. }
  1023. AimHeadInTravelDirection( 0.2 );
  1024. }
  1025. break;
  1026. case TASK_HYDRA_PREP_STAB:
  1027. {
  1028. int i;
  1029. if (m_body.Count() < 2)
  1030. {
  1031. TaskFail( "hydra is too short to begin stab" );
  1032. return;
  1033. }
  1034. CBaseEntity *pTarget = GetTarget();
  1035. if (pTarget == NULL)
  1036. {
  1037. TaskFail( FAIL_NO_TARGET );
  1038. }
  1039. if (pTarget->IsPlayer())
  1040. {
  1041. m_vecTarget = pTarget->EyePosition( );
  1042. }
  1043. else
  1044. {
  1045. m_vecTarget = pTarget->BodyTarget( EyePosition( ) );
  1046. }
  1047. float distToTarget = (m_vecTarget - m_vecHeadGoal).Length();
  1048. float distToBase = (m_vecHeadGoal - GetAbsOrigin()).Length();
  1049. m_idealLength = distToTarget + distToBase * 0.5;
  1050. if (m_idealLength > HYDRA_MAX_LENGTH)
  1051. m_idealLength = HYDRA_MAX_LENGTH;
  1052. if (distToTarget < 100.0)
  1053. {
  1054. m_vecTargetDir = (m_vecTarget - m_vecHeadGoal);
  1055. VectorNormalize( m_vecTargetDir );
  1056. m_vecHeadGoal = m_vecHeadGoal - m_vecTargetDir * (100 - distToTarget) * 0.5;
  1057. }
  1058. else if (distToTarget > 200.0)
  1059. {
  1060. m_vecTargetDir = (m_vecTarget - m_vecHeadGoal);
  1061. VectorNormalize( m_vecTargetDir );
  1062. m_vecHeadGoal = m_vecHeadGoal - m_vecTargetDir * (200.0 - distToTarget) * 0.5;
  1063. }
  1064. // face enemy
  1065. m_vecTargetDir = (m_vecTarget - m_body[m_body.Count()-1].vecPos);
  1066. VectorNormalize( m_vecTargetDir );
  1067. m_vecHeadDir = m_vecHeadDir * 0.6 + m_vecTargetDir * 0.4;
  1068. VectorNormalize( m_vecHeadDir.GetForModify() );
  1069. // build tension towards strike time
  1070. float influence = 1.0 - (m_flTaskEndTime - gpGlobals->curtime) / pTask->flTaskData;
  1071. if (influence > 1)
  1072. influence = 1.0;
  1073. influence = influence * influence * influence;
  1074. m_flHeadGoalInfluence = influence;
  1075. // keep head segment straight
  1076. i = m_body.Count() - 2;
  1077. m_body[i].vecGoalPos = m_vecHeadGoal - m_vecHeadDir * m_body[i].flActualLength;
  1078. m_body[i].flGoalInfluence = influence;
  1079. // curve neck into spiral
  1080. float distBackFromHead = m_body[i].flActualLength;
  1081. Vector right, up;
  1082. VectorVectors( m_vecHeadDir, right, up );
  1083. for (i = i - 1; i > 1 && distBackFromHead < distToTarget; i--)
  1084. {
  1085. distBackFromHead += m_body[i].flActualLength;
  1086. float r = (distBackFromHead / 200) * 3.1415 * 2;
  1087. // spiral
  1088. Vector p0 = m_vecHeadGoal
  1089. - m_vecHeadDir * distBackFromHead * 0.5
  1090. + cos( r ) * m_body[i].flActualLength * right
  1091. + sin( r ) * m_body[i].flActualLength * up;
  1092. // base
  1093. r = (distBackFromHead / m_idealLength) * 3.1415 * 0.2;
  1094. r = sin( r );
  1095. p0 = p0 * (1 - r) + r * GetAbsOrigin();
  1096. m_body[i].vecGoalPos = p0;
  1097. m_body[i].flGoalInfluence = influence * (1.0 - (distBackFromHead / distToTarget));
  1098. /*
  1099. if ( (pEnemy->EyePosition( ) - m_body[i].vecPos).Length() < distBackFromHead)
  1100. {
  1101. if ( gpGlobals->curtime - m_flLastAttackTime > 4.0)
  1102. {
  1103. TaskComplete();
  1104. }
  1105. return;
  1106. }
  1107. */
  1108. }
  1109. // look to see if any of the goal positions are stuck
  1110. for (i = i; i < m_body.Count() - 1; i++)
  1111. {
  1112. if (m_body[i].bStuck)
  1113. {
  1114. Vector delta = DotProduct( m_body[i].vecGoalPos - m_body[i].vecPos, m_vecHeadDir) * m_vecHeadDir;
  1115. m_vecHeadGoal -= delta * m_body[i].flGoalInfluence;
  1116. break;
  1117. }
  1118. }
  1119. if ( gpGlobals->curtime >= m_flTaskEndTime )
  1120. {
  1121. if (distToTarget < 500)
  1122. {
  1123. TaskComplete( );
  1124. return;
  1125. }
  1126. else
  1127. {
  1128. TaskFail( "target is too far away" );
  1129. return;
  1130. }
  1131. }
  1132. }
  1133. return;
  1134. case TASK_HYDRA_STAB:
  1135. {
  1136. int i;
  1137. if (m_body.Count() < 2)
  1138. {
  1139. TaskFail( "hydra is too short to begin stab" );
  1140. return;
  1141. }
  1142. if (m_flTaskEndTime <= gpGlobals->curtime)
  1143. {
  1144. TaskComplete( );
  1145. return;
  1146. }
  1147. m_flHeadGoalInfluence = 1.0;
  1148. // face enemy
  1149. //m_vecHeadDir = (pEnemy->EyePosition( ) - m_body[m_body.Count()-1].vecPos);
  1150. //VectorNormalize( m_vecHeadDir.GetForModify() );
  1151. // keep head segment straight
  1152. i = m_body.Count() - 2;
  1153. m_body[i].vecGoalPos = m_vecHeadGoal + m_vecHeadDir * m_body[i].flActualLength;
  1154. m_body[i].flGoalInfluence = 1.0;
  1155. Vector vecToTarget = (m_vecTarget - EyePosition( ));
  1156. // check to see if we went past target
  1157. if (DotProduct( vecToTarget, m_vecHeadDir ) < 0.0)
  1158. {
  1159. TaskComplete( );
  1160. return;
  1161. }
  1162. float distToTarget = vecToTarget.Length();
  1163. float distToBase = (EyePosition( ) - GetAbsOrigin()).Length();
  1164. m_idealLength = distToTarget + distToBase;
  1165. /*
  1166. if (distToTarget < 20)
  1167. {
  1168. m_vecHeadGoal = m_vecTarget;
  1169. SetLastAttackTime( gpGlobals->curtime );
  1170. TaskComplete();
  1171. return;
  1172. }
  1173. else
  1174. */
  1175. {
  1176. // hit enemy
  1177. m_vecHeadGoal = m_vecTarget + m_vecHeadDir * 300;
  1178. }
  1179. if (m_idealLength > HYDRA_MAX_LENGTH)
  1180. m_idealLength = HYDRA_MAX_LENGTH;
  1181. // curve neck into spiral
  1182. float distBackFromHead = m_body[i].flActualLength;
  1183. Vector right, up;
  1184. VectorVectors( m_vecHeadDir, right, up );
  1185. #if 1
  1186. for (i = i - 1; i > 1 && distBackFromHead < distToTarget; i--)
  1187. {
  1188. Vector p0 = m_vecHeadGoal - m_vecHeadDir * distBackFromHead * 1.0;
  1189. m_body[i].vecGoalPos = p0;
  1190. if ((m_vecTarget - m_body[i].vecPos).Length() > distToTarget + distBackFromHead)
  1191. {
  1192. m_body[i].flGoalInfluence = 1.0 - (distBackFromHead / distToTarget);
  1193. }
  1194. else
  1195. {
  1196. m_body[i].vecGoalPos = EyePosition( ) - m_vecHeadDir * distBackFromHead;
  1197. m_body[i].flGoalInfluence = 1.0 - (distBackFromHead / distToTarget);
  1198. }
  1199. distBackFromHead += m_body[i].flActualLength;
  1200. }
  1201. #endif
  1202. }
  1203. return;
  1204. case TASK_HYDRA_PULLBACK:
  1205. {
  1206. if (m_body.Count() < 2)
  1207. {
  1208. TaskFail( "hydra is too short to begin stab" );
  1209. return;
  1210. }
  1211. CBaseEntity *pEnemy = (CBaseEntity *)UTIL_GetLocalPlayer();
  1212. if (GetEnemy() != NULL)
  1213. {
  1214. pEnemy = GetEnemy();
  1215. }
  1216. AimHeadInTravelDirection( 0.2 );
  1217. // float dist = (EyePosition() - m_vecHeadGoal).Length();
  1218. if (m_flCurrentLength < m_idealLength + m_idealSegmentLength)
  1219. {
  1220. TaskComplete();
  1221. }
  1222. }
  1223. break;
  1224. default:
  1225. BaseClass::RunTask( pTask );
  1226. break;
  1227. }
  1228. }
  1229. //-------------------------------------
  1230. Vector CNPC_Hydra::EyePosition( )
  1231. {
  1232. int i = m_body.Count() - 1;
  1233. if (i >= 0)
  1234. {
  1235. return m_body[i].vecPos;
  1236. }
  1237. return GetAbsOrigin();
  1238. }
  1239. const QAngle &CNPC_Hydra::EyeAngles()
  1240. {
  1241. return GetAbsAngles();
  1242. }
  1243. Vector CNPC_Hydra::BodyTarget( const Vector &posSrc, bool bNoisy)
  1244. {
  1245. int i;
  1246. if (m_body.Count() < 2)
  1247. {
  1248. return GetAbsOrigin();
  1249. }
  1250. int iShortest = 1;
  1251. float flShortestDist = (posSrc - m_body[iShortest].vecPos).LengthSqr();
  1252. for (i = 2; i < m_body.Count(); i++)
  1253. {
  1254. float flDist = (posSrc - m_body[i].vecPos).LengthSqr();
  1255. if (flDist < flShortestDist)
  1256. {
  1257. iShortest = i;
  1258. flShortestDist = flDist;
  1259. }
  1260. }
  1261. // NDebugOverlay::Box(m_body[iShortest].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0, 0, 255, 20, .1);
  1262. return m_body[iShortest].vecPos;
  1263. }
  1264. void CNPC_Hydra::AimHeadInTravelDirection( float flInfluence )
  1265. {
  1266. // aim in the direction of movement enemy
  1267. Vector delta = m_body[m_body.Count()-1].vecDelta;
  1268. VectorNormalize( delta );
  1269. if (DotProduct( delta, m_vecHeadDir ) < 0)
  1270. {
  1271. delta = -delta;
  1272. }
  1273. m_vecHeadDir = m_vecHeadDir * (1 - flInfluence) + delta * flInfluence;
  1274. VectorNormalize( m_vecHeadDir.GetForModify() );
  1275. }
  1276. //-------------------------------------
  1277. //-----------------------------------------------------------------------------
  1278. // Purpose: Hydra impaling is done by creating an entity, forming a constraint
  1279. // between that entity and the target ragdoll, and then updating then
  1280. // entity to follow the hydra.
  1281. //-----------------------------------------------------------------------------
  1282. //-----------------------------------------------------------------------------
  1283. // Purpose: This is the entity we create to follow the hydra
  1284. //-----------------------------------------------------------------------------
  1285. class CHydraImpale : public CBaseAnimating
  1286. {
  1287. DECLARE_CLASS( CHydraImpale, CBaseAnimating );
  1288. public:
  1289. DECLARE_DATADESC();
  1290. void Spawn( void );
  1291. void Precache( void );
  1292. void ImpaleThink( void );
  1293. IPhysicsConstraint *CreateConstraint( CNPC_Hydra *pHydra, IPhysicsObject *pTargetPhys, IPhysicsConstraintGroup *pGroup );
  1294. static CHydraImpale *Create( CNPC_Hydra *pHydra, CBaseEntity *pObject2 );
  1295. public:
  1296. IPhysicsConstraint *m_pConstraint;
  1297. CHandle<CNPC_Hydra> m_hHydra;
  1298. };
  1299. BEGIN_DATADESC( CHydraImpale )
  1300. DEFINE_PHYSPTR( m_pConstraint ),
  1301. DEFINE_FIELD( m_hHydra, FIELD_EHANDLE ),
  1302. DEFINE_THINKFUNC( ImpaleThink ),
  1303. END_DATADESC()
  1304. LINK_ENTITY_TO_CLASS( hydra_impale, CHydraImpale );
  1305. //-----------------------------------------------------------------------------
  1306. // Purpose: To by usable by the constraint system, this needs to have a phys model.
  1307. //-----------------------------------------------------------------------------
  1308. void CHydraImpale::Spawn( void )
  1309. {
  1310. Precache();
  1311. SetModel( "models/props_junk/cardboard_box001a.mdl" );
  1312. AddEffects( EF_NODRAW );
  1313. // We don't want this to be solid, because we don't want it to collide with the hydra.
  1314. SetSolid( SOLID_VPHYSICS );
  1315. AddSolidFlags( FSOLID_NOT_SOLID );
  1316. VPhysicsInitShadow( false, false );
  1317. // Disable movement on this sucker, we're going to move him manually
  1318. SetMoveType( MOVETYPE_FLY );
  1319. BaseClass::Spawn();
  1320. m_pConstraint = NULL;
  1321. }
  1322. //-----------------------------------------------------------------------------
  1323. // Purpose:
  1324. //-----------------------------------------------------------------------------
  1325. void CHydraImpale::Precache( void )
  1326. {
  1327. PrecacheModel( "models/props_junk/cardboard_box001a.mdl" );
  1328. BaseClass::Precache();
  1329. }
  1330. //-----------------------------------------------------------------------------
  1331. // Purpose: Update the impale entity's position to the hydra's desired
  1332. //-----------------------------------------------------------------------------
  1333. void CHydraImpale::ImpaleThink( void )
  1334. {
  1335. if ( !m_hHydra )
  1336. {
  1337. // Remove ourselves.
  1338. m_pConstraint->Deactivate();
  1339. UTIL_Remove( this );
  1340. return;
  1341. }
  1342. // Ask the Hydra where he'd like the ragdoll, and move ourselves there
  1343. Vector vecOrigin;
  1344. QAngle vecAngles;
  1345. m_hHydra->GetDesiredImpaledPosition( &vecOrigin, &vecAngles );
  1346. SetAbsOrigin( vecOrigin );
  1347. SetAbsAngles( vecAngles );
  1348. //NDebugOverlay::Cross3D( vecOrigin, Vector( -5, -5, -5 ), Vector( 5, 5, 5 ), 255, 0, 0, 20, .1);
  1349. SetNextThink( gpGlobals->curtime + 0.1f );
  1350. }
  1351. //-----------------------------------------------------------------------------
  1352. // Purpose: Activate/create the constraint
  1353. //-----------------------------------------------------------------------------
  1354. IPhysicsConstraint *CHydraImpale::CreateConstraint( CNPC_Hydra *pHydra, IPhysicsObject *pTargetPhys, IPhysicsConstraintGroup *pGroup )
  1355. {
  1356. m_hHydra = pHydra;
  1357. IPhysicsObject *pImpalePhysObject = VPhysicsGetObject();
  1358. Assert( pImpalePhysObject );
  1359. constraint_fixedparams_t fixed;
  1360. fixed.Defaults();
  1361. fixed.InitWithCurrentObjectState( pImpalePhysObject, pTargetPhys );
  1362. fixed.constraint.Defaults();
  1363. m_pConstraint = physenv->CreateFixedConstraint( pImpalePhysObject, pTargetPhys, pGroup, fixed );
  1364. if ( m_pConstraint )
  1365. {
  1366. m_pConstraint->SetGameData( (void *)this );
  1367. }
  1368. SetThink( ImpaleThink );
  1369. SetNextThink( gpGlobals->curtime );
  1370. return m_pConstraint;
  1371. }
  1372. //-----------------------------------------------------------------------------
  1373. // Purpose: Create a Hydra Impale between the hydra and the entity passed in
  1374. //-----------------------------------------------------------------------------
  1375. CHydraImpale *CHydraImpale::Create( CNPC_Hydra *pHydra, CBaseEntity *pTarget )
  1376. {
  1377. Vector vecOrigin;
  1378. QAngle vecAngles;
  1379. pHydra->GetDesiredImpaledPosition( &vecOrigin, &vecAngles );
  1380. pTarget->Teleport( &vecOrigin, &vecAngles, &vec3_origin );
  1381. CHydraImpale *pImpale = (CHydraImpale *)CBaseEntity::Create( "hydra_impale", vecOrigin, vecAngles );
  1382. if ( !pImpale )
  1383. return NULL;
  1384. IPhysicsObject *pTargetPhysObject = pTarget->VPhysicsGetObject();
  1385. if ( !pTargetPhysObject )
  1386. {
  1387. DevMsg(" Error: Tried to hydra_impale an entity without a physics model.\n" );
  1388. return NULL;
  1389. }
  1390. IPhysicsConstraintGroup *pGroup = NULL;
  1391. // Ragdoll? If so, use it's constraint group
  1392. CRagdollProp *pRagdoll = dynamic_cast<CRagdollProp*>(pTarget);
  1393. if ( pRagdoll )
  1394. {
  1395. pGroup = pRagdoll->GetRagdoll()->pGroup;
  1396. }
  1397. if ( !pImpale->CreateConstraint( pHydra, pTargetPhysObject, pGroup ) )
  1398. return NULL;
  1399. return pImpale;
  1400. }
  1401. void CNPC_Hydra::AttachStabbedEntity( CBaseAnimating *pAnimating, Vector vecForce, trace_t &tr )
  1402. {
  1403. CTakeDamageInfo info( this, this, pAnimating->m_iHealth+25, DMG_SLASH );
  1404. info.SetDamageForce( vecForce );
  1405. info.SetDamagePosition( tr.endpos );
  1406. CBaseEntity *pRagdoll = CreateServerRagdoll( pAnimating, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS );
  1407. // Create our impale entity
  1408. CHydraImpale::Create( this, pRagdoll );
  1409. m_bStabbedEntity = true;
  1410. UTIL_Remove( pAnimating );
  1411. }
  1412. void CNPC_Hydra::UpdateStabbedEntity( void )
  1413. {
  1414. /*
  1415. CBaseEntity *pEntity = m_grabController.GetAttached();
  1416. if ( !pEntity )
  1417. {
  1418. DetachStabbedEntity( false );
  1419. return;
  1420. }
  1421. QAngle vecAngles(0,0,1);
  1422. Vector vecOrigin = m_body[m_body.Count()-2].vecPos;
  1423. //NDebugOverlay::Cross3D( vecOrigin, Vector( -5, -5, -5 ), Vector( 5, 5, 5 ), 255, 0, 0, 20, .1);
  1424. m_grabController.SetTargetPosition( vecOrigin, vecAngles );
  1425. */
  1426. }
  1427. void CNPC_Hydra::DetachStabbedEntity( bool playSound )
  1428. {
  1429. /*
  1430. CBaseEntity *pObject = m_grabController.GetAttached();
  1431. if ( pObject != NULL )
  1432. {
  1433. IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
  1434. // Enable collision with this object again
  1435. if ( pPhysics != NULL )
  1436. {
  1437. physenv->EnableCollisions( pPhysics, VPhysicsGetObject() );
  1438. pPhysics->RecheckCollisionFilter();
  1439. }
  1440. }
  1441. m_grabController.DetachEntity();
  1442. */
  1443. if ( playSound )
  1444. {
  1445. //Play the detach sound
  1446. }
  1447. m_bStabbedEntity = false;
  1448. }
  1449. void CNPC_Hydra::GetDesiredImpaledPosition( Vector *vecOrigin, QAngle *vecAngles )
  1450. {
  1451. *vecOrigin = m_body[m_body.Count()-2].vecPos;
  1452. *vecAngles = QAngle(0,0,0);
  1453. }
  1454. //-----------------------------------------------------------------------------
  1455. //
  1456. // CNPC_Hydra Schedules
  1457. //
  1458. //-------------------------------------
  1459. AI_BEGIN_CUSTOM_NPC( npc_hydra, CNPC_Hydra )
  1460. //Register our interactions
  1461. //Conditions
  1462. DECLARE_CONDITION( COND_HYDRA_SNAGGED )
  1463. DECLARE_CONDITION( COND_HYDRA_STUCK )
  1464. DECLARE_CONDITION( COND_HYDRA_OVERSHOOT )
  1465. DECLARE_CONDITION( COND_HYDRA_OVERSTRETCH )
  1466. DECLARE_CONDITION( COND_HYDRA_STRIKE )
  1467. DECLARE_CONDITION( COND_HYDRA_NOSTUCK )
  1468. //Squad slots
  1469. //Tasks
  1470. DECLARE_TASK( TASK_HYDRA_RETRACT )
  1471. DECLARE_TASK( TASK_HYDRA_DEPLOY )
  1472. DECLARE_TASK( TASK_HYDRA_GET_OBJECT )
  1473. DECLARE_TASK( TASK_HYDRA_THROW_OBJECT )
  1474. DECLARE_TASK( TASK_HYDRA_PREP_STAB )
  1475. DECLARE_TASK( TASK_HYDRA_STAB )
  1476. DECLARE_TASK( TASK_HYDRA_PULLBACK )
  1477. //Activities
  1478. DECLARE_ACTIVITY( ACT_HYDRA_COWER )
  1479. DECLARE_ACTIVITY( ACT_HYDRA_STAB )
  1480. //=========================================================
  1481. // > SCHED_HYDRA_STAND_LOOK
  1482. //=========================================================
  1483. DEFINE_SCHEDULE
  1484. (
  1485. SCHED_HYDRA_DEPLOY,
  1486. " Tasks"
  1487. " TASK_HYDRA_DEPLOY 0"
  1488. " TASK_WAIT 0.5"
  1489. ""
  1490. " Interrupts"
  1491. " COND_NEW_ENEMY"
  1492. )
  1493. //=========================================================
  1494. // > SCHED_HYDRA_COWER
  1495. //=========================================================
  1496. DEFINE_SCHEDULE
  1497. (
  1498. SCHED_HYDRA_RETRACT,
  1499. " Tasks"
  1500. " TASK_STOP_MOVING 0"
  1501. " TASK_SET_ACTIVITY ACTIVITY:ACT_HYDRA_COWER"
  1502. " TASK_WAIT 0.5"
  1503. ""
  1504. " Interrupts"
  1505. )
  1506. DEFINE_SCHEDULE
  1507. (
  1508. SCHED_HYDRA_IDLE,
  1509. " Tasks"
  1510. " TASK_STOP_MOVING 0"
  1511. " TASK_WAIT_INDEFINITE 0"
  1512. ""
  1513. " Interrupts "
  1514. " COND_NEW_ENEMY"
  1515. )
  1516. DEFINE_SCHEDULE
  1517. (
  1518. SCHED_HYDRA_STAB,
  1519. " Tasks"
  1520. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HYDRA_DEPLOY"
  1521. " TASK_HYDRA_PREP_STAB 4.0"
  1522. " TASK_HYDRA_STAB 0"
  1523. " TASK_WAIT 0.5"
  1524. // " TASK_HYDRA_PULLBACK 100"
  1525. ""
  1526. " Interrupts "
  1527. " COND_NEW_ENEMY"
  1528. " COND_HYDRA_OVERSTRETCH"
  1529. )
  1530. DEFINE_SCHEDULE
  1531. (
  1532. SCHED_HYDRA_PULLBACK,
  1533. " Tasks"
  1534. " TASK_STOP_MOVING 0"
  1535. " TASK_WAIT 0.4"
  1536. " TASK_HYDRA_PULLBACK 100"
  1537. ""
  1538. " Interrupts "
  1539. " COND_NEW_ENEMY"
  1540. )
  1541. DEFINE_SCHEDULE
  1542. (
  1543. SCHED_HYDRA_THROW,
  1544. " Tasks"
  1545. " TASK_STOP_MOVING 0"
  1546. " TASK_HYDRA_GET_OBJECT 0"
  1547. " TASK_WAIT_FOR_MOVEMENT 0"
  1548. " TASK_HYDRA_THROW_OBJECT 0"
  1549. " TASK_WAIT 1"
  1550. ""
  1551. " Interrupts"
  1552. )
  1553. DEFINE_SCHEDULE
  1554. (
  1555. SCHED_HYDRA_RANGE_ATTACK,
  1556. " Tasks"
  1557. " TASK_STOP_MOVING 0"
  1558. " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
  1559. " TASK_FACE_ENEMY 0"
  1560. " TASK_RANGE_ATTACK1 0"
  1561. ""
  1562. " Interrupts"
  1563. " COND_NEW_ENEMY"
  1564. " COND_ENEMY_DEAD"
  1565. " COND_LIGHT_DAMAGE"
  1566. " COND_HEAVY_DAMAGE"
  1567. " COND_ENEMY_OCCLUDED"
  1568. " COND_NO_PRIMARY_AMMO"
  1569. " COND_HEAR_DANGER"
  1570. )
  1571. AI_END_CUSTOM_NPC()