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.

1101 lines
28 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ammodef.h"
  8. #include "AI_Hint.h"
  9. #include "AI_Navigator.h"
  10. #include "npc_Assassin.h"
  11. #include "game.h"
  12. #include "npcevent.h"
  13. #include "engine/IEngineSound.h"
  14. #include "ai_squad.h"
  15. #include "AI_SquadSlot.h"
  16. #include "ai_moveprobe.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. ConVar sk_assassin_health( "sk_assassin_health","150");
  20. ConVar g_debug_assassin( "g_debug_assassin", "0" );
  21. //=========================================================
  22. // Anim Events
  23. //=========================================================
  24. #define ASSASSIN_AE_FIRE_PISTOL_RIGHT 1
  25. #define ASSASSIN_AE_FIRE_PISTOL_LEFT 2
  26. #define ASSASSIN_AE_KICK_HIT 3
  27. int AE_ASSASIN_FIRE_PISTOL_RIGHT;
  28. int AE_ASSASIN_FIRE_PISTOL_LEFT;
  29. int AE_ASSASIN_KICK_HIT;
  30. //=========================================================
  31. // Assassin activities
  32. //=========================================================
  33. int ACT_ASSASSIN_FLIP_LEFT;
  34. int ACT_ASSASSIN_FLIP_RIGHT;
  35. int ACT_ASSASSIN_FLIP_BACK;
  36. int ACT_ASSASSIN_FLIP_FORWARD;
  37. int ACT_ASSASSIN_PERCH;
  38. //=========================================================
  39. // Flip types
  40. //=========================================================
  41. enum
  42. {
  43. FLIP_LEFT,
  44. FLIP_RIGHT,
  45. FLIP_FORWARD,
  46. FLIP_BACKWARD,
  47. NUM_FLIP_TYPES,
  48. };
  49. //=========================================================
  50. // Private conditions
  51. //=========================================================
  52. enum Assassin_Conds
  53. {
  54. COND_ASSASSIN_ENEMY_TARGETTING_ME = LAST_SHARED_CONDITION,
  55. };
  56. //=========================================================
  57. // Assassin schedules
  58. //=========================================================
  59. enum
  60. {
  61. SCHED_ASSASSIN_FIND_VANTAGE_POINT = LAST_SHARED_SCHEDULE,
  62. SCHED_ASSASSIN_EVADE,
  63. SCHED_ASSASSIN_STALK_ENEMY,
  64. SCHED_ASSASSIN_LUNGE,
  65. };
  66. //=========================================================
  67. // Assassin tasks
  68. //=========================================================
  69. enum
  70. {
  71. TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT = LAST_SHARED_TASK,
  72. TASK_ASSASSIN_EVADE,
  73. TASK_ASSASSIN_SET_EYE_STATE,
  74. TASK_ASSASSIN_LUNGE,
  75. };
  76. //-----------------------------------------------------------------------------
  77. // Purpose: Class Constructor
  78. //-----------------------------------------------------------------------------
  79. CNPC_Assassin::CNPC_Assassin( void )
  80. {
  81. }
  82. //-----------------------------------------------------------------------------
  83. LINK_ENTITY_TO_CLASS( npc_assassin, CNPC_Assassin );
  84. #if 0
  85. //---------------------------------------------------------
  86. // Custom Client entity
  87. //---------------------------------------------------------
  88. IMPLEMENT_SERVERCLASS_ST(CNPC_Assassin, DT_NPC_Assassin)
  89. END_SEND_TABLE()
  90. #endif
  91. //---------------------------------------------------------
  92. // Save/Restore
  93. //---------------------------------------------------------
  94. BEGIN_DATADESC( CNPC_Assassin )
  95. DEFINE_FIELD( m_nNumFlips, FIELD_INTEGER ),
  96. DEFINE_FIELD( m_nLastFlipType, FIELD_INTEGER ),
  97. DEFINE_FIELD( m_flNextFlipTime, FIELD_TIME ),
  98. DEFINE_FIELD( m_flNextLungeTime, FIELD_TIME ),
  99. DEFINE_FIELD( m_flNextShotTime, FIELD_TIME ),
  100. DEFINE_FIELD( m_bEvade, FIELD_BOOLEAN ),
  101. DEFINE_FIELD( m_bAggressive, FIELD_BOOLEAN ),
  102. DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ),
  103. DEFINE_FIELD( m_pEyeSprite, FIELD_CLASSPTR ),
  104. DEFINE_FIELD( m_pEyeTrail, FIELD_CLASSPTR ),
  105. END_DATADESC()
  106. //-----------------------------------------------------------------------------
  107. // Purpose:
  108. //
  109. //
  110. //-----------------------------------------------------------------------------
  111. void CNPC_Assassin::Precache( void )
  112. {
  113. PrecacheModel( "models/fassassin.mdl" );
  114. PrecacheScriptSound( "NPC_Assassin.ShootPistol" );
  115. PrecacheScriptSound( "Zombie.AttackHit" );
  116. PrecacheScriptSound( "Assassin.AttackMiss" );
  117. PrecacheScriptSound( "NPC_Assassin.Footstep" );
  118. PrecacheModel( "sprites/redglow1.vmt" );
  119. BaseClass::Precache();
  120. }
  121. //-----------------------------------------------------------------------------
  122. // Purpose:
  123. //
  124. //
  125. //-----------------------------------------------------------------------------
  126. void CNPC_Assassin::Spawn( void )
  127. {
  128. Precache();
  129. SetModel( "models/fassassin.mdl" );
  130. SetHullType(HULL_HUMAN);
  131. SetHullSizeNormal();
  132. SetSolid( SOLID_BBOX );
  133. AddSolidFlags( FSOLID_NOT_STANDABLE );
  134. SetMoveType( MOVETYPE_STEP );
  135. SetBloodColor( BLOOD_COLOR_RED );
  136. m_iHealth = sk_assassin_health.GetFloat();
  137. m_flFieldOfView = 0.1;
  138. m_NPCState = NPC_STATE_NONE;
  139. CapabilitiesClear();
  140. CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_GROUND | bits_CAP_MOVE_JUMP );
  141. CapabilitiesAdd( bits_CAP_SQUAD | bits_CAP_USE_WEAPONS | bits_CAP_AIM_GUN | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
  142. //Turn on our guns
  143. SetBodygroup( 1, 1 );
  144. int attachment = LookupAttachment( "Eye" );
  145. // Start up the eye glow
  146. m_pEyeSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false );
  147. if ( m_pEyeSprite != NULL )
  148. {
  149. m_pEyeSprite->SetAttachment( this, attachment );
  150. m_pEyeSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 200, kRenderFxNone );
  151. m_pEyeSprite->SetScale( 0.25f );
  152. }
  153. // Start up the eye trail
  154. m_pEyeTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false );
  155. if ( m_pEyeTrail != NULL )
  156. {
  157. m_pEyeTrail->SetAttachment( this, attachment );
  158. m_pEyeTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 200, kRenderFxNone );
  159. m_pEyeTrail->SetStartWidth( 8.0f );
  160. m_pEyeTrail->SetLifeTime( 0.75f );
  161. }
  162. NPCInit();
  163. m_bEvade = false;
  164. m_bAggressive = false;
  165. }
  166. //-----------------------------------------------------------------------------
  167. // Purpose: Returns true if a reasonable jumping distance
  168. // Input :
  169. // Output :
  170. //-----------------------------------------------------------------------------
  171. bool CNPC_Assassin::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
  172. {
  173. const float MAX_JUMP_RISE = 256.0f;
  174. const float MAX_JUMP_DISTANCE = 256.0f;
  175. const float MAX_JUMP_DROP = 512.0f;
  176. return BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE );
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose:
  180. // Input : flDot -
  181. // flDist -
  182. // Output : int CNPC_Assassin::MeleeAttack1Conditions
  183. //-----------------------------------------------------------------------------
  184. int CNPC_Assassin::MeleeAttack1Conditions ( float flDot, float flDist )
  185. {
  186. if ( flDist > 84 )
  187. return COND_TOO_FAR_TO_ATTACK;
  188. if ( flDot < 0.7f )
  189. return 0;
  190. if ( GetEnemy() == NULL )
  191. return 0;
  192. return COND_CAN_MELEE_ATTACK1;
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose:
  196. // Input : flDot -
  197. // flDist -
  198. // Output : int CNPC_Assassin::RangeAttack1Conditions
  199. //-----------------------------------------------------------------------------
  200. int CNPC_Assassin::RangeAttack1Conditions ( float flDot, float flDist )
  201. {
  202. if ( flDist < 84 )
  203. return COND_TOO_CLOSE_TO_ATTACK;
  204. if ( flDist > 1024 )
  205. return COND_TOO_FAR_TO_ATTACK;
  206. if ( flDot < 0.5f )
  207. return COND_NOT_FACING_ATTACK;
  208. return COND_CAN_RANGE_ATTACK1;
  209. }
  210. //-----------------------------------------------------------------------------
  211. // Purpose:
  212. // Input : flDot -
  213. // flDist -
  214. // Output : int CNPC_Assassin::RangeAttack1Conditions
  215. //-----------------------------------------------------------------------------
  216. int CNPC_Assassin::RangeAttack2Conditions ( float flDot, float flDist )
  217. {
  218. if ( m_flNextLungeTime > gpGlobals->curtime )
  219. return 0;
  220. float lungeRange = GetSequenceMoveDist( SelectWeightedSequence( (Activity) ACT_ASSASSIN_FLIP_FORWARD ) );
  221. if ( flDist < lungeRange * 0.25f )
  222. return COND_TOO_CLOSE_TO_ATTACK;
  223. if ( flDist > lungeRange * 1.5f )
  224. return COND_TOO_FAR_TO_ATTACK;
  225. if ( flDot < 0.75f )
  226. return COND_NOT_FACING_ATTACK;
  227. if ( GetEnemy() == NULL )
  228. return 0;
  229. // Check for a clear path
  230. trace_t tr;
  231. UTIL_TraceHull( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  232. if ( tr.fraction == 1.0f || tr.m_pEnt == GetEnemy() )
  233. return COND_CAN_RANGE_ATTACK2;
  234. return 0;
  235. }
  236. //-----------------------------------------------------------------------------
  237. // Purpose:
  238. // Input : hand -
  239. //-----------------------------------------------------------------------------
  240. void CNPC_Assassin::FirePistol( int hand )
  241. {
  242. if ( m_flNextShotTime > gpGlobals->curtime )
  243. return;
  244. m_flNextShotTime = gpGlobals->curtime + random->RandomFloat( 0.05f, 0.15f );
  245. Vector muzzlePos;
  246. QAngle muzzleAngle;
  247. const char *handName = ( hand ) ? "LeftMuzzle" : "RightMuzzle";
  248. GetAttachment( handName, muzzlePos, muzzleAngle );
  249. Vector muzzleDir;
  250. if ( GetEnemy() == NULL )
  251. {
  252. AngleVectors( muzzleAngle, &muzzleDir );
  253. }
  254. else
  255. {
  256. muzzleDir = GetEnemy()->BodyTarget( muzzlePos ) - muzzlePos;
  257. VectorNormalize( muzzleDir );
  258. }
  259. int bulletType = GetAmmoDef()->Index( "Pistol" );
  260. FireBullets( 1, muzzlePos, muzzleDir, VECTOR_CONE_5DEGREES, 1024, bulletType, 2 );
  261. UTIL_MuzzleFlash( muzzlePos, muzzleAngle, 0.5f, 1 );
  262. CPASAttenuationFilter filter( this );
  263. EmitSound( filter, entindex(), "NPC_Assassin.ShootPistol" );
  264. }
  265. //---------------------------------------------------------
  266. //---------------------------------------------------------
  267. void CNPC_Assassin::HandleAnimEvent( animevent_t *pEvent )
  268. {
  269. if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_RIGHT )
  270. {
  271. FirePistol( 0 );
  272. return;
  273. }
  274. if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_LEFT )
  275. {
  276. FirePistol( 1 );
  277. return;
  278. }
  279. if ( pEvent->event == AE_ASSASIN_KICK_HIT )
  280. {
  281. Vector attackDir = BodyDirection2D();
  282. Vector attackPos = WorldSpaceCenter() + ( attackDir * 64.0f );
  283. trace_t tr;
  284. UTIL_TraceHull( WorldSpaceCenter(), attackPos, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );
  285. if ( ( tr.m_pEnt != NULL ) && ( tr.DidHitWorld() == false ) )
  286. {
  287. if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
  288. {
  289. CTakeDamageInfo info( this, this, 5, DMG_CLUB );
  290. CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
  291. tr.m_pEnt->TakeDamage( info );
  292. CBasePlayer *pPlayer = ToBasePlayer( tr.m_pEnt );
  293. if ( pPlayer != NULL )
  294. {
  295. //Kick the player angles
  296. pPlayer->ViewPunch( QAngle( -30, 40, 10 ) );
  297. }
  298. EmitSound( "Zombie.AttackHit" );
  299. //EmitSound( "Assassin.AttackHit" );
  300. }
  301. }
  302. else
  303. {
  304. EmitSound( "Assassin.AttackMiss" );
  305. //EmitSound( "Assassin.AttackMiss" );
  306. }
  307. return;
  308. }
  309. BaseClass::HandleAnimEvent( pEvent );
  310. }
  311. //-----------------------------------------------------------------------------
  312. // Purpose: Causes the assassin to prefer to run away, rather than towards her target
  313. //-----------------------------------------------------------------------------
  314. bool CNPC_Assassin::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
  315. {
  316. if ( GetEnemy() == NULL )
  317. return true;
  318. float multiplier = 1.0f;
  319. Vector moveDir = ( vecEnd - vecStart );
  320. VectorNormalize( moveDir );
  321. Vector enemyDir = ( GetEnemy()->GetAbsOrigin() - vecStart );
  322. VectorNormalize( enemyDir );
  323. // If we're moving towards our enemy, then the cost is much higher than normal
  324. if ( DotProduct( enemyDir, moveDir ) > 0.5f )
  325. {
  326. multiplier = 16.0f;
  327. }
  328. *pCost *= multiplier;
  329. return ( multiplier != 1 );
  330. }
  331. //---------------------------------------------------------
  332. //---------------------------------------------------------
  333. int CNPC_Assassin::SelectSchedule ( void )
  334. {
  335. switch ( m_NPCState )
  336. {
  337. case NPC_STATE_IDLE:
  338. case NPC_STATE_ALERT:
  339. {
  340. if ( HasCondition ( COND_HEAR_DANGER ) )
  341. return SCHED_TAKE_COVER_FROM_BEST_SOUND;
  342. if ( HasCondition ( COND_HEAR_COMBAT ) )
  343. return SCHED_INVESTIGATE_SOUND;
  344. }
  345. break;
  346. case NPC_STATE_COMBAT:
  347. {
  348. // dead enemy
  349. if ( HasCondition( COND_ENEMY_DEAD ) )
  350. {
  351. // call base class, all code to handle dead enemies is centralized there.
  352. return BaseClass::SelectSchedule();
  353. }
  354. // Need to move
  355. if ( /*( HasCondition( COND_SEE_ENEMY ) && HasCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME ) && random->RandomInt( 0, 32 ) == 0 && m_flNextFlipTime < gpGlobals->curtime ) )*/
  356. ( m_nNumFlips > 0 ) ||
  357. ( ( HasCondition ( COND_LIGHT_DAMAGE ) && random->RandomInt( 0, 2 ) == 0 ) ) || ( HasCondition ( COND_HEAVY_DAMAGE ) ) )
  358. {
  359. if ( m_nNumFlips <= 0 )
  360. {
  361. m_nNumFlips = random->RandomInt( 1, 2 );
  362. }
  363. return SCHED_ASSASSIN_EVADE;
  364. }
  365. // Can kick
  366. if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
  367. return SCHED_MELEE_ATTACK1;
  368. // Can shoot
  369. if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
  370. {
  371. m_flNextLungeTime = gpGlobals->curtime + 2.0f;
  372. m_nLastFlipType = FLIP_FORWARD;
  373. return SCHED_ASSASSIN_LUNGE;
  374. }
  375. // Can shoot
  376. if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  377. return SCHED_RANGE_ATTACK1;
  378. // Face our enemy
  379. if ( HasCondition( COND_SEE_ENEMY ) )
  380. return SCHED_COMBAT_FACE;
  381. // new enemy
  382. if ( HasCondition( COND_NEW_ENEMY ) )
  383. return SCHED_TAKE_COVER_FROM_ENEMY;
  384. // ALERT( at_console, "stand\n");
  385. return SCHED_ASSASSIN_FIND_VANTAGE_POINT;
  386. }
  387. break;
  388. }
  389. return BaseClass::SelectSchedule();
  390. }
  391. //-----------------------------------------------------------------------------
  392. // Purpose:
  393. //-----------------------------------------------------------------------------
  394. void CNPC_Assassin::PrescheduleThink( void )
  395. {
  396. if ( GetActivity() == ACT_RUN || GetActivity() == ACT_WALK)
  397. {
  398. CPASAttenuationFilter filter( this );
  399. static int iStep = 0;
  400. iStep = ! iStep;
  401. if (iStep)
  402. {
  403. EmitSound( filter, entindex(), "NPC_Assassin.Footstep" );
  404. }
  405. }
  406. }
  407. //-----------------------------------------------------------------------------
  408. // Purpose:
  409. // Input : right -
  410. // Output : Returns true on success, false on failure.
  411. //-----------------------------------------------------------------------------
  412. bool CNPC_Assassin::CanFlip( int flipType, Activity &activity, const Vector *avoidPosition )
  413. {
  414. Vector testDir;
  415. Activity act = ACT_INVALID;
  416. switch( flipType )
  417. {
  418. case FLIP_RIGHT:
  419. GetVectors( NULL, &testDir, NULL );
  420. act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_RIGHT );
  421. break;
  422. case FLIP_LEFT:
  423. GetVectors( NULL, &testDir, NULL );
  424. testDir.Negate();
  425. act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_LEFT );
  426. break;
  427. case FLIP_FORWARD:
  428. GetVectors( &testDir, NULL, NULL );
  429. act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_FORWARD );
  430. break;
  431. case FLIP_BACKWARD:
  432. GetVectors( &testDir, NULL, NULL );
  433. testDir.Negate();
  434. act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_BACK );
  435. break;
  436. default:
  437. assert(0); //NOTENOTE: Invalid flip type
  438. activity = ACT_INVALID;
  439. return false;
  440. break;
  441. }
  442. // Make sure we don't flip towards our avoidance position/
  443. if ( avoidPosition != NULL )
  444. {
  445. Vector avoidDir = (*avoidPosition) - GetAbsOrigin();
  446. VectorNormalize( avoidDir );
  447. if ( DotProduct( avoidDir, testDir ) > 0.0f )
  448. return false;
  449. }
  450. int seq = SelectWeightedSequence( act );
  451. // Find out the length of this sequence
  452. float testDist = GetSequenceMoveDist( seq );
  453. // Find the resulting end position from the sequence's movement
  454. Vector endPos = GetAbsOrigin() + ( testDir * testDist );
  455. trace_t tr;
  456. if ( ( flipType != FLIP_BACKWARD ) && ( avoidPosition != NULL ) )
  457. {
  458. UTIL_TraceLine( (*avoidPosition), endPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  459. if ( tr.fraction == 1.0f )
  460. return false;
  461. }
  462. /*
  463. UTIL_TraceHull( GetAbsOrigin(), endPos, NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  464. // See if we're hit an obstruction in that direction
  465. if ( tr.fraction < 1.0f )
  466. {
  467. if ( g_debug_assassin.GetBool() )
  468. {
  469. NDebugOverlay::BoxDirection( GetAbsOrigin(), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( testDist, 0, StepHeight() ), testDir, 255, 0, 0, true, 2.0f );
  470. }
  471. return false;
  472. }
  473. #define NUM_STEPS 2
  474. float stepLength = testDist / NUM_STEPS;
  475. for ( int i = 1; i <= NUM_STEPS; i++ )
  476. {
  477. endPos = GetAbsOrigin() + ( testDir * (stepLength*i) );
  478. // Also check for a cliff edge
  479. UTIL_TraceHull( endPos, endPos - Vector( 0, 0, StepHeight() * 4.0f ), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
  480. if ( tr.fraction == 1.0f )
  481. {
  482. if ( g_debug_assassin.GetBool() )
  483. {
  484. NDebugOverlay::BoxDirection( endPos, NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( StepHeight() * 4.0f, 0, StepHeight() ), Vector(0,0,-1), 255, 0, 0, true, 2.0f );
  485. }
  486. return false;
  487. }
  488. }
  489. if ( g_debug_assassin.GetBool() )
  490. {
  491. NDebugOverlay::BoxDirection( GetAbsOrigin(), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( testDist, 0, StepHeight() ), testDir, 0, 255, 0, true, 2.0f );
  492. }
  493. */
  494. AIMoveTrace_t moveTrace;
  495. GetMoveProbe()->TestGroundMove( GetAbsOrigin(), endPos, MASK_NPCSOLID, AITGM_DEFAULT, &moveTrace );
  496. if ( moveTrace.fStatus != AIMR_OK )
  497. return false;
  498. // Return the activity to use
  499. activity = (Activity) act;
  500. return true;
  501. }
  502. //---------------------------------------------------------
  503. // Purpose:
  504. //---------------------------------------------------------
  505. void CNPC_Assassin::StartTask( const Task_t *pTask )
  506. {
  507. switch( pTask->iTask )
  508. {
  509. case TASK_ASSASSIN_SET_EYE_STATE:
  510. {
  511. SetEyeState( (eyeState_t) ( (int) pTask->flTaskData ) );
  512. TaskComplete();
  513. }
  514. break;
  515. case TASK_ASSASSIN_EVADE:
  516. {
  517. Activity flipAct = ACT_INVALID;
  518. const Vector *avoidPos = ( GetEnemy() != NULL ) ? &(GetEnemy()->GetAbsOrigin()) : NULL;
  519. for ( int i = FLIP_LEFT; i < NUM_FLIP_TYPES; i++ )
  520. {
  521. if ( CanFlip( i, flipAct, avoidPos ) )
  522. {
  523. // Don't flip back to where we just were
  524. if ( ( ( i == FLIP_LEFT ) && ( m_nLastFlipType == FLIP_RIGHT ) ) ||
  525. ( ( i == FLIP_RIGHT ) && ( m_nLastFlipType == FLIP_LEFT ) ) ||
  526. ( ( i == FLIP_FORWARD ) && ( m_nLastFlipType == FLIP_BACKWARD ) ) ||
  527. ( ( i == FLIP_BACKWARD ) && ( m_nLastFlipType == FLIP_FORWARD ) ) )
  528. {
  529. flipAct = ACT_INVALID;
  530. continue;
  531. }
  532. m_nNumFlips--;
  533. ResetIdealActivity( flipAct );
  534. m_flNextFlipTime = gpGlobals->curtime + 2.0f;
  535. m_nLastFlipType = i;
  536. break;
  537. }
  538. }
  539. if ( flipAct == ACT_INVALID )
  540. {
  541. m_nNumFlips = 0;
  542. m_nLastFlipType = -1;
  543. m_flNextFlipTime = gpGlobals->curtime + 2.0f;
  544. TaskFail( "Unable to find flip evasion direction!\n" );
  545. }
  546. }
  547. break;
  548. case TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT:
  549. {
  550. assert( GetEnemy() != NULL );
  551. if ( GetEnemy() == NULL )
  552. break;
  553. Vector goalPos;
  554. CHintCriteria hint;
  555. // Find a disadvantage node near the player, but away from ourselves
  556. hint.SetHintType( HINT_TACTICAL_ENEMY_DISADVANTAGED );
  557. hint.AddExcludePosition( GetAbsOrigin(), 256 );
  558. hint.AddExcludePosition( GetEnemy()->GetAbsOrigin(), 256 );
  559. if ( ( m_pSquad != NULL ) && ( m_pSquad->NumMembers() > 1 ) )
  560. {
  561. AISquadIter_t iter;
  562. for ( CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
  563. {
  564. if ( pSquadMember == NULL )
  565. continue;
  566. hint.AddExcludePosition( pSquadMember->GetAbsOrigin(), 128 );
  567. }
  568. }
  569. hint.SetFlag( bits_HINT_NODE_NEAREST );
  570. CAI_Hint *pHint = CAI_HintManager::FindHint( this, GetEnemy()->GetAbsOrigin(), &hint );
  571. if ( pHint == NULL )
  572. {
  573. TaskFail( "Unable to find vantage point!\n" );
  574. break;
  575. }
  576. pHint->GetPosition( this, &goalPos );
  577. AI_NavGoal_t goal( goalPos );
  578. //Try to run directly there
  579. if ( GetNavigator()->SetGoal( goal ) == false )
  580. {
  581. TaskFail( "Unable to find path to vantage point!\n" );
  582. break;
  583. }
  584. TaskComplete();
  585. }
  586. break;
  587. default:
  588. BaseClass::StartTask( pTask );
  589. break;
  590. }
  591. }
  592. //-----------------------------------------------------------------------------
  593. // Purpose:
  594. //
  595. //
  596. //-----------------------------------------------------------------------------
  597. float CNPC_Assassin::MaxYawSpeed( void )
  598. {
  599. switch( GetActivity() )
  600. {
  601. case ACT_TURN_LEFT:
  602. case ACT_TURN_RIGHT:
  603. return 160;
  604. break;
  605. case ACT_RUN:
  606. return 900;
  607. break;
  608. case ACT_RANGE_ATTACK1:
  609. return 0;
  610. break;
  611. default:
  612. return 60;
  613. break;
  614. }
  615. }
  616. //---------------------------------------------------------
  617. //---------------------------------------------------------
  618. void CNPC_Assassin::RunTask( const Task_t *pTask )
  619. {
  620. switch( pTask->iTask )
  621. {
  622. case TASK_ASSASSIN_EVADE:
  623. AutoMovement();
  624. if ( IsActivityFinished() )
  625. {
  626. TaskComplete();
  627. }
  628. break;
  629. default:
  630. BaseClass::RunTask( pTask );
  631. break;
  632. }
  633. }
  634. //---------------------------------------------------------
  635. //---------------------------------------------------------
  636. bool CNPC_Assassin::FValidateHintType ( CAI_Hint *pHint )
  637. {
  638. switch( pHint->HintType() )
  639. {
  640. case HINT_TACTICAL_ENEMY_DISADVANTAGED:
  641. {
  642. Vector hintPos;
  643. pHint->GetPosition( this, &hintPos );
  644. // Verify that we can see the target from that position
  645. hintPos += GetViewOffset();
  646. trace_t tr;
  647. UTIL_TraceLine( hintPos, GetEnemy()->BodyTarget( hintPos, true ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  648. // Check for seeing our target at the new location
  649. if ( ( tr.fraction == 1.0f ) || ( tr.m_pEnt == GetEnemy() ) )
  650. return false;
  651. return true;
  652. break;
  653. }
  654. default:
  655. return false;
  656. break;
  657. }
  658. return FALSE;
  659. }
  660. //-----------------------------------------------------------------------------
  661. // Purpose:
  662. // Output : const Vector
  663. //-----------------------------------------------------------------------------
  664. const Vector &CNPC_Assassin::GetViewOffset( void )
  665. {
  666. static Vector eyeOffset;
  667. //FIXME: Use eye attachment?
  668. // If we're crouching, offset appropriately
  669. if ( ( GetActivity() == ACT_ASSASSIN_PERCH ) ||
  670. ( GetActivity() == ACT_RANGE_ATTACK1 ) )
  671. {
  672. eyeOffset = Vector( 0, 0, 24.0f );
  673. }
  674. else
  675. {
  676. eyeOffset = BaseClass::GetViewOffset();
  677. }
  678. return eyeOffset;
  679. }
  680. //-----------------------------------------------------------------------------
  681. // Purpose:
  682. //-----------------------------------------------------------------------------
  683. void CNPC_Assassin::OnScheduleChange( void )
  684. {
  685. //TODO: Change eye state?
  686. BaseClass::OnScheduleChange();
  687. }
  688. //-----------------------------------------------------------------------------
  689. // Purpose:
  690. // Input : state -
  691. //-----------------------------------------------------------------------------
  692. void CNPC_Assassin::SetEyeState( eyeState_t state )
  693. {
  694. //Must have a valid eye to affect
  695. if ( ( m_pEyeSprite == NULL ) || ( m_pEyeTrail == NULL ) )
  696. return;
  697. //Set the state
  698. switch( state )
  699. {
  700. default:
  701. case ASSASSIN_EYE_SEE_TARGET: //Fade in and scale up
  702. m_pEyeSprite->SetColor( 255, 0, 0 );
  703. m_pEyeSprite->SetBrightness( 164, 0.1f );
  704. m_pEyeSprite->SetScale( 0.4f, 0.1f );
  705. m_pEyeTrail->SetColor( 255, 0, 0 );
  706. m_pEyeTrail->SetScale( 8.0f );
  707. m_pEyeTrail->SetBrightness( 164 );
  708. break;
  709. case ASSASSIN_EYE_SEEKING_TARGET: //Ping-pongs
  710. //Toggle our state
  711. m_bBlinkState = !m_bBlinkState;
  712. m_pEyeSprite->SetColor( 255, 128, 0 );
  713. if ( m_bBlinkState )
  714. {
  715. //Fade up and scale up
  716. m_pEyeSprite->SetScale( 0.25f, 0.1f );
  717. m_pEyeSprite->SetBrightness( 164, 0.1f );
  718. }
  719. else
  720. {
  721. //Fade down and scale down
  722. m_pEyeSprite->SetScale( 0.2f, 0.1f );
  723. m_pEyeSprite->SetBrightness( 64, 0.1f );
  724. }
  725. break;
  726. case ASSASSIN_EYE_DORMANT: //Fade out and scale down
  727. m_pEyeSprite->SetScale( 0.5f, 0.5f );
  728. m_pEyeSprite->SetBrightness( 64, 0.5f );
  729. m_pEyeTrail->SetScale( 2.0f );
  730. m_pEyeTrail->SetBrightness( 64 );
  731. break;
  732. case ASSASSIN_EYE_DEAD: //Fade out slowly
  733. m_pEyeSprite->SetColor( 255, 0, 0 );
  734. m_pEyeSprite->SetScale( 0.1f, 5.0f );
  735. m_pEyeSprite->SetBrightness( 0, 5.0f );
  736. m_pEyeTrail->SetColor( 255, 0, 0 );
  737. m_pEyeTrail->SetScale( 0.1f );
  738. m_pEyeTrail->SetBrightness( 0 );
  739. break;
  740. case ASSASSIN_EYE_ACTIVE:
  741. m_pEyeSprite->SetColor( 255, 0, 0 );
  742. m_pEyeSprite->SetScale( 0.1f );
  743. m_pEyeSprite->SetBrightness( 0 );
  744. break;
  745. }
  746. }
  747. //-----------------------------------------------------------------------------
  748. // Purpose:
  749. //-----------------------------------------------------------------------------
  750. void CNPC_Assassin::GatherEnemyConditions( CBaseEntity *pEnemy )
  751. {
  752. ClearCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
  753. BaseClass::GatherEnemyConditions( pEnemy );
  754. // See if we're being targetted specifically
  755. if ( HasCondition( COND_ENEMY_FACING_ME ) )
  756. {
  757. Vector enemyDir = GetAbsOrigin() - pEnemy->GetAbsOrigin();
  758. VectorNormalize( enemyDir );
  759. Vector enemyBodyDir;
  760. CBasePlayer *pPlayer = ToBasePlayer( pEnemy );
  761. if ( pPlayer != NULL )
  762. {
  763. enemyBodyDir = pPlayer->BodyDirection3D();
  764. }
  765. else
  766. {
  767. AngleVectors( pEnemy->GetAbsAngles(), &enemyBodyDir );
  768. }
  769. float enemyDot = DotProduct( enemyBodyDir, enemyDir );
  770. //FIXME: Need to refine this a bit
  771. if ( enemyDot > 0.97f )
  772. {
  773. SetCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
  774. }
  775. }
  776. }
  777. //-----------------------------------------------------------------------------
  778. // Purpose:
  779. //-----------------------------------------------------------------------------
  780. void CNPC_Assassin::BuildScheduleTestBits( void )
  781. {
  782. SetNextThink( gpGlobals->curtime + 0.05 );
  783. //Don't allow any modifications when scripted
  784. if ( m_NPCState == NPC_STATE_SCRIPT )
  785. return;
  786. //Become interrupted if we're targetted when shooting an enemy
  787. if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) )
  788. {
  789. SetCustomInterruptCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
  790. }
  791. }
  792. //-----------------------------------------------------------------------------
  793. // Purpose:
  794. // Input : &info -
  795. //-----------------------------------------------------------------------------
  796. void CNPC_Assassin::Event_Killed( const CTakeDamageInfo &info )
  797. {
  798. BaseClass::Event_Killed( info );
  799. // Turn off the eye
  800. SetEyeState( ASSASSIN_EYE_DEAD );
  801. // Turn off the pistols
  802. SetBodygroup( 1, 0 );
  803. // Spawn her guns
  804. }
  805. //-----------------------------------------------------------------------------
  806. //
  807. // Schedules
  808. //
  809. //-----------------------------------------------------------------------------
  810. AI_BEGIN_CUSTOM_NPC( npc_assassin, CNPC_Assassin )
  811. DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_LEFT)
  812. DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_RIGHT)
  813. DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_BACK)
  814. DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_FORWARD)
  815. DECLARE_ACTIVITY(ACT_ASSASSIN_PERCH)
  816. //Adrian: events go here
  817. DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_RIGHT )
  818. DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_LEFT )
  819. DECLARE_ANIMEVENT( AE_ASSASIN_KICK_HIT )
  820. DECLARE_TASK(TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT)
  821. DECLARE_TASK(TASK_ASSASSIN_EVADE)
  822. DECLARE_TASK(TASK_ASSASSIN_SET_EYE_STATE)
  823. DECLARE_TASK(TASK_ASSASSIN_LUNGE)
  824. DECLARE_CONDITION(COND_ASSASSIN_ENEMY_TARGETTING_ME)
  825. //=========================================================
  826. // ASSASSIN_STALK_ENEMY
  827. //=========================================================
  828. DEFINE_SCHEDULE
  829. (
  830. SCHED_ASSASSIN_STALK_ENEMY,
  831. " Tasks"
  832. " TASK_STOP_MOVING 0"
  833. " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ASSASSIN_PERCH"
  834. " "
  835. " Interrupts"
  836. " COND_ASSASSIN_ENEMY_TARGETTING_ME"
  837. " COND_SEE_ENEMY"
  838. " COND_LIGHT_DAMAGE"
  839. " COND_HEAVY_DAMAGE"
  840. )
  841. //=========================================================
  842. // > ASSASSIN_FIND_VANTAGE_POINT
  843. //=========================================================
  844. DEFINE_SCHEDULE
  845. (
  846. SCHED_ASSASSIN_FIND_VANTAGE_POINT,
  847. " Tasks"
  848. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
  849. " TASK_STOP_MOVING 0"
  850. " TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT 0"
  851. " TASK_RUN_PATH 0"
  852. " TASK_WAIT_FOR_MOVEMENT 0"
  853. " TASK_SET_SCHEDULE SCHEDULE:SCHED_ASSASSIN_STALK_ENEMY"
  854. " "
  855. " Interrupts"
  856. " COND_LIGHT_DAMAGE"
  857. " COND_HEAVY_DAMAGE"
  858. " COND_TASK_FAILED"
  859. )
  860. //=========================================================
  861. // Assassin needs to avoid the player
  862. //=========================================================
  863. DEFINE_SCHEDULE
  864. (
  865. SCHED_ASSASSIN_EVADE,
  866. " Tasks"
  867. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
  868. " TASK_STOP_MOVING 0"
  869. " TASK_ASSASSIN_EVADE 0"
  870. " "
  871. " Interrupts"
  872. " COND_TASK_FAILED"
  873. )
  874. //=========================================================
  875. // Assassin needs to avoid the player
  876. //=========================================================
  877. DEFINE_SCHEDULE
  878. (
  879. SCHED_ASSASSIN_LUNGE,
  880. " Tasks"
  881. " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
  882. " TASK_STOP_MOVING 0"
  883. " TASK_FACE_ENEMY 0"
  884. " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ASSASSIN_FLIP_FORWARD"
  885. " "
  886. " Interrupts"
  887. " COND_TASK_FAILED"
  888. )
  889. AI_END_CUSTOM_NPC()