Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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

//========= 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()