From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/hl2/npc_assassin.cpp | 1101 +++++++++++++++++++++++++++++++ 1 file changed, 1101 insertions(+) create mode 100644 mp/src/game/server/hl2/npc_assassin.cpp (limited to 'mp/src/game/server/hl2/npc_assassin.cpp') 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() -- cgit v1.2.3