//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #ifndef NPC_STRIDER_H #define NPC_STRIDER_H #include "ai_blended_movement.h" #include "ai_navigator.h" #include "ai_pathfinder.h" #include "ai_utils.h" #include "physics_bone_follower.h" #include "physics_prop_ragdoll.h" #include "smoke_trail.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 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 //=============================================================================