2013-12-02 19:31:46 -08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Alyx, the female sidekick and love interest that's taking the world by storm!
//
// Try the new Alyx Brite toothpaste!
// Alyx lederhosen!
//
// FIXME: need a better comment block
//
//=============================================================================//
# include "cbase.h"
# include "npcevent.h"
# include "ai_basenpc.h"
# include "ai_hull.h"
# include "ai_basehumanoid.h"
# include "ai_behavior_follow.h"
# include "npc_alyx_episodic.h"
# include "npc_headcrab.h"
# include "npc_BaseZombie.h"
# include "ai_senses.h"
# include "ai_memory.h"
# include "soundent.h"
# include "props.h"
# include "IEffects.h"
# include "globalstate.h"
# include "weapon_physcannon.h"
# include "info_darknessmode_lightsource.h"
# include "sceneentity.h"
# include "hl2_gamerules.h"
# include "scripted.h"
# include "hl2_player.h"
# include "env_alyxemp_shared.h"
# include "basehlcombatweapon.h"
# include "basegrenade_shared.h"
# include "ai_interactions.h"
# include "weapon_flaregun.h"
# include "env_debughistory.h"
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
# include "mapbase/GlobalStrings.h"
# endif
2013-12-02 19:31:46 -08:00
extern Vector PointOnLineNearestPoint ( const Vector & vStartPos , const Vector & vEndPos , const Vector & vPoint ) ;
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
bool g_HackOutland10DamageHack ;
int ACT_ALYX_DRAW_TOOL ;
int ACT_ALYX_IDLE_TOOL ;
int ACT_ALYX_ZAP_TOOL ;
int ACT_ALYX_HOLSTER_TOOL ;
int ACT_ALYX_PICKUP_RACK ;
string_t CLASSNAME_ALYXGUN ;
string_t CLASSNAME_SMG1 ;
string_t CLASSNAME_SHOTGUN ;
string_t CLASSNAME_AR2 ;
bool IsInCommentaryMode ( void ) ;
# define ALYX_BREATHING_VOLUME_MAX 1.0
# define ALYX_DARKNESS_LOST_PLAYER_DIST ( 120 * 120 ) // 12 feet
# define ALYX_MIN_MOB_DIST_SQR Square(120) // Any enemy closer than this adds to the 'mob'
# define ALYX_MIN_CONSIDER_DIST Square(1200) // Only enemies within this range are counted and considered to generate AI speech
# define CONCEPT_ALYX_REQUEST_ITEM "TLK_ALYX_REQUEST_ITEM"
# define CONCEPT_ALYX_INTERACTION_DONE "TLK_ALYX_INTERACTION_DONE"
# define CONCEPT_ALYX_CANCEL_INTERACTION "TLK_ALYX_CANCEL_INTERACTION"
# define ALYX_MIN_ENEMY_DIST_TO_CROUCH 360 // Minimum distance that our enemy must be for me to crouch
# define ALYX_MIN_ENEMY_HEALTH_TO_CROUCH 15
# define ALYX_CROUCH_DELAY 5 // Time after crouching before Alyx will crouch again
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# ifdef MAPBASE
ConVar sk_alyx_health ( " sk_alyx_health " , " 80 " ) ;
# endif
2013-12-02 19:31:46 -08:00
//-----------------------------------------------------------------------------
// Interactions
//-----------------------------------------------------------------------------
extern int g_interactionZombieMeleeWarning ;
LINK_ENTITY_TO_CLASS ( npc_alyx , CNPC_Alyx ) ;
BEGIN_DATADESC ( CNPC_Alyx )
DEFINE_FIELD ( m_hEmpTool , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hHackTarget , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hStealthLookTarget , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_bInteractionAllowed , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_fTimeNextSearchForInteractTargets , FIELD_TIME ) ,
DEFINE_FIELD ( m_bDarknessSpeechAllowed , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bIsEMPHolstered , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bIsFlashlightBlind , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_fStayBlindUntil , FIELD_TIME ) ,
DEFINE_FIELD ( m_flDontBlindUntil , FIELD_TIME ) ,
DEFINE_FIELD ( m_bSpokeLostPlayerInDarkness , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bPlayerFlashlightState , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bHadCondSeeEnemy , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_iszCurrentBlindScene , FIELD_STRING ) ,
DEFINE_FIELD ( m_fTimeUntilNextDarknessFoundPlayer , FIELD_TIME ) ,
DEFINE_FIELD ( m_fCombatStartTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_fCombatEndTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_flNextCrouchTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_WeaponType , FIELD_INTEGER ) ,
DEFINE_KEYFIELD ( m_bShouldHaveEMP , FIELD_BOOLEAN , " ShouldHaveEMP " ) ,
DEFINE_SOUNDPATCH ( m_sndDarknessBreathing ) ,
DEFINE_EMBEDDED ( m_SpeechWatch_LostPlayer ) ,
DEFINE_EMBEDDED ( m_SpeechTimer_HeardSound ) ,
DEFINE_EMBEDDED ( m_SpeechWatch_SoundDelay ) ,
DEFINE_EMBEDDED ( m_SpeechWatch_BreathingRamp ) ,
DEFINE_EMBEDDED ( m_SpeechWatch_FoundPlayer ) ,
DEFINE_EMBEDDED ( m_MoveMonitor ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " DisallowInteraction " , InputDisallowInteraction ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " AllowInteraction " , InputAllowInteraction ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " GiveWeapon " , InputGiveWeapon ) ,
DEFINE_INPUTFUNC ( FIELD_BOOLEAN , " AllowDarknessSpeech " , InputAllowDarknessSpeech ) ,
DEFINE_INPUTFUNC ( FIELD_BOOLEAN , " GiveEMP " , InputGiveEMP ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " VehiclePunted " , InputVehiclePunted ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " OutsideTransition " , InputOutsideTransition ) ,
DEFINE_OUTPUT ( m_OnFinishInteractWithObject , " OnFinishInteractWithObject " ) ,
DEFINE_OUTPUT ( m_OnPlayerUse , " OnPlayerUse " ) ,
DEFINE_USEFUNC ( Use ) ,
END_DATADESC ( )
# define ALYX_FEAR_ZOMBIE_DIST_SQR Square(60)
# define ALYX_FEAR_ANTLION_DIST_SQR Square(360)
//-----------------------------------------------------------------------------
// Anim events
//-----------------------------------------------------------------------------
static int AE_ALYX_EMPTOOL_ATTACHMENT ;
static int AE_ALYX_EMPTOOL_SEQUENCE ;
static int AE_ALYX_EMPTOOL_USE ;
2019-08-31 19:28:20 +00:00
# ifndef MAPBASE
2013-12-02 19:31:46 -08:00
static int COMBINE_AE_BEGIN_ALTFIRE ;
static int COMBINE_AE_ALTFIRE ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
ConVar npc_alyx_readiness ( " npc_alyx_readiness " , " 1 " ) ;
ConVar npc_alyx_force_stop_moving ( " npc_alyx_force_stop_moving " , " 1 " ) ;
ConVar npc_alyx_readiness_transitions ( " npc_alyx_readiness_transitions " , " 1 " ) ;
ConVar npc_alyx_crouch ( " npc_alyx_crouch " , " 1 " ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
ConVar npc_alyx_interact_manhacks ( " npc_alyx_interact_manhacks " , " 1 " ) ;
ConVar npc_alyx_interact_turrets ( " npc_alyx_interact_turrets " , " 0 " ) ;
# endif
2013-12-02 19:31:46 -08:00
// global pointer to Alyx for fast lookups
CEntityClassList < CNPC_Alyx > g_AlyxList ;
template < > CNPC_Alyx * CEntityClassList < CNPC_Alyx > : : m_pClassList = NULL ;
//=========================================================
// initialize Alyx before keyvalues are processed
//=========================================================
CNPC_Alyx : : CNPC_Alyx ( )
{
g_AlyxList . Insert ( this ) ;
// defaults to having an EMP
m_bShouldHaveEMP = true ;
}
CNPC_Alyx : : ~ CNPC_Alyx ( )
{
g_AlyxList . Remove ( this ) ;
}
//=========================================================
// Classify - indicates this NPC's place in the
// relationship table.
//=========================================================
Class_T CNPC_Alyx : : Classify ( void )
{
return CLASS_PLAYER_ALLY_VITAL ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : FValidateHintType ( CAI_Hint * pHint )
{
switch ( pHint - > HintType ( ) )
{
case HINT_WORLD_VISUALLY_INTERESTING :
return true ;
break ;
case HINT_WORLD_VISUALLY_INTERESTING_STEALTH :
return true ;
break ;
}
return BaseClass : : FValidateHintType ( pHint ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Alyx : : ObjectCaps ( )
{
int caps = BaseClass : : ObjectCaps ( ) ;
if ( m_FuncTankBehavior . IsMounted ( ) )
{
caps & = ~ FCAP_IMPULSE_USE ;
}
return caps ;
}
//=========================================================
// HandleAnimEvent - catches the NPC-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Alyx : : HandleAnimEvent ( animevent_t * pEvent )
{
if ( pEvent - > event = = AE_ALYX_EMPTOOL_ATTACHMENT )
{
if ( ! m_hEmpTool )
{
// Old savegame?
CreateEmpTool ( ) ;
if ( ! m_hEmpTool )
return ;
}
int iAttachment = LookupAttachment ( pEvent - > options ) ;
m_hEmpTool - > SetParent ( this , iAttachment ) ;
m_hEmpTool - > SetLocalOrigin ( Vector ( 0 , 0 , 0 ) ) ;
m_hEmpTool - > SetLocalAngles ( QAngle ( 0 , 0 , 0 ) ) ;
if ( ! stricmp ( pEvent - > options , " Emp_Holster " ) )
{
SetEMPHolstered ( true ) ;
}
else
{
SetEMPHolstered ( false ) ;
}
return ;
}
else if ( pEvent - > event = = AE_ALYX_EMPTOOL_SEQUENCE )
{
if ( ! m_hEmpTool )
return ;
CDynamicProp * pEmpTool = dynamic_cast < CDynamicProp * > ( m_hEmpTool . Get ( ) ) ;
if ( ! pEmpTool )
return ;
int iSequence = pEmpTool - > LookupSequence ( pEvent - > options ) ;
if ( iSequence ! = ACT_INVALID )
{
pEmpTool - > PropSetSequence ( iSequence ) ;
}
return ;
}
else if ( pEvent - > event = = AE_ALYX_EMPTOOL_USE )
{
if ( m_OperatorBehavior . IsGoalReady ( ) )
{
if ( m_OperatorBehavior . m_hContextTarget . Get ( ) ! = NULL )
{
EmpZapTarget ( m_OperatorBehavior . m_hContextTarget ) ;
}
}
return ;
}
2019-08-31 19:28:20 +00:00
# ifndef MAPBASE
2013-12-02 19:31:46 -08:00
else if ( pEvent - > event = = COMBINE_AE_BEGIN_ALTFIRE )
{
EmitSound ( " Weapon_CombineGuard.Special1 " ) ;
return ;
}
else if ( pEvent - > event = = COMBINE_AE_ALTFIRE )
{
animevent_t fakeEvent ;
fakeEvent . pSource = this ;
fakeEvent . event = EVENT_WEAPON_AR2_ALTFIRE ;
GetActiveWeapon ( ) - > Operator_HandleAnimEvent ( & fakeEvent , this ) ;
//m_iNumGrenades--;
return ;
}
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
switch ( pEvent - > event )
{
case 1 :
default :
BaseClass : : HandleAnimEvent ( pEvent ) ;
break ;
}
}
//=========================================================
// Returns a pointer to Alyx's entity
//=========================================================
CNPC_Alyx * CNPC_Alyx : : GetAlyx ( void )
{
return g_AlyxList . m_pClassList ;
}
//=========================================================
//
//=========================================================
bool CNPC_Alyx : : CreateBehaviors ( )
{
AddBehavior ( & m_FuncTankBehavior ) ;
bool result = BaseClass : : CreateBehaviors ( ) ;
return result ;
}
//=========================================================
// Spawn
//=========================================================
void CNPC_Alyx : : Spawn ( )
{
BaseClass : : Spawn ( ) ;
// If Alyx has a parent, she's currently inside a pod. Prevent her from moving.
if ( GetMoveParent ( ) )
{
SetMoveType ( MOVETYPE_NONE ) ;
CapabilitiesClear ( ) ;
CapabilitiesAdd ( bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD ) ;
CapabilitiesAdd ( bits_CAP_FRIENDLY_DMG_IMMUNE ) ;
}
else
{
SetupAlyxWithoutParent ( ) ;
CreateEmpTool ( ) ;
}
AddEFlags ( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION ) ;
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# ifdef MAPBASE
m_iHealth = sk_alyx_health . GetInt ( ) ;
# else
2013-12-02 19:31:46 -08:00
m_iHealth = 80 ;
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# endif
2013-12-02 19:31:46 -08:00
m_bloodColor = DONT_BLEED ;
NPCInit ( ) ;
SetUse ( & CNPC_Alyx : : Use ) ;
m_bInteractionAllowed = true ;
m_fTimeNextSearchForInteractTargets = gpGlobals - > curtime ;
SetEMPHolstered ( true ) ;
m_bDontPickupWeapons = true ;
m_bDarknessSpeechAllowed = true ;
m_fCombatStartTime = 0.0f ;
m_fCombatEndTime = 0.0f ;
m_AnnounceAttackTimer . Set ( 3 , 5 ) ;
}
//=========================================================
// Precache - precaches all resources this NPC needs
//=========================================================
void CNPC_Alyx : : Precache ( )
{
BaseClass : : Precache ( ) ;
PrecacheScriptSound ( " npc_alyx.die " ) ;
PrecacheModel ( STRING ( GetModelName ( ) ) ) ;
PrecacheModel ( " models/alyx_emptool_prop.mdl " ) ;
// For hacking
PrecacheScriptSound ( " DoSpark " ) ;
PrecacheScriptSound ( " npc_alyx.starthacking " ) ;
PrecacheScriptSound ( " npc_alyx.donehacking " ) ;
PrecacheScriptSound ( " npc_alyx.readytohack " ) ;
PrecacheScriptSound ( " npc_alyx.interruptedhacking " ) ;
PrecacheScriptSound ( " ep_01.al_dark_breathing01 " ) ;
PrecacheScriptSound ( " Weapon_CombineGuard.Special1 " ) ;
UTIL_PrecacheOther ( " env_alyxemp " ) ;
CLASSNAME_ALYXGUN = AllocPooledString ( " weapon_alyxgun " ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CLASSNAME_SMG1 = gm_iszSMG1Classname ;
CLASSNAME_SHOTGUN = gm_iszShotgunClassname ;
CLASSNAME_AR2 = gm_iszAR2Classname ;
# else
2013-12-02 19:31:46 -08:00
CLASSNAME_SMG1 = AllocPooledString ( " weapon_smg1 " ) ;
CLASSNAME_SHOTGUN = AllocPooledString ( " weapon_shotgun " ) ;
CLASSNAME_AR2 = AllocPooledString ( " weapon_ar2 " ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : Activate ( void )
{
// Alyx always kicks her health back up to full after loading a savegame.
// Avoids problems with players saving the game in places where she dies immediately afterwards.
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# ifdef MAPBASE
// Alyx's health can be >80 thanks to the new convar, and we don't want a 1000-health Alyx to reload
// from 1 health to 1000 health, so this should only kick in if her health is less than her default
// (we also probably don't want this to happen if she's not an ally)
if ( IsPlayerAlly ( ) & & m_iHealth < 80 )
m_iHealth = 80 ;
# else
2013-12-02 19:31:46 -08:00
m_iHealth = 80 ;
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# endif
2013-12-02 19:31:46 -08:00
BaseClass : : Activate ( ) ;
// Alyx always assumes she has said hello to Gordon!
SetSpokeConcept ( TLK_HELLO , NULL , false ) ;
// Add my personal concepts
CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager ( ) ;
if ( pSpeechManager )
{
ConceptInfo_t conceptRequestItem =
{
CONCEPT_ALYX_REQUEST_ITEM , SPEECH_IMPORTANT , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , AICF_TARGET_PLAYER
} ;
pSpeechManager - > AddCustomConcept ( conceptRequestItem ) ;
}
// cleanup savegames that may not have this set
if ( m_hEmpTool )
{
m_hEmpTool - > AddEffects ( EF_PARENT_ANIMATES ) ;
}
m_WeaponType = ComputeWeaponType ( ) ;
// !!!HACKHACK for Overwatch, If we're in ep2_outland_10, do half damage to Combine
// Be advised, this will also happen in 10a, but this is not a problem.
g_HackOutland10DamageHack = false ;
if ( ! Q_strnicmp ( STRING ( gpGlobals - > mapname ) , " ep2_outland_10 " , 14 ) )
{
g_HackOutland10DamageHack = true ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Please, this is not the worst hack you've caught me doing.
for ( int i = 0 ; i < m_ScriptedInteractions . Count ( ) ; i + + )
{
ScriptedNPCInteraction_t * pInteraction = & m_ScriptedInteractions [ i ] ;
if ( pInteraction - > iszMyWeapon = = CLASSNAME_ALYXGUN )
pInteraction - > iszMyWeapon = AllocPooledString ( " WEPCLASS_HANDGUN " ) ;
else if ( pInteraction - > iszMyWeapon = = CLASSNAME_SHOTGUN )
pInteraction - > iszMyWeapon = AllocPooledString ( " !=WEPCLASS_HANDGUN " ) ;
}
# endif
2013-12-02 19:31:46 -08:00
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : StopLoopingSounds ( void )
{
CSoundEnvelopeController : : GetController ( ) . SoundDestroy ( m_sndDarknessBreathing ) ;
m_sndDarknessBreathing = NULL ;
BaseClass : : StopLoopingSounds ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : SelectModel ( )
{
// Alyx is allowed to use multiple models, because she appears in the pod.
// She defaults to her normal model.
const char * szModel = STRING ( GetModelName ( ) ) ;
if ( ! szModel | | ! * szModel )
{
SetModelName ( AllocPooledString ( " models/alyx.mdl " ) ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : SetupAlyxWithoutParent ( void )
{
SetSolid ( SOLID_BBOX ) ;
AddSolidFlags ( FSOLID_NOT_STANDABLE ) ;
SetMoveType ( MOVETYPE_STEP ) ;
CapabilitiesAdd ( bits_CAP_MOVE_GROUND | bits_CAP_DOORS_GROUP | bits_CAP_TURN_HEAD | bits_CAP_DUCK | bits_CAP_SQUAD ) ;
CapabilitiesAdd ( bits_CAP_USE_WEAPONS ) ;
CapabilitiesAdd ( bits_CAP_ANIMATEDFACE ) ;
CapabilitiesAdd ( bits_CAP_FRIENDLY_DMG_IMMUNE ) ;
CapabilitiesAdd ( bits_CAP_AIM_GUN ) ;
CapabilitiesAdd ( bits_CAP_MOVE_SHOOT ) ;
CapabilitiesAdd ( bits_CAP_USE_SHOT_REGULATOR ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Create and initialized Alyx's EMP tool
//-----------------------------------------------------------------------------
void CNPC_Alyx : : CreateEmpTool ( void )
{
if ( ! m_bShouldHaveEMP | | m_hEmpTool )
return ;
m_hEmpTool = ( CBaseAnimating * ) CreateEntityByName ( " prop_dynamic " ) ;
if ( m_hEmpTool )
{
m_hEmpTool - > SetModel ( " models/alyx_emptool_prop.mdl " ) ;
m_hEmpTool - > SetName ( AllocPooledString ( " Alyx_Emptool " ) ) ;
int iAttachment = LookupAttachment ( " Emp_Holster " ) ;
m_hEmpTool - > SetParent ( this , iAttachment ) ;
m_hEmpTool - > SetOwnerEntity ( this ) ;
m_hEmpTool - > SetSolid ( SOLID_NONE ) ;
m_hEmpTool - > SetLocalOrigin ( Vector ( 0 , 0 , 0 ) ) ;
m_hEmpTool - > SetLocalAngles ( QAngle ( 0 , 0 , 0 ) ) ;
m_hEmpTool - > AddSpawnFlags ( SF_DYNAMICPROP_NO_VPHYSICS ) ;
m_hEmpTool - > AddEffects ( EF_PARENT_ANIMATES ) ;
m_hEmpTool - > Spawn ( ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Map input to create or destroy alyx's EMP tool
//-----------------------------------------------------------------------------
void CNPC_Alyx : : InputGiveEMP ( inputdata_t & inputdata )
{
m_bShouldHaveEMP = inputdata . value . Bool ( ) ;
if ( m_bShouldHaveEMP )
{
if ( ! m_hEmpTool )
{
CreateEmpTool ( ) ;
}
}
else
{
if ( m_hEmpTool )
{
UTIL_Remove ( m_hEmpTool ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
struct ReadinessTransition_t
{
int iPreviousLevel ;
int iCurrentLevel ;
Activity requiredActivity ;
Activity transitionActivity ;
} ;
void CNPC_Alyx : : ReadinessLevelChanged ( int iPriorLevel )
{
BaseClass : : ReadinessLevelChanged ( iPriorLevel ) ;
// When we drop from agitated to stimulated, stand up if we were crouching.
if ( iPriorLevel = = AIRL_AGITATED & & GetReadinessLevel ( ) = = AIRL_STIMULATED )
{
//Warning("CROUCH: Standing, dropping back to stimulated.\n" );
Stand ( ) ;
}
if ( GetActiveWeapon ( ) = = NULL )
return ;
//If Alyx is going from Relaxed to Agitated or Stimulated, let her raise her weapon before she's able to fire.
if ( iPriorLevel = = AIRL_RELAXED & & GetReadinessLevel ( ) > iPriorLevel )
{
GetShotRegulator ( ) - > FireNoEarlierThan ( gpGlobals - > curtime + 0.5 ) ;
}
// FIXME: Are there certain animations that we DO want to interrupt?
if ( HasActiveLayer ( ) )
return ;
if ( npc_alyx_readiness_transitions . GetBool ( ) )
{
// We don't have crouching readiness transitions yet
if ( IsCrouching ( ) )
return ;
static ReadinessTransition_t readinessTransitions [ ] =
{
//Previous Readiness level - Current Readiness Level - Activity NPC needs to be playing - Gesture to play
{ AIRL_RELAXED , AIRL_STIMULATED , ACT_IDLE , ACT_READINESS_RELAXED_TO_STIMULATED , } ,
{ AIRL_RELAXED , AIRL_STIMULATED , ACT_WALK , ACT_READINESS_RELAXED_TO_STIMULATED_WALK , } ,
{ AIRL_AGITATED , AIRL_STIMULATED , ACT_IDLE , ACT_READINESS_AGITATED_TO_STIMULATED , } ,
{ AIRL_STIMULATED , AIRL_RELAXED , ACT_IDLE , ACT_READINESS_STIMULATED_TO_RELAXED , }
} ;
for ( int i = 0 ; i < ARRAYSIZE ( readinessTransitions ) ; i + + )
{
if ( GetIdealActivity ( ) ! = readinessTransitions [ i ] . requiredActivity )
continue ;
Activity translatedTransitionActivity = Weapon_TranslateActivity ( readinessTransitions [ i ] . transitionActivity ) ;
if ( translatedTransitionActivity = = ACT_INVALID | | translatedTransitionActivity = = readinessTransitions [ i ] . transitionActivity )
continue ;
Activity finalActivity = TranslateActivityReadiness ( translatedTransitionActivity ) ;
if ( iPriorLevel = = readinessTransitions [ i ] . iPreviousLevel & & GetReadinessLevel ( ) = = readinessTransitions [ i ] . iCurrentLevel )
{
RestartGesture ( finalActivity ) ;
break ;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : PrescheduleThink ( void )
{
BaseClass : : PrescheduleThink ( ) ;
// Figure out if Alyx has just been removed from her parent
if ( GetMoveType ( ) = = MOVETYPE_NONE & & ! GetMoveParent ( ) )
{
// Don't confuse the passenger behavior with just removing Alyx's parent!
if ( m_PassengerBehavior . IsEnabled ( ) = = false )
{
SetupAlyxWithoutParent ( ) ;
SetupVPhysicsHull ( ) ;
}
}
2019-08-31 19:28:20 +00:00
# ifndef MAPBASE // See CAI_BaseNPC
2013-12-02 19:31:46 -08:00
// If Alyx is in combat, and she doesn't have her gun out, fetch it
if ( GetState ( ) = = NPC_STATE_COMBAT & & IsWeaponHolstered ( ) & & ! m_FuncTankBehavior . IsRunning ( ) )
{
SetDesiredWeaponState ( DESIREDWEAPONSTATE_UNHOLSTERED ) ;
}
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
// If we're in stealth mode, and we can still see the stealth node, keep using it
if ( GetReadinessLevel ( ) = = AIRL_STEALTH )
{
if ( m_hStealthLookTarget & & ! m_hStealthLookTarget - > IsDisabled ( ) )
{
if ( m_hStealthLookTarget - > IsInNodeFOV ( this ) & & FVisible ( m_hStealthLookTarget ) )
return ;
}
// Break out of stealth mode
SetReadinessLevel ( AIRL_STIMULATED , true , true ) ;
ClearLookTarget ( m_hStealthLookTarget ) ;
m_hStealthLookTarget = NULL ;
}
// If we're being blinded by the flashlight, see if we should stop
if ( m_bIsFlashlightBlind )
{
// we used to have a bug where if we tried to remove alyx from the blind scene before it got loaded asynchronously,
// she would get stuck in the animation with m_bIsFlashlightBlind set to false. that should be fixed, but just to
// be sure, we wait a bit to prevent this from happening.
if ( m_fStayBlindUntil < gpGlobals - > curtime )
{
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( pPlayer & & ( ! CanBeBlindedByFlashlight ( true ) | | ! pPlayer - > IsIlluminatedByFlashlight ( this , NULL ) | | ! PlayerFlashlightOnMyEyes ( pPlayer ) ) & &
! BlindedByFlare ( ) )
{
// Remove the actor from the flashlight scene
ADD_DEBUG_HISTORY ( HISTORY_ALYX_BLIND , UTIL_VarArgs ( " (%0.2f) Alyx: end blind scene '%s' \n " , gpGlobals - > curtime , STRING ( m_iszCurrentBlindScene ) ) ) ;
RemoveActorFromScriptedScenes ( this , true , false , STRING ( m_iszCurrentBlindScene ) ) ;
// Allow firing again, but prevent myself from firing until I'm done
GetShotRegulator ( ) - > EnableShooting ( ) ;
GetShotRegulator ( ) - > FireNoEarlierThan ( gpGlobals - > curtime + 1.0 ) ;
m_bIsFlashlightBlind = false ;
m_flDontBlindUntil = gpGlobals - > curtime + RandomFloat ( 1 , 3 ) ;
}
}
}
else
{
CheckBlindedByFlare ( ) ;
}
}
//-----------------------------------------------------------------------------
// Periodically look for opportunities to interact with objects in the world.
// Right now Alyx only interacts with things the player picks up with
// physcannon.
//-----------------------------------------------------------------------------
# define ALYX_INTERACT_SEARCH_FREQUENCY 1.0f // seconds
void CNPC_Alyx : : SearchForInteractTargets ( )
{
if ( m_fTimeNextSearchForInteractTargets > gpGlobals - > curtime )
{
return ;
}
m_fTimeNextSearchForInteractTargets = gpGlobals - > curtime + ALYX_INTERACT_SEARCH_FREQUENCY ;
// Ensure player can be seen.
if ( ! HasCondition ( COND_SEE_PLAYER ) )
{
//Msg("ALYX Can't interact: can't see player\n");
return ;
}
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( ! pPlayer )
{
return ;
}
CBaseEntity * pProspect = PhysCannonGetHeldEntity ( pPlayer - > GetActiveWeapon ( ) ) ;
if ( ! pProspect )
{
//Msg("ALYX Can't interact: player not holding anything\n");
return ;
}
if ( ! IsValidInteractTarget ( pProspect ) )
{
//Msg("ALYX Can't interact: player holding an invalid object\n");
return ;
}
SetInteractTarget ( pProspect ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : GatherConditions ( )
{
BaseClass : : GatherConditions ( ) ;
if ( HasCondition ( COND_HEAR_DANGER ) )
{
// Don't let Alyx worry about combat sounds if she's panicking
// from danger sounds. This prevents her from running ALERT_FACE_BEST_SOUND
// as soon as a grenade explodes (which makes a loud combat sound). If Alyx
// is NOT panicking over a Danger sound, she'll hear the combat sounds as normal.
ClearCondition ( COND_HEAR_COMBAT ) ;
}
// Update flashlight state
ClearCondition ( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) ;
ClearCondition ( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT ) ;
ClearCondition ( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) ;
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( pPlayer )
{
bool bFlashlightState = pPlayer - > FlashlightIsOn ( ) ! = 0 ;
if ( bFlashlightState ! = m_bPlayerFlashlightState )
{
if ( bFlashlightState )
{
SetCondition ( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT ) ;
}
else
{
// If the power level is low, consider it expired, due
// to it running out or the player turning it off in anticipation.
CHL2_Player * pHLPlayer = assert_cast < CHL2_Player * > ( pPlayer ) ;
if ( pHLPlayer - > SuitPower_GetCurrentPercentage ( ) < 15 )
{
SetCondition ( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) ;
}
else
{
SetCondition ( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) ;
}
}
m_bPlayerFlashlightState = bFlashlightState ;
}
}
2019-08-31 19:28:20 +00:00
# ifndef MAPBASE // Moved to CNPC_PlayerCompanion
2013-12-02 19:31:46 -08:00
if ( m_NPCState = = NPC_STATE_COMBAT )
{
DoCustomCombatAI ( ) ;
}
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
if ( HasInteractTarget ( ) )
{
// Check that any current interact target is still valid.
if ( ! IsValidInteractTarget ( GetInteractTarget ( ) ) )
{
SetInteractTarget ( NULL ) ;
}
}
// This is not an else...if because the code above could have started
// with an interact target and ended without one.
if ( ! HasInteractTarget ( ) )
{
SearchForInteractTargets ( ) ;
}
// Set up our interact conditions.
if ( HasInteractTarget ( ) )
{
if ( CanInteractWithTarget ( GetInteractTarget ( ) ) )
{
SetCondition ( COND_ALYX_CAN_INTERACT_WITH_TARGET ) ;
ClearCondition ( COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET ) ;
}
else
{
SetCondition ( COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET ) ;
ClearCondition ( COND_ALYX_CAN_INTERACT_WITH_TARGET ) ;
}
SetCondition ( COND_ALYX_HAS_INTERACT_TARGET ) ;
ClearCondition ( COND_ALYX_NO_INTERACT_TARGET ) ;
}
else
{
SetCondition ( COND_ALYX_NO_INTERACT_TARGET ) ;
ClearCondition ( COND_ALYX_HAS_INTERACT_TARGET ) ;
}
// Check for explosions!
if ( HasCondition ( COND_HEAR_COMBAT ) )
{
CSound * pSound = GetBestSound ( ) ;
if ( IsInAVehicle ( ) = = false ) // For now, don't do these animations while in the vehicle
{
if ( ( pSound - > SoundTypeNoContext ( ) & SOUND_COMBAT ) & & ( pSound - > SoundContext ( ) & SOUND_CONTEXT_EXPLOSION ) )
{
if ( HasShotgun ( ) )
{
if ( ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST_SHOTGUN ) & & ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN ) )
{
RestartGesture ( ACT_GESTURE_FLINCH_BLAST_SHOTGUN ) ;
GetShotRegulator ( ) - > FireNoEarlierThan ( gpGlobals - > curtime + SequenceDuration ( ACT_GESTURE_FLINCH_BLAST_SHOTGUN ) + 0.5f ) ; // Allow another second for Alyx to bring her weapon to bear after the flinch.
}
}
else
{
if ( ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST ) & & ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST_DAMAGED ) )
{
RestartGesture ( ACT_GESTURE_FLINCH_BLAST ) ;
GetShotRegulator ( ) - > FireNoEarlierThan ( gpGlobals - > curtime + SequenceDuration ( ACT_GESTURE_FLINCH_BLAST ) + 0.5f ) ; // Allow another second for Alyx to bring her weapon to bear after the flinch.
}
}
}
}
}
// ROBIN: This was here to solve a problem in a playtest. We've since found what we think was the cause.
// It's a useful piece of debug to have lying there, so I've left it in.
if ( ( GetFlags ( ) & FL_FLY ) & & m_NPCState ! = NPC_STATE_SCRIPT & & ! m_ActBusyBehavior . IsActive ( ) & & ! m_PassengerBehavior . IsEnabled ( ) )
{
Warning ( " Removed FL_FLY from Alyx, who wasn't running a script or actbusy. Time %.2f, map %s. \n " , gpGlobals - > curtime , STRING ( gpGlobals - > mapname ) ) ;
RemoveFlag ( FL_FLY ) ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : ShouldPlayerAvoid ( void )
{
if ( IsCurSchedule ( SCHED_ALYX_NEW_WEAPON , false ) )
return true ;
# if 1
if ( IsCurSchedule ( SCHED_PC_GET_OFF_COMPANION , false ) )
{
CBaseEntity * pGroundEnt = GetGroundEntity ( ) ;
if ( pGroundEnt ! = NULL & & pGroundEnt - > IsPlayer ( ) )
{
if ( GetAbsOrigin ( ) . z < pGroundEnt - > EyePosition ( ) . z )
return true ;
}
}
# endif
return BaseClass : : ShouldPlayerAvoid ( ) ;
}
//-----------------------------------------------------------------------------
// Just heard a gunfire sound. Try to figure out how much we should know
// about it.
//-----------------------------------------------------------------------------
void CNPC_Alyx : : AnalyzeGunfireSound ( CSound * pSound )
{
Assert ( pSound ! = NULL ) ;
if ( GetState ( ) ! = NPC_STATE_ALERT & & GetState ( ) ! = NPC_STATE_IDLE )
{
// Only have code for IDLE and ALERT now.
return ;
}
// Have to verify a bunch of stuff about the sound. It must have a valid BaseCombatCharacter as the owner,
// must have a valid target, and we need a valid pointer to the player.
if ( pSound - > m_hOwner . Get ( ) = = NULL )
return ;
if ( pSound - > m_hTarget . Get ( ) = = NULL )
return ;
CBaseCombatCharacter * pSoundOriginBCC = pSound - > m_hOwner - > MyCombatCharacterPointer ( ) ;
if ( pSoundOriginBCC = = NULL )
return ;
CBaseEntity * pSoundTarget = pSound - > m_hTarget . Get ( ) ;
CBasePlayer * pPlayer = AI_GetSinglePlayer ( ) ;
Assert ( pPlayer ! = NULL ) ;
if ( pSoundTarget = = this )
{
// The shooter is firing at me. Assume if Alyx can hear the gunfire, she can deduce its origin.
UpdateEnemyMemory ( pSoundOriginBCC , pSoundOriginBCC - > GetAbsOrigin ( ) , this ) ;
}
else if ( pSoundTarget = = pPlayer )
{
// The shooter is firing at the player. Assume Alyx can deduce the origin if the player COULD see the origin, and Alyx COULD see the player.
if ( pPlayer - > FVisible ( pSoundOriginBCC ) & & FVisible ( pPlayer ) )
{
UpdateEnemyMemory ( pSoundOriginBCC , pSoundOriginBCC - > GetAbsOrigin ( ) , this ) ;
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : IsValidEnemy ( CBaseEntity * pEnemy )
{
if ( HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) )
{
if ( ! CanSeeEntityInDarkness ( pEnemy ) )
return false ;
}
// Alyx can only take a stalker as her enemy which is angry at the player or her.
if ( pEnemy - > Classify ( ) = = CLASS_STALKER )
{
if ( ! pEnemy - > GetEnemy ( ) )
{
return false ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Come to the defense of anyone we like, not just ourselves or the player.
if ( pEnemy - > GetEnemy ( ) ! = this & & IRelationType ( pEnemy - > GetEnemy ( ) ) = = D_LI )
# else
2013-12-02 19:31:46 -08:00
if ( pEnemy - > GetEnemy ( ) ! = this & & ! pEnemy - > GetEnemy ( ) - > IsPlayer ( ) )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
return false ;
}
}
if ( m_AssaultBehavior . IsRunning ( ) & & IsTurret ( pEnemy ) )
{
CBaseCombatCharacter * pBCC = dynamic_cast < CBaseCombatCharacter * > ( pEnemy ) ;
if ( pBCC ! = NULL & & ! pBCC - > FInViewCone ( this ) )
{
// Don't let turrets that can't shoot me distract me from my assault behavior.
// This fixes a very specific problem that appeared in Episode 2 map ep2_outland_09
// Where Alyx wouldn't terminate an assault while standing on an assault point because
// she was afraid of a turret that was visible from the assault point, but facing the
// other direction and thus not a threat.
return false ;
}
}
return BaseClass : : IsValidEnemy ( pEnemy ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : Event_Killed ( const CTakeDamageInfo & info )
{
// Destroy our EMP tool since it won't follow us onto the ragdoll anyway
if ( m_hEmpTool ! = NULL )
{
UTIL_Remove ( m_hEmpTool ) ;
}
BaseClass : : Event_Killed ( info ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : Event_KilledOther ( CBaseEntity * pVictim , const CTakeDamageInfo & info )
{
// comment on killing npc's
if ( pVictim - > IsNPC ( ) )
{
SpeakIfAllowed ( TLK_ALYX_ENEMY_DEAD ) ;
}
// Alyx builds a proxy for the dead enemy so she has something to shoot at for a short time after
// the enemy ragdolls.
if ( ! ( pVictim - > GetFlags ( ) & FL_ONGROUND ) | | pVictim - > GetMoveType ( ) ! = MOVETYPE_STEP )
{
// Don't fire up in the air, since the dead enemy will have fallen.
return ;
}
if ( pVictim - > GetAbsOrigin ( ) . DistTo ( GetAbsOrigin ( ) ) < 96.0f )
{
// Don't shoot at an enemy corpse that dies very near to me. This will prevent Alyx attacking
// Other nearby enemies.
return ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Don't do the custom target thing against dissolve or blast damage (Alyx can do that with companion grenades/balls)
if ( ! HasShotgun ( ) & & ! ( info . GetDamageType ( ) & ( DMG_DISSOLVE | DMG_BLAST ) ) )
# else
2013-12-02 19:31:46 -08:00
if ( ! HasShotgun ( ) )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
CAI_BaseNPC * pTarget = CreateCustomTarget ( pVictim - > GetAbsOrigin ( ) , 2.0f ) ;
AddEntityRelationship ( pTarget , IRelationType ( pVictim ) , IRelationPriority ( pVictim ) ) ;
// Update or Create a memory entry for this target and make Alyx think she's seen this target recently.
// This prevents the baseclass from not recognizing this target and forcing Alyx into
// SCHED_WAKE_ANGRY, which wastes time and causes her to change animation sequences rapidly.
GetEnemies ( ) - > UpdateMemory ( GetNavigator ( ) - > GetNetwork ( ) , pTarget , pTarget - > GetAbsOrigin ( ) , 0.0f , true ) ;
AI_EnemyInfo_t * pMemory = GetEnemies ( ) - > Find ( pTarget ) ;
if ( pMemory )
{
// Pretend we've known about this target longer than we really have.
pMemory - > timeFirstSeen = gpGlobals - > curtime - 10.0f ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Called by enemy NPC's when they are ignited
// Input : pVictim - entity that was ignited
//-----------------------------------------------------------------------------
void CNPC_Alyx : : EnemyIgnited ( CAI_BaseNPC * pVictim )
{
if ( FVisible ( pVictim ) )
{
SpeakIfAllowed ( TLK_ENEMY_BURNING ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Called by combine balls when they're socketed
// Input : pVictim - entity killed by player
//-----------------------------------------------------------------------------
void CNPC_Alyx : : CombineBallSocketed ( int iNumBounces )
{
CBasePlayer * pPlayer = AI_GetSinglePlayer ( ) ;
if ( ! pPlayer | | ! FVisible ( pPlayer ) )
{
return ;
}
// set up the speech modifiers
CFmtStrN < 128 > modifiers ( " num_bounces:%d " , iNumBounces ) ;
// fire off a ball socketed concept
SpeakIfAllowed ( TLK_BALLSOCKETED , modifiers ) ;
}
//-----------------------------------------------------------------------------
// Purpose: If we're a passenger in a vehicle
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : RunningPassengerBehavior ( void )
{
// Must be active and not outside the vehicle
if ( m_PassengerBehavior . IsRunning ( ) & & m_PassengerBehavior . GetPassengerState ( ) ! = PASSENGER_STATE_OUTSIDE )
return true ;
return false ;
}
//-----------------------------------------------------------------------------
// Purpose: Handle "mobbed" combat condition when Alyx is overwhelmed by force
//-----------------------------------------------------------------------------
void CNPC_Alyx : : DoMobbedCombatAI ( void )
{
AIEnemiesIter_t iter ;
float visibleEnemiesScore = 0.0f ;
float closeEnemiesScore = 0.0f ;
for ( AI_EnemyInfo_t * pEMemory = GetEnemies ( ) - > GetFirst ( & iter ) ; pEMemory ! = NULL ; pEMemory = GetEnemies ( ) - > GetNext ( & iter ) )
{
if ( IRelationType ( pEMemory - > hEnemy ) ! = D_NU & & IRelationType ( pEMemory - > hEnemy ) ! = D_LI & & pEMemory - > hEnemy - > GetAbsOrigin ( ) . DistToSqr ( GetAbsOrigin ( ) ) < = ALYX_MIN_CONSIDER_DIST )
{
if ( pEMemory - > hEnemy & & pEMemory - > hEnemy - > IsAlive ( ) & & gpGlobals - > curtime - pEMemory - > timeLastSeen < = 0.5f & & pEMemory - > hEnemy - > Classify ( ) ! = CLASS_BULLSEYE )
{
if ( pEMemory - > hEnemy - > GetAbsOrigin ( ) . DistToSqr ( GetAbsOrigin ( ) ) < = ALYX_MIN_MOB_DIST_SQR )
{
closeEnemiesScore + = 1.0f ;
}
else
{
visibleEnemiesScore + = 1.0f ;
}
}
}
}
if ( closeEnemiesScore > 2 )
{
SetCondition ( COND_MOBBED_BY_ENEMIES ) ;
// mark anyone in the mob as having mobbed me
for ( AI_EnemyInfo_t * pEMemory = GetEnemies ( ) - > GetFirst ( & iter ) ; pEMemory ! = NULL ; pEMemory = GetEnemies ( ) - > GetNext ( & iter ) )
{
if ( pEMemory - > bMobbedMe )
continue ;
if ( IRelationType ( pEMemory - > hEnemy ) ! = D_NU & & IRelationType ( pEMemory - > hEnemy ) ! = D_LI & & pEMemory - > hEnemy - > GetAbsOrigin ( ) . DistToSqr ( GetAbsOrigin ( ) ) < = ALYX_MIN_CONSIDER_DIST )
{
if ( pEMemory - > hEnemy & & pEMemory - > hEnemy - > IsAlive ( ) & & gpGlobals - > curtime - pEMemory - > timeLastSeen < = 0.5f & & pEMemory - > hEnemy - > Classify ( ) ! = CLASS_BULLSEYE )
{
if ( pEMemory - > hEnemy - > GetAbsOrigin ( ) . DistToSqr ( GetAbsOrigin ( ) ) < = ALYX_MIN_MOB_DIST_SQR )
{
pEMemory - > bMobbedMe = true ;
}
}
}
}
}
else
{
ClearCondition ( COND_MOBBED_BY_ENEMIES ) ;
}
// Alyx's gun can never run out of ammo. Allow Alyx to ignore LOW AMMO warnings
// if she's in a close quarters fight with several enemies. She'll attempt to reload
// as soon as her combat situation is less pressing.
if ( HasCondition ( COND_MOBBED_BY_ENEMIES ) )
{
ClearCondition ( COND_LOW_PRIMARY_AMMO ) ;
}
// Say a combat thing
if ( HasCondition ( COND_MOBBED_BY_ENEMIES ) )
{
SpeakIfAllowed ( TLK_MOBBED ) ;
}
else if ( visibleEnemiesScore > 4 )
{
SpeakIfAllowed ( TLK_MANY_ENEMIES ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Custom AI for Alyx while in combat
//-----------------------------------------------------------------------------
void CNPC_Alyx : : DoCustomCombatAI ( void )
{
// Only run the following code if we're not in a vehicle
if ( RunningPassengerBehavior ( ) = = false )
{
// Do our mobbed by enemies logic
DoMobbedCombatAI ( ) ;
}
CBaseEntity * pEnemy = GetEnemy ( ) ;
if ( HasCondition ( COND_LOW_PRIMARY_AMMO ) )
{
if ( pEnemy )
{
if ( GetAbsOrigin ( ) . DistToSqr ( pEnemy - > GetAbsOrigin ( ) ) < Square ( 60.0f ) )
{
// Don't reload if an enemy is right in my face.
ClearCondition ( COND_LOW_PRIMARY_AMMO ) ;
}
}
}
if ( HasCondition ( COND_LIGHT_DAMAGE ) )
{
if ( pEnemy & & ! IsCrouching ( ) )
{
// If my enemy is shooting at me from a distance, crouch for protection
if ( EnemyDistance ( pEnemy ) > ALYX_MIN_ENEMY_DIST_TO_CROUCH )
{
DesireCrouch ( ) ;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : DoCustomSpeechAI ( void )
{
BaseClass : : DoCustomSpeechAI ( ) ;
CBasePlayer * pPlayer = AI_GetSinglePlayer ( ) ;
if ( HasCondition ( COND_NEW_ENEMY ) & & GetEnemy ( ) )
{
if ( GetEnemy ( ) - > Classify ( ) = = CLASS_HEADCRAB )
{
CBaseHeadcrab * pHC = assert_cast < CBaseHeadcrab * > ( GetEnemy ( ) ) ;
// If we see a headcrab for the first time as he's jumping at me, freak out!
if ( ( GetEnemy ( ) - > GetEnemy ( ) = = this ) & & pHC - > IsJumping ( ) & & gpGlobals - > curtime - GetEnemies ( ) - > FirstTimeSeen ( GetEnemy ( ) ) < 0.5 )
{
SpeakIfAllowed ( " TLK_SPOTTED_INCOMING_HEADCRAB " ) ;
}
// If we see a headcrab leaving a zombie that just died, mention it
else if ( pHC - > GetOwnerEntity ( ) & & ( pHC - > GetOwnerEntity ( ) - > Classify ( ) = = CLASS_ZOMBIE ) & & ! pHC - > GetOwnerEntity ( ) - > IsAlive ( ) )
{
SpeakIfAllowed ( " TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE " ) ;
}
}
else if ( GetEnemy ( ) - > Classify ( ) = = CLASS_ZOMBIE )
{
CNPC_BaseZombie * pZombie = assert_cast < CNPC_BaseZombie * > ( GetEnemy ( ) ) ;
// If we see a zombie getting up, mention it
if ( pZombie - > IsGettingUp ( ) )
{
SpeakIfAllowed ( " TLK_SPOTTED_ZOMBIE_WAKEUP " ) ;
}
}
}
// Darkness mode speech
ClearCondition ( COND_ALYX_IN_DARK ) ;
if ( HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) )
{
// Even though the darkness light system will take flares into account when Alyx
// says she's lost the player in the darkness, players still think she's silly
// when they're too far from the flare to be seen.
// So, check for lit flares or other dynamic lights, and don't do
// a bunch of the darkness speech if there's a lit flare nearby.
bool bNearbyFlare = DarknessLightSourceWithinRadius ( this , 500 ) ;
if ( ! bNearbyFlare )
{
SetCondition ( COND_ALYX_IN_DARK ) ;
if ( HasCondition ( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) | | HasCondition ( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) )
{
// Player just turned off the flashlight. Start ramping up Alyx's breathing.
if ( ! m_sndDarknessBreathing )
{
CPASAttenuationFilter filter ( this ) ;
m_sndDarknessBreathing = CSoundEnvelopeController : : GetController ( ) . SoundCreate ( filter , entindex ( ) , CHAN_STATIC ,
" ep_01.al_dark_breathing01 " , SNDLVL_TALKING ) ;
CSoundEnvelopeController : : GetController ( ) . Play ( m_sndDarknessBreathing , 0.0f , PITCH_NORM ) ;
}
if ( m_sndDarknessBreathing )
{
CSoundEnvelopeController : : GetController ( ) . SoundChangeVolume ( m_sndDarknessBreathing , ALYX_BREATHING_VOLUME_MAX , RandomFloat ( 10 , 20 ) ) ;
m_SpeechWatch_BreathingRamp . Stop ( ) ;
}
}
}
// If we lose an enemy due to the flashlight, comment about it
if ( ! HasCondition ( COND_SEE_ENEMY ) & & m_bHadCondSeeEnemy & & ! HasCondition ( COND_TALKER_PLAYER_DEAD ) )
{
if ( m_bDarknessSpeechAllowed & & HasCondition ( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) & &
GetEnemy ( ) & & ( GetEnemy ( ) - > Classify ( ) ! = CLASS_BULLSEYE ) )
{
SpeakIfAllowed ( " TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT " ) ;
}
else if ( m_bDarknessSpeechAllowed & & HasCondition ( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) & &
GetEnemy ( ) & & ( GetEnemy ( ) - > Classify ( ) ! = CLASS_BULLSEYE ) )
{
SpeakIfAllowed ( " TLK_DARKNESS_LOSTENEMY_BY_FLASHLIGHT_EXPIRED " ) ;
}
else if ( m_bDarknessSpeechAllowed & & GetEnemy ( ) & & ( GetEnemy ( ) - > Classify ( ) ! = CLASS_BULLSEYE ) & &
pPlayer & & pPlayer - > FlashlightIsOn ( ) & & ! pPlayer - > IsIlluminatedByFlashlight ( GetEnemy ( ) , NULL ) & &
FVisible ( GetEnemy ( ) ) )
{
SpeakIfAllowed ( TLK_DARKNESS_ENEMY_IN_DARKNESS ) ;
}
m_bHadCondSeeEnemy = false ;
}
else if ( HasCondition ( COND_SEE_ENEMY ) )
{
m_bHadCondSeeEnemy = true ;
}
else if ( ( ! GetEnemy ( ) | | ( GetEnemy ( ) - > Classify ( ) = = CLASS_BULLSEYE ) ) & & m_bDarknessSpeechAllowed )
{
if ( HasCondition ( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) )
{
SpeakIfAllowed ( TLK_DARKNESS_FLASHLIGHT_EXPIRED ) ;
}
else if ( HasCondition ( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) )
{
SpeakIfAllowed ( TLK_FLASHLIGHT_OFF ) ;
}
else if ( HasCondition ( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT ) )
{
SpeakIfAllowed ( TLK_FLASHLIGHT_ON ) ;
}
}
// If we've just seen a new enemy, and it's illuminated by the flashlight,
// tell the player to keep the flashlight on 'em.
if ( HasCondition ( COND_NEW_ENEMY ) & & ! HasCondition ( COND_TALKER_PLAYER_DEAD ) )
{
// First time we've seen this guy?
if ( gpGlobals - > curtime - GetEnemies ( ) - > FirstTimeSeen ( GetEnemy ( ) ) < 0.5 )
{
if ( pPlayer & & pPlayer - > IsIlluminatedByFlashlight ( GetEnemy ( ) , NULL ) & & m_bDarknessSpeechAllowed & &
! LookerCouldSeeTargetInDarkness ( this , GetEnemy ( ) ) )
{
SpeakIfAllowed ( " TLK_DARKNESS_FOUNDENEMY_BY_FLASHLIGHT " ) ;
}
}
}
// When we lose the player, start lost-player talker after some time
if ( ! bNearbyFlare & & m_bDarknessSpeechAllowed )
{
if ( ! HasCondition ( COND_SEE_PLAYER ) & & ! m_SpeechWatch_LostPlayer . IsRunning ( ) )
{
m_SpeechWatch_LostPlayer . Set ( 5 , 8 ) ;
m_SpeechWatch_LostPlayer . Start ( ) ;
m_MoveMonitor . SetMark ( AI_GetSinglePlayer ( ) , 48 ) ;
}
else if ( m_SpeechWatch_LostPlayer . Expired ( ) )
{
// Can't see the player?
if ( ! HasCondition ( COND_SEE_PLAYER ) & & ! HasCondition ( COND_TALKER_PLAYER_DEAD ) & & ! HasCondition ( COND_SEE_ENEMY ) & &
( ! pPlayer | | pPlayer - > GetAbsOrigin ( ) . DistToSqr ( GetAbsOrigin ( ) ) > ALYX_DARKNESS_LOST_PLAYER_DIST ) )
{
// only speak if player hasn't moved.
if ( m_MoveMonitor . TargetMoved ( AI_GetSinglePlayer ( ) ) )
{
SpeakIfAllowed ( " TLK_DARKNESS_LOSTPLAYER " ) ;
m_SpeechWatch_LostPlayer . Set ( 10 ) ;
m_SpeechWatch_LostPlayer . Start ( ) ;
m_bSpokeLostPlayerInDarkness = true ;
}
}
}
// Speech concepts that only occur when the player's flashlight is off
if ( pPlayer & & ! HasCondition ( COND_TALKER_PLAYER_DEAD ) & & ! pPlayer - > FlashlightIsOn ( ) )
{
// When the player first turns off the light, don't talk about sounds for a bit
if ( HasCondition ( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT ) | | HasCondition ( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED ) )
{
m_SpeechTimer_HeardSound . Set ( 4 ) ;
}
else if ( m_SpeechWatch_SoundDelay . Expired ( ) )
{
// We've waited for a bit after the sound, now talk about it
SpeakIfAllowed ( " TLK_DARKNESS_HEARDSOUND " ) ;
m_SpeechWatch_SoundDelay . Stop ( ) ;
}
else if ( HasCondition ( COND_HEAR_SPOOKY ) )
{
// If we hear anything while the player's flashlight is off, randomly mention it
if ( m_SpeechTimer_HeardSound . Expired ( ) )
{
m_SpeechTimer_HeardSound . Set ( 10 ) ;
// Wait for the sound to play for a bit before speaking about it
m_SpeechWatch_SoundDelay . Set ( 1.0 , 3.0 ) ;
m_SpeechWatch_SoundDelay . Start ( ) ;
}
}
}
}
// Stop the heard sound response if the player turns the flashlight on
if ( bNearbyFlare | | HasCondition ( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT ) )
{
m_SpeechWatch_SoundDelay . Stop ( ) ;
if ( m_sndDarknessBreathing )
{
CSoundEnvelopeController : : GetController ( ) . SoundChangeVolume ( m_sndDarknessBreathing , 0.0f , 0.5 ) ;
m_SpeechWatch_BreathingRamp . Stop ( ) ;
}
}
}
else
{
if ( m_sndDarknessBreathing )
{
CSoundEnvelopeController : : GetController ( ) . SoundChangeVolume ( m_sndDarknessBreathing , 0.0f , 0.5 ) ;
m_SpeechWatch_BreathingRamp . Stop ( ) ;
}
if ( ! HasCondition ( COND_SEE_PLAYER ) & & ! m_SpeechWatch_FoundPlayer . IsRunning ( ) )
{
// wait a minute before saying something when alyx sees him again
m_SpeechWatch_FoundPlayer . Set ( 60 , 75 ) ;
m_SpeechWatch_FoundPlayer . Start ( ) ;
}
else if ( HasCondition ( COND_SEE_PLAYER ) )
{
if ( m_SpeechWatch_FoundPlayer . Expired ( ) & & m_bDarknessSpeechAllowed )
{
SpeakIfAllowed ( " TLK_FOUNDPLAYER " ) ;
}
m_SpeechWatch_FoundPlayer . Stop ( ) ;
}
}
// If we spoke lost-player, and now we see him/her, say so
if ( m_bSpokeLostPlayerInDarkness )
{
// If we've left darkness mode, or if the player has blinded me with
// the flashlight, don't bother speaking the found player line.
if ( ! m_bIsFlashlightBlind & & HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) & & m_bDarknessSpeechAllowed )
{
if ( HasCondition ( COND_SEE_PLAYER ) & & ! HasCondition ( COND_TALKER_PLAYER_DEAD ) )
{
if ( ( m_fTimeUntilNextDarknessFoundPlayer = = AI_INVALID_TIME ) | | ( gpGlobals - > curtime < m_fTimeUntilNextDarknessFoundPlayer ) )
{
SpeakIfAllowed ( " TLK_DARKNESS_FOUNDPLAYER " ) ;
}
m_bSpokeLostPlayerInDarkness = false ;
}
}
else
{
m_bSpokeLostPlayerInDarkness = false ;
}
}
if ( ( ! m_bDarknessSpeechAllowed | | HasCondition ( COND_SEE_PLAYER ) ) & & m_SpeechWatch_LostPlayer . IsRunning ( ) )
{
m_SpeechWatch_LostPlayer . Stop ( ) ;
m_MoveMonitor . ClearMark ( ) ;
}
// Ramp the breathing back up after speaking
if ( m_SpeechWatch_BreathingRamp . IsRunning ( ) )
{
if ( m_SpeechWatch_BreathingRamp . Expired ( ) )
{
CSoundEnvelopeController : : GetController ( ) . SoundChangeVolume ( m_sndDarknessBreathing , ALYX_BREATHING_VOLUME_MAX , RandomFloat ( 5 , 10 ) ) ;
m_SpeechWatch_BreathingRamp . Stop ( ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : SpeakIfAllowed ( AIConcept_t concept , const char * modifiers /*= NULL*/ , bool bRespondingToPlayer /*= false*/ , char * pszOutResponseChosen /*= NULL*/ , size_t bufsize /* = 0 */ )
{
if ( BaseClass : : SpeakIfAllowed ( concept , modifiers , bRespondingToPlayer , pszOutResponseChosen , bufsize ) )
{
// If we're breathing in the darkness, drop the volume quickly
if ( m_sndDarknessBreathing & & CSoundEnvelopeController : : GetController ( ) . SoundGetVolume ( m_sndDarknessBreathing ) > 0.0 )
{
CSoundEnvelopeController : : GetController ( ) . SoundChangeVolume ( m_sndDarknessBreathing , 0.0f , 0.1 ) ;
// Ramp up the sound again after the response is over
float flDelay = ( GetTimeSpeechComplete ( ) - gpGlobals - > curtime ) ;
m_SpeechWatch_BreathingRamp . Set ( flDelay ) ;
m_SpeechWatch_BreathingRamp . Start ( ) ;
}
return true ;
}
return false ;
}
extern int ACT_ANTLION_FLIP ;
extern int ACT_ANTLION_ZAP_FLIP ;
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Disposition_t CNPC_Alyx : : IRelationType ( CBaseEntity * pTarget )
{
Disposition_t disposition = BaseClass : : IRelationType ( pTarget ) ;
if ( pTarget = = NULL )
return disposition ;
if ( pTarget - > Classify ( ) = = CLASS_ANTLION )
{
if ( disposition = = D_HT )
{
// If Alyx hates this antlion (default relationship), make her fear it, if it is very close.
if ( GetAbsOrigin ( ) . DistToSqr ( pTarget - > GetAbsOrigin ( ) ) < ALYX_FEAR_ANTLION_DIST_SQR )
{
disposition = D_FR ;
}
// Fall through...
}
}
else if ( pTarget - > Classify ( ) = = CLASS_ZOMBIE & & disposition = = D_HT & & GetActiveWeapon ( ) )
{
if ( GetAbsOrigin ( ) . DistToSqr ( pTarget - > GetAbsOrigin ( ) ) < ALYX_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 ;
}
}
else if ( pTarget - > Classify ( ) = = CLASS_MISSILE )
{
// Fire at missiles while in the vehicle
if ( IsInAVehicle ( ) )
return D_HT ;
}
return disposition ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Alyx : : IRelationPriority ( CBaseEntity * pTarget )
{
int priority = BaseClass : : IRelationPriority ( pTarget ) ;
if ( pTarget - > Classify ( ) = = CLASS_ANTLION )
{
// Make Alyx prefer Antlions that are flipped onto their backs.
// UNLESS she has a different enemy that could melee attack her while her back is turned.
CAI_BaseNPC * pNPC = pTarget - > MyNPCPointer ( ) ;
if ( pNPC & & ( pNPC - > GetActivity ( ) = = ACT_ANTLION_FLIP | | pNPC - > GetActivity ( ) = = ACT_ANTLION_ZAP_FLIP ) )
{
if ( GetEnemy ( ) & & GetEnemy ( ) ! = pTarget )
{
// I have an enemy that is not this thing. If that enemy is near, I shouldn't
// become distracted.
if ( GetAbsOrigin ( ) . DistToSqr ( GetEnemy ( ) - > GetAbsOrigin ( ) ) < Square ( 180 ) )
{
return priority ;
}
}
priority + = 1 ;
}
}
return priority ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
# define ALYX_360_VIEW_DIST_SQR 129600 // 30 feet
bool CNPC_Alyx : : FInViewCone ( CBaseEntity * pEntity )
{
// Alyx can see 360 degrees but only at limited distance. This allows her to be aware of a
// large mob of enemies (usually antlions or zombies) closing in. This situation is so obvious to the
// player that it doesn't make sense for Alyx to be unaware of the entire group simply because she
// hasn't seen all of the enemies with her own eyes.
if ( ( pEntity - > IsNPC ( ) | | pEntity - > IsPlayer ( ) ) & & pEntity - > GetAbsOrigin ( ) . DistToSqr ( GetAbsOrigin ( ) ) < = ALYX_360_VIEW_DIST_SQR )
{
// Only see players and NPC's with 360 cone
// For instance, DON'T tell the eyeball/head tracking code that you can see an object that is behind you!
return true ;
}
// Else, fall through...
if ( HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) )
{
if ( CanSeeEntityInDarkness ( pEntity ) )
return true ;
}
return BaseClass : : FInViewCone ( pEntity ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pEntity -
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : CanSeeEntityInDarkness ( CBaseEntity * pEntity )
{
/*
// Alyx can see enemies that are right next to her
// Robin: Disabled, made her too effective, you could safely leave her alone.
if ( pEntity - > IsNPC ( ) )
{
if ( ( pEntity - > WorldSpaceCenter ( ) - EyePosition ( ) ) . LengthSqr ( ) < ( 80 * 80 ) )
return true ;
}
*/
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( 1 ) ;
if ( pPlayer & & pEntity ! = pPlayer )
{
if ( pPlayer - > IsIlluminatedByFlashlight ( pEntity , NULL ) )
return true ;
}
return LookerCouldSeeTargetInDarkness ( this , pEntity ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : QuerySeeEntity ( CBaseEntity * pEntity , bool bOnlyHateOrFearIfNPC )
{
if ( HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) )
{
if ( ! CanSeeEntityInDarkness ( pEntity ) )
return false ;
}
return BaseClass : : QuerySeeEntity ( pEntity , bOnlyHateOrFearIfNPC ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : IsCoverPosition ( const Vector & vecThreat , const Vector & vecPosition )
{
return BaseClass : : IsCoverPosition ( vecThreat , vecPosition ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
Activity CNPC_Alyx : : NPC_TranslateActivity ( Activity activity )
{
activity = BaseClass : : NPC_TranslateActivity ( activity ) ;
if ( activity = = ACT_RUN & & GetEnemy ( ) & & GetEnemy ( ) - > Classify ( ) = = CLASS_COMBINE_GUNSHIP )
{
// Always cower from gunship!
if ( HaveSequenceForActivity ( ACT_RUN_PROTECTED ) )
activity = ACT_RUN_PROTECTED ;
}
switch ( activity )
{
// !!!HACK - Alyx doesn't have the required animations for shotguns,
// so trick her into using the rifle counterparts for now (sjb)
case ACT_RUN_AIM_SHOTGUN : return ACT_RUN_AIM_RIFLE ;
case ACT_WALK_AIM_SHOTGUN : return ACT_WALK_AIM_RIFLE ;
case ACT_IDLE_ANGRY_SHOTGUN : return ACT_IDLE_ANGRY_SMG1 ;
case ACT_RANGE_ATTACK_SHOTGUN_LOW : return ACT_RANGE_ATTACK_SMG1_LOW ;
case ACT_PICKUP_RACK : return ( Activity ) ACT_ALYX_PICKUP_RACK ;
case ACT_DROP_WEAPON : if ( HasShotgun ( ) ) return ( Activity ) ACT_DROP_WEAPON_SHOTGUN ;
}
return activity ;
}
bool CNPC_Alyx : : ShouldDeferToFollowBehavior ( )
{
return BaseClass : : ShouldDeferToFollowBehavior ( ) ;
}
void CNPC_Alyx : : BuildScheduleTestBits ( )
{
bool bIsInteracting = false ;
bIsInteracting = ( IsCurSchedule ( SCHED_ALYX_PREPARE_TO_INTERACT_WITH_TARGET , false ) | |
IsCurSchedule ( SCHED_ALYX_WAIT_TO_INTERACT_WITH_TARGET , false ) | |
IsCurSchedule ( SCHED_ALYX_INTERACT_WITH_TARGET , false ) | |
IsCurSchedule ( SCHED_ALYX_INTERACTION_INTERRUPTED , false ) | |
IsCurSchedule ( SCHED_ALYX_FINISH_INTERACTING_WITH_TARGET , false ) ) ;
if ( ! bIsInteracting & & IsAllowedToInteract ( ) )
{
switch ( m_NPCState )
{
case NPC_STATE_COMBAT :
SetCustomInterruptCondition ( COND_ALYX_HAS_INTERACT_TARGET ) ;
SetCustomInterruptCondition ( COND_ALYX_CAN_INTERACT_WITH_TARGET ) ;
break ;
case NPC_STATE_ALERT :
case NPC_STATE_IDLE :
SetCustomInterruptCondition ( COND_ALYX_HAS_INTERACT_TARGET ) ;
SetCustomInterruptCondition ( COND_ALYX_CAN_INTERACT_WITH_TARGET ) ;
break ;
}
}
// This nugget fixes a bug where Alyx will continue to attack an enemy she no longer hates in the
// case where her relationship with the enemy changes while she's running a SCHED_SCENE_GENERIC.
// Since we don't run ChooseEnemy() when we're running a schedule that doesn't interrupt on COND_NEW_ENEMY,
// we also do not re-evaluate and flush enemies we don't hate anymore. (sjb 6/9/2005)
if ( IsCurSchedule ( SCHED_SCENE_GENERIC ) & & GetEnemy ( ) & & GetEnemy ( ) - > VPhysicsGetObject ( ) )
{
if ( GetEnemy ( ) - > VPhysicsGetObject ( ) - > GetGameFlags ( ) & FVPHYSICS_PLAYER_HELD )
{
SetCustomInterruptCondition ( COND_NEW_ENEMY ) ;
}
}
if ( GetCurSchedule ( ) - > HasInterrupt ( COND_IDLE_INTERRUPT ) )
{
SetCustomInterruptCondition ( COND_BETTER_WEAPON_AVAILABLE ) ;
}
// If we're not in a script, keep an eye out for falling
if ( m_NPCState ! = NPC_STATE_SCRIPT & & ! IsInAVehicle ( ) & & ! IsCurSchedule ( SCHED_ALYX_FALL_TO_GROUND , false ) )
{
SetCustomInterruptCondition ( COND_FLOATING_OFF_GROUND ) ;
}
BaseClass : : BuildScheduleTestBits ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : ShouldBehaviorSelectSchedule ( CAI_BehaviorBase * pBehavior )
{
if ( pBehavior = = & m_AssaultBehavior )
{
if ( HasCondition ( COND_MOBBED_BY_ENEMIES ) )
return false ;
}
return BaseClass : : ShouldBehaviorSelectSchedule ( pBehavior ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Alyx : : SelectSchedule ( void )
{
// If we're in darkness mode, and the player has the flashlight off, and we hear a zombie footstep,
// and the player isn't nearby, deliberately turn away from the zombie to let the zombie grab me.
if ( HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) & & m_NPCState = = NPC_STATE_ALERT )
{
if ( HasCondition ( COND_HEAR_COMBAT ) & & ! HasCondition ( COND_SEE_PLAYER ) )
{
CSound * pBestSound = GetBestSound ( ) ;
if ( pBestSound & & pBestSound - > m_hOwner )
{
if ( pBestSound - > m_hOwner - > Classify ( ) = = CLASS_ZOMBIE & & pBestSound - > SoundChannel ( ) = = SOUNDENT_CHANNEL_NPC_FOOTSTEP )
return SCHED_ALYX_ALERT_FACE_AWAYFROM_BESTSOUND ;
}
}
}
if ( HasCondition ( COND_ALYX_CAN_INTERACT_WITH_TARGET ) )
return SCHED_ALYX_INTERACT_WITH_TARGET ;
if ( HasCondition ( COND_ALYX_HAS_INTERACT_TARGET ) & & HasCondition ( COND_SEE_PLAYER ) & & IsAllowedToInteract ( ) )
{
ExpireCurrentRandomLookTarget ( ) ;
if ( IsEMPHolstered ( ) )
{
return SCHED_ALYX_PREPARE_TO_INTERACT_WITH_TARGET ;
}
return SCHED_ALYX_WAIT_TO_INTERACT_WITH_TARGET ;
}
if ( ! IsEMPHolstered ( ) & & ! HasInteractTarget ( ) & & ! m_ActBusyBehavior . IsActive ( ) )
return SCHED_ALYX_HOLSTER_EMP ;
if ( HasCondition ( COND_BETTER_WEAPON_AVAILABLE ) )
{
if ( m_iszPendingWeapon ! = NULL_STRING )
{
return SCHED_SWITCH_TO_PENDING_WEAPON ;
}
else
{
CBaseHLCombatWeapon * pWeapon = dynamic_cast < CBaseHLCombatWeapon * > ( Weapon_FindUsable ( WEAPON_SEARCH_DELTA ) ) ;
if ( pWeapon )
{
m_flNextWeaponSearchTime = gpGlobals - > curtime + 10.0 ;
// Now lock the weapon for several seconds while we go to pick it up.
pWeapon - > Lock ( 10.0 , this ) ;
SetTarget ( pWeapon ) ;
return SCHED_ALYX_NEW_WEAPON ;
}
}
}
if ( HasCondition ( COND_ENEMY_OCCLUDED ) )
{
//Warning("CROUCH: Standing, enemy is occluded.\n" );
Stand ( ) ;
}
return BaseClass : : SelectSchedule ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Alyx : : SelectScheduleDanger ( void )
{
if ( HasCondition ( COND_HEAR_DANGER ) )
{
CSound * pSound ;
pSound = GetBestSound ( SOUND_DANGER ) ;
ASSERT ( pSound ! = NULL ) ;
if ( pSound & & ( pSound - > m_iType & SOUND_DANGER ) & & ( pSound - > SoundChannel ( ) = = SOUNDENT_CHANNEL_ZOMBINE_GRENADE ) )
{
SpeakIfAllowed ( TLK_DANGER_ZOMBINE_GRENADE ) ;
}
}
return BaseClass : : SelectScheduleDanger ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Alyx : : TranslateSchedule ( int scheduleType )
{
switch ( scheduleType )
{
case SCHED_ALERT_FACE_BESTSOUND :
return SCHED_ALYX_ALERT_FACE_BESTSOUND ;
break ;
case SCHED_COMBAT_FACE :
if ( ! HasCondition ( COND_TASK_FAILED ) & & ! IsCrouching ( ) )
return SCHED_ALYX_COMBAT_FACE ;
break ;
case SCHED_WAKE_ANGRY :
return SCHED_ALYX_WAKE_ANGRY ;
break ;
case SCHED_FALL_TO_GROUND :
return SCHED_ALYX_FALL_TO_GROUND ;
break ;
case SCHED_ALERT_REACT_TO_COMBAT_SOUND :
return SCHED_ALYX_ALERT_REACT_TO_COMBAT_SOUND ;
break ;
case SCHED_COWER :
case SCHED_PC_COWER :
// Alyx doesn't have cower animations.
return SCHED_FAIL ;
case SCHED_RANGE_ATTACK1 :
{
if ( GetEnemy ( ) )
{
CBaseEntity * pEnemy = GetEnemy ( ) ;
if ( ! IsCrouching ( ) )
{
// Does my enemy have enough health to warrant crouching?
if ( pEnemy - > GetHealth ( ) > ALYX_MIN_ENEMY_HEALTH_TO_CROUCH )
{
// And are they far enough away? Expand the min dist so we don't crouch & stand immediately.
if ( EnemyDistance ( pEnemy ) > ( ALYX_MIN_ENEMY_DIST_TO_CROUCH * 1.5 ) & & ( pEnemy - > GetFlags ( ) & FL_ONGROUND ) )
{
//Warning("CROUCH: Desiring due to enemy far away.\n" );
DesireCrouch ( ) ;
}
}
}
// Are we supposed to be crouching?
if ( IsCrouching ( ) | | ( CrouchIsDesired ( ) & & ! HasCondition ( COND_HEAVY_DAMAGE ) ) )
{
// See if they're a valid crouch target
if ( EnemyIsValidCrouchTarget ( pEnemy ) )
{
Crouch ( ) ;
}
else
{
//Warning("CROUCH: Standing, enemy not valid crouch target.\n" );
Stand ( ) ;
}
}
else
{
//Warning("CROUCH: Standing, no enemy.\n" );
Stand ( ) ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// This stuff was ported from npc_playercompanion to help Alyx use grenades.
if ( HasGrenades ( ) & & ! IsCrouching ( ) )
{
if ( CanAltFireEnemy ( true ) & & OccupyStrategySlot ( SQUAD_SLOT_SPECIAL_ATTACK ) )
{
return SCHED_PC_AR2_ALTFIRE ;
}
if ( ! OccupyStrategySlotRange ( SQUAD_SLOT_ATTACK1 , SQUAD_SLOT_ATTACK2 ) )
{
// Try throwing a grenade if Alyx is in a squad that already has attacking well in hand.
if ( CanGrenadeEnemy ( ) )
{
if ( OccupyStrategySlot ( SQUAD_SLOT_SPECIAL_ATTACK ) )
{
return SCHED_RANGE_ATTACK2 ;
}
}
}
}
# endif
2013-12-02 19:31:46 -08:00
}
return SCHED_ALYX_RANGE_ATTACK1 ;
}
break ;
case SCHED_HIDE_AND_RELOAD :
{
if ( HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) )
return SCHED_RELOAD ;
// If I don't have a ranged attacker as an enemy, don't try to hide
AIEnemiesIter_t iter ;
for ( AI_EnemyInfo_t * pEMemory = GetEnemies ( ) - > GetFirst ( & iter ) ; pEMemory ! = NULL ; pEMemory = GetEnemies ( ) - > GetNext ( & iter ) )
{
CAI_BaseNPC * pEnemy = pEMemory - > hEnemy . Get ( ) - > MyNPCPointer ( ) ;
if ( ! pEnemy )
continue ;
// Ignore enemies that don't hate me
if ( pEnemy - > IRelationType ( this ) ! = D_HT )
continue ;
// Look for enemies with ranged capabilities
if ( pEnemy - > CapabilitiesGet ( ) & ( bits_CAP_WEAPON_RANGE_ATTACK1 | bits_CAP_WEAPON_RANGE_ATTACK2 | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 ) )
return SCHED_HIDE_AND_RELOAD ;
}
return SCHED_RELOAD ;
}
break ;
case SCHED_RUN_FROM_ENEMY :
if ( HasCondition ( COND_MOBBED_BY_ENEMIES ) )
{
return SCHED_RUN_FROM_ENEMY_MOB ;
}
break ;
case SCHED_IDLE_STAND :
return SCHED_ALYX_IDLE_STAND ;
# ifdef HL2_EPISODIC
case SCHED_RUN_RANDOM :
if ( GetEnemy ( ) & & HasCondition ( COND_SEE_ENEMY ) & & GetActiveWeapon ( ) )
{
// SCHED_RUN_RANDOM is a last ditch effort, it's the bottom of a chain of
// sequential schedule failures. Since this can cause Alyx to freeze up,
// just let her fight if she can. (sjb).
return SCHED_RANGE_ATTACK1 ;
}
break ;
# endif // HL2_EPISODIC
}
return BaseClass : : TranslateSchedule ( scheduleType ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : StartTask ( const Task_t * pTask )
{
switch ( pTask - > iTask )
{
case TASK_SOUND_WAKE :
LocateEnemySound ( ) ;
// Don't do the half second wait here that the PlayerCompanion class does. (sbj) 1/4/2006
TaskComplete ( ) ;
break ;
case TASK_ANNOUNCE_ATTACK :
{
SpeakAttacking ( ) ;
BaseClass : : StartTask ( pTask ) ;
break ;
}
case TASK_ALYX_BUILD_COMBAT_FACE_PATH :
{
if ( GetEnemy ( ) & & ! FInAimCone ( GetEnemyLKP ( ) ) & & FVisible ( GetEnemyLKP ( ) ) )
{
Vector vecToEnemy = GetEnemyLKP ( ) - GetAbsOrigin ( ) ;
VectorNormalize ( vecToEnemy ) ;
Vector vecMoveGoal = GetAbsOrigin ( ) - ( vecToEnemy * 24.0f ) ;
if ( ! GetNavigator ( ) - > SetGoal ( vecMoveGoal ) )
{
TaskFail ( FAIL_NO_ROUTE ) ;
}
else
{
GetMotor ( ) - > SetIdealYawToTarget ( GetEnemy ( ) - > WorldSpaceCenter ( ) ) ;
GetNavigator ( ) - > SetArrivalDirection ( GetEnemy ( ) ) ;
TaskComplete ( ) ;
}
}
else
{
TaskFail ( " Defaulting To BaseClass::CombatFace " ) ;
}
}
break ;
case TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL :
{
// If we don't have the alyx gun, throw away our current,
// since the alyx gun is the only one we can tuck away.
if ( HasAlyxgun ( ) )
{
SetDesiredWeaponState ( DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) ;
}
else
{
Weapon_Drop ( GetActiveWeapon ( ) ) ;
}
SetWait ( 1 ) ; // Wait while she does it.
}
break ;
case TASK_STOP_MOVING :
if ( npc_alyx_force_stop_moving . GetBool ( ) )
{
if ( ( GetNavigator ( ) - > IsGoalSet ( ) & & GetNavigator ( ) - > IsGoalActive ( ) ) | | GetNavType ( ) = = NAV_JUMP )
{
DbgNavMsg ( this , " Start TASK_STOP_MOVING \n " ) ;
DbgNavMsg ( this , " Initiating stopping path \n " ) ;
GetNavigator ( ) - > StopMoving ( false ) ;
// E3 Hack
if ( HasPoseMoveYaw ( ) )
{
SetPoseParameter ( m_poseMove_Yaw , 0 ) ;
}
}
else
{
if ( GetNavigator ( ) - > SetGoalFromStoppingPath ( ) )
{
DbgNavMsg ( this , " Start TASK_STOP_MOVING \n " ) ;
DbgNavMsg ( this , " Initiating stopping path \n " ) ;
}
else
{
GetNavigator ( ) - > ClearGoal ( ) ;
if ( IsMoving ( ) )
{
SetIdealActivity ( GetStoppedActivity ( ) ) ;
}
TaskComplete ( ) ;
}
}
}
else
{
BaseClass : : StartTask ( pTask ) ;
}
break ;
case TASK_REACT_TO_COMBAT_SOUND :
{
CSound * pSound = GetBestSound ( ) ;
if ( pSound & & pSound - > IsSoundType ( SOUND_COMBAT ) & & pSound - > IsSoundType ( SOUND_CONTEXT_GUNFIRE ) )
{
AnalyzeGunfireSound ( pSound ) ;
}
TaskComplete ( ) ;
}
break ;
case TASK_ALYX_HOLSTER_PISTOL :
HolsterPistol ( ) ;
TaskComplete ( ) ;
break ;
case TASK_ALYX_DRAW_PISTOL :
DrawPistol ( ) ;
TaskComplete ( ) ;
break ;
case TASK_ALYX_WAIT_HACKING :
SetWait ( pTask - > flTaskData ) ;
break ;
case TASK_ALYX_GET_PATH_TO_INTERACT_TARGET :
{
if ( ! HasInteractTarget ( ) )
{
TaskFail ( " No interact target " ) ;
return ;
}
AI_NavGoal_t goal ;
goal . type = GOALTYPE_LOCATION ;
goal . dest = GetInteractTarget ( ) - > WorldSpaceCenter ( ) ;
goal . pTarget = GetInteractTarget ( ) ;
GetNavigator ( ) - > SetGoal ( goal ) ;
}
break ;
case TASK_ALYX_ANNOUNCE_HACK :
SpeakIfAllowed ( CONCEPT_ALYX_REQUEST_ITEM ) ;
TaskComplete ( ) ;
break ;
case TASK_ALYX_BEGIN_INTERACTION :
{
INPCInteractive * pInteractive = dynamic_cast < INPCInteractive * > ( GetInteractTarget ( ) ) ;
if ( pInteractive )
{
EmpZapTarget ( GetInteractTarget ( ) ) ;
pInteractive - > AlyxStartedInteraction ( ) ;
pInteractive - > NotifyInteraction ( this ) ;
pInteractive - > AlyxFinishedInteraction ( ) ;
m_OnFinishInteractWithObject . FireOutput ( GetInteractTarget ( ) , this ) ;
}
TaskComplete ( ) ;
}
break ;
case TASK_ALYX_COMPLETE_INTERACTION :
{
INPCInteractive * pInteractive = dynamic_cast < INPCInteractive * > ( GetInteractTarget ( ) ) ;
if ( pInteractive )
{
for ( int i = 0 ; i < 3 ; i + + )
{
g_pEffects - > Sparks ( GetInteractTarget ( ) - > WorldSpaceCenter ( ) ) ;
}
GetInteractTarget ( ) - > EmitSound ( " DoSpark " ) ;
Speak ( CONCEPT_ALYX_INTERACTION_DONE ) ;
SetInteractTarget ( NULL ) ;
}
TaskComplete ( ) ;
}
break ;
case TASK_ALYX_SET_IDLE_ACTIVITY :
{
Activity goalActivity = ( Activity ) ( ( int ) pTask - > flTaskData ) ;
if ( IsActivityFinished ( ) )
{
SetIdealActivity ( goalActivity ) ;
}
}
break ;
case TASK_ALYX_FALL_TO_GROUND :
// If we wait this long without landing, we'll fall to our death
SetWait ( 2 ) ;
break ;
default :
BaseClass : : StartTask ( pTask ) ;
break ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : RunTask ( const Task_t * pTask )
{
switch ( pTask - > iTask )
{
case TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL :
if ( IsWaitFinished ( ) )
TaskComplete ( ) ;
break ;
case TASK_STOP_MOVING :
{
if ( npc_alyx_force_stop_moving . GetBool ( ) )
{
ChainRunTask ( TASK_WAIT_FOR_MOVEMENT ) ;
if ( ! TaskIsRunning ( ) )
{
DbgNavMsg ( this , " TASK_STOP_MOVING Complete \n " ) ;
}
}
else
{
BaseClass : : RunTask ( pTask ) ;
}
break ;
}
case TASK_ALYX_WAIT_HACKING :
if ( GetInteractTarget ( ) & & random - > RandomInt ( 0 , 3 ) = = 0 )
{
g_pEffects - > Sparks ( GetInteractTarget ( ) - > WorldSpaceCenter ( ) ) ;
GetInteractTarget ( ) - > EmitSound ( " DoSpark " ) ;
}
if ( IsWaitFinished ( ) )
{
TaskComplete ( ) ;
}
break ;
case TASK_ALYX_SET_IDLE_ACTIVITY :
{
if ( IsActivityStarted ( ) )
{
TaskComplete ( ) ;
}
}
break ;
case TASK_ALYX_FALL_TO_GROUND :
if ( GetFlags ( ) & FL_ONGROUND )
{
TaskComplete ( ) ;
}
else if ( IsWaitFinished ( ) )
{
// Call back to the base class & see if it can find a ground for us
// If it can't, we'll fall to our death
ChainRunTask ( TASK_FALL_TO_GROUND ) ;
if ( TaskIsRunning ( ) )
{
CTakeDamageInfo info ;
info . SetDamage ( m_iHealth ) ;
info . SetAttacker ( this ) ;
info . SetInflictor ( this ) ;
info . SetDamageType ( DMG_GENERIC ) ;
TakeDamage ( info ) ;
}
}
break ;
default :
BaseClass : : RunTask ( pTask ) ;
break ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : OnStateChange ( NPC_STATE OldState , NPC_STATE NewState )
{
switch ( NewState )
{
case NPC_STATE_COMBAT :
{
m_fCombatStartTime = gpGlobals - > curtime ;
}
break ;
default :
if ( OldState = = NPC_STATE_COMBAT )
{
// coming out of combat state.
m_fCombatEndTime = gpGlobals - > curtime + 2.0f ;
}
break ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : TraceAttack ( const CTakeDamageInfo & info , const Vector & vecDir , trace_t * ptr , CDmgAccumulator * pAccumulator )
{
BaseClass : : TraceAttack ( info , vecDir , ptr , pAccumulator ) ;
// FIXME: hack until some way of removing decals after healing
m_fNoDamageDecal = true ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : CanBeHitByMeleeAttack ( CBaseEntity * pAttacker )
{
if ( IsCurSchedule ( SCHED_DUCK_DODGE ) )
{
return false ;
}
return BaseClass : : CanBeHitByMeleeAttack ( pAttacker ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Alyx : : OnTakeDamage_Alive ( const CTakeDamageInfo & info )
{
//!!!HACKHACK - EP1 - Stop alyx taking all physics damage to prevent her dying
// in freak accidents resembling spontaneous stress damage death (which are now impossible)
// Also stop her taking damage from flames: Fixes her being burnt to death from entity flames
// attached to random debris chunks while inside scripted sequences.
if ( info . GetDamageType ( ) & ( DMG_CRUSH | DMG_BURN ) )
return 0 ;
// If we're in commentary mode, prevent her taking damage from other NPCs
if ( IsInCommentaryMode ( ) & & info . GetAttacker ( ) & & info . GetAttacker ( ) - > IsNPC ( ) )
return 0 ;
int taken = BaseClass : : OnTakeDamage_Alive ( info ) ;
if ( taken & & HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) & & ! HasCondition ( COND_TALKER_PLAYER_DEAD ) )
{
if ( ! HasCondition ( COND_SEE_ENEMY ) & & ( info . GetDamageType ( ) & ( DMG_SLASH | DMG_CLUB ) ) )
{
// I've taken melee damage. If I haven't seen the enemy for a few seconds, make some noise.
float flLastTimeSeen = GetEnemies ( ) - > LastTimeSeen ( info . GetAttacker ( ) , false ) ;
if ( flLastTimeSeen = = AI_INVALID_TIME | | gpGlobals - > curtime - flLastTimeSeen > 3.0 )
{
SpeakIfAllowed ( " TLK_DARKNESS_UNKNOWN_WOUND " ) ;
m_fTimeUntilNextDarknessFoundPlayer = gpGlobals - > curtime + RandomFloat ( 3 , 5 ) ;
}
}
}
if ( taken & & ( info . GetDamageType ( ) & DMG_BLAST ) )
{
if ( HasShotgun ( ) )
{
if ( ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST ) & & ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN ) )
{
RestartGesture ( ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN ) ;
GetShotRegulator ( ) - > FireNoEarlierThan ( gpGlobals - > curtime + SequenceDuration ( ACT_GESTURE_FLINCH_BLAST_DAMAGED_SHOTGUN ) + 0.5f ) ;
}
}
else
{
if ( ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST ) & & ! IsPlayingGesture ( ACT_GESTURE_FLINCH_BLAST_DAMAGED ) )
{
RestartGesture ( ACT_GESTURE_FLINCH_BLAST_DAMAGED ) ;
GetShotRegulator ( ) - > FireNoEarlierThan ( gpGlobals - > curtime + SequenceDuration ( ACT_GESTURE_FLINCH_BLAST_DAMAGED ) + 0.5f ) ;
}
}
}
return taken ;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool CNPC_Alyx : : FCanCheckAttacks ( )
{
if ( GetEnemy ( ) & & IsGunship ( GetEnemy ( ) ) )
{
// Don't attack gunships
return false ;
}
return BaseClass : : FCanCheckAttacks ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Half damage against Combine Soldiers in outland_10
//-----------------------------------------------------------------------------
float CNPC_Alyx : : GetAttackDamageScale ( CBaseEntity * pVictim )
{
if ( g_HackOutland10DamageHack & & pVictim - > Classify ( ) = = CLASS_COMBINE )
{
return 0.75f ;
}
return BaseClass : : GetAttackDamageScale ( pVictim ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : HandleInteraction ( int interactionType , void * data , CBaseCombatCharacter * sourceEnt )
{
if ( interactionType = = g_interactionZombieMeleeWarning & & IsAllowedToDodge ( ) )
{
// If a zombie is attacking, ditch my current schedule and duck if I'm running a schedule that will
// be interrupted if I'm hit.
if ( ConditionInterruptsCurSchedule ( COND_LIGHT_DAMAGE ) | | ConditionInterruptsCurSchedule ( COND_HEAVY_DAMAGE ) )
{
//Only dodge an NPC you can see attacking.
if ( sourceEnt & & FInViewCone ( sourceEnt ) )
{
SetSchedule ( SCHED_DUCK_DODGE ) ;
}
}
return true ;
}
if ( interactionType = = g_interactionPlayerPuntedHeavyObject )
{
// Try to get Alyx out of the way when player is punting cars around.
CBaseEntity * pProp = ( CBaseEntity * ) ( data ) ;
if ( pProp )
{
float distToProp = pProp - > WorldSpaceCenter ( ) . DistTo ( GetAbsOrigin ( ) ) ;
float distToPlayer = sourceEnt - > WorldSpaceCenter ( ) . DistTo ( GetAbsOrigin ( ) ) ;
// Do this if the prop is within 60 feet, and is closer to me than the player is.
if ( distToProp < ( 60.0f * 12.0f ) & & ( distToProp < distToPlayer ) )
{
if ( fabs ( pProp - > WorldSpaceCenter ( ) . z - WorldSpaceCenter ( ) . z ) < = 120.0f )
{
if ( sourceEnt - > FInViewCone ( this ) )
{
CSoundEnt : : InsertSound ( SOUND_MOVE_AWAY , EarPosition ( ) , 16 , 1.0f , pProp ) ;
}
}
}
}
return true ;
}
return BaseClass : : HandleInteraction ( interactionType , data , sourceEnt ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : HolsterPistol ( )
{
if ( GetActiveWeapon ( ) )
{
GetActiveWeapon ( ) - > AddEffects ( EF_NODRAW ) ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : DrawPistol ( )
{
if ( GetActiveWeapon ( ) )
{
GetActiveWeapon ( ) - > RemoveEffects ( EF_NODRAW ) ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : Weapon_Drop ( CBaseCombatWeapon * pWeapon , const Vector * pvecTarget , const Vector * pVelocity )
{
BaseClass : : Weapon_Drop ( pWeapon , pvecTarget , pVelocity ) ;
if ( pWeapon & & pWeapon - > ClassMatches ( CLASSNAME_ALYXGUN ) )
{
pWeapon - > SUB_Remove ( ) ;
}
m_WeaponType = WT_NONE ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : IsAllowedToAim ( )
{
// Alyx can aim only if fully agitated
if ( GetReadinessLevel ( ) ! = AIRL_AGITATED )
return false ;
return BaseClass : : IsAllowedToAim ( ) ;
}
//-----------------------------------------------------------------------------
void CNPC_Alyx : : PainSound ( const CTakeDamageInfo & info )
{
// Alex has specific sounds for when attacked in the dark
if ( ! HasCondition ( COND_ALYX_IN_DARK ) )
{
// set up the speech modifiers
CFmtStrN < 128 > modifiers ( " damageammo:%s " , info . GetAmmoName ( ) ) ;
SpeakIfAllowed ( TLK_WOUND , modifiers ) ;
}
}
//-----------------------------------------------------------------------------
void CNPC_Alyx : : DeathSound ( const CTakeDamageInfo & info )
{
// Sentences don't play on dead NPCs
SentenceStop ( ) ;
if ( ! SpokeConcept ( TLK_SELF_IN_BARNACLE ) )
{
EmitSound ( " npc_alyx.die " ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : OnSeeEntity ( CBaseEntity * pEntity )
{
BaseClass : : OnSeeEntity ( pEntity ) ;
if ( pEntity - > IsPlayer ( ) & & pEntity - > IsEFlagSet ( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
{
SpeakIfAllowed ( TLK_ALLY_IN_BARNACLE ) ;
}
}
//---------------------------------------------------------
// A sort of trivial rejection, this function tells us whether
// this object is something Alyx can interact with at all.
// (Alyx's state and the object's state are not considered
// at this stage)
//---------------------------------------------------------
bool CNPC_Alyx : : IsValidInteractTarget ( CBaseEntity * pTarget )
{
INPCInteractive * pInteractive = dynamic_cast < INPCInteractive * > ( pTarget ) ;
if ( ! pInteractive )
{
// Not an INPCInteractive entity.
return false ;
}
if ( ! pInteractive - > CanInteractWith ( this ) )
{
return false ;
}
if ( pInteractive - > HasBeenInteractedWith ( ) )
{
// Already been interacted with.
return false ;
}
IPhysicsObject * pPhysics ;
pPhysics = pTarget - > VPhysicsGetObject ( ) ;
if ( pPhysics )
{
if ( ! ( pPhysics - > GetGameFlags ( ) & FVPHYSICS_PLAYER_HELD ) )
{
// Player isn't holding this physics object
return false ;
}
}
if ( GetAbsOrigin ( ) . DistToSqr ( pTarget - > WorldSpaceCenter ( ) ) > ( 360.0f * 360.0f ) )
{
// Too far away!
return false ;
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : SetInteractTarget ( CBaseEntity * pTarget )
{
if ( ! pTarget )
{
ClearCondition ( COND_ALYX_HAS_INTERACT_TARGET ) ;
ClearCondition ( COND_ALYX_CAN_INTERACT_WITH_TARGET ) ;
SetCondition ( COND_ALYX_NO_INTERACT_TARGET ) ;
SetCondition ( COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET ) ;
}
m_hHackTarget . Set ( pTarget ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : EmpZapTarget ( CBaseEntity * pTarget )
{
g_pEffects - > Sparks ( pTarget - > WorldSpaceCenter ( ) ) ;
CAlyxEmpEffect * pEmpEffect = ( CAlyxEmpEffect * ) CreateEntityByName ( " env_alyxemp " ) ;
if ( pEmpEffect )
{
pEmpEffect - > Spawn ( ) ;
pEmpEffect - > ActivateAutomatic ( this , pTarget ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : CanInteractWithTarget ( CBaseEntity * pTarget )
{
if ( ! IsValidInteractTarget ( pTarget ) )
return false ;
float flDist ;
flDist = ( WorldSpaceCenter ( ) - pTarget - > WorldSpaceCenter ( ) ) . Length ( ) ;
if ( flDist > 80.0f )
{
return false ;
}
if ( ! IsAllowedToInteract ( ) )
{
SpeakIfAllowed ( TLK_CANT_INTERACT_NOW ) ;
return false ;
}
if ( IsEMPHolstered ( ) )
return false ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose: Player has illuminated this NPC with the flashlight
//-----------------------------------------------------------------------------
void CNPC_Alyx : : PlayerHasIlluminatedNPC ( CBasePlayer * pPlayer , float flDot )
{
if ( m_bIsFlashlightBlind )
return ;
if ( ! CanBeBlindedByFlashlight ( true ) )
return ;
// Ignore the flashlight if it's not shining at my eyes
if ( PlayerFlashlightOnMyEyes ( pPlayer ) )
{
char szResponse [ AI_Response : : MAX_RESPONSE_NAME ] ;
// Only say the blinding speech if it's time to
if ( SpeakIfAllowed ( " TLK_FLASHLIGHT_ILLUM " , NULL , false , szResponse , AI_Response : : MAX_RESPONSE_NAME ) )
{
m_iszCurrentBlindScene = AllocPooledString ( szResponse ) ;
ADD_DEBUG_HISTORY ( HISTORY_ALYX_BLIND , UTIL_VarArgs ( " (%0.2f) Alyx: start flashlight blind scene '%s' \n " , gpGlobals - > curtime , STRING ( m_iszCurrentBlindScene ) ) ) ;
GetShotRegulator ( ) - > DisableShooting ( ) ;
m_bIsFlashlightBlind = true ;
m_fStayBlindUntil = gpGlobals - > curtime + 0.1f ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Check if player has illuminated this NPC with a flare
//-----------------------------------------------------------------------------
void CNPC_Alyx : : CheckBlindedByFlare ( void )
{
if ( m_bIsFlashlightBlind )
return ;
if ( ! CanBeBlindedByFlashlight ( false ) )
return ;
// Ignore the flare if it's not too close
if ( BlindedByFlare ( ) )
{
char szResponse [ AI_Response : : MAX_RESPONSE_NAME ] ;
// Only say the blinding speech if it's time to
if ( SpeakIfAllowed ( " TLK_FLASHLIGHT_ILLUM " , NULL , false , szResponse , AI_Response : : MAX_RESPONSE_NAME ) )
{
m_iszCurrentBlindScene = AllocPooledString ( szResponse ) ;
ADD_DEBUG_HISTORY ( HISTORY_ALYX_BLIND , UTIL_VarArgs ( " (%0.2f) Alyx: start flare blind scene '%s' \n " , gpGlobals - > curtime ,
STRING ( m_iszCurrentBlindScene ) ) ) ;
GetShotRegulator ( ) - > DisableShooting ( ) ;
m_bIsFlashlightBlind = true ;
m_fStayBlindUntil = gpGlobals - > curtime + 0.1f ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input: bCheckLightSources - if true, checks if any light darkness lightsources are near
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : CanBeBlindedByFlashlight ( bool bCheckLightSources )
{
// Can't be blinded if we're not in alyx darkness mode
/*
if ( ! HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) )
return false ;
*/
// Can't be blinded if I'm in a script, or in combat
if ( IsInAScript ( ) | | GetState ( ) = = NPC_STATE_COMBAT | | GetState ( ) = = NPC_STATE_SCRIPT )
return false ;
if ( IsSpeaking ( ) )
return false ;
// can't be blinded if Alyx is near a light source
if ( bCheckLightSources & & DarknessLightSourceWithinRadius ( this , 500 ) )
return false ;
// Not during an actbusy
if ( m_ActBusyBehavior . IsActive ( ) )
return false ;
if ( m_OperatorBehavior . IsRunning ( ) )
return false ;
// Can't be blinded if I've been in combat recently, to fix anim snaps
if ( GetLastEnemyTime ( ) ! = 0.0 )
{
if ( ( gpGlobals - > curtime - GetLastEnemyTime ( ) ) < 2 )
return false ;
}
// Can't be blinded if I'm reloading
if ( IsCurSchedule ( SCHED_RELOAD , false ) )
return false ;
// Can't be blinded right after being blind, to prevent oscillation
if ( gpGlobals - > curtime < m_flDontBlindUntil )
return false ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : PlayerFlashlightOnMyEyes ( CBasePlayer * pPlayer )
{
Vector vecEyes , vecPlayerForward ;
vecEyes = EyePosition ( ) ;
pPlayer - > EyeVectors ( & vecPlayerForward ) ;
Vector vecToEyes = ( vecEyes - pPlayer - > EyePosition ( ) ) ;
float flDist = VectorNormalize ( vecToEyes ) ;
// We can be blinded in daylight, but only at close range
if ( HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) = = false )
{
if ( flDist > ( 8 * 12.0f ) )
return false ;
}
float flDot = DotProduct ( vecPlayerForward , vecToEyes ) ;
if ( flDot < 0.98 )
return false ;
// Check facing to ensure we're in front of her
Vector los = ( pPlayer - > EyePosition ( ) - vecEyes ) ;
los . z = 0 ;
VectorNormalize ( los ) ;
Vector facingDir = EyeDirection2D ( ) ;
flDot = DotProduct ( los , facingDir ) ;
return ( flDot > 0.3 ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Checks if Alyx is blinded by a flare
// Input :
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : BlindedByFlare ( void )
{
Vector vecEyes = EyePosition ( ) ;
Vector los ;
Vector vecToEyes ;
Vector facingDir = EyeDirection2D ( ) ;
// use a wider radius when she's already blind to help with edge cases
// where she flickers back and forth due to animation
float fBlindDist = ( m_bIsFlashlightBlind ) ? 35.0f : 30.0f ;
CFlare * pFlare = CFlare : : GetActiveFlares ( ) ;
while ( pFlare ! = NULL )
{
vecToEyes = ( vecEyes - pFlare - > GetAbsOrigin ( ) ) ;
float fDist = VectorNormalize ( vecToEyes ) ;
if ( fDist < fBlindDist )
{
// Check facing to ensure we're in front of her
los = ( pFlare - > GetAbsOrigin ( ) - vecEyes ) ;
los . z = 0 ;
VectorNormalize ( los ) ;
float flDot = DotProduct ( los , facingDir ) ;
if ( ( flDot > 0.3 ) & & FVisible ( pFlare ) )
{
return true ;
}
}
pFlare = pFlare - > GetNextFlare ( ) ;
}
return false ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : CanReload ( void )
{
if ( m_bIsFlashlightBlind )
return false ;
return BaseClass : : CanReload ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : PickTacticalLookTarget ( AILookTargetArgs_t * pArgs )
{
if ( HasInteractTarget ( ) )
{
pArgs - > hTarget = GetInteractTarget ( ) ;
pArgs - > flInfluence = 0.8f ;
pArgs - > flDuration = 3.0f ;
return true ;
}
if ( m_ActBusyBehavior . IsActive ( ) & & m_ActBusyBehavior . IsCombatActBusy ( ) )
{
return false ;
}
return BaseClass : : PickTacticalLookTarget ( pArgs ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : OnSelectedLookTarget ( AILookTargetArgs_t * pArgs )
{
if ( pArgs & & pArgs - > hTarget )
{
// If it's a stealth target, we want to go into stealth mode
CAI_Hint * pHint = dynamic_cast < CAI_Hint * > ( pArgs - > hTarget . Get ( ) ) ;
if ( pHint & & pHint - > HintType ( ) = = HINT_WORLD_VISUALLY_INTERESTING_STEALTH )
{
SetReadinessLevel ( AIRL_STEALTH , true , true ) ;
pArgs - > flDuration = 9999999 ;
m_hStealthLookTarget = pHint ;
return ;
}
}
// If we're in stealth mode, break out now
if ( GetReadinessLevel ( ) = = AIRL_STEALTH )
{
SetReadinessLevel ( AIRL_STIMULATED , true , true ) ;
if ( m_hStealthLookTarget )
{
ClearLookTarget ( m_hStealthLookTarget ) ;
m_hStealthLookTarget = NULL ;
}
}
}
//-----------------------------------------------------------------------------
// Output : Behavior to use
//-----------------------------------------------------------------------------
CAI_FollowBehavior & CNPC_Alyx : : GetFollowBehavior ( void )
{
// Use the base class
return m_FollowBehavior ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : AimGun ( void )
{
if ( m_FuncTankBehavior . IsMounted ( ) )
{
m_FuncTankBehavior . AimGun ( ) ;
return ;
}
// Always allow the passenger behavior to handle this
if ( m_PassengerBehavior . IsEnabled ( ) )
{
m_PassengerBehavior . AimGun ( ) ;
return ;
}
if ( ! GetEnemy ( ) )
{
if ( GetReadinessLevel ( ) = = AIRL_STEALTH & & m_hStealthLookTarget ! = NULL )
{
// Only aim if we're not far from the node
Vector vecAimDir = m_hStealthLookTarget - > GetAbsOrigin ( ) - Weapon_ShootPosition ( ) ;
if ( VectorNormalize ( vecAimDir ) > 80 )
{
// Ignore nodes that are behind her
Vector vecForward ;
GetVectors ( & vecForward , NULL , NULL ) ;
float flDot = DotProduct ( vecAimDir , vecForward ) ;
if ( flDot > 0 )
{
SetAim ( vecAimDir ) ;
return ;
}
}
}
}
BaseClass : : AimGun ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Vector CNPC_Alyx : : GetActualShootPosition ( const Vector & shootOrigin )
{
if ( HasShotgun ( ) & & GetEnemy ( ) & & GetEnemy ( ) - > Classify ( ) = = CLASS_ZOMBIE & & random - > RandomInt ( 0 , 1 ) = = 1 )
{
// 50-50 zombie headshots with shotgun!
return GetEnemy ( ) - > HeadTarget ( shootOrigin ) ;
}
return BaseClass : : GetActualShootPosition ( shootOrigin ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : EnemyIsValidCrouchTarget ( CBaseEntity * pEnemy )
{
// Don't crouch to shoot flying enemies (or jumping antlions)
if ( ! ( pEnemy - > GetFlags ( ) & FL_ONGROUND ) )
return false ;
// Don't crouch to shoot if we couldn't see them while crouching
if ( ! CouldShootIfCrouching ( pEnemy ) )
{
//Warning("CROUCH: Not valid due to crouch-no-LOS.\n" );
return false ;
}
// Don't crouch to shoot enemies that are close to me
if ( EnemyDistance ( pEnemy ) < = ALYX_MIN_ENEMY_DIST_TO_CROUCH )
{
//Warning("CROUCH: Not valid due to enemy-too-close.\n" );
return false ;
}
// Don't crouch to shoot enemies that are too far off my vertical plane
if ( fabs ( pEnemy - > GetAbsOrigin ( ) . z - GetAbsOrigin ( ) . z ) > 64 )
return false ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose: degrees to turn in 0.1 seconds
//-----------------------------------------------------------------------------
float CNPC_Alyx : : MaxYawSpeed ( void )
{
if ( IsCrouching ( ) )
return 10 ;
return BaseClass : : MaxYawSpeed ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : Stand ( void )
{
bool bWasCrouching = IsCrouching ( ) ;
if ( ! BaseClass : : Stand ( ) )
return false ;
if ( bWasCrouching )
{
m_flNextCrouchTime = gpGlobals - > curtime + ALYX_CROUCH_DELAY ;
OnUpdateShotRegulator ( ) ;
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : Crouch ( void )
{
if ( ! npc_alyx_crouch . GetBool ( ) )
return false ;
// Alyx will ignore crouch requests while she has the shotgun
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Alyx will ignore crouch requests from anything that isn't a "pistol", e.g. her Alyx gun.
if ( GetActiveWeapon ( ) & & GetActiveWeapon ( ) - > WeaponClassify ( ) ! = WEPCLASS_HANDGUN )
# else
2013-12-02 19:31:46 -08:00
if ( HasShotgun ( ) )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
return false ;
bool bWasStanding = ! IsCrouching ( ) ;
if ( ! BaseClass : : Crouch ( ) )
return false ;
if ( bWasStanding )
{
OnUpdateShotRegulator ( ) ;
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : DesireCrouch ( void )
{
// Ignore crouch desire if we've been crouching recently to reduce oscillation
if ( m_flNextCrouchTime > gpGlobals - > curtime )
return ;
BaseClass : : DesireCrouch ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Tack on extra criteria for responses
//-----------------------------------------------------------------------------
void CNPC_Alyx : : ModifyOrAppendCriteria ( AI_CriteriaSet & set )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
float fLengthOfLastCombat ;
# else
2013-12-02 19:31:46 -08:00
AIEnemiesIter_t iter ;
float fLengthOfLastCombat ;
int iNumEnemies ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
if ( GetState ( ) = = NPC_STATE_COMBAT )
{
fLengthOfLastCombat = gpGlobals - > curtime - m_fCombatStartTime ;
}
else
{
fLengthOfLastCombat = m_fCombatEndTime - m_fCombatStartTime ;
}
set . AppendCriteria ( " combat_length " , UTIL_VarArgs ( " %.3f " , fLengthOfLastCombat ) ) ;
2019-08-31 19:28:20 +00:00
# ifndef MAPBASE // Moved to CNPC_PlayerCompanion
2013-12-02 19:31:46 -08:00
iNumEnemies = 0 ;
for ( AI_EnemyInfo_t * pEMemory = GetEnemies ( ) - > GetFirst ( & iter ) ; pEMemory ! = NULL ; pEMemory = GetEnemies ( ) - > GetNext ( & iter ) )
{
if ( pEMemory - > hEnemy - > IsAlive ( ) & & ( pEMemory - > hEnemy - > Classify ( ) ! = CLASS_BULLSEYE ) )
{
iNumEnemies + + ;
}
}
set . AppendCriteria ( " num_enemies " , UTIL_VarArgs ( " %d " , iNumEnemies ) ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
set . AppendCriteria ( " darkness_mode " , UTIL_VarArgs ( " %d " , HasCondition ( COND_ALYX_IN_DARK ) ) ) ;
set . AppendCriteria ( " water_level " , UTIL_VarArgs ( " %d " , GetWaterLevel ( ) ) ) ;
CHL2_Player * pPlayer = assert_cast < CHL2_Player * > ( UTIL_PlayerByIndex ( 1 ) ) ;
set . AppendCriteria ( " num_companions " , UTIL_VarArgs ( " %d " , pPlayer ? pPlayer - > GetNumSquadCommandables ( ) : 0 ) ) ;
set . AppendCriteria ( " flashlight_on " , UTIL_VarArgs ( " %d " , pPlayer ? pPlayer - > FlashlightIsOn ( ) : 0 ) ) ;
BaseClass : : ModifyOrAppendCriteria ( set ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Turn off Alyx's readiness when she's around a vehicle
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : IsReadinessCapable ( void )
{
// Let the convar decide
return npc_alyx_readiness . GetBool ( ) ; ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : IsAllowedToInteract ( )
{
if ( RunningPassengerBehavior ( ) )
return false ;
if ( IsInAScript ( ) )
return false ;
if ( IsCurSchedule ( SCHED_SCENE_GENERIC ) )
return false ;
if ( GetEnemy ( ) )
{
if ( GetEnemy ( ) - > GetAbsOrigin ( ) . DistTo ( GetAbsOrigin ( ) ) < = 240.0f )
{
// Enemy is nearby!
return false ;
}
}
return m_bInteractionAllowed ;
}
//-----------------------------------------------------------------------------
// Purpose: Allows the NPC to react to being given a weapon
// Input : *pNewWeapon - Weapon given
//-----------------------------------------------------------------------------
void CNPC_Alyx : : OnChangeActiveWeapon ( CBaseCombatWeapon * pOldWeapon , CBaseCombatWeapon * pNewWeapon )
{
m_WeaponType = ComputeWeaponType ( ) ;
BaseClass : : OnChangeActiveWeapon ( pOldWeapon , pNewWeapon ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Allows the NPC to react to being given a weapon
// Input : *pNewWeapon - Weapon given
//-----------------------------------------------------------------------------
void CNPC_Alyx : : OnGivenWeapon ( CBaseCombatWeapon * pNewWeapon )
{
// HACK: This causes Alyx to pull her gun from a holstered position
if ( pNewWeapon - > ClassMatches ( CLASSNAME_ALYXGUN ) )
{
// Put it away so we can pull it out properly
GetActiveWeapon ( ) - > Holster ( ) ;
SetActiveWeapon ( NULL ) ;
// Draw the weapon when we're next able to
SetDesiredWeaponState ( DESIREDWEAPONSTATE_UNHOLSTERED ) ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : Weapon_Equip ( CBaseCombatWeapon * pWeapon )
{
m_WeaponType = ComputeWeaponType ( pWeapon ) ;
BaseClass : : Weapon_Equip ( pWeapon ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : Weapon_CanUse ( CBaseCombatWeapon * pWeapon )
{
if ( ! pWeapon - > ClassMatches ( CLASSNAME_SHOTGUN ) )
return false ;
return BaseClass : : Weapon_CanUse ( pWeapon ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void CNPC_Alyx : : OnUpdateShotRegulator ( )
{
BaseClass : : OnUpdateShotRegulator ( ) ;
if ( ! HasShotgun ( ) & & IsCrouching ( ) )
{
// While crouching, Alyx fires longer bursts
int iMinBurst , iMaxBurst ;
GetShotRegulator ( ) - > GetBurstShotCountRange ( & iMinBurst , & iMaxBurst ) ;
GetShotRegulator ( ) - > SetBurstShotCountRange ( iMinBurst * 2 , iMaxBurst * 2 ) ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : BarnacleDeathSound ( void )
{
Speak ( TLK_SELF_IN_BARNACLE ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : PassengerState_e
//-----------------------------------------------------------------------------
PassengerState_e CNPC_Alyx : : GetPassengerState ( void )
{
return m_PassengerBehavior . GetPassengerState ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Alyx : : Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value )
{
// if I'm in the vehicle, the player is probably trying to use the vehicle
if ( GetPassengerState ( ) = = PASSENGER_STATE_INSIDE & & pActivator - > IsPlayer ( ) & & GetParent ( ) )
{
GetParent ( ) - > Use ( pActivator , pCaller , useType , value ) ;
return ;
}
m_bDontUseSemaphore = true ;
SpeakIfAllowed ( TLK_USE ) ;
m_bDontUseSemaphore = false ;
m_OnPlayerUse . FireOutput ( pActivator , pCaller ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : PlayerInSpread ( const Vector & sourcePos , const Vector & targetPos , float flSpread , float maxDistOffCenter , bool ignoreHatedPlayers )
{
// loop through all players
for ( int i = 1 ; i < = gpGlobals - > maxClients ; i + + )
{
CBasePlayer * pPlayer = UTIL_PlayerByIndex ( i ) ;
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# ifdef MAPBASE
// "> D_FR" means it isn't D_HT, D_FR, or D_ER (error disposition)
if ( pPlayer & & ( ! ignoreHatedPlayers | | IRelationType ( pPlayer ) > D_FR ) & & ! ( pPlayer - > GetFlags ( ) & FL_NOTARGET ) )
# else
2013-12-02 19:31:46 -08:00
if ( pPlayer & & ( ! ignoreHatedPlayers | | IRelationType ( pPlayer ) ! = D_HT ) )
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
//If the player is being lifted by a barnacle then go ahead and ignore the player and shoot.
# ifdef HL2_EPISODIC
if ( pPlayer - > IsEFlagSet ( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
return false ;
# endif
if ( PointInSpread ( pPlayer , sourcePos , targetPos , pPlayer - > WorldSpaceCenter ( ) , flSpread , maxDistOffCenter ) )
return true ;
}
}
return false ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : IsCrouchedActivity ( Activity activity )
{
Activity realActivity = TranslateActivity ( activity ) ;
switch ( realActivity )
{
case ACT_RELOAD_LOW :
case ACT_COVER_LOW :
case ACT_COVER_PISTOL_LOW :
case ACT_COVER_SMG1_LOW :
case ACT_RELOAD_SMG1_LOW :
// Aren't these supposed to be a little higher than the above?
case ACT_RANGE_ATTACK1_LOW :
case ACT_RANGE_ATTACK2_LOW :
case ACT_RANGE_ATTACK_AR2_LOW :
case ACT_RANGE_ATTACK_SMG1_LOW :
case ACT_RANGE_ATTACK_SHOTGUN_LOW :
case ACT_RANGE_ATTACK_PISTOL_LOW :
case ACT_RANGE_AIM_LOW :
case ACT_RANGE_AIM_SMG1_LOW :
case ACT_RANGE_AIM_PISTOL_LOW :
case ACT_RANGE_AIM_AR2_LOW :
return true ;
}
return false ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : OnBeginMoveAndShoot ( )
{
if ( BaseClass : : OnBeginMoveAndShoot ( ) )
{
SpeakAttacking ( ) ;
return true ;
}
return false ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Alyx : : SpeakAttacking ( void )
{
if ( GetActiveWeapon ( ) & & m_AnnounceAttackTimer . Expired ( ) )
{
SpeakIfAllowed ( TLK_ATTACKING , UTIL_VarArgs ( " attacking_with_weapon:%s " , GetActiveWeapon ( ) - > GetClassname ( ) ) ) ;
m_AnnounceAttackTimer . Set ( 3 , 5 ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *lpszInteractionName -
// *pOther -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Alyx : : ForceVehicleInteraction ( const char * lpszInteractionName , CBaseCombatCharacter * pOther )
{
return m_PassengerBehavior . ForceVehicleInteraction ( lpszInteractionName , pOther ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CNPC_Alyx : : WeaponType_t CNPC_Alyx : : ComputeWeaponType ( CBaseEntity * pWeapon )
{
if ( ! pWeapon )
{
pWeapon = GetActiveWeapon ( ) ;
}
if ( ! pWeapon )
{
return WT_NONE ;
}
if ( pWeapon - > ClassMatches ( CLASSNAME_ALYXGUN ) )
{
return WT_ALYXGUN ;
}
if ( pWeapon - > ClassMatches ( CLASSNAME_SMG1 ) )
{
return WT_SMG1 ;
}
if ( pWeapon - > ClassMatches ( CLASSNAME_SHOTGUN ) )
{
return WT_SHOTGUN ;
}
if ( pWeapon - > ClassMatches ( CLASSNAME_AR2 ) )
{
return WT_AR2 ;
}
return WT_OTHER ;
}
//-----------------------------------------------------------------------------
// Purpose: Complain about being punted
//-----------------------------------------------------------------------------
void CNPC_Alyx : : InputVehiclePunted ( inputdata_t & inputdata )
{
// If we're in a vehicle, complain about being punted
if ( IsInAVehicle ( ) & & GetVehicleEntity ( ) = = inputdata . pCaller )
{
// FIXME: Pass this up into the behavior?
SpeakIfAllowed ( TLK_PASSENGER_PUNTED ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_Alyx : : InputOutsideTransition ( inputdata_t & inputdata )
{
CBasePlayer * pPlayer = AI_GetSinglePlayer ( ) ;
if ( pPlayer & & pPlayer - > IsInAVehicle ( ) )
{
if ( ShouldAlwaysTransition ( ) = = false )
return ;
// Enter immediately
EnterVehicle ( pPlayer - > GetVehicleEntity ( ) , true ) ;
return ;
}
// If the player is in the vehicle and we're not, then we need to enter the vehicle immediately
BaseClass : : InputOutsideTransition ( inputdata ) ;
}
//=========================================================
// AI Schedules Specific to this NPC
//=========================================================
AI_BEGIN_CUSTOM_NPC ( npc_alyx , CNPC_Alyx )
DECLARE_TASK ( TASK_ALYX_BEGIN_INTERACTION )
DECLARE_TASK ( TASK_ALYX_COMPLETE_INTERACTION )
DECLARE_TASK ( TASK_ALYX_ANNOUNCE_HACK )
DECLARE_TASK ( TASK_ALYX_GET_PATH_TO_INTERACT_TARGET )
DECLARE_TASK ( TASK_ALYX_WAIT_HACKING )
DECLARE_TASK ( TASK_ALYX_DRAW_PISTOL )
DECLARE_TASK ( TASK_ALYX_HOLSTER_PISTOL )
DECLARE_TASK ( TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL )
DECLARE_TASK ( TASK_ALYX_BUILD_COMBAT_FACE_PATH )
DECLARE_TASK ( TASK_ALYX_SET_IDLE_ACTIVITY )
DECLARE_TASK ( TASK_ALYX_FALL_TO_GROUND )
DECLARE_ANIMEVENT ( AE_ALYX_EMPTOOL_ATTACHMENT )
DECLARE_ANIMEVENT ( AE_ALYX_EMPTOOL_SEQUENCE )
DECLARE_ANIMEVENT ( AE_ALYX_EMPTOOL_USE )
2019-08-31 19:28:20 +00:00
# ifndef MAPBASE
2013-12-02 19:31:46 -08:00
DECLARE_ANIMEVENT ( COMBINE_AE_BEGIN_ALTFIRE )
DECLARE_ANIMEVENT ( COMBINE_AE_ALTFIRE )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
DECLARE_CONDITION ( COND_ALYX_HAS_INTERACT_TARGET )
DECLARE_CONDITION ( COND_ALYX_NO_INTERACT_TARGET )
DECLARE_CONDITION ( COND_ALYX_CAN_INTERACT_WITH_TARGET )
DECLARE_CONDITION ( COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET )
DECLARE_CONDITION ( COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT )
DECLARE_CONDITION ( COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT )
DECLARE_CONDITION ( COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED )
DECLARE_CONDITION ( COND_ALYX_IN_DARK )
DECLARE_ACTIVITY ( ACT_ALYX_DRAW_TOOL )
DECLARE_ACTIVITY ( ACT_ALYX_IDLE_TOOL )
DECLARE_ACTIVITY ( ACT_ALYX_ZAP_TOOL )
DECLARE_ACTIVITY ( ACT_ALYX_HOLSTER_TOOL )
DECLARE_ACTIVITY ( ACT_ALYX_PICKUP_RACK )
DEFINE_SCHEDULE
(
SCHED_ALYX_PREPARE_TO_INTERACT_WITH_TARGET ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_DRAW_TOOL "
" TASK_SET_ACTIVITY ACTIVITY:ACT_ALYX_IDLE_TOOL "
" TASK_FACE_PLAYER 0 "
" "
" Interrupts "
" "
)
DEFINE_SCHEDULE
(
SCHED_ALYX_WAIT_TO_INTERACT_WITH_TARGET ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_ALYX_ANNOUNCE_HACK 0 "
" TASK_FACE_PLAYER 0 "
" TASK_SET_ACTIVITY ACTIVITY:ACT_ALYX_IDLE_TOOL "
" TASK_WAIT 2 "
" "
" Interrupts "
" COND_ALYX_CAN_INTERACT_WITH_TARGET "
" COND_ALYX_NO_INTERACT_TARGET "
" COND_LIGHT_DAMAGE "
" COND_HEAVY_DAMAGE "
)
DEFINE_SCHEDULE
(
SCHED_ALYX_INTERACT_WITH_TARGET ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_FACE_PLAYER 0 "
" TASK_ALYX_BEGIN_INTERACTION 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_ZAP_TOOL "
" TASK_SET_SCHEDULE SCHEDULE:SCHED_ALYX_FINISH_INTERACTING_WITH_TARGET "
" "
" Interrupts "
" COND_ALYX_NO_INTERACT_TARGET "
" COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET "
)
DEFINE_SCHEDULE
(
SCHED_ALYX_FINISH_INTERACTING_WITH_TARGET ,
" Tasks "
" TASK_ALYX_COMPLETE_INTERACTION 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_HOLSTER_TOOL "
" "
" Interrupts "
" "
)
DEFINE_SCHEDULE
(
SCHED_ALYX_HOLSTER_EMP ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALYX_HOLSTER_TOOL "
" TASK_ALYX_DRAW_PISTOL 0 "
" "
" Interrupts "
" "
)
DEFINE_SCHEDULE
(
SCHED_ALYX_INTERACTION_INTERRUPTED ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
" TASK_FACE_PLAYER 0 "
" TASK_WAIT 2 "
" "
" Interrupts "
)
DEFINE_SCHEDULE
(
SCHED_ALYX_ALERT_FACE_AWAYFROM_BESTSOUND ,
" Tasks "
" TASK_STORE_BESTSOUND_REACTORIGIN_IN_SAVEPOSITION 0 "
" TASK_STOP_MOVING 0 "
" TASK_FACE_AWAY_FROM_SAVEPOSITION 0 "
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
" TASK_WAIT 10.0 "
" TASK_FACE_REASONABLE 0 "
" "
" Interrupts "
" COND_NEW_ENEMY "
" COND_SEE_FEAR "
" COND_LIGHT_DAMAGE "
" COND_HEAVY_DAMAGE "
" COND_PROVOKED "
)
//===============================================
// > RangeAttack1
//===============================================
DEFINE_SCHEDULE
(
SCHED_ALYX_RANGE_ATTACK1 ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_FACE_ENEMY 0 "
" TASK_ANNOUNCE_ATTACK 1 " // 1 = primary attack
" TASK_RANGE_ATTACK1 0 "
" "
" Interrupts "
" COND_ENEMY_WENT_NULL "
" COND_HEAVY_DAMAGE "
" COND_ENEMY_OCCLUDED "
" COND_NO_PRIMARY_AMMO "
" COND_HEAR_DANGER "
" COND_WEAPON_BLOCKED_BY_FRIEND "
" COND_WEAPON_SIGHT_OCCLUDED "
)
//===============================================
// > SCHED_ALYX_ALERT_REACT_TO_COMBAT_SOUND
//===============================================
DEFINE_SCHEDULE
(
SCHED_ALYX_ALERT_REACT_TO_COMBAT_SOUND ,
" Tasks "
" TASK_REACT_TO_COMBAT_SOUND 0 "
" TASK_SET_SCHEDULE SCHEDULE:SCHED_ALERT_FACE_BESTSOUND "
" "
" Interrupts "
" COND_NEW_ENEMY "
)
//=========================================================
// > SCHED_ALYX_COMBAT_FACE
//=========================================================
DEFINE_SCHEDULE
(
SCHED_ALYX_COMBAT_FACE ,
" Tasks "
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE "
" TASK_STOP_MOVING 0 "
" TASK_ALYX_BUILD_COMBAT_FACE_PATH 0 "
" TASK_RUN_PATH 0 "
" TASK_FACE_IDEAL 0 "
" TASK_WAIT_FOR_MOVEMENT 0 "
" "
" Interrupts "
" COND_CAN_RANGE_ATTACK1 "
" COND_CAN_RANGE_ATTACK2 "
" COND_CAN_MELEE_ATTACK1 "
" COND_CAN_MELEE_ATTACK2 "
" COND_NEW_ENEMY "
" COND_ENEMY_DEAD "
)
//=========================================================
// > SCHED_ALYX_WAKE_ANGRY
//=========================================================
DEFINE_SCHEDULE
(
SCHED_ALYX_WAKE_ANGRY ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_SOUND_WAKE 0 "
" "
" Interrupts "
)
//===============================================
// > NewWeapon
//===============================================
DEFINE_SCHEDULE
(
SCHED_ALYX_NEW_WEAPON ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_SET_TOLERANCE_DISTANCE 5 "
" TASK_GET_PATH_TO_TARGET_WEAPON 0 "
" TASK_WEAPON_RUN_PATH 0 "
" TASK_STOP_MOVING 0 "
" TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL 0 "
" TASK_FACE_TARGET 0 "
" TASK_WEAPON_PICKUP 0 "
" TASK_WAIT 1 " // Don't move before done standing up
" "
" Interrupts "
)
//===============================================
// > Alyx_Idle_Stand
//===============================================
DEFINE_SCHEDULE
(
SCHED_ALYX_IDLE_STAND ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_ALYX_SET_IDLE_ACTIVITY ACTIVITY:ACT_IDLE "
" TASK_WAIT 5 "
" TASK_WAIT_PVS 0 "
" "
" Interrupts "
" COND_NEW_ENEMY "
" COND_SEE_FEAR "
" COND_LIGHT_DAMAGE "
" COND_HEAVY_DAMAGE "
" COND_SMELL "
" COND_PROVOKED "
" COND_GIVE_WAY "
" COND_HEAR_PLAYER "
" COND_HEAR_DANGER "
" COND_HEAR_COMBAT "
" COND_HEAR_BULLET_IMPACT "
" COND_IDLE_INTERRUPT "
)
//===============================================
// Makes Alyx die if she falls too long
//===============================================
DEFINE_SCHEDULE
(
SCHED_ALYX_FALL_TO_GROUND ,
" Tasks "
" TASK_ALYX_FALL_TO_GROUND 0 "
" "
" Interrupts "
)
DEFINE_SCHEDULE
(
SCHED_ALYX_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 "
) ;
AI_END_CUSTOM_NPC ( )