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.

516 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: barnacle - stationary ceiling mounted 'fishing' monster
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //=============================================================================//
  9. #include "cbase.h"
  10. #include "hl1_npc_barnacle.h"
  11. #include "npcevent.h"
  12. #include "gib.h"
  13. #include "ai_default.h"
  14. #include "activitylist.h"
  15. #include "hl2_player.h"
  16. #include "vstdlib/random.h"
  17. #include "physics_saverestore.h"
  18. #include "vcollide_parse.h"
  19. #include "engine/IEngineSound.h"
  20. ConVar sk_barnacle_health( "sk_barnacle_health","25");
  21. //-----------------------------------------------------------------------------
  22. // Private activities.
  23. //-----------------------------------------------------------------------------
  24. static int ACT_EAT = 0;
  25. //-----------------------------------------------------------------------------
  26. // Interactions
  27. //-----------------------------------------------------------------------------
  28. int g_interactionBarnacleVictimDangle = 0;
  29. int g_interactionBarnacleVictimReleased = 0;
  30. int g_interactionBarnacleVictimGrab = 0;
  31. LINK_ENTITY_TO_CLASS( monster_barnacle, CNPC_Barnacle );
  32. IMPLEMENT_CUSTOM_AI( monster_barnacle, CNPC_Barnacle );
  33. //-----------------------------------------------------------------------------
  34. // Purpose: Initialize the custom schedules
  35. // Input :
  36. // Output :
  37. //-----------------------------------------------------------------------------
  38. void CNPC_Barnacle::InitCustomSchedules(void)
  39. {
  40. INIT_CUSTOM_AI(CNPC_Barnacle);
  41. ADD_CUSTOM_ACTIVITY(CNPC_Barnacle, ACT_EAT);
  42. g_interactionBarnacleVictimDangle = CBaseCombatCharacter::GetInteractionID();
  43. g_interactionBarnacleVictimReleased = CBaseCombatCharacter::GetInteractionID();
  44. g_interactionBarnacleVictimGrab = CBaseCombatCharacter::GetInteractionID();
  45. }
  46. BEGIN_DATADESC( CNPC_Barnacle )
  47. DEFINE_FIELD( m_flAltitude, FIELD_FLOAT ),
  48. DEFINE_FIELD( m_flKillVictimTime, FIELD_TIME ),
  49. DEFINE_FIELD( m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something.
  50. DEFINE_FIELD( m_fLiftingPrey, FIELD_BOOLEAN ),
  51. DEFINE_FIELD( m_flTongueAdj, FIELD_FLOAT ),
  52. DEFINE_FIELD( m_flIgnoreTouchesUntil, FIELD_TIME ),
  53. // Function pointers
  54. DEFINE_THINKFUNC( BarnacleThink ),
  55. DEFINE_THINKFUNC( WaitTillDead ),
  56. END_DATADESC()
  57. //=========================================================
  58. // Classify - indicates this monster's place in the
  59. // relationship table.
  60. //=========================================================
  61. Class_T CNPC_Barnacle::Classify ( void )
  62. {
  63. return CLASS_ALIEN_MONSTER;
  64. }
  65. //=========================================================
  66. // HandleAnimEvent - catches the monster-specific messages
  67. // that occur when tagged animation frames are played.
  68. //
  69. // Returns number of events handled, 0 if none.
  70. //=========================================================
  71. void CNPC_Barnacle::HandleAnimEvent( animevent_t *pEvent )
  72. {
  73. switch( pEvent->event )
  74. {
  75. case BARNACLE_AE_PUKEGIB:
  76. CGib::SpawnRandomGibs( this, 1, GIB_HUMAN );
  77. break;
  78. default:
  79. BaseClass::HandleAnimEvent( pEvent );
  80. break;
  81. }
  82. }
  83. //=========================================================
  84. // Spawn
  85. //=========================================================
  86. void CNPC_Barnacle::Spawn()
  87. {
  88. Precache( );
  89. SetModel( "models/barnacle.mdl" );
  90. UTIL_SetSize( this, Vector(-16, -16, -32), Vector(16, 16, 0) );
  91. SetSolid( SOLID_BBOX );
  92. AddSolidFlags( FSOLID_NOT_STANDABLE );
  93. SetMoveType( MOVETYPE_NONE );
  94. SetBloodColor( BLOOD_COLOR_GREEN );
  95. m_iHealth = sk_barnacle_health.GetFloat();
  96. m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
  97. m_NPCState = NPC_STATE_NONE;
  98. m_flKillVictimTime = 0;
  99. m_cGibs = 0;
  100. m_fLiftingPrey = FALSE;
  101. m_takedamage = DAMAGE_YES;
  102. InitBoneControllers();
  103. InitTonguePosition();
  104. // set eye position
  105. SetDefaultEyeOffset();
  106. SetActivity ( ACT_IDLE );
  107. SetThink ( &CNPC_Barnacle::BarnacleThink );
  108. SetNextThink( gpGlobals->curtime + 0.5f );
  109. //Do not have a shadow
  110. AddEffects( EF_NOSHADOW );
  111. m_flIgnoreTouchesUntil = gpGlobals->curtime;
  112. }
  113. //-----------------------------------------------------------------------------
  114. // Purpose:
  115. // Input :
  116. // Output :
  117. //-----------------------------------------------------------------------------
  118. int CNPC_Barnacle::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
  119. {
  120. CTakeDamageInfo info = inputInfo;
  121. if ( info.GetDamageType() & DMG_CLUB )
  122. {
  123. info.SetDamage( m_iHealth );
  124. }
  125. return BaseClass::OnTakeDamage_Alive( info );
  126. }
  127. //-----------------------------------------------------------------------------
  128. // Purpose: Initialize tongue position when first spawned
  129. // Input :
  130. // Output :
  131. //-----------------------------------------------------------------------------
  132. void CNPC_Barnacle::InitTonguePosition( void )
  133. {
  134. CBaseEntity *pTouchEnt;
  135. float flLength;
  136. pTouchEnt = TongueTouchEnt( &flLength );
  137. m_flAltitude = flLength;
  138. Vector origin;
  139. QAngle angle;
  140. GetAttachment( "TongueEnd", origin, angle );
  141. m_flTongueAdj = origin.z - GetAbsOrigin().z;
  142. }
  143. //-----------------------------------------------------------------------------
  144. // Purpose:
  145. //-----------------------------------------------------------------------------
  146. void CNPC_Barnacle::BarnacleThink ( void )
  147. {
  148. CBaseEntity *pTouchEnt;
  149. float flLength;
  150. SetNextThink( gpGlobals->curtime + 0.1f );
  151. if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
  152. {
  153. // AI Disabled, don't do anything
  154. }
  155. else if ( GetEnemy() != NULL )
  156. {
  157. // barnacle has prey.
  158. if ( !GetEnemy()->IsAlive() )
  159. {
  160. // someone (maybe even the barnacle) killed the prey. Reset barnacle.
  161. m_fLiftingPrey = FALSE;// indicate that we're not lifting prey.
  162. SetEnemy( NULL );
  163. return;
  164. }
  165. CBaseCombatCharacter* pVictim = GetEnemyCombatCharacterPointer();
  166. Assert( pVictim );
  167. if ( m_fLiftingPrey )
  168. {
  169. if ( GetEnemy() != NULL && pVictim->m_lifeState == LIFE_DEAD )
  170. {
  171. // crap, someone killed the prey on the way up.
  172. SetEnemy( NULL );
  173. m_fLiftingPrey = FALSE;
  174. return;
  175. }
  176. // still pulling prey.
  177. Vector vecNewEnemyOrigin = GetEnemy()->GetLocalOrigin();
  178. vecNewEnemyOrigin.x = GetLocalOrigin().x;
  179. vecNewEnemyOrigin.y = GetLocalOrigin().y;
  180. // guess as to where their neck is
  181. // FIXME: remove, ask victim where their neck is
  182. vecNewEnemyOrigin.x -= 6 * cos(GetEnemy()->GetLocalAngles().y * M_PI/180.0);
  183. vecNewEnemyOrigin.y -= 6 * sin(GetEnemy()->GetLocalAngles().y * M_PI/180.0);
  184. m_flAltitude -= BARNACLE_PULL_SPEED;
  185. vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED;
  186. if ( fabs( GetLocalOrigin().z - ( vecNewEnemyOrigin.z + GetEnemy()->GetViewOffset().z ) ) < BARNACLE_BODY_HEIGHT )
  187. {
  188. // prey has just been lifted into position ( if the victim origin + eye height + 8 is higher than the bottom of the barnacle, it is assumed that the head is within barnacle's body )
  189. m_fLiftingPrey = FALSE;
  190. CPASAttenuationFilter filter( this );
  191. EmitSound( filter, entindex(), "Barnacle.Bite");
  192. // Take a while to kill the player
  193. m_flKillVictimTime = gpGlobals->curtime + 10;
  194. if ( pVictim )
  195. {
  196. pVictim->DispatchInteraction( g_interactionBarnacleVictimDangle, NULL, this );
  197. SetActivity ( (Activity)ACT_EAT );
  198. }
  199. }
  200. CBaseEntity *pEnemy = GetEnemy();
  201. trace_t trace;
  202. UTIL_TraceEntity( pEnemy, pEnemy->GetAbsOrigin(), vecNewEnemyOrigin, MASK_SOLID_BRUSHONLY, pEnemy, COLLISION_GROUP_NONE, &trace );
  203. if( trace.fraction != 1.0 )
  204. {
  205. // The victim cannot be moved from their current origin to this new origin. So drop them.
  206. SetEnemy( NULL );
  207. m_fLiftingPrey = FALSE;
  208. if( pEnemy->MyCombatCharacterPointer() )
  209. {
  210. pEnemy->MyCombatCharacterPointer()->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
  211. }
  212. // Ignore touches long enough to let the victim move away.
  213. m_flIgnoreTouchesUntil = gpGlobals->curtime + 1.5;
  214. SetActivity( ACT_IDLE );
  215. return;
  216. }
  217. UTIL_SetOrigin ( GetEnemy(), vecNewEnemyOrigin );
  218. }
  219. else
  220. {
  221. // prey is lifted fully into feeding position and is dangling there.
  222. if ( m_flKillVictimTime != -1 && gpGlobals->curtime > m_flKillVictimTime )
  223. {
  224. // kill!
  225. if ( pVictim )
  226. {
  227. // DMG_CRUSH added so no physics force is generated
  228. pVictim->TakeDamage( CTakeDamageInfo( this, this, pVictim->m_iHealth, DMG_SLASH | DMG_ALWAYSGIB | DMG_CRUSH ) );
  229. m_cGibs = 3;
  230. }
  231. return;
  232. }
  233. // bite prey every once in a while
  234. if ( pVictim && ( random->RandomInt( 0, 49 ) == 0 ) )
  235. {
  236. CPASAttenuationFilter filter( this );
  237. EmitSound( filter, entindex(), "Barnacle.Chew" );
  238. if ( pVictim )
  239. {
  240. pVictim->DispatchInteraction( g_interactionBarnacleVictimDangle, NULL, this );
  241. }
  242. }
  243. }
  244. }
  245. else
  246. {
  247. // barnacle has no prey right now, so just idle and check to see if anything is touching the tongue.
  248. // If idle and no nearby client, don't think so often. Client should be out of PVS and not within 50 feet.
  249. if ( !UTIL_FindClientInPVS(edict()) )
  250. {
  251. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  252. if( pPlayer )
  253. {
  254. Vector vecDist = pPlayer->GetAbsOrigin() - GetAbsOrigin();
  255. if( vecDist.Length2DSqr() >= Square(600.0f) )
  256. {
  257. SetNextThink( gpGlobals->curtime + 1.5f );
  258. }
  259. }
  260. }
  261. if ( IsActivityFinished() )
  262. {// this is done so barnacle will fidget.
  263. SetActivity ( ACT_IDLE );
  264. }
  265. if ( m_cGibs && random->RandomInt(0,99) == 1 )
  266. {
  267. // cough up a gib.
  268. CGib::SpawnRandomGibs( this, 1, GIB_HUMAN );
  269. m_cGibs--;
  270. CPASAttenuationFilter filter( this );
  271. EmitSound( filter, entindex(), "Barnacle.Chew" );
  272. }
  273. pTouchEnt = TongueTouchEnt( &flLength );
  274. //NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, flLength ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,0, 0, 0.1 );
  275. if ( pTouchEnt != NULL )
  276. {
  277. // tongue is fully extended, and is touching someone.
  278. CBaseCombatCharacter* pBCC = (CBaseCombatCharacter *)pTouchEnt;
  279. // FIXME: humans should return neck position
  280. Vector vecGrabPos = pTouchEnt->GetAbsOrigin();
  281. if ( pBCC && pBCC->DispatchInteraction( g_interactionBarnacleVictimGrab, &vecGrabPos, this ) )
  282. {
  283. CPASAttenuationFilter filter( this );
  284. EmitSound( filter, entindex(), "Barnacle.Alert" );
  285. SetSequenceByName ( "attack1" );
  286. SetEnemy( pTouchEnt );
  287. pTouchEnt->SetMoveType( MOVETYPE_FLY );
  288. pTouchEnt->SetAbsVelocity( vec3_origin );
  289. pTouchEnt->SetBaseVelocity( vec3_origin );
  290. Vector origin = GetAbsOrigin();
  291. origin.z = pTouchEnt->GetAbsOrigin().z;
  292. pTouchEnt->SetLocalOrigin( origin );
  293. m_fLiftingPrey = TRUE;// indicate that we should be lifting prey.
  294. m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted.
  295. m_flAltitude = (GetAbsOrigin().z - vecGrabPos.z);
  296. }
  297. }
  298. else
  299. {
  300. // calculate a new length for the tongue to be clear of anything else that moves under it.
  301. if ( m_flAltitude < flLength )
  302. {
  303. // if tongue is higher than is should be, lower it kind of slowly.
  304. m_flAltitude += BARNACLE_PULL_SPEED;
  305. }
  306. else
  307. {
  308. m_flAltitude = flLength;
  309. }
  310. }
  311. }
  312. // ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj );
  313. //NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, m_flAltitude ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,255,255, 0, 0.1 );
  314. SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) );
  315. StudioFrameAdvance();
  316. }
  317. //-----------------------------------------------------------------------------
  318. // Purpose:
  319. //-----------------------------------------------------------------------------
  320. void CNPC_Barnacle::Event_Killed( const CTakeDamageInfo &info )
  321. {
  322. AddSolidFlags( FSOLID_NOT_SOLID );
  323. m_takedamage = DAMAGE_NO;
  324. m_lifeState = LIFE_DEAD;
  325. if ( GetEnemy() != NULL )
  326. {
  327. CBaseCombatCharacter *pVictim = GetEnemyCombatCharacterPointer();
  328. if ( pVictim )
  329. {
  330. pVictim->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
  331. }
  332. }
  333. CGib::SpawnRandomGibs( this, 4, GIB_HUMAN );
  334. CPASAttenuationFilter filter( this );
  335. EmitSound( filter, entindex(), "Barnacle.Die" );
  336. SetActivity ( ACT_DIESIMPLE );
  337. SetBoneController( 0, 0 );
  338. StudioFrameAdvance();
  339. SetNextThink( gpGlobals->curtime + 0.1f );
  340. SetThink ( &CNPC_Barnacle::WaitTillDead );
  341. }
  342. //-----------------------------------------------------------------------------
  343. // Purpose:
  344. //-----------------------------------------------------------------------------
  345. void CNPC_Barnacle::WaitTillDead ( void )
  346. {
  347. SetNextThink( gpGlobals->curtime + 0.1f );
  348. StudioFrameAdvance();
  349. DispatchAnimEvents ( this );
  350. if ( IsActivityFinished() )
  351. {
  352. // death anim finished.
  353. StopAnimation();
  354. SetThink ( NULL );
  355. }
  356. }
  357. //=========================================================
  358. // Precache - precaches all resources this monster needs
  359. //=========================================================
  360. void CNPC_Barnacle::Precache()
  361. {
  362. PrecacheModel("models/barnacle.mdl");
  363. PrecacheScriptSound( "Barnacle.Bite" );
  364. PrecacheScriptSound( "Barnacle.Chew" );
  365. PrecacheScriptSound( "Barnacle.Alert" );
  366. PrecacheScriptSound( "Barnacle.Die" );
  367. BaseClass::Precache();
  368. }
  369. //=========================================================
  370. // TongueTouchEnt - does a trace along the barnacle's tongue
  371. // to see if any entity is touching it. Also stores the length
  372. // of the trace in the int pointer provided.
  373. //=========================================================
  374. #define BARNACLE_CHECK_SPACING 8
  375. CBaseEntity *CNPC_Barnacle::TongueTouchEnt ( float *pflLength )
  376. {
  377. trace_t tr;
  378. float length;
  379. // trace once to hit architecture and see if the tongue needs to change position.
  380. UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() - Vector ( 0 , 0 , 2048 ),
  381. MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  382. length = fabs( GetAbsOrigin().z - tr.endpos.z );
  383. // Pull it up a tad
  384. length -= 16;
  385. if ( pflLength )
  386. {
  387. *pflLength = length;
  388. }
  389. // Don't try to touch any prey.
  390. if ( m_flIgnoreTouchesUntil > gpGlobals->curtime )
  391. return NULL;
  392. Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 );
  393. Vector mins = GetAbsOrigin() - delta;
  394. Vector maxs = GetAbsOrigin() + delta;
  395. maxs.z = GetAbsOrigin().z;
  396. // Take our current tongue's length or a point higher if we hit a wall
  397. // NOTENOTE: (this relieves the need to know if the tongue is currently moving)
  398. mins.z -= MIN( m_flAltitude, length );
  399. CBaseEntity *pList[10];
  400. int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_NPC) );
  401. if ( count )
  402. {
  403. for ( int i = 0; i < count; i++ )
  404. {
  405. CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pList[ i ] );
  406. bool bCanHurt = false;
  407. if ( IRelationType( pList[i] ) == D_HT || IRelationType( pList[i] ) == D_FR )
  408. bCanHurt = true;
  409. if ( pList[i] != this && bCanHurt == true && pVictim->m_lifeState == LIFE_ALIVE )
  410. {
  411. return pList[i];
  412. }
  413. }
  414. }
  415. return NULL;
  416. }