//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Base combat character with no AI // //=====================================================================================// #include "ai_baseactor.h" #include "npc_playercompanion.h" #include "ai_behavior_holster.h" #include "ai_behavior_functank.h" #include "soundenvelope.h" extern ConVar npc_alyx_readiness; class CNPC_Alyx : public CNPC_PlayerCompanion { public: DECLARE_CLASS( CNPC_Alyx, CNPC_PlayerCompanion ); // fast class list CNPC_Alyx *m_pNext; virtual void ModifyOrAppendCriteria( AI_CriteriaSet &set ); bool ForceVehicleInteraction( const char *lpszInteractionName, CBaseCombatCharacter *pOther ); CNPC_Alyx( ); ~CNPC_Alyx( ); static CNPC_Alyx *GetAlyx( void ); bool CreateBehaviors(); void Spawn( void ); void Activate( void ); void StopLoopingSounds( void ); void SelectModel(); void Precache( void ); void SetupAlyxWithoutParent( void ); void CreateEmpTool( void ); void PrescheduleThink( void ); void GatherConditions(); bool ShouldPlayerAvoid( void ); void AnalyzeGunfireSound( CSound *pSound ); bool IsValidEnemy( CBaseEntity *pEnemy ); void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); void Event_Killed( const CTakeDamageInfo &info ); void EnemyIgnited( CAI_BaseNPC *pVictim ); void CombineBallSocketed( int iNumBounces ); void AimGun( void ); Vector GetActualShootPosition( const Vector &shootOrigin ); float MaxYawSpeed( void ); void OnUpdateShotRegulator(); bool IsCrouchedActivity( Activity activity ); bool OnBeginMoveAndShoot(); void SpeakAttacking( void ); virtual float GetJumpGravity() const { return 1.8f; } // Crouching Vector GetCrouchEyeOffset( void ) { return Vector(0,0,50); } Vector GetCrouchGunOffset( void ) { return Vector(0,0,40); } bool EnemyIsValidCrouchTarget( CBaseEntity *pEnemy ); bool Stand( void ); bool Crouch( void ); void DesireCrouch( void ); // Custom AI void DoCustomCombatAI( void ); void DoMobbedCombatAI( void ); void DoCustomSpeechAI( void ); Disposition_t IRelationType( CBaseEntity *pTarget ); int IRelationPriority( CBaseEntity *pTarget ); CAI_FollowBehavior &GetFollowBehavior( void ); Class_T Classify ( void ); bool FValidateHintType( CAI_Hint *pHint ); int ObjectCaps(); void HandleAnimEvent( animevent_t *pEvent ); bool FInViewCone( CBaseEntity *pEntity ); bool QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC = false ); bool CanSeeEntityInDarkness( CBaseEntity *pEntity ); bool IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition ); Activity NPC_TranslateActivity ( Activity activity ); bool ShouldDeferToFollowBehavior(); void BuildScheduleTestBits(); bool ShouldBehaviorSelectSchedule( CAI_BehaviorBase *pBehavior ); int SelectSchedule( void ); int SelectScheduleDanger( void ); int TranslateSchedule( int scheduleType ); void StartTask( const Task_t *pTask ); void RunTask( const Task_t *pTask ); void OnStateChange( NPC_STATE OldState, NPC_STATE NewState ); float LengthOfLastCombat( void ) const; // bool IsNavigationUrgent(); void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ); bool CanBeHitByMeleeAttack( CBaseEntity *pAttacker ); int OnTakeDamage_Alive( const CTakeDamageInfo &info ); bool FCanCheckAttacks(); float GetAttackDamageScale( CBaseEntity *pVictim ); bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt); virtual bool SpeakIfAllowed( AIConcept_t concept, const char *modifiers = NULL, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0 ); void HolsterPistol(); void DrawPistol(); void Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget = NULL, const Vector *pVelocity = NULL ); void SetEMPHolstered( bool bHolstered ) { m_bIsEMPHolstered = bHolstered; } bool IsEMPHolstered() { return (!m_hEmpTool || m_hEmpTool->GetParent() != this || m_bIsEMPHolstered); } float GetReadinessDecay() { return 60.0f; } virtual bool IsAllowedToAim(); virtual void PainSound( const CTakeDamageInfo &info ); virtual void DeathSound( const CTakeDamageInfo &info ); // Hacking and object interaction void SearchForInteractTargets(); bool IsValidInteractTarget( CBaseEntity *pTarget ); bool CanInteractWithTarget( CBaseEntity *pTarget ); void SetInteractTarget( CBaseEntity *pTarget ); bool HasInteractTarget() { return m_hHackTarget != NULL; } CBaseEntity *GetInteractTarget() { return m_hHackTarget; } void EmpZapTarget( CBaseEntity *pTarget ); virtual void OnSeeEntity( CBaseEntity *pEntity ); void InputAllowInteraction( inputdata_t &inputdata ) { m_bInteractionAllowed = true; } void InputDisallowInteraction( inputdata_t &inputdata ) { m_bInteractionAllowed = false; } void InputAllowDarknessSpeech( inputdata_t &inputdata ) { m_bDarknessSpeechAllowed = inputdata.value.Bool(); } void InputGiveEMP( inputdata_t &inputdata ); void InputVehiclePunted( inputdata_t &inputdata ); void InputOutsideTransition( inputdata_t &inputdata ); virtual void OnGivenWeapon( CBaseCombatWeapon *pNewWeapon ); virtual void OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseCombatWeapon *pNewWeapon ); virtual void Weapon_Equip( CBaseCombatWeapon *pWeapon ); virtual bool Weapon_CanUse( CBaseCombatWeapon *pWeapon ); // Blinding virtual void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ); void CheckBlindedByFlare( void ); bool CanBeBlindedByFlashlight( bool bCheckLightSources ); bool PlayerFlashlightOnMyEyes( CBasePlayer *pPlayer ); bool BlindedByFlare( void ); bool CanReload( void ); virtual bool PickTacticalLookTarget( AILookTargetArgs_t *pArgs ); virtual void OnSelectedLookTarget( AILookTargetArgs_t *pArgs ); virtual bool IsReadinessCapable( void ); virtual void ReadinessLevelChanged( int iPriorLevel ); bool IsAllowedToInteract(); virtual void BarnacleDeathSound( void ); virtual const char *GetDeathMessageText( void ) { return "GAMEOVER_ALYXDEAD"; } PassengerState_e GetPassengerState( void ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); bool PlayerInSpread( const Vector &sourcePos, const Vector &targetPos, float flSpread, float maxDistOffCenter, bool ignoreHatedPlayers ); private: EHANDLE m_hEmpTool; EHANDLE m_hHackTarget; CHandle m_hStealthLookTarget; bool m_bInteractionAllowed; float m_fTimeNextSearchForInteractTargets; bool m_bDarknessSpeechAllowed; bool m_bIsEMPHolstered; bool m_bIsFlashlightBlind; float m_fStayBlindUntil; float m_flDontBlindUntil; bool m_bSpokeLostPlayerInDarkness; bool m_bPlayerFlashlightState; bool m_bHadCondSeeEnemy; string_t m_iszCurrentBlindScene; float m_fTimeUntilNextDarknessFoundPlayer; float m_fCombatStartTime; float m_fCombatEndTime; float m_flNextCrouchTime; CSoundPatch *m_sndDarknessBreathing; // Speech timers // Theoretically, these shouldn't be needed. Instead, each response // should prevent the concept being spoken for the desired time. But // until the responses exists, Alyx will spam the response rules forever, // so these timers stop that. CRandStopwatch m_SpeechWatch_LostPlayer; CSimpleSimTimer m_SpeechTimer_HeardSound; CRandStopwatch m_SpeechWatch_SoundDelay; CRandStopwatch m_SpeechWatch_BreathingRamp; CRandStopwatch m_SpeechWatch_FoundPlayer; CAI_MoveMonitor m_MoveMonitor; enum WeaponType_t { WT_NONE, WT_ALYXGUN, WT_SMG1, WT_SHOTGUN, WT_AR2, WT_OTHER, }; int m_WeaponType; bool m_bShouldHaveEMP; CAI_FuncTankBehavior m_FuncTankBehavior; COutputEvent m_OnFinishInteractWithObject; COutputEvent m_OnPlayerUse; bool RunningPassengerBehavior( void ); WeaponType_t ComputeWeaponType( CBaseEntity *pWeapon = NULL ); WeaponType_t GetWeaponType() { return (WeaponType_t)m_WeaponType; } bool HasShotgun() { Assert( m_WeaponType == ComputeWeaponType() ); return ( m_WeaponType == WT_SHOTGUN ); } bool HasAlyxgun() { Assert( m_WeaponType == ComputeWeaponType() ); return ( m_WeaponType == WT_ALYXGUN ); } bool HasAR2() { Assert( m_WeaponType == ComputeWeaponType() ); return ( m_WeaponType == WT_AR2 ); } private: enum { COND_ALYX_HAS_INTERACT_TARGET = BaseClass::NEXT_CONDITION, COND_ALYX_NO_INTERACT_TARGET, COND_ALYX_CAN_INTERACT_WITH_TARGET, // Hack target is in a suitable state and location to hack COND_ALYX_CAN_NOT_INTERACT_WITH_TARGET, COND_ALYX_PLAYER_TURNED_ON_FLASHLIGHT, COND_ALYX_PLAYER_TURNED_OFF_FLASHLIGHT, COND_ALYX_PLAYER_FLASHLIGHT_EXPIRED, COND_ALYX_IN_DARK, // lights are out and she can't see }; enum { SCHED_ALYX_PREPARE_TO_INTERACT_WITH_TARGET = BaseClass::NEXT_SCHEDULE, SCHED_ALYX_WAIT_TO_INTERACT_WITH_TARGET, SCHED_ALYX_INTERACT_WITH_TARGET, SCHED_ALYX_INTERACTION_INTERRUPTED, SCHED_ALYX_FINISH_INTERACTING_WITH_TARGET, SCHED_ALYX_HOLSTER_EMP, SCHED_ALYX_ALERT_FACE_AWAYFROM_BESTSOUND, SCHED_ALYX_RANGE_ATTACK1, SCHED_ALYX_ALERT_REACT_TO_COMBAT_SOUND, SCHED_ALYX_COMBAT_FACE, SCHED_ALYX_WAKE_ANGRY, SCHED_ALYX_NEW_WEAPON, SCHED_ALYX_IDLE_STAND, SCHED_ALYX_ALERT_FACE_BESTSOUND, SCHED_ALYX_FALL_TO_GROUND, }; enum { TASK_ALYX_BEGIN_INTERACTION = BaseClass::NEXT_TASK, TASK_ALYX_COMPLETE_INTERACTION, TASK_ALYX_ANNOUNCE_HACK, TASK_ALYX_GET_PATH_TO_INTERACT_TARGET, TASK_ALYX_WAIT_HACKING, TASK_ALYX_HOLSTER_PISTOL, TASK_ALYX_DRAW_PISTOL, TASK_ALYX_HOLSTER_AND_DESTROY_PISTOL, TASK_ALYX_BUILD_COMBAT_FACE_PATH, TASK_ALYX_SET_IDLE_ACTIVITY, TASK_ALYX_FALL_TO_GROUND, }; DECLARE_DATADESC(); DEFINE_CUSTOM_AI; };