aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_assassin.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hl2/npc_assassin.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/hl2/npc_assassin.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_assassin.cpp2202
1 files changed, 1101 insertions, 1101 deletions
diff --git a/mp/src/game/server/hl2/npc_assassin.cpp b/mp/src/game/server/hl2/npc_assassin.cpp
index 7b755054..25b4ba93 100644
--- a/mp/src/game/server/hl2/npc_assassin.cpp
+++ b/mp/src/game/server/hl2/npc_assassin.cpp
@@ -1,1101 +1,1101 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================//
-
-#include "cbase.h"
-#include "ammodef.h"
-#include "AI_Hint.h"
-#include "AI_Navigator.h"
-#include "npc_Assassin.h"
-#include "game.h"
-#include "npcevent.h"
-#include "engine/IEngineSound.h"
-#include "ai_squad.h"
-#include "AI_SquadSlot.h"
-#include "ai_moveprobe.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-ConVar sk_assassin_health( "sk_assassin_health","150");
-ConVar g_debug_assassin( "g_debug_assassin", "0" );
-
-//=========================================================
-// Anim Events
-//=========================================================
-#define ASSASSIN_AE_FIRE_PISTOL_RIGHT 1
-#define ASSASSIN_AE_FIRE_PISTOL_LEFT 2
-#define ASSASSIN_AE_KICK_HIT 3
-
-int AE_ASSASIN_FIRE_PISTOL_RIGHT;
-int AE_ASSASIN_FIRE_PISTOL_LEFT;
-int AE_ASSASIN_KICK_HIT;
-
-//=========================================================
-// Assassin activities
-//=========================================================
-int ACT_ASSASSIN_FLIP_LEFT;
-int ACT_ASSASSIN_FLIP_RIGHT;
-int ACT_ASSASSIN_FLIP_BACK;
-int ACT_ASSASSIN_FLIP_FORWARD;
-int ACT_ASSASSIN_PERCH;
-
-//=========================================================
-// Flip types
-//=========================================================
-enum
-{
- FLIP_LEFT,
- FLIP_RIGHT,
- FLIP_FORWARD,
- FLIP_BACKWARD,
- NUM_FLIP_TYPES,
-};
-
-//=========================================================
-// Private conditions
-//=========================================================
-enum Assassin_Conds
-{
- COND_ASSASSIN_ENEMY_TARGETTING_ME = LAST_SHARED_CONDITION,
-};
-
-//=========================================================
-// Assassin schedules
-//=========================================================
-enum
-{
- SCHED_ASSASSIN_FIND_VANTAGE_POINT = LAST_SHARED_SCHEDULE,
- SCHED_ASSASSIN_EVADE,
- SCHED_ASSASSIN_STALK_ENEMY,
- SCHED_ASSASSIN_LUNGE,
-};
-
-//=========================================================
-// Assassin tasks
-//=========================================================
-enum
-{
- TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT = LAST_SHARED_TASK,
- TASK_ASSASSIN_EVADE,
- TASK_ASSASSIN_SET_EYE_STATE,
- TASK_ASSASSIN_LUNGE,
-};
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Class Constructor
-//-----------------------------------------------------------------------------
-CNPC_Assassin::CNPC_Assassin( void )
-{
-}
-
-//-----------------------------------------------------------------------------
-
-LINK_ENTITY_TO_CLASS( npc_assassin, CNPC_Assassin );
-
-#if 0
-//---------------------------------------------------------
-// Custom Client entity
-//---------------------------------------------------------
-IMPLEMENT_SERVERCLASS_ST(CNPC_Assassin, DT_NPC_Assassin)
-END_SEND_TABLE()
-
-#endif
-
-//---------------------------------------------------------
-// Save/Restore
-//---------------------------------------------------------
-BEGIN_DATADESC( CNPC_Assassin )
- DEFINE_FIELD( m_nNumFlips, FIELD_INTEGER ),
- DEFINE_FIELD( m_nLastFlipType, FIELD_INTEGER ),
- DEFINE_FIELD( m_flNextFlipTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextLungeTime, FIELD_TIME ),
- DEFINE_FIELD( m_flNextShotTime, FIELD_TIME ),
- DEFINE_FIELD( m_bEvade, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bAggressive, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_pEyeSprite, FIELD_CLASSPTR ),
- DEFINE_FIELD( m_pEyeTrail, FIELD_CLASSPTR ),
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//
-//
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::Precache( void )
-{
- PrecacheModel( "models/fassassin.mdl" );
-
- PrecacheScriptSound( "NPC_Assassin.ShootPistol" );
- PrecacheScriptSound( "Zombie.AttackHit" );
- PrecacheScriptSound( "Assassin.AttackMiss" );
- PrecacheScriptSound( "NPC_Assassin.Footstep" );
-
- PrecacheModel( "sprites/redglow1.vmt" );
-
- BaseClass::Precache();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//
-//
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::Spawn( void )
-{
- Precache();
-
- SetModel( "models/fassassin.mdl" );
-
- SetHullType(HULL_HUMAN);
- SetHullSizeNormal();
-
- SetSolid( SOLID_BBOX );
- AddSolidFlags( FSOLID_NOT_STANDABLE );
- SetMoveType( MOVETYPE_STEP );
- SetBloodColor( BLOOD_COLOR_RED );
-
- m_iHealth = sk_assassin_health.GetFloat();
- m_flFieldOfView = 0.1;
- m_NPCState = NPC_STATE_NONE;
-
- CapabilitiesClear();
- CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_GROUND | bits_CAP_MOVE_JUMP );
- 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 );
-
- //Turn on our guns
- SetBodygroup( 1, 1 );
-
- int attachment = LookupAttachment( "Eye" );
-
- // Start up the eye glow
- m_pEyeSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false );
-
- if ( m_pEyeSprite != NULL )
- {
- m_pEyeSprite->SetAttachment( this, attachment );
- m_pEyeSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 200, kRenderFxNone );
- m_pEyeSprite->SetScale( 0.25f );
- }
-
- // Start up the eye trail
- m_pEyeTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false );
-
- if ( m_pEyeTrail != NULL )
- {
- m_pEyeTrail->SetAttachment( this, attachment );
- m_pEyeTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 200, kRenderFxNone );
- m_pEyeTrail->SetStartWidth( 8.0f );
- m_pEyeTrail->SetLifeTime( 0.75f );
- }
-
- NPCInit();
-
- m_bEvade = false;
- m_bAggressive = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns true if a reasonable jumping distance
-// Input :
-// Output :
-//-----------------------------------------------------------------------------
-bool CNPC_Assassin::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
-{
- const float MAX_JUMP_RISE = 256.0f;
- const float MAX_JUMP_DISTANCE = 256.0f;
- const float MAX_JUMP_DROP = 512.0f;
-
- return BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flDot -
-// flDist -
-// Output : int CNPC_Assassin::MeleeAttack1Conditions
-//-----------------------------------------------------------------------------
-int CNPC_Assassin::MeleeAttack1Conditions ( float flDot, float flDist )
-{
- if ( flDist > 84 )
- return COND_TOO_FAR_TO_ATTACK;
-
- if ( flDot < 0.7f )
- return 0;
-
- if ( GetEnemy() == NULL )
- return 0;
-
- return COND_CAN_MELEE_ATTACK1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flDot -
-// flDist -
-// Output : int CNPC_Assassin::RangeAttack1Conditions
-//-----------------------------------------------------------------------------
-int CNPC_Assassin::RangeAttack1Conditions ( float flDot, float flDist )
-{
- if ( flDist < 84 )
- return COND_TOO_CLOSE_TO_ATTACK;
-
- if ( flDist > 1024 )
- return COND_TOO_FAR_TO_ATTACK;
-
- if ( flDot < 0.5f )
- return COND_NOT_FACING_ATTACK;
-
- return COND_CAN_RANGE_ATTACK1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flDot -
-// flDist -
-// Output : int CNPC_Assassin::RangeAttack1Conditions
-//-----------------------------------------------------------------------------
-int CNPC_Assassin::RangeAttack2Conditions ( float flDot, float flDist )
-{
- if ( m_flNextLungeTime > gpGlobals->curtime )
- return 0;
-
- float lungeRange = GetSequenceMoveDist( SelectWeightedSequence( (Activity) ACT_ASSASSIN_FLIP_FORWARD ) );
-
- if ( flDist < lungeRange * 0.25f )
- return COND_TOO_CLOSE_TO_ATTACK;
-
- if ( flDist > lungeRange * 1.5f )
- return COND_TOO_FAR_TO_ATTACK;
-
- if ( flDot < 0.75f )
- return COND_NOT_FACING_ATTACK;
-
- if ( GetEnemy() == NULL )
- return 0;
-
- // Check for a clear path
- trace_t tr;
- UTIL_TraceHull( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction == 1.0f || tr.m_pEnt == GetEnemy() )
- return COND_CAN_RANGE_ATTACK2;
-
- return 0;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : hand -
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::FirePistol( int hand )
-{
- if ( m_flNextShotTime > gpGlobals->curtime )
- return;
-
- m_flNextShotTime = gpGlobals->curtime + random->RandomFloat( 0.05f, 0.15f );
-
- Vector muzzlePos;
- QAngle muzzleAngle;
-
- const char *handName = ( hand ) ? "LeftMuzzle" : "RightMuzzle";
-
- GetAttachment( handName, muzzlePos, muzzleAngle );
-
- Vector muzzleDir;
-
- if ( GetEnemy() == NULL )
- {
- AngleVectors( muzzleAngle, &muzzleDir );
- }
- else
- {
- muzzleDir = GetEnemy()->BodyTarget( muzzlePos ) - muzzlePos;
- VectorNormalize( muzzleDir );
- }
-
- int bulletType = GetAmmoDef()->Index( "Pistol" );
-
- FireBullets( 1, muzzlePos, muzzleDir, VECTOR_CONE_5DEGREES, 1024, bulletType, 2 );
-
- UTIL_MuzzleFlash( muzzlePos, muzzleAngle, 0.5f, 1 );
-
- CPASAttenuationFilter filter( this );
- EmitSound( filter, entindex(), "NPC_Assassin.ShootPistol" );
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Assassin::HandleAnimEvent( animevent_t *pEvent )
-{
-
- if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_RIGHT )
- {
- FirePistol( 0 );
- return;
- }
-
- if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_LEFT )
- {
- FirePistol( 1 );
- return;
- }
-
- if ( pEvent->event == AE_ASSASIN_KICK_HIT )
- {
- Vector attackDir = BodyDirection2D();
- Vector attackPos = WorldSpaceCenter() + ( attackDir * 64.0f );
-
- trace_t tr;
- UTIL_TraceHull( WorldSpaceCenter(), attackPos, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );
-
- if ( ( tr.m_pEnt != NULL ) && ( tr.DidHitWorld() == false ) )
- {
- if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
- {
- CTakeDamageInfo info( this, this, 5, DMG_CLUB );
- CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
- tr.m_pEnt->TakeDamage( info );
-
- CBasePlayer *pPlayer = ToBasePlayer( tr.m_pEnt );
-
- if ( pPlayer != NULL )
- {
- //Kick the player angles
- pPlayer->ViewPunch( QAngle( -30, 40, 10 ) );
- }
-
- EmitSound( "Zombie.AttackHit" );
- //EmitSound( "Assassin.AttackHit" );
- }
- }
- else
- {
- EmitSound( "Assassin.AttackMiss" );
- //EmitSound( "Assassin.AttackMiss" );
- }
-
- return;
- }
-
- BaseClass::HandleAnimEvent( pEvent );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Causes the assassin to prefer to run away, rather than towards her target
-//-----------------------------------------------------------------------------
-bool CNPC_Assassin::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
-{
- if ( GetEnemy() == NULL )
- return true;
-
- float multiplier = 1.0f;
-
- Vector moveDir = ( vecEnd - vecStart );
- VectorNormalize( moveDir );
-
- Vector enemyDir = ( GetEnemy()->GetAbsOrigin() - vecStart );
- VectorNormalize( enemyDir );
-
- // If we're moving towards our enemy, then the cost is much higher than normal
- if ( DotProduct( enemyDir, moveDir ) > 0.5f )
- {
- multiplier = 16.0f;
- }
-
- *pCost *= multiplier;
-
- return ( multiplier != 1 );
-}
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-int CNPC_Assassin::SelectSchedule ( void )
-{
- switch ( m_NPCState )
- {
- case NPC_STATE_IDLE:
- case NPC_STATE_ALERT:
- {
- if ( HasCondition ( COND_HEAR_DANGER ) )
- return SCHED_TAKE_COVER_FROM_BEST_SOUND;
-
- if ( HasCondition ( COND_HEAR_COMBAT ) )
- return SCHED_INVESTIGATE_SOUND;
- }
- break;
-
- case NPC_STATE_COMBAT:
- {
- // dead enemy
- if ( HasCondition( COND_ENEMY_DEAD ) )
- {
- // call base class, all code to handle dead enemies is centralized there.
- return BaseClass::SelectSchedule();
- }
-
- // Need to move
- if ( /*( HasCondition( COND_SEE_ENEMY ) && HasCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME ) && random->RandomInt( 0, 32 ) == 0 && m_flNextFlipTime < gpGlobals->curtime ) )*/
- ( m_nNumFlips > 0 ) ||
- ( ( HasCondition ( COND_LIGHT_DAMAGE ) && random->RandomInt( 0, 2 ) == 0 ) ) || ( HasCondition ( COND_HEAVY_DAMAGE ) ) )
- {
- if ( m_nNumFlips <= 0 )
- {
- m_nNumFlips = random->RandomInt( 1, 2 );
- }
-
- return SCHED_ASSASSIN_EVADE;
- }
-
- // Can kick
- if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
- return SCHED_MELEE_ATTACK1;
-
- // Can shoot
- if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
- {
- m_flNextLungeTime = gpGlobals->curtime + 2.0f;
- m_nLastFlipType = FLIP_FORWARD;
-
- return SCHED_ASSASSIN_LUNGE;
- }
-
- // Can shoot
- if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
- return SCHED_RANGE_ATTACK1;
-
- // Face our enemy
- if ( HasCondition( COND_SEE_ENEMY ) )
- return SCHED_COMBAT_FACE;
-
- // new enemy
- if ( HasCondition( COND_NEW_ENEMY ) )
- return SCHED_TAKE_COVER_FROM_ENEMY;
-
- // ALERT( at_console, "stand\n");
- return SCHED_ASSASSIN_FIND_VANTAGE_POINT;
- }
- break;
- }
-
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::PrescheduleThink( void )
-{
- if ( GetActivity() == ACT_RUN || GetActivity() == ACT_WALK)
- {
- CPASAttenuationFilter filter( this );
-
- static int iStep = 0;
- iStep = ! iStep;
- if (iStep)
- {
- EmitSound( filter, entindex(), "NPC_Assassin.Footstep" );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : right -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Assassin::CanFlip( int flipType, Activity &activity, const Vector *avoidPosition )
-{
- Vector testDir;
- Activity act = ACT_INVALID;
-
- switch( flipType )
- {
- case FLIP_RIGHT:
- GetVectors( NULL, &testDir, NULL );
- act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_RIGHT );
- break;
-
- case FLIP_LEFT:
- GetVectors( NULL, &testDir, NULL );
- testDir.Negate();
- act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_LEFT );
- break;
-
- case FLIP_FORWARD:
- GetVectors( &testDir, NULL, NULL );
- act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_FORWARD );
- break;
-
- case FLIP_BACKWARD:
- GetVectors( &testDir, NULL, NULL );
- testDir.Negate();
- act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_BACK );
- break;
-
- default:
- assert(0); //NOTENOTE: Invalid flip type
- activity = ACT_INVALID;
- return false;
- break;
- }
-
- // Make sure we don't flip towards our avoidance position/
- if ( avoidPosition != NULL )
- {
- Vector avoidDir = (*avoidPosition) - GetAbsOrigin();
- VectorNormalize( avoidDir );
-
- if ( DotProduct( avoidDir, testDir ) > 0.0f )
- return false;
- }
-
- int seq = SelectWeightedSequence( act );
-
- // Find out the length of this sequence
- float testDist = GetSequenceMoveDist( seq );
-
- // Find the resulting end position from the sequence's movement
- Vector endPos = GetAbsOrigin() + ( testDir * testDist );
-
- trace_t tr;
-
- if ( ( flipType != FLIP_BACKWARD ) && ( avoidPosition != NULL ) )
- {
- UTIL_TraceLine( (*avoidPosition), endPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- if ( tr.fraction == 1.0f )
- return false;
- }
-
- /*
- 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 );
-
- // See if we're hit an obstruction in that direction
- if ( tr.fraction < 1.0f )
- {
- if ( g_debug_assassin.GetBool() )
- {
- 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 );
- }
-
- return false;
- }
-
-#define NUM_STEPS 2
-
- float stepLength = testDist / NUM_STEPS;
-
- for ( int i = 1; i <= NUM_STEPS; i++ )
- {
- endPos = GetAbsOrigin() + ( testDir * (stepLength*i) );
-
- // Also check for a cliff edge
- 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 );
-
- if ( tr.fraction == 1.0f )
- {
- if ( g_debug_assassin.GetBool() )
- {
- 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 );
- }
-
- return false;
- }
- }
-
- if ( g_debug_assassin.GetBool() )
- {
- 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 );
- }
- */
-
- AIMoveTrace_t moveTrace;
- GetMoveProbe()->TestGroundMove( GetAbsOrigin(), endPos, MASK_NPCSOLID, AITGM_DEFAULT, &moveTrace );
-
- if ( moveTrace.fStatus != AIMR_OK )
- return false;
-
- // Return the activity to use
- activity = (Activity) act;
-
- return true;
-}
-
-//---------------------------------------------------------
-// Purpose:
-//---------------------------------------------------------
-void CNPC_Assassin::StartTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_ASSASSIN_SET_EYE_STATE:
- {
- SetEyeState( (eyeState_t) ( (int) pTask->flTaskData ) );
- TaskComplete();
- }
- break;
-
- case TASK_ASSASSIN_EVADE:
- {
- Activity flipAct = ACT_INVALID;
-
- const Vector *avoidPos = ( GetEnemy() != NULL ) ? &(GetEnemy()->GetAbsOrigin()) : NULL;
-
- for ( int i = FLIP_LEFT; i < NUM_FLIP_TYPES; i++ )
- {
- if ( CanFlip( i, flipAct, avoidPos ) )
- {
- // Don't flip back to where we just were
- if ( ( ( i == FLIP_LEFT ) && ( m_nLastFlipType == FLIP_RIGHT ) ) ||
- ( ( i == FLIP_RIGHT ) && ( m_nLastFlipType == FLIP_LEFT ) ) ||
- ( ( i == FLIP_FORWARD ) && ( m_nLastFlipType == FLIP_BACKWARD ) ) ||
- ( ( i == FLIP_BACKWARD ) && ( m_nLastFlipType == FLIP_FORWARD ) ) )
- {
- flipAct = ACT_INVALID;
- continue;
- }
-
- m_nNumFlips--;
- ResetIdealActivity( flipAct );
- m_flNextFlipTime = gpGlobals->curtime + 2.0f;
- m_nLastFlipType = i;
- break;
- }
- }
-
- if ( flipAct == ACT_INVALID )
- {
- m_nNumFlips = 0;
- m_nLastFlipType = -1;
- m_flNextFlipTime = gpGlobals->curtime + 2.0f;
- TaskFail( "Unable to find flip evasion direction!\n" );
- }
- }
- break;
-
- case TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT:
- {
- assert( GetEnemy() != NULL );
- if ( GetEnemy() == NULL )
- break;
-
- Vector goalPos;
-
- CHintCriteria hint;
-
- // Find a disadvantage node near the player, but away from ourselves
- hint.SetHintType( HINT_TACTICAL_ENEMY_DISADVANTAGED );
- hint.AddExcludePosition( GetAbsOrigin(), 256 );
- hint.AddExcludePosition( GetEnemy()->GetAbsOrigin(), 256 );
-
- if ( ( m_pSquad != NULL ) && ( m_pSquad->NumMembers() > 1 ) )
- {
- AISquadIter_t iter;
- for ( CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
- {
- if ( pSquadMember == NULL )
- continue;
-
- hint.AddExcludePosition( pSquadMember->GetAbsOrigin(), 128 );
- }
- }
-
- hint.SetFlag( bits_HINT_NODE_NEAREST );
-
- CAI_Hint *pHint = CAI_HintManager::FindHint( this, GetEnemy()->GetAbsOrigin(), &hint );
-
- if ( pHint == NULL )
- {
- TaskFail( "Unable to find vantage point!\n" );
- break;
- }
-
- pHint->GetPosition( this, &goalPos );
-
- AI_NavGoal_t goal( goalPos );
-
- //Try to run directly there
- if ( GetNavigator()->SetGoal( goal ) == false )
- {
- TaskFail( "Unable to find path to vantage point!\n" );
- break;
- }
-
- TaskComplete();
- }
- break;
-
- default:
- BaseClass::StartTask( pTask );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//
-//
-//-----------------------------------------------------------------------------
-float CNPC_Assassin::MaxYawSpeed( void )
-{
- switch( GetActivity() )
- {
- case ACT_TURN_LEFT:
- case ACT_TURN_RIGHT:
- return 160;
- break;
- case ACT_RUN:
- return 900;
- break;
- case ACT_RANGE_ATTACK1:
- return 0;
- break;
- default:
- return 60;
- break;
- }
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-void CNPC_Assassin::RunTask( const Task_t *pTask )
-{
- switch( pTask->iTask )
- {
- case TASK_ASSASSIN_EVADE:
-
- AutoMovement();
-
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
-
- break;
-
- default:
- BaseClass::RunTask( pTask );
- break;
- }
-}
-
-
-//---------------------------------------------------------
-//---------------------------------------------------------
-bool CNPC_Assassin::FValidateHintType ( CAI_Hint *pHint )
-{
- switch( pHint->HintType() )
- {
- case HINT_TACTICAL_ENEMY_DISADVANTAGED:
- {
- Vector hintPos;
- pHint->GetPosition( this, &hintPos );
-
- // Verify that we can see the target from that position
- hintPos += GetViewOffset();
-
- trace_t tr;
- UTIL_TraceLine( hintPos, GetEnemy()->BodyTarget( hintPos, true ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
-
- // Check for seeing our target at the new location
- if ( ( tr.fraction == 1.0f ) || ( tr.m_pEnt == GetEnemy() ) )
- return false;
-
- return true;
- break;
- }
-
- default:
- return false;
- break;
- }
-
- return FALSE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : const Vector
-//-----------------------------------------------------------------------------
-const Vector &CNPC_Assassin::GetViewOffset( void )
-{
- static Vector eyeOffset;
-
- //FIXME: Use eye attachment?
- // If we're crouching, offset appropriately
- if ( ( GetActivity() == ACT_ASSASSIN_PERCH ) ||
- ( GetActivity() == ACT_RANGE_ATTACK1 ) )
- {
- eyeOffset = Vector( 0, 0, 24.0f );
- }
- else
- {
- eyeOffset = BaseClass::GetViewOffset();
- }
-
- return eyeOffset;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::OnScheduleChange( void )
-{
- //TODO: Change eye state?
-
- BaseClass::OnScheduleChange();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : state -
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::SetEyeState( eyeState_t state )
-{
- //Must have a valid eye to affect
- if ( ( m_pEyeSprite == NULL ) || ( m_pEyeTrail == NULL ) )
- return;
-
- //Set the state
- switch( state )
- {
- default:
- case ASSASSIN_EYE_SEE_TARGET: //Fade in and scale up
- m_pEyeSprite->SetColor( 255, 0, 0 );
- m_pEyeSprite->SetBrightness( 164, 0.1f );
- m_pEyeSprite->SetScale( 0.4f, 0.1f );
-
- m_pEyeTrail->SetColor( 255, 0, 0 );
- m_pEyeTrail->SetScale( 8.0f );
- m_pEyeTrail->SetBrightness( 164 );
-
- break;
-
- case ASSASSIN_EYE_SEEKING_TARGET: //Ping-pongs
-
- //Toggle our state
- m_bBlinkState = !m_bBlinkState;
- m_pEyeSprite->SetColor( 255, 128, 0 );
-
- if ( m_bBlinkState )
- {
- //Fade up and scale up
- m_pEyeSprite->SetScale( 0.25f, 0.1f );
- m_pEyeSprite->SetBrightness( 164, 0.1f );
- }
- else
- {
- //Fade down and scale down
- m_pEyeSprite->SetScale( 0.2f, 0.1f );
- m_pEyeSprite->SetBrightness( 64, 0.1f );
- }
-
- break;
-
- case ASSASSIN_EYE_DORMANT: //Fade out and scale down
- m_pEyeSprite->SetScale( 0.5f, 0.5f );
- m_pEyeSprite->SetBrightness( 64, 0.5f );
-
- m_pEyeTrail->SetScale( 2.0f );
- m_pEyeTrail->SetBrightness( 64 );
- break;
-
- case ASSASSIN_EYE_DEAD: //Fade out slowly
- m_pEyeSprite->SetColor( 255, 0, 0 );
- m_pEyeSprite->SetScale( 0.1f, 5.0f );
- m_pEyeSprite->SetBrightness( 0, 5.0f );
-
- m_pEyeTrail->SetColor( 255, 0, 0 );
- m_pEyeTrail->SetScale( 0.1f );
- m_pEyeTrail->SetBrightness( 0 );
- break;
-
- case ASSASSIN_EYE_ACTIVE:
- m_pEyeSprite->SetColor( 255, 0, 0 );
- m_pEyeSprite->SetScale( 0.1f );
- m_pEyeSprite->SetBrightness( 0 );
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::GatherEnemyConditions( CBaseEntity *pEnemy )
-{
- ClearCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
-
- BaseClass::GatherEnemyConditions( pEnemy );
-
- // See if we're being targetted specifically
- if ( HasCondition( COND_ENEMY_FACING_ME ) )
- {
- Vector enemyDir = GetAbsOrigin() - pEnemy->GetAbsOrigin();
- VectorNormalize( enemyDir );
-
- Vector enemyBodyDir;
- CBasePlayer *pPlayer = ToBasePlayer( pEnemy );
-
- if ( pPlayer != NULL )
- {
- enemyBodyDir = pPlayer->BodyDirection3D();
- }
- else
- {
- AngleVectors( pEnemy->GetAbsAngles(), &enemyBodyDir );
- }
-
- float enemyDot = DotProduct( enemyBodyDir, enemyDir );
-
- //FIXME: Need to refine this a bit
- if ( enemyDot > 0.97f )
- {
- SetCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::BuildScheduleTestBits( void )
-{
- SetNextThink( gpGlobals->curtime + 0.05 );
-
- //Don't allow any modifications when scripted
- if ( m_NPCState == NPC_STATE_SCRIPT )
- return;
-
- //Become interrupted if we're targetted when shooting an enemy
- if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) )
- {
- SetCustomInterruptCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
- }
-
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &info -
-//-----------------------------------------------------------------------------
-void CNPC_Assassin::Event_Killed( const CTakeDamageInfo &info )
-{
- BaseClass::Event_Killed( info );
-
- // Turn off the eye
- SetEyeState( ASSASSIN_EYE_DEAD );
-
- // Turn off the pistols
- SetBodygroup( 1, 0 );
-
- // Spawn her guns
-}
-
-//-----------------------------------------------------------------------------
-//
-// Schedules
-//
-//-----------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( npc_assassin, CNPC_Assassin )
-
- DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_LEFT)
- DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_RIGHT)
- DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_BACK)
- DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_FORWARD)
- DECLARE_ACTIVITY(ACT_ASSASSIN_PERCH)
-
- //Adrian: events go here
- DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_RIGHT )
- DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_LEFT )
- DECLARE_ANIMEVENT( AE_ASSASIN_KICK_HIT )
-
- DECLARE_TASK(TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT)
- DECLARE_TASK(TASK_ASSASSIN_EVADE)
- DECLARE_TASK(TASK_ASSASSIN_SET_EYE_STATE)
- DECLARE_TASK(TASK_ASSASSIN_LUNGE)
-
- DECLARE_CONDITION(COND_ASSASSIN_ENEMY_TARGETTING_ME)
-
- //=========================================================
- // ASSASSIN_STALK_ENEMY
- //=========================================================
-
- DEFINE_SCHEDULE
- (
- SCHED_ASSASSIN_STALK_ENEMY,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ASSASSIN_PERCH"
- " "
- " Interrupts"
- " COND_ASSASSIN_ENEMY_TARGETTING_ME"
- " COND_SEE_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- )
-
- //=========================================================
- // > ASSASSIN_FIND_VANTAGE_POINT
- //=========================================================
-
- DEFINE_SCHEDULE
- (
- SCHED_ASSASSIN_FIND_VANTAGE_POINT,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
- " TASK_STOP_MOVING 0"
- " TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT 0"
- " TASK_RUN_PATH 0"
- " TASK_WAIT_FOR_MOVEMENT 0"
- " TASK_SET_SCHEDULE SCHEDULE:SCHED_ASSASSIN_STALK_ENEMY"
- " "
- " Interrupts"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_TASK_FAILED"
- )
-
- //=========================================================
- // Assassin needs to avoid the player
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ASSASSIN_EVADE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
- " TASK_STOP_MOVING 0"
- " TASK_ASSASSIN_EVADE 0"
- " "
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
- //=========================================================
- // Assassin needs to avoid the player
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_ASSASSIN_LUNGE,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_ENEMY 0"
- " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ASSASSIN_FLIP_FORWARD"
- " "
- " Interrupts"
- " COND_TASK_FAILED"
- )
-
-AI_END_CUSTOM_NPC()
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "ammodef.h"
+#include "AI_Hint.h"
+#include "AI_Navigator.h"
+#include "npc_Assassin.h"
+#include "game.h"
+#include "npcevent.h"
+#include "engine/IEngineSound.h"
+#include "ai_squad.h"
+#include "AI_SquadSlot.h"
+#include "ai_moveprobe.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar sk_assassin_health( "sk_assassin_health","150");
+ConVar g_debug_assassin( "g_debug_assassin", "0" );
+
+//=========================================================
+// Anim Events
+//=========================================================
+#define ASSASSIN_AE_FIRE_PISTOL_RIGHT 1
+#define ASSASSIN_AE_FIRE_PISTOL_LEFT 2
+#define ASSASSIN_AE_KICK_HIT 3
+
+int AE_ASSASIN_FIRE_PISTOL_RIGHT;
+int AE_ASSASIN_FIRE_PISTOL_LEFT;
+int AE_ASSASIN_KICK_HIT;
+
+//=========================================================
+// Assassin activities
+//=========================================================
+int ACT_ASSASSIN_FLIP_LEFT;
+int ACT_ASSASSIN_FLIP_RIGHT;
+int ACT_ASSASSIN_FLIP_BACK;
+int ACT_ASSASSIN_FLIP_FORWARD;
+int ACT_ASSASSIN_PERCH;
+
+//=========================================================
+// Flip types
+//=========================================================
+enum
+{
+ FLIP_LEFT,
+ FLIP_RIGHT,
+ FLIP_FORWARD,
+ FLIP_BACKWARD,
+ NUM_FLIP_TYPES,
+};
+
+//=========================================================
+// Private conditions
+//=========================================================
+enum Assassin_Conds
+{
+ COND_ASSASSIN_ENEMY_TARGETTING_ME = LAST_SHARED_CONDITION,
+};
+
+//=========================================================
+// Assassin schedules
+//=========================================================
+enum
+{
+ SCHED_ASSASSIN_FIND_VANTAGE_POINT = LAST_SHARED_SCHEDULE,
+ SCHED_ASSASSIN_EVADE,
+ SCHED_ASSASSIN_STALK_ENEMY,
+ SCHED_ASSASSIN_LUNGE,
+};
+
+//=========================================================
+// Assassin tasks
+//=========================================================
+enum
+{
+ TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT = LAST_SHARED_TASK,
+ TASK_ASSASSIN_EVADE,
+ TASK_ASSASSIN_SET_EYE_STATE,
+ TASK_ASSASSIN_LUNGE,
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Class Constructor
+//-----------------------------------------------------------------------------
+CNPC_Assassin::CNPC_Assassin( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+
+LINK_ENTITY_TO_CLASS( npc_assassin, CNPC_Assassin );
+
+#if 0
+//---------------------------------------------------------
+// Custom Client entity
+//---------------------------------------------------------
+IMPLEMENT_SERVERCLASS_ST(CNPC_Assassin, DT_NPC_Assassin)
+END_SEND_TABLE()
+
+#endif
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_Assassin )
+ DEFINE_FIELD( m_nNumFlips, FIELD_INTEGER ),
+ DEFINE_FIELD( m_nLastFlipType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNextFlipTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextLungeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextShotTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bEvade, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bAggressive, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_pEyeSprite, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_pEyeTrail, FIELD_CLASSPTR ),
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::Precache( void )
+{
+ PrecacheModel( "models/fassassin.mdl" );
+
+ PrecacheScriptSound( "NPC_Assassin.ShootPistol" );
+ PrecacheScriptSound( "Zombie.AttackHit" );
+ PrecacheScriptSound( "Assassin.AttackMiss" );
+ PrecacheScriptSound( "NPC_Assassin.Footstep" );
+
+ PrecacheModel( "sprites/redglow1.vmt" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::Spawn( void )
+{
+ Precache();
+
+ SetModel( "models/fassassin.mdl" );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ SetBloodColor( BLOOD_COLOR_RED );
+
+ m_iHealth = sk_assassin_health.GetFloat();
+ m_flFieldOfView = 0.1;
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_GROUND | bits_CAP_MOVE_JUMP );
+ 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 );
+
+ //Turn on our guns
+ SetBodygroup( 1, 1 );
+
+ int attachment = LookupAttachment( "Eye" );
+
+ // Start up the eye glow
+ m_pEyeSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false );
+
+ if ( m_pEyeSprite != NULL )
+ {
+ m_pEyeSprite->SetAttachment( this, attachment );
+ m_pEyeSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 200, kRenderFxNone );
+ m_pEyeSprite->SetScale( 0.25f );
+ }
+
+ // Start up the eye trail
+ m_pEyeTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false );
+
+ if ( m_pEyeTrail != NULL )
+ {
+ m_pEyeTrail->SetAttachment( this, attachment );
+ m_pEyeTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 200, kRenderFxNone );
+ m_pEyeTrail->SetStartWidth( 8.0f );
+ m_pEyeTrail->SetLifeTime( 0.75f );
+ }
+
+ NPCInit();
+
+ m_bEvade = false;
+ m_bAggressive = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if a reasonable jumping distance
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CNPC_Assassin::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
+{
+ const float MAX_JUMP_RISE = 256.0f;
+ const float MAX_JUMP_DISTANCE = 256.0f;
+ const float MAX_JUMP_DROP = 512.0f;
+
+ return BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flDot -
+// flDist -
+// Output : int CNPC_Assassin::MeleeAttack1Conditions
+//-----------------------------------------------------------------------------
+int CNPC_Assassin::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( flDist > 84 )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ if ( flDot < 0.7f )
+ return 0;
+
+ if ( GetEnemy() == NULL )
+ return 0;
+
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flDot -
+// flDist -
+// Output : int CNPC_Assassin::RangeAttack1Conditions
+//-----------------------------------------------------------------------------
+int CNPC_Assassin::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( flDist < 84 )
+ return COND_TOO_CLOSE_TO_ATTACK;
+
+ if ( flDist > 1024 )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ if ( flDot < 0.5f )
+ return COND_NOT_FACING_ATTACK;
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flDot -
+// flDist -
+// Output : int CNPC_Assassin::RangeAttack1Conditions
+//-----------------------------------------------------------------------------
+int CNPC_Assassin::RangeAttack2Conditions ( float flDot, float flDist )
+{
+ if ( m_flNextLungeTime > gpGlobals->curtime )
+ return 0;
+
+ float lungeRange = GetSequenceMoveDist( SelectWeightedSequence( (Activity) ACT_ASSASSIN_FLIP_FORWARD ) );
+
+ if ( flDist < lungeRange * 0.25f )
+ return COND_TOO_CLOSE_TO_ATTACK;
+
+ if ( flDist > lungeRange * 1.5f )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ if ( flDot < 0.75f )
+ return COND_NOT_FACING_ATTACK;
+
+ if ( GetEnemy() == NULL )
+ return 0;
+
+ // Check for a clear path
+ trace_t tr;
+ UTIL_TraceHull( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction == 1.0f || tr.m_pEnt == GetEnemy() )
+ return COND_CAN_RANGE_ATTACK2;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : hand -
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::FirePistol( int hand )
+{
+ if ( m_flNextShotTime > gpGlobals->curtime )
+ return;
+
+ m_flNextShotTime = gpGlobals->curtime + random->RandomFloat( 0.05f, 0.15f );
+
+ Vector muzzlePos;
+ QAngle muzzleAngle;
+
+ const char *handName = ( hand ) ? "LeftMuzzle" : "RightMuzzle";
+
+ GetAttachment( handName, muzzlePos, muzzleAngle );
+
+ Vector muzzleDir;
+
+ if ( GetEnemy() == NULL )
+ {
+ AngleVectors( muzzleAngle, &muzzleDir );
+ }
+ else
+ {
+ muzzleDir = GetEnemy()->BodyTarget( muzzlePos ) - muzzlePos;
+ VectorNormalize( muzzleDir );
+ }
+
+ int bulletType = GetAmmoDef()->Index( "Pistol" );
+
+ FireBullets( 1, muzzlePos, muzzleDir, VECTOR_CONE_5DEGREES, 1024, bulletType, 2 );
+
+ UTIL_MuzzleFlash( muzzlePos, muzzleAngle, 0.5f, 1 );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NPC_Assassin.ShootPistol" );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Assassin::HandleAnimEvent( animevent_t *pEvent )
+{
+
+ if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_RIGHT )
+ {
+ FirePistol( 0 );
+ return;
+ }
+
+ if ( pEvent->event == AE_ASSASIN_FIRE_PISTOL_LEFT )
+ {
+ FirePistol( 1 );
+ return;
+ }
+
+ if ( pEvent->event == AE_ASSASIN_KICK_HIT )
+ {
+ Vector attackDir = BodyDirection2D();
+ Vector attackPos = WorldSpaceCenter() + ( attackDir * 64.0f );
+
+ trace_t tr;
+ UTIL_TraceHull( WorldSpaceCenter(), attackPos, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( ( tr.m_pEnt != NULL ) && ( tr.DidHitWorld() == false ) )
+ {
+ if ( tr.m_pEnt->m_takedamage != DAMAGE_NO )
+ {
+ CTakeDamageInfo info( this, this, 5, DMG_CLUB );
+ CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
+ tr.m_pEnt->TakeDamage( info );
+
+ CBasePlayer *pPlayer = ToBasePlayer( tr.m_pEnt );
+
+ if ( pPlayer != NULL )
+ {
+ //Kick the player angles
+ pPlayer->ViewPunch( QAngle( -30, 40, 10 ) );
+ }
+
+ EmitSound( "Zombie.AttackHit" );
+ //EmitSound( "Assassin.AttackHit" );
+ }
+ }
+ else
+ {
+ EmitSound( "Assassin.AttackMiss" );
+ //EmitSound( "Assassin.AttackMiss" );
+ }
+
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Causes the assassin to prefer to run away, rather than towards her target
+//-----------------------------------------------------------------------------
+bool CNPC_Assassin::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
+{
+ if ( GetEnemy() == NULL )
+ return true;
+
+ float multiplier = 1.0f;
+
+ Vector moveDir = ( vecEnd - vecStart );
+ VectorNormalize( moveDir );
+
+ Vector enemyDir = ( GetEnemy()->GetAbsOrigin() - vecStart );
+ VectorNormalize( enemyDir );
+
+ // If we're moving towards our enemy, then the cost is much higher than normal
+ if ( DotProduct( enemyDir, moveDir ) > 0.5f )
+ {
+ multiplier = 16.0f;
+ }
+
+ *pCost *= multiplier;
+
+ return ( multiplier != 1 );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CNPC_Assassin::SelectSchedule ( void )
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_IDLE:
+ case NPC_STATE_ALERT:
+ {
+ if ( HasCondition ( COND_HEAR_DANGER ) )
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+
+ if ( HasCondition ( COND_HEAR_COMBAT ) )
+ return SCHED_INVESTIGATE_SOUND;
+ }
+ break;
+
+ case NPC_STATE_COMBAT:
+ {
+ // dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+ // Need to move
+ if ( /*( HasCondition( COND_SEE_ENEMY ) && HasCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME ) && random->RandomInt( 0, 32 ) == 0 && m_flNextFlipTime < gpGlobals->curtime ) )*/
+ ( m_nNumFlips > 0 ) ||
+ ( ( HasCondition ( COND_LIGHT_DAMAGE ) && random->RandomInt( 0, 2 ) == 0 ) ) || ( HasCondition ( COND_HEAVY_DAMAGE ) ) )
+ {
+ if ( m_nNumFlips <= 0 )
+ {
+ m_nNumFlips = random->RandomInt( 1, 2 );
+ }
+
+ return SCHED_ASSASSIN_EVADE;
+ }
+
+ // Can kick
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ return SCHED_MELEE_ATTACK1;
+
+ // Can shoot
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ m_flNextLungeTime = gpGlobals->curtime + 2.0f;
+ m_nLastFlipType = FLIP_FORWARD;
+
+ return SCHED_ASSASSIN_LUNGE;
+ }
+
+ // Can shoot
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ return SCHED_RANGE_ATTACK1;
+
+ // Face our enemy
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ return SCHED_COMBAT_FACE;
+
+ // new enemy
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+
+ // ALERT( at_console, "stand\n");
+ return SCHED_ASSASSIN_FIND_VANTAGE_POINT;
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::PrescheduleThink( void )
+{
+ if ( GetActivity() == ACT_RUN || GetActivity() == ACT_WALK)
+ {
+ CPASAttenuationFilter filter( this );
+
+ static int iStep = 0;
+ iStep = ! iStep;
+ if (iStep)
+ {
+ EmitSound( filter, entindex(), "NPC_Assassin.Footstep" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : right -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Assassin::CanFlip( int flipType, Activity &activity, const Vector *avoidPosition )
+{
+ Vector testDir;
+ Activity act = ACT_INVALID;
+
+ switch( flipType )
+ {
+ case FLIP_RIGHT:
+ GetVectors( NULL, &testDir, NULL );
+ act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_RIGHT );
+ break;
+
+ case FLIP_LEFT:
+ GetVectors( NULL, &testDir, NULL );
+ testDir.Negate();
+ act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_LEFT );
+ break;
+
+ case FLIP_FORWARD:
+ GetVectors( &testDir, NULL, NULL );
+ act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_FORWARD );
+ break;
+
+ case FLIP_BACKWARD:
+ GetVectors( &testDir, NULL, NULL );
+ testDir.Negate();
+ act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_BACK );
+ break;
+
+ default:
+ assert(0); //NOTENOTE: Invalid flip type
+ activity = ACT_INVALID;
+ return false;
+ break;
+ }
+
+ // Make sure we don't flip towards our avoidance position/
+ if ( avoidPosition != NULL )
+ {
+ Vector avoidDir = (*avoidPosition) - GetAbsOrigin();
+ VectorNormalize( avoidDir );
+
+ if ( DotProduct( avoidDir, testDir ) > 0.0f )
+ return false;
+ }
+
+ int seq = SelectWeightedSequence( act );
+
+ // Find out the length of this sequence
+ float testDist = GetSequenceMoveDist( seq );
+
+ // Find the resulting end position from the sequence's movement
+ Vector endPos = GetAbsOrigin() + ( testDir * testDist );
+
+ trace_t tr;
+
+ if ( ( flipType != FLIP_BACKWARD ) && ( avoidPosition != NULL ) )
+ {
+ UTIL_TraceLine( (*avoidPosition), endPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction == 1.0f )
+ return false;
+ }
+
+ /*
+ 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 );
+
+ // See if we're hit an obstruction in that direction
+ if ( tr.fraction < 1.0f )
+ {
+ if ( g_debug_assassin.GetBool() )
+ {
+ 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 );
+ }
+
+ return false;
+ }
+
+#define NUM_STEPS 2
+
+ float stepLength = testDist / NUM_STEPS;
+
+ for ( int i = 1; i <= NUM_STEPS; i++ )
+ {
+ endPos = GetAbsOrigin() + ( testDir * (stepLength*i) );
+
+ // Also check for a cliff edge
+ 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 );
+
+ if ( tr.fraction == 1.0f )
+ {
+ if ( g_debug_assassin.GetBool() )
+ {
+ 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 );
+ }
+
+ return false;
+ }
+ }
+
+ if ( g_debug_assassin.GetBool() )
+ {
+ 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 );
+ }
+ */
+
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->TestGroundMove( GetAbsOrigin(), endPos, MASK_NPCSOLID, AITGM_DEFAULT, &moveTrace );
+
+ if ( moveTrace.fStatus != AIMR_OK )
+ return false;
+
+ // Return the activity to use
+ activity = (Activity) act;
+
+ return true;
+}
+
+//---------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------
+void CNPC_Assassin::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_ASSASSIN_SET_EYE_STATE:
+ {
+ SetEyeState( (eyeState_t) ( (int) pTask->flTaskData ) );
+ TaskComplete();
+ }
+ break;
+
+ case TASK_ASSASSIN_EVADE:
+ {
+ Activity flipAct = ACT_INVALID;
+
+ const Vector *avoidPos = ( GetEnemy() != NULL ) ? &(GetEnemy()->GetAbsOrigin()) : NULL;
+
+ for ( int i = FLIP_LEFT; i < NUM_FLIP_TYPES; i++ )
+ {
+ if ( CanFlip( i, flipAct, avoidPos ) )
+ {
+ // Don't flip back to where we just were
+ if ( ( ( i == FLIP_LEFT ) && ( m_nLastFlipType == FLIP_RIGHT ) ) ||
+ ( ( i == FLIP_RIGHT ) && ( m_nLastFlipType == FLIP_LEFT ) ) ||
+ ( ( i == FLIP_FORWARD ) && ( m_nLastFlipType == FLIP_BACKWARD ) ) ||
+ ( ( i == FLIP_BACKWARD ) && ( m_nLastFlipType == FLIP_FORWARD ) ) )
+ {
+ flipAct = ACT_INVALID;
+ continue;
+ }
+
+ m_nNumFlips--;
+ ResetIdealActivity( flipAct );
+ m_flNextFlipTime = gpGlobals->curtime + 2.0f;
+ m_nLastFlipType = i;
+ break;
+ }
+ }
+
+ if ( flipAct == ACT_INVALID )
+ {
+ m_nNumFlips = 0;
+ m_nLastFlipType = -1;
+ m_flNextFlipTime = gpGlobals->curtime + 2.0f;
+ TaskFail( "Unable to find flip evasion direction!\n" );
+ }
+ }
+ break;
+
+ case TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT:
+ {
+ assert( GetEnemy() != NULL );
+ if ( GetEnemy() == NULL )
+ break;
+
+ Vector goalPos;
+
+ CHintCriteria hint;
+
+ // Find a disadvantage node near the player, but away from ourselves
+ hint.SetHintType( HINT_TACTICAL_ENEMY_DISADVANTAGED );
+ hint.AddExcludePosition( GetAbsOrigin(), 256 );
+ hint.AddExcludePosition( GetEnemy()->GetAbsOrigin(), 256 );
+
+ if ( ( m_pSquad != NULL ) && ( m_pSquad->NumMembers() > 1 ) )
+ {
+ AISquadIter_t iter;
+ for ( CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
+ {
+ if ( pSquadMember == NULL )
+ continue;
+
+ hint.AddExcludePosition( pSquadMember->GetAbsOrigin(), 128 );
+ }
+ }
+
+ hint.SetFlag( bits_HINT_NODE_NEAREST );
+
+ CAI_Hint *pHint = CAI_HintManager::FindHint( this, GetEnemy()->GetAbsOrigin(), &hint );
+
+ if ( pHint == NULL )
+ {
+ TaskFail( "Unable to find vantage point!\n" );
+ break;
+ }
+
+ pHint->GetPosition( this, &goalPos );
+
+ AI_NavGoal_t goal( goalPos );
+
+ //Try to run directly there
+ if ( GetNavigator()->SetGoal( goal ) == false )
+ {
+ TaskFail( "Unable to find path to vantage point!\n" );
+ break;
+ }
+
+ TaskComplete();
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+float CNPC_Assassin::MaxYawSpeed( void )
+{
+ switch( GetActivity() )
+ {
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 160;
+ break;
+ case ACT_RUN:
+ return 900;
+ break;
+ case ACT_RANGE_ATTACK1:
+ return 0;
+ break;
+ default:
+ return 60;
+ break;
+ }
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Assassin::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_ASSASSIN_EVADE:
+
+ AutoMovement();
+
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+bool CNPC_Assassin::FValidateHintType ( CAI_Hint *pHint )
+{
+ switch( pHint->HintType() )
+ {
+ case HINT_TACTICAL_ENEMY_DISADVANTAGED:
+ {
+ Vector hintPos;
+ pHint->GetPosition( this, &hintPos );
+
+ // Verify that we can see the target from that position
+ hintPos += GetViewOffset();
+
+ trace_t tr;
+ UTIL_TraceLine( hintPos, GetEnemy()->BodyTarget( hintPos, true ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ // Check for seeing our target at the new location
+ if ( ( tr.fraction == 1.0f ) || ( tr.m_pEnt == GetEnemy() ) )
+ return false;
+
+ return true;
+ break;
+ }
+
+ default:
+ return false;
+ break;
+ }
+
+ return FALSE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const Vector
+//-----------------------------------------------------------------------------
+const Vector &CNPC_Assassin::GetViewOffset( void )
+{
+ static Vector eyeOffset;
+
+ //FIXME: Use eye attachment?
+ // If we're crouching, offset appropriately
+ if ( ( GetActivity() == ACT_ASSASSIN_PERCH ) ||
+ ( GetActivity() == ACT_RANGE_ATTACK1 ) )
+ {
+ eyeOffset = Vector( 0, 0, 24.0f );
+ }
+ else
+ {
+ eyeOffset = BaseClass::GetViewOffset();
+ }
+
+ return eyeOffset;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::OnScheduleChange( void )
+{
+ //TODO: Change eye state?
+
+ BaseClass::OnScheduleChange();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : state -
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::SetEyeState( eyeState_t state )
+{
+ //Must have a valid eye to affect
+ if ( ( m_pEyeSprite == NULL ) || ( m_pEyeTrail == NULL ) )
+ return;
+
+ //Set the state
+ switch( state )
+ {
+ default:
+ case ASSASSIN_EYE_SEE_TARGET: //Fade in and scale up
+ m_pEyeSprite->SetColor( 255, 0, 0 );
+ m_pEyeSprite->SetBrightness( 164, 0.1f );
+ m_pEyeSprite->SetScale( 0.4f, 0.1f );
+
+ m_pEyeTrail->SetColor( 255, 0, 0 );
+ m_pEyeTrail->SetScale( 8.0f );
+ m_pEyeTrail->SetBrightness( 164 );
+
+ break;
+
+ case ASSASSIN_EYE_SEEKING_TARGET: //Ping-pongs
+
+ //Toggle our state
+ m_bBlinkState = !m_bBlinkState;
+ m_pEyeSprite->SetColor( 255, 128, 0 );
+
+ if ( m_bBlinkState )
+ {
+ //Fade up and scale up
+ m_pEyeSprite->SetScale( 0.25f, 0.1f );
+ m_pEyeSprite->SetBrightness( 164, 0.1f );
+ }
+ else
+ {
+ //Fade down and scale down
+ m_pEyeSprite->SetScale( 0.2f, 0.1f );
+ m_pEyeSprite->SetBrightness( 64, 0.1f );
+ }
+
+ break;
+
+ case ASSASSIN_EYE_DORMANT: //Fade out and scale down
+ m_pEyeSprite->SetScale( 0.5f, 0.5f );
+ m_pEyeSprite->SetBrightness( 64, 0.5f );
+
+ m_pEyeTrail->SetScale( 2.0f );
+ m_pEyeTrail->SetBrightness( 64 );
+ break;
+
+ case ASSASSIN_EYE_DEAD: //Fade out slowly
+ m_pEyeSprite->SetColor( 255, 0, 0 );
+ m_pEyeSprite->SetScale( 0.1f, 5.0f );
+ m_pEyeSprite->SetBrightness( 0, 5.0f );
+
+ m_pEyeTrail->SetColor( 255, 0, 0 );
+ m_pEyeTrail->SetScale( 0.1f );
+ m_pEyeTrail->SetBrightness( 0 );
+ break;
+
+ case ASSASSIN_EYE_ACTIVE:
+ m_pEyeSprite->SetColor( 255, 0, 0 );
+ m_pEyeSprite->SetScale( 0.1f );
+ m_pEyeSprite->SetBrightness( 0 );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::GatherEnemyConditions( CBaseEntity *pEnemy )
+{
+ ClearCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
+
+ BaseClass::GatherEnemyConditions( pEnemy );
+
+ // See if we're being targetted specifically
+ if ( HasCondition( COND_ENEMY_FACING_ME ) )
+ {
+ Vector enemyDir = GetAbsOrigin() - pEnemy->GetAbsOrigin();
+ VectorNormalize( enemyDir );
+
+ Vector enemyBodyDir;
+ CBasePlayer *pPlayer = ToBasePlayer( pEnemy );
+
+ if ( pPlayer != NULL )
+ {
+ enemyBodyDir = pPlayer->BodyDirection3D();
+ }
+ else
+ {
+ AngleVectors( pEnemy->GetAbsAngles(), &enemyBodyDir );
+ }
+
+ float enemyDot = DotProduct( enemyBodyDir, enemyDir );
+
+ //FIXME: Need to refine this a bit
+ if ( enemyDot > 0.97f )
+ {
+ SetCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::BuildScheduleTestBits( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.05 );
+
+ //Don't allow any modifications when scripted
+ if ( m_NPCState == NPC_STATE_SCRIPT )
+ return;
+
+ //Become interrupted if we're targetted when shooting an enemy
+ if ( IsCurSchedule( SCHED_RANGE_ATTACK1 ) )
+ {
+ SetCustomInterruptCondition( COND_ASSASSIN_ENEMY_TARGETTING_ME );
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+//-----------------------------------------------------------------------------
+void CNPC_Assassin::Event_Killed( const CTakeDamageInfo &info )
+{
+ BaseClass::Event_Killed( info );
+
+ // Turn off the eye
+ SetEyeState( ASSASSIN_EYE_DEAD );
+
+ // Turn off the pistols
+ SetBodygroup( 1, 0 );
+
+ // Spawn her guns
+}
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_assassin, CNPC_Assassin )
+
+ DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_LEFT)
+ DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_RIGHT)
+ DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_BACK)
+ DECLARE_ACTIVITY(ACT_ASSASSIN_FLIP_FORWARD)
+ DECLARE_ACTIVITY(ACT_ASSASSIN_PERCH)
+
+ //Adrian: events go here
+ DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_RIGHT )
+ DECLARE_ANIMEVENT( AE_ASSASIN_FIRE_PISTOL_LEFT )
+ DECLARE_ANIMEVENT( AE_ASSASIN_KICK_HIT )
+
+ DECLARE_TASK(TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT)
+ DECLARE_TASK(TASK_ASSASSIN_EVADE)
+ DECLARE_TASK(TASK_ASSASSIN_SET_EYE_STATE)
+ DECLARE_TASK(TASK_ASSASSIN_LUNGE)
+
+ DECLARE_CONDITION(COND_ASSASSIN_ENEMY_TARGETTING_ME)
+
+ //=========================================================
+ // ASSASSIN_STALK_ENEMY
+ //=========================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_STALK_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ASSASSIN_PERCH"
+ " "
+ " Interrupts"
+ " COND_ASSASSIN_ENEMY_TARGETTING_ME"
+ " COND_SEE_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > ASSASSIN_FIND_VANTAGE_POINT
+ //=========================================================
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_FIND_VANTAGE_POINT,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
+ " TASK_STOP_MOVING 0"
+ " TASK_ASSASSIN_GET_PATH_TO_VANTAGE_POINT 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ASSASSIN_STALK_ENEMY"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_TASK_FAILED"
+ )
+
+ //=========================================================
+ // Assassin needs to avoid the player
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_EVADE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
+ " TASK_STOP_MOVING 0"
+ " TASK_ASSASSIN_EVADE 0"
+ " "
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+ //=========================================================
+ // Assassin needs to avoid the player
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_LUNGE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_FIND_VANTAGE_POINT"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ASSASSIN_FLIP_FORWARD"
+ " "
+ " Interrupts"
+ " COND_TASK_FAILED"
+ )
+
+AI_END_CUSTOM_NPC()