aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_vortigaunt_episodic.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mp/src/game/server/hl2/npc_vortigaunt_episodic.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_vortigaunt_episodic.cpp6820
1 files changed, 3410 insertions, 3410 deletions
diff --git a/mp/src/game/server/hl2/npc_vortigaunt_episodic.cpp b/mp/src/game/server/hl2/npc_vortigaunt_episodic.cpp
index d1bc6a94..3337a65b 100644
--- a/mp/src/game/server/hl2/npc_vortigaunt_episodic.cpp
+++ b/mp/src/game/server/hl2/npc_vortigaunt_episodic.cpp
@@ -1,3410 +1,3410 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose: Vortigaunt - now much friendlier!
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "beam_shared.h"
-#include "globalstate.h"
-#include "npcevent.h"
-#include "Sprite.h"
-#include "IEffects.h"
-#include "te_effect_dispatch.h"
-#include "SoundEmitterSystem/isoundemittersystembase.h"
-#include "physics_prop_ragdoll.h"
-#include "RagdollBoogie.h"
-#include "ai_squadslot.h"
-#include "npc_antlion.h"
-#include "particle_parse.h"
-#include "particle_system.h"
-#include "ai_senses.h"
-
-#include "npc_vortigaunt_episodic.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-#define HAND_LEFT 0
-#define HAND_RIGHT 1
-#define HAND_BOTH 2
-
-class CVortigauntChargeToken;
-
-#define VORTIGAUNT_LIMP_HEALTH 20
-#define VORTIGAUNT_SENTENCE_VOLUME (float)0.35 // volume of vortigaunt sentences
-#define VORTIGAUNT_VOL 0.35 // volume of vortigaunt sounds
-#define VORTIGAUNT_ATTN ATTN_NORM // attenutation of vortigaunt sentences
-#define VORTIGAUNT_HEAL_RECHARGE 30.0 // How long to rest between heals
-#define VORTIGAUNT_ZAP_GLOWGROW_TIME 0.5 // How long does glow last
-#define VORTIGAUNT_HEAL_GLOWGROW_TIME 1.4 // How long does glow last
-#define VORTIGAUNT_GLOWFADE_TIME 0.5 // How long does it fade
-#define VORTIGAUNT_CURE_LIFESPAN 8.0 // cure tokens only live this long (so they don't get stuck on geometry)
-
-#define VORTIGAUNT_BLUE_FADE_TIME 2.25f // takes this long to fade from green to blue or back
-
-#define VORTIGAUNT_FEAR_ZOMBIE_DIST_SQR Square(60) // back away from zombies inside this radius
-
-#define PLAYER_CRITICAL_HEALTH_PERC 0.15f
-
-#define TLK_SQUISHED_GRUB "TLK_SQUISHED_GRUB" // response rule for vortigaunts when they step on a grub
-
-static const char *VORTIGAUNT_LEFT_CLAW = "leftclaw";
-static const char *VORTIGAUNT_RIGHT_CLAW = "rightclaw";
-
-static const char *VORT_CURE = "VORT_CURE";
-static const char *VORT_CURESTOP = "VORT_CURESTOP";
-static const char *VORT_CURE_INTERRUPT = "VORT_CURE_INTERRUPT";
-static const char *VORT_ATTACK = "VORT_ATTACK";
-static const char *VORT_MAD = "VORT_MAD";
-static const char *VORT_SHOT = "VORT_SHOT";
-static const char *VORT_PAIN = "VORT_PAIN";
-static const char *VORT_DIE = "VORT_DIE";
-static const char *VORT_KILL = "VORT_KILL";
-static const char *VORT_LINE_FIRE = "VORT_LINE_FIRE";
-static const char *VORT_POK = "VORT_POK";
-static const char *VORT_EXTRACT_START = "VORT_EXTRACT_START";
-static const char *VORT_EXTRACT_FINISH = "VORT_EXTRACT_FINISH";
-
-// Target must be within this range to heal
-#define HEAL_RANGE (40*12) //ft
-#define HEAL_SEARCH_RANGE (40*12) //ft
-
-ConVar sk_vortigaunt_armor_charge( "sk_vortigaunt_armor_charge","30");
-ConVar sk_vortigaunt_armor_charge_per_token( "sk_vortigaunt_armor_charge_per_token","5");
-
-ConVar sk_vortigaunt_health( "sk_vortigaunt_health","0");
-ConVar sk_vortigaunt_dmg_claw( "sk_vortigaunt_dmg_claw","0");
-ConVar sk_vortigaunt_dmg_rake( "sk_vortigaunt_dmg_rake","0");
-ConVar sk_vortigaunt_dmg_zap( "sk_vortigaunt_dmg_zap","0");
-ConVar sk_vortigaunt_zap_range( "sk_vortigaunt_zap_range", "100", FCVAR_NONE, "Range of vortigaunt's ranged attack (feet)" );
-ConVar sk_vortigaunt_vital_antlion_worker_dmg("sk_vortigaunt_vital_antlion_worker_dmg", "0.2", FCVAR_NONE, "Vital-ally vortigaunts scale damage taken from antlion workers by this amount." );
-
-ConVar g_debug_vortigaunt_aim( "g_debug_vortigaunt_aim", "0" );
-
-// FIXME: Move to shared code!
-#define VORTFX_ZAPBEAM 0 // Beam that damages the target
-#define VORTFX_ARMBEAM 1 // Smaller beams from the hands as we charge up
-
-//-----------------------------------------------------------------------------
-// Interactions
-//-----------------------------------------------------------------------------
-int g_interactionVortigauntStomp = 0;
-int g_interactionVortigauntStompFail = 0;
-int g_interactionVortigauntStompHit = 0;
-int g_interactionVortigauntKick = 0;
-int g_interactionVortigauntClaw = 0;
-
-//=========================================================
-// Vortigaunt activities
-//=========================================================
-int ACT_VORTIGAUNT_AIM;
-int ACT_VORTIGAUNT_START_HEAL;
-int ACT_VORTIGAUNT_HEAL_LOOP;
-int ACT_VORTIGAUNT_END_HEAL;
-int ACT_VORTIGAUNT_TO_ACTION;
-int ACT_VORTIGAUNT_TO_IDLE;
-int ACT_VORTIGAUNT_HEAL; // Heal gesture
-int ACT_VORTIGAUNT_DISPEL;
-int ACT_VORTIGAUNT_ANTLION_THROW;
-
-//=========================================================
-// Monster's Anim Events Go Here
-//=========================================================
-int AE_VORTIGAUNT_CLAW_LEFT;
-int AE_VORTIGAUNT_CLAW_RIGHT;
-int AE_VORTIGAUNT_ZAP_POWERUP;
-int AE_VORTIGAUNT_ZAP_SHOOT;
-int AE_VORTIGAUNT_ZAP_DONE;
-int AE_VORTIGAUNT_HEAL_STARTGLOW;
-int AE_VORTIGAUNT_HEAL_STARTBEAMS;
-int AE_VORTIGAUNT_HEAL_STARTSOUND;
-int AE_VORTIGAUNT_SWING_SOUND;
-int AE_VORTIGAUNT_SHOOT_SOUNDSTART;
-int AE_VORTIGAUNT_HEAL_PAUSE;
-
-int AE_VORTIGAUNT_START_DISPEL; // Start the warm-up
-int AE_VORTIGAUNT_ACCEL_DISPEL; // Indicates we're ramping up
-int AE_VORTIGAUNT_DISPEL; // Actual blast
-
-int AE_VORTIGAUNT_START_HURT_GLOW; // Start the hurt handglow: 0=left, 1=right
-int AE_VORTIGAUNT_STOP_HURT_GLOW; // Turn off the hurt handglow: 0=left, 1=right
-
-int AE_VORTIGAUNT_START_HEAL_GLOW; // 0 - Left, 1 - Right
-int AE_VORTIGAUNT_STOP_HEAL_GLOW; // '
-
-//-----------------------------------------------------------------------------
-// Squad slots
-//-----------------------------------------------------------------------------
-enum SquadSlot_T
-{
- SQUAD_SLOT_HEAL_PLAYER = LAST_SHARED_SQUADSLOT,
-};
-
-//---------------------------------------------------------
-// Save/Restore
-//---------------------------------------------------------
-BEGIN_DATADESC( CNPC_Vortigaunt )
-
- DEFINE_FIELD( m_eHealState, FIELD_INTEGER ),
- DEFINE_FIELD( m_flNextHealTokenTime, FIELD_TIME ),
- DEFINE_ARRAY( m_hHandEffect, FIELD_EHANDLE, 2 ),
- DEFINE_FIELD( m_flNextHealTime, FIELD_TIME ),
- DEFINE_FIELD( m_bPlayerRequestedHeal, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flDispelTestTime, FIELD_TIME ),
- DEFINE_FIELD( m_flHealHinderedTime, FIELD_FLOAT ),
- DEFINE_FIELD( m_nLightningSprite, FIELD_INTEGER),
- DEFINE_FIELD( m_fGlowAge, FIELD_FLOAT),
- DEFINE_FIELD( m_fGlowChangeTime, FIELD_TIME),
- DEFINE_FIELD( m_bGlowTurningOn, FIELD_BOOLEAN),
- DEFINE_FIELD( m_nCurGlowIndex, FIELD_INTEGER),
- DEFINE_FIELD( m_flNextHealTime, FIELD_TIME),
- DEFINE_FIELD( m_flPainTime, FIELD_TIME),
- DEFINE_FIELD( m_nextLineFireTime, FIELD_TIME),
- DEFINE_KEYFIELD( m_bArmorRechargeEnabled,FIELD_BOOLEAN, "ArmorRechargeEnabled" ),
- DEFINE_FIELD( m_bForceArmorRecharge, FIELD_BOOLEAN),
- DEFINE_FIELD( m_bExtractingBugbait, FIELD_BOOLEAN),
- DEFINE_FIELD( m_iLeftHandAttachment, FIELD_INTEGER ),
- DEFINE_FIELD( m_iRightHandAttachment, FIELD_INTEGER ),
- DEFINE_FIELD( m_hHealTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flBlueEndFadeTime, FIELD_TIME ),
- DEFINE_FIELD( m_bIsBlue, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_bIsBlack, FIELD_BOOLEAN ),
- DEFINE_FIELD( m_flAimDelay, FIELD_TIME ),
- DEFINE_FIELD( m_bCarryingNPC, FIELD_BOOLEAN ),
- DEFINE_KEYFIELD( m_bRegenerateHealth, FIELD_BOOLEAN, "HealthRegenerateEnabled" ),
-
- // m_AssaultBehavior (auto saved by AI)
- // m_LeadBehavior
- // DEFINE_FIELD( m_bStopLoopingSounds, FIELD_BOOLEAN ),
-
- // Function Pointers
- DEFINE_USEFUNC( Use ),
-
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableArmorRecharge", InputEnableArmorRecharge ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableArmorRecharge", InputDisableArmorRecharge ),
- DEFINE_INPUTFUNC( FIELD_STRING, "ChargeTarget", InputChargeTarget ),
- DEFINE_INPUTFUNC( FIELD_STRING, "ExtractBugbait", InputExtractBugbait ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EnableHealthRegeneration", InputEnableHealthRegeneration ),
- DEFINE_INPUTFUNC( FIELD_VOID, "DisableHealthRegeneration",InputDisableHealthRegeneration ),
- DEFINE_INPUTFUNC( FIELD_VOID, "Dispel", InputDispel ),
- DEFINE_INPUTFUNC( FIELD_VOID, "BeginCarryNPC", InputBeginCarryNPC ),
- DEFINE_INPUTFUNC( FIELD_VOID, "EndCarryNPC", InputEndCarryNPC ),
-
- DEFINE_INPUTFUNC( FIELD_BOOLEAN, "TurnBlue", InputTurnBlue ),
- DEFINE_INPUTFUNC( FIELD_BOOLEAN, "TurnBlack", InputTurnBlack ),
-
- // Outputs
- DEFINE_OUTPUT(m_OnFinishedExtractingBugbait, "OnFinishedExtractingBugbait"),
- DEFINE_OUTPUT(m_OnFinishedChargingTarget, "OnFinishedChargingTarget"),
- DEFINE_OUTPUT(m_OnPlayerUse, "OnPlayerUse" ),
-
-END_DATADESC()
-
-LINK_ENTITY_TO_CLASS( npc_vortigaunt, CNPC_Vortigaunt );
-
-IMPLEMENT_SERVERCLASS_ST( CNPC_Vortigaunt, DT_NPC_Vortigaunt )
- SendPropTime( SENDINFO (m_flBlueEndFadeTime ) ),
- SendPropBool( SENDINFO( m_bIsBlue )),
- SendPropBool( SENDINFO ( m_bIsBlack ) ),
-END_SEND_TABLE()
-
-// for special behavior with rollermines
-static bool IsRoller( CBaseEntity *pRoller )
-{
- return FClassnameIs( pRoller, "npc_rollermine" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-CNPC_Vortigaunt::CNPC_Vortigaunt( void ) :
-m_bPlayerRequestedHeal( false ),
-m_flNextHealTime( 3.0f ), // Let the player settle before we decide to do this
-m_nNumTokensToSpawn( 0 ),
-m_flAimDelay( 0.0f ),
-m_eHealState( HEAL_STATE_NONE )
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determines whether or not the player is below a certain percentage
-// of their maximum health
-// Input : flPerc - Percentage to test against
-// Output : Returns true if less than supplied parameter
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::PlayerBelowHealthPercentage( CBasePlayer *pPlayer, float flPerc )
-{
- if ( pPlayer == NULL )
- return false;
-
- if ( pPlayer->ArmorValue() )
- return false;
-
- float flMaxHealth = pPlayer->GetMaxHealth();
- if ( flMaxHealth == 0.0f )
- return false;
-
- float flHealthPerc = (flMaxHealth != 0 ) ? (float) pPlayer->GetHealth() / flMaxHealth : 0.0f;
- return ( flHealthPerc <= flPerc );
-}
-
-#define VORT_START_EXTRACT_SENTENCE 500
-#define VORT_FINISH_EXTRACT_SENTENCE 501
-
-//------------------------------------------------------------------------------
-// Purpose: Make the vort speak a line
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::SpeakSentence( int sentenceType )
-{
- if (sentenceType == VORT_START_EXTRACT_SENTENCE)
- {
- Speak( VORT_EXTRACT_START );
- }
- else if (sentenceType == VORT_FINISH_EXTRACT_SENTENCE)
- {
- Speak( VORT_EXTRACT_FINISH );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::StartTask( const Task_t *pTask )
-{
- switch ( pTask->iTask)
- {
-
- case TASK_ANNOUNCE_ATTACK:
- {
- // We override this to add our innate weapon
- if ( m_AnnounceAttackTimer.Expired() )
- {
- if ( SpeakIfAllowed( TLK_ATTACKING, "attacking_with_weapon:zap" ) )
- {
- m_AnnounceAttackTimer.Set( 10, 30 );
- }
- }
-
- BaseClass::StartTask( pTask );
- break;
- }
-
- // Sets our target to the entity that we cached earlier.
- case TASK_VORTIGAUNT_GET_HEAL_TARGET:
- {
- if ( m_hHealTarget == NULL )
- {
- TaskFail( FAIL_NO_TARGET );
- }
- else
- {
- SetTarget( m_hHealTarget );
- TaskComplete();
- }
-
- break;
- }
-
- case TASK_VORTIGAUNT_EXTRACT_WARMUP:
- {
- ResetIdealActivity( (Activity) ACT_VORTIGAUNT_TO_ACTION );
- break;
- }
-
- case TASK_VORTIGAUNT_EXTRACT:
- {
- SetActivity( (Activity) ACT_RANGE_ATTACK1 );
- break;
- }
-
- case TASK_VORTIGAUNT_EXTRACT_COOLDOWN:
- {
- ResetIdealActivity( (Activity)ACT_VORTIGAUNT_TO_IDLE );
- break;
- }
-
- case TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT:
- {
- // Cheat, and fire both outputs
- m_OnFinishedExtractingBugbait.FireOutput( this, this );
- TaskComplete();
- break;
- }
-
- case TASK_VORTIGAUNT_HEAL:
- {
- // Start the layer up and give it a higher priority than normal
- int nLayer = AddGesture( (Activity) ACT_VORTIGAUNT_HEAL );
- SetLayerPriority( nLayer, 1.0f );
-
- m_eHealState = HEAL_STATE_WARMUP;
-
- CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
- if ( pPlayer == NULL )
- {
- TaskFail( "NULL Player in heal schedule!\n" );
- return;
- }
-
- // Figure out how many tokens to spawn
- float flArmorDelta = (float) sk_vortigaunt_armor_charge.GetInt() - pPlayer->ArmorValue();
- m_nNumTokensToSpawn = ceil( flArmorDelta / sk_vortigaunt_armor_charge_per_token.GetInt() );
-
- // If we're forced to recharge, then at least send one
- if ( m_bForceArmorRecharge && m_nNumTokensToSpawn <= 0 )
- m_nNumTokensToSpawn = 1;
-
- TaskComplete();
- break;
- }
-
- case TASK_VORTIGAUNT_WAIT_FOR_PLAYER:
- {
- // Wait for the player to get near (before starting the bugbait sequence)
- break;
- }
-
- case TASK_VORTIGAUNT_DISPEL_ANTLIONS:
- {
- if ( IsOkToCombatSpeak() )
- {
- Speak( TLK_VORTIGAUNT_DISPEL );
- }
-
- ResetIdealActivity( (Activity) ACT_VORTIGAUNT_DISPEL );
- break;
- }
-
- default:
- {
- BaseClass::StartTask( pTask );
- break;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::RunTask( const Task_t *pTask )
-{
- switch ( pTask->iTask )
- {
- case TASK_RANGE_ATTACK1:
- {
- // If our enemy is gone, dead or out of sight, pick a new one (only if we're not delaying this behavior)
- if ( ( HasCondition( COND_ENEMY_OCCLUDED ) ||
- GetEnemy() == NULL ||
- GetEnemy()->IsAlive() == false ) &&
- m_flAimDelay < gpGlobals->curtime )
- {
- CBaseEntity *pNewEnemy = BestEnemy();
- if ( pNewEnemy != NULL )
- {
- SetEnemy( pNewEnemy );
- SetState( NPC_STATE_COMBAT );
- }
- }
-
- BaseClass::RunTask( pTask );
- break;
- }
-
- case TASK_VORTIGAUNT_EXTRACT_WARMUP:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_VORTIGAUNT_EXTRACT:
- {
- if ( IsActivityFinished() )
- {
- TaskComplete();
- }
- break;
- }
-
- case TASK_VORTIGAUNT_EXTRACT_COOLDOWN:
- {
- if ( IsActivityFinished() )
- {
- m_bExtractingBugbait = false;
- TaskComplete();
- }
- break;
- }
-
- case TASK_VORTIGAUNT_WAIT_FOR_PLAYER:
- {
- // Wait for the player to get near (before starting the bugbait sequence)
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer != NULL )
- {
- GetMotor()->SetIdealYawToTargetAndUpdate( pPlayer->GetAbsOrigin(), AI_KEEP_YAW_SPEED );
- SetTurnActivity();
- if ( GetMotor()->DeltaIdealYaw() < 10 )
- {
- // Wait for the player to get close enough
- if ( ( GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr() < Square(32*12) )
- {
- TaskComplete();
- }
- }
- }
- else
- {
- TaskFail( FAIL_NO_PLAYER );
- }
- break;
- }
-
- case TASK_VORTIGAUNT_DISPEL_ANTLIONS:
- {
- if ( IsSequenceFinished() )
- {
- TaskComplete();
- }
-
- break;
- }
-
- default:
- {
- BaseClass::RunTask( pTask );
- break;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::AlertSound( void )
-{
- if ( GetEnemy() != NULL && IsOkToCombatSpeak() )
- {
- Speak( VORT_ATTACK );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Allows each sequence to have a different turn rate associated with it.
-// Output : float CNPC_Vortigaunt::MaxYawSpeed
-//-----------------------------------------------------------------------------
-float CNPC_Vortigaunt::MaxYawSpeed ( void )
-{
- switch ( GetActivity() )
- {
- case ACT_IDLE:
- return 35;
- break;
- case ACT_WALK:
- return 35;
- break;
- case ACT_RUN:
- return 45;
- break;
- default:
- return 35;
- break;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Normal facing position is the eyes, but with the vort eyes on such a
-// long swing arm, this causes stability issues when an npc is trying to
-// face a vort that's also turning their head
-// Output :
-//-----------------------------------------------------------------------------
-Vector CNPC_Vortigaunt::FacingPosition( void )
-{
- return WorldSpaceCenter();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Normal body target is the mid-point between the center and the eyes, but
-// the vort's eyes are so far offset, that this is usually in the middle of
-// empty space
-// Output :
-//-----------------------------------------------------------------------------
-
-Vector CNPC_Vortigaunt::BodyTarget( const Vector &posSrc, bool bNoisy )
-{
- Vector low = WorldSpaceCenter() - ( WorldSpaceCenter() - GetAbsOrigin() ) * .25;
-
- Vector high;
- int iBone = LookupBone( "ValveBiped.neck1" );
- if (iBone >= 0)
- {
- QAngle angHigh;
- GetBonePosition( iBone, high, angHigh );
- }
- else
- {
- high = WorldSpaceCenter();
- }
-
- Vector delta = high - low;
- Vector result;
- if ( bNoisy )
- {
- // bell curve
- float rand1 = random->RandomFloat( 0.0, 0.5 );
- float rand2 = random->RandomFloat( 0.0, 0.5 );
- result = low + delta * rand1 + delta * rand2;
- }
- else
- result = low + delta * 0.5;
-
- return result;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Try a more predictive approach
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
-{
- // Try and figure out a rough idea of where we'll be after a certain time delta and base our
- // conditions on that instead. This is necessary because the vortigaunt takes a long time to
- // deliver his attack and looks very strange if he starts to attack when he'd never be able to hit
- // due to movement.
-
- const float flTimeDelta = 0.5f;
- Vector vecNewOwnerPos;
- Vector vecNewTargetPos;
- UTIL_PredictedPosition( this, flTimeDelta, &vecNewOwnerPos );
- UTIL_PredictedPosition( GetEnemy(), flTimeDelta, &vecNewTargetPos );
-
- Vector vecDelta = vecNewTargetPos - GetEnemy()->GetAbsOrigin();
- Vector vecFinalTargetPos = GetEnemy()->BodyTarget( vecNewOwnerPos ) + vecDelta;
-
- // Debug data
- /*
- NDebugOverlay::Box( GetEnemy()->BodyTarget( vecNewOwnerPos ), -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, 0, 3.0f );
- NDebugOverlay::Box( vecFinalTargetPos, -Vector(4,4,4), Vector(4,4,4), 255, 255, 0, 0, 3.0f );
- NDebugOverlay::HorzArrow( GetEnemy()->BodyTarget( vecNewOwnerPos ), vecFinalTargetPos, 12.0f, 255, 0, 0, 16.0f, true, 3.0f );
- NDebugOverlay::HorzArrow( vecNewOwnerPos, GetEnemy()->BodyTarget( vecNewOwnerPos ), 8.0f, 255, 255, 0, 32.0f, true, 3.0f );
- */
-
- return BaseClass::InnateWeaponLOSCondition( vecNewOwnerPos, vecFinalTargetPos, bSetConditions );
-}
-
-//------------------------------------------------------------------------------
-// Purpose : For innate range attack
-//------------------------------------------------------------------------------
-int CNPC_Vortigaunt::RangeAttack1Conditions( float flDot, float flDist )
-{
- if ( GetEnemy() == NULL )
- return COND_NONE;
-
- if ( gpGlobals->curtime < m_flNextAttack )
- return COND_NONE;
-
- // Don't do shooting while playing a scene
- if ( IsCurSchedule( SCHED_SCENE_GENERIC ) )
- return COND_NONE;
-
- // dvs: Allow up-close range attacks for episodic as the vort's melee
- // attack is rather ineffective.
-#ifndef HL2_EPISODIC
- if ( flDist <= 70 )
- {
- return( COND_TOO_CLOSE_TO_ATTACK );
- }
- else
-#else
- if ( flDist < 32.0f )
- return COND_TOO_CLOSE_TO_ATTACK;
-#endif // HL2_EPISODIC
- if ( flDist > InnateRange1MaxRange() )
- {
- return( COND_TOO_FAR_TO_ATTACK );
- }
- else if ( flDot < 0.65 )
- {
- return( COND_NOT_FACING_ATTACK );
- }
-
-#ifdef HL2_EPISODIC
-
- // Do an extra check for workers near myself or the player
- if ( IsAntlionWorker( GetEnemy() ) )
- {
- // See if it's too close to me
- if ( ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr() < Square( AntlionWorkerBurstRadius() ) )
- return COND_TOO_CLOSE_TO_ATTACK;
-
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer && ( pPlayer->GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr() < Square( AntlionWorkerBurstRadius() ) )
- {
- // Warn the player to get away!
- CFmtStrN<128> modifiers( "antlion_worker:true" );
- SpeakIfAllowed( TLK_DANGER, modifiers );
- return COND_NONE;
- }
- }
-
-#endif // HL2_EPISODIC
-
- return COND_CAN_RANGE_ATTACK1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Test for close-up dispel
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::MeleeAttack1Conditions( float flDot, float flDist )
-{
- if ( m_flDispelTestTime > gpGlobals->curtime )
- return COND_NONE;
-
- m_flDispelTestTime = gpGlobals->curtime + 1.0f;
-
- if ( GetEnemy() && GetEnemy()->Classify() == CLASS_ANTLION )
- {
- if ( NumAntlionsInRadius(128) > 3 )
- {
- m_flDispelTestTime = gpGlobals->curtime + 15.0f;
- return COND_VORTIGAUNT_DISPEL_ANTLIONS;
- }
- }
-
- return COND_NONE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flRadius -
-// Output : int
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::NumAntlionsInRadius( float flRadius )
-{
- CBaseEntity *sEnemySearch[16];
- int nNumAntlions = 0;
- int nNumEnemies = UTIL_EntitiesInBox( sEnemySearch, ARRAYSIZE(sEnemySearch), GetAbsOrigin()-Vector(flRadius,flRadius,flRadius), GetAbsOrigin()+Vector(flRadius,flRadius,flRadius), FL_NPC );
- for ( int i = 0; i < nNumEnemies; i++ )
- {
- // We only care about antlions
- if ( sEnemySearch[i] == NULL || sEnemySearch[i]->Classify() != CLASS_ANTLION )
- continue;
-
- nNumAntlions++;
- }
-
- return nNumAntlions;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Used for a more powerful, concussive blast
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::RangeAttack2Conditions( float flDot, float flDist )
-{
- return COND_NONE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::HandleAnimEvent( animevent_t *pEvent )
-{
- // Start our heal glows (choreo driven)
- if ( pEvent->event == AE_VORTIGAUNT_START_HEAL_GLOW )
- {
- StartHandGlow( VORTIGAUNT_BEAM_HEAL, atoi( pEvent->options ) );
- return;
- }
-
- // Stop our heal glows (choreo driven)
- if ( pEvent->event == AE_VORTIGAUNT_STOP_HEAL_GLOW )
- {
- EndHandGlow();
- return;
- }
-
- // Start our hurt glows (choreo driven)
- if ( pEvent->event == AE_VORTIGAUNT_START_HURT_GLOW )
- {
- StartHandGlow( VORTIGAUNT_BEAM_DISPEL, atoi( pEvent->options ) );
- return;
- }
-
- // Stop our hurt glows (choreo driven)
- if ( pEvent->event == AE_VORTIGAUNT_STOP_HURT_GLOW )
- {
- EndHandGlow();
- return;
- }
-
- // Start our dispel effect
- if ( pEvent->event == AE_VORTIGAUNT_START_DISPEL )
- {
- StartHandGlow( VORTIGAUNT_BEAM_DISPEL, HAND_LEFT );
- StartHandGlow( VORTIGAUNT_BEAM_DISPEL, HAND_RIGHT );
-
- // Boom!
- //EmitSound( "NPC_Vortigaunt.DispelImpact" );
- CSoundParameters params;
- if ( GetParametersForSound( "NPC_Vortigaunt.DispelImpact", params, NULL ) )
- {
- CPASAttenuationFilter filter( this );
- EmitSound_t ep( params );
- ep.m_nChannel = CHAN_BODY;
- EmitSound( filter, entindex(), ep );
- }
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_ACCEL_DISPEL )
- {
- // TODO: Increase the size?
- return;
- }
-
- // Kaboom!
- if ( pEvent->event == AE_VORTIGAUNT_DISPEL )
- {
- DispelAntlions( GetAbsOrigin(), 400.0f );
- return;
- }
-
- // Start of our heal loop
- if ( pEvent->event == AE_VORTIGAUNT_HEAL_PAUSE )
- {
- StartHealing();
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_ZAP_POWERUP )
- {
- if ( m_fGlowChangeTime > gpGlobals->curtime )
- return;
-
- int nHand = 0;
- if ( pEvent->options )
- {
- nHand = atoi( pEvent->options );
- }
-
- if ( ( nHand == HAND_LEFT ) || (nHand == HAND_BOTH ) )
- {
- ArmBeam( VORTIGAUNT_BEAM_ZAP, HAND_LEFT );
- }
-
- if ( ( nHand == HAND_RIGHT ) || (nHand == HAND_BOTH ) )
- {
- ArmBeam( VORTIGAUNT_BEAM_ZAP, HAND_RIGHT );
- }
-
- // Make hands glow if not already glowing
- if ( m_fGlowAge == 0 )
- {
- if ( ( nHand == HAND_LEFT ) || (nHand == HAND_BOTH ) )
- {
- StartHandGlow( VORTIGAUNT_BEAM_ZAP, HAND_LEFT );
- }
-
- if ( ( nHand == HAND_RIGHT ) || (nHand == HAND_BOTH ) )
- {
- StartHandGlow( VORTIGAUNT_BEAM_ZAP, HAND_RIGHT );
- }
- m_fGlowAge = 1;
- }
-
- CPASAttenuationFilter filter( this );
-
- CSoundParameters params;
- if ( GetParametersForSound( "NPC_Vortigaunt.ZapPowerup", params, NULL ) )
- {
- EmitSound_t ep( params );
- //ep.m_nPitch = 100 + m_iBeams * 10;
- ep.m_nPitch = 150;
-
- EmitSound( filter, entindex(), ep );
-
- m_bStopLoopingSounds = true;
- }
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_ZAP_SHOOT )
- {
- ClearBeams();
-
- ClearMultiDamage();
-
- int nHand = 0;
- if ( pEvent->options )
- {
- nHand = atoi( pEvent->options );
- }
-
- if ( ( nHand == HAND_LEFT ) || (nHand == HAND_BOTH ) )
- {
- ZapBeam( HAND_LEFT );
- }
-
- if ( ( nHand == HAND_RIGHT ) || (nHand == HAND_BOTH ) )
- {
- ZapBeam( HAND_RIGHT );
- }
-
- EndHandGlow();
-
- EmitSound( "NPC_Vortigaunt.ClawBeam" );
- m_bStopLoopingSounds = true;
- ApplyMultiDamage();
-
- // Suppress our aiming until we're done with the animation
- m_flAimDelay = gpGlobals->curtime + 0.75f;
-
- if ( m_bExtractingBugbait )
- {
- // Spawn bugbait!
- CBaseCombatWeapon *pWeapon = Weapon_Create( "weapon_bugbait" );
- if ( pWeapon )
- {
- // Starting above the body, spawn closer and closer to the vort until it's clear
- Vector vecSpawnOrigin = GetTarget()->WorldSpaceCenter() + Vector(0, 0, 32);
- int iNumAttempts = 4;
- Vector vecToVort = (WorldSpaceCenter() - vecSpawnOrigin);
- float flDistance = VectorNormalize( vecToVort ) / (iNumAttempts-1);
- int i = 0;
- for (; i < iNumAttempts; i++ )
- {
- trace_t tr;
- CTraceFilterSkipTwoEntities traceFilter( GetTarget(), this, COLLISION_GROUP_NONE );
- AI_TraceLine( vecSpawnOrigin, vecSpawnOrigin, MASK_SHOT, &traceFilter, &tr );
-
- if ( tr.fraction == 1.0 && !tr.m_pEnt )
- {
- // Make sure it can fit there
- AI_TraceHull( vecSpawnOrigin, vecSpawnOrigin, -Vector(16,16,16), Vector(16,16,48), MASK_SHOT, &traceFilter, &tr );
- if ( tr.fraction == 1.0 && !tr.m_pEnt )
- break;
- }
-
- //NDebugOverlay::Box( vecSpawnOrigin, pWeapon->WorldAlignMins(), pWeapon->WorldAlignMins(), 255,0,0, 64, 100 );
-
- // Move towards the vort
- vecSpawnOrigin = vecSpawnOrigin + (vecToVort * flDistance);
- }
-
- // HACK: If we've still failed, just spawn it on the player
- if ( i == iNumAttempts )
- {
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer )
- {
- vecSpawnOrigin = pPlayer->WorldSpaceCenter();
- }
- }
-
- //NDebugOverlay::Box( vecSpawnOrigin, -Vector(20,20,20), Vector(20,20,20), 0,255,0, 64, 100 );
-
- pWeapon->SetAbsOrigin( vecSpawnOrigin );
- pWeapon->Drop( Vector(0,0,1) );
- }
-
- CEffectData data;
-
- data.m_vOrigin = GetTarget()->WorldSpaceCenter();
- data.m_vNormal = WorldSpaceCenter() - GetTarget()->WorldSpaceCenter();
- VectorNormalize( data.m_vNormal );
-
- data.m_flScale = 4;
-
- DispatchEffect( "AntlionGib", data );
- }
-
- // Stagger the next time we can attack
- m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 2.0f, 3.0f );
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_ZAP_DONE )
- {
- ClearBeams();
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_HEAL_STARTGLOW )
- {
- // Make hands glow
- StartHandGlow( VORTIGAUNT_BEAM_HEAL, HAND_RIGHT );
- m_eHealState = HEAL_STATE_WARMUP;
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_HEAL_STARTSOUND )
- {
- CPASAttenuationFilter filter( this );
-
- CSoundParameters params;
- if ( GetParametersForSound( "NPC_Vortigaunt.StartHealLoop", params, NULL ) )
- {
- EmitSound_t ep( params );
- //ep.m_nPitch = 100 + m_iBeams * 10;
- ep.m_nPitch = 150;
-
- EmitSound( filter, entindex(), ep );
- m_bStopLoopingSounds = true;
- }
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_SWING_SOUND )
- {
- EmitSound( "NPC_Vortigaunt.Swing" );
- return;
- }
-
- if ( pEvent->event == AE_VORTIGAUNT_SHOOT_SOUNDSTART )
- {
- if ( m_fGlowChangeTime > gpGlobals->curtime )
- return;
-
- CPASAttenuationFilter filter( this );
- CSoundParameters params;
- if ( GetParametersForSound( "NPC_Vortigaunt.StartShootLoop", params, NULL ) )
- {
- EmitSound_t ep( params );
- //ep.m_nPitch = 100 + m_iBeams * 10;
- ep.m_nPitch = 150;
-
- EmitSound( filter, entindex(), ep );
- m_bStopLoopingSounds = true;
- }
- return;
- }
-
- if ( pEvent->event == AE_NPC_LEFTFOOT )
- {
- EmitSound( "NPC_Vortigaunt.FootstepLeft", pEvent->eventtime );
- return;
- }
-
- if ( pEvent->event == AE_NPC_RIGHTFOOT )
- {
- EmitSound( "NPC_Vortigaunt.FootstepRight", pEvent->eventtime );
- return;
- }
-
- BaseClass::HandleAnimEvent( pEvent );
-}
-
-
-//------------------------------------------------------------------------------
-// Purpose : Turn blue or green
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputTurnBlue( inputdata_t &data )
-{
- bool goBlue = data.value.Bool();
- if (goBlue != m_bIsBlue)
- {
- m_bIsBlue = goBlue;
- m_flBlueEndFadeTime = gpGlobals->curtime + VORTIGAUNT_BLUE_FADE_TIME;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Turn blue or green
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputTurnBlack( inputdata_t &data )
-{
- bool goBlack = data.value.Bool();
- if (goBlack != m_bIsBlack)
- {
- m_bIsBlack = goBlack;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Translate some activites for the Vortigaunt
-//------------------------------------------------------------------------------
-Activity CNPC_Vortigaunt::NPC_TranslateActivity( Activity eNewActivity )
-{
- // This is a hack solution for the Vort carrying Alyx in Ep2
- if ( IsCarryingNPC() )
- {
- if ( eNewActivity == ACT_IDLE )
- return ACT_IDLE_CARRY;
-
- if ( eNewActivity == ACT_WALK || eNewActivity == ACT_WALK_AIM || eNewActivity == ACT_RUN || eNewActivity == ACT_RUN_AIM )
- return ACT_WALK_CARRY;
- }
-
- // NOTE: This is a stand-in until the readiness system can handle non-weapon holding NPC's
- if ( eNewActivity == ACT_IDLE )
- {
- // More than relaxed means we're stimulated
- if ( GetReadinessLevel() >= AIRL_STIMULATED )
- return ACT_IDLE_STIMULATED;
- }
-
- if ( eNewActivity == ACT_RANGE_ATTACK2 )
- return (Activity) ACT_VORTIGAUNT_DISPEL;
-
- return BaseClass::NPC_TranslateActivity( eNewActivity );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::UpdateOnRemove( void)
-{
- ClearBeams();
- ClearHandGlow();
-
- // Chain at end to mimic destructor unwind order
- BaseClass::UpdateOnRemove();
-}
-
-//------------------------------------------------------------------------------
-// Purpose :
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::Event_Killed( const CTakeDamageInfo &info )
-{
- ClearBeams();
- ClearHandGlow();
-
- BaseClass::Event_Killed( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::Spawn( void )
-{
-#if !defined( HL2_EPISODIC )
- // Disable back-away
- AddSpawnFlags( SF_NPC_NO_PLAYER_PUSHAWAY );
-#endif // HL2_EPISODIC
-
- // Allow multiple models (for slaves), but default to vortigaunt.mdl
- char *szModel = (char *)STRING( GetModelName() );
- if (!szModel || !*szModel)
- {
- szModel = "models/vortigaunt.mdl";
- SetModelName( AllocPooledString(szModel) );
- }
-
- BaseClass::Spawn();
-
- m_HackedGunPos.x = 0.0f;
- m_HackedGunPos.y = 0.0f;
- m_HackedGunPos.z = 48.0f;
-
- SetHullType( HULL_WIDE_HUMAN );
- SetHullSizeNormal();
-
- m_bloodColor = BLOOD_COLOR_GREEN;
- m_iHealth = sk_vortigaunt_health.GetFloat();
- SetViewOffset( Vector ( 0, 0, 64 ) );// position of the eyes relative to monster's origin.
-
- CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK1 );
- CapabilitiesRemove( bits_CAP_USE_SHOT_REGULATOR );
-
- m_flEyeIntegRate = 0.6f; // Got a big eyeball so turn it slower
- m_bForceArmorRecharge = false;
- m_flHealHinderedTime = 0.0f;
-
- m_nCurGlowIndex = 0;
-
- m_bStopLoopingSounds = false;
-
- m_iLeftHandAttachment = LookupAttachment( VORTIGAUNT_LEFT_CLAW );
- m_iRightHandAttachment = LookupAttachment( VORTIGAUNT_RIGHT_CLAW );
-
- NPCInit();
-
- SetUse( &CNPC_Vortigaunt::Use );
-
- // Setup our re-fire times when moving and shooting
- GetShotRegulator()->SetBurstInterval( 2.0f, 2.0f );
- GetShotRegulator()->SetBurstShotCountRange( 1, 1 );
- GetShotRegulator()->SetRestInterval( 2.0f, 2.0f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::Precache()
-{
- UTIL_PrecacheOther( "vort_charge_token" );
-
- PrecacheModel( STRING( GetModelName() ) );
-
- m_nLightningSprite = PrecacheModel("sprites/lgtning.vmt");
- PrecacheModel("sprites/vortring1.vmt");
-
- // HACK: Only precache this for EP2 because reslists cannot be rebuilt - 08/22/07 - jdw
- if ( hl2_episodic.GetBool() )
- {
- char modDir[MAX_PATH];
- if ( UTIL_GetModDir( modDir, sizeof(modDir) ) )
- {
- if ( !Q_stricmp( modDir, "ep2" ) )
- {
- PrecacheMaterial( "effects/rollerglow" );
- }
- }
- }
-
- PrecacheScriptSound( "NPC_Vortigaunt.SuitOn" );
- PrecacheScriptSound( "NPC_Vortigaunt.SuitCharge" );
- PrecacheScriptSound( "NPC_Vortigaunt.ZapPowerup" );
- PrecacheScriptSound( "NPC_Vortigaunt.ClawBeam" );
- PrecacheScriptSound( "NPC_Vortigaunt.StartHealLoop" );
- PrecacheScriptSound( "NPC_Vortigaunt.Swing" );
- PrecacheScriptSound( "NPC_Vortigaunt.StartShootLoop" );
- PrecacheScriptSound( "NPC_Vortigaunt.FootstepLeft" );
- PrecacheScriptSound( "NPC_Vortigaunt.FootstepRight" );
- PrecacheScriptSound( "NPC_Vortigaunt.DispelStart" );
- PrecacheScriptSound( "NPC_Vortigaunt.DispelImpact" );
- PrecacheScriptSound( "NPC_Vortigaunt.Explode" );
-
- PrecacheParticleSystem( "vortigaunt_beam" );
- PrecacheParticleSystem( "vortigaunt_beam_charge" );
- PrecacheParticleSystem( "vortigaunt_hand_glow" );
-
- PrecacheMaterial( "sprites/light_glow02_add" );
-
- BaseClass::Precache();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Interpret a player +USE'ing us
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
-{
- m_OnPlayerUse.FireOutput( pActivator, pCaller );
-
- // Foremost, try and heal a wounded player
- if ( HealBehaviorAvailable() )
- {
- // See if we should heal the player
- CBaseEntity *pHealTarget = FindHealTarget();
- if ( pHealTarget != NULL )
- {
- SetCondition( COND_PROVOKED );
- SetHealTarget( pHealTarget, true );
- return;
- }
- }
-
- // Next, try to speak the +USE concept
- if ( IsOkToSpeakInResponseToPlayer() && m_eHealState == HEAL_STATE_NONE )
- {
- if ( Speak( TLK_USE ) == false )
- {
- // If we haven't said hi, say that first
- if ( !SpokeConcept( TLK_HELLO ) )
- {
- Speak( TLK_HELLO );
- }
- else
- {
- Speak( TLK_IDLE );
- }
- }
- else
- {
- // Don't say hi after you've said your +USE speech
- SetSpokeConcept( TLK_HELLO, NULL );
- }
- }
-}
-
-//=========================================================
-// PainSound
-//=========================================================
-void CNPC_Vortigaunt::PainSound( const CTakeDamageInfo &info )
-{
- if ( gpGlobals->curtime < m_flPainTime )
- return;
-
- m_flPainTime = gpGlobals->curtime + random->RandomFloat(0.5, 0.75);
-
- Speak( VORT_PAIN );
-}
-
-//=========================================================
-// DeathSound
-//=========================================================
-void CNPC_Vortigaunt::DeathSound( const CTakeDamageInfo &info )
-{
- Speak( VORT_DIE );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
-{
- CTakeDamageInfo info = inputInfo;
-
- if ( (info.GetDamageType() & DMG_SHOCK) && FClassnameIs( info.GetAttacker(), GetClassname() ) )
- {
- // mask off damage from other vorts for now
- info.SetDamage( 0.01 );
- }
-
- switch( ptr->hitgroup)
- {
- case HITGROUP_CHEST:
- case HITGROUP_STOMACH:
- if (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_BLAST))
- {
- info.ScaleDamage( 0.5f );
- }
- break;
- case 10:
- if (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB))
- {
- info.SetDamage( info.GetDamage() - 20 );
- if (info.GetDamage() <= 0)
- {
- g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) );
- info.SetDamage( 0.01 );
- }
- }
- // always a head shot
- ptr->hitgroup = HITGROUP_HEAD;
- break;
- }
-
- BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::TranslateSchedule( int scheduleType )
-{
- switch( scheduleType )
- {
- case SCHED_ALERT_FACE_BESTSOUND:
- return SCHED_VORT_ALERT_FACE_BESTSOUND;
- break;
-
- case SCHED_TAKE_COVER_FROM_BEST_SOUND:
-
- // Stand still if we're in the middle of an attack. Failing to do so can make us miss our shot!
- if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
- return SCHED_COMBAT_FACE;
-
- return SCHED_VORT_FLEE_FROM_BEST_SOUND;
- break;
-
- case SCHED_COWER:
- case SCHED_PC_COWER:
- // Vort doesn't have cower animations
- return SCHED_COMBAT_FACE;
- break;
-
- case SCHED_RANGE_ATTACK1:
-
- // If we're told to fire when we're already firing, just face our target. If we don't do this, we get a bizarre double-shot
- if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
- return SCHED_COMBAT_FACE;
-
- // Otherwise we use our own schedule to attack
- return SCHED_VORTIGAUNT_RANGE_ATTACK;
- break;
-
- /*
- case SCHED_CHASE_ENEMY:
- case SCHED_ESTABLISH_LINE_OF_FIRE:
- case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
-
- // Don't go running off after an enemy just because we're in an attack delay! This has to do with
- // the base systems assuming that held weapons are driving certain decisions when this creature
- // uses an innate ability.
- if ( ( GetNextAttack() > gpGlobals->curtime ) && HasCondition( COND_ENEMY_TOO_FAR ) == false )
- return SCHED_COMBAT_FACE;
-
- break;
- */
- }
-
- return BaseClass::TranslateSchedule( scheduleType );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the heal target for the vort and preps him for completing the action
-// Input : *pTarget - Target we're after
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::SetHealTarget( CBaseEntity *pTarget, bool bPlayerRequested )
-{
- SetCondition( COND_VORTIGAUNT_CAN_HEAL );
- OccupyStrategySlot( SQUAD_SLOT_HEAL_PLAYER );
- m_hHealTarget = pTarget;
- m_bPlayerRequestedHeal = bPlayerRequested;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Finds a player in range that can be healed
-// Output : Target that can be healed
-//-----------------------------------------------------------------------------
-CBaseEntity *CNPC_Vortigaunt::FindHealTarget( void )
-{
- // Need to be looking at the player to decide to heal them.
- //if ( HasCondition( COND_SEE_PLAYER ) == false )
- // return false;
-
- // Find a likely target in range
- CBaseEntity *pEntity = PlayerInRange( GetAbsOrigin(), HEAL_SEARCH_RANGE );
-
- // Make sure we can heal that target
- if ( ShouldHealTarget( pEntity ) == false )
- return NULL;
-
- return pEntity;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Whether or not the vort is able to attempt to heal targets
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::HealBehaviorAvailable( void )
-{
- // Cannot already be healing
- if ( m_eHealState != HEAL_STATE_NONE )
- return false;
-
- // Must be allowed to do this behavior
- if ( m_bArmorRechargeEnabled == false )
- return false;
-
- // Don't interrupt a script
- if ( IsInAScript() || m_NPCState == NPC_STATE_SCRIPT )
- return false;
-
- // Cannot interrupt bugbait extraction
- if ( IsCurSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT ) )
- return false;
-
- // Don't bother while we're under attack
- if ( GetEnemy() != NULL )
- return false;
-
- // Can't heal if we're leading the player
- if ( IsLeading() )
- return false;
-
- // Must be a valid squad activity to do
- if ( IsStrategySlotRangeOccupied( SQUAD_SLOT_HEAL_PLAYER, SQUAD_SLOT_HEAL_PLAYER ) )
- return false;
-
- // Heal is valid
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determines whether or not the
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::ShouldHealTarget( CBaseEntity *pTarget )
-{
- // Must have a valid target
- if ( pTarget == NULL )
- return false;
-
- // If we're scripting or waiting to run one, we won't heal a target
- if ( IsInAScript() || HasSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ) )
- return false;
-
- // We only heal players
- CBasePlayer *pPlayer = ToBasePlayer( pTarget );
- if ( pPlayer == NULL )
- return false;
-
- // Make sure the player's got a suit
- if ( pPlayer->IsSuitEquipped() == false )
- return false;
-
- // Don't heal a target we can't see..?
- if ( pPlayer->GetFlags() & FL_NOTARGET )
- return false;
-
- // See if the player needs armor
- if ( pPlayer->ArmorValue() >= (sk_vortigaunt_armor_charge.GetFloat()*0.66f) )
- return false;
-
- // Must be alive!
- if ( pPlayer->IsAlive() == false )
- return false;
-
- // Only consider things in here if the player is NOT at critical health or the heal is a passive one (not requested)
- if ( PlayerBelowHealthPercentage( pPlayer, PLAYER_CRITICAL_HEALTH_PERC ) == false || m_bPlayerRequestedHeal )
- {
- // Don't heal when fighting
- if ( m_NPCState == NPC_STATE_COMBAT )
- return false;
-
- // No enemies
- if ( GetEnemy() )
- return false;
-
- // No recent damage
- if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
- return false;
- }
-
- // Allow the heal
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : int
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::SelectHealSchedule( void )
-{
- // If our lead behavior has a goal, don't wait around to heal anyone
- if ( m_LeadBehavior.HasGoal() )
- return SCHED_NONE;
-
- // Break out of healing if a script has started
- if ( IsInAScript() && m_bForceArmorRecharge == false )
- {
- if ( m_eHealState != HEAL_STATE_NONE )
- {
- StopHealing( true );
- }
-
- return SCHED_NONE;
- }
-
- // Cannot already be healing the player
- if ( m_hHealTarget != NULL )
- {
- // For now, just grab the global, single player
- CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
-
- // Check for an interruption occurring
- if ( PlayerBelowHealthPercentage( pPlayer, PLAYER_CRITICAL_HEALTH_PERC ) == false && HasCondition( COND_HEAVY_DAMAGE ) )
- {
- StopHealing( true );
- return SCHED_NONE;
- }
-
- // See if we're in an ideal position to heal
- if ( m_eHealState != HEAL_STATE_HEALING && m_eHealState != HEAL_STATE_WARMUP && HasCondition( COND_VORTIGAUNT_HEAL_VALID ) )
- return SCHED_VORTIGAUNT_HEAL;
-
- // If the player is too far away or blocked, give chase
- if ( HasCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR ) ||
- HasCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED ) )
- return SCHED_VORTIGAUNT_RUN_TO_PLAYER;
-
- // Stand and face the player
- if ( HasCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US ) || HasCondition( COND_VORTIGAUNT_HEAL_VALID ) )
- return SCHED_VORTIGAUNT_FACE_PLAYER;
- }
-
- return SCHED_NONE;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Watch this function path for a route around our normal schedule changing callbacks
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::ClearSchedule( const char *szReason )
-{
- MaintainGlows();
-
- BaseClass::ClearSchedule( szReason );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Watch our glows and turn them off appropriately
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::OnScheduleChange( void )
-{
- BaseClass::OnScheduleChange();
-
- // If we're in the middle of healing, don't bother doing this
- if ( m_eHealState != HEAL_STATE_NONE )
- return;
-
- // If we're changing sequences, always clear
- EndHandGlow( VORTIGAUNT_BEAM_ALL );
- m_fGlowChangeTime = gpGlobals->curtime + 0.1f; // No more glows for this amount of time!
-}
-
-//------------------------------------------------------------------------------
-// Purpose: Select a schedule
-//------------------------------------------------------------------------------
-int CNPC_Vortigaunt::SelectSchedule( void )
-{
- // Always recharge in this case
- if ( m_bForceArmorRecharge )
- {
- m_flNextHealTime = 0;
- int nSchedule = SelectHealSchedule();
- return nSchedule;
- }
-
-#ifndef HL2_EPISODIC
- if ( BehaviorSelectSchedule() )
- return BaseClass::SelectSchedule();
-#else
- if ( IsInAScript() )
- return BaseClass::SelectSchedule();
-#endif
-
- // If we're currently supposed to be doing something scripted, do it immediately.
- if ( m_bExtractingBugbait )
- return SCHED_VORTIGAUNT_EXTRACT_BUGBAIT;
-
- int schedule = SelectHealSchedule();
- if ( schedule != SCHED_NONE )
- return schedule;
-
- if ( HasCondition(COND_VORTIGAUNT_DISPEL_ANTLIONS ) )
- {
- ClearCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
- return SCHED_VORTIGAUNT_DISPEL_ANTLIONS;
- }
-
- // Heal a player if they can be
- if ( HasCondition( COND_VORTIGAUNT_CAN_HEAL ) )
- return SCHED_VORTIGAUNT_HEAL;
-
- return BaseClass::SelectSchedule();
-}
-
-//-----------------------------------------------------------------------------
-//
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
-{
- if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY )
- {
- if ( GetEnemy() && GetSenses()->CanSeeEntity( GetEnemy() ) )
- {
- return SCHED_RANGE_ATTACK1;
- }
- }
-
- return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::DeclineFollowing( void )
-{
- Speak( VORT_POK );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return true if you're willing to be idly talked to by other friends.
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::CanBeUsedAsAFriend( void )
-{
- // We don't want to be used if we're busy
- if ( IsCurSchedule( SCHED_VORTIGAUNT_HEAL ) )
- return false;
-
- if ( IsCurSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT ) )
- return false;
-
- return BaseClass::CanBeUsedAsAFriend();
-}
-
-//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-#define VORT_360_VIEW_DIST_SQR ((60*12)*(60*12))
-bool CNPC_Vortigaunt::FInViewCone( CBaseEntity *pEntity )
-{
- // Vort can see 360 degrees but only at limited distance
- if( ( pEntity->IsNPC() || pEntity->IsPlayer() ) && pEntity->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= VORT_360_VIEW_DIST_SQR )
- return true;
-
- return BaseClass::FInViewCone( pEntity );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Start our heal loop
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::StartHealing( void )
-{
- // Find the layer and stop it from moving forward in the cycle
- int nLayer = FindGestureLayer( (Activity) ACT_VORTIGAUNT_HEAL );
- SetLayerPlaybackRate( nLayer, 0.0f );
-
- // We're now in the healing loop
- m_eHealState = HEAL_STATE_HEALING;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::StopHealing( bool bInterrupt )
-{
- // Clear out our healing states
- m_eHealState = HEAL_STATE_NONE;
- m_bForceArmorRecharge = false;
- m_hHealTarget = NULL;
-
- EndHandGlow( VORTIGAUNT_BEAM_HEAL );
- VacateStrategySlot();
-
- // See if we're completely interrupting the heal or just ending normally
- if ( bInterrupt )
- {
- RemoveGesture( (Activity) ACT_VORTIGAUNT_HEAL );
- m_flNextHealTime = gpGlobals->curtime + 2.0f;
- }
- else
- {
- // Start our animation back up again
- int nLayer = FindGestureLayer( (Activity) ACT_VORTIGAUNT_HEAL );
- SetLayerPlaybackRate( nLayer, 1.0f );
-
- m_flNextHealTime = gpGlobals->curtime + VORTIGAUNT_HEAL_RECHARGE;
- m_OnFinishedChargingTarget.FireOutput( this, this );
- }
-
- // Give us time to stop our animation before we start attacking (otherwise we get weird collisions)
- SetNextAttack( gpGlobals->curtime + 2.0f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Update our heal schedule and gestures if we're currently healing
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::MaintainHealSchedule( void )
-{
- // Need to be healing
- if ( m_eHealState == HEAL_STATE_NONE )
- return;
-
- // For now, we only heal the player
- CBasePlayer *pPlayer = AI_GetSinglePlayer();
- if ( pPlayer == NULL )
- return;
-
- // FIXME: How can this happen?
- if ( m_AssaultBehavior.GetOuter() != NULL )
- {
- // Interrupt us on an urgent assault
- if ( m_AssaultBehavior.IsRunning() && ( m_AssaultBehavior.IsUrgent() || m_AssaultBehavior.OnStrictAssault() ) )
- {
- StopHealing( true );
- return;
- }
- }
-
- // Don't let us shoot while we're healing
- GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + 0.5f );
-
- // If we're in the healing phase, heal our target (if able)
- if ( m_eHealState == HEAL_STATE_HEALING )
- {
- // FIXME: We need to have better logic controlling this
- if ( HasCondition( COND_VORTIGAUNT_HEAL_VALID ) )
- {
- if ( m_flNextHealTokenTime < gpGlobals->curtime )
- {
- CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
-
- // We're done, so stop playing the animation
- if ( m_nNumTokensToSpawn <= 0 || ( m_bForceArmorRecharge == false && ( pPlayer && pPlayer->ArmorValue() >= sk_vortigaunt_armor_charge.GetInt() ) ) )
- {
- m_flHealHinderedTime = 0.0f;
- m_nNumTokensToSpawn = 0;
- SpeakIfAllowed( VORT_CURESTOP );
- StopHealing( false );
- return;
- }
-
- // Create a charge token
- Vector vecHandPos;
- QAngle vecHandAngles;
- GetAttachment( m_iRightHandAttachment, vecHandPos, vecHandAngles );
- CVortigauntChargeToken::CreateChargeToken( vecHandPos, this, m_hHealTarget );
- m_flNextHealTokenTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f );
- m_nNumTokensToSpawn--;
-
- // If we're stopping, delay our animation a bit so it's not so robotic
- if ( m_nNumTokensToSpawn <= 0 )
- {
- m_nNumTokensToSpawn = 0;
- m_flNextHealTokenTime = gpGlobals->curtime + 1.0f;
- }
- }
- }
- else
- {
- /*
- // NOTENOTE: It's better if the vort give up than ignore things around him to try and continue -- jdw
-
- // Increment a counter to let us know how long we've failed
- m_flHealHinderedTime += gpGlobals->curtime - GetLastThink();
-
- if ( m_flHealHinderedTime > 2.0f )
- {
- // If too long, stop trying
- StopHealing();
- }
- */
-
- bool bInterrupt = false;
- if ( HasCondition( COND_NEW_ENEMY ) )
- {
- bInterrupt = true;
- }
-
- StopHealing( true );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-inline bool CNPC_Vortigaunt::InAttackSequence( void )
-{
- if ( m_MoveAndShootOverlay.IsMovingAndShooting() )
- return true;
-
- if ( GetActivity() == ACT_RANGE_ATTACK1 )
- return true;
-
- if ( GetActivity() == ACT_VORTIGAUNT_DISPEL )
- return true;
-
- if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
- return true;
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Watch our beams and make sure we don't leave them on mistakenly
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::MaintainGlows( void )
-{
- // Verify that if we're not in an attack gesture, that we're not doing an attack glow
- if ( InAttackSequence() == false && m_eHealState == HEAL_STATE_NONE )
- {
- EndHandGlow( VORTIGAUNT_BEAM_ALL );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Squelch looping sounds and glows after a restore.
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::OnRestore( void )
-{
- BaseClass::OnRestore();
-
- m_bStopLoopingSounds = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Do various non-schedule specific maintainence
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::PrescheduleThink( void )
-{
- // Update our healing (if active)
- MaintainHealSchedule();
-
- // Let the base class have a go
- BaseClass::PrescheduleThink();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : &move -
-// flInterval -
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
-{
- // If we're in our aiming gesture, then always face our target as we run
- Activity nActivity = NPC_TranslateActivity( ACT_GESTURE_RANGE_ATTACK1 );
- if ( IsPlayingGesture( nActivity ) ||
- IsCurSchedule( SCHED_PC_MOVE_TOWARDS_COVER_FROM_BEST_SOUND ) ||
- IsCurSchedule( SCHED_VORT_FLEE_FROM_BEST_SOUND ) ||
- IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) )
- {
- Vector vecEnemyLKP = GetEnemyLKP();
- AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
- }
-
- return BaseClass::OverrideMoveFacing( move, flInterval );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::BuildScheduleTestBits( void )
-{
- // Call to base
- BaseClass::BuildScheduleTestBits();
-
- // Allow healing to interrupt us if we're standing around
- if ( IsCurSchedule( SCHED_IDLE_STAND ) ||
- IsCurSchedule( SCHED_ALERT_STAND ) )
- {
- if ( m_eHealState == HEAL_STATE_NONE )
- {
- SetCustomInterruptCondition( COND_VORTIGAUNT_CAN_HEAL );
- SetCustomInterruptCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
- }
- }
-
- // Always interrupt when healing
- if ( m_eHealState != HEAL_STATE_NONE )
- {
- // Interrupt if we're not already adjusting
- if ( IsCurSchedule( SCHED_VORTIGAUNT_RUN_TO_PLAYER ) == false )
- {
- SetCustomInterruptCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR );
- SetCustomInterruptCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED );
-
- // Interrupt if we're not already turning
- if ( IsCurSchedule( SCHED_VORTIGAUNT_FACE_PLAYER ) == false )
- {
- SetCustomInterruptCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US );
- }
- }
- }
-
- if ( IsCurSchedule( SCHED_COMBAT_STAND ) )
- {
- SetCustomInterruptCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Small beam from arm to nearby geometry
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::ArmBeam( int beamType, int nHand )
-{
- trace_t tr;
- float flDist = 1.0;
- int side = ( nHand == HAND_LEFT ) ? -1 : 1;
-
- Vector forward, right, up;
- AngleVectors( GetLocalAngles(), &forward, &right, &up );
- Vector vecSrc = GetLocalOrigin() + up * 36 + right * side * 16 + forward * 32;
-
- for (int i = 0; i < 3; i++)
- {
- Vector vecAim = forward * random->RandomFloat( -1, 1 ) + right * side * random->RandomFloat( 0, 1 ) + up * random->RandomFloat( -1, 1 );
- trace_t tr1;
- AI_TraceLine ( vecSrc, vecSrc + vecAim * (10*12), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr1);
-
- // Don't hit the sky
- if ( tr1.surface.flags & SURF_SKY )
- continue;
-
- // Choose a farther distance if we have one
- if ( flDist > tr1.fraction )
- {
- tr = tr1;
- flDist = tr.fraction;
- }
- }
-
- // Couldn't find anything close enough
- if ( flDist == 1.0 )
- return;
-
- // Tell the client to start an arm beam
- unsigned char uchAttachment = (nHand==HAND_LEFT) ? m_iLeftHandAttachment : m_iRightHandAttachment;
- EntityMessageBegin( this, true );
- WRITE_BYTE( VORTFX_ARMBEAM );
- WRITE_LONG( entindex() );
- WRITE_BYTE( uchAttachment );
- WRITE_VEC3COORD( tr.endpos );
- WRITE_VEC3NORMAL( tr.plane.normal );
- MessageEnd();
-}
-
-//------------------------------------------------------------------------------
-// Purpose : Put glowing sprites on hands
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::StartHandGlow( int beamType, int nHand )
-{
- // We need this because there's a rare case where a scene can interrupt and turn off our hand glows, but are then
- // turned back on in the same frame due to how animations are applied and anim events are executed after the AI frame.
- if ( m_fGlowChangeTime > gpGlobals->curtime )
- return;
-
- switch( beamType )
- {
- case VORTIGAUNT_BEAM_DISPEL:
- case VORTIGAUNT_BEAM_HEAL:
- case VORTIGAUNT_BEAM_ZAP:
- {
- // Validate the hand's range
- if ( nHand >= ARRAYSIZE( m_hHandEffect ) )
- return;
-
- // Start up
- if ( m_hHandEffect[nHand] == NULL )
- {
- // Create the token if it doesn't already exist
- m_hHandEffect[nHand] = CVortigauntEffectDispel::CreateEffectDispel( GetAbsOrigin(), this, NULL );
- if ( m_hHandEffect[nHand] == NULL )
- return;
- }
-
- // Stomp our settings
- m_hHandEffect[nHand]->SetParent( this, (nHand==HAND_LEFT) ? m_iLeftHandAttachment : m_iRightHandAttachment );
- m_hHandEffect[nHand]->SetMoveType( MOVETYPE_NONE );
- m_hHandEffect[nHand]->SetLocalOrigin( Vector( 8.0f, 4.0f, 0.0f ) );
- }
- break;
-
- case VORTIGAUNT_BEAM_ALL:
- Assert( 0 );
- break;
- }
-}
-
-//------------------------------------------------------------------------------
-// Purpose: Fade glow from hands.
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::EndHandGlow( int beamType /*= VORTIGAUNT_BEAM_ALL*/ )
-{
- if ( m_hHandEffect[0] )
- {
- m_hHandEffect[0]->FadeAndDie();
- m_hHandEffect[0] = NULL;
- }
-
- if ( m_hHandEffect[1] )
- {
- m_hHandEffect[1]->FadeAndDie();
- m_hHandEffect[1] = NULL;
- }
-
- // Zap
- if ( beamType == VORTIGAUNT_BEAM_ZAP || beamType == VORTIGAUNT_BEAM_ALL )
- {
- m_fGlowAge = 0;
-
- // Stop our smaller beams as well
- ClearBeams();
- }
-}
-
-extern int ACT_ANTLION_ZAP_FLIP;
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::IsValidEnemy( CBaseEntity *pEnemy )
-{
- if ( IsRoller( pEnemy ) )
- {
- CAI_BaseNPC *pNPC = pEnemy->MyNPCPointer();
- if ( pNPC && pNPC->GetEnemy() != NULL )
- return true;
- return false;
- }
-
- // Wait until our animation is finished
- if ( GetEnemy() == NULL && m_flAimDelay > gpGlobals->curtime )
- return false;
-
- return BaseClass::IsValidEnemy( pEnemy );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Creates a blast where the beam has struck a target
-// Input : &vecOrigin - position to eminate from
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::CreateBeamBlast( const Vector &vecOrigin )
-{
- CSprite *pBlastSprite = CSprite::SpriteCreate( "sprites/vortring1.vmt", vecOrigin, true );
- if ( pBlastSprite != NULL )
- {
- pBlastSprite->SetTransparency( kRenderTransAddFrameBlend, 255, 255, 255, 255, kRenderFxNone );
- pBlastSprite->SetBrightness( 255 );
- pBlastSprite->SetScale( random->RandomFloat( 1.0f, 1.5f ) );
- pBlastSprite->AnimateAndDie( 45.0f );
- pBlastSprite->EmitSound( "NPC_Vortigaunt.Explode" );
- }
-
- CPVSFilter filter( vecOrigin );
- te->GaussExplosion( filter, 0.0f, vecOrigin, Vector( 0, 0, 1 ), 0 );
-}
-
-#define COS_30 0.866025404f // sqrt(3) / 2
-#define COS_60 0.5 // sqrt(1) / 2
-
-//-----------------------------------------------------------------------------
-// Purpose: Heavy damage directly forward
-// Input : nHand - Handedness of the beam
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::ZapBeam( int nHand )
-{
- Vector forward;
- GetVectors( &forward, NULL, NULL );
-
- Vector vecSrc = GetAbsOrigin() + GetViewOffset();
- Vector vecAim = GetShootEnemyDir( vecSrc, false ); // We want a clear shot to their core
-
- if ( GetEnemy() )
- {
- Vector vecTarget = GetEnemy()->BodyTarget( vecSrc, false );
-
- if ( g_debug_vortigaunt_aim.GetBool() )
- {
- NDebugOverlay::Cross3D( vecTarget, 4.0f, 255, 0, 0, true, 10.0f );
- CBaseAnimating *pAnim = GetEnemy()->GetBaseAnimating();
- if ( pAnim )
- {
- pAnim->DrawServerHitboxes( 10.0f );
- }
- }
- }
-
- // If we're too far off our center, the shot must miss!
- if ( DotProduct( vecAim, forward ) < COS_60 )
- {
- // Missed, so just shoot forward
- vecAim = forward;
- }
-
- trace_t tr;
-
- if ( m_bExtractingBugbait == true )
- {
- CRagdollProp *pTest = dynamic_cast< CRagdollProp *>( GetTarget() );
-
- if ( pTest )
- {
- ragdoll_t *m_ragdoll = pTest->GetRagdoll();
-
- if ( m_ragdoll )
- {
- Vector vOrigin;
- m_ragdoll->list[0].pObject->GetPosition( &vOrigin, 0 );
-
- AI_TraceLine( vecSrc, vOrigin, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
- }
-
- CRagdollBoogie::Create( pTest, 200, gpGlobals->curtime, 1.0f );
- }
- }
- else
- {
- AI_TraceLine( vecSrc, vecSrc + ( vecAim * InnateRange1MaxRange() ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
- }
-
- if ( g_debug_vortigaunt_aim.GetBool() )
- {
- NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 0, true, 10.0f );
- }
-
- // Send a message to the client to create a "zap" beam
- unsigned char uchAttachment = (nHand==HAND_LEFT) ? m_iLeftHandAttachment : m_iRightHandAttachment;
- EntityMessageBegin( this, true );
- WRITE_BYTE( VORTFX_ZAPBEAM );
- WRITE_BYTE( uchAttachment );
- WRITE_VEC3COORD( tr.endpos );
- MessageEnd();
-
- CBaseEntity *pEntity = tr.m_pEnt;
- if ( pEntity != NULL && m_takedamage )
- {
- if ( g_debug_vortigaunt_aim.GetBool() )
- {
- NDebugOverlay::Box( tr.endpos, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, 8, 10.0f );
- }
-
- CTakeDamageInfo dmgInfo( this, this, sk_vortigaunt_dmg_zap.GetFloat(), DMG_SHOCK );
- dmgInfo.SetDamagePosition( tr.endpos );
- VectorNormalize( vecAim );// not a unit vec yet
- // hit like a 5kg object flying 100 ft/s
- dmgInfo.SetDamageForce( 5 * 100 * 12 * vecAim );
-
- // Our zaps do special things to antlions
- if ( FClassnameIs( pEntity, "npc_antlion" ) )
- {
- // Make a worker flip instead of explode
- if ( IsAntlionWorker( pEntity ) )
- {
- CNPC_Antlion *pAntlion = static_cast<CNPC_Antlion *>(pEntity);
- pAntlion->Flip();
- }
- else
- {
- // Always gib the antlion hit!
- dmgInfo.ScaleDamage( 4.0f );
- }
-
- // Look in a ring and flip other antlions nearby
- DispelAntlions( tr.endpos, 200.0f, false );
- }
-
- // Send the damage to the recipient
- pEntity->DispatchTraceAttack( dmgInfo, vecAim, &tr );
- }
-
- // Create a cover for the end of the beam
- CreateBeamBlast( tr.endpos );
-}
-
-//------------------------------------------------------------------------------
-// Purpose: Clear glow from hands immediately
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::ClearHandGlow( void )
-{
- if ( m_hHandEffect[0] != NULL )
- {
- UTIL_Remove( m_hHandEffect[0] );
- m_hHandEffect[0] = NULL;
- }
-
- if ( m_hHandEffect[1] != NULL )
- {
- UTIL_Remove( m_hHandEffect[1] );
- m_hHandEffect[1] = NULL;
- }
-
- m_fGlowAge = 0;
-}
-
-//------------------------------------------------------------------------------
-// Purpose: remove all beams
-//------------------------------------------------------------------------------
-void CNPC_Vortigaunt::ClearBeams( void )
-{
- // Stop looping suit charge sound.
- if ( m_bStopLoopingSounds )
- {
- StopSound( "NPC_Vortigaunt.StartHealLoop" );
- StopSound( "NPC_Vortigaunt.StartShootLoop" );
- StopSound( "NPC_Vortigaunt.SuitCharge" );
- StopSound( "NPC_Vortigaunt.ZapPowerup" );
- m_bStopLoopingSounds = false;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputEnableArmorRecharge( inputdata_t &data )
-{
- m_bArmorRechargeEnabled = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputDisableArmorRecharge( inputdata_t &data )
-{
- m_bArmorRechargeEnabled = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputChargeTarget( inputdata_t &data )
-{
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, data.value.String(), NULL, data.pActivator, data.pCaller );
-
- // Must be valid
- if ( pTarget == NULL )
- {
- DevMsg( 1, "Unable to charge from unknown entity: %s!\n", data.value.String() );
- return;
- }
-
- int playerArmor = (pTarget->IsPlayer()) ? ((CBasePlayer *)pTarget)->ArmorValue() : 0;
-
- if ( playerArmor >= 100 || ( pTarget->GetFlags() & FL_NOTARGET ) )
- {
- m_OnFinishedChargingTarget.FireOutput( this, this );
- return;
- }
-
- m_hHealTarget = pTarget;
- m_bForceArmorRecharge = true;
-
- SetCondition( COND_PROVOKED );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputExtractBugbait( inputdata_t &data )
-{
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, data.value.String(), NULL, data.pActivator, data.pCaller );
-
- // Must be valid
- if ( pTarget == NULL )
- {
- DevMsg( 1, "Unable to extract bugbait from unknown entity %s!\n", data.value.String() );
- return;
- }
-
- // Keep this as our target
- SetTarget( pTarget );
-
- // Start to extract
- m_bExtractingBugbait = true;
- SetSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Allows the vortigaunt to use health regeneration
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputEnableHealthRegeneration( inputdata_t &data )
-{
- m_bRegenerateHealth = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Stops the vortigaunt from using health regeneration (default)
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputDisableHealthRegeneration( inputdata_t &data )
-{
- m_bRegenerateHealth = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::IRelationPriority( CBaseEntity *pTarget )
-{
- int priority = BaseClass::IRelationPriority( pTarget );
-
- if ( pTarget == NULL )
- return priority;
-
- CBaseEntity *pEnemy = GetEnemy();
-
- // Handle antlion cases
- if ( pEnemy != NULL && pEnemy != pTarget )
- {
- // I have an enemy that is not this thing. If that enemy is near, I shouldn't become distracted.
- if ( GetAbsOrigin().DistToSqr( pEnemy->GetAbsOrigin()) < Square(15*12) )
- return priority;
- }
-
- // Targets near our follow target have a higher priority to us
- if ( m_FollowBehavior.GetFollowTarget() &&
- m_FollowBehavior.GetFollowTarget()->GetAbsOrigin().DistToSqr( pTarget->GetAbsOrigin() ) < Square(25*12) )
- {
- priority++;
- }
-
- // Flipped antlions are of lower priority
- CAI_BaseNPC *pNPC = pTarget->MyNPCPointer();
- if ( pNPC && pNPC->Classify() == CLASS_ANTLION && pNPC->GetActivity() == ACT_ANTLION_ZAP_FLIP )
- priority--;
-
- return priority;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: back away from overly close zombies
-//-----------------------------------------------------------------------------
-Disposition_t CNPC_Vortigaunt::IRelationType( CBaseEntity *pTarget )
-{
- if ( pTarget == NULL )
- return D_NU;
-
- Disposition_t disposition = BaseClass::IRelationType( pTarget );
-
- if ( pTarget->Classify() == CLASS_ZOMBIE && disposition == D_HT )
- {
- if( GetAbsOrigin().DistToSqr(pTarget->GetAbsOrigin()) < VORTIGAUNT_FEAR_ZOMBIE_DIST_SQR )
- {
- // Be afraid of a zombie that's near if I'm not allowed to dodge. This will make Alyx back away.
- return D_FR;
- }
- }
-
- return disposition;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Determines whether the heal gesture can successfully reach the player
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::HealGestureHasLOS( void )
-{
- //For now the player is always our target
- CBaseEntity *pTargetEnt = AI_GetSinglePlayer();
- if ( pTargetEnt == NULL )
- return false;
-
- // Find our left hand as the starting point
- Vector vecHandPos;
- QAngle vecHandAngle;
- GetAttachment( m_iRightHandAttachment, vecHandPos, vecHandAngle );
-
- // Trace to our target, skipping ourselves and the target
- trace_t tr;
- CTraceFilterSkipTwoEntities filter( this, pTargetEnt, COLLISION_GROUP_NONE );
- UTIL_TraceLine( vecHandPos, pTargetEnt->WorldSpaceCenter(), MASK_SHOT, &filter, &tr );
-
- // Must be clear
- if ( tr.fraction < 1.0f || tr.startsolid || tr.allsolid )
- return false;
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Gather conditions for our healing behavior
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::GatherHealConditions( void )
-{
- ClearCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR );
- ClearCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED );
- ClearCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US );
-
- // We stop if there are enemies around
- if ( m_bArmorRechargeEnabled == false ||
- HasCondition( COND_NEW_ENEMY ) ||
- HasCondition( COND_HEAR_DANGER ) ||
- HasCondition( COND_HEAVY_DAMAGE ) )
- {
- ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
- return;
- }
-
- // Start by assuming that we'll succeed
- SetCondition( COND_VORTIGAUNT_HEAL_VALID );
-
- // Just assume we should
- if ( m_bForceArmorRecharge )
- return;
-
- // For now we only act on the player
- CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
- if ( pPlayer != NULL )
- {
- Vector vecToPlayer = ( pPlayer->WorldSpaceCenter() - WorldSpaceCenter() );
-
- // Make sure he's still within heal range
- if ( vecToPlayer.LengthSqr() > (HEAL_RANGE*HEAL_RANGE) )
- {
- SetCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR );
- // NOTE: We allow him to send tokens over large distances
- //ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
- }
-
- vecToPlayer.z = 0.0f;
- VectorNormalize( vecToPlayer );
- Vector facingDir = BodyDirection2D();
-
- // Check our direction towards the player
- if ( DotProduct( vecToPlayer, facingDir ) < VIEW_FIELD_NARROW )
- {
- SetCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US );
- ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
- }
-
- // Now ensure he's not blocked
- if ( HealGestureHasLOS() == false )
- {
- SetCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED );
- ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
- }
- }
- else
- {
- ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Gather conditions specific to this NPC
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::GatherConditions( void )
-{
- // Call our base
- BaseClass::GatherConditions();
-
- // See if we're able to heal now
- if ( HealBehaviorAvailable() && ( m_flNextHealTime < gpGlobals->curtime ) )
- {
- // See if we should heal the player
- CBaseEntity *pHealTarget = FindHealTarget();
- if ( pHealTarget != NULL )
- {
- SetHealTarget( pHealTarget, false );
- }
-
- // Don't try again for a period of time
- m_flNextHealTime = gpGlobals->curtime + 2.0f;
- }
-
- // Get our state for healing
- GatherHealConditions();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::DispelAntlions( const Vector &vecOrigin, float flRadius, bool bDispel /*= true*/ )
-{
- // More effects
- if ( bDispel )
- {
- UTIL_ScreenShake( vecOrigin, 20.0f, 150.0, 1.0, 1250.0f, SHAKE_START );
-
- CBroadcastRecipientFilter filter2;
- te->BeamRingPoint( filter2, 0, vecOrigin, //origin
- 64, //start radius
- 800, //end radius
- m_nLightningSprite, //texture
- 0, //halo index
- 0, //start frame
- 2, //framerate
- 0.1f, //life
- 128, //width
- 0, //spread
- 0, //amplitude
- 255, //r
- 255, //g
- 225, //b
- 32, //a
- 0, //speed
- FBEAM_FADEOUT
- );
-
- //Shockring
- te->BeamRingPoint( filter2, 0, vecOrigin + Vector( 0, 0, 16 ), //origin
- 64, //start radius
- 800, //end radius
- m_nLightningSprite, //texture
- 0, //halo index
- 0, //start frame
- 2, //framerate
- 0.2f, //life
- 64, //width
- 0, //spread
- 0, //amplitude
- 255, //r
- 255, //g
- 225, //b
- 200, //a
- 0, //speed
- FBEAM_FADEOUT
- );
-
- // Ground effects
- CEffectData data;
- data.m_vOrigin = vecOrigin;
-
- DispatchEffect( "VortDispel", data );
- }
-
- // Make antlions flip all around us!
- trace_t tr;
- CBaseEntity *pEnemySearch[32];
- int nNumEnemies = UTIL_EntitiesInBox( pEnemySearch, ARRAYSIZE(pEnemySearch), vecOrigin-Vector(flRadius,flRadius,flRadius), vecOrigin+Vector(flRadius,flRadius,flRadius), FL_NPC );
- for ( int i = 0; i < nNumEnemies; i++ )
- {
- // We only care about antlions
- if ( IsAntlion( pEnemySearch[i] ) == false )
- continue;
-
- CNPC_Antlion *pAntlion = static_cast<CNPC_Antlion *>(pEnemySearch[i]);
- if ( pAntlion->IsWorker() == false )
- {
- // Attempt to trace a line to hit the target
- UTIL_TraceLine( vecOrigin, pAntlion->BodyTarget( vecOrigin ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
- if ( tr.fraction < 1.0f && tr.m_pEnt != pAntlion )
- continue;
-
- Vector vecDir = ( pAntlion->GetAbsOrigin() - vecOrigin );
- vecDir[2] = 0.0f;
- float flDist = VectorNormalize( vecDir );
-
- float flFalloff = RemapValClamped( flDist, 0, flRadius*0.75f, 1.0f, 0.1f );
-
- vecDir *= ( flRadius * 1.5f * flFalloff );
- vecDir[2] += ( flRadius * 0.5f * flFalloff );
-
- pAntlion->ApplyAbsVelocityImpulse( vecDir );
-
- // gib nearby antlions, knock over distant ones.
- if ( flDist < 128 && bDispel )
- {
- // splat!
- vecDir[2] += 400.0f * flFalloff;
- CTakeDamageInfo dmgInfo( this, this, vecDir, pAntlion->GetAbsOrigin() , 100, DMG_SHOCK );
- pAntlion->TakeDamage( dmgInfo );
- }
- else
- {
- // Turn them over
- pAntlion->Flip( true );
-
- // Display an effect between us and the flipped creature
- // Tell the client to start an arm beam
- /*
- unsigned char uchAttachment = pAntlion->LookupAttachment( "mouth" );
- EntityMessageBegin( this, true );
- WRITE_BYTE( VORTFX_ARMBEAM );
- WRITE_LONG( pAntlion->entindex() );
- WRITE_BYTE( uchAttachment );
- WRITE_VEC3COORD( vecOrigin );
- WRITE_VEC3NORMAL( Vector( 0, 0, 1 ) );
- MessageEnd();
- */
- }
- }
- }
-
- // Stop our effects
- if ( bDispel )
- {
- EndHandGlow( VORTIGAUNT_BEAM_ALL );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Simply tell us to dispel
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputDispel( inputdata_t &data )
-{
- SetCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Decide when we're allowed to interact with other NPCs
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::CanRunAScriptedNPCInteraction( bool bForced /*= false*/ )
-{
- // Never interrupt a range attack!
- if ( InAttackSequence() )
- return false;
-
- // Can't do them while we're trying to heal the player
- if ( m_eHealState != HEAL_STATE_NONE )
- return false;
-
- return BaseClass::CanRunAScriptedNPCInteraction( bForced );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : interrupt -
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::SetScriptedScheduleIgnoreConditions( Interruptability_t interrupt )
-{
- // First add our base conditions to ignore
- BaseClass::SetScriptedScheduleIgnoreConditions( interrupt );
-
- static int g_VortConditions[] =
- {
- COND_VORTIGAUNT_CAN_HEAL,
- COND_VORTIGAUNT_DISPEL_ANTLIONS,
- COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR,
- COND_VORTIGAUNT_HEAL_TARGET_BLOCKED,
- COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US,
- COND_VORTIGAUNT_HEAL_VALID
- };
-
- ClearIgnoreConditions( g_VortConditions, ARRAYSIZE(g_VortConditions) );
-
- // Ignore these if we're damage only
- if ( interrupt > GENERAL_INTERRUPTABILITY )
- SetIgnoreConditions( g_VortConditions, ARRAYSIZE(g_VortConditions) );
-}
-
-//-----------------------------------------------------------------------------
-// !!!HACKHACK - EP2 - Stop vortigaunt taking all physics damage to prevent it dying
-// in freak accidents resembling spontaneous stress damage death (which are now impossible)
-// Also stop it taking damage from flames: Fixes it being burnt to death from entity flames
-// attached to random debris chunks while inside scripted sequences.
-//-----------------------------------------------------------------------------
-int CNPC_Vortigaunt::OnTakeDamage_Alive( const CTakeDamageInfo &info )
-{
- if( info.GetDamageType() & (DMG_CRUSH | DMG_BURN) )
- return 0;
-
- // vital vortigaunts (eg the vortigoth in ep2) take less damage from explosions
- // so that zombines don't blow them up disappointingly. They take less damage
- // still from antlion workers.
- if ( Classify() == CLASS_PLAYER_ALLY_VITAL )
- {
- // half damage
- CTakeDamageInfo subInfo = info;
-
- // take less damage from antlion worker acid/poison
- if ( info.GetAttacker()->Classify() == CLASS_ANTLION &&
- (info.GetDamageType() & ( DMG_ACID | DMG_POISON ))!=0
- )
- {
- subInfo.ScaleDamage( sk_vortigaunt_vital_antlion_worker_dmg.GetFloat() );
- }
-
- else if ( info.GetDamageType() & DMG_BLAST )
- {
- subInfo.ScaleDamage( 0.5f );
- }
-
- return BaseClass::OnTakeDamage_Alive( subInfo );
- }
-
- return BaseClass::OnTakeDamage_Alive( info );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Override move and shoot if we're following someone
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::ShouldMoveAndShoot( void )
-{
- if ( m_FollowBehavior.IsActive() )
- return true;
-
- return BaseClass::ShouldMoveAndShoot();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: notification from a grub that I squished it. This special case
-// function is necessary because what you would think to be the ordinary
-// channels are in fact missing: Event_KilledOther doesn't actually do anything
-// and KilledNPC expects a BaseCombatCharacter, and always uses the same Speak
-// line.
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::OnSquishedGrub( const CBaseEntity *pGrub )
-{
- Speak(TLK_SQUISHED_GRUB);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::AimGun( void )
-{
- // If our aim lock is on, don't bother
- if ( m_flAimDelay >= gpGlobals->curtime )
- return;
-
- // Aim at our target
- if ( GetEnemy() )
- {
- Vector vecShootOrigin;
-
- vecShootOrigin = Weapon_ShootPosition();
- Vector vecShootDir;
-
- // Aim where it is
- vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
-
- if ( g_debug_vortigaunt_aim.GetBool() )
- {
- NDebugOverlay::Line( WorldSpaceCenter(), WorldSpaceCenter() + vecShootDir * 256.0f, 255, 0, 0, true, 0.1f );
- }
-
- SetAim( vecShootDir );
- }
- else
- {
- RelaxAim();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: A scripted sequence has interrupted us
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::OnStartScene( void )
-{
- // Watch our hand state
- EndHandGlow( VORTIGAUNT_BEAM_ALL );
- m_fGlowChangeTime = gpGlobals->curtime + 0.1f; // No more glows for this amount of time!
-
- BaseClass::OnStartScene();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Output : Returns true on success, false on failure.
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::IsInterruptable( void )
-{
- // Don't interrupt my attack schedule!
- if ( InAttackSequence() )
- return false;
-
- return BaseClass::IsInterruptable();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Start overriding our animations to "carry" an NPC
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputBeginCarryNPC( inputdata_t &indputdata )
-{
- m_bCarryingNPC = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Stop overriding our animations for carrying an NPC
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::InputEndCarryNPC( inputdata_t &indputdata )
-{
- m_bCarryingNPC = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Turn off flinching under certain circumstances
-//-----------------------------------------------------------------------------
-bool CNPC_Vortigaunt::CanFlinch( void )
-{
- if ( IsActiveDynamicInteraction() )
- return false;
-
- if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
- return false;
-
- if ( IsCurSchedule( SCHED_VORTIGAUNT_DISPEL_ANTLIONS ) || IsCurSchedule( SCHED_RANGE_ATTACK1 ) )
- return false;
-
- return BaseClass::CanFlinch();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CNPC_Vortigaunt::OnUpdateShotRegulator( void )
-{
- // Do nothing, we're not really running this code in a normal manner
- GetShotRegulator()->SetBurstInterval( 2.0f, 2.0f );
- GetShotRegulator()->SetBurstShotCountRange( 1, 1 );
- GetShotRegulator()->SetRestInterval( 2.0f, 2.0f );
-}
-
-/*
-IMPLEMENT_SERVERCLASS_ST( CVortigauntChargeToken, DT_VortigauntChargeToken )
- SendPropFloat( SENDINFO(m_flFadeOutTime), 0, SPROP_NOSCALE),
- SendPropBool( SENDINFO(m_bFadeOut) ),
- SendPropFloat( SENDINFO(m_flScale), 0, SPROP_NOSCALE),
-END_SEND_TABLE()
-*/
-
-//------------------------------------------------------------------------------
-//
-// Schedules
-//
-//------------------------------------------------------------------------------
-
-AI_BEGIN_CUSTOM_NPC( npc_vortigaunt, CNPC_Vortigaunt )
-
- DECLARE_USES_SCHEDULE_PROVIDER( CAI_LeadBehavior )
-
- DECLARE_TASK(TASK_VORTIGAUNT_HEAL)
- DECLARE_TASK(TASK_VORTIGAUNT_EXTRACT)
- DECLARE_TASK(TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT)
- DECLARE_TASK(TASK_VORTIGAUNT_WAIT_FOR_PLAYER)
-
- DECLARE_TASK( TASK_VORTIGAUNT_EXTRACT_WARMUP )
- DECLARE_TASK( TASK_VORTIGAUNT_EXTRACT_COOLDOWN )
- DECLARE_TASK( TASK_VORTIGAUNT_GET_HEAL_TARGET )
- DECLARE_TASK( TASK_VORTIGAUNT_DISPEL_ANTLIONS )
-
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_AIM)
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_START_HEAL )
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_HEAL_LOOP )
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_END_HEAL )
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_TO_ACTION )
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_TO_IDLE )
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_HEAL )
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_DISPEL )
- DECLARE_ACTIVITY( ACT_VORTIGAUNT_ANTLION_THROW )
-
- DECLARE_CONDITION( COND_VORTIGAUNT_CAN_HEAL )
- DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR )
- DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED )
- DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US )
- DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_VALID )
- DECLARE_CONDITION( COND_VORTIGAUNT_DISPEL_ANTLIONS )
-
- DECLARE_SQUADSLOT( SQUAD_SLOT_HEAL_PLAYER )
-
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_CLAW_LEFT )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_CLAW_RIGHT )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_POWERUP )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_SHOOT )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_DONE )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTGLOW )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTBEAMS )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTSOUND )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_SWING_SOUND )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_SHOOT_SOUNDSTART )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_PAUSE )
-
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_START_DISPEL )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_ACCEL_DISPEL )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_DISPEL )
-
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_START_HURT_GLOW )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_STOP_HURT_GLOW )
-
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_START_HEAL_GLOW )
- DECLARE_ANIMEVENT( AE_VORTIGAUNT_STOP_HEAL_GLOW )
-
- //=========================================================
- // > SCHED_VORTIGAUNT_RANGE_ATTACK
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORTIGAUNT_RANGE_ATTACK,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_IDEAL 0"
- " TASK_ANNOUNCE_ATTACK 0"
- " TASK_RANGE_ATTACK1 0"
- " TASK_WAIT 0.2" // Wait a sec before killing beams
- ""
- " Interrupts"
- " COND_NO_CUSTOM_INTERRUPTS"
- );
-
-
- //=========================================================
- // > SCHED_VORTIGAUNT_HEAL
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORTIGAUNT_HEAL,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_STAND"
- " TASK_STOP_MOVING 0"
- " TASK_VORTIGAUNT_GET_HEAL_TARGET 0"
- " TASK_GET_PATH_TO_TARGET 0"
- " TASK_MOVE_TO_TARGET_RANGE 350"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_PLAYER 0"
- " TASK_VORTIGAUNT_HEAL 0"
- ""
- " Interrupts"
- " COND_HEAVY_DAMAGE"
- );
-
- //=========================================================
- // > SCHED_VORTIGAUNT_STAND
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORTIGAUNT_STAND,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
- " TASK_WAIT 2" // repick IDLESTAND every two seconds."
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_SMELL"
- " COND_PROVOKED"
- " COND_HEAR_COMBAT"
- " COND_HEAR_DANGER"
- " COND_VORTIGAUNT_DISPEL_ANTLIONS"
- " COND_VORTIGAUNT_CAN_HEAL"
- );
-
- //=========================================================
- // > SCHED_VORTIGAUNT_EXTRACT_BUGBAIT
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORTIGAUNT_EXTRACT_BUGBAIT,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_STAND"
- " TASK_STOP_MOVING 0"
- " TASK_GET_PATH_TO_TARGET 0"
- " TASK_MOVE_TO_TARGET_RANGE 128" // Move within 128 of target ent (client)
- " TASK_STOP_MOVING 0"
- " TASK_VORTIGAUNT_WAIT_FOR_PLAYER 0"
- " TASK_SPEAK_SENTENCE 500" // Start extracting sentence
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_FACE_TARGET 0"
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_VORTIGAUNT_EXTRACT_WARMUP 0"
- " TASK_VORTIGAUNT_EXTRACT 0"
- " TASK_VORTIGAUNT_EXTRACT_COOLDOWN 0"
- " TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT 0"
- " TASK_SPEAK_SENTENCE 501" // Finish extracting sentence
- " TASK_WAIT_FOR_SPEAK_FINISH 1"
- " TASK_WAIT 2"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // > SCHED_VORTIGAUNT_FACE_PLAYER
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORTIGAUNT_FACE_PLAYER,
-
- " Tasks"
- " TASK_STOP_MOVING 0"
- " TASK_TARGET_PLAYER 0"
- " TASK_FACE_PLAYER 0"
- " TASK_WAIT 3"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_VORTIGAUNT_DISPEL_ANTLIONS"
- " COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR"
- " COND_VORTIGAUNT_HEAL_TARGET_BLOCKED"
- " COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US"
- );
-
- //=========================================================
- // > SCHED_VORTIGAUNT_RUN_TO_PLAYER
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORTIGAUNT_RUN_TO_PLAYER,
-
- " Tasks"
- " TASK_TARGET_PLAYER 0"
- " TASK_GET_PATH_TO_TARGET 0"
- " TASK_MOVE_TO_TARGET_RANGE 350"
- ""
- " Interrupts"
- " COND_HEAVY_DAMAGE"
- );
-
- //=========================================================
- // > SCHED_VORTIGAUNT_DISPEL_ANTLIONS
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORTIGAUNT_DISPEL_ANTLIONS,
-
- " Tasks"
- " TASK_VORTIGAUNT_DISPEL_ANTLIONS 0"
- ""
- " Interrupts"
- " COND_NO_CUSTOM_INTERRUPTS"
- );
-
- //=========================================================
- //
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORT_FLEE_FROM_BEST_SOUND,
-
- " Tasks"
- " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COWER"
- " TASK_GET_PATH_AWAY_FROM_BEST_SOUND 600"
- " TASK_RUN_PATH_TIMED 1.5"
- " TASK_STOP_MOVING 0"
- ""
- " Interrupts"
- )
-
- //=========================================================
- // > AlertFace best sound
- //=========================================================
- DEFINE_SCHEDULE
- (
- SCHED_VORT_ALERT_FACE_BESTSOUND,
-
- " Tasks"
- " TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0"
- " TASK_STOP_MOVING 0"
- " TASK_FACE_SAVEPOSITION 0"
- ""
- " Interrupts"
- " COND_NEW_ENEMY"
- " COND_SEE_FEAR"
- " COND_LIGHT_DAMAGE"
- " COND_HEAVY_DAMAGE"
- " COND_PROVOKED"
- " COND_HEAR_DANGER"
- );
-AI_END_CUSTOM_NPC()
-
-
-//=============================================================================
-//
-// Charge Token
-//
-//=============================================================================
-
-LINK_ENTITY_TO_CLASS( vort_charge_token, CVortigauntChargeToken );
-
-BEGIN_DATADESC( CVortigauntChargeToken )
- DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
- DEFINE_FIELD( m_flLifetime, FIELD_TIME ),
- DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ),
-
- DEFINE_ENTITYFUNC( SeekThink ),
- DEFINE_ENTITYFUNC( SeekTouch ),
-END_DATADESC()
-
-IMPLEMENT_SERVERCLASS_ST( CVortigauntChargeToken, DT_VortigauntChargeToken )
- SendPropBool( SENDINFO(m_bFadeOut) ),
-END_SEND_TABLE()
-
-CVortigauntChargeToken::CVortigauntChargeToken( void ) :
-m_hTarget( NULL )
-{
- m_bFadeOut = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Create a charge token for the player to collect
-// Input : &vecOrigin - Where we start
-// *pOwner - Who created us
-// *pTarget - Who we're seeking towards
-//-----------------------------------------------------------------------------
-CVortigauntChargeToken *CVortigauntChargeToken::CreateChargeToken( const Vector &vecOrigin, CBaseEntity *pOwner, CBaseEntity *pTarget )
-{
- CVortigauntChargeToken *pToken = (CVortigauntChargeToken *) CreateEntityByName( "vort_charge_token" );
- if ( pToken == NULL )
- return NULL;
-
- // Set up our internal data
- UTIL_SetOrigin( pToken, vecOrigin );
- pToken->SetOwnerEntity( pOwner );
- pToken->SetTargetEntity( pTarget );
- pToken->SetThink( &CVortigauntChargeToken::SeekThink );
- pToken->SetTouch( &CVortigauntChargeToken::SeekTouch );
- pToken->Spawn();
-
- // Start out at the same velocity as our owner
- Vector vecInitialVelocity;
- CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pOwner);
- if ( pAnimating != NULL )
- {
- vecInitialVelocity = pAnimating->GetGroundSpeedVelocity();
- }
- else
- {
- vecInitialVelocity = pTarget->GetSmoothedVelocity();
- }
-
- // Start out at that speed
- pToken->SetAbsVelocity( vecInitialVelocity );
-
- return pToken;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CVortigauntChargeToken::Precache( void )
-{
- PrecacheParticleSystem( "vortigaunt_charge_token" );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: We want to move through grates!
-//-----------------------------------------------------------------------------
-unsigned int CVortigauntChargeToken::PhysicsSolidMaskForEntity( void ) const
-{
- return MASK_SHOT;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CVortigauntChargeToken::Spawn( void )
-{
- Precache();
-
- // Point-sized
- UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
-
- SetMoveType( MOVETYPE_FLY );
- SetSolid( SOLID_BBOX );
- SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
- SetGravity( 0.0f );
-
- // No model but we still need to force this!
- AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
-
- SetNextThink( gpGlobals->curtime + 0.05f );
-
- m_flLifetime = gpGlobals->curtime + VORTIGAUNT_CURE_LIFESPAN;
-
- BaseClass::Spawn();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Creates an influence vector which causes the token to move away from obstructions
-//-----------------------------------------------------------------------------
-Vector CVortigauntChargeToken::GetSteerVector( const Vector &vecForward )
-{
- Vector vecSteer = vec3_origin;
- Vector vecRight, vecUp;
- VectorVectors( vecForward, vecRight, vecUp );
-
- // Use two probes fanned out a head of us
- Vector vecProbe;
- float flSpeed = GetAbsVelocity().Length();
-
- // Try right
- vecProbe = vecForward + vecRight;
- vecProbe *= flSpeed;
-
- // We ignore multiple targets
- CTraceFilterSimpleList filterSkip( COLLISION_GROUP_NONE );
- filterSkip.AddEntityToIgnore( this );
- filterSkip.AddEntityToIgnore( GetOwnerEntity() );
- filterSkip.AddEntityToIgnore( m_hTarget );
-
- trace_t tr;
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecProbe, MASK_SHOT, &filterSkip, &tr );
- vecSteer -= vecRight * 100.0f * ( 1.0f - tr.fraction );
-
- // Try left
- vecProbe = vecForward - vecRight;
- vecProbe *= flSpeed;
-
- UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecProbe, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
- vecSteer += vecRight * 100.0f * ( 1.0f - tr.fraction );
-
- return vecSteer;
-}
-
-#define VTOKEN_MAX_SPEED 320.0f // U/sec
-#define VTOKEN_ACCEL_SPEED 320.0f // '
-
-//-----------------------------------------------------------------------------
-// Purpose: Move towards our target entity with accel/decel parameters
-//-----------------------------------------------------------------------------
-void CVortigauntChargeToken::SeekThink( void )
-{
- // Move away from the creator and towards the target
- if ( m_hTarget == NULL || m_flLifetime < gpGlobals->curtime )
- {
- // TODO: Play an extinguish sound and fade out
- FadeAndDie();
- return;
- }
-
- // Find the direction towards our goal and start to go there
- Vector vecDir = ( m_hTarget->WorldSpaceCenter() - GetAbsOrigin() );
- VectorNormalize( vecDir );
-
- float flSpeed = GetAbsVelocity().Length();
- float flDelta = gpGlobals->curtime - GetLastThink();
-
- if ( flSpeed < VTOKEN_MAX_SPEED )
- {
- // Accelerate by the desired amount
- flSpeed += ( VTOKEN_ACCEL_SPEED * flDelta );
- if ( flSpeed > VTOKEN_MAX_SPEED )
- {
- flSpeed = VTOKEN_MAX_SPEED;
- }
- }
-
- // Steer!
- Vector vecRight, vecUp;
- VectorVectors( vecDir, vecRight, vecUp );
- Vector vecOffset = vec3_origin;
- vecOffset += vecUp * cos( gpGlobals->curtime * 20.0f ) * 200.0f * gpGlobals->frametime;
- vecOffset += vecRight * sin( gpGlobals->curtime * 15.0f ) * 200.0f * gpGlobals->frametime;
-
- vecOffset += GetSteerVector( vecDir );
-
- SetAbsVelocity( ( vecDir * flSpeed ) + vecOffset );
- SetNextThink( gpGlobals->curtime + 0.05f );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CVortigauntChargeToken::SeekTouch( CBaseEntity *pOther )
-{
- // Make sure this is a player
- CBasePlayer *pPlayer = ToBasePlayer( pOther );
- if ( pPlayer == NULL )
- return;
-
- // FIXME: This probably isn't that interesting for single player missions
- if ( pPlayer != m_hTarget )
- return;
-
- // TODO: Play a special noise for this event!
- EmitSound( "NPC_Vortigaunt.SuitOn" );
-
- // Charge the suit's armor
- if ( pPlayer->ArmorValue() < sk_vortigaunt_armor_charge.GetInt() )
- {
- pPlayer->IncrementArmorValue( sk_vortigaunt_armor_charge_per_token.GetInt()+random->RandomInt( -1, 1 ), sk_vortigaunt_armor_charge.GetInt() );
- }
-
- // Stay attached to the thing we hit as we fade away
- SetSolidFlags( FSOLID_NOT_SOLID );
- SetMoveType( MOVETYPE_NONE );
- SetParent( pOther );
-
- // TODO: Play a "poof!" effect here?
- FadeAndDie();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flTime -
-//-----------------------------------------------------------------------------
-void CVortigauntChargeToken::FadeAndDie( void )
-{
- SetTouch( NULL );
-
- SetAbsVelocity( vec3_origin );
-
- m_bFadeOut = true;
- SetThink( &CBaseEntity::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 2.0f );
-}
-
-//=============================================================================
-//
-// Dispel Effect
-//
-//=============================================================================
-
-LINK_ENTITY_TO_CLASS( vort_effect_dispel, CVortigauntEffectDispel );
-
-BEGIN_DATADESC( CVortigauntEffectDispel )
- DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ),
-END_DATADESC()
-
-IMPLEMENT_SERVERCLASS_ST( CVortigauntEffectDispel, DT_VortigauntEffectDispel )
- SendPropBool( SENDINFO(m_bFadeOut) ),
-END_SEND_TABLE()
-
-CVortigauntEffectDispel::CVortigauntEffectDispel( void )
-{
- m_bFadeOut = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Create a charge token for the player to collect
-// Input : &vecOrigin - Where we start
-// *pOwner - Who created us
-// *pTarget - Who we're seeking towards
-//-----------------------------------------------------------------------------
-CVortigauntEffectDispel *CVortigauntEffectDispel::CreateEffectDispel( const Vector &vecOrigin, CBaseEntity *pOwner, CBaseEntity *pTarget )
-{
- CVortigauntEffectDispel *pToken = (CVortigauntEffectDispel *) CreateEntityByName( "vort_effect_dispel" );
- if ( pToken == NULL )
- return NULL;
-
- // Set up our internal data
- UTIL_SetOrigin( pToken, vecOrigin );
- pToken->SetOwnerEntity( pOwner );
- pToken->Spawn();
-
- return pToken;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CVortigauntEffectDispel::Spawn( void )
-{
- Precache();
-
- UTIL_SetSize( this, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ) );
-
- SetSolid( SOLID_BBOX );
- SetSolidFlags( FSOLID_NOT_SOLID );
-
- // No model but we still need to force this!
- AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
-
- BaseClass::Spawn();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : flTime -
-//-----------------------------------------------------------------------------
-void CVortigauntEffectDispel::FadeAndDie( void )
-{
- m_bFadeOut = true;
- SetThink( &CBaseEntity::SUB_Remove );
- SetNextThink( gpGlobals->curtime + 2.0f );
-}
-
-
-//=============================================================================
-//
-// Flesh effect target (used for orchestrating the "Invisible Alyx" moment
-//
-//=============================================================================
-
-#ifdef HL2_EPISODIC
-
-class CFleshEffectTarget : public CPointEntity
-{
- DECLARE_CLASS( CFleshEffectTarget, CPointEntity );
-
-public:
- void InputSetRadius( inputdata_t &inputData );
-
- virtual void Spawn( void )
- {
- BaseClass::Spawn();
-
- AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
- }
-
-private:
-
- CNetworkVar( float, m_flRadius );
- CNetworkVar( float, m_flScaleTime );
-
- DECLARE_SERVERCLASS();
- DECLARE_DATADESC();
-};
-
-LINK_ENTITY_TO_CLASS( point_flesh_effect_target, CFleshEffectTarget );
-
-BEGIN_DATADESC( CFleshEffectTarget )
-
- DEFINE_FIELD( m_flScaleTime, FIELD_TIME ),
- DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
-
- DEFINE_INPUTFUNC( FIELD_VECTOR, "SetRadius", InputSetRadius ),
-
-END_DATADESC()
-
-IMPLEMENT_SERVERCLASS_ST( CFleshEffectTarget, DT_FleshEffectTarget )
- SendPropFloat( SENDINFO(m_flRadius), 0, SPROP_NOSCALE),
- SendPropFloat( SENDINFO(m_flScaleTime), 0, SPROP_NOSCALE),
-END_SEND_TABLE()
-
-void CFleshEffectTarget::InputSetRadius( inputdata_t &inputData )
-{
- Vector vecRadius;
- inputData.value.Vector3D( vecRadius );
-
- m_flRadius = vecRadius.x;
- m_flScaleTime = vecRadius.y;
-}
-
-#endif // HL2_EPISODIC
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Vortigaunt - now much friendlier!
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "beam_shared.h"
+#include "globalstate.h"
+#include "npcevent.h"
+#include "Sprite.h"
+#include "IEffects.h"
+#include "te_effect_dispatch.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+#include "physics_prop_ragdoll.h"
+#include "RagdollBoogie.h"
+#include "ai_squadslot.h"
+#include "npc_antlion.h"
+#include "particle_parse.h"
+#include "particle_system.h"
+#include "ai_senses.h"
+
+#include "npc_vortigaunt_episodic.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define HAND_LEFT 0
+#define HAND_RIGHT 1
+#define HAND_BOTH 2
+
+class CVortigauntChargeToken;
+
+#define VORTIGAUNT_LIMP_HEALTH 20
+#define VORTIGAUNT_SENTENCE_VOLUME (float)0.35 // volume of vortigaunt sentences
+#define VORTIGAUNT_VOL 0.35 // volume of vortigaunt sounds
+#define VORTIGAUNT_ATTN ATTN_NORM // attenutation of vortigaunt sentences
+#define VORTIGAUNT_HEAL_RECHARGE 30.0 // How long to rest between heals
+#define VORTIGAUNT_ZAP_GLOWGROW_TIME 0.5 // How long does glow last
+#define VORTIGAUNT_HEAL_GLOWGROW_TIME 1.4 // How long does glow last
+#define VORTIGAUNT_GLOWFADE_TIME 0.5 // How long does it fade
+#define VORTIGAUNT_CURE_LIFESPAN 8.0 // cure tokens only live this long (so they don't get stuck on geometry)
+
+#define VORTIGAUNT_BLUE_FADE_TIME 2.25f // takes this long to fade from green to blue or back
+
+#define VORTIGAUNT_FEAR_ZOMBIE_DIST_SQR Square(60) // back away from zombies inside this radius
+
+#define PLAYER_CRITICAL_HEALTH_PERC 0.15f
+
+#define TLK_SQUISHED_GRUB "TLK_SQUISHED_GRUB" // response rule for vortigaunts when they step on a grub
+
+static const char *VORTIGAUNT_LEFT_CLAW = "leftclaw";
+static const char *VORTIGAUNT_RIGHT_CLAW = "rightclaw";
+
+static const char *VORT_CURE = "VORT_CURE";
+static const char *VORT_CURESTOP = "VORT_CURESTOP";
+static const char *VORT_CURE_INTERRUPT = "VORT_CURE_INTERRUPT";
+static const char *VORT_ATTACK = "VORT_ATTACK";
+static const char *VORT_MAD = "VORT_MAD";
+static const char *VORT_SHOT = "VORT_SHOT";
+static const char *VORT_PAIN = "VORT_PAIN";
+static const char *VORT_DIE = "VORT_DIE";
+static const char *VORT_KILL = "VORT_KILL";
+static const char *VORT_LINE_FIRE = "VORT_LINE_FIRE";
+static const char *VORT_POK = "VORT_POK";
+static const char *VORT_EXTRACT_START = "VORT_EXTRACT_START";
+static const char *VORT_EXTRACT_FINISH = "VORT_EXTRACT_FINISH";
+
+// Target must be within this range to heal
+#define HEAL_RANGE (40*12) //ft
+#define HEAL_SEARCH_RANGE (40*12) //ft
+
+ConVar sk_vortigaunt_armor_charge( "sk_vortigaunt_armor_charge","30");
+ConVar sk_vortigaunt_armor_charge_per_token( "sk_vortigaunt_armor_charge_per_token","5");
+
+ConVar sk_vortigaunt_health( "sk_vortigaunt_health","0");
+ConVar sk_vortigaunt_dmg_claw( "sk_vortigaunt_dmg_claw","0");
+ConVar sk_vortigaunt_dmg_rake( "sk_vortigaunt_dmg_rake","0");
+ConVar sk_vortigaunt_dmg_zap( "sk_vortigaunt_dmg_zap","0");
+ConVar sk_vortigaunt_zap_range( "sk_vortigaunt_zap_range", "100", FCVAR_NONE, "Range of vortigaunt's ranged attack (feet)" );
+ConVar sk_vortigaunt_vital_antlion_worker_dmg("sk_vortigaunt_vital_antlion_worker_dmg", "0.2", FCVAR_NONE, "Vital-ally vortigaunts scale damage taken from antlion workers by this amount." );
+
+ConVar g_debug_vortigaunt_aim( "g_debug_vortigaunt_aim", "0" );
+
+// FIXME: Move to shared code!
+#define VORTFX_ZAPBEAM 0 // Beam that damages the target
+#define VORTFX_ARMBEAM 1 // Smaller beams from the hands as we charge up
+
+//-----------------------------------------------------------------------------
+// Interactions
+//-----------------------------------------------------------------------------
+int g_interactionVortigauntStomp = 0;
+int g_interactionVortigauntStompFail = 0;
+int g_interactionVortigauntStompHit = 0;
+int g_interactionVortigauntKick = 0;
+int g_interactionVortigauntClaw = 0;
+
+//=========================================================
+// Vortigaunt activities
+//=========================================================
+int ACT_VORTIGAUNT_AIM;
+int ACT_VORTIGAUNT_START_HEAL;
+int ACT_VORTIGAUNT_HEAL_LOOP;
+int ACT_VORTIGAUNT_END_HEAL;
+int ACT_VORTIGAUNT_TO_ACTION;
+int ACT_VORTIGAUNT_TO_IDLE;
+int ACT_VORTIGAUNT_HEAL; // Heal gesture
+int ACT_VORTIGAUNT_DISPEL;
+int ACT_VORTIGAUNT_ANTLION_THROW;
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+int AE_VORTIGAUNT_CLAW_LEFT;
+int AE_VORTIGAUNT_CLAW_RIGHT;
+int AE_VORTIGAUNT_ZAP_POWERUP;
+int AE_VORTIGAUNT_ZAP_SHOOT;
+int AE_VORTIGAUNT_ZAP_DONE;
+int AE_VORTIGAUNT_HEAL_STARTGLOW;
+int AE_VORTIGAUNT_HEAL_STARTBEAMS;
+int AE_VORTIGAUNT_HEAL_STARTSOUND;
+int AE_VORTIGAUNT_SWING_SOUND;
+int AE_VORTIGAUNT_SHOOT_SOUNDSTART;
+int AE_VORTIGAUNT_HEAL_PAUSE;
+
+int AE_VORTIGAUNT_START_DISPEL; // Start the warm-up
+int AE_VORTIGAUNT_ACCEL_DISPEL; // Indicates we're ramping up
+int AE_VORTIGAUNT_DISPEL; // Actual blast
+
+int AE_VORTIGAUNT_START_HURT_GLOW; // Start the hurt handglow: 0=left, 1=right
+int AE_VORTIGAUNT_STOP_HURT_GLOW; // Turn off the hurt handglow: 0=left, 1=right
+
+int AE_VORTIGAUNT_START_HEAL_GLOW; // 0 - Left, 1 - Right
+int AE_VORTIGAUNT_STOP_HEAL_GLOW; // '
+
+//-----------------------------------------------------------------------------
+// Squad slots
+//-----------------------------------------------------------------------------
+enum SquadSlot_T
+{
+ SQUAD_SLOT_HEAL_PLAYER = LAST_SHARED_SQUADSLOT,
+};
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_Vortigaunt )
+
+ DEFINE_FIELD( m_eHealState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNextHealTokenTime, FIELD_TIME ),
+ DEFINE_ARRAY( m_hHandEffect, FIELD_EHANDLE, 2 ),
+ DEFINE_FIELD( m_flNextHealTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bPlayerRequestedHeal, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flDispelTestTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flHealHinderedTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_nLightningSprite, FIELD_INTEGER),
+ DEFINE_FIELD( m_fGlowAge, FIELD_FLOAT),
+ DEFINE_FIELD( m_fGlowChangeTime, FIELD_TIME),
+ DEFINE_FIELD( m_bGlowTurningOn, FIELD_BOOLEAN),
+ DEFINE_FIELD( m_nCurGlowIndex, FIELD_INTEGER),
+ DEFINE_FIELD( m_flNextHealTime, FIELD_TIME),
+ DEFINE_FIELD( m_flPainTime, FIELD_TIME),
+ DEFINE_FIELD( m_nextLineFireTime, FIELD_TIME),
+ DEFINE_KEYFIELD( m_bArmorRechargeEnabled,FIELD_BOOLEAN, "ArmorRechargeEnabled" ),
+ DEFINE_FIELD( m_bForceArmorRecharge, FIELD_BOOLEAN),
+ DEFINE_FIELD( m_bExtractingBugbait, FIELD_BOOLEAN),
+ DEFINE_FIELD( m_iLeftHandAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iRightHandAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hHealTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flBlueEndFadeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bIsBlue, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIsBlack, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flAimDelay, FIELD_TIME ),
+ DEFINE_FIELD( m_bCarryingNPC, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_bRegenerateHealth, FIELD_BOOLEAN, "HealthRegenerateEnabled" ),
+
+ // m_AssaultBehavior (auto saved by AI)
+ // m_LeadBehavior
+ // DEFINE_FIELD( m_bStopLoopingSounds, FIELD_BOOLEAN ),
+
+ // Function Pointers
+ DEFINE_USEFUNC( Use ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableArmorRecharge", InputEnableArmorRecharge ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableArmorRecharge", InputDisableArmorRecharge ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "ChargeTarget", InputChargeTarget ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "ExtractBugbait", InputExtractBugbait ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableHealthRegeneration", InputEnableHealthRegeneration ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableHealthRegeneration",InputDisableHealthRegeneration ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Dispel", InputDispel ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "BeginCarryNPC", InputBeginCarryNPC ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EndCarryNPC", InputEndCarryNPC ),
+
+ DEFINE_INPUTFUNC( FIELD_BOOLEAN, "TurnBlue", InputTurnBlue ),
+ DEFINE_INPUTFUNC( FIELD_BOOLEAN, "TurnBlack", InputTurnBlack ),
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnFinishedExtractingBugbait, "OnFinishedExtractingBugbait"),
+ DEFINE_OUTPUT(m_OnFinishedChargingTarget, "OnFinishedChargingTarget"),
+ DEFINE_OUTPUT(m_OnPlayerUse, "OnPlayerUse" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( npc_vortigaunt, CNPC_Vortigaunt );
+
+IMPLEMENT_SERVERCLASS_ST( CNPC_Vortigaunt, DT_NPC_Vortigaunt )
+ SendPropTime( SENDINFO (m_flBlueEndFadeTime ) ),
+ SendPropBool( SENDINFO( m_bIsBlue )),
+ SendPropBool( SENDINFO ( m_bIsBlack ) ),
+END_SEND_TABLE()
+
+// for special behavior with rollermines
+static bool IsRoller( CBaseEntity *pRoller )
+{
+ return FClassnameIs( pRoller, "npc_rollermine" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CNPC_Vortigaunt::CNPC_Vortigaunt( void ) :
+m_bPlayerRequestedHeal( false ),
+m_flNextHealTime( 3.0f ), // Let the player settle before we decide to do this
+m_nNumTokensToSpawn( 0 ),
+m_flAimDelay( 0.0f ),
+m_eHealState( HEAL_STATE_NONE )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines whether or not the player is below a certain percentage
+// of their maximum health
+// Input : flPerc - Percentage to test against
+// Output : Returns true if less than supplied parameter
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::PlayerBelowHealthPercentage( CBasePlayer *pPlayer, float flPerc )
+{
+ if ( pPlayer == NULL )
+ return false;
+
+ if ( pPlayer->ArmorValue() )
+ return false;
+
+ float flMaxHealth = pPlayer->GetMaxHealth();
+ if ( flMaxHealth == 0.0f )
+ return false;
+
+ float flHealthPerc = (flMaxHealth != 0 ) ? (float) pPlayer->GetHealth() / flMaxHealth : 0.0f;
+ return ( flHealthPerc <= flPerc );
+}
+
+#define VORT_START_EXTRACT_SENTENCE 500
+#define VORT_FINISH_EXTRACT_SENTENCE 501
+
+//------------------------------------------------------------------------------
+// Purpose: Make the vort speak a line
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::SpeakSentence( int sentenceType )
+{
+ if (sentenceType == VORT_START_EXTRACT_SENTENCE)
+ {
+ Speak( VORT_EXTRACT_START );
+ }
+ else if (sentenceType == VORT_FINISH_EXTRACT_SENTENCE)
+ {
+ Speak( VORT_EXTRACT_FINISH );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask)
+ {
+
+ case TASK_ANNOUNCE_ATTACK:
+ {
+ // We override this to add our innate weapon
+ if ( m_AnnounceAttackTimer.Expired() )
+ {
+ if ( SpeakIfAllowed( TLK_ATTACKING, "attacking_with_weapon:zap" ) )
+ {
+ m_AnnounceAttackTimer.Set( 10, 30 );
+ }
+ }
+
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+ // Sets our target to the entity that we cached earlier.
+ case TASK_VORTIGAUNT_GET_HEAL_TARGET:
+ {
+ if ( m_hHealTarget == NULL )
+ {
+ TaskFail( FAIL_NO_TARGET );
+ }
+ else
+ {
+ SetTarget( m_hHealTarget );
+ TaskComplete();
+ }
+
+ break;
+ }
+
+ case TASK_VORTIGAUNT_EXTRACT_WARMUP:
+ {
+ ResetIdealActivity( (Activity) ACT_VORTIGAUNT_TO_ACTION );
+ break;
+ }
+
+ case TASK_VORTIGAUNT_EXTRACT:
+ {
+ SetActivity( (Activity) ACT_RANGE_ATTACK1 );
+ break;
+ }
+
+ case TASK_VORTIGAUNT_EXTRACT_COOLDOWN:
+ {
+ ResetIdealActivity( (Activity)ACT_VORTIGAUNT_TO_IDLE );
+ break;
+ }
+
+ case TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT:
+ {
+ // Cheat, and fire both outputs
+ m_OnFinishedExtractingBugbait.FireOutput( this, this );
+ TaskComplete();
+ break;
+ }
+
+ case TASK_VORTIGAUNT_HEAL:
+ {
+ // Start the layer up and give it a higher priority than normal
+ int nLayer = AddGesture( (Activity) ACT_VORTIGAUNT_HEAL );
+ SetLayerPriority( nLayer, 1.0f );
+
+ m_eHealState = HEAL_STATE_WARMUP;
+
+ CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
+ if ( pPlayer == NULL )
+ {
+ TaskFail( "NULL Player in heal schedule!\n" );
+ return;
+ }
+
+ // Figure out how many tokens to spawn
+ float flArmorDelta = (float) sk_vortigaunt_armor_charge.GetInt() - pPlayer->ArmorValue();
+ m_nNumTokensToSpawn = ceil( flArmorDelta / sk_vortigaunt_armor_charge_per_token.GetInt() );
+
+ // If we're forced to recharge, then at least send one
+ if ( m_bForceArmorRecharge && m_nNumTokensToSpawn <= 0 )
+ m_nNumTokensToSpawn = 1;
+
+ TaskComplete();
+ break;
+ }
+
+ case TASK_VORTIGAUNT_WAIT_FOR_PLAYER:
+ {
+ // Wait for the player to get near (before starting the bugbait sequence)
+ break;
+ }
+
+ case TASK_VORTIGAUNT_DISPEL_ANTLIONS:
+ {
+ if ( IsOkToCombatSpeak() )
+ {
+ Speak( TLK_VORTIGAUNT_DISPEL );
+ }
+
+ ResetIdealActivity( (Activity) ACT_VORTIGAUNT_DISPEL );
+ break;
+ }
+
+ default:
+ {
+ BaseClass::StartTask( pTask );
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RANGE_ATTACK1:
+ {
+ // If our enemy is gone, dead or out of sight, pick a new one (only if we're not delaying this behavior)
+ if ( ( HasCondition( COND_ENEMY_OCCLUDED ) ||
+ GetEnemy() == NULL ||
+ GetEnemy()->IsAlive() == false ) &&
+ m_flAimDelay < gpGlobals->curtime )
+ {
+ CBaseEntity *pNewEnemy = BestEnemy();
+ if ( pNewEnemy != NULL )
+ {
+ SetEnemy( pNewEnemy );
+ SetState( NPC_STATE_COMBAT );
+ }
+ }
+
+ BaseClass::RunTask( pTask );
+ break;
+ }
+
+ case TASK_VORTIGAUNT_EXTRACT_WARMUP:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_VORTIGAUNT_EXTRACT:
+ {
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_VORTIGAUNT_EXTRACT_COOLDOWN:
+ {
+ if ( IsActivityFinished() )
+ {
+ m_bExtractingBugbait = false;
+ TaskComplete();
+ }
+ break;
+ }
+
+ case TASK_VORTIGAUNT_WAIT_FOR_PLAYER:
+ {
+ // Wait for the player to get near (before starting the bugbait sequence)
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer != NULL )
+ {
+ GetMotor()->SetIdealYawToTargetAndUpdate( pPlayer->GetAbsOrigin(), AI_KEEP_YAW_SPEED );
+ SetTurnActivity();
+ if ( GetMotor()->DeltaIdealYaw() < 10 )
+ {
+ // Wait for the player to get close enough
+ if ( ( GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr() < Square(32*12) )
+ {
+ TaskComplete();
+ }
+ }
+ }
+ else
+ {
+ TaskFail( FAIL_NO_PLAYER );
+ }
+ break;
+ }
+
+ case TASK_VORTIGAUNT_DISPEL_ANTLIONS:
+ {
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+
+ break;
+ }
+
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::AlertSound( void )
+{
+ if ( GetEnemy() != NULL && IsOkToCombatSpeak() )
+ {
+ Speak( VORT_ATTACK );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows each sequence to have a different turn rate associated with it.
+// Output : float CNPC_Vortigaunt::MaxYawSpeed
+//-----------------------------------------------------------------------------
+float CNPC_Vortigaunt::MaxYawSpeed ( void )
+{
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ return 35;
+ break;
+ case ACT_WALK:
+ return 35;
+ break;
+ case ACT_RUN:
+ return 45;
+ break;
+ default:
+ return 35;
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Normal facing position is the eyes, but with the vort eyes on such a
+// long swing arm, this causes stability issues when an npc is trying to
+// face a vort that's also turning their head
+// Output :
+//-----------------------------------------------------------------------------
+Vector CNPC_Vortigaunt::FacingPosition( void )
+{
+ return WorldSpaceCenter();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Normal body target is the mid-point between the center and the eyes, but
+// the vort's eyes are so far offset, that this is usually in the middle of
+// empty space
+// Output :
+//-----------------------------------------------------------------------------
+
+Vector CNPC_Vortigaunt::BodyTarget( const Vector &posSrc, bool bNoisy )
+{
+ Vector low = WorldSpaceCenter() - ( WorldSpaceCenter() - GetAbsOrigin() ) * .25;
+
+ Vector high;
+ int iBone = LookupBone( "ValveBiped.neck1" );
+ if (iBone >= 0)
+ {
+ QAngle angHigh;
+ GetBonePosition( iBone, high, angHigh );
+ }
+ else
+ {
+ high = WorldSpaceCenter();
+ }
+
+ Vector delta = high - low;
+ Vector result;
+ if ( bNoisy )
+ {
+ // bell curve
+ float rand1 = random->RandomFloat( 0.0, 0.5 );
+ float rand2 = random->RandomFloat( 0.0, 0.5 );
+ result = low + delta * rand1 + delta * rand2;
+ }
+ else
+ result = low + delta * 0.5;
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Try a more predictive approach
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::InnateWeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions )
+{
+ // Try and figure out a rough idea of where we'll be after a certain time delta and base our
+ // conditions on that instead. This is necessary because the vortigaunt takes a long time to
+ // deliver his attack and looks very strange if he starts to attack when he'd never be able to hit
+ // due to movement.
+
+ const float flTimeDelta = 0.5f;
+ Vector vecNewOwnerPos;
+ Vector vecNewTargetPos;
+ UTIL_PredictedPosition( this, flTimeDelta, &vecNewOwnerPos );
+ UTIL_PredictedPosition( GetEnemy(), flTimeDelta, &vecNewTargetPos );
+
+ Vector vecDelta = vecNewTargetPos - GetEnemy()->GetAbsOrigin();
+ Vector vecFinalTargetPos = GetEnemy()->BodyTarget( vecNewOwnerPos ) + vecDelta;
+
+ // Debug data
+ /*
+ NDebugOverlay::Box( GetEnemy()->BodyTarget( vecNewOwnerPos ), -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, 0, 3.0f );
+ NDebugOverlay::Box( vecFinalTargetPos, -Vector(4,4,4), Vector(4,4,4), 255, 255, 0, 0, 3.0f );
+ NDebugOverlay::HorzArrow( GetEnemy()->BodyTarget( vecNewOwnerPos ), vecFinalTargetPos, 12.0f, 255, 0, 0, 16.0f, true, 3.0f );
+ NDebugOverlay::HorzArrow( vecNewOwnerPos, GetEnemy()->BodyTarget( vecNewOwnerPos ), 8.0f, 255, 255, 0, 32.0f, true, 3.0f );
+ */
+
+ return BaseClass::InnateWeaponLOSCondition( vecNewOwnerPos, vecFinalTargetPos, bSetConditions );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : For innate range attack
+//------------------------------------------------------------------------------
+int CNPC_Vortigaunt::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( GetEnemy() == NULL )
+ return COND_NONE;
+
+ if ( gpGlobals->curtime < m_flNextAttack )
+ return COND_NONE;
+
+ // Don't do shooting while playing a scene
+ if ( IsCurSchedule( SCHED_SCENE_GENERIC ) )
+ return COND_NONE;
+
+ // dvs: Allow up-close range attacks for episodic as the vort's melee
+ // attack is rather ineffective.
+#ifndef HL2_EPISODIC
+ if ( flDist <= 70 )
+ {
+ return( COND_TOO_CLOSE_TO_ATTACK );
+ }
+ else
+#else
+ if ( flDist < 32.0f )
+ return COND_TOO_CLOSE_TO_ATTACK;
+#endif // HL2_EPISODIC
+ if ( flDist > InnateRange1MaxRange() )
+ {
+ return( COND_TOO_FAR_TO_ATTACK );
+ }
+ else if ( flDot < 0.65 )
+ {
+ return( COND_NOT_FACING_ATTACK );
+ }
+
+#ifdef HL2_EPISODIC
+
+ // Do an extra check for workers near myself or the player
+ if ( IsAntlionWorker( GetEnemy() ) )
+ {
+ // See if it's too close to me
+ if ( ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr() < Square( AntlionWorkerBurstRadius() ) )
+ return COND_TOO_CLOSE_TO_ATTACK;
+
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer && ( pPlayer->GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr() < Square( AntlionWorkerBurstRadius() ) )
+ {
+ // Warn the player to get away!
+ CFmtStrN<128> modifiers( "antlion_worker:true" );
+ SpeakIfAllowed( TLK_DANGER, modifiers );
+ return COND_NONE;
+ }
+ }
+
+#endif // HL2_EPISODIC
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Test for close-up dispel
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ if ( m_flDispelTestTime > gpGlobals->curtime )
+ return COND_NONE;
+
+ m_flDispelTestTime = gpGlobals->curtime + 1.0f;
+
+ if ( GetEnemy() && GetEnemy()->Classify() == CLASS_ANTLION )
+ {
+ if ( NumAntlionsInRadius(128) > 3 )
+ {
+ m_flDispelTestTime = gpGlobals->curtime + 15.0f;
+ return COND_VORTIGAUNT_DISPEL_ANTLIONS;
+ }
+ }
+
+ return COND_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flRadius -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::NumAntlionsInRadius( float flRadius )
+{
+ CBaseEntity *sEnemySearch[16];
+ int nNumAntlions = 0;
+ int nNumEnemies = UTIL_EntitiesInBox( sEnemySearch, ARRAYSIZE(sEnemySearch), GetAbsOrigin()-Vector(flRadius,flRadius,flRadius), GetAbsOrigin()+Vector(flRadius,flRadius,flRadius), FL_NPC );
+ for ( int i = 0; i < nNumEnemies; i++ )
+ {
+ // We only care about antlions
+ if ( sEnemySearch[i] == NULL || sEnemySearch[i]->Classify() != CLASS_ANTLION )
+ continue;
+
+ nNumAntlions++;
+ }
+
+ return nNumAntlions;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for a more powerful, concussive blast
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::RangeAttack2Conditions( float flDot, float flDist )
+{
+ return COND_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::HandleAnimEvent( animevent_t *pEvent )
+{
+ // Start our heal glows (choreo driven)
+ if ( pEvent->event == AE_VORTIGAUNT_START_HEAL_GLOW )
+ {
+ StartHandGlow( VORTIGAUNT_BEAM_HEAL, atoi( pEvent->options ) );
+ return;
+ }
+
+ // Stop our heal glows (choreo driven)
+ if ( pEvent->event == AE_VORTIGAUNT_STOP_HEAL_GLOW )
+ {
+ EndHandGlow();
+ return;
+ }
+
+ // Start our hurt glows (choreo driven)
+ if ( pEvent->event == AE_VORTIGAUNT_START_HURT_GLOW )
+ {
+ StartHandGlow( VORTIGAUNT_BEAM_DISPEL, atoi( pEvent->options ) );
+ return;
+ }
+
+ // Stop our hurt glows (choreo driven)
+ if ( pEvent->event == AE_VORTIGAUNT_STOP_HURT_GLOW )
+ {
+ EndHandGlow();
+ return;
+ }
+
+ // Start our dispel effect
+ if ( pEvent->event == AE_VORTIGAUNT_START_DISPEL )
+ {
+ StartHandGlow( VORTIGAUNT_BEAM_DISPEL, HAND_LEFT );
+ StartHandGlow( VORTIGAUNT_BEAM_DISPEL, HAND_RIGHT );
+
+ // Boom!
+ //EmitSound( "NPC_Vortigaunt.DispelImpact" );
+ CSoundParameters params;
+ if ( GetParametersForSound( "NPC_Vortigaunt.DispelImpact", params, NULL ) )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound_t ep( params );
+ ep.m_nChannel = CHAN_BODY;
+ EmitSound( filter, entindex(), ep );
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_ACCEL_DISPEL )
+ {
+ // TODO: Increase the size?
+ return;
+ }
+
+ // Kaboom!
+ if ( pEvent->event == AE_VORTIGAUNT_DISPEL )
+ {
+ DispelAntlions( GetAbsOrigin(), 400.0f );
+ return;
+ }
+
+ // Start of our heal loop
+ if ( pEvent->event == AE_VORTIGAUNT_HEAL_PAUSE )
+ {
+ StartHealing();
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_ZAP_POWERUP )
+ {
+ if ( m_fGlowChangeTime > gpGlobals->curtime )
+ return;
+
+ int nHand = 0;
+ if ( pEvent->options )
+ {
+ nHand = atoi( pEvent->options );
+ }
+
+ if ( ( nHand == HAND_LEFT ) || (nHand == HAND_BOTH ) )
+ {
+ ArmBeam( VORTIGAUNT_BEAM_ZAP, HAND_LEFT );
+ }
+
+ if ( ( nHand == HAND_RIGHT ) || (nHand == HAND_BOTH ) )
+ {
+ ArmBeam( VORTIGAUNT_BEAM_ZAP, HAND_RIGHT );
+ }
+
+ // Make hands glow if not already glowing
+ if ( m_fGlowAge == 0 )
+ {
+ if ( ( nHand == HAND_LEFT ) || (nHand == HAND_BOTH ) )
+ {
+ StartHandGlow( VORTIGAUNT_BEAM_ZAP, HAND_LEFT );
+ }
+
+ if ( ( nHand == HAND_RIGHT ) || (nHand == HAND_BOTH ) )
+ {
+ StartHandGlow( VORTIGAUNT_BEAM_ZAP, HAND_RIGHT );
+ }
+ m_fGlowAge = 1;
+ }
+
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "NPC_Vortigaunt.ZapPowerup", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ //ep.m_nPitch = 100 + m_iBeams * 10;
+ ep.m_nPitch = 150;
+
+ EmitSound( filter, entindex(), ep );
+
+ m_bStopLoopingSounds = true;
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_ZAP_SHOOT )
+ {
+ ClearBeams();
+
+ ClearMultiDamage();
+
+ int nHand = 0;
+ if ( pEvent->options )
+ {
+ nHand = atoi( pEvent->options );
+ }
+
+ if ( ( nHand == HAND_LEFT ) || (nHand == HAND_BOTH ) )
+ {
+ ZapBeam( HAND_LEFT );
+ }
+
+ if ( ( nHand == HAND_RIGHT ) || (nHand == HAND_BOTH ) )
+ {
+ ZapBeam( HAND_RIGHT );
+ }
+
+ EndHandGlow();
+
+ EmitSound( "NPC_Vortigaunt.ClawBeam" );
+ m_bStopLoopingSounds = true;
+ ApplyMultiDamage();
+
+ // Suppress our aiming until we're done with the animation
+ m_flAimDelay = gpGlobals->curtime + 0.75f;
+
+ if ( m_bExtractingBugbait )
+ {
+ // Spawn bugbait!
+ CBaseCombatWeapon *pWeapon = Weapon_Create( "weapon_bugbait" );
+ if ( pWeapon )
+ {
+ // Starting above the body, spawn closer and closer to the vort until it's clear
+ Vector vecSpawnOrigin = GetTarget()->WorldSpaceCenter() + Vector(0, 0, 32);
+ int iNumAttempts = 4;
+ Vector vecToVort = (WorldSpaceCenter() - vecSpawnOrigin);
+ float flDistance = VectorNormalize( vecToVort ) / (iNumAttempts-1);
+ int i = 0;
+ for (; i < iNumAttempts; i++ )
+ {
+ trace_t tr;
+ CTraceFilterSkipTwoEntities traceFilter( GetTarget(), this, COLLISION_GROUP_NONE );
+ AI_TraceLine( vecSpawnOrigin, vecSpawnOrigin, MASK_SHOT, &traceFilter, &tr );
+
+ if ( tr.fraction == 1.0 && !tr.m_pEnt )
+ {
+ // Make sure it can fit there
+ AI_TraceHull( vecSpawnOrigin, vecSpawnOrigin, -Vector(16,16,16), Vector(16,16,48), MASK_SHOT, &traceFilter, &tr );
+ if ( tr.fraction == 1.0 && !tr.m_pEnt )
+ break;
+ }
+
+ //NDebugOverlay::Box( vecSpawnOrigin, pWeapon->WorldAlignMins(), pWeapon->WorldAlignMins(), 255,0,0, 64, 100 );
+
+ // Move towards the vort
+ vecSpawnOrigin = vecSpawnOrigin + (vecToVort * flDistance);
+ }
+
+ // HACK: If we've still failed, just spawn it on the player
+ if ( i == iNumAttempts )
+ {
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer )
+ {
+ vecSpawnOrigin = pPlayer->WorldSpaceCenter();
+ }
+ }
+
+ //NDebugOverlay::Box( vecSpawnOrigin, -Vector(20,20,20), Vector(20,20,20), 0,255,0, 64, 100 );
+
+ pWeapon->SetAbsOrigin( vecSpawnOrigin );
+ pWeapon->Drop( Vector(0,0,1) );
+ }
+
+ CEffectData data;
+
+ data.m_vOrigin = GetTarget()->WorldSpaceCenter();
+ data.m_vNormal = WorldSpaceCenter() - GetTarget()->WorldSpaceCenter();
+ VectorNormalize( data.m_vNormal );
+
+ data.m_flScale = 4;
+
+ DispatchEffect( "AntlionGib", data );
+ }
+
+ // Stagger the next time we can attack
+ m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 2.0f, 3.0f );
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_ZAP_DONE )
+ {
+ ClearBeams();
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_HEAL_STARTGLOW )
+ {
+ // Make hands glow
+ StartHandGlow( VORTIGAUNT_BEAM_HEAL, HAND_RIGHT );
+ m_eHealState = HEAL_STATE_WARMUP;
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_HEAL_STARTSOUND )
+ {
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "NPC_Vortigaunt.StartHealLoop", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ //ep.m_nPitch = 100 + m_iBeams * 10;
+ ep.m_nPitch = 150;
+
+ EmitSound( filter, entindex(), ep );
+ m_bStopLoopingSounds = true;
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_SWING_SOUND )
+ {
+ EmitSound( "NPC_Vortigaunt.Swing" );
+ return;
+ }
+
+ if ( pEvent->event == AE_VORTIGAUNT_SHOOT_SOUNDSTART )
+ {
+ if ( m_fGlowChangeTime > gpGlobals->curtime )
+ return;
+
+ CPASAttenuationFilter filter( this );
+ CSoundParameters params;
+ if ( GetParametersForSound( "NPC_Vortigaunt.StartShootLoop", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ //ep.m_nPitch = 100 + m_iBeams * 10;
+ ep.m_nPitch = 150;
+
+ EmitSound( filter, entindex(), ep );
+ m_bStopLoopingSounds = true;
+ }
+ return;
+ }
+
+ if ( pEvent->event == AE_NPC_LEFTFOOT )
+ {
+ EmitSound( "NPC_Vortigaunt.FootstepLeft", pEvent->eventtime );
+ return;
+ }
+
+ if ( pEvent->event == AE_NPC_RIGHTFOOT )
+ {
+ EmitSound( "NPC_Vortigaunt.FootstepRight", pEvent->eventtime );
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Turn blue or green
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputTurnBlue( inputdata_t &data )
+{
+ bool goBlue = data.value.Bool();
+ if (goBlue != m_bIsBlue)
+ {
+ m_bIsBlue = goBlue;
+ m_flBlueEndFadeTime = gpGlobals->curtime + VORTIGAUNT_BLUE_FADE_TIME;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Turn blue or green
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputTurnBlack( inputdata_t &data )
+{
+ bool goBlack = data.value.Bool();
+ if (goBlack != m_bIsBlack)
+ {
+ m_bIsBlack = goBlack;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Translate some activites for the Vortigaunt
+//------------------------------------------------------------------------------
+Activity CNPC_Vortigaunt::NPC_TranslateActivity( Activity eNewActivity )
+{
+ // This is a hack solution for the Vort carrying Alyx in Ep2
+ if ( IsCarryingNPC() )
+ {
+ if ( eNewActivity == ACT_IDLE )
+ return ACT_IDLE_CARRY;
+
+ if ( eNewActivity == ACT_WALK || eNewActivity == ACT_WALK_AIM || eNewActivity == ACT_RUN || eNewActivity == ACT_RUN_AIM )
+ return ACT_WALK_CARRY;
+ }
+
+ // NOTE: This is a stand-in until the readiness system can handle non-weapon holding NPC's
+ if ( eNewActivity == ACT_IDLE )
+ {
+ // More than relaxed means we're stimulated
+ if ( GetReadinessLevel() >= AIRL_STIMULATED )
+ return ACT_IDLE_STIMULATED;
+ }
+
+ if ( eNewActivity == ACT_RANGE_ATTACK2 )
+ return (Activity) ACT_VORTIGAUNT_DISPEL;
+
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::UpdateOnRemove( void)
+{
+ ClearBeams();
+ ClearHandGlow();
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::Event_Killed( const CTakeDamageInfo &info )
+{
+ ClearBeams();
+ ClearHandGlow();
+
+ BaseClass::Event_Killed( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::Spawn( void )
+{
+#if !defined( HL2_EPISODIC )
+ // Disable back-away
+ AddSpawnFlags( SF_NPC_NO_PLAYER_PUSHAWAY );
+#endif // HL2_EPISODIC
+
+ // Allow multiple models (for slaves), but default to vortigaunt.mdl
+ char *szModel = (char *)STRING( GetModelName() );
+ if (!szModel || !*szModel)
+ {
+ szModel = "models/vortigaunt.mdl";
+ SetModelName( AllocPooledString(szModel) );
+ }
+
+ BaseClass::Spawn();
+
+ m_HackedGunPos.x = 0.0f;
+ m_HackedGunPos.y = 0.0f;
+ m_HackedGunPos.z = 48.0f;
+
+ SetHullType( HULL_WIDE_HUMAN );
+ SetHullSizeNormal();
+
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_iHealth = sk_vortigaunt_health.GetFloat();
+ SetViewOffset( Vector ( 0, 0, 64 ) );// position of the eyes relative to monster's origin.
+
+ CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK1 );
+ CapabilitiesRemove( bits_CAP_USE_SHOT_REGULATOR );
+
+ m_flEyeIntegRate = 0.6f; // Got a big eyeball so turn it slower
+ m_bForceArmorRecharge = false;
+ m_flHealHinderedTime = 0.0f;
+
+ m_nCurGlowIndex = 0;
+
+ m_bStopLoopingSounds = false;
+
+ m_iLeftHandAttachment = LookupAttachment( VORTIGAUNT_LEFT_CLAW );
+ m_iRightHandAttachment = LookupAttachment( VORTIGAUNT_RIGHT_CLAW );
+
+ NPCInit();
+
+ SetUse( &CNPC_Vortigaunt::Use );
+
+ // Setup our re-fire times when moving and shooting
+ GetShotRegulator()->SetBurstInterval( 2.0f, 2.0f );
+ GetShotRegulator()->SetBurstShotCountRange( 1, 1 );
+ GetShotRegulator()->SetRestInterval( 2.0f, 2.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::Precache()
+{
+ UTIL_PrecacheOther( "vort_charge_token" );
+
+ PrecacheModel( STRING( GetModelName() ) );
+
+ m_nLightningSprite = PrecacheModel("sprites/lgtning.vmt");
+ PrecacheModel("sprites/vortring1.vmt");
+
+ // HACK: Only precache this for EP2 because reslists cannot be rebuilt - 08/22/07 - jdw
+ if ( hl2_episodic.GetBool() )
+ {
+ char modDir[MAX_PATH];
+ if ( UTIL_GetModDir( modDir, sizeof(modDir) ) )
+ {
+ if ( !Q_stricmp( modDir, "ep2" ) )
+ {
+ PrecacheMaterial( "effects/rollerglow" );
+ }
+ }
+ }
+
+ PrecacheScriptSound( "NPC_Vortigaunt.SuitOn" );
+ PrecacheScriptSound( "NPC_Vortigaunt.SuitCharge" );
+ PrecacheScriptSound( "NPC_Vortigaunt.ZapPowerup" );
+ PrecacheScriptSound( "NPC_Vortigaunt.ClawBeam" );
+ PrecacheScriptSound( "NPC_Vortigaunt.StartHealLoop" );
+ PrecacheScriptSound( "NPC_Vortigaunt.Swing" );
+ PrecacheScriptSound( "NPC_Vortigaunt.StartShootLoop" );
+ PrecacheScriptSound( "NPC_Vortigaunt.FootstepLeft" );
+ PrecacheScriptSound( "NPC_Vortigaunt.FootstepRight" );
+ PrecacheScriptSound( "NPC_Vortigaunt.DispelStart" );
+ PrecacheScriptSound( "NPC_Vortigaunt.DispelImpact" );
+ PrecacheScriptSound( "NPC_Vortigaunt.Explode" );
+
+ PrecacheParticleSystem( "vortigaunt_beam" );
+ PrecacheParticleSystem( "vortigaunt_beam_charge" );
+ PrecacheParticleSystem( "vortigaunt_hand_glow" );
+
+ PrecacheMaterial( "sprites/light_glow02_add" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Interpret a player +USE'ing us
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ m_OnPlayerUse.FireOutput( pActivator, pCaller );
+
+ // Foremost, try and heal a wounded player
+ if ( HealBehaviorAvailable() )
+ {
+ // See if we should heal the player
+ CBaseEntity *pHealTarget = FindHealTarget();
+ if ( pHealTarget != NULL )
+ {
+ SetCondition( COND_PROVOKED );
+ SetHealTarget( pHealTarget, true );
+ return;
+ }
+ }
+
+ // Next, try to speak the +USE concept
+ if ( IsOkToSpeakInResponseToPlayer() && m_eHealState == HEAL_STATE_NONE )
+ {
+ if ( Speak( TLK_USE ) == false )
+ {
+ // If we haven't said hi, say that first
+ if ( !SpokeConcept( TLK_HELLO ) )
+ {
+ Speak( TLK_HELLO );
+ }
+ else
+ {
+ Speak( TLK_IDLE );
+ }
+ }
+ else
+ {
+ // Don't say hi after you've said your +USE speech
+ SetSpokeConcept( TLK_HELLO, NULL );
+ }
+ }
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Vortigaunt::PainSound( const CTakeDamageInfo &info )
+{
+ if ( gpGlobals->curtime < m_flPainTime )
+ return;
+
+ m_flPainTime = gpGlobals->curtime + random->RandomFloat(0.5, 0.75);
+
+ Speak( VORT_PAIN );
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_Vortigaunt::DeathSound( const CTakeDamageInfo &info )
+{
+ Speak( VORT_DIE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ if ( (info.GetDamageType() & DMG_SHOCK) && FClassnameIs( info.GetAttacker(), GetClassname() ) )
+ {
+ // mask off damage from other vorts for now
+ info.SetDamage( 0.01 );
+ }
+
+ switch( ptr->hitgroup)
+ {
+ case HITGROUP_CHEST:
+ case HITGROUP_STOMACH:
+ if (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_BLAST))
+ {
+ info.ScaleDamage( 0.5f );
+ }
+ break;
+ case 10:
+ if (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB))
+ {
+ info.SetDamage( info.GetDamage() - 20 );
+ if (info.GetDamage() <= 0)
+ {
+ g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) );
+ info.SetDamage( 0.01 );
+ }
+ }
+ // always a head shot
+ ptr->hitgroup = HITGROUP_HEAD;
+ break;
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_ALERT_FACE_BESTSOUND:
+ return SCHED_VORT_ALERT_FACE_BESTSOUND;
+ break;
+
+ case SCHED_TAKE_COVER_FROM_BEST_SOUND:
+
+ // Stand still if we're in the middle of an attack. Failing to do so can make us miss our shot!
+ if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
+ return SCHED_COMBAT_FACE;
+
+ return SCHED_VORT_FLEE_FROM_BEST_SOUND;
+ break;
+
+ case SCHED_COWER:
+ case SCHED_PC_COWER:
+ // Vort doesn't have cower animations
+ return SCHED_COMBAT_FACE;
+ break;
+
+ case SCHED_RANGE_ATTACK1:
+
+ // If we're told to fire when we're already firing, just face our target. If we don't do this, we get a bizarre double-shot
+ if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
+ return SCHED_COMBAT_FACE;
+
+ // Otherwise we use our own schedule to attack
+ return SCHED_VORTIGAUNT_RANGE_ATTACK;
+ break;
+
+ /*
+ case SCHED_CHASE_ENEMY:
+ case SCHED_ESTABLISH_LINE_OF_FIRE:
+ case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK:
+
+ // Don't go running off after an enemy just because we're in an attack delay! This has to do with
+ // the base systems assuming that held weapons are driving certain decisions when this creature
+ // uses an innate ability.
+ if ( ( GetNextAttack() > gpGlobals->curtime ) && HasCondition( COND_ENEMY_TOO_FAR ) == false )
+ return SCHED_COMBAT_FACE;
+
+ break;
+ */
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the heal target for the vort and preps him for completing the action
+// Input : *pTarget - Target we're after
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::SetHealTarget( CBaseEntity *pTarget, bool bPlayerRequested )
+{
+ SetCondition( COND_VORTIGAUNT_CAN_HEAL );
+ OccupyStrategySlot( SQUAD_SLOT_HEAL_PLAYER );
+ m_hHealTarget = pTarget;
+ m_bPlayerRequestedHeal = bPlayerRequested;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds a player in range that can be healed
+// Output : Target that can be healed
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_Vortigaunt::FindHealTarget( void )
+{
+ // Need to be looking at the player to decide to heal them.
+ //if ( HasCondition( COND_SEE_PLAYER ) == false )
+ // return false;
+
+ // Find a likely target in range
+ CBaseEntity *pEntity = PlayerInRange( GetAbsOrigin(), HEAL_SEARCH_RANGE );
+
+ // Make sure we can heal that target
+ if ( ShouldHealTarget( pEntity ) == false )
+ return NULL;
+
+ return pEntity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether or not the vort is able to attempt to heal targets
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::HealBehaviorAvailable( void )
+{
+ // Cannot already be healing
+ if ( m_eHealState != HEAL_STATE_NONE )
+ return false;
+
+ // Must be allowed to do this behavior
+ if ( m_bArmorRechargeEnabled == false )
+ return false;
+
+ // Don't interrupt a script
+ if ( IsInAScript() || m_NPCState == NPC_STATE_SCRIPT )
+ return false;
+
+ // Cannot interrupt bugbait extraction
+ if ( IsCurSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT ) )
+ return false;
+
+ // Don't bother while we're under attack
+ if ( GetEnemy() != NULL )
+ return false;
+
+ // Can't heal if we're leading the player
+ if ( IsLeading() )
+ return false;
+
+ // Must be a valid squad activity to do
+ if ( IsStrategySlotRangeOccupied( SQUAD_SLOT_HEAL_PLAYER, SQUAD_SLOT_HEAL_PLAYER ) )
+ return false;
+
+ // Heal is valid
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines whether or not the
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::ShouldHealTarget( CBaseEntity *pTarget )
+{
+ // Must have a valid target
+ if ( pTarget == NULL )
+ return false;
+
+ // If we're scripting or waiting to run one, we won't heal a target
+ if ( IsInAScript() || HasSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ) )
+ return false;
+
+ // We only heal players
+ CBasePlayer *pPlayer = ToBasePlayer( pTarget );
+ if ( pPlayer == NULL )
+ return false;
+
+ // Make sure the player's got a suit
+ if ( pPlayer->IsSuitEquipped() == false )
+ return false;
+
+ // Don't heal a target we can't see..?
+ if ( pPlayer->GetFlags() & FL_NOTARGET )
+ return false;
+
+ // See if the player needs armor
+ if ( pPlayer->ArmorValue() >= (sk_vortigaunt_armor_charge.GetFloat()*0.66f) )
+ return false;
+
+ // Must be alive!
+ if ( pPlayer->IsAlive() == false )
+ return false;
+
+ // Only consider things in here if the player is NOT at critical health or the heal is a passive one (not requested)
+ if ( PlayerBelowHealthPercentage( pPlayer, PLAYER_CRITICAL_HEALTH_PERC ) == false || m_bPlayerRequestedHeal )
+ {
+ // Don't heal when fighting
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ return false;
+
+ // No enemies
+ if ( GetEnemy() )
+ return false;
+
+ // No recent damage
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ return false;
+ }
+
+ // Allow the heal
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::SelectHealSchedule( void )
+{
+ // If our lead behavior has a goal, don't wait around to heal anyone
+ if ( m_LeadBehavior.HasGoal() )
+ return SCHED_NONE;
+
+ // Break out of healing if a script has started
+ if ( IsInAScript() && m_bForceArmorRecharge == false )
+ {
+ if ( m_eHealState != HEAL_STATE_NONE )
+ {
+ StopHealing( true );
+ }
+
+ return SCHED_NONE;
+ }
+
+ // Cannot already be healing the player
+ if ( m_hHealTarget != NULL )
+ {
+ // For now, just grab the global, single player
+ CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
+
+ // Check for an interruption occurring
+ if ( PlayerBelowHealthPercentage( pPlayer, PLAYER_CRITICAL_HEALTH_PERC ) == false && HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ StopHealing( true );
+ return SCHED_NONE;
+ }
+
+ // See if we're in an ideal position to heal
+ if ( m_eHealState != HEAL_STATE_HEALING && m_eHealState != HEAL_STATE_WARMUP && HasCondition( COND_VORTIGAUNT_HEAL_VALID ) )
+ return SCHED_VORTIGAUNT_HEAL;
+
+ // If the player is too far away or blocked, give chase
+ if ( HasCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR ) ||
+ HasCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED ) )
+ return SCHED_VORTIGAUNT_RUN_TO_PLAYER;
+
+ // Stand and face the player
+ if ( HasCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US ) || HasCondition( COND_VORTIGAUNT_HEAL_VALID ) )
+ return SCHED_VORTIGAUNT_FACE_PLAYER;
+ }
+
+ return SCHED_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Watch this function path for a route around our normal schedule changing callbacks
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::ClearSchedule( const char *szReason )
+{
+ MaintainGlows();
+
+ BaseClass::ClearSchedule( szReason );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Watch our glows and turn them off appropriately
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::OnScheduleChange( void )
+{
+ BaseClass::OnScheduleChange();
+
+ // If we're in the middle of healing, don't bother doing this
+ if ( m_eHealState != HEAL_STATE_NONE )
+ return;
+
+ // If we're changing sequences, always clear
+ EndHandGlow( VORTIGAUNT_BEAM_ALL );
+ m_fGlowChangeTime = gpGlobals->curtime + 0.1f; // No more glows for this amount of time!
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Select a schedule
+//------------------------------------------------------------------------------
+int CNPC_Vortigaunt::SelectSchedule( void )
+{
+ // Always recharge in this case
+ if ( m_bForceArmorRecharge )
+ {
+ m_flNextHealTime = 0;
+ int nSchedule = SelectHealSchedule();
+ return nSchedule;
+ }
+
+#ifndef HL2_EPISODIC
+ if ( BehaviorSelectSchedule() )
+ return BaseClass::SelectSchedule();
+#else
+ if ( IsInAScript() )
+ return BaseClass::SelectSchedule();
+#endif
+
+ // If we're currently supposed to be doing something scripted, do it immediately.
+ if ( m_bExtractingBugbait )
+ return SCHED_VORTIGAUNT_EXTRACT_BUGBAIT;
+
+ int schedule = SelectHealSchedule();
+ if ( schedule != SCHED_NONE )
+ return schedule;
+
+ if ( HasCondition(COND_VORTIGAUNT_DISPEL_ANTLIONS ) )
+ {
+ ClearCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
+ return SCHED_VORTIGAUNT_DISPEL_ANTLIONS;
+ }
+
+ // Heal a player if they can be
+ if ( HasCondition( COND_VORTIGAUNT_CAN_HEAL ) )
+ return SCHED_VORTIGAUNT_HEAL;
+
+ return BaseClass::SelectSchedule();
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
+{
+ if ( failedSchedule == SCHED_BACK_AWAY_FROM_ENEMY )
+ {
+ if ( GetEnemy() && GetSenses()->CanSeeEntity( GetEnemy() ) )
+ {
+ return SCHED_RANGE_ATTACK1;
+ }
+ }
+
+ return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::DeclineFollowing( void )
+{
+ Speak( VORT_POK );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if you're willing to be idly talked to by other friends.
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::CanBeUsedAsAFriend( void )
+{
+ // We don't want to be used if we're busy
+ if ( IsCurSchedule( SCHED_VORTIGAUNT_HEAL ) )
+ return false;
+
+ if ( IsCurSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT ) )
+ return false;
+
+ return BaseClass::CanBeUsedAsAFriend();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+#define VORT_360_VIEW_DIST_SQR ((60*12)*(60*12))
+bool CNPC_Vortigaunt::FInViewCone( CBaseEntity *pEntity )
+{
+ // Vort can see 360 degrees but only at limited distance
+ if( ( pEntity->IsNPC() || pEntity->IsPlayer() ) && pEntity->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= VORT_360_VIEW_DIST_SQR )
+ return true;
+
+ return BaseClass::FInViewCone( pEntity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start our heal loop
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::StartHealing( void )
+{
+ // Find the layer and stop it from moving forward in the cycle
+ int nLayer = FindGestureLayer( (Activity) ACT_VORTIGAUNT_HEAL );
+ SetLayerPlaybackRate( nLayer, 0.0f );
+
+ // We're now in the healing loop
+ m_eHealState = HEAL_STATE_HEALING;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::StopHealing( bool bInterrupt )
+{
+ // Clear out our healing states
+ m_eHealState = HEAL_STATE_NONE;
+ m_bForceArmorRecharge = false;
+ m_hHealTarget = NULL;
+
+ EndHandGlow( VORTIGAUNT_BEAM_HEAL );
+ VacateStrategySlot();
+
+ // See if we're completely interrupting the heal or just ending normally
+ if ( bInterrupt )
+ {
+ RemoveGesture( (Activity) ACT_VORTIGAUNT_HEAL );
+ m_flNextHealTime = gpGlobals->curtime + 2.0f;
+ }
+ else
+ {
+ // Start our animation back up again
+ int nLayer = FindGestureLayer( (Activity) ACT_VORTIGAUNT_HEAL );
+ SetLayerPlaybackRate( nLayer, 1.0f );
+
+ m_flNextHealTime = gpGlobals->curtime + VORTIGAUNT_HEAL_RECHARGE;
+ m_OnFinishedChargingTarget.FireOutput( this, this );
+ }
+
+ // Give us time to stop our animation before we start attacking (otherwise we get weird collisions)
+ SetNextAttack( gpGlobals->curtime + 2.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update our heal schedule and gestures if we're currently healing
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::MaintainHealSchedule( void )
+{
+ // Need to be healing
+ if ( m_eHealState == HEAL_STATE_NONE )
+ return;
+
+ // For now, we only heal the player
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ if ( pPlayer == NULL )
+ return;
+
+ // FIXME: How can this happen?
+ if ( m_AssaultBehavior.GetOuter() != NULL )
+ {
+ // Interrupt us on an urgent assault
+ if ( m_AssaultBehavior.IsRunning() && ( m_AssaultBehavior.IsUrgent() || m_AssaultBehavior.OnStrictAssault() ) )
+ {
+ StopHealing( true );
+ return;
+ }
+ }
+
+ // Don't let us shoot while we're healing
+ GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + 0.5f );
+
+ // If we're in the healing phase, heal our target (if able)
+ if ( m_eHealState == HEAL_STATE_HEALING )
+ {
+ // FIXME: We need to have better logic controlling this
+ if ( HasCondition( COND_VORTIGAUNT_HEAL_VALID ) )
+ {
+ if ( m_flNextHealTokenTime < gpGlobals->curtime )
+ {
+ CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
+
+ // We're done, so stop playing the animation
+ if ( m_nNumTokensToSpawn <= 0 || ( m_bForceArmorRecharge == false && ( pPlayer && pPlayer->ArmorValue() >= sk_vortigaunt_armor_charge.GetInt() ) ) )
+ {
+ m_flHealHinderedTime = 0.0f;
+ m_nNumTokensToSpawn = 0;
+ SpeakIfAllowed( VORT_CURESTOP );
+ StopHealing( false );
+ return;
+ }
+
+ // Create a charge token
+ Vector vecHandPos;
+ QAngle vecHandAngles;
+ GetAttachment( m_iRightHandAttachment, vecHandPos, vecHandAngles );
+ CVortigauntChargeToken::CreateChargeToken( vecHandPos, this, m_hHealTarget );
+ m_flNextHealTokenTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f );
+ m_nNumTokensToSpawn--;
+
+ // If we're stopping, delay our animation a bit so it's not so robotic
+ if ( m_nNumTokensToSpawn <= 0 )
+ {
+ m_nNumTokensToSpawn = 0;
+ m_flNextHealTokenTime = gpGlobals->curtime + 1.0f;
+ }
+ }
+ }
+ else
+ {
+ /*
+ // NOTENOTE: It's better if the vort give up than ignore things around him to try and continue -- jdw
+
+ // Increment a counter to let us know how long we've failed
+ m_flHealHinderedTime += gpGlobals->curtime - GetLastThink();
+
+ if ( m_flHealHinderedTime > 2.0f )
+ {
+ // If too long, stop trying
+ StopHealing();
+ }
+ */
+
+ bool bInterrupt = false;
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ bInterrupt = true;
+ }
+
+ StopHealing( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+inline bool CNPC_Vortigaunt::InAttackSequence( void )
+{
+ if ( m_MoveAndShootOverlay.IsMovingAndShooting() )
+ return true;
+
+ if ( GetActivity() == ACT_RANGE_ATTACK1 )
+ return true;
+
+ if ( GetActivity() == ACT_VORTIGAUNT_DISPEL )
+ return true;
+
+ if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Watch our beams and make sure we don't leave them on mistakenly
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::MaintainGlows( void )
+{
+ // Verify that if we're not in an attack gesture, that we're not doing an attack glow
+ if ( InAttackSequence() == false && m_eHealState == HEAL_STATE_NONE )
+ {
+ EndHandGlow( VORTIGAUNT_BEAM_ALL );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Squelch looping sounds and glows after a restore.
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::OnRestore( void )
+{
+ BaseClass::OnRestore();
+
+ m_bStopLoopingSounds = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Do various non-schedule specific maintainence
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::PrescheduleThink( void )
+{
+ // Update our healing (if active)
+ MaintainHealSchedule();
+
+ // Let the base class have a go
+ BaseClass::PrescheduleThink();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &move -
+// flInterval -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
+{
+ // If we're in our aiming gesture, then always face our target as we run
+ Activity nActivity = NPC_TranslateActivity( ACT_GESTURE_RANGE_ATTACK1 );
+ if ( IsPlayingGesture( nActivity ) ||
+ IsCurSchedule( SCHED_PC_MOVE_TOWARDS_COVER_FROM_BEST_SOUND ) ||
+ IsCurSchedule( SCHED_VORT_FLEE_FROM_BEST_SOUND ) ||
+ IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) )
+ {
+ Vector vecEnemyLKP = GetEnemyLKP();
+ AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
+ }
+
+ return BaseClass::OverrideMoveFacing( move, flInterval );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::BuildScheduleTestBits( void )
+{
+ // Call to base
+ BaseClass::BuildScheduleTestBits();
+
+ // Allow healing to interrupt us if we're standing around
+ if ( IsCurSchedule( SCHED_IDLE_STAND ) ||
+ IsCurSchedule( SCHED_ALERT_STAND ) )
+ {
+ if ( m_eHealState == HEAL_STATE_NONE )
+ {
+ SetCustomInterruptCondition( COND_VORTIGAUNT_CAN_HEAL );
+ SetCustomInterruptCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
+ }
+ }
+
+ // Always interrupt when healing
+ if ( m_eHealState != HEAL_STATE_NONE )
+ {
+ // Interrupt if we're not already adjusting
+ if ( IsCurSchedule( SCHED_VORTIGAUNT_RUN_TO_PLAYER ) == false )
+ {
+ SetCustomInterruptCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR );
+ SetCustomInterruptCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED );
+
+ // Interrupt if we're not already turning
+ if ( IsCurSchedule( SCHED_VORTIGAUNT_FACE_PLAYER ) == false )
+ {
+ SetCustomInterruptCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US );
+ }
+ }
+ }
+
+ if ( IsCurSchedule( SCHED_COMBAT_STAND ) )
+ {
+ SetCustomInterruptCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Small beam from arm to nearby geometry
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::ArmBeam( int beamType, int nHand )
+{
+ trace_t tr;
+ float flDist = 1.0;
+ int side = ( nHand == HAND_LEFT ) ? -1 : 1;
+
+ Vector forward, right, up;
+ AngleVectors( GetLocalAngles(), &forward, &right, &up );
+ Vector vecSrc = GetLocalOrigin() + up * 36 + right * side * 16 + forward * 32;
+
+ for (int i = 0; i < 3; i++)
+ {
+ Vector vecAim = forward * random->RandomFloat( -1, 1 ) + right * side * random->RandomFloat( 0, 1 ) + up * random->RandomFloat( -1, 1 );
+ trace_t tr1;
+ AI_TraceLine ( vecSrc, vecSrc + vecAim * (10*12), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr1);
+
+ // Don't hit the sky
+ if ( tr1.surface.flags & SURF_SKY )
+ continue;
+
+ // Choose a farther distance if we have one
+ if ( flDist > tr1.fraction )
+ {
+ tr = tr1;
+ flDist = tr.fraction;
+ }
+ }
+
+ // Couldn't find anything close enough
+ if ( flDist == 1.0 )
+ return;
+
+ // Tell the client to start an arm beam
+ unsigned char uchAttachment = (nHand==HAND_LEFT) ? m_iLeftHandAttachment : m_iRightHandAttachment;
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( VORTFX_ARMBEAM );
+ WRITE_LONG( entindex() );
+ WRITE_BYTE( uchAttachment );
+ WRITE_VEC3COORD( tr.endpos );
+ WRITE_VEC3NORMAL( tr.plane.normal );
+ MessageEnd();
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Put glowing sprites on hands
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::StartHandGlow( int beamType, int nHand )
+{
+ // We need this because there's a rare case where a scene can interrupt and turn off our hand glows, but are then
+ // turned back on in the same frame due to how animations are applied and anim events are executed after the AI frame.
+ if ( m_fGlowChangeTime > gpGlobals->curtime )
+ return;
+
+ switch( beamType )
+ {
+ case VORTIGAUNT_BEAM_DISPEL:
+ case VORTIGAUNT_BEAM_HEAL:
+ case VORTIGAUNT_BEAM_ZAP:
+ {
+ // Validate the hand's range
+ if ( nHand >= ARRAYSIZE( m_hHandEffect ) )
+ return;
+
+ // Start up
+ if ( m_hHandEffect[nHand] == NULL )
+ {
+ // Create the token if it doesn't already exist
+ m_hHandEffect[nHand] = CVortigauntEffectDispel::CreateEffectDispel( GetAbsOrigin(), this, NULL );
+ if ( m_hHandEffect[nHand] == NULL )
+ return;
+ }
+
+ // Stomp our settings
+ m_hHandEffect[nHand]->SetParent( this, (nHand==HAND_LEFT) ? m_iLeftHandAttachment : m_iRightHandAttachment );
+ m_hHandEffect[nHand]->SetMoveType( MOVETYPE_NONE );
+ m_hHandEffect[nHand]->SetLocalOrigin( Vector( 8.0f, 4.0f, 0.0f ) );
+ }
+ break;
+
+ case VORTIGAUNT_BEAM_ALL:
+ Assert( 0 );
+ break;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Fade glow from hands.
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::EndHandGlow( int beamType /*= VORTIGAUNT_BEAM_ALL*/ )
+{
+ if ( m_hHandEffect[0] )
+ {
+ m_hHandEffect[0]->FadeAndDie();
+ m_hHandEffect[0] = NULL;
+ }
+
+ if ( m_hHandEffect[1] )
+ {
+ m_hHandEffect[1]->FadeAndDie();
+ m_hHandEffect[1] = NULL;
+ }
+
+ // Zap
+ if ( beamType == VORTIGAUNT_BEAM_ZAP || beamType == VORTIGAUNT_BEAM_ALL )
+ {
+ m_fGlowAge = 0;
+
+ // Stop our smaller beams as well
+ ClearBeams();
+ }
+}
+
+extern int ACT_ANTLION_ZAP_FLIP;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ if ( IsRoller( pEnemy ) )
+ {
+ CAI_BaseNPC *pNPC = pEnemy->MyNPCPointer();
+ if ( pNPC && pNPC->GetEnemy() != NULL )
+ return true;
+ return false;
+ }
+
+ // Wait until our animation is finished
+ if ( GetEnemy() == NULL && m_flAimDelay > gpGlobals->curtime )
+ return false;
+
+ return BaseClass::IsValidEnemy( pEnemy );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a blast where the beam has struck a target
+// Input : &vecOrigin - position to eminate from
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::CreateBeamBlast( const Vector &vecOrigin )
+{
+ CSprite *pBlastSprite = CSprite::SpriteCreate( "sprites/vortring1.vmt", vecOrigin, true );
+ if ( pBlastSprite != NULL )
+ {
+ pBlastSprite->SetTransparency( kRenderTransAddFrameBlend, 255, 255, 255, 255, kRenderFxNone );
+ pBlastSprite->SetBrightness( 255 );
+ pBlastSprite->SetScale( random->RandomFloat( 1.0f, 1.5f ) );
+ pBlastSprite->AnimateAndDie( 45.0f );
+ pBlastSprite->EmitSound( "NPC_Vortigaunt.Explode" );
+ }
+
+ CPVSFilter filter( vecOrigin );
+ te->GaussExplosion( filter, 0.0f, vecOrigin, Vector( 0, 0, 1 ), 0 );
+}
+
+#define COS_30 0.866025404f // sqrt(3) / 2
+#define COS_60 0.5 // sqrt(1) / 2
+
+//-----------------------------------------------------------------------------
+// Purpose: Heavy damage directly forward
+// Input : nHand - Handedness of the beam
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::ZapBeam( int nHand )
+{
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+
+ Vector vecSrc = GetAbsOrigin() + GetViewOffset();
+ Vector vecAim = GetShootEnemyDir( vecSrc, false ); // We want a clear shot to their core
+
+ if ( GetEnemy() )
+ {
+ Vector vecTarget = GetEnemy()->BodyTarget( vecSrc, false );
+
+ if ( g_debug_vortigaunt_aim.GetBool() )
+ {
+ NDebugOverlay::Cross3D( vecTarget, 4.0f, 255, 0, 0, true, 10.0f );
+ CBaseAnimating *pAnim = GetEnemy()->GetBaseAnimating();
+ if ( pAnim )
+ {
+ pAnim->DrawServerHitboxes( 10.0f );
+ }
+ }
+ }
+
+ // If we're too far off our center, the shot must miss!
+ if ( DotProduct( vecAim, forward ) < COS_60 )
+ {
+ // Missed, so just shoot forward
+ vecAim = forward;
+ }
+
+ trace_t tr;
+
+ if ( m_bExtractingBugbait == true )
+ {
+ CRagdollProp *pTest = dynamic_cast< CRagdollProp *>( GetTarget() );
+
+ if ( pTest )
+ {
+ ragdoll_t *m_ragdoll = pTest->GetRagdoll();
+
+ if ( m_ragdoll )
+ {
+ Vector vOrigin;
+ m_ragdoll->list[0].pObject->GetPosition( &vOrigin, 0 );
+
+ AI_TraceLine( vecSrc, vOrigin, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
+ }
+
+ CRagdollBoogie::Create( pTest, 200, gpGlobals->curtime, 1.0f );
+ }
+ }
+ else
+ {
+ AI_TraceLine( vecSrc, vecSrc + ( vecAim * InnateRange1MaxRange() ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
+ }
+
+ if ( g_debug_vortigaunt_aim.GetBool() )
+ {
+ NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 0, true, 10.0f );
+ }
+
+ // Send a message to the client to create a "zap" beam
+ unsigned char uchAttachment = (nHand==HAND_LEFT) ? m_iLeftHandAttachment : m_iRightHandAttachment;
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( VORTFX_ZAPBEAM );
+ WRITE_BYTE( uchAttachment );
+ WRITE_VEC3COORD( tr.endpos );
+ MessageEnd();
+
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( pEntity != NULL && m_takedamage )
+ {
+ if ( g_debug_vortigaunt_aim.GetBool() )
+ {
+ NDebugOverlay::Box( tr.endpos, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, 8, 10.0f );
+ }
+
+ CTakeDamageInfo dmgInfo( this, this, sk_vortigaunt_dmg_zap.GetFloat(), DMG_SHOCK );
+ dmgInfo.SetDamagePosition( tr.endpos );
+ VectorNormalize( vecAim );// not a unit vec yet
+ // hit like a 5kg object flying 100 ft/s
+ dmgInfo.SetDamageForce( 5 * 100 * 12 * vecAim );
+
+ // Our zaps do special things to antlions
+ if ( FClassnameIs( pEntity, "npc_antlion" ) )
+ {
+ // Make a worker flip instead of explode
+ if ( IsAntlionWorker( pEntity ) )
+ {
+ CNPC_Antlion *pAntlion = static_cast<CNPC_Antlion *>(pEntity);
+ pAntlion->Flip();
+ }
+ else
+ {
+ // Always gib the antlion hit!
+ dmgInfo.ScaleDamage( 4.0f );
+ }
+
+ // Look in a ring and flip other antlions nearby
+ DispelAntlions( tr.endpos, 200.0f, false );
+ }
+
+ // Send the damage to the recipient
+ pEntity->DispatchTraceAttack( dmgInfo, vecAim, &tr );
+ }
+
+ // Create a cover for the end of the beam
+ CreateBeamBlast( tr.endpos );
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Clear glow from hands immediately
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::ClearHandGlow( void )
+{
+ if ( m_hHandEffect[0] != NULL )
+ {
+ UTIL_Remove( m_hHandEffect[0] );
+ m_hHandEffect[0] = NULL;
+ }
+
+ if ( m_hHandEffect[1] != NULL )
+ {
+ UTIL_Remove( m_hHandEffect[1] );
+ m_hHandEffect[1] = NULL;
+ }
+
+ m_fGlowAge = 0;
+}
+
+//------------------------------------------------------------------------------
+// Purpose: remove all beams
+//------------------------------------------------------------------------------
+void CNPC_Vortigaunt::ClearBeams( void )
+{
+ // Stop looping suit charge sound.
+ if ( m_bStopLoopingSounds )
+ {
+ StopSound( "NPC_Vortigaunt.StartHealLoop" );
+ StopSound( "NPC_Vortigaunt.StartShootLoop" );
+ StopSound( "NPC_Vortigaunt.SuitCharge" );
+ StopSound( "NPC_Vortigaunt.ZapPowerup" );
+ m_bStopLoopingSounds = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputEnableArmorRecharge( inputdata_t &data )
+{
+ m_bArmorRechargeEnabled = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputDisableArmorRecharge( inputdata_t &data )
+{
+ m_bArmorRechargeEnabled = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputChargeTarget( inputdata_t &data )
+{
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, data.value.String(), NULL, data.pActivator, data.pCaller );
+
+ // Must be valid
+ if ( pTarget == NULL )
+ {
+ DevMsg( 1, "Unable to charge from unknown entity: %s!\n", data.value.String() );
+ return;
+ }
+
+ int playerArmor = (pTarget->IsPlayer()) ? ((CBasePlayer *)pTarget)->ArmorValue() : 0;
+
+ if ( playerArmor >= 100 || ( pTarget->GetFlags() & FL_NOTARGET ) )
+ {
+ m_OnFinishedChargingTarget.FireOutput( this, this );
+ return;
+ }
+
+ m_hHealTarget = pTarget;
+ m_bForceArmorRecharge = true;
+
+ SetCondition( COND_PROVOKED );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputExtractBugbait( inputdata_t &data )
+{
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, data.value.String(), NULL, data.pActivator, data.pCaller );
+
+ // Must be valid
+ if ( pTarget == NULL )
+ {
+ DevMsg( 1, "Unable to extract bugbait from unknown entity %s!\n", data.value.String() );
+ return;
+ }
+
+ // Keep this as our target
+ SetTarget( pTarget );
+
+ // Start to extract
+ m_bExtractingBugbait = true;
+ SetSchedule( SCHED_VORTIGAUNT_EXTRACT_BUGBAIT );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows the vortigaunt to use health regeneration
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputEnableHealthRegeneration( inputdata_t &data )
+{
+ m_bRegenerateHealth = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stops the vortigaunt from using health regeneration (default)
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputDisableHealthRegeneration( inputdata_t &data )
+{
+ m_bRegenerateHealth = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::IRelationPriority( CBaseEntity *pTarget )
+{
+ int priority = BaseClass::IRelationPriority( pTarget );
+
+ if ( pTarget == NULL )
+ return priority;
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ // Handle antlion cases
+ if ( pEnemy != NULL && pEnemy != pTarget )
+ {
+ // I have an enemy that is not this thing. If that enemy is near, I shouldn't become distracted.
+ if ( GetAbsOrigin().DistToSqr( pEnemy->GetAbsOrigin()) < Square(15*12) )
+ return priority;
+ }
+
+ // Targets near our follow target have a higher priority to us
+ if ( m_FollowBehavior.GetFollowTarget() &&
+ m_FollowBehavior.GetFollowTarget()->GetAbsOrigin().DistToSqr( pTarget->GetAbsOrigin() ) < Square(25*12) )
+ {
+ priority++;
+ }
+
+ // Flipped antlions are of lower priority
+ CAI_BaseNPC *pNPC = pTarget->MyNPCPointer();
+ if ( pNPC && pNPC->Classify() == CLASS_ANTLION && pNPC->GetActivity() == ACT_ANTLION_ZAP_FLIP )
+ priority--;
+
+ return priority;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: back away from overly close zombies
+//-----------------------------------------------------------------------------
+Disposition_t CNPC_Vortigaunt::IRelationType( CBaseEntity *pTarget )
+{
+ if ( pTarget == NULL )
+ return D_NU;
+
+ Disposition_t disposition = BaseClass::IRelationType( pTarget );
+
+ if ( pTarget->Classify() == CLASS_ZOMBIE && disposition == D_HT )
+ {
+ if( GetAbsOrigin().DistToSqr(pTarget->GetAbsOrigin()) < VORTIGAUNT_FEAR_ZOMBIE_DIST_SQR )
+ {
+ // Be afraid of a zombie that's near if I'm not allowed to dodge. This will make Alyx back away.
+ return D_FR;
+ }
+ }
+
+ return disposition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines whether the heal gesture can successfully reach the player
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::HealGestureHasLOS( void )
+{
+ //For now the player is always our target
+ CBaseEntity *pTargetEnt = AI_GetSinglePlayer();
+ if ( pTargetEnt == NULL )
+ return false;
+
+ // Find our left hand as the starting point
+ Vector vecHandPos;
+ QAngle vecHandAngle;
+ GetAttachment( m_iRightHandAttachment, vecHandPos, vecHandAngle );
+
+ // Trace to our target, skipping ourselves and the target
+ trace_t tr;
+ CTraceFilterSkipTwoEntities filter( this, pTargetEnt, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( vecHandPos, pTargetEnt->WorldSpaceCenter(), MASK_SHOT, &filter, &tr );
+
+ // Must be clear
+ if ( tr.fraction < 1.0f || tr.startsolid || tr.allsolid )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gather conditions for our healing behavior
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::GatherHealConditions( void )
+{
+ ClearCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR );
+ ClearCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED );
+ ClearCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US );
+
+ // We stop if there are enemies around
+ if ( m_bArmorRechargeEnabled == false ||
+ HasCondition( COND_NEW_ENEMY ) ||
+ HasCondition( COND_HEAR_DANGER ) ||
+ HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
+ return;
+ }
+
+ // Start by assuming that we'll succeed
+ SetCondition( COND_VORTIGAUNT_HEAL_VALID );
+
+ // Just assume we should
+ if ( m_bForceArmorRecharge )
+ return;
+
+ // For now we only act on the player
+ CBasePlayer *pPlayer = ToBasePlayer( m_hHealTarget );
+ if ( pPlayer != NULL )
+ {
+ Vector vecToPlayer = ( pPlayer->WorldSpaceCenter() - WorldSpaceCenter() );
+
+ // Make sure he's still within heal range
+ if ( vecToPlayer.LengthSqr() > (HEAL_RANGE*HEAL_RANGE) )
+ {
+ SetCondition( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR );
+ // NOTE: We allow him to send tokens over large distances
+ //ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
+ }
+
+ vecToPlayer.z = 0.0f;
+ VectorNormalize( vecToPlayer );
+ Vector facingDir = BodyDirection2D();
+
+ // Check our direction towards the player
+ if ( DotProduct( vecToPlayer, facingDir ) < VIEW_FIELD_NARROW )
+ {
+ SetCondition( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US );
+ ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
+ }
+
+ // Now ensure he's not blocked
+ if ( HealGestureHasLOS() == false )
+ {
+ SetCondition( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED );
+ ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
+ }
+ }
+ else
+ {
+ ClearCondition( COND_VORTIGAUNT_HEAL_VALID );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gather conditions specific to this NPC
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::GatherConditions( void )
+{
+ // Call our base
+ BaseClass::GatherConditions();
+
+ // See if we're able to heal now
+ if ( HealBehaviorAvailable() && ( m_flNextHealTime < gpGlobals->curtime ) )
+ {
+ // See if we should heal the player
+ CBaseEntity *pHealTarget = FindHealTarget();
+ if ( pHealTarget != NULL )
+ {
+ SetHealTarget( pHealTarget, false );
+ }
+
+ // Don't try again for a period of time
+ m_flNextHealTime = gpGlobals->curtime + 2.0f;
+ }
+
+ // Get our state for healing
+ GatherHealConditions();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::DispelAntlions( const Vector &vecOrigin, float flRadius, bool bDispel /*= true*/ )
+{
+ // More effects
+ if ( bDispel )
+ {
+ UTIL_ScreenShake( vecOrigin, 20.0f, 150.0, 1.0, 1250.0f, SHAKE_START );
+
+ CBroadcastRecipientFilter filter2;
+ te->BeamRingPoint( filter2, 0, vecOrigin, //origin
+ 64, //start radius
+ 800, //end radius
+ m_nLightningSprite, //texture
+ 0, //halo index
+ 0, //start frame
+ 2, //framerate
+ 0.1f, //life
+ 128, //width
+ 0, //spread
+ 0, //amplitude
+ 255, //r
+ 255, //g
+ 225, //b
+ 32, //a
+ 0, //speed
+ FBEAM_FADEOUT
+ );
+
+ //Shockring
+ te->BeamRingPoint( filter2, 0, vecOrigin + Vector( 0, 0, 16 ), //origin
+ 64, //start radius
+ 800, //end radius
+ m_nLightningSprite, //texture
+ 0, //halo index
+ 0, //start frame
+ 2, //framerate
+ 0.2f, //life
+ 64, //width
+ 0, //spread
+ 0, //amplitude
+ 255, //r
+ 255, //g
+ 225, //b
+ 200, //a
+ 0, //speed
+ FBEAM_FADEOUT
+ );
+
+ // Ground effects
+ CEffectData data;
+ data.m_vOrigin = vecOrigin;
+
+ DispatchEffect( "VortDispel", data );
+ }
+
+ // Make antlions flip all around us!
+ trace_t tr;
+ CBaseEntity *pEnemySearch[32];
+ int nNumEnemies = UTIL_EntitiesInBox( pEnemySearch, ARRAYSIZE(pEnemySearch), vecOrigin-Vector(flRadius,flRadius,flRadius), vecOrigin+Vector(flRadius,flRadius,flRadius), FL_NPC );
+ for ( int i = 0; i < nNumEnemies; i++ )
+ {
+ // We only care about antlions
+ if ( IsAntlion( pEnemySearch[i] ) == false )
+ continue;
+
+ CNPC_Antlion *pAntlion = static_cast<CNPC_Antlion *>(pEnemySearch[i]);
+ if ( pAntlion->IsWorker() == false )
+ {
+ // Attempt to trace a line to hit the target
+ UTIL_TraceLine( vecOrigin, pAntlion->BodyTarget( vecOrigin ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1.0f && tr.m_pEnt != pAntlion )
+ continue;
+
+ Vector vecDir = ( pAntlion->GetAbsOrigin() - vecOrigin );
+ vecDir[2] = 0.0f;
+ float flDist = VectorNormalize( vecDir );
+
+ float flFalloff = RemapValClamped( flDist, 0, flRadius*0.75f, 1.0f, 0.1f );
+
+ vecDir *= ( flRadius * 1.5f * flFalloff );
+ vecDir[2] += ( flRadius * 0.5f * flFalloff );
+
+ pAntlion->ApplyAbsVelocityImpulse( vecDir );
+
+ // gib nearby antlions, knock over distant ones.
+ if ( flDist < 128 && bDispel )
+ {
+ // splat!
+ vecDir[2] += 400.0f * flFalloff;
+ CTakeDamageInfo dmgInfo( this, this, vecDir, pAntlion->GetAbsOrigin() , 100, DMG_SHOCK );
+ pAntlion->TakeDamage( dmgInfo );
+ }
+ else
+ {
+ // Turn them over
+ pAntlion->Flip( true );
+
+ // Display an effect between us and the flipped creature
+ // Tell the client to start an arm beam
+ /*
+ unsigned char uchAttachment = pAntlion->LookupAttachment( "mouth" );
+ EntityMessageBegin( this, true );
+ WRITE_BYTE( VORTFX_ARMBEAM );
+ WRITE_LONG( pAntlion->entindex() );
+ WRITE_BYTE( uchAttachment );
+ WRITE_VEC3COORD( vecOrigin );
+ WRITE_VEC3NORMAL( Vector( 0, 0, 1 ) );
+ MessageEnd();
+ */
+ }
+ }
+ }
+
+ // Stop our effects
+ if ( bDispel )
+ {
+ EndHandGlow( VORTIGAUNT_BEAM_ALL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Simply tell us to dispel
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputDispel( inputdata_t &data )
+{
+ SetCondition( COND_VORTIGAUNT_DISPEL_ANTLIONS );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Decide when we're allowed to interact with other NPCs
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::CanRunAScriptedNPCInteraction( bool bForced /*= false*/ )
+{
+ // Never interrupt a range attack!
+ if ( InAttackSequence() )
+ return false;
+
+ // Can't do them while we're trying to heal the player
+ if ( m_eHealState != HEAL_STATE_NONE )
+ return false;
+
+ return BaseClass::CanRunAScriptedNPCInteraction( bForced );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : interrupt -
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::SetScriptedScheduleIgnoreConditions( Interruptability_t interrupt )
+{
+ // First add our base conditions to ignore
+ BaseClass::SetScriptedScheduleIgnoreConditions( interrupt );
+
+ static int g_VortConditions[] =
+ {
+ COND_VORTIGAUNT_CAN_HEAL,
+ COND_VORTIGAUNT_DISPEL_ANTLIONS,
+ COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR,
+ COND_VORTIGAUNT_HEAL_TARGET_BLOCKED,
+ COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US,
+ COND_VORTIGAUNT_HEAL_VALID
+ };
+
+ ClearIgnoreConditions( g_VortConditions, ARRAYSIZE(g_VortConditions) );
+
+ // Ignore these if we're damage only
+ if ( interrupt > GENERAL_INTERRUPTABILITY )
+ SetIgnoreConditions( g_VortConditions, ARRAYSIZE(g_VortConditions) );
+}
+
+//-----------------------------------------------------------------------------
+// !!!HACKHACK - EP2 - Stop vortigaunt taking all physics damage to prevent it dying
+// in freak accidents resembling spontaneous stress damage death (which are now impossible)
+// Also stop it taking damage from flames: Fixes it being burnt to death from entity flames
+// attached to random debris chunks while inside scripted sequences.
+//-----------------------------------------------------------------------------
+int CNPC_Vortigaunt::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ if( info.GetDamageType() & (DMG_CRUSH | DMG_BURN) )
+ return 0;
+
+ // vital vortigaunts (eg the vortigoth in ep2) take less damage from explosions
+ // so that zombines don't blow them up disappointingly. They take less damage
+ // still from antlion workers.
+ if ( Classify() == CLASS_PLAYER_ALLY_VITAL )
+ {
+ // half damage
+ CTakeDamageInfo subInfo = info;
+
+ // take less damage from antlion worker acid/poison
+ if ( info.GetAttacker()->Classify() == CLASS_ANTLION &&
+ (info.GetDamageType() & ( DMG_ACID | DMG_POISON ))!=0
+ )
+ {
+ subInfo.ScaleDamage( sk_vortigaunt_vital_antlion_worker_dmg.GetFloat() );
+ }
+
+ else if ( info.GetDamageType() & DMG_BLAST )
+ {
+ subInfo.ScaleDamage( 0.5f );
+ }
+
+ return BaseClass::OnTakeDamage_Alive( subInfo );
+ }
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override move and shoot if we're following someone
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::ShouldMoveAndShoot( void )
+{
+ if ( m_FollowBehavior.IsActive() )
+ return true;
+
+ return BaseClass::ShouldMoveAndShoot();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: notification from a grub that I squished it. This special case
+// function is necessary because what you would think to be the ordinary
+// channels are in fact missing: Event_KilledOther doesn't actually do anything
+// and KilledNPC expects a BaseCombatCharacter, and always uses the same Speak
+// line.
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::OnSquishedGrub( const CBaseEntity *pGrub )
+{
+ Speak(TLK_SQUISHED_GRUB);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::AimGun( void )
+{
+ // If our aim lock is on, don't bother
+ if ( m_flAimDelay >= gpGlobals->curtime )
+ return;
+
+ // Aim at our target
+ if ( GetEnemy() )
+ {
+ Vector vecShootOrigin;
+
+ vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir;
+
+ // Aim where it is
+ vecShootDir = GetShootEnemyDir( vecShootOrigin, false );
+
+ if ( g_debug_vortigaunt_aim.GetBool() )
+ {
+ NDebugOverlay::Line( WorldSpaceCenter(), WorldSpaceCenter() + vecShootDir * 256.0f, 255, 0, 0, true, 0.1f );
+ }
+
+ SetAim( vecShootDir );
+ }
+ else
+ {
+ RelaxAim();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A scripted sequence has interrupted us
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::OnStartScene( void )
+{
+ // Watch our hand state
+ EndHandGlow( VORTIGAUNT_BEAM_ALL );
+ m_fGlowChangeTime = gpGlobals->curtime + 0.1f; // No more glows for this amount of time!
+
+ BaseClass::OnStartScene();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::IsInterruptable( void )
+{
+ // Don't interrupt my attack schedule!
+ if ( InAttackSequence() )
+ return false;
+
+ return BaseClass::IsInterruptable();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start overriding our animations to "carry" an NPC
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputBeginCarryNPC( inputdata_t &indputdata )
+{
+ m_bCarryingNPC = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop overriding our animations for carrying an NPC
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::InputEndCarryNPC( inputdata_t &indputdata )
+{
+ m_bCarryingNPC = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turn off flinching under certain circumstances
+//-----------------------------------------------------------------------------
+bool CNPC_Vortigaunt::CanFlinch( void )
+{
+ if ( IsActiveDynamicInteraction() )
+ return false;
+
+ if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) )
+ return false;
+
+ if ( IsCurSchedule( SCHED_VORTIGAUNT_DISPEL_ANTLIONS ) || IsCurSchedule( SCHED_RANGE_ATTACK1 ) )
+ return false;
+
+ return BaseClass::CanFlinch();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Vortigaunt::OnUpdateShotRegulator( void )
+{
+ // Do nothing, we're not really running this code in a normal manner
+ GetShotRegulator()->SetBurstInterval( 2.0f, 2.0f );
+ GetShotRegulator()->SetBurstShotCountRange( 1, 1 );
+ GetShotRegulator()->SetRestInterval( 2.0f, 2.0f );
+}
+
+/*
+IMPLEMENT_SERVERCLASS_ST( CVortigauntChargeToken, DT_VortigauntChargeToken )
+ SendPropFloat( SENDINFO(m_flFadeOutTime), 0, SPROP_NOSCALE),
+ SendPropBool( SENDINFO(m_bFadeOut) ),
+ SendPropFloat( SENDINFO(m_flScale), 0, SPROP_NOSCALE),
+END_SEND_TABLE()
+*/
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_vortigaunt, CNPC_Vortigaunt )
+
+ DECLARE_USES_SCHEDULE_PROVIDER( CAI_LeadBehavior )
+
+ DECLARE_TASK(TASK_VORTIGAUNT_HEAL)
+ DECLARE_TASK(TASK_VORTIGAUNT_EXTRACT)
+ DECLARE_TASK(TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT)
+ DECLARE_TASK(TASK_VORTIGAUNT_WAIT_FOR_PLAYER)
+
+ DECLARE_TASK( TASK_VORTIGAUNT_EXTRACT_WARMUP )
+ DECLARE_TASK( TASK_VORTIGAUNT_EXTRACT_COOLDOWN )
+ DECLARE_TASK( TASK_VORTIGAUNT_GET_HEAL_TARGET )
+ DECLARE_TASK( TASK_VORTIGAUNT_DISPEL_ANTLIONS )
+
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_AIM)
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_START_HEAL )
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_HEAL_LOOP )
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_END_HEAL )
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_TO_ACTION )
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_TO_IDLE )
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_HEAL )
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_DISPEL )
+ DECLARE_ACTIVITY( ACT_VORTIGAUNT_ANTLION_THROW )
+
+ DECLARE_CONDITION( COND_VORTIGAUNT_CAN_HEAL )
+ DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR )
+ DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_TARGET_BLOCKED )
+ DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US )
+ DECLARE_CONDITION( COND_VORTIGAUNT_HEAL_VALID )
+ DECLARE_CONDITION( COND_VORTIGAUNT_DISPEL_ANTLIONS )
+
+ DECLARE_SQUADSLOT( SQUAD_SLOT_HEAL_PLAYER )
+
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_CLAW_LEFT )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_CLAW_RIGHT )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_POWERUP )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_SHOOT )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_ZAP_DONE )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTGLOW )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTBEAMS )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_STARTSOUND )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_SWING_SOUND )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_SHOOT_SOUNDSTART )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_HEAL_PAUSE )
+
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_START_DISPEL )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_ACCEL_DISPEL )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_DISPEL )
+
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_START_HURT_GLOW )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_STOP_HURT_GLOW )
+
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_START_HEAL_GLOW )
+ DECLARE_ANIMEVENT( AE_VORTIGAUNT_STOP_HEAL_GLOW )
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_RANGE_ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_RANGE_ATTACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_ANNOUNCE_ATTACK 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_WAIT 0.2" // Wait a sec before killing beams
+ ""
+ " Interrupts"
+ " COND_NO_CUSTOM_INTERRUPTS"
+ );
+
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_HEAL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_HEAL,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_STAND"
+ " TASK_STOP_MOVING 0"
+ " TASK_VORTIGAUNT_GET_HEAL_TARGET 0"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 350"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_PLAYER 0"
+ " TASK_VORTIGAUNT_HEAL 0"
+ ""
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ );
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_STAND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_STAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2" // repick IDLESTAND every two seconds."
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SMELL"
+ " COND_PROVOKED"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_VORTIGAUNT_DISPEL_ANTLIONS"
+ " COND_VORTIGAUNT_CAN_HEAL"
+ );
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_EXTRACT_BUGBAIT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_EXTRACT_BUGBAIT,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_VORTIGAUNT_STAND"
+ " TASK_STOP_MOVING 0"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 128" // Move within 128 of target ent (client)
+ " TASK_STOP_MOVING 0"
+ " TASK_VORTIGAUNT_WAIT_FOR_PLAYER 0"
+ " TASK_SPEAK_SENTENCE 500" // Start extracting sentence
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_FACE_TARGET 0"
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_VORTIGAUNT_EXTRACT_WARMUP 0"
+ " TASK_VORTIGAUNT_EXTRACT 0"
+ " TASK_VORTIGAUNT_EXTRACT_COOLDOWN 0"
+ " TASK_VORTIGAUNT_FIRE_EXTRACT_OUTPUT 0"
+ " TASK_SPEAK_SENTENCE 501" // Finish extracting sentence
+ " TASK_WAIT_FOR_SPEAK_FINISH 1"
+ " TASK_WAIT 2"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_FACE_PLAYER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_FACE_PLAYER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_TARGET_PLAYER 0"
+ " TASK_FACE_PLAYER 0"
+ " TASK_WAIT 3"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_VORTIGAUNT_DISPEL_ANTLIONS"
+ " COND_VORTIGAUNT_HEAL_TARGET_TOO_FAR"
+ " COND_VORTIGAUNT_HEAL_TARGET_BLOCKED"
+ " COND_VORTIGAUNT_HEAL_TARGET_BEHIND_US"
+ );
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_RUN_TO_PLAYER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_RUN_TO_PLAYER,
+
+ " Tasks"
+ " TASK_TARGET_PLAYER 0"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 350"
+ ""
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ );
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_DISPEL_ANTLIONS
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_DISPEL_ANTLIONS,
+
+ " Tasks"
+ " TASK_VORTIGAUNT_DISPEL_ANTLIONS 0"
+ ""
+ " Interrupts"
+ " COND_NO_CUSTOM_INTERRUPTS"
+ );
+
+ //=========================================================
+ //
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORT_FLEE_FROM_BEST_SOUND,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COWER"
+ " TASK_GET_PATH_AWAY_FROM_BEST_SOUND 600"
+ " TASK_RUN_PATH_TIMED 1.5"
+ " TASK_STOP_MOVING 0"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > AlertFace best sound
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORT_ALERT_FACE_BESTSOUND,
+
+ " Tasks"
+ " TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_SAVEPOSITION 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_FEAR"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_HEAR_DANGER"
+ );
+AI_END_CUSTOM_NPC()
+
+
+//=============================================================================
+//
+// Charge Token
+//
+//=============================================================================
+
+LINK_ENTITY_TO_CLASS( vort_charge_token, CVortigauntChargeToken );
+
+BEGIN_DATADESC( CVortigauntChargeToken )
+ DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flLifetime, FIELD_TIME ),
+ DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ),
+
+ DEFINE_ENTITYFUNC( SeekThink ),
+ DEFINE_ENTITYFUNC( SeekTouch ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CVortigauntChargeToken, DT_VortigauntChargeToken )
+ SendPropBool( SENDINFO(m_bFadeOut) ),
+END_SEND_TABLE()
+
+CVortigauntChargeToken::CVortigauntChargeToken( void ) :
+m_hTarget( NULL )
+{
+ m_bFadeOut = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a charge token for the player to collect
+// Input : &vecOrigin - Where we start
+// *pOwner - Who created us
+// *pTarget - Who we're seeking towards
+//-----------------------------------------------------------------------------
+CVortigauntChargeToken *CVortigauntChargeToken::CreateChargeToken( const Vector &vecOrigin, CBaseEntity *pOwner, CBaseEntity *pTarget )
+{
+ CVortigauntChargeToken *pToken = (CVortigauntChargeToken *) CreateEntityByName( "vort_charge_token" );
+ if ( pToken == NULL )
+ return NULL;
+
+ // Set up our internal data
+ UTIL_SetOrigin( pToken, vecOrigin );
+ pToken->SetOwnerEntity( pOwner );
+ pToken->SetTargetEntity( pTarget );
+ pToken->SetThink( &CVortigauntChargeToken::SeekThink );
+ pToken->SetTouch( &CVortigauntChargeToken::SeekTouch );
+ pToken->Spawn();
+
+ // Start out at the same velocity as our owner
+ Vector vecInitialVelocity;
+ CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pOwner);
+ if ( pAnimating != NULL )
+ {
+ vecInitialVelocity = pAnimating->GetGroundSpeedVelocity();
+ }
+ else
+ {
+ vecInitialVelocity = pTarget->GetSmoothedVelocity();
+ }
+
+ // Start out at that speed
+ pToken->SetAbsVelocity( vecInitialVelocity );
+
+ return pToken;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVortigauntChargeToken::Precache( void )
+{
+ PrecacheParticleSystem( "vortigaunt_charge_token" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We want to move through grates!
+//-----------------------------------------------------------------------------
+unsigned int CVortigauntChargeToken::PhysicsSolidMaskForEntity( void ) const
+{
+ return MASK_SHOT;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVortigauntChargeToken::Spawn( void )
+{
+ Precache();
+
+ // Point-sized
+ UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) );
+
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+ SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
+ SetGravity( 0.0f );
+
+ // No model but we still need to force this!
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+
+ SetNextThink( gpGlobals->curtime + 0.05f );
+
+ m_flLifetime = gpGlobals->curtime + VORTIGAUNT_CURE_LIFESPAN;
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates an influence vector which causes the token to move away from obstructions
+//-----------------------------------------------------------------------------
+Vector CVortigauntChargeToken::GetSteerVector( const Vector &vecForward )
+{
+ Vector vecSteer = vec3_origin;
+ Vector vecRight, vecUp;
+ VectorVectors( vecForward, vecRight, vecUp );
+
+ // Use two probes fanned out a head of us
+ Vector vecProbe;
+ float flSpeed = GetAbsVelocity().Length();
+
+ // Try right
+ vecProbe = vecForward + vecRight;
+ vecProbe *= flSpeed;
+
+ // We ignore multiple targets
+ CTraceFilterSimpleList filterSkip( COLLISION_GROUP_NONE );
+ filterSkip.AddEntityToIgnore( this );
+ filterSkip.AddEntityToIgnore( GetOwnerEntity() );
+ filterSkip.AddEntityToIgnore( m_hTarget );
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecProbe, MASK_SHOT, &filterSkip, &tr );
+ vecSteer -= vecRight * 100.0f * ( 1.0f - tr.fraction );
+
+ // Try left
+ vecProbe = vecForward - vecRight;
+ vecProbe *= flSpeed;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecProbe, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ vecSteer += vecRight * 100.0f * ( 1.0f - tr.fraction );
+
+ return vecSteer;
+}
+
+#define VTOKEN_MAX_SPEED 320.0f // U/sec
+#define VTOKEN_ACCEL_SPEED 320.0f // '
+
+//-----------------------------------------------------------------------------
+// Purpose: Move towards our target entity with accel/decel parameters
+//-----------------------------------------------------------------------------
+void CVortigauntChargeToken::SeekThink( void )
+{
+ // Move away from the creator and towards the target
+ if ( m_hTarget == NULL || m_flLifetime < gpGlobals->curtime )
+ {
+ // TODO: Play an extinguish sound and fade out
+ FadeAndDie();
+ return;
+ }
+
+ // Find the direction towards our goal and start to go there
+ Vector vecDir = ( m_hTarget->WorldSpaceCenter() - GetAbsOrigin() );
+ VectorNormalize( vecDir );
+
+ float flSpeed = GetAbsVelocity().Length();
+ float flDelta = gpGlobals->curtime - GetLastThink();
+
+ if ( flSpeed < VTOKEN_MAX_SPEED )
+ {
+ // Accelerate by the desired amount
+ flSpeed += ( VTOKEN_ACCEL_SPEED * flDelta );
+ if ( flSpeed > VTOKEN_MAX_SPEED )
+ {
+ flSpeed = VTOKEN_MAX_SPEED;
+ }
+ }
+
+ // Steer!
+ Vector vecRight, vecUp;
+ VectorVectors( vecDir, vecRight, vecUp );
+ Vector vecOffset = vec3_origin;
+ vecOffset += vecUp * cos( gpGlobals->curtime * 20.0f ) * 200.0f * gpGlobals->frametime;
+ vecOffset += vecRight * sin( gpGlobals->curtime * 15.0f ) * 200.0f * gpGlobals->frametime;
+
+ vecOffset += GetSteerVector( vecDir );
+
+ SetAbsVelocity( ( vecDir * flSpeed ) + vecOffset );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVortigauntChargeToken::SeekTouch( CBaseEntity *pOther )
+{
+ // Make sure this is a player
+ CBasePlayer *pPlayer = ToBasePlayer( pOther );
+ if ( pPlayer == NULL )
+ return;
+
+ // FIXME: This probably isn't that interesting for single player missions
+ if ( pPlayer != m_hTarget )
+ return;
+
+ // TODO: Play a special noise for this event!
+ EmitSound( "NPC_Vortigaunt.SuitOn" );
+
+ // Charge the suit's armor
+ if ( pPlayer->ArmorValue() < sk_vortigaunt_armor_charge.GetInt() )
+ {
+ pPlayer->IncrementArmorValue( sk_vortigaunt_armor_charge_per_token.GetInt()+random->RandomInt( -1, 1 ), sk_vortigaunt_armor_charge.GetInt() );
+ }
+
+ // Stay attached to the thing we hit as we fade away
+ SetSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ SetParent( pOther );
+
+ // TODO: Play a "poof!" effect here?
+ FadeAndDie();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flTime -
+//-----------------------------------------------------------------------------
+void CVortigauntChargeToken::FadeAndDie( void )
+{
+ SetTouch( NULL );
+
+ SetAbsVelocity( vec3_origin );
+
+ m_bFadeOut = true;
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 2.0f );
+}
+
+//=============================================================================
+//
+// Dispel Effect
+//
+//=============================================================================
+
+LINK_ENTITY_TO_CLASS( vort_effect_dispel, CVortigauntEffectDispel );
+
+BEGIN_DATADESC( CVortigauntEffectDispel )
+ DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CVortigauntEffectDispel, DT_VortigauntEffectDispel )
+ SendPropBool( SENDINFO(m_bFadeOut) ),
+END_SEND_TABLE()
+
+CVortigauntEffectDispel::CVortigauntEffectDispel( void )
+{
+ m_bFadeOut = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a charge token for the player to collect
+// Input : &vecOrigin - Where we start
+// *pOwner - Who created us
+// *pTarget - Who we're seeking towards
+//-----------------------------------------------------------------------------
+CVortigauntEffectDispel *CVortigauntEffectDispel::CreateEffectDispel( const Vector &vecOrigin, CBaseEntity *pOwner, CBaseEntity *pTarget )
+{
+ CVortigauntEffectDispel *pToken = (CVortigauntEffectDispel *) CreateEntityByName( "vort_effect_dispel" );
+ if ( pToken == NULL )
+ return NULL;
+
+ // Set up our internal data
+ UTIL_SetOrigin( pToken, vecOrigin );
+ pToken->SetOwnerEntity( pOwner );
+ pToken->Spawn();
+
+ return pToken;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CVortigauntEffectDispel::Spawn( void )
+{
+ Precache();
+
+ UTIL_SetSize( this, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ) );
+
+ SetSolid( SOLID_BBOX );
+ SetSolidFlags( FSOLID_NOT_SOLID );
+
+ // No model but we still need to force this!
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+
+ BaseClass::Spawn();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flTime -
+//-----------------------------------------------------------------------------
+void CVortigauntEffectDispel::FadeAndDie( void )
+{
+ m_bFadeOut = true;
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 2.0f );
+}
+
+
+//=============================================================================
+//
+// Flesh effect target (used for orchestrating the "Invisible Alyx" moment
+//
+//=============================================================================
+
+#ifdef HL2_EPISODIC
+
+class CFleshEffectTarget : public CPointEntity
+{
+ DECLARE_CLASS( CFleshEffectTarget, CPointEntity );
+
+public:
+ void InputSetRadius( inputdata_t &inputData );
+
+ virtual void Spawn( void )
+ {
+ BaseClass::Spawn();
+
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+ }
+
+private:
+
+ CNetworkVar( float, m_flRadius );
+ CNetworkVar( float, m_flScaleTime );
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( point_flesh_effect_target, CFleshEffectTarget );
+
+BEGIN_DATADESC( CFleshEffectTarget )
+
+ DEFINE_FIELD( m_flScaleTime, FIELD_TIME ),
+ DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
+
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "SetRadius", InputSetRadius ),
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CFleshEffectTarget, DT_FleshEffectTarget )
+ SendPropFloat( SENDINFO(m_flRadius), 0, SPROP_NOSCALE),
+ SendPropFloat( SENDINFO(m_flScaleTime), 0, SPROP_NOSCALE),
+END_SEND_TABLE()
+
+void CFleshEffectTarget::InputSetRadius( inputdata_t &inputData )
+{
+ Vector vecRadius;
+ inputData.value.Vector3D( vecRadius );
+
+ m_flRadius = vecRadius.x;
+ m_flScaleTime = vecRadius.y;
+}
+
+#endif // HL2_EPISODIC