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.

1022 lines
27 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Implements the bullsquid
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "game.h"
  9. #include "AI_Default.h"
  10. #include "AI_Schedule.h"
  11. #include "AI_Hull.h"
  12. #include "AI_Navigator.h"
  13. #include "AI_Motor.h"
  14. #include "ai_squad.h"
  15. #include "npc_bullsquid.h"
  16. #include "npcevent.h"
  17. #include "soundent.h"
  18. #include "activitylist.h"
  19. #include "weapon_brickbat.h"
  20. #include "npc_headcrab.h"
  21. #include "player.h"
  22. #include "gamerules.h" // For g_pGameRules
  23. #include "ammodef.h"
  24. #include "grenade_spit.h"
  25. #include "grenade_brickbat.h"
  26. #include "entitylist.h"
  27. #include "shake.h"
  28. #include "vstdlib/random.h"
  29. #include "engine/IEngineSound.h"
  30. #include "movevars_shared.h"
  31. #include "AI_Hint.h"
  32. #include "AI_Senses.h"
  33. // memdbgon must be the last include file in a .cpp file!!!
  34. #include "tier0/memdbgon.h"
  35. #define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve
  36. ConVar sk_bullsquid_health( "sk_bullsquid_health", "0" );
  37. ConVar sk_bullsquid_dmg_bite( "sk_bullsquid_dmg_bite", "0" );
  38. ConVar sk_bullsquid_dmg_whip( "sk_bullsquid_dmg_whip", "0" );
  39. //=========================================================
  40. // monster-specific schedule types
  41. //=========================================================
  42. enum
  43. {
  44. SCHED_SQUID_HURTHOP = LAST_SHARED_SCHEDULE + 1,
  45. SCHED_SQUID_SEECRAB,
  46. SCHED_SQUID_EAT,
  47. SCHED_SQUID_SNIFF_AND_EAT,
  48. SCHED_SQUID_WALLOW,
  49. };
  50. //=========================================================
  51. // monster-specific tasks
  52. //=========================================================
  53. enum
  54. {
  55. TASK_SQUID_HOPTURN = LAST_SHARED_TASK + 1,
  56. TASK_SQUID_EAT,
  57. };
  58. //-----------------------------------------------------------------------------
  59. // Squid Conditions
  60. //-----------------------------------------------------------------------------
  61. enum
  62. {
  63. COND_SQUID_SMELL_FOOD = LAST_SHARED_CONDITION + 1,
  64. };
  65. //=========================================================
  66. // Interactions
  67. //=========================================================
  68. int g_interactionBullsquidThrow = 0;
  69. //=========================================================
  70. // Monster's Anim Events Go Here
  71. //=========================================================
  72. #define BSQUID_AE_SPIT ( 1 )
  73. #define BSQUID_AE_BITE ( 2 )
  74. #define BSQUID_AE_BLINK ( 3 )
  75. #define BSQUID_AE_ROAR ( 4 )
  76. #define BSQUID_AE_HOP ( 5 )
  77. #define BSQUID_AE_THROW ( 6 )
  78. #define BSQUID_AE_WHIP_SND ( 7 )
  79. LINK_ENTITY_TO_CLASS( npc_bullsquid, CNPC_Bullsquid );
  80. int ACT_SQUID_EXCITED;
  81. int ACT_SQUID_EAT;
  82. int ACT_SQUID_DETECT_SCENT;
  83. int ACT_SQUID_INSPECT_FLOOR;
  84. //---------------------------------------------------------
  85. // Save/Restore
  86. //---------------------------------------------------------
  87. BEGIN_DATADESC( CNPC_Bullsquid )
  88. DEFINE_FIELD( m_fCanThreatDisplay, FIELD_BOOLEAN ),
  89. DEFINE_FIELD( m_flLastHurtTime, FIELD_TIME ),
  90. DEFINE_FIELD( m_flNextSpitTime, FIELD_TIME ),
  91. // DEFINE_FIELD( m_nSquidSpitSprite, FIELD_INTEGER ),
  92. DEFINE_FIELD( m_flHungryTime, FIELD_TIME ),
  93. DEFINE_FIELD( m_nextSquidSoundTime, FIELD_TIME ),
  94. END_DATADESC()
  95. //=========================================================
  96. // Spawn
  97. //=========================================================
  98. void CNPC_Bullsquid::Spawn()
  99. {
  100. Precache( );
  101. SetModel( "models/bullsquid.mdl");
  102. SetHullType(HULL_WIDE_SHORT);
  103. SetHullSizeNormal();
  104. SetSolid( SOLID_BBOX );
  105. AddSolidFlags( FSOLID_NOT_STANDABLE );
  106. SetMoveType( MOVETYPE_STEP );
  107. m_bloodColor = BLOOD_COLOR_GREEN;
  108. SetRenderColor( 255, 255, 255, 255 );
  109. m_iHealth = sk_bullsquid_health.GetFloat();
  110. m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
  111. m_NPCState = NPC_STATE_NONE;
  112. CapabilitiesClear();
  113. CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 );
  114. m_fCanThreatDisplay = TRUE;
  115. m_flNextSpitTime = gpGlobals->curtime;
  116. NPCInit();
  117. m_flDistTooFar = 784;
  118. }
  119. //=========================================================
  120. // Precache - precaches all resources this monster needs
  121. //=========================================================
  122. void CNPC_Bullsquid::Precache()
  123. {
  124. PrecacheModel( "models/bullsquid.mdl" );
  125. m_nSquidSpitSprite = PrecacheModel("sprites/greenspit1.vmt");// client side spittle.
  126. UTIL_PrecacheOther( "grenade_spit" );
  127. PrecacheScriptSound( "NPC_Bullsquid.Idle" );
  128. PrecacheScriptSound( "NPC_Bullsquid.Pain" );
  129. PrecacheScriptSound( "NPC_Bullsquid.Alert" );
  130. PrecacheScriptSound( "NPC_Bullsquid.Death" );
  131. PrecacheScriptSound( "NPC_Bullsquid.Attack1" );
  132. PrecacheScriptSound( "NPC_Bullsquid.Growl" );
  133. PrecacheScriptSound( "NPC_Bullsquid.TailWhip");
  134. BaseClass::Precache();
  135. }
  136. //-----------------------------------------------------------------------------
  137. // Purpose: Indicates this monster's place in the relationship table.
  138. // Output :
  139. //-----------------------------------------------------------------------------
  140. Class_T CNPC_Bullsquid::Classify( void )
  141. {
  142. return CLASS_BULLSQUID;
  143. }
  144. //=========================================================
  145. // IdleSound
  146. //=========================================================
  147. #define SQUID_ATTN_IDLE (float)1.5
  148. void CNPC_Bullsquid::IdleSound( void )
  149. {
  150. EmitSound( "NPC_Bullsquid.Idle" );
  151. }
  152. //=========================================================
  153. // PainSound
  154. //=========================================================
  155. void CNPC_Bullsquid::PainSound( const CTakeDamageInfo &info )
  156. {
  157. EmitSound( "NPC_Bullsquid.Pain" );
  158. }
  159. //=========================================================
  160. // AlertSound
  161. //=========================================================
  162. void CNPC_Bullsquid::AlertSound( void )
  163. {
  164. EmitSound( "NPC_Bullsquid.Alert" );
  165. }
  166. //=========================================================
  167. // DeathSound
  168. //=========================================================
  169. void CNPC_Bullsquid::DeathSound( const CTakeDamageInfo &info )
  170. {
  171. EmitSound( "NPC_Bullsquid.Death" );
  172. }
  173. //=========================================================
  174. // AttackSound
  175. //=========================================================
  176. void CNPC_Bullsquid::AttackSound( void )
  177. {
  178. EmitSound( "NPC_Bullsquid.Attack1" );
  179. }
  180. //=========================================================
  181. // GrowlSound
  182. //=========================================================
  183. void CNPC_Bullsquid::GrowlSound( void )
  184. {
  185. if (gpGlobals->curtime >= m_nextSquidSoundTime)
  186. {
  187. EmitSound( "NPC_Bullsquid.Growl" );
  188. m_nextSquidSoundTime = gpGlobals->curtime + random->RandomInt(1.5,3.0);
  189. }
  190. }
  191. //=========================================================
  192. // SetYawSpeed - allows each sequence to have a different
  193. // turn rate associated with it.
  194. //=========================================================
  195. float CNPC_Bullsquid::MaxYawSpeed( void )
  196. {
  197. float flYS = 0;
  198. switch ( GetActivity() )
  199. {
  200. case ACT_WALK: flYS = 90; break;
  201. case ACT_RUN: flYS = 90; break;
  202. case ACT_IDLE: flYS = 90; break;
  203. case ACT_RANGE_ATTACK1: flYS = 90; break;
  204. default:
  205. flYS = 90;
  206. break;
  207. }
  208. return flYS;
  209. }
  210. //=========================================================
  211. // HandleAnimEvent - catches the monster-specific messages
  212. // that occur when tagged animation frames are played.
  213. //=========================================================
  214. void CNPC_Bullsquid::HandleAnimEvent( animevent_t *pEvent )
  215. {
  216. switch( pEvent->event )
  217. {
  218. case BSQUID_AE_SPIT:
  219. {
  220. if ( GetEnemy() )
  221. {
  222. Vector vSpitPos;
  223. GetAttachment( "Mouth", vSpitPos );
  224. Vector vTarget = GetEnemy()->GetAbsOrigin();
  225. Vector vToss;
  226. CBaseEntity* pBlocker;
  227. float flGravity = SPIT_GRAVITY;
  228. ThrowLimit(vSpitPos, vTarget, flGravity, 3, Vector(0,0,0), Vector(0,0,0), GetEnemy(), &vToss, &pBlocker);
  229. CGrenadeSpit *pGrenade = (CGrenadeSpit*)CreateNoSpawn( "grenade_spit", vSpitPos, vec3_angle, this );
  230. //pGrenade->KeyValue( "velocity", vToss );
  231. pGrenade->Spawn( );
  232. pGrenade->SetThrower( this );
  233. pGrenade->SetOwnerEntity( this );
  234. pGrenade->SetSpitSize( 2 );
  235. pGrenade->SetAbsVelocity( vToss );
  236. // Tumble through the air
  237. pGrenade->SetLocalAngularVelocity(
  238. QAngle(
  239. random->RandomFloat( -100, -500 ),
  240. random->RandomFloat( -100, -500 ),
  241. random->RandomFloat( -100, -500 )
  242. )
  243. );
  244. AttackSound();
  245. CPVSFilter filter( vSpitPos );
  246. te->SpriteSpray( filter, 0.0,
  247. &vSpitPos, &vToss, m_nSquidSpitSprite, 5, 10, 15 );
  248. }
  249. }
  250. break;
  251. case BSQUID_AE_BITE:
  252. {
  253. // SOUND HERE!
  254. CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_bite.GetFloat(), DMG_SLASH );
  255. if ( pHurt )
  256. {
  257. Vector forward, up;
  258. AngleVectors( GetAbsAngles(), &forward, NULL, &up );
  259. pHurt->ApplyAbsVelocityImpulse( 100 * (up-forward) );
  260. pHurt->SetGroundEntity( NULL );
  261. }
  262. }
  263. break;
  264. case BSQUID_AE_WHIP_SND:
  265. {
  266. EmitSound( "NPC_Bullsquid.TailWhip" );
  267. break;
  268. }
  269. /*
  270. case BSQUID_AE_TAILWHIP:
  271. {
  272. CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_whip.GetFloat(), DMG_SLASH | DMG_ALWAYSGIB );
  273. if ( pHurt )
  274. {
  275. Vector right, up;
  276. AngleVectors( GetAbsAngles(), NULL, &right, &up );
  277. if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
  278. pHurt->ViewPunch( QAngle( 20, 0, -20 ) );
  279. pHurt->ApplyAbsVelocityImpulse( 100 * (up+2*right) );
  280. }
  281. }
  282. break;
  283. */
  284. case BSQUID_AE_BLINK:
  285. {
  286. // close eye.
  287. m_nSkin = 1;
  288. }
  289. break;
  290. case BSQUID_AE_HOP:
  291. {
  292. float flGravity = GetCurrentGravity();
  293. // throw the squid up into the air on this frame.
  294. if ( GetFlags() & FL_ONGROUND )
  295. {
  296. SetGroundEntity( NULL );
  297. }
  298. // jump 40 inches into the air
  299. Vector vecVel = GetAbsVelocity();
  300. vecVel.z += sqrt( flGravity * 2.0 * 40 );
  301. SetAbsVelocity( vecVel );
  302. }
  303. break;
  304. case BSQUID_AE_THROW:
  305. {
  306. // squid throws its prey IF the prey is a client.
  307. CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), 0, 0 );
  308. if ( pHurt )
  309. {
  310. pHurt->ViewPunch( QAngle(20,0,-20) );
  311. // screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels.
  312. UTIL_ScreenShake( pHurt->GetAbsOrigin(), 25.0, 1.5, 0.7, 2, SHAKE_START );
  313. // If the player, throw him around
  314. if ( pHurt->IsPlayer())
  315. {
  316. Vector forward, up;
  317. AngleVectors( GetLocalAngles(), &forward, NULL, &up );
  318. pHurt->ApplyAbsVelocityImpulse( forward * 300 + up * 300 );
  319. }
  320. // If not the player see if has bullsquid throw interatcion
  321. else
  322. {
  323. CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pHurt );
  324. if (pVictim)
  325. {
  326. if ( pVictim->DispatchInteraction( g_interactionBullsquidThrow, NULL, this ) )
  327. {
  328. Vector forward, up;
  329. AngleVectors( GetLocalAngles(), &forward, NULL, &up );
  330. pVictim->ApplyAbsVelocityImpulse( forward * 300 + up * 250 );
  331. }
  332. }
  333. }
  334. }
  335. }
  336. break;
  337. default:
  338. BaseClass::HandleAnimEvent( pEvent );
  339. }
  340. }
  341. int CNPC_Bullsquid::RangeAttack1Conditions( float flDot, float flDist )
  342. {
  343. if ( IsMoving() && flDist >= 512 )
  344. {
  345. // squid will far too far behind if he stops running to spit at this distance from the enemy.
  346. return ( COND_NONE );
  347. }
  348. if ( flDist > 85 && flDist <= 784 && flDot >= 0.5 && gpGlobals->curtime >= m_flNextSpitTime )
  349. {
  350. if ( GetEnemy() != NULL )
  351. {
  352. if ( fabs( GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z ) > 256 )
  353. {
  354. // don't try to spit at someone up really high or down really low.
  355. return( COND_NONE );
  356. }
  357. }
  358. if ( IsMoving() )
  359. {
  360. // don't spit again for a long time, resume chasing enemy.
  361. m_flNextSpitTime = gpGlobals->curtime + 5;
  362. }
  363. else
  364. {
  365. // not moving, so spit again pretty soon.
  366. m_flNextSpitTime = gpGlobals->curtime + 0.5;
  367. }
  368. return( COND_CAN_RANGE_ATTACK1 );
  369. }
  370. return( COND_NONE );
  371. }
  372. //=========================================================
  373. // MeleeAttack2Conditions - bullsquid is a big guy, so has a longer
  374. // melee range than most monsters. This is the tailwhip attack
  375. //=========================================================
  376. int CNPC_Bullsquid::MeleeAttack1Conditions( float flDot, float flDist )
  377. {
  378. if ( GetEnemy()->m_iHealth <= sk_bullsquid_dmg_whip.GetFloat() && flDist <= 85 && flDot >= 0.7 )
  379. {
  380. return ( COND_CAN_MELEE_ATTACK1 );
  381. }
  382. return( COND_NONE );
  383. }
  384. //=========================================================
  385. // MeleeAttack2Conditions - bullsquid is a big guy, so has a longer
  386. // melee range than most monsters. This is the bite attack.
  387. // this attack will not be performed if the tailwhip attack
  388. // is valid.
  389. //=========================================================
  390. int CNPC_Bullsquid::MeleeAttack2Conditions( float flDot, float flDist )
  391. {
  392. if ( flDist <= 85 && flDot >= 0.7 && !HasCondition( COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes
  393. return ( COND_CAN_MELEE_ATTACK2 );
  394. return( COND_NONE );
  395. }
  396. bool CNPC_Bullsquid::FValidateHintType( CAI_Hint *pHint )
  397. {
  398. if ( pHint->HintType() == HINT_HL1_WORLD_HUMAN_BLOOD )
  399. return true;
  400. DevMsg( "Couldn't validate hint type" );
  401. return false;
  402. }
  403. void CNPC_Bullsquid::RemoveIgnoredConditions( void )
  404. {
  405. if ( m_flHungryTime > gpGlobals->curtime )
  406. ClearCondition( COND_SQUID_SMELL_FOOD );
  407. if ( gpGlobals->curtime - m_flLastHurtTime <= 20 )
  408. {
  409. // haven't been hurt in 20 seconds, so let the squid care about stink.
  410. ClearCondition( COND_SMELL );
  411. }
  412. if ( GetEnemy() != NULL )
  413. {
  414. // ( Unless after a tasty headcrab, yumm ^_^ )
  415. if ( FClassnameIs( GetEnemy(), "monster_headcrab" ) )
  416. ClearCondition( COND_SMELL );
  417. }
  418. }
  419. Disposition_t CNPC_Bullsquid::IRelationType( CBaseEntity *pTarget )
  420. {
  421. if ( gpGlobals->curtime - m_flLastHurtTime < 5 && FClassnameIs( pTarget, "monster_headcrab" ) )
  422. {
  423. // if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab,
  424. // tell squid to disregard crab.
  425. return D_NU;
  426. }
  427. return BaseClass::IRelationType( pTarget );
  428. }
  429. //=========================================================
  430. // TakeDamage - overridden for bullsquid so we can keep track
  431. // of how much time has passed since it was last injured
  432. //=========================================================
  433. int CNPC_Bullsquid::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
  434. {
  435. #if 0 //Fix later.
  436. float flDist;
  437. Vector vecApex, vOffset;
  438. // if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy,
  439. // it will swerve. (whew).
  440. if ( GetEnemy() != NULL && IsMoving() && pevAttacker == GetEnemy() && gpGlobals->curtime - m_flLastHurtTime > 3 )
  441. {
  442. flDist = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).Length2D();
  443. if ( flDist > SQUID_SPRINT_DIST )
  444. {
  445. AI_Waypoint_t* pRoute = GetNavigator()->GetPath()->Route();
  446. if ( pRoute )
  447. {
  448. flDist = ( GetAbsOrigin() - pRoute[ pRoute->iNodeID ].vecLocation ).Length2D();// reusing flDist.
  449. if ( GetNavigator()->GetPath()->BuildTriangulationRoute( GetAbsOrigin(), pRoute[ pRoute->iNodeID ].vecLocation, flDist * 0.5, GetEnemy(), &vecApex, &vOffset, NAV_GROUND ) )
  450. {
  451. GetNavigator()->PrependWaypoint( vecApex, bits_WP_TO_DETOUR | bits_WP_DONT_SIMPLIFY );
  452. }
  453. }
  454. }
  455. }
  456. #endif
  457. if ( !FClassnameIs( inputInfo.GetAttacker(), "monster_headcrab" ) )
  458. {
  459. // don't forget about headcrabs if it was a headcrab that hurt the squid.
  460. m_flLastHurtTime = gpGlobals->curtime;
  461. }
  462. return BaseClass::OnTakeDamage_Alive( inputInfo );
  463. }
  464. //=========================================================
  465. // GetSoundInterests - returns a bit mask indicating which types
  466. // of sounds this monster regards. In the base class implementation,
  467. // monsters care about all sounds, but no scents.
  468. //=========================================================
  469. int CNPC_Bullsquid::GetSoundInterests( void )
  470. {
  471. return SOUND_WORLD |
  472. SOUND_COMBAT |
  473. SOUND_CARCASS |
  474. SOUND_MEAT |
  475. SOUND_GARBAGE |
  476. SOUND_PLAYER;
  477. }
  478. //=========================================================
  479. // OnListened - monsters dig through the active sound list for
  480. // any sounds that may interest them. (smells, too!)
  481. //=========================================================
  482. void CNPC_Bullsquid::OnListened( void )
  483. {
  484. AISoundIter_t iter;
  485. CSound *pCurrentSound;
  486. static int conditionsToClear[] =
  487. {
  488. COND_SQUID_SMELL_FOOD,
  489. };
  490. ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
  491. pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
  492. while ( pCurrentSound )
  493. {
  494. // the npc cares about this sound, and it's close enough to hear.
  495. int condition = COND_NONE;
  496. if ( !pCurrentSound->FIsSound() )
  497. {
  498. // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent
  499. if ( pCurrentSound->m_iType & ( SOUND_MEAT | SOUND_CARCASS ) )
  500. {
  501. // the detected scent is a food item
  502. condition = COND_SQUID_SMELL_FOOD;
  503. }
  504. }
  505. if ( condition != COND_NONE )
  506. SetCondition( condition );
  507. pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
  508. }
  509. BaseClass::OnListened();
  510. }
  511. //========================================================
  512. // RunAI - overridden for bullsquid because there are things
  513. // that need to be checked every think.
  514. //========================================================
  515. void CNPC_Bullsquid::RunAI( void )
  516. {
  517. // first, do base class stuff
  518. BaseClass::RunAI();
  519. if ( m_nSkin != 0 )
  520. {
  521. // close eye if it was open.
  522. m_nSkin = 0;
  523. }
  524. if ( random->RandomInt( 0,39 ) == 0 )
  525. {
  526. m_nSkin = 1;
  527. }
  528. if ( GetEnemy() != NULL && GetActivity() == ACT_RUN )
  529. {
  530. // chasing enemy. Sprint for last bit
  531. if ( (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length2D() < SQUID_SPRINT_DIST )
  532. {
  533. m_flPlaybackRate = 1.25;
  534. }
  535. }
  536. }
  537. //=========================================================
  538. // GetSchedule
  539. //=========================================================
  540. int CNPC_Bullsquid::SelectSchedule( void )
  541. {
  542. switch ( m_NPCState )
  543. {
  544. case NPC_STATE_ALERT:
  545. {
  546. if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
  547. {
  548. return SCHED_SQUID_HURTHOP;
  549. }
  550. if ( HasCondition( COND_SQUID_SMELL_FOOD ) )
  551. {
  552. CSound *pSound;
  553. pSound = GetBestScent();
  554. if ( pSound && (!FInViewCone( pSound->GetSoundOrigin() ) || !FVisible( pSound->GetSoundOrigin() )) )
  555. {
  556. // scent is behind or occluded
  557. return SCHED_SQUID_SNIFF_AND_EAT;
  558. }
  559. // food is right out in the open. Just go get it.
  560. return SCHED_SQUID_EAT;
  561. }
  562. if ( HasCondition( COND_SMELL ) )
  563. {
  564. // there's something stinky.
  565. CSound *pSound;
  566. pSound = GetBestScent();
  567. if ( pSound )
  568. return SCHED_SQUID_WALLOW;
  569. }
  570. break;
  571. }
  572. case NPC_STATE_COMBAT:
  573. {
  574. // dead enemy
  575. if ( HasCondition( COND_ENEMY_DEAD ) )
  576. {
  577. // call base class, all code to handle dead enemies is centralized there.
  578. return BaseClass::SelectSchedule();
  579. }
  580. if ( HasCondition( COND_NEW_ENEMY ) )
  581. {
  582. if ( m_fCanThreatDisplay && IRelationType( GetEnemy() ) == D_HT && FClassnameIs( GetEnemy(), "monster_headcrab" ) )
  583. {
  584. // this means squid sees a headcrab!
  585. m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime.
  586. return SCHED_SQUID_SEECRAB;
  587. }
  588. else
  589. {
  590. return SCHED_WAKE_ANGRY;
  591. }
  592. }
  593. if ( HasCondition( COND_SQUID_SMELL_FOOD ) )
  594. {
  595. CSound *pSound;
  596. pSound = GetBestScent();
  597. if ( pSound && (!FInViewCone( pSound->GetSoundOrigin() ) || !FVisible( pSound->GetSoundOrigin() )) )
  598. {
  599. // scent is behind or occluded
  600. return SCHED_SQUID_SNIFF_AND_EAT;
  601. }
  602. // food is right out in the open. Just go get it.
  603. return SCHED_SQUID_EAT;
  604. }
  605. if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  606. {
  607. return SCHED_RANGE_ATTACK1;
  608. }
  609. if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
  610. {
  611. return SCHED_MELEE_ATTACK1;
  612. }
  613. if ( HasCondition( COND_CAN_MELEE_ATTACK2 ) )
  614. {
  615. return SCHED_MELEE_ATTACK2;
  616. }
  617. return SCHED_CHASE_ENEMY;
  618. break;
  619. }
  620. }
  621. return BaseClass::SelectSchedule();
  622. }
  623. //=========================================================
  624. // FInViewCone - returns true is the passed vector is in
  625. // the caller's forward view cone. The dot product is performed
  626. // in 2d, making the view cone infinitely tall.
  627. //=========================================================
  628. bool CNPC_Bullsquid::FInViewCone( Vector pOrigin )
  629. {
  630. Vector los = ( pOrigin - GetAbsOrigin() );
  631. // do this in 2D
  632. los.z = 0;
  633. VectorNormalize( los );
  634. Vector facingDir = EyeDirection2D( );
  635. float flDot = DotProduct( los, facingDir );
  636. if ( flDot > m_flFieldOfView )
  637. return true;
  638. return false;
  639. }
  640. //=========================================================
  641. // Start task - selects the correct activity and performs
  642. // any necessary calculations to start the next task on the
  643. // schedule. OVERRIDDEN for bullsquid because it needs to
  644. // know explicitly when the last attempt to chase the enemy
  645. // failed, since that impacts its attack choices.
  646. //=========================================================
  647. void CNPC_Bullsquid::StartTask( const Task_t *pTask )
  648. {
  649. switch ( pTask->iTask )
  650. {
  651. case TASK_MELEE_ATTACK2:
  652. {
  653. if (GetEnemy())
  654. {
  655. GrowlSound();
  656. m_flLastAttackTime = gpGlobals->curtime;
  657. BaseClass::StartTask( pTask );
  658. }
  659. break;
  660. }
  661. case TASK_SQUID_HOPTURN:
  662. {
  663. SetActivity( ACT_HOP );
  664. if ( GetEnemy() )
  665. {
  666. Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
  667. VectorNormalize( vecFacing );
  668. GetMotor()->SetIdealYaw( vecFacing );
  669. }
  670. break;
  671. }
  672. case TASK_SQUID_EAT:
  673. {
  674. m_flHungryTime = gpGlobals->curtime + pTask->flTaskData;
  675. TaskComplete();
  676. break;
  677. }
  678. default:
  679. {
  680. BaseClass::StartTask( pTask );
  681. break;
  682. }
  683. }
  684. }
  685. //=========================================================
  686. // RunTask
  687. //=========================================================
  688. void CNPC_Bullsquid::RunTask( const Task_t *pTask )
  689. {
  690. switch ( pTask->iTask )
  691. {
  692. case TASK_SQUID_HOPTURN:
  693. {
  694. if ( GetEnemy() )
  695. {
  696. Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
  697. VectorNormalize( vecFacing );
  698. GetMotor()->SetIdealYaw( vecFacing );
  699. }
  700. if ( IsSequenceFinished() )
  701. {
  702. TaskComplete();
  703. }
  704. break;
  705. }
  706. default:
  707. {
  708. BaseClass::RunTask( pTask );
  709. break;
  710. }
  711. }
  712. }
  713. //=========================================================
  714. // GetIdealState - Overridden for Bullsquid to deal with
  715. // the feature that makes it lose interest in headcrabs for
  716. // a while if something injures it.
  717. //=========================================================
  718. NPC_STATE CNPC_Bullsquid::SelectIdealState( void )
  719. {
  720. // If no schedule conditions, the new ideal state is probably the reason we're in here.
  721. switch ( m_NPCState )
  722. {
  723. case NPC_STATE_COMBAT:
  724. {
  725. // COMBAT goes to ALERT upon death of enemy
  726. if ( GetEnemy() != NULL && ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) && FClassnameIs( GetEnemy(), "monster_headcrab" ) )
  727. {
  728. // if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while.
  729. SetEnemy( NULL );
  730. return NPC_STATE_ALERT;
  731. }
  732. break;
  733. }
  734. }
  735. return BaseClass::SelectIdealState();
  736. }
  737. //------------------------------------------------------------------------------
  738. //
  739. // Schedules
  740. //
  741. //------------------------------------------------------------------------------
  742. AI_BEGIN_CUSTOM_NPC( npc_bullsquid, CNPC_Bullsquid )
  743. DECLARE_TASK( TASK_SQUID_HOPTURN )
  744. DECLARE_TASK( TASK_SQUID_EAT )
  745. DECLARE_CONDITION( COND_SQUID_SMELL_FOOD )
  746. DECLARE_ACTIVITY( ACT_SQUID_EXCITED )
  747. DECLARE_ACTIVITY( ACT_SQUID_EAT )
  748. DECLARE_ACTIVITY( ACT_SQUID_DETECT_SCENT )
  749. DECLARE_ACTIVITY( ACT_SQUID_INSPECT_FLOOR )
  750. DECLARE_INTERACTION( g_interactionBullsquidThrow )
  751. //=========================================================
  752. // > SCHED_SQUID_HURTHOP
  753. //=========================================================
  754. DEFINE_SCHEDULE
  755. (
  756. SCHED_SQUID_HURTHOP,
  757. " Tasks"
  758. " TASK_STOP_MOVING 0"
  759. " TASK_SOUND_WAKE 0"
  760. " TASK_SQUID_HOPTURN 0"
  761. " TASK_FACE_ENEMY 0"
  762. " "
  763. " Interrupts"
  764. )
  765. //=========================================================
  766. // > SCHED_SQUID_SEECRAB
  767. //=========================================================
  768. DEFINE_SCHEDULE
  769. (
  770. SCHED_SQUID_SEECRAB,
  771. " Tasks"
  772. " TASK_STOP_MOVING 0"
  773. " TASK_SOUND_WAKE 0"
  774. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EXCITED"
  775. " TASK_FACE_ENEMY 0"
  776. " "
  777. " Interrupts"
  778. " COND_LIGHT_DAMAGE"
  779. " COND_HEAVY_DAMAGE"
  780. )
  781. //=========================================================
  782. // > SCHED_SQUID_EAT
  783. //=========================================================
  784. DEFINE_SCHEDULE
  785. (
  786. SCHED_SQUID_EAT,
  787. " Tasks"
  788. " TASK_STOP_MOVING 0"
  789. " TASK_SQUID_EAT 10"
  790. " TASK_STORE_LASTPOSITION 0"
  791. " TASK_GET_PATH_TO_BESTSCENT 0"
  792. " TASK_WALK_PATH 0"
  793. " TASK_WAIT_FOR_MOVEMENT 0"
  794. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
  795. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
  796. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
  797. " TASK_SQUID_EAT 50"
  798. " TASK_GET_PATH_TO_LASTPOSITION 0"
  799. " TASK_WALK_PATH 0"
  800. " TASK_WAIT_FOR_MOVEMENT 0"
  801. " TASK_CLEAR_LASTPOSITION 0"
  802. " "
  803. " Interrupts"
  804. " COND_LIGHT_DAMAGE"
  805. " COND_HEAVY_DAMAGE"
  806. " COND_NEW_ENEMY"
  807. " COND_SMELL"
  808. )
  809. //=========================================================
  810. // > SCHED_SQUID_SNIFF_AND_EAT
  811. //=========================================================
  812. DEFINE_SCHEDULE
  813. (
  814. SCHED_SQUID_SNIFF_AND_EAT,
  815. " Tasks"
  816. " TASK_STOP_MOVING 0"
  817. " TASK_SQUID_EAT 10"
  818. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_DETECT_SCENT"
  819. " TASK_STORE_LASTPOSITION 0"
  820. " TASK_GET_PATH_TO_BESTSCENT 0"
  821. " TASK_WALK_PATH 0"
  822. " TASK_WAIT_FOR_MOVEMENT 0"
  823. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
  824. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
  825. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
  826. " TASK_SQUID_EAT 50"
  827. " TASK_GET_PATH_TO_LASTPOSITION 0"
  828. " TASK_WALK_PATH 0"
  829. " TASK_WAIT_FOR_MOVEMENT 0"
  830. " TASK_CLEAR_LASTPOSITION 0"
  831. " "
  832. " Interrupts"
  833. " COND_LIGHT_DAMAGE"
  834. " COND_HEAVY_DAMAGE"
  835. " COND_NEW_ENEMY"
  836. " COND_SMELL"
  837. )
  838. //=========================================================
  839. // > SCHED_SQUID_WALLOW
  840. //=========================================================
  841. DEFINE_SCHEDULE
  842. (
  843. SCHED_SQUID_WALLOW,
  844. " Tasks"
  845. " TASK_STOP_MOVING 0"
  846. " TASK_SQUID_EAT 10"
  847. " TASK_STORE_LASTPOSITION 0"
  848. " TASK_GET_PATH_TO_BESTSCENT 0"
  849. " TASK_WALK_PATH 0"
  850. " TASK_WAIT_FOR_MOVEMENT 0"
  851. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_INSPECT_FLOOR"
  852. " TASK_SQUID_EAT 50"
  853. " TASK_GET_PATH_TO_LASTPOSITION 0"
  854. " TASK_WALK_PATH 0"
  855. " TASK_WAIT_FOR_MOVEMENT 0"
  856. " TASK_CLEAR_LASTPOSITION 0"
  857. " "
  858. " Interrupts"
  859. " COND_LIGHT_DAMAGE"
  860. " COND_HEAVY_DAMAGE"
  861. " COND_NEW_ENEMY"
  862. )
  863. AI_END_CUSTOM_NPC()