//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #ifndef NPC_STRIDER_H #define NPC_STRIDER_H #include "ai_blended_movement.h" #include "ai_pathfinder.h" #include "ai_navigator.h" #include "ai_utils.h" #include "smoke_trail.h" #include "physics_bone_follower.h" #include "physics_prop_ragdoll.h" #if defined( _WIN32 ) #pragma once #endif #include "tier0/memdbgon.h" class CNPC_Strider; class CNPC_Bullseye; class CStriderMinigun; //----------------------------------------------------------------------------- // // Support for moving Strider air nodes to the correct Z for the Strider // regardless of Hammer placement // //----------------------------------------------------------------------------- class CAI_Network; class CAI_Node; struct StriderMinigunViewcone_t; struct AI_EnemyInfo_t; void AdjustStriderNodePosition( CAI_Network *pNetwork, CAI_Node *pNode ); //----------------------------------------------------------------------------- // // Strider Minigun // //----------------------------------------------------------------------------- abstract_class IMinigunHost { public: virtual void ShootMinigun( const Vector *pTarget, float aimError, const Vector &vecSpread = vec3_origin ) = 0; virtual void UpdateMinigunControls( float &yaw, float &pitch ) = 0; virtual void GetViewCone( StriderMinigunViewcone_t &cone ) = 0; virtual void NewTarget() = 0; virtual void OnMinigunStartShooting( CBaseEntity *pTarget ) = 0; virtual void OnMinigunStopShooting( CBaseEntity *pTarget ) = 0; virtual CAI_BaseNPC *GetEntity() = 0; }; abstract_class IStriderMinigunHost : public IMinigunHost { public: virtual float GetMinigunRateOfFire() = 0; virtual float GetMinigunOnTargetTime() = 0; virtual float GetMinigunShootDuration() = 0; virtual float GetMinigunShootDowntime() = 0; virtual float GetMinigunShootVariation() = 0; }; //----------------------------------------------------------------------------- // // npc_strider // //----------------------------------------------------------------------------- const int NUM_STRIDER_IK_TARGETS = 6; //--------------------------------------------------------- class CNPC_Strider : public CAI_BlendingHost, public IStriderMinigunHost { DECLARE_CLASS( CNPC_Strider, CAI_BaseNPC ); DECLARE_SERVERCLASS(); public: CNPC_Strider(); ~CNPC_Strider(); //--------------------------------- void Precache(); void Spawn(); bool CreateVPhysics(); void InitBoneFollowers( void ); void PostNPCInit(); void Activate(); void UpdateOnRemove(); void InitBoneControllers(); void OnRestore(); Class_T Classify(); bool ShouldAttractAutoAim( CBaseEntity *pAimingEnt ); virtual float GetAutoAimRadius() { return 80.0f; } int DrawDebugTextOverlays(); void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); } virtual bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity ); //--------------------------------- virtual Vector GetNodeViewOffset() { return BaseClass::GetDefaultEyeOffset(); } Vector EyePosition(); const Vector & GetViewOffset(); Vector EyePositionCrouched() { return GetAbsOrigin() - Vector( 0, 0, 330 ); } //--------------------------------- // CBaseAnimating void CalculateIKLocks( float currentTime ); float GetIdealAccel() const { return GetIdealSpeed(); } //--------------------------------- // Behavior //--------------------------------- void NPCThink(); void PrescheduleThink(); void GatherConditions(); void CheckFlinches() {} // Strider handles on own void GatherHeightConditions( const Vector &vTestPos, CBaseEntity *pEntity ); void OnStateChange( NPC_STATE oldState, NPC_STATE newState ); void BuildScheduleTestBits(); int SelectSchedule(); int TranslateSchedule( int scheduleType ); void StartTask( const Task_t *pTask ); void RunTask( const Task_t *pTask ); bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ); void HandleAnimEvent( animevent_t *pEvent ); Disposition_t IRelationType( CBaseEntity *pTarget ); void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority ); bool ScheduledMoveToGoalEntity( int scheduleType, CBaseEntity *pGoalEntity, Activity movementActivity ); bool ScheduledFollowPath( int scheduleType, CBaseEntity *pPathStart, Activity movementActivity ); //--------------------------------- // Inputs //--------------------------------- void InputSetMinigunTime( inputdata_t &inputdata ); void InputSetMinigunTarget( inputdata_t &inputdata ); void InputDisableMinigun( inputdata_t &inputdata ); void InputEnableMinigun( inputdata_t &inputdata ); void InputSetCannonTarget( inputdata_t &inputdata ); void InputFlickRagdoll( inputdata_t &inputdata ); void InputDisableCollisionWith( inputdata_t &inputdata ); void InputEnableCollisionWith( inputdata_t &inputdata ); void InputCrouch( inputdata_t &inputdata ); void InputCrouchInstantly( inputdata_t &inputdata ); void InputStand( inputdata_t &inputdata ); void InputSetHeight( inputdata_t &inputdata ); void InputSetTargetPath( inputdata_t &inputdata ); void InputClearTargetPath( inputdata_t &inputdata ); void InputDisableCrouchWalk( inputdata_t &inputdata ); void InputEnableCrouchWalk( inputdata_t &inputdata ); void InputEnableAggressiveBehavior( inputdata_t &inputdata ); void InputDisableAggressiveBehavior( inputdata_t &inputdata ); void InputStopShootingMinigunForSeconds( inputdata_t &inputdata ); void InputDisableCrouch( inputdata_t &inputdata ); void InputDisableMoveToLOS( inputdata_t &inputdata ); void InputExplode( inputdata_t &inputdata ); void InputScaleGroundSpeed( inputdata_t &inputdata ); //--------------------------------- // Combat //--------------------------------- bool HasPass() { return m_PlayerFreePass.HasPass(); } bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); Vector BodyTarget( const Vector &posSrc, bool bNoisy ); bool IsValidEnemy( CBaseEntity *pTarget ); bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL ); float StriderEnemyDistance( CBaseEntity *pEnemy ); bool FCanCheckAttacks(); int RangeAttack2Conditions( float flDot, float flDist ); int MeleeAttack1Conditions( float flDot, float flDist ); int MeleeAttack2Conditions( float flDot, float flDist ); bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions); bool CurrentWeaponLOSCondition(const Vector &targetPos, bool bSetConditions); bool IsValidShootPosition ( const Vector &vecCoverLocation, CAI_Node *pNode, CAI_Hint const *pHint ); bool TestShootPosition(const Vector &vecShootPos, const Vector &targetPos ); Vector Weapon_ShootPosition(); void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); void DoImpactEffect( trace_t &tr, int nDamageType ); void DoMuzzleFlash( void ); bool CanShootThrough( const trace_t &tr, const Vector &vecTarget ); void CreateFocus(); CNPC_Bullseye * GetFocus(); bool GetWeaponLosZ( const Vector &vOrigin, float minZ, float maxZ, float increment, CBaseEntity *pTarget, float *pResult ); //--------------------------------- // Sounds & speech //--------------------------------- void AlertSound(); void PainSound( const CTakeDamageInfo &info ); void DeathSound( const CTakeDamageInfo &info ); void HuntSound(); //--------------------------------- // Damage handling //--------------------------------- void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ); int OnTakeDamage_Alive( const CTakeDamageInfo &info ); int TakeDamageFromCombineBall( const CTakeDamageInfo &info ); void Event_Killed( const CTakeDamageInfo &info ); void RagdollDeathEffect( CRagdollProp *pRagdoll, float flDuration ); bool BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector ); void StartSmoking(); void StopSmoking( float flDelay = 0.1 ); bool IsSmoking() { return m_hSmoke != NULL; } void Explode(); //--------------------------------- // Posture //--------------------------------- float GetMaxHeightModel() const { return 500.0; } float GetMaxHeight() const { return 490.0; } float GetMinHeight() const { return 200.0; } float GetHeightRange() const { return GetMaxHeight() - GetMinHeight(); } void SetHeight( float h ); float GetHeight() { return GetPoseParameter( gm_BodyHeightPoseParam ); } void SetIdealHeight( float h ); void SetAbsIdealHeight( float z ); float GetIdealHeight() { return m_idealHeight; } Vector GetAdjustedOrigin() { Vector result = GetAbsOrigin(); result.z -= GetMaxHeightModel() - GetHeight(); return result; } bool IsInCrouchedPosture() { return GetIdealHeight() < GetMaxHeight() * .5; } bool IsInStandingPosture() { return !IsInCrouchedPosture(); } bool IsStriderCrouching(); bool IsStriderStanding(); void SetupGlobalModelData(); virtual bool CanBecomeServerRagdoll( void ) { return false; } //--------------------------------- // Navigation & Movement //--------------------------------- class CNavigator : public CAI_ComponentWithOuter { typedef CAI_ComponentWithOuter BaseClass; public: CNavigator( CNPC_Strider *pOuter ) : BaseClass( pOuter ) { } void MoveCalcBaseGoal( AILocalMoveGoal_t *pMoveGoal ); bool MoveUpdateWaypoint( AIMoveResult_t *pResult ); bool DoFindPathToPos(); bool ShouldOptimizeInitialPathSegment( AI_Waypoint_t *pFirstWaypoint ); bool GetStoppingPath( CAI_WaypointList *pClippedWaypoints ); }; class CPathfinder : public CAI_Pathfinder { typedef CAI_Pathfinder BaseClass; public: CPathfinder( CNPC_Strider *pOuter ) : BaseClass( pOuter ) {} virtual bool CanUseLocalNavigation() { return false; } }; friend class CNavigator; friend void AdjustStriderNodePosition( CAI_Network *pNetwork, CAI_Node *pNode ); bool OverrideMove( float flInterval ); void MaintainTurnActivity( void ); bool IsUnusableNode(int iNodeID, CAI_Hint *pHint); // Override for special NPC behavior void TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition ); bool HasPendingTargetPath(); void SetTargetPath(); float GetDefaultNavGoalTolerance(); void OnMovementComplete(); float GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence ); float MaxYawSpeed(); CAI_Navigator * CreateNavigator() { return new CNavigator( this ); } CAI_Pathfinder *CreatePathfinder() { return new CPathfinder( this ); } //--------------------------------- // Minigun //--------------------------------- void ShootMinigun( const Vector *pTarget, float aimError, const Vector &vecSpread = vec3_origin ); void UpdateMinigunControls( float &yaw, float &pitch ); void GetViewCone( StriderMinigunViewcone_t &cone ); void NewTarget() { m_flTargetAcquiredTime = gpGlobals->curtime; } void OnMinigunStartShooting( CBaseEntity *pTarget ) {}; void OnMinigunStopShooting( CBaseEntity *pTarget ); float GetMinigunRateOfFire(); float GetMinigunOnTargetTime(); float GetMinigunShootDuration(); float GetMinigunShootDowntime(); float GetMinigunShootVariation(); CAI_BaseNPC * GetEntity() { return this; } bool IsUsingAggressiveBehavior() { return m_bUseAggressiveBehavior; } //--------------------------------- // Cannon //--------------------------------- Vector CannonPosition(); CBaseEntity * GetCannonTarget(); bool HasCannonTarget() const; bool IsCannonTarget( CBaseEntity *pTarget ) const; bool AimCannonAt( CBaseEntity *pEntity, float flInterval ); void FireCannon(); void CannonHitThink(); //--------------------------------- // Collision handling //--------------------------------- void VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent ); bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ); // Conservative collision volumes static float gm_strideLength; #ifdef HL2_EPISODIC void StriderBusterAttached( CBaseEntity *pAttached ); void StriderBusterDetached( CBaseEntity *pAttached ); #endif // HL2_EPISODIC public: //--------------------------------- // Misc //--------------------------------- bool CarriedByDropship(); void CarriedThink(); //--------------------------------- // Foot handling //--------------------------------- Vector LeftFootHit( float eventtime ); Vector RightFootHit( float eventtime ); Vector BackFootHit( float eventtime ); void StompHit( int followerBoneIndex ); void FootFX( const Vector &origin ); Vector CalculateStompHitPosition( CBaseEntity *pEnemy ); bool IsLegBoneFollower( CBoneFollower *pFollower ); CBoneFollower *GetBoneFollowerByIndex( int nIndex ); int GetBoneFollowerIndex( CBoneFollower *pFollower ); protected: // Because the strider is a leaf class, we can use // static variables to store this information, and save some memory. // Should the strider end up having inheritors, their activate may // stomp these numbers, in which case you should make these ordinary members // again. // // The strider also caches some pose parameters in SetupGlobalModelData(). static int m_poseMiniGunYaw, m_poseMiniGunPitch; static bool m_sbStaticPoseParamsLoaded; virtual void PopulatePoseParameters( void ); private: bool ShouldExplodeFromDamage( const CTakeDamageInfo &info ); bool m_bExploding; //----------------------------------------------------- // Conditions, Schedules, Tasks //----------------------------------------------------- enum { SCHED_STRIDER_RANGE_ATTACK1 = BaseClass::NEXT_SCHEDULE, SCHED_STRIDER_RANGE_ATTACK2, // Immolator SCHED_STRIDER_CROUCH, SCHED_STRIDER_STAND, SCHED_STRIDER_DODGE, SCHED_STRIDER_STOMPL, SCHED_STRIDER_STOMPR, SCHED_STRIDER_FLICKL, SCHED_STRIDER_FLICKR, SCHED_STRIDER_HUNT, SCHED_STRIDER_DIE, SCHED_STRIDER_ATTACK_CANNON_TARGET, SCHED_STRIDER_CHASE_ENEMY, SCHED_STRIDER_COMBAT_FACE, SCHED_STRIDER_AGGRESSIVE_COMBAT_STAND, SCHED_STRIDER_ESTABLISH_LINE_OF_FIRE_CANNON, SCHED_STRIDER_FALL_TO_GROUND, TASK_STRIDER_AIM = BaseClass::NEXT_TASK, TASK_STRIDER_DODGE, TASK_STRIDER_STOMP, TASK_STRIDER_BREAKDOWN, TASK_STRIDER_START_MOVING, TASK_STRIDER_REFRESH_HUNT_PATH, TASK_STRIDER_GET_PATH_TO_CANNON_TARGET, TASK_STRIDER_FACE_CANNON_TARGET, TASK_STRIDER_SET_HEIGHT, TASK_STRIDER_GET_PATH_TO_CANNON_LOS, TASK_STRIDER_SET_CANNON_HEIGHT, TASK_STRIDER_FIRE_CANNON, TASK_STRIDER_FALL_TO_GROUND, COND_STRIDER_DO_FLICK = BaseClass::NEXT_CONDITION, COND_TRACK_PATH_GO, COND_STRIDER_SHOULD_CROUCH, COND_STRIDER_SHOULD_STAND, COND_STRIDER_MINIGUN_SHOOTING, COND_STRIDER_MINIGUN_NOT_SHOOTING, COND_STRIDER_HAS_CANNON_TARGET, COND_STRIDER_ENEMY_UPDATED, COND_STRIDER_HAS_LOS_Z, }; string_t m_iszStriderBusterName; string_t m_iszMagnadeClassname; string_t m_iszHunterClassname; CStriderMinigun *m_pMinigun; int m_miniGunAmmo; int m_miniGunDirectAmmo; float m_nextShootTime; float m_nextStompTime; float m_ragdollTime; float m_miniGunShootDuration; float m_aimYaw; float m_aimPitch; Vector m_blastHit; Vector m_blastNormal; CNetworkVector( m_vecHitPos ); CNetworkArray( Vector, m_vecIKTarget, NUM_STRIDER_IK_TARGETS ); CRandSimTimer m_PostureAnimationTimer; EHANDLE m_hRagdoll; EHANDLE m_hCannonTarget; CSimpleSimTimer m_AttemptCannonLOSTimer; float m_flSpeedScale; float m_flTargetSpeedScale; CSimpleSimTimer m_LowZCorrectionTimer; // Contained Bone Follower manager CBoneFollowerManager m_BoneFollowerManager; int m_BodyTargetBone; bool m_bDisableBoneFollowers; int m_iVisibleEnemies; float m_flTargetAcquiredTime; bool m_bCrouchLocked; // Designer made the strider crouch. Don't let the AI stand him up. bool m_bNoCrouchWalk; bool m_bDontCrouch; bool m_bNoMoveToLOS; bool m_bFastCrouch; bool m_bMinigunEnabled; // If false, minigun disabled by level designer until further notice. float m_idealHeight; float m_HeightVelocity; // FIXME: move to a base class to handle turning for blended movement derived characters float m_prevYaw; float m_doTurn; float m_doLeft; float m_doRight; float m_flNextTurnAct; string_t m_strTrackName; EHANDLE m_hFocus; float m_flTimeLastAlertSound; float m_flTimeNextHuntSound; bool m_bUseAggressiveBehavior; float m_flTimePlayerMissileDetected; EHANDLE m_hPlayersMissile; bool m_bMinigunUseDirectFire; CHandle m_hSmoke; CSimpleSimTimer m_EnemyUpdatedTimer; CAI_FreePass m_PlayerFreePass; #ifdef HL2_EPISODIC CUtlVector< EHANDLE > m_hAttachedBusters; // List of busters attached to us #endif // HL2_EPISODIC static float gm_zCannonDist; static float gm_zMinigunDist; static Vector gm_vLocalRelativePositionCannon; static Vector gm_vLocalRelativePositionMinigun; static int gm_YawControl; static int gm_PitchControl; static int gm_CannonAttachment; static int gm_BodyHeightPoseParam; DEFINE_CUSTOM_AI; DECLARE_DATADESC(); }; //----------------------------------------------------------------------------- //--------------------------------------------------------- enum StriderMinigunPeg_t { MINIGUN_PEGGED_DONT_CARE = 0, MINIGUN_PEGGED_UP, MINIGUN_PEGGED_DOWN, MINIGUN_PEGGED_LEFT, MINIGUN_PEGGED_RIGHT, }; //--------------------------------------------------------- struct StriderMinigunViewcone_t { Vector origin; Vector axis; float cosAngle; float length; }; //--------------------------------------------------------- struct StriderMinigunAnimController_t { float current; float target; float rate; void Update( float dt, bool approach = true ) { if( approach ) { current = Approach( target, current, rate * dt ); } else { current = target; } } void Random( float minTarget, float maxTarget, float minRate, float maxRate ) { target = random->RandomFloat( minTarget, maxTarget ); rate = random->RandomFloat( minRate, maxRate ); } }; //--------------------------------------------------------- class CStriderMinigun { public: DECLARE_DATADESC(); void Init(); void SetTarget( IStriderMinigunHost *pHost, CBaseEntity *pTarget, bool bOverrideEnemy = false ); CBaseEntity *GetTarget() { return m_hTarget.Get(); } void Think( IStriderMinigunHost *pHost, float dt ); void SetState( int newState ); bool ShouldFindTarget( IMinigunHost *pHost ); void AimAtPoint( IStriderMinigunHost *pHost, const Vector &vecPoint, bool bSnap = false ); void AimAtTarget( IStriderMinigunHost *pHost, CBaseEntity *pTarget, bool bSnap = false ); void ShootAtTarget( IStriderMinigunHost *pHost, CBaseEntity *pTarget, float shootTime ); void StartShooting( IStriderMinigunHost *pHost, CBaseEntity *pTarget, float duration ); void ExtendShooting( float timeExtend ); void SetShootDuration( float duration ); void StopShootingForSeconds( IStriderMinigunHost *pHost, CBaseEntity *pTarget, float duration ); bool IsPegged( int dir = MINIGUN_PEGGED_DONT_CARE ); bool CanStartShooting( IStriderMinigunHost *pHost, CBaseEntity *pTargetEnt ); float GetBurstTimeRemaining() { return m_burstTime - gpGlobals->curtime; } void RecordShotOnTarget() { m_iOnTargetShots++; } void ClearOnTarget() { m_iOnTargetShots = 0; } bool IsOnTarget( int numShots = 0 ) { return ( numShots == 0 ) ? (m_iOnTargetShots > 0) : (m_iOnTargetShots >= numShots); } void Enable( IMinigunHost *pHost, bool enable ); float GetAimError(); enum minigunstates_t { MINIGUN_OFF = 0, MINIGUN_SHOOTING = 1, }; int GetState() { return m_minigunState; } bool IsShooting() { return GetState() == MINIGUN_SHOOTING; } private: bool m_enable; int m_minigunState; float m_nextBulletTime; // Minigun is shooting, when can I fire my next bullet? float m_burstTime; // If firing, how long till done? If not, how long till I can? float m_nextTwitchTime; int m_randomState; EHANDLE m_hTarget; StriderMinigunAnimController_t m_yaw; StriderMinigunAnimController_t m_pitch; bool m_bWarnedAI; float m_shootDuration; Vector m_vecAnchor; // A burst starts here and goes to the target's orgin. bool m_bOverrideEnemy; // The minigun wants something other than the Strider's enemy as a target right now. Vector m_vecLastTargetPos; // Last place minigun saw the target. int m_iOnTargetShots; }; class CSparkTrail : public CPointEntity { DECLARE_CLASS( CSparkTrail, CPointEntity ); void Spawn( void ); void SparkThink( void ); virtual void Precache(); DECLARE_DATADESC(); }; #include "tier0/memdbgoff.h" #endif // NPC_STRIDER_H //=============================================================================