diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hl2/npc_assassin.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-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.cpp | 2202 |
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() |