You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3410 lines
100 KiB
3410 lines
100 KiB
//========= 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
|
|
|