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.

1287 lines
31 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Cute hound like Alien.
  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_route.h"
  14. #include "ai_squad.h"
  15. #include "ai_squadslot.h"
  16. #include "ai_hint.h"
  17. #include "npcevent.h"
  18. #include "animation.h"
  19. #include "hl1_npc_houndeye.h"
  20. #include "gib.h"
  21. #include "soundent.h"
  22. #include "ndebugoverlay.h"
  23. #include "vstdlib/random.h"
  24. #include "engine/IEngineSound.h"
  25. #include "movevars_shared.h"
  26. // houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional
  27. // squad member increases the BASE damage by 110%, per the spec.
  28. #define HOUNDEYE_MAX_SQUAD_SIZE 4
  29. #define HOUNDEYE_SQUAD_BONUS (float)1.1
  30. #define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye
  31. #define HOUNDEYE_SOUND_STARTLE_VOLUME 128 // how loud a sound has to be to badly scare a sleeping houndeye
  32. #define HOUNDEYE_TOP_MASS 300.0f
  33. ConVar sk_houndeye_health ( "sk_houndeye_health", "20" );
  34. ConVar sk_houndeye_dmg_blast ( "sk_houndeye_dmg_blast", "15" );
  35. static int s_iSquadIndex = 0;
  36. //=========================================================
  37. // Monster's Anim Events Go Here
  38. //=========================================================
  39. #define HOUND_AE_WARN 1
  40. #define HOUND_AE_STARTATTACK 2
  41. #define HOUND_AE_THUMP 3
  42. #define HOUND_AE_ANGERSOUND1 4
  43. #define HOUND_AE_ANGERSOUND2 5
  44. #define HOUND_AE_HOPBACK 6
  45. #define HOUND_AE_CLOSE_EYE 7
  46. BEGIN_DATADESC( CNPC_Houndeye )
  47. DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
  48. DEFINE_FIELD( m_fAsleep, FIELD_BOOLEAN ),
  49. DEFINE_FIELD( m_fDontBlink, FIELD_BOOLEAN ),
  50. DEFINE_FIELD( m_vecPackCenter, FIELD_POSITION_VECTOR ),
  51. END_DATADESC()
  52. LINK_ENTITY_TO_CLASS( monster_houndeye, CNPC_Houndeye );
  53. //=========================================================
  54. // monster-specific tasks
  55. //=========================================================
  56. enum
  57. {
  58. TASK_HOUND_CLOSE_EYE = LAST_SHARED_TASK,
  59. TASK_HOUND_OPEN_EYE,
  60. TASK_HOUND_THREAT_DISPLAY,
  61. TASK_HOUND_FALL_ASLEEP,
  62. TASK_HOUND_WAKE_UP,
  63. TASK_HOUND_HOP_BACK,
  64. };
  65. //=========================================================
  66. // monster-specific schedule types
  67. //=========================================================
  68. enum
  69. {
  70. SCHED_HOUND_AGITATED = LAST_SHARED_SCHEDULE,
  71. SCHED_HOUND_HOP_RETREAT,
  72. SCHED_HOUND_YELL1,
  73. SCHED_HOUND_YELL2,
  74. SCHED_HOUND_RANGEATTACK,
  75. SCHED_HOUND_SLEEP,
  76. SCHED_HOUND_WAKE_LAZY,
  77. SCHED_HOUND_WAKE_URGENT,
  78. SCHED_HOUND_SPECIALATTACK,
  79. SCHED_HOUND_COMBAT_FAIL_PVS,
  80. SCHED_HOUND_COMBAT_FAIL_NOPVS,
  81. // SCHED_HOUND_FAIL,
  82. };
  83. enum HoundEyeSquadSlots
  84. {
  85. SQUAD_SLOTS_HOUND_ATTACK = LAST_SHARED_SQUADSLOT,
  86. };
  87. //=========================================================
  88. // Spawn
  89. //=========================================================
  90. void CNPC_Houndeye::Spawn()
  91. {
  92. Precache( );
  93. SetRenderColor( 255, 255, 255, 255 );
  94. SetModel( "models/houndeye.mdl" );
  95. SetHullType(HULL_TINY);
  96. SetHullSizeNormal();
  97. SetSolid( SOLID_BBOX );
  98. AddSolidFlags( FSOLID_NOT_STANDABLE );
  99. SetMoveType( MOVETYPE_STEP );
  100. m_bloodColor = BLOOD_COLOR_YELLOW;
  101. ClearEffects();
  102. m_iHealth = sk_houndeye_health.GetFloat();
  103. m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
  104. m_NPCState = NPC_STATE_NONE;
  105. m_fAsleep = FALSE; // everyone spawns awake
  106. m_fDontBlink = FALSE;
  107. CapabilitiesClear();
  108. CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
  109. CapabilitiesAdd( bits_CAP_SQUAD);
  110. NPCInit();
  111. }
  112. //=========================================================
  113. // Precache - precaches all resources this monster needs
  114. //=========================================================
  115. void CNPC_Houndeye::Precache()
  116. {
  117. PrecacheModel("models/houndeye.mdl");
  118. m_iSpriteTexture = PrecacheModel( "sprites/shockwave.vmt" );
  119. PrecacheScriptSound( "HoundEye.Idle" );
  120. PrecacheScriptSound( "HoundEye.Warn" );
  121. PrecacheScriptSound( "HoundEye.Hunt" );
  122. PrecacheScriptSound( "HoundEye.Alert" );
  123. PrecacheScriptSound( "HoundEye.Die" );
  124. PrecacheScriptSound( "HoundEye.Pain" );
  125. PrecacheScriptSound( "HoundEye.Anger1" );
  126. PrecacheScriptSound( "HoundEye.Anger2" );
  127. PrecacheScriptSound( "HoundEye.Sonic" );
  128. BaseClass::Precache();
  129. }
  130. void CNPC_Houndeye::Event_Killed( const CTakeDamageInfo &info )
  131. {
  132. // Close the eye to make death more obvious
  133. m_nSkin = 1;
  134. BaseClass::Event_Killed( info );
  135. }
  136. int CNPC_Houndeye::RangeAttack1Conditions ( float flDot, float flDist )
  137. {
  138. // I'm not allowed to attack if standing in another hound eye
  139. // (note houndeyes allowed to interpenetrate)
  140. trace_t tr;
  141. UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,0.1),
  142. GetHullMins(), GetHullMaxs(),
  143. MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  144. if (tr.startsolid)
  145. {
  146. CBaseEntity *pEntity = tr.m_pEnt;
  147. if (pEntity->Classify() == CLASS_ALIEN_MONSTER)
  148. {
  149. return( COND_NONE );
  150. }
  151. }
  152. // If I'm really close to my enemy allow me to attack if
  153. // I'm facing regardless of next attack time
  154. if (flDist < 100 && flDot >= 0.3)
  155. {
  156. return COND_CAN_RANGE_ATTACK1;
  157. }
  158. if ( gpGlobals->curtime < m_flNextAttack )
  159. {
  160. return( COND_NONE );
  161. }
  162. if (flDist > ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ))
  163. {
  164. return COND_TOO_FAR_TO_ATTACK;
  165. }
  166. if (flDot < 0.3)
  167. {
  168. return COND_NOT_FACING_ATTACK;
  169. }
  170. return COND_CAN_RANGE_ATTACK1;
  171. }
  172. //=========================================================
  173. // IdleSound
  174. //=========================================================
  175. void CNPC_Houndeye::IdleSound ( void )
  176. {
  177. CPASAttenuationFilter filter( this );
  178. EmitSound( filter, entindex(), "HoundEye.Idle" );
  179. }
  180. //=========================================================
  181. // IdleSound
  182. //=========================================================
  183. void CNPC_Houndeye::WarmUpSound ( void )
  184. {
  185. CPASAttenuationFilter filter( this );
  186. EmitSound( filter, entindex(),"HoundEye.Warn" );
  187. }
  188. //=========================================================
  189. // WarnSound
  190. //=========================================================
  191. void CNPC_Houndeye::WarnSound ( void )
  192. {
  193. CPASAttenuationFilter filter( this );
  194. EmitSound( filter, entindex(), "HoundEye.Hunt" );
  195. }
  196. //=========================================================
  197. // AlertSound
  198. //=========================================================
  199. void CNPC_Houndeye::AlertSound ( void )
  200. {
  201. if ( m_pSquad && !m_pSquad->IsLeader( this ) )
  202. return; // only leader makes ALERT sound.
  203. CPASAttenuationFilter filter( this );
  204. EmitSound( filter, entindex(), "HoundEye.Alert" );
  205. }
  206. //=========================================================
  207. // DeathSound
  208. //=========================================================
  209. void CNPC_Houndeye::DeathSound( const CTakeDamageInfo &info )
  210. {
  211. CPASAttenuationFilter filter( this );
  212. EmitSound( filter, entindex(), "HoundEye.Die" );
  213. }
  214. //=========================================================
  215. // PainSound
  216. //=========================================================
  217. void CNPC_Houndeye::PainSound ( const CTakeDamageInfo &info )
  218. {
  219. CPASAttenuationFilter filter( this );
  220. EmitSound( filter, entindex(), "HoundEye.Pain" );
  221. }
  222. //=========================================================
  223. // MaxYawSpeed - allows each sequence to have a different
  224. // turn rate associated with it.
  225. //=========================================================
  226. float CNPC_Houndeye::MaxYawSpeed ( void )
  227. {
  228. int flYS;
  229. flYS = 90;
  230. switch ( GetActivity() )
  231. {
  232. case ACT_CROUCHIDLE://sleeping!
  233. flYS = 0;
  234. break;
  235. case ACT_IDLE:
  236. flYS = 60;
  237. break;
  238. case ACT_WALK:
  239. flYS = 90;
  240. break;
  241. case ACT_RUN:
  242. flYS = 90;
  243. break;
  244. case ACT_TURN_LEFT:
  245. case ACT_TURN_RIGHT:
  246. flYS = 90;
  247. break;
  248. }
  249. return flYS;
  250. }
  251. //=========================================================
  252. // Classify - indicates this monster's place in the
  253. // relationship table.
  254. //=========================================================
  255. Class_T CNPC_Houndeye::Classify ( void )
  256. {
  257. return CLASS_ALIEN_MONSTER;
  258. }
  259. //=========================================================
  260. // HandleAnimEvent - catches the monster-specific messages
  261. // that occur when tagged animation frames are played.
  262. //=========================================================
  263. void CNPC_Houndeye::HandleAnimEvent( animevent_t *pEvent )
  264. {
  265. switch ( pEvent->event )
  266. {
  267. case HOUND_AE_WARN:
  268. // do stuff for this event.
  269. WarnSound();
  270. break;
  271. case HOUND_AE_STARTATTACK:
  272. WarmUpSound();
  273. break;
  274. case HOUND_AE_HOPBACK:
  275. {
  276. float flGravity = GetCurrentGravity();
  277. Vector v_forward;
  278. GetVectors( &v_forward, NULL, NULL );
  279. SetGroundEntity( NULL );
  280. Vector vecVel = v_forward * -200;
  281. vecVel.z += ( 0.6 * flGravity ) * 0.5;
  282. SetAbsVelocity( vecVel );
  283. break;
  284. }
  285. case HOUND_AE_THUMP:
  286. // emit the shockwaves
  287. SonicAttack();
  288. break;
  289. case HOUND_AE_ANGERSOUND1:
  290. {
  291. CPASAttenuationFilter filter( this );
  292. EmitSound( filter, entindex(), "HoundEye.Anger1" );
  293. }
  294. break;
  295. case HOUND_AE_ANGERSOUND2:
  296. {
  297. CPASAttenuationFilter filter2( this );
  298. EmitSound( filter2, entindex(), "HoundEye.Anger2" );
  299. }
  300. break;
  301. case HOUND_AE_CLOSE_EYE:
  302. if ( !m_fDontBlink )
  303. {
  304. m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
  305. }
  306. break;
  307. default:
  308. BaseClass::HandleAnimEvent( pEvent );
  309. break;
  310. }
  311. }
  312. //=========================================================
  313. // SonicAttack
  314. //=========================================================
  315. void CNPC_Houndeye::SonicAttack ( void )
  316. {
  317. float flAdjustedDamage;
  318. float flDist;
  319. CPASAttenuationFilter filter( this );
  320. EmitSound( filter, entindex(), "HoundEye.Sonic");
  321. CBroadcastRecipientFilter filter2;
  322. te->BeamRingPoint( filter2, 0.0,
  323. GetAbsOrigin(), //origin
  324. 16, //start radius
  325. HOUNDEYE_MAX_ATTACK_RADIUS,//end radius
  326. m_iSpriteTexture, //texture
  327. 0, //halo index
  328. 0, //start frame
  329. 0, //framerate
  330. 0.2, //life
  331. 24, //width
  332. 16, //spread
  333. 0, //amplitude
  334. WriteBeamColor().x, //r
  335. WriteBeamColor().y, //g
  336. WriteBeamColor().z, //b
  337. 192, //a
  338. 0 //speed
  339. );
  340. CBroadcastRecipientFilter filter3;
  341. te->BeamRingPoint( filter3, 0.0,
  342. GetAbsOrigin(), //origin
  343. 16, //start radius
  344. HOUNDEYE_MAX_ATTACK_RADIUS / 2, //end radius
  345. m_iSpriteTexture, //texture
  346. 0, //halo index
  347. 0, //start frame
  348. 0, //framerate
  349. 0.2, //life
  350. 24, //width
  351. 16, //spread
  352. 0, //amplitude
  353. WriteBeamColor().x, //r
  354. WriteBeamColor().y, //g
  355. WriteBeamColor().z, //b
  356. 192, //a
  357. 0 //speed
  358. );
  359. CBaseEntity *pEntity = NULL;
  360. // iterate on all entities in the vicinity.
  361. while ((pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL)
  362. {
  363. if ( pEntity->m_takedamage != DAMAGE_NO )
  364. {
  365. if ( !FClassnameIs(pEntity, "monster_houndeye") )
  366. {// houndeyes don't hurt other houndeyes with their attack
  367. // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise.
  368. // This means that you must get out of the houndeye's attack range entirely to avoid damage.
  369. // Calculate full damage first
  370. if ( m_pSquad && m_pSquad->NumMembers() > 1 )
  371. {
  372. // squad gets attack bonus.
  373. flAdjustedDamage = sk_houndeye_dmg_blast.GetFloat() + sk_houndeye_dmg_blast.GetFloat() * ( HOUNDEYE_SQUAD_BONUS * ( m_pSquad->NumMembers() - 1 ) );
  374. }
  375. else
  376. {
  377. // solo
  378. flAdjustedDamage =sk_houndeye_dmg_blast.GetFloat();
  379. }
  380. flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).Length();
  381. flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage;
  382. if ( !FVisible( pEntity ) )
  383. {
  384. if ( pEntity->IsPlayer() )
  385. {
  386. // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still
  387. // take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients
  388. // so that monsters in other parts of the level don't take the damage and get pissed.
  389. flAdjustedDamage *= 0.5;
  390. }
  391. else if ( !FClassnameIs( pEntity, "func_breakable" ) && !FClassnameIs( pEntity, "func_pushable" ) )
  392. {
  393. // do not hurt nonclients through walls, but allow damage to be done to breakables
  394. flAdjustedDamage = 0;
  395. }
  396. }
  397. //ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage );
  398. if (flAdjustedDamage > 0 )
  399. {
  400. CTakeDamageInfo info( this, this, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB );
  401. CalculateExplosiveDamageForce( &info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin() );
  402. pEntity->TakeDamage( info );
  403. if ( (pEntity->GetAbsOrigin() - GetAbsOrigin()).Length2D() <= HOUNDEYE_MAX_ATTACK_RADIUS )
  404. {
  405. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer()) )
  406. {
  407. IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
  408. if ( pPhysObject )
  409. {
  410. float flMass = pPhysObject->GetMass();
  411. if ( flMass <= HOUNDEYE_TOP_MASS )
  412. {
  413. // Increase the vertical lift of the force
  414. Vector vecForce = info.GetDamageForce();
  415. vecForce.z *= 2.0f;
  416. info.SetDamageForce( vecForce );
  417. pEntity->VPhysicsTakeDamage( info );
  418. }
  419. }
  420. }
  421. }
  422. }
  423. }
  424. }
  425. }
  426. }
  427. //=========================================================
  428. // WriteBeamColor - writes a color vector to the network
  429. // based on the size of the group.
  430. //=========================================================
  431. Vector CNPC_Houndeye::WriteBeamColor ( void )
  432. {
  433. BYTE bRed, bGreen, bBlue;
  434. if ( m_pSquad )
  435. {
  436. switch ( m_pSquad->NumMembers() )
  437. {
  438. case 1:
  439. // solo houndeye - weakest beam
  440. bRed = 188;
  441. bGreen = 220;
  442. bBlue = 255;
  443. break;
  444. case 2:
  445. bRed = 101;
  446. bGreen = 133;
  447. bBlue = 221;
  448. break;
  449. case 3:
  450. bRed = 67;
  451. bGreen = 85;
  452. bBlue = 255;
  453. break;
  454. case 4:
  455. bRed = 62;
  456. bGreen = 33;
  457. bBlue = 211;
  458. break;
  459. default:
  460. Msg ( "Unsupported Houndeye SquadSize!\n" );
  461. bRed = 188;
  462. bGreen = 220;
  463. bBlue = 255;
  464. break;
  465. }
  466. }
  467. else
  468. {
  469. // solo houndeye - weakest beam
  470. bRed = 188;
  471. bGreen = 220;
  472. bBlue = 255;
  473. }
  474. return Vector ( bRed, bGreen, bBlue );
  475. }
  476. bool CNPC_Houndeye::ShouldGoToIdleState( void )
  477. {
  478. if ( m_pSquad )
  479. {
  480. AISquadIter_t iter;
  481. for (CAI_BaseNPC *pMember = m_pSquad->GetFirstMember( &iter ); pMember; pMember = m_pSquad->GetNextMember( &iter ) )
  482. {
  483. if ( pMember != this && pMember->GetHintNode() && pMember->GetHintNode()->HintType() != NO_NODE )
  484. return true;
  485. }
  486. return true;
  487. }
  488. return true;
  489. }
  490. bool CNPC_Houndeye::FValidateHintType ( CAI_Hint *pHint )
  491. {
  492. switch( pHint->HintType() )
  493. {
  494. case HINT_HL1_WORLD_MACHINERY:
  495. return true;
  496. break;
  497. case HINT_HL1_WORLD_BLINKING_LIGHT:
  498. return true;
  499. break;
  500. case HINT_HL1_WORLD_HUMAN_BLOOD:
  501. return true;
  502. break;
  503. case HINT_HL1_WORLD_ALIEN_BLOOD:
  504. return true;
  505. break;
  506. }
  507. Msg ( "Couldn't validate hint type" );
  508. return false;
  509. }
  510. //=========================================================
  511. // SetActivity
  512. //=========================================================
  513. void CNPC_Houndeye::SetActivity ( Activity NewActivity )
  514. {
  515. int iSequence;
  516. if ( NewActivity == GetActivity() )
  517. return;
  518. if ( m_NPCState == NPC_STATE_COMBAT && NewActivity == ACT_IDLE && random->RandomInt( 0, 1 ) )
  519. {
  520. // play pissed idle.
  521. iSequence = LookupSequence( "madidle" );
  522. SetActivity( NewActivity ); // Go ahead and set this so it doesn't keep trying when the anim is not present
  523. // In case someone calls this with something other than the ideal activity
  524. SetIdealActivity( GetActivity() );
  525. // Set to the desired anim, or default anim if the desired is not present
  526. if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  527. {
  528. SetSequence( iSequence ); // Set to the reset anim (if it's there)
  529. SetCycle( 0 ); // FIX: frame counter shouldn't be reset when its the same activity as before
  530. ResetSequenceInfo();
  531. }
  532. }
  533. else
  534. {
  535. BaseClass::SetActivity ( NewActivity );
  536. }
  537. }
  538. //=========================================================
  539. // start task
  540. //=========================================================
  541. void CNPC_Houndeye::StartTask ( const Task_t *pTask )
  542. {
  543. switch ( pTask->iTask )
  544. {
  545. case TASK_HOUND_FALL_ASLEEP:
  546. {
  547. m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!)
  548. TaskComplete();
  549. break;
  550. }
  551. case TASK_HOUND_WAKE_UP:
  552. {
  553. m_fAsleep = FALSE; // signal that hound is standing again
  554. TaskComplete();
  555. break;
  556. }
  557. case TASK_HOUND_OPEN_EYE:
  558. {
  559. m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye
  560. TaskComplete();
  561. break;
  562. }
  563. case TASK_HOUND_CLOSE_EYE:
  564. {
  565. m_nSkin = 0;
  566. m_fDontBlink = TRUE; // tell blink code to leave the eye alone.
  567. break;
  568. }
  569. case TASK_HOUND_THREAT_DISPLAY:
  570. {
  571. SetIdealActivity( ACT_IDLE_ANGRY );
  572. break;
  573. }
  574. case TASK_HOUND_HOP_BACK:
  575. {
  576. SetIdealActivity( ACT_LEAP );
  577. break;
  578. }
  579. case TASK_RANGE_ATTACK1:
  580. {
  581. SetIdealActivity( ACT_RANGE_ATTACK1 );
  582. break;
  583. }
  584. case TASK_SPECIAL_ATTACK1:
  585. {
  586. SetIdealActivity( ACT_SPECIAL_ATTACK1 );
  587. break;
  588. }
  589. default:
  590. {
  591. BaseClass::StartTask(pTask);
  592. break;
  593. }
  594. }
  595. }
  596. //=========================================================
  597. // RunTask
  598. //=========================================================
  599. void CNPC_Houndeye::RunTask ( const Task_t *pTask )
  600. {
  601. switch ( pTask->iTask )
  602. {
  603. case TASK_HOUND_THREAT_DISPLAY:
  604. {
  605. if ( GetEnemy() )
  606. {
  607. float idealYaw;
  608. idealYaw = UTIL_VecToYaw( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
  609. GetMotor()->SetIdealYawAndUpdate( idealYaw );
  610. }
  611. if ( IsSequenceFinished() )
  612. {
  613. TaskComplete();
  614. }
  615. break;
  616. }
  617. case TASK_HOUND_CLOSE_EYE:
  618. {
  619. if ( m_nSkin < HOUNDEYE_EYE_FRAMES - 1 )
  620. m_nSkin++;
  621. break;
  622. }
  623. case TASK_HOUND_HOP_BACK:
  624. {
  625. if ( IsSequenceFinished() )
  626. {
  627. TaskComplete();
  628. }
  629. break;
  630. }
  631. case TASK_SPECIAL_ATTACK1:
  632. {
  633. m_nSkin = random->RandomInt(0, HOUNDEYE_EYE_FRAMES - 1);
  634. float idealYaw;
  635. idealYaw = UTIL_VecToYaw( GetNavigator()->GetGoalPos() );
  636. GetMotor()->SetIdealYawAndUpdate( idealYaw );
  637. float life;
  638. life = ((255 - GetCycle()) / ( m_flPlaybackRate * m_flPlaybackRate));
  639. if (life < 0.1)
  640. {
  641. life = 0.1;
  642. }
  643. /* MessageBegin( MSG_PAS, SVC_TEMPENTITY, GetAbsOrigin() );
  644. WRITE_BYTE( TE_IMPLOSION);
  645. WRITE_COORD( GetAbsOrigin().x);
  646. WRITE_COORD( GetAbsOrigin().y);
  647. WRITE_COORD( GetAbsOrigin().z + 16);
  648. WRITE_BYTE( 50 * life + 100); // radius
  649. WRITE_BYTE( pev->frame / 25.0 ); // count
  650. WRITE_BYTE( life * 10 ); // life
  651. MessageEnd();*/
  652. if ( IsSequenceFinished() )
  653. {
  654. SonicAttack();
  655. TaskComplete();
  656. }
  657. break;
  658. }
  659. default:
  660. {
  661. BaseClass::RunTask(pTask);
  662. break;
  663. }
  664. }
  665. }
  666. //=========================================================
  667. // PrescheduleThink
  668. //=========================================================
  669. void CNPC_Houndeye::PrescheduleThink ( void )
  670. {
  671. BaseClass::PrescheduleThink();
  672. // if the hound is mad and is running, make hunt noises.
  673. if ( m_NPCState == NPC_STATE_COMBAT && GetActivity() == ACT_RUN && random->RandomFloat( 0, 1 ) < 0.2 )
  674. {
  675. WarnSound();
  676. }
  677. // at random, initiate a blink if not already blinking or sleeping
  678. if ( !m_fDontBlink )
  679. {
  680. if ( ( m_nSkin == 0 ) && random->RandomInt(0,0x7F) == 0 )
  681. {// start blinking!
  682. m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
  683. }
  684. else if ( m_nSkin != 0 )
  685. {// already blinking
  686. m_nSkin--;
  687. }
  688. }
  689. // if you are the leader, average the origins of each pack member to get an approximate center.
  690. if ( m_pSquad && m_pSquad->IsLeader( this ) )
  691. {
  692. int iSquadCount = 0;
  693. AISquadIter_t iter;
  694. for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
  695. {
  696. iSquadCount++;
  697. m_vecPackCenter = m_vecPackCenter + pSquadMember->GetAbsOrigin();
  698. }
  699. m_vecPackCenter = m_vecPackCenter / iSquadCount;
  700. }
  701. }
  702. //=========================================================
  703. // GetScheduleOfType
  704. //=========================================================
  705. int CNPC_Houndeye::TranslateSchedule( int scheduleType )
  706. {
  707. if ( m_fAsleep )
  708. {
  709. // if the hound is sleeping, must wake and stand!
  710. if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_THUMPER ) || HasCondition( COND_HEAR_COMBAT ) ||
  711. HasCondition( COND_HEAR_WORLD ) || HasCondition( COND_HEAR_PLAYER ) || HasCondition( COND_HEAR_BULLET_IMPACT ) )
  712. {
  713. CSound *pWakeSound;
  714. pWakeSound = GetBestSound();
  715. ASSERT( pWakeSound != NULL );
  716. if ( pWakeSound )
  717. {
  718. GetMotor()->SetIdealYawToTarget ( pWakeSound->GetSoundOrigin() );
  719. if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME )
  720. {
  721. // awakened by a loud sound
  722. return SCHED_HOUND_WAKE_URGENT;
  723. }
  724. }
  725. // sound was not loud enough to scare the bejesus out of houndeye
  726. return SCHED_HOUND_WAKE_LAZY;
  727. }
  728. else if ( HasCondition( COND_NEW_ENEMY ) )
  729. {
  730. // get up fast, to fight.
  731. return SCHED_HOUND_WAKE_URGENT;
  732. }
  733. else
  734. {
  735. // hound is waking up on its own
  736. return SCHED_HOUND_WAKE_LAZY;
  737. }
  738. }
  739. switch ( scheduleType )
  740. {
  741. case SCHED_IDLE_STAND:
  742. {
  743. // we may want to sleep instead of stand!
  744. if ( m_pSquad && !m_pSquad->IsLeader( this ) && !m_fAsleep && random->RandomInt( 0,29 ) < 1 )
  745. {
  746. return SCHED_HOUND_SLEEP;
  747. }
  748. else
  749. {
  750. return BaseClass::TranslateSchedule( scheduleType );
  751. }
  752. }
  753. case SCHED_RANGE_ATTACK1:
  754. return SCHED_HOUND_RANGEATTACK;
  755. case SCHED_SPECIAL_ATTACK1:
  756. return SCHED_HOUND_SPECIALATTACK;
  757. case SCHED_FAIL:
  758. {
  759. if ( m_NPCState == NPC_STATE_COMBAT )
  760. {
  761. if ( !FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
  762. {
  763. // client in PVS
  764. return SCHED_HOUND_COMBAT_FAIL_PVS;
  765. }
  766. else
  767. {
  768. // client has taken off!
  769. return SCHED_HOUND_COMBAT_FAIL_NOPVS;
  770. }
  771. }
  772. else
  773. {
  774. return BaseClass::TranslateSchedule ( scheduleType );
  775. }
  776. }
  777. default:
  778. {
  779. return BaseClass::TranslateSchedule ( scheduleType );
  780. }
  781. }
  782. }
  783. int CNPC_Houndeye::SelectSchedule( void )
  784. {
  785. switch ( m_NPCState )
  786. {
  787. case NPC_STATE_COMBAT:
  788. {
  789. // dead enemy
  790. if ( HasCondition( COND_ENEMY_DEAD ) )
  791. {
  792. // call base class, all code to handle dead enemies is centralized there.
  793. return BaseClass::SelectSchedule();
  794. }
  795. if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
  796. {
  797. if ( random->RandomFloat( 0 , 1 ) <= 0.4 )
  798. {
  799. trace_t trace;
  800. Vector v_forward;
  801. GetVectors( &v_forward, NULL, NULL );
  802. UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + v_forward * -128, MASK_SOLID, &trace );
  803. if ( trace.fraction == 1.0 )
  804. {
  805. // it's clear behind, so the hound will jump
  806. return SCHED_HOUND_HOP_RETREAT;
  807. }
  808. }
  809. return SCHED_TAKE_COVER_FROM_ENEMY;
  810. }
  811. if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  812. {
  813. // don't constraint attacks based on squad slots, just let em all go for it
  814. // if ( OccupyStrategySlot ( SQUAD_SLOTS_HOUND_ATTACK ) )
  815. return SCHED_RANGE_ATTACK1;
  816. }
  817. break;
  818. }
  819. }
  820. return BaseClass::SelectSchedule();
  821. }
  822. //=========================================================
  823. // FLSoundVolume - subtracts the volume of the given sound
  824. // from the distance the sound source is from the caller,
  825. // and returns that value, which is considered to be the 'local'
  826. // volume of the sound.
  827. //=========================================================
  828. float CNPC_Houndeye::FLSoundVolume( CSound *pSound )
  829. {
  830. return ( pSound->Volume() - ( ( pSound->GetSoundOrigin() - GetAbsOrigin() ).Length() ) );
  831. }
  832. //=========================================================
  833. //
  834. // SquadRecruit(), get some monsters of my classification and
  835. // link them as a group. returns the group size
  836. //
  837. //=========================================================
  838. int CNPC_Houndeye::SquadRecruit( int searchRadius, int maxMembers )
  839. {
  840. int squadCount;
  841. int iMyClass = Classify();// cache this monster's class
  842. if ( maxMembers < 2 )
  843. return 0;
  844. // I am my own leader
  845. squadCount = 1;
  846. CBaseEntity *pEntity = NULL;
  847. if ( m_SquadName != NULL_STRING )
  848. {
  849. // I have a netname, so unconditionally recruit everyone else with that name.
  850. pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );
  851. while ( pEntity && squadCount < maxMembers )
  852. {
  853. CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();
  854. if ( pRecruit )
  855. {
  856. if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass && pRecruit != this )
  857. {
  858. // minimum protection here against user error.in worldcraft.
  859. if ( pRecruit->m_SquadName != NULL_STRING && FStrEq( STRING( m_SquadName ), STRING( pRecruit->m_SquadName ) ) )
  860. {
  861. pRecruit->InitSquad();
  862. squadCount++;
  863. }
  864. }
  865. }
  866. pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );
  867. }
  868. return squadCount;
  869. }
  870. else
  871. {
  872. char szSquadName[64];
  873. Q_snprintf( szSquadName, sizeof( szSquadName ), "squad%d\n", s_iSquadIndex );
  874. m_SquadName = MAKE_STRING( szSquadName );
  875. while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius ) ) != NULL && squadCount < maxMembers )
  876. {
  877. if ( !FClassnameIs ( pEntity, "monster_houndeye" ) )
  878. continue;
  879. CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();
  880. if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_hCine )
  881. {
  882. // Can we recruit this guy?
  883. if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass &&
  884. ( (iMyClass != CLASS_ALIEN_MONSTER) || FClassnameIs( this, pRecruit->GetClassname() ) ) &&
  885. !pRecruit->m_SquadName )
  886. {
  887. trace_t tr;
  888. UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), pRecruit->GetAbsOrigin() + GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pRecruit, COLLISION_GROUP_NONE, &tr );// try to hit recruit with a traceline.
  889. if ( tr.fraction == 1.0 )
  890. {
  891. //We're ready to recruit people, so start a squad if I don't have one.
  892. if ( !m_pSquad )
  893. {
  894. InitSquad();
  895. }
  896. pRecruit->m_SquadName = m_SquadName;
  897. pRecruit->CapabilitiesAdd ( bits_CAP_SQUAD );
  898. pRecruit->InitSquad();
  899. squadCount++;
  900. }
  901. }
  902. }
  903. }
  904. if ( squadCount > 1 )
  905. {
  906. s_iSquadIndex++;
  907. }
  908. }
  909. return squadCount;
  910. }
  911. void CNPC_Houndeye::StartNPC ( void )
  912. {
  913. if ( !m_pSquad )
  914. {
  915. if ( m_SquadName != NULL_STRING )
  916. {
  917. BaseClass::StartNPC();
  918. return;
  919. }
  920. else
  921. {
  922. int iSquadSize = SquadRecruit( 1024, 4 );
  923. if ( iSquadSize )
  924. {
  925. Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
  926. }
  927. }
  928. }
  929. BaseClass::StartNPC();
  930. }
  931. //------------------------------------------------------------------------------
  932. //
  933. // Schedules
  934. //
  935. //------------------------------------------------------------------------------
  936. AI_BEGIN_CUSTOM_NPC( monster_houndeye, CNPC_Houndeye )
  937. DECLARE_TASK ( TASK_HOUND_CLOSE_EYE )
  938. DECLARE_TASK ( TASK_HOUND_OPEN_EYE )
  939. DECLARE_TASK ( TASK_HOUND_THREAT_DISPLAY )
  940. DECLARE_TASK ( TASK_HOUND_FALL_ASLEEP )
  941. DECLARE_TASK ( TASK_HOUND_WAKE_UP )
  942. DECLARE_TASK ( TASK_HOUND_HOP_BACK )
  943. //=========================================================
  944. // > SCHED_HOUND_RANGEATTACK
  945. //=========================================================
  946. DEFINE_SCHEDULE
  947. (
  948. SCHED_HOUND_RANGEATTACK,
  949. " Tasks"
  950. " TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_YELL1"
  951. " "
  952. " Interrupts"
  953. " COND_LIGHT_DAMAGE"
  954. " COND_HEAVY_DAMAGE"
  955. )
  956. //=========================================================
  957. // > SCHED_HOUND_AGITATED
  958. //=========================================================
  959. DEFINE_SCHEDULE
  960. (
  961. SCHED_HOUND_AGITATED,
  962. " Tasks"
  963. " TASK_STOP_MOVING 0"
  964. " TASK_HOUND_THREAT_DISPLAY 0"
  965. " "
  966. " Interrupts"
  967. " COND_NEW_ENEMY"
  968. " COND_HEAVY_DAMAGE"
  969. )
  970. //=========================================================
  971. // > SCHED_HOUND_HOP_RETREAT
  972. //=========================================================
  973. DEFINE_SCHEDULE
  974. (
  975. SCHED_HOUND_HOP_RETREAT,
  976. " Tasks"
  977. " TASK_STOP_MOVING 0"
  978. " TASK_HOUND_HOP_BACK 0"
  979. " TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
  980. " "
  981. " Interrupts"
  982. )
  983. //=========================================================
  984. // > SCHED_HOUND_YELL1
  985. //=========================================================
  986. DEFINE_SCHEDULE
  987. (
  988. SCHED_HOUND_YELL1,
  989. " Tasks"
  990. " TASK_STOP_MOVING 0"
  991. " TASK_FACE_IDEAL 0"
  992. " TASK_RANGE_ATTACK1 0"
  993. " TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_AGITATED"
  994. " "
  995. " Interrupts"
  996. )
  997. //=========================================================
  998. // > SCHED_HOUND_YELL2
  999. //=========================================================
  1000. DEFINE_SCHEDULE
  1001. (
  1002. SCHED_HOUND_YELL2,
  1003. " Tasks"
  1004. " TASK_STOP_MOVING 0"
  1005. " TASK_FACE_IDEAL 0"
  1006. " TASK_RANGE_ATTACK1 0"
  1007. " "
  1008. " Interrupts"
  1009. )
  1010. //=========================================================
  1011. // > SCHED_HOUND_SLEEP
  1012. //=========================================================
  1013. DEFINE_SCHEDULE
  1014. (
  1015. SCHED_HOUND_SLEEP,
  1016. " Tasks"
  1017. " TASK_STOP_MOVING 0"
  1018. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1019. " TASK_WAIT_RANDOM 5"
  1020. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH"
  1021. " TASK_SET_ACTIVITY ACTIVITY:ACT_CROUCHIDLE"
  1022. " TASK_HOUND_FALL_ASLEEP 0"
  1023. " TASK_WAIT_RANDOM 25"
  1024. " TASK_HOUND_CLOSE_EYE 0"
  1025. " "
  1026. " Interrupts"
  1027. " COND_LIGHT_DAMAGE"
  1028. " COND_HEAVY_DAMAGE"
  1029. " COND_NEW_ENEMY"
  1030. " COND_HEAR_COMBAT"
  1031. " COND_HEAR_DANGER"
  1032. " COND_HEAR_PLAYER"
  1033. " COND_HEAR_WORLD"
  1034. )
  1035. //=========================================================
  1036. // > SCHED_HOUND_WAKE_LAZY
  1037. //=========================================================
  1038. DEFINE_SCHEDULE
  1039. (
  1040. SCHED_HOUND_WAKE_LAZY,
  1041. " Tasks"
  1042. " TASK_STOP_MOVING 0"
  1043. " TASK_HOUND_OPEN_EYE 0"
  1044. " TASK_WAIT_RANDOM 2.5"
  1045. " TASK_PLAY_SEQUENCE ACT_STAND"
  1046. " TASK_HOUND_WAKE_UP 0"
  1047. " "
  1048. " Interrupts"
  1049. )
  1050. //=========================================================
  1051. // > SCHED_HOUND_WAKE_URGENT
  1052. //=========================================================
  1053. DEFINE_SCHEDULE
  1054. (
  1055. SCHED_HOUND_WAKE_URGENT,
  1056. " Tasks"
  1057. " TASK_HOUND_OPEN_EYE 0"
  1058. " TASK_PLAY_SEQUENCE ACT_HOP"
  1059. " TASK_FACE_IDEAL 0"
  1060. " TASK_HOUND_WAKE_UP 0"
  1061. " "
  1062. " Interrupts"
  1063. )
  1064. //=========================================================
  1065. // > SCHED_HOUND_SPECIALATTACK
  1066. //=========================================================
  1067. DEFINE_SCHEDULE
  1068. (
  1069. SCHED_HOUND_SPECIALATTACK,
  1070. " Tasks"
  1071. " TASK_STOP_MOVING 0"
  1072. " TASK_FACE_IDEAL 0"
  1073. " TASK_SPECIAL_ATTACK1 0"
  1074. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_IDLE_ANGRY"
  1075. " "
  1076. " Interrupts"
  1077. " "
  1078. " COND_NEW_ENEMY"
  1079. " COND_LIGHT_DAMAGE"
  1080. " COND_HEAVY_DAMAGE"
  1081. " COND_ENEMY_OCCLUDED"
  1082. )
  1083. //=========================================================
  1084. // > SCHED_HOUND_COMBAT_FAIL_PVS
  1085. //=========================================================
  1086. DEFINE_SCHEDULE
  1087. (
  1088. SCHED_HOUND_COMBAT_FAIL_PVS,
  1089. " Tasks"
  1090. " TASK_STOP_MOVING 0"
  1091. " TASK_HOUND_THREAT_DISPLAY 0"
  1092. " TASK_WAIT_FACE_ENEMY 1"
  1093. " "
  1094. " Interrupts"
  1095. " "
  1096. " COND_NEW_ENEMY"
  1097. " COND_LIGHT_DAMAGE"
  1098. " COND_HEAVY_DAMAGE"
  1099. )
  1100. //=========================================================
  1101. // > SCHED_HOUND_COMBAT_FAIL_NOPVS
  1102. //=========================================================
  1103. DEFINE_SCHEDULE
  1104. (
  1105. SCHED_HOUND_COMBAT_FAIL_NOPVS,
  1106. " Tasks"
  1107. " TASK_STOP_MOVING 0"
  1108. " TASK_HOUND_THREAT_DISPLAY 0"
  1109. " TASK_WAIT_FACE_ENEMY 1"
  1110. " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
  1111. " TASK_WAIT_PVS 0"
  1112. " "
  1113. " Interrupts"
  1114. " COND_NEW_ENEMY"
  1115. " COND_LIGHT_DAMAGE"
  1116. " COND_HEAVY_DAMAGE"
  1117. )
  1118. AI_END_CUSTOM_NPC()