aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_assassin.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_assassin.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_assassin.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_assassin.cpp1101
1 files changed, 1101 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_assassin.cpp b/mp/src/game/server/hl2/npc_assassin.cpp
new file mode 100644
index 00000000..7b755054
--- /dev/null
+++ b/mp/src/game/server/hl2/npc_assassin.cpp
@@ -0,0 +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()