You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5048 lines
152 KiB
5048 lines
152 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Antlion Guard |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "ai_hint.h" |
|
#include "ai_localnavigator.h" |
|
#include "ai_memory.h" |
|
#include "ai_moveprobe.h" |
|
#include "npcevent.h" |
|
#include "IEffects.h" |
|
#include "ndebugoverlay.h" |
|
#include "soundent.h" |
|
#include "soundenvelope.h" |
|
#include "ai_squad.h" |
|
#include "ai_network.h" |
|
#include "ai_pathfinder.h" |
|
#include "ai_navigator.h" |
|
#include "ai_senses.h" |
|
#include "npc_rollermine.h" |
|
#include "ai_blended_movement.h" |
|
#include "physics_prop_ragdoll.h" |
|
#include "iservervehicle.h" |
|
#include "player_pickup.h" |
|
#include "props.h" |
|
#include "antlion_dust.h" |
|
#include "npc_antlion.h" |
|
#include "decals.h" |
|
#include "prop_combine_ball.h" |
|
#include "eventqueue.h" |
|
#include "te_effect_dispatch.h" |
|
#include "Sprite.h" |
|
#include "particle_parse.h" |
|
#include "particle_system.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
inline void TraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin, |
|
const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore, |
|
int collisionGroup, trace_t *ptr, float minMass ); |
|
|
|
ConVar g_debug_antlionguard( "g_debug_antlionguard", "0" ); |
|
ConVar sk_antlionguard_dmg_charge( "sk_antlionguard_dmg_charge", "0" ); |
|
ConVar sk_antlionguard_dmg_shove( "sk_antlionguard_dmg_shove", "0" ); |
|
|
|
#if HL2_EPISODIC |
|
// When enabled, add code to have the antlion bleed profusely as it is badly injured. |
|
#define ANTLIONGUARD_BLOOD_EFFECTS 2 |
|
ConVar g_antlionguard_hemorrhage( "g_antlionguard_hemorrhage", "1", FCVAR_NONE, "If 1, guard will emit a bleeding particle effect when wounded." ); |
|
#endif |
|
|
|
// Spawnflags |
|
#define SF_ANTLIONGUARD_SERVERSIDE_RAGDOLL ( 1 << 16 ) |
|
#define SF_ANTLIONGUARD_INSIDE_FOOTSTEPS ( 1 << 17 ) |
|
|
|
#define ENVELOPE_CONTROLLER (CSoundEnvelopeController::GetController()) |
|
#define ANTLIONGUARD_MODEL "models/antlion_guard.mdl" |
|
#define MIN_BLAST_DAMAGE 25.0f |
|
#define MIN_CRUSH_DAMAGE 20.0f |
|
|
|
//================================================== |
|
// |
|
// Antlion Guard |
|
// |
|
//================================================== |
|
|
|
#define ANTLIONGUARD_MAX_OBJECTS 128 |
|
#define ANTLIONGUARD_MIN_OBJECT_MASS 8 |
|
#define ANTLIONGUARD_MAX_OBJECT_MASS 750 |
|
#define ANTLIONGUARD_FARTHEST_PHYSICS_OBJECT 350 |
|
#define ANTLIONGUARD_OBJECTFINDING_FOV DOT_45DEGREE // 1/sqrt(2) |
|
|
|
//Melee definitions |
|
#define ANTLIONGUARD_MELEE1_RANGE 156.0f |
|
#define ANTLIONGUARD_MELEE1_CONE 0.7f |
|
|
|
// Antlion summoning |
|
#define ANTLIONGUARD_SUMMON_COUNT 3 |
|
|
|
// Sight |
|
#define ANTLIONGUARD_FOV_NORMAL -0.4f |
|
|
|
// cavern guard's poisoning behavior |
|
#if HL2_EPISODIC |
|
#define ANTLIONGUARD_POISON_TO 12 // we only poison Gordon down to twelve to give him a chance to regen up to 20 by the next charge |
|
#endif |
|
|
|
#define ANTLIONGUARD_CHARGE_MIN 256 |
|
#define ANTLIONGUARD_CHARGE_MAX 2048 |
|
|
|
ConVar sk_antlionguard_health( "sk_antlionguard_health", "0" ); |
|
|
|
int g_interactionAntlionGuardFoundPhysicsObject = 0; // We're moving to a physics object to shove it, don't all choose the same object |
|
int g_interactionAntlionGuardShovedPhysicsObject = 0; // We've punted an object, it is now clear to be chosen by others |
|
|
|
//================================================== |
|
// AntlionGuardSchedules |
|
//================================================== |
|
|
|
enum |
|
{ |
|
SCHED_ANTLIONGUARD_CHARGE = LAST_SHARED_SCHEDULE, |
|
SCHED_ANTLIONGUARD_CHARGE_CRASH, |
|
SCHED_ANTLIONGUARD_CHARGE_CANCEL, |
|
SCHED_ANTLIONGUARD_PHYSICS_ATTACK, |
|
SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY, |
|
SCHED_ANTLIONGUARD_UNBURROW, |
|
SCHED_ANTLIONGUARD_CHARGE_TARGET, |
|
SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION, |
|
SCHED_ANTLIONGUARD_MELEE_ATTACK1, |
|
SCHED_ANTLIONGUARD_SUMMON, |
|
SCHED_ANTLIONGUARD_PATROL_RUN, |
|
SCHED_ANTLIONGUARD_ROAR, |
|
SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE, |
|
SCHED_FORCE_ANTLIONGUARD_PHYSICS_ATTACK, |
|
SCHED_ANTLIONGUARD_CANT_ATTACK, |
|
SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY, |
|
SCHED_ANTLIONGUARD_CHASE_ENEMY |
|
}; |
|
|
|
|
|
//================================================== |
|
// AntlionGuardTasks |
|
//================================================== |
|
|
|
enum |
|
{ |
|
TASK_ANTLIONGUARD_CHARGE = LAST_SHARED_TASK, |
|
TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT, |
|
TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT, |
|
TASK_ANTLIONGUARD_SUMMON, |
|
TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY, |
|
TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION, |
|
TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE, |
|
TASK_ANTLIONGUARD_GET_CHASE_PATH_ENEMY_TOLERANCE, |
|
TASK_ANTLIONGUARD_OPPORTUNITY_THROW, |
|
TASK_ANTLIONGUARD_FIND_PHYSOBJECT, |
|
}; |
|
|
|
//================================================== |
|
// AntlionGuardConditions |
|
//================================================== |
|
|
|
enum |
|
{ |
|
COND_ANTLIONGUARD_PHYSICS_TARGET = LAST_SHARED_CONDITION, |
|
COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID, |
|
COND_ANTLIONGUARD_HAS_CHARGE_TARGET, |
|
COND_ANTLIONGUARD_CAN_SUMMON, |
|
COND_ANTLIONGUARD_CAN_CHARGE |
|
}; |
|
|
|
enum |
|
{ |
|
SQUAD_SLOT_ANTLIONGUARD_CHARGE = LAST_SHARED_SQUADSLOT, |
|
}; |
|
|
|
//================================================== |
|
// AntlionGuard Activities |
|
//================================================== |
|
|
|
Activity ACT_ANTLIONGUARD_SEARCH; |
|
Activity ACT_ANTLIONGUARD_PEEK_FLINCH; |
|
Activity ACT_ANTLIONGUARD_PEEK_ENTER; |
|
Activity ACT_ANTLIONGUARD_PEEK_EXIT; |
|
Activity ACT_ANTLIONGUARD_PEEK1; |
|
Activity ACT_ANTLIONGUARD_BARK; |
|
Activity ACT_ANTLIONGUARD_PEEK_SIGHTED; |
|
Activity ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT; |
|
Activity ACT_ANTLIONGUARD_FLINCH_LIGHT; |
|
Activity ACT_ANTLIONGUARD_UNBURROW; |
|
Activity ACT_ANTLIONGUARD_ROAR; |
|
Activity ACT_ANTLIONGUARD_RUN_HURT; |
|
|
|
// Flinches |
|
Activity ACT_ANTLIONGUARD_PHYSHIT_FR; |
|
Activity ACT_ANTLIONGUARD_PHYSHIT_FL; |
|
Activity ACT_ANTLIONGUARD_PHYSHIT_RR; |
|
Activity ACT_ANTLIONGUARD_PHYSHIT_RL; |
|
|
|
// Charge |
|
Activity ACT_ANTLIONGUARD_CHARGE_START; |
|
Activity ACT_ANTLIONGUARD_CHARGE_CANCEL; |
|
Activity ACT_ANTLIONGUARD_CHARGE_RUN; |
|
Activity ACT_ANTLIONGUARD_CHARGE_CRASH; |
|
Activity ACT_ANTLIONGUARD_CHARGE_STOP; |
|
Activity ACT_ANTLIONGUARD_CHARGE_HIT; |
|
Activity ACT_ANTLIONGUARD_CHARGE_ANTICIPATION; |
|
|
|
// Anim events |
|
int AE_ANTLIONGUARD_CHARGE_HIT; |
|
int AE_ANTLIONGUARD_SHOVE_PHYSOBJECT; |
|
int AE_ANTLIONGUARD_SHOVE; |
|
int AE_ANTLIONGUARD_FOOTSTEP_LIGHT; |
|
int AE_ANTLIONGUARD_FOOTSTEP_HEAVY; |
|
int AE_ANTLIONGUARD_CHARGE_EARLYOUT; |
|
int AE_ANTLIONGUARD_VOICE_GROWL; |
|
int AE_ANTLIONGUARD_VOICE_BARK; |
|
int AE_ANTLIONGUARD_VOICE_PAIN; |
|
int AE_ANTLIONGUARD_VOICE_SQUEEZE; |
|
int AE_ANTLIONGUARD_VOICE_SCRATCH; |
|
int AE_ANTLIONGUARD_VOICE_GRUNT; |
|
int AE_ANTLIONGUARD_VOICE_ROAR; |
|
int AE_ANTLIONGUARD_BURROW_OUT; |
|
|
|
struct PhysicsObjectCriteria_t |
|
{ |
|
CBaseEntity *pTarget; |
|
Vector vecCenter; // Center point to look around |
|
float flRadius; // Radius to search within |
|
float flTargetCone; |
|
bool bPreferObjectsAlongTargetVector; // Prefer objects that we can strike easily as we move towards our target |
|
float flNearRadius; // If we won't hit the player with the object, but get this close, throw anyway |
|
}; |
|
|
|
#define MAX_FAILED_PHYSOBJECTS 8 |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
class CNPC_AntlionGuard : public CAI_BlendedNPC |
|
{ |
|
public: |
|
DECLARE_CLASS( CNPC_AntlionGuard, CAI_BlendedNPC ); |
|
DECLARE_SERVERCLASS(); |
|
DECLARE_DATADESC(); |
|
|
|
CNPC_AntlionGuard( void ); |
|
|
|
Class_T Classify( void ) { return CLASS_ANTLION; } |
|
virtual int GetSoundInterests( void ) { return (SOUND_WORLD|SOUND_COMBAT|SOUND_PLAYER|SOUND_DANGER); } |
|
virtual bool QueryHearSound( CSound *pSound ); |
|
|
|
const impactdamagetable_t &GetPhysicsImpactDamageTable( void ); |
|
|
|
virtual int MeleeAttack1Conditions( float flDot, float flDist ); |
|
virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ); |
|
|
|
virtual int TranslateSchedule( int scheduleType ); |
|
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info ); |
|
virtual void DeathSound( const CTakeDamageInfo &info ); |
|
virtual void Event_Killed( const CTakeDamageInfo &info ); |
|
virtual int SelectSchedule( void ); |
|
|
|
virtual float GetAutoAimRadius() { return 36.0f; } |
|
|
|
virtual void Precache( void ); |
|
virtual void Spawn( void ); |
|
virtual void Activate( void ); |
|
virtual void HandleAnimEvent( animevent_t *pEvent ); |
|
virtual void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); } |
|
virtual void PrescheduleThink( void ); |
|
virtual void GatherConditions( void ); |
|
virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ); |
|
virtual void StartTask( const Task_t *pTask ); |
|
virtual void RunTask( const Task_t *pTask ); |
|
virtual void StopLoopingSounds(); |
|
virtual bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender ); |
|
|
|
// Input handlers. |
|
void InputSetShoveTarget( inputdata_t &inputdata ); |
|
void InputSetChargeTarget( inputdata_t &inputdata ); |
|
void InputClearChargeTarget( inputdata_t &inputdata ); |
|
void InputUnburrow( inputdata_t &inputdata ); |
|
void InputRagdoll( inputdata_t &inputdata ); |
|
void InputEnableBark( inputdata_t &inputdata ); |
|
void InputDisableBark( inputdata_t &inputdata ); |
|
void InputSummonedAntlionDied( inputdata_t &inputdata ); |
|
void InputEnablePreferPhysicsAttack( inputdata_t &inputdata ); |
|
void InputDisablePreferPhysicsAttack( inputdata_t &inputdata ); |
|
|
|
virtual bool IsLightDamage( const CTakeDamageInfo &info ); |
|
virtual bool IsHeavyDamage( const CTakeDamageInfo &info ); |
|
virtual bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ); |
|
virtual bool BecomeRagdollOnClient( const Vector &force ); |
|
virtual void UpdateOnRemove( void ); |
|
virtual bool IsUnreachable( CBaseEntity* pEntity ); // Is entity is unreachable? |
|
|
|
virtual float MaxYawSpeed( void ); |
|
virtual bool OverrideMove( float flInterval ); |
|
virtual bool CanBecomeRagdoll( void ); |
|
|
|
virtual bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity ); |
|
|
|
virtual Activity NPC_TranslateActivity( Activity baseAct ); |
|
|
|
#if HL2_EPISODIC |
|
//--------------------------------- |
|
// Navigation & Movement -- prevent stopping paths for the guard |
|
//--------------------------------- |
|
class CNavigator : public CAI_ComponentWithOuter<CNPC_AntlionGuard, CAI_Navigator> |
|
{ |
|
typedef CAI_ComponentWithOuter<CNPC_AntlionGuard, CAI_Navigator> BaseClass; |
|
public: |
|
CNavigator( CNPC_AntlionGuard *pOuter ) |
|
: BaseClass( pOuter ) |
|
{ |
|
} |
|
|
|
bool GetStoppingPath( CAI_WaypointList *pClippedWaypoints ); |
|
}; |
|
CAI_Navigator * CreateNavigator() { return new CNavigator( this ); } |
|
#endif |
|
|
|
DEFINE_CUSTOM_AI; |
|
|
|
private: |
|
|
|
inline bool CanStandAtPoint( const Vector &vecPos, Vector *pOut ); |
|
bool RememberFailedPhysicsTarget( CBaseEntity *pTarget ); |
|
void GetPhysicsShoveDir( CBaseEntity *pObject, float flMass, Vector *pOut ); |
|
void CreateGlow( CSprite **pSprite, const char *pAttachName ); |
|
void DestroyGlows( void ); |
|
void Footstep( bool bHeavy ); |
|
int SelectCombatSchedule( void ); |
|
int SelectUnreachableSchedule( void ); |
|
bool CanSummon( bool bIgnoreTime ); |
|
void SummonAntlions( void ); |
|
|
|
void ChargeLookAhead( void ); |
|
bool EnemyIsRightInFrontOfMe( CBaseEntity **pEntity ); |
|
bool HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity ); |
|
bool ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel ); |
|
bool ShouldWatchEnemy( void ); |
|
|
|
void ImpactShock( const Vector &origin, float radius, float magnitude, CBaseEntity *pIgnored = NULL ); |
|
void BuildScheduleTestBits( void ); |
|
void Shove( void ); |
|
void FoundEnemy( void ); |
|
void LostEnemy( void ); |
|
void UpdateHead( void ); |
|
void UpdatePhysicsTarget( bool bPreferObjectsAlongTargetVector, float flRadius = ANTLIONGUARD_FARTHEST_PHYSICS_OBJECT ); |
|
void MaintainPhysicsTarget( void ); |
|
void ChargeDamage( CBaseEntity *pTarget ); |
|
void StartSounds( void ); |
|
void SetHeavyDamageAnim( const Vector &vecSource ); |
|
float ChargeSteer( void ); |
|
CBaseEntity *FindPhysicsObjectTarget( const PhysicsObjectCriteria_t &criteria ); |
|
Vector GetPhysicsHitPosition( CBaseEntity *pObject, CBaseEntity *pTarget, Vector *vecTrajectory, float *flClearDistance ); |
|
bool CanStandAtShoveTarget( CBaseEntity *pShoveObject, CBaseEntity *pTarget, Vector *pOut ); |
|
CBaseEntity *GetNextShoveTarget( CBaseEntity *pLastEntity, AISightIter_t &iter ); |
|
|
|
int m_nFlinchActivity; |
|
|
|
bool m_bStopped; |
|
bool m_bIsBurrowed; |
|
bool m_bBarkEnabled; |
|
float m_flNextSummonTime; |
|
int m_iNumLiveAntlions; |
|
|
|
float m_flSearchNoiseTime; |
|
float m_flAngerNoiseTime; |
|
float m_flBreathTime; |
|
float m_flChargeTime; |
|
float m_flPhysicsCheckTime; |
|
float m_flNextHeavyFlinchTime; |
|
float m_flNextRoarTime; |
|
int m_iChargeMisses; |
|
bool m_bDecidedNotToStop; |
|
bool m_bPreferPhysicsAttack; |
|
|
|
CNetworkVar( bool, m_bCavernBreed ); // If this guard is meant to be a cavern dweller (uses different assets) |
|
CNetworkVar( bool, m_bInCavern ); // Behavioral hint telling the guard to change his behavior |
|
|
|
Vector m_vecPhysicsTargetStartPos; |
|
Vector m_vecPhysicsHitPosition; |
|
|
|
EHANDLE m_hShoveTarget; |
|
EHANDLE m_hChargeTarget; |
|
EHANDLE m_hChargeTargetPosition; |
|
EHANDLE m_hOldTarget; |
|
EHANDLE m_hPhysicsTarget; |
|
|
|
CUtlVectorFixed<EHANDLE, MAX_FAILED_PHYSOBJECTS> m_FailedPhysicsTargets; |
|
|
|
COutputEvent m_OnSummon; |
|
|
|
CSoundPatch *m_pGrowlHighSound; |
|
CSoundPatch *m_pGrowlLowSound; |
|
CSoundPatch *m_pGrowlIdleSound; |
|
CSoundPatch *m_pBreathSound; |
|
CSoundPatch *m_pConfusedSound; |
|
|
|
string_t m_iszPhysicsPropClass; |
|
string_t m_strShoveTargets; |
|
|
|
CSprite *m_hCaveGlow[2]; |
|
|
|
#if ANTLIONGUARD_BLOOD_EFFECTS |
|
CNetworkVar( uint8, m_iBleedingLevel ); |
|
|
|
unsigned char GetBleedingLevel( void ) const; |
|
#endif |
|
protected: |
|
|
|
int m_poseThrow; |
|
int m_poseHead_Yaw, m_poseHead_Pitch; |
|
virtual void PopulatePoseParameters( void ); |
|
|
|
|
|
// inline accessors |
|
public: |
|
inline bool IsCavernBreed( void ) const { return m_bCavernBreed; } |
|
inline bool IsInCavern( void ) const { return m_bInCavern; } |
|
}; |
|
|
|
//================================================== |
|
// CNPC_AntlionGuard::m_DataDesc |
|
//================================================== |
|
|
|
BEGIN_DATADESC( CNPC_AntlionGuard ) |
|
|
|
DEFINE_FIELD( m_nFlinchActivity, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_bStopped, FIELD_BOOLEAN ), |
|
DEFINE_KEYFIELD( m_bIsBurrowed, FIELD_BOOLEAN, "startburrowed" ), |
|
DEFINE_KEYFIELD( m_bBarkEnabled, FIELD_BOOLEAN, "allowbark" ), |
|
DEFINE_FIELD( m_flNextSummonTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_iNumLiveAntlions, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_flSearchNoiseTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flAngerNoiseTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flBreathTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flChargeTime, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( m_hShoveTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hChargeTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hChargeTargetPosition, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hOldTarget, FIELD_EHANDLE ), |
|
// m_FailedPhysicsTargets // We do not save/load these |
|
|
|
DEFINE_FIELD( m_hPhysicsTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_vecPhysicsTargetStartPos, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vecPhysicsHitPosition, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_flPhysicsCheckTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flNextHeavyFlinchTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flNextRoarTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_iChargeMisses, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_bDecidedNotToStop, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bPreferPhysicsAttack, FIELD_BOOLEAN ), |
|
|
|
#if ANTLIONGUARD_BLOOD_EFFECTS |
|
DEFINE_FIELD( m_iBleedingLevel, FIELD_CHARACTER ), |
|
#endif |
|
|
|
DEFINE_KEYFIELD( m_bCavernBreed,FIELD_BOOLEAN, "cavernbreed" ), |
|
DEFINE_KEYFIELD( m_bInCavern, FIELD_BOOLEAN, "incavern" ), |
|
DEFINE_KEYFIELD( m_strShoveTargets, FIELD_STRING, "shovetargets" ), |
|
|
|
DEFINE_AUTO_ARRAY( m_hCaveGlow, FIELD_CLASSPTR ), |
|
|
|
DEFINE_OUTPUT( m_OnSummon, "OnSummon" ), |
|
|
|
DEFINE_SOUNDPATCH( m_pGrowlHighSound ), |
|
DEFINE_SOUNDPATCH( m_pGrowlLowSound ), |
|
DEFINE_SOUNDPATCH( m_pGrowlIdleSound ), |
|
DEFINE_SOUNDPATCH( m_pBreathSound ), |
|
DEFINE_SOUNDPATCH( m_pConfusedSound ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetShoveTarget", InputSetShoveTarget ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetChargeTarget", InputSetChargeTarget ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearChargeTarget", InputClearChargeTarget ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Unburrow", InputUnburrow ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Ragdoll", InputRagdoll ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableBark", InputEnableBark ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableBark", InputDisableBark ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "SummonedAntlionDied", InputSummonedAntlionDied ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnablePreferPhysicsAttack", InputEnablePreferPhysicsAttack ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisablePreferPhysicsAttack", InputDisablePreferPhysicsAttack ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
//Fast Growl (Growl High) |
|
envelopePoint_t envAntlionGuardFastGrowl[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.2f, 0.4f, |
|
}, |
|
{ 0.1f, 0.1f, |
|
0.8f, 1.0f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.4f, 0.8f, |
|
}, |
|
}; |
|
|
|
|
|
//Bark 1 (Growl High) |
|
envelopePoint_t envAntlionGuardBark1[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.2f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.4f, 0.6f, |
|
}, |
|
}; |
|
|
|
//Bark 2 (Confused) |
|
envelopePoint_t envAntlionGuardBark2[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.2f, |
|
}, |
|
{ 0.2f, 0.3f, |
|
0.1f, 0.2f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.4f, 0.6f, |
|
}, |
|
}; |
|
|
|
//Pain |
|
envelopePoint_t envAntlionGuardPain1[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.2f, |
|
}, |
|
{ -1.0f, -1.0f, |
|
0.5f, 0.8f, |
|
}, |
|
{ 0.1f, 0.2f, |
|
0.1f, 0.2f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.5f, 0.75f, |
|
}, |
|
}; |
|
|
|
//Squeeze (High Growl) |
|
envelopePoint_t envAntlionGuardSqueeze[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.1f, 0.2f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
1.0f, 1.5f, |
|
}, |
|
}; |
|
|
|
//Scratch (Low Growl) |
|
envelopePoint_t envAntlionGuardScratch[] = |
|
{ |
|
{ 1.0f, 1.0f, |
|
0.4f, 0.6f, |
|
}, |
|
{ 0.5f, 0.5f, |
|
0.4f, 0.6f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
1.0f, 1.5f, |
|
}, |
|
}; |
|
|
|
//Grunt |
|
envelopePoint_t envAntlionGuardGrunt[] = |
|
{ |
|
{ 0.6f, 1.0f, |
|
0.1f, 0.2f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.1f, 0.2f, |
|
}, |
|
}; |
|
|
|
envelopePoint_t envAntlionGuardGrunt2[] = |
|
{ |
|
{ 0.2f, 0.4f, |
|
0.1f, 0.2f, |
|
}, |
|
{ 0.0f, 0.0f, |
|
0.4f, 0.6f, |
|
}, |
|
}; |
|
|
|
//============================================================================================== |
|
// ANTLION GUARD PHYSICS DAMAGE TABLE |
|
//============================================================================================== |
|
static impactentry_t antlionGuardLinearTable[] = |
|
{ |
|
{ 100*100, 10 }, |
|
{ 250*250, 25 }, |
|
{ 350*350, 50 }, |
|
{ 500*500, 75 }, |
|
{ 1000*1000,100 }, |
|
}; |
|
|
|
static impactentry_t antlionGuardAngularTable[] = |
|
{ |
|
{ 50* 50, 10 }, |
|
{ 100*100, 25 }, |
|
{ 150*150, 50 }, |
|
{ 200*200, 75 }, |
|
}; |
|
|
|
impactdamagetable_t gAntlionGuardImpactDamageTable = |
|
{ |
|
antlionGuardLinearTable, |
|
antlionGuardAngularTable, |
|
|
|
ARRAYSIZE(antlionGuardLinearTable), |
|
ARRAYSIZE(antlionGuardAngularTable), |
|
|
|
200*200,// minimum linear speed squared |
|
180*180,// minimum angular speed squared (360 deg/s to cause spin/slice damage) |
|
15, // can't take damage from anything under 15kg |
|
|
|
10, // anything less than 10kg is "small" |
|
5, // never take more than 1 pt of damage from anything under 15kg |
|
128*128,// <15kg objects must go faster than 36 in/s to do damage |
|
|
|
45, // large mass in kg |
|
2, // large mass scale (anything over 500kg does 4X as much energy to read from damage table) |
|
1, // large mass falling scale |
|
0, // my min velocity |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : const impactdamagetable_t |
|
//----------------------------------------------------------------------------- |
|
const impactdamagetable_t &CNPC_AntlionGuard::GetPhysicsImpactDamageTable( void ) |
|
{ |
|
return gAntlionGuardImpactDamageTable; |
|
} |
|
|
|
//================================================== |
|
// CNPC_AntlionGuard |
|
//================================================== |
|
|
|
CNPC_AntlionGuard::CNPC_AntlionGuard( void ) |
|
{ |
|
m_bCavernBreed = false; |
|
m_bInCavern = false; |
|
|
|
m_iszPhysicsPropClass = AllocPooledString( "prop_physics" ); |
|
} |
|
|
|
LINK_ENTITY_TO_CLASS( npc_antlionguard, CNPC_AntlionGuard ); |
|
|
|
IMPLEMENT_SERVERCLASS_ST(CNPC_AntlionGuard, DT_NPC_AntlionGuard) |
|
SendPropBool( SENDINFO( m_bCavernBreed ) ), |
|
SendPropBool( SENDINFO( m_bInCavern ) ), |
|
|
|
#if ANTLIONGUARD_BLOOD_EFFECTS |
|
SendPropInt( SENDINFO( m_iBleedingLevel ), 2, SPROP_UNSIGNED ), |
|
#endif |
|
END_SEND_TABLE() |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::UpdateOnRemove( void ) |
|
{ |
|
DestroyGlows(); |
|
|
|
// Chain to the base class |
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::Precache( void ) |
|
{ |
|
PrecacheModel( ANTLIONGUARD_MODEL ); |
|
|
|
PrecacheScriptSound( "NPC_AntlionGuard.Shove" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.HitHard" ); |
|
|
|
if ( HasSpawnFlags(SF_ANTLIONGUARD_INSIDE_FOOTSTEPS) ) |
|
{ |
|
PrecacheScriptSound( "NPC_AntlionGuard.Inside.StepLight" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.Inside.StepHeavy" ); |
|
} |
|
else |
|
{ |
|
PrecacheScriptSound( "NPC_AntlionGuard.StepLight" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.StepHeavy" ); |
|
} |
|
|
|
#if HL2_EPISODIC |
|
PrecacheScriptSound( "NPC_AntlionGuard.NearStepLight" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.NearStepHeavy" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.FarStepLight" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.FarStepHeavy" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.BreatheLoop" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.ShellCrack" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.Pain_Roar" ); |
|
PrecacheModel( "sprites/grubflare1.vmt" ); |
|
#endif // HL2_EPISODIC |
|
|
|
PrecacheScriptSound( "NPC_AntlionGuard.Anger" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.Roar" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.Die" ); |
|
|
|
PrecacheScriptSound( "NPC_AntlionGuard.GrowlHigh" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.GrowlIdle" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.BreathSound" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.Confused" ); |
|
PrecacheScriptSound( "NPC_AntlionGuard.Fallover" ); |
|
|
|
PrecacheScriptSound( "NPC_AntlionGuard.FrustratedRoar" ); |
|
|
|
PrecacheParticleSystem( "blood_antlionguard_injured_light" ); |
|
PrecacheParticleSystem( "blood_antlionguard_injured_heavy" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::DestroyGlows( void ) |
|
{ |
|
if ( m_hCaveGlow[0] ) |
|
{ |
|
UTIL_Remove( m_hCaveGlow[0] ); |
|
|
|
// reset it to NULL in case there is a double death cleanup for some reason. |
|
m_hCaveGlow[0] = NULL; |
|
} |
|
|
|
if ( m_hCaveGlow[1] ) |
|
{ |
|
UTIL_Remove( m_hCaveGlow[1] ); |
|
|
|
// reset it to NULL in case there is a double death cleanup for some reason. |
|
m_hCaveGlow[1] = NULL; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::CreateGlow( CSprite **pSprite, const char *pAttachName ) |
|
{ |
|
if ( pSprite == NULL ) |
|
return; |
|
|
|
// Create the glow sprite |
|
*pSprite = CSprite::SpriteCreate( "sprites/grubflare1.vmt", GetLocalOrigin(), false ); |
|
Assert( *pSprite ); |
|
if ( *pSprite == NULL ) |
|
return; |
|
|
|
(*pSprite)->TurnOn(); |
|
(*pSprite)->SetTransparency( kRenderWorldGlow, 156, 169, 121, 164, kRenderFxNoDissipation ); |
|
(*pSprite)->SetScale( 1.0f ); |
|
(*pSprite)->SetGlowProxySize( 16.0f ); |
|
int nAttachment = LookupAttachment( pAttachName ); |
|
(*pSprite)->SetParent( this, nAttachment ); |
|
(*pSprite)->SetLocalOrigin( vec3_origin ); |
|
|
|
// Don't uselessly animate, we're a static sprite! |
|
(*pSprite)->SetThink( NULL ); |
|
(*pSprite)->SetNextThink( TICK_NEVER_THINK ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
SetModel( ANTLIONGUARD_MODEL ); |
|
|
|
// Switch our skin (for now), if we're the cavern guard |
|
if ( m_bCavernBreed ) |
|
{ |
|
m_nSkin = 1; |
|
|
|
// Add glows |
|
CreateGlow( &(m_hCaveGlow[0]), "attach_glow1" ); |
|
CreateGlow( &(m_hCaveGlow[1]), "attach_glow2" ); |
|
} |
|
else |
|
{ |
|
m_hCaveGlow[0] = NULL; |
|
m_hCaveGlow[1] = NULL; |
|
} |
|
|
|
SetHullType( HULL_LARGE ); |
|
SetHullSizeNormal(); |
|
SetDefaultEyeOffset(); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
SetMoveType( MOVETYPE_STEP ); |
|
|
|
SetNavType( NAV_GROUND ); |
|
SetBloodColor( BLOOD_COLOR_YELLOW ); |
|
|
|
m_iHealth = sk_antlionguard_health.GetFloat(); |
|
m_iMaxHealth = m_iHealth; |
|
m_flFieldOfView = ANTLIONGUARD_FOV_NORMAL; |
|
|
|
m_flPhysicsCheckTime = 0; |
|
m_flChargeTime = 0; |
|
m_flNextRoarTime = 0; |
|
m_flNextSummonTime = 0; |
|
m_iNumLiveAntlions = 0; |
|
m_iChargeMisses = 0; |
|
m_flNextHeavyFlinchTime = 0; |
|
m_bDecidedNotToStop = false; |
|
|
|
ClearHintGroup(); |
|
|
|
m_bStopped = false; |
|
|
|
m_hShoveTarget = NULL; |
|
m_hChargeTarget = NULL; |
|
m_hChargeTargetPosition = NULL; |
|
m_hPhysicsTarget = NULL; |
|
|
|
m_HackedGunPos.x = 10; |
|
m_HackedGunPos.y = 0; |
|
m_HackedGunPos.z = 30; |
|
|
|
CapabilitiesClear(); |
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_SQUAD ); |
|
CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK ); |
|
|
|
NPCInit(); |
|
|
|
BaseClass::Spawn(); |
|
|
|
//See if we're supposed to start burrowed |
|
if ( m_bIsBurrowed ) |
|
{ |
|
AddEffects( EF_NODRAW ); |
|
AddFlag( FL_NOTARGET ); |
|
|
|
m_spawnflags |= SF_NPC_GAG; |
|
|
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
m_takedamage = DAMAGE_NO; |
|
|
|
if ( m_hCaveGlow[0] ) |
|
m_hCaveGlow[0]->TurnOff(); |
|
|
|
if ( m_hCaveGlow[1] ) |
|
m_hCaveGlow[1]->TurnOff(); |
|
} |
|
|
|
// Do not dissolve |
|
AddEFlags( EFL_NO_DISSOLVE ); |
|
|
|
// We get a minute of free knowledge about the target |
|
GetEnemies()->SetEnemyDiscardTime( 120.0f ); |
|
GetEnemies()->SetFreeKnowledgeDuration( 60.0f ); |
|
|
|
// We need to bloat the absbox to encompass all the hitboxes |
|
Vector absMin = -Vector(100,100,0); |
|
Vector absMax = Vector(100,100,128); |
|
|
|
CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &absMin, &absMax ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
// Find all nearby physics objects and add them to the list of objects we will sense |
|
CBaseEntity *pObject = NULL; |
|
while ( ( pObject = gEntList.FindEntityInSphere( pObject, WorldSpaceCenter(), 2500 ) ) != NULL ) |
|
{ |
|
// Can't throw around debris |
|
if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) |
|
continue; |
|
|
|
// We can only throw a few types of things |
|
if ( !FClassnameIs( pObject, "prop_physics" ) && !FClassnameIs( pObject, "func_physbox" ) ) |
|
continue; |
|
|
|
// Ensure it's mass is within range |
|
IPhysicsObject *pPhysObj = pObject->VPhysicsGetObject(); |
|
if( ( pPhysObj == NULL ) || ( pPhysObj->GetMass() > ANTLIONGUARD_MAX_OBJECT_MASS ) || ( pPhysObj->GetMass() < ANTLIONGUARD_MIN_OBJECT_MASS ) ) |
|
continue; |
|
|
|
// Tell the AI sensing list that we want to consider this |
|
g_AI_SensedObjectsManager.AddEntity( pObject ); |
|
|
|
if ( g_debug_antlionguard.GetInt() == 5 ) |
|
{ |
|
Msg("Antlion Guard: Added prop with model '%s' to sense list.\n", STRING(pObject->GetModelName()) ); |
|
pObject->m_debugOverlays |= OVERLAY_BBOX_BIT; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if the guard's allowed to summon antlions |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::CanSummon( bool bIgnoreTime ) |
|
{ |
|
if ( !m_bBarkEnabled ) |
|
return false; |
|
|
|
if ( !bIgnoreTime && m_flNextSummonTime > gpGlobals->curtime ) |
|
return false; |
|
|
|
// Hit the max number of them allowed? Only summon when we're 2 down. |
|
if ( m_iNumLiveAntlions >= MAX(1, ANTLIONGUARD_SUMMON_COUNT-1) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Our enemy is unreachable. Select a schedule. |
|
//----------------------------------------------------------------------------- |
|
int CNPC_AntlionGuard::SelectUnreachableSchedule( void ) |
|
{ |
|
// If we're in the cavern setting, we opt out of this |
|
if ( m_bInCavern ) |
|
return SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE; |
|
// Summon antlions if we can |
|
if ( HasCondition( COND_ANTLIONGUARD_CAN_SUMMON ) ) |
|
return SCHED_ANTLIONGUARD_SUMMON; |
|
|
|
// First, look for objects near ourselves |
|
PhysicsObjectCriteria_t criteria; |
|
criteria.bPreferObjectsAlongTargetVector = false; |
|
criteria.flNearRadius = (40*12); |
|
criteria.flRadius = (250*12); |
|
criteria.flTargetCone = -1.0f; |
|
criteria.pTarget = GetEnemy(); |
|
criteria.vecCenter = GetAbsOrigin(); |
|
|
|
CBaseEntity *pTarget = FindPhysicsObjectTarget( criteria ); |
|
if ( pTarget == NULL && GetEnemy() ) |
|
{ |
|
// Use the same criteria, but search near the target instead of us |
|
criteria.vecCenter = GetEnemy()->GetAbsOrigin(); |
|
pTarget = FindPhysicsObjectTarget( criteria ); |
|
} |
|
|
|
// If we found one, we'll want to attack it |
|
if ( pTarget ) |
|
{ |
|
m_hPhysicsTarget = pTarget; |
|
SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
m_vecPhysicsTargetStartPos = m_hPhysicsTarget->WorldSpaceCenter(); |
|
|
|
// Tell any squadmates I'm going for this item so they don't as well |
|
if ( GetSquad() != NULL ) |
|
{ |
|
GetSquad()->BroadcastInteraction( g_interactionAntlionGuardFoundPhysicsObject, (void *)((CBaseEntity *)m_hPhysicsTarget), this ); |
|
} |
|
|
|
return SCHED_ANTLIONGUARD_PHYSICS_ATTACK; |
|
} |
|
|
|
// Deal with a visible player |
|
if ( HasCondition( COND_SEE_ENEMY ) ) |
|
{ |
|
// Roar at the player as show of frustration |
|
if ( m_flNextRoarTime < gpGlobals->curtime ) |
|
{ |
|
m_flNextRoarTime = gpGlobals->curtime + RandomFloat( 20,40 ); |
|
return SCHED_ANTLIONGUARD_ROAR; |
|
} |
|
|
|
// If we're under attack, then let's leave for a bit |
|
if ( GetEnemy() && HasCondition( COND_HEAVY_DAMAGE ) ) |
|
{ |
|
Vector dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); |
|
VectorNormalize(dir); |
|
|
|
GetMotor()->SetIdealYaw( -dir ); |
|
|
|
return SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY; |
|
} |
|
} |
|
|
|
// If we're too far away, try to close distance to our target first |
|
float flDistToEnemySqr = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); |
|
if ( flDistToEnemySqr > Square( 100*12 ) ) |
|
return SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE; |
|
|
|
// Fire that we're unable to reach our target! |
|
if ( GetEnemy() && GetEnemy()->IsPlayer() ) |
|
{ |
|
m_OnLostPlayer.FireOutput( this, this ); |
|
} |
|
|
|
m_OnLostEnemy.FireOutput( this, this ); |
|
GetEnemies()->MarkAsEluded( GetEnemy() ); |
|
|
|
// Move randomly for the moment |
|
return SCHED_ANTLIONGUARD_CANT_ATTACK; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CNPC_AntlionGuard::SelectCombatSchedule( void ) |
|
{ |
|
ClearHintGroup(); |
|
|
|
/* |
|
bool bCanCharge = false; |
|
if ( HasCondition( COND_SEE_ENEMY ) ) |
|
{ |
|
bCanCharge = ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ); |
|
} |
|
*/ |
|
|
|
// Attack if we can |
|
if ( HasCondition(COND_CAN_MELEE_ATTACK1) ) |
|
return SCHED_MELEE_ATTACK1; |
|
|
|
// Otherwise, summon antlions |
|
if ( HasCondition(COND_ANTLIONGUARD_CAN_SUMMON) ) |
|
{ |
|
// If I can charge, and have antlions, charge instead |
|
if ( HasCondition( COND_ANTLIONGUARD_CAN_CHARGE ) && m_iNumLiveAntlions ) |
|
return SCHED_ANTLIONGUARD_CHARGE; |
|
|
|
return SCHED_ANTLIONGUARD_SUMMON; |
|
} |
|
|
|
// See if we can bark |
|
if ( HasCondition( COND_ENEMY_UNREACHABLE ) ) |
|
return SelectUnreachableSchedule(); |
|
|
|
//Physics target |
|
if ( HasCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ) && !m_bInCavern ) |
|
return SCHED_ANTLIONGUARD_PHYSICS_ATTACK; |
|
|
|
// If we've missed a couple of times, and we can summon, make it harder |
|
if ( m_iChargeMisses >= 2 && CanSummon(true) ) |
|
{ |
|
m_iChargeMisses--; |
|
return SCHED_ANTLIONGUARD_SUMMON; |
|
} |
|
|
|
// Charging |
|
if ( HasCondition( COND_ANTLIONGUARD_CAN_CHARGE ) ) |
|
{ |
|
// Don't let other squad members charge while we're doing it |
|
OccupyStrategySlot( SQUAD_SLOT_ANTLIONGUARD_CHARGE ); |
|
return SCHED_ANTLIONGUARD_CHARGE; |
|
} |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel ) |
|
{ |
|
// Don't charge in tight spaces unless forced to |
|
if ( hl2_episodic.GetBool() && m_bInCavern && !(m_hChargeTarget.Get() && m_hChargeTarget->IsAlive()) ) |
|
return false; |
|
|
|
// Must have a target |
|
if ( !GetEnemy() ) |
|
return false; |
|
|
|
// No one else in the squad can be charging already |
|
if ( IsStrategySlotRangeOccupied( SQUAD_SLOT_ANTLIONGUARD_CHARGE, SQUAD_SLOT_ANTLIONGUARD_CHARGE ) ) |
|
return false; |
|
|
|
// Don't check the distance once we start charging |
|
if ( !bCheckForCancel ) |
|
{ |
|
// Don't allow use to charge again if it's been too soon |
|
if ( useTime && ( m_flChargeTime > gpGlobals->curtime ) ) |
|
return false; |
|
|
|
float distance = UTIL_DistApprox2D( startPos, endPos ); |
|
|
|
// Must be within our tolerance range |
|
if ( ( distance < ANTLIONGUARD_CHARGE_MIN ) || ( distance > ANTLIONGUARD_CHARGE_MAX ) ) |
|
return false; |
|
} |
|
|
|
if ( GetSquad() ) |
|
{ |
|
// If someone in our squad is closer to the enemy, then don't charge (we end up hitting them more often than not!) |
|
float flOurDistToEnemySqr = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr(); |
|
AISquadIter_t iter; |
|
for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) ) |
|
{ |
|
if ( pSquadMember->IsAlive() == false || pSquadMember == this ) |
|
continue; |
|
|
|
if ( ( pSquadMember->GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr() < flOurDistToEnemySqr ) |
|
return false; |
|
} |
|
} |
|
|
|
//FIXME: We'd like to exclude small physics objects from this check! |
|
|
|
// We only need to hit the endpos with the edge of our bounding box |
|
Vector vecDir = endPos - startPos; |
|
VectorNormalize( vecDir ); |
|
float flWidth = WorldAlignSize().x * 0.5; |
|
Vector vecTargetPos = endPos - (vecDir * flWidth); |
|
|
|
// See if we can directly move there |
|
AIMoveTrace_t moveTrace; |
|
GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace ); |
|
|
|
// Draw the probe |
|
if ( g_debug_antlionguard.GetInt() == 1 ) |
|
{ |
|
Vector enemyDir = (vecTargetPos - startPos); |
|
float enemyDist = VectorNormalize( enemyDir ); |
|
|
|
NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f ); |
|
} |
|
|
|
// If we're not blocked, charge |
|
if ( IsMoveBlocked( moveTrace ) ) |
|
{ |
|
// Don't allow it if it's too close to us |
|
if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < ANTLIONGUARD_CHARGE_MIN ) |
|
return false; |
|
|
|
// Allow some special cases to not block us |
|
if ( moveTrace.pObstruction != NULL ) |
|
{ |
|
// If we've hit the world, see if it's a cliff |
|
if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) ) |
|
{ |
|
// Can't be too far above/below the target |
|
if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() ) |
|
return false; |
|
|
|
// Allow it if we got pretty close |
|
if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 ) |
|
return true; |
|
} |
|
|
|
// Hit things that will take damage |
|
if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO ) |
|
return true; |
|
|
|
// Hit things that will move |
|
if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// Only update this if we've requested it |
|
if ( useTime ) |
|
{ |
|
m_flChargeTime = gpGlobals->curtime + 4.0f; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CNPC_AntlionGuard::SelectSchedule( void ) |
|
{ |
|
// Don't do anything if we're burrowed |
|
if ( m_bIsBurrowed ) |
|
return SCHED_IDLE_STAND; |
|
|
|
#if 0 |
|
// Debug physics object finding |
|
if ( 0 ) //g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
m_flPhysicsCheckTime = 0; |
|
UpdatePhysicsTarget( false ); |
|
return SCHED_IDLE_STAND; |
|
} |
|
#endif |
|
|
|
// Flinch on heavy damage, but not if we've flinched too recently. |
|
// This is done to prevent stun-locks from grenades. |
|
if ( !m_bInCavern && HasCondition( COND_HEAVY_DAMAGE ) && m_flNextHeavyFlinchTime < gpGlobals->curtime ) |
|
{ |
|
m_flNextHeavyFlinchTime = gpGlobals->curtime + 8.0f; |
|
return SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY; |
|
} |
|
|
|
// Prefer to use physics, in this case |
|
if ( m_bPreferPhysicsAttack ) |
|
{ |
|
// If we have a target, try to go for it |
|
if ( HasCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ) ) |
|
return SCHED_ANTLIONGUARD_PHYSICS_ATTACK; |
|
} |
|
|
|
// Charge after a target if it's set |
|
if ( m_hChargeTarget && m_hChargeTargetPosition ) |
|
{ |
|
ClearCondition( COND_ANTLIONGUARD_HAS_CHARGE_TARGET ); |
|
ClearHintGroup(); |
|
|
|
if ( m_hChargeTarget->IsAlive() == false ) |
|
{ |
|
m_hChargeTarget = NULL; |
|
m_hChargeTargetPosition = NULL; |
|
SetEnemy( m_hOldTarget ); |
|
|
|
if ( m_hOldTarget == NULL ) |
|
{ |
|
m_NPCState = NPC_STATE_ALERT; |
|
} |
|
} |
|
else |
|
{ |
|
m_hOldTarget = GetEnemy(); |
|
SetEnemy( m_hChargeTarget ); |
|
UpdateEnemyMemory( m_hChargeTarget, m_hChargeTarget->GetAbsOrigin() ); |
|
|
|
//If we can't see the target, run to somewhere we can |
|
if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), false, false ) == false ) |
|
return SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION; |
|
|
|
return SCHED_ANTLIONGUARD_CHARGE_TARGET; |
|
} |
|
} |
|
|
|
// See if we need to clear a path to our enemy |
|
if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_UNREACHABLE ) ) |
|
{ |
|
CBaseEntity *pBlocker = GetEnemyOccluder(); |
|
|
|
if ( ( pBlocker != NULL ) && FClassnameIs( pBlocker, "prop_physics" ) && !m_bInCavern ) |
|
{ |
|
m_hPhysicsTarget = pBlocker; |
|
return SCHED_ANTLIONGUARD_PHYSICS_ATTACK; |
|
} |
|
} |
|
|
|
//Only do these in combat states |
|
if ( m_NPCState == NPC_STATE_COMBAT && GetEnemy() ) |
|
return SelectCombatSchedule(); |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CNPC_AntlionGuard::MeleeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
// Don't attack again too soon |
|
if ( GetNextAttack() > gpGlobals->curtime ) |
|
return 0; |
|
|
|
// While charging, we can't melee attack |
|
if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) ) |
|
return 0; |
|
|
|
if ( hl2_episodic.GetBool() && m_bInCavern ) |
|
{ |
|
// Predict where they'll be and see if THAT is within range |
|
Vector vecPredPos; |
|
UTIL_PredictedPosition( GetEnemy(), 0.25f, &vecPredPos ); |
|
if ( ( GetAbsOrigin() - vecPredPos ).Length() > ANTLIONGUARD_MELEE1_RANGE ) |
|
return COND_TOO_FAR_TO_ATTACK; |
|
} |
|
else |
|
{ |
|
// Must be close enough |
|
if ( flDist > ANTLIONGUARD_MELEE1_RANGE ) |
|
return COND_TOO_FAR_TO_ATTACK; |
|
} |
|
|
|
// Must be within a viable cone |
|
if ( flDot < ANTLIONGUARD_MELEE1_CONE ) |
|
return COND_NOT_FACING_ATTACK; |
|
|
|
// If the enemy is on top of me, I'm allowed to hit the sucker |
|
if ( GetEnemy()->GetGroundEntity() == this ) |
|
return COND_CAN_MELEE_ATTACK1; |
|
|
|
trace_t tr; |
|
TraceHull_SkipPhysics( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), Vector(-10,-10,-10), Vector(10,10,10), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 ); |
|
|
|
// If we hit anything, go for it |
|
if ( tr.fraction < 1.0f ) |
|
return COND_CAN_MELEE_ATTACK1; |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CNPC_AntlionGuard::MaxYawSpeed( void ) |
|
{ |
|
Activity eActivity = GetActivity(); |
|
|
|
// Stay still |
|
if (( eActivity == ACT_ANTLIONGUARD_SEARCH ) || |
|
( eActivity == ACT_ANTLIONGUARD_PEEK_ENTER ) || |
|
( eActivity == ACT_ANTLIONGUARD_PEEK_EXIT ) || |
|
( eActivity == ACT_ANTLIONGUARD_PEEK1 ) || |
|
( eActivity == ACT_ANTLIONGUARD_BARK ) || |
|
( eActivity == ACT_ANTLIONGUARD_PEEK_SIGHTED ) || |
|
( eActivity == ACT_MELEE_ATTACK1 ) ) |
|
return 0.0f; |
|
|
|
CBaseEntity *pEnemy = GetEnemy(); |
|
|
|
if ( pEnemy != NULL && pEnemy->IsPlayer() == false ) |
|
return 16.0f; |
|
|
|
// Turn slowly when you're charging |
|
if ( eActivity == ACT_ANTLIONGUARD_CHARGE_START ) |
|
return 4.0f; |
|
|
|
if ( hl2_episodic.GetBool() && m_bInCavern ) |
|
{ |
|
// Allow a better turning rate when moving quickly but not charging the player |
|
if ( ( eActivity == ACT_ANTLIONGUARD_CHARGE_RUN ) && IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) == false ) |
|
return 16.0f; |
|
} |
|
|
|
// Turn more slowly as we close in on our target |
|
if ( eActivity == ACT_ANTLIONGUARD_CHARGE_RUN ) |
|
{ |
|
if ( pEnemy == NULL ) |
|
return 2.0f; |
|
|
|
float dist = UTIL_DistApprox2D( GetEnemy()->WorldSpaceCenter(), WorldSpaceCenter() ); |
|
|
|
if ( dist > 512 ) |
|
return 16.0f; |
|
|
|
//FIXME: Alter by skill level |
|
float yawSpeed = RemapVal( dist, 0, 512, 1.0f, 2.0f ); |
|
yawSpeed = clamp( yawSpeed, 1.0f, 2.0f ); |
|
|
|
return yawSpeed; |
|
} |
|
|
|
if ( eActivity == ACT_ANTLIONGUARD_CHARGE_STOP ) |
|
return 8.0f; |
|
|
|
switch( eActivity ) |
|
{ |
|
case ACT_TURN_LEFT: |
|
case ACT_TURN_RIGHT: |
|
return 40.0f; |
|
break; |
|
|
|
case ACT_RUN: |
|
default: |
|
return 20.0f; |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : flInterval - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::OverrideMove( float flInterval ) |
|
{ |
|
// If the guard's charging, we're handling the movement |
|
if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) ) |
|
return true; |
|
|
|
// TODO: This code increases the guard's ability to successfully hit a target, but adds a new dimension of navigation |
|
// trouble to do with him not being able to "close the distance" between himself and the object he wants to hit. |
|
// Fixing this will require some thought on how he picks the correct distances to his targets and when he's "close enough". -- jdw |
|
|
|
/* |
|
if ( m_hPhysicsTarget != NULL ) |
|
{ |
|
float flWidth = m_hPhysicsTarget->CollisionProp()->BoundingRadius2D(); |
|
GetLocalNavigator()->AddObstacle( m_hPhysicsTarget->WorldSpaceCenter(), flWidth * 0.75f, AIMST_AVOID_OBJECT ); |
|
//NDebugOverlay::Sphere( m_hPhysicsTarget->WorldSpaceCenter(), vec3_angle, flWidth, 255, 255, 255, 0, true, 0.5f ); |
|
} |
|
*/ |
|
|
|
return BaseClass::OverrideMove( flInterval ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::Shove( void ) |
|
{ |
|
if ( GetNextAttack() > gpGlobals->curtime ) |
|
return; |
|
|
|
CBaseEntity *pHurt = NULL; |
|
CBaseEntity *pTarget; |
|
|
|
pTarget = ( m_hShoveTarget == NULL ) ? GetEnemy() : m_hShoveTarget.Get(); |
|
|
|
if ( pTarget == NULL ) |
|
{ |
|
m_hShoveTarget = NULL; |
|
return; |
|
} |
|
|
|
//Always damage bullseyes if we're scripted to hit them |
|
if ( pTarget->Classify() == CLASS_BULLSEYE ) |
|
{ |
|
pTarget->TakeDamage( CTakeDamageInfo( this, this, 1.0f, DMG_CRUSH ) ); |
|
m_hShoveTarget = NULL; |
|
return; |
|
} |
|
|
|
float damage = ( pTarget->IsPlayer() ) ? sk_antlionguard_dmg_shove.GetFloat() : 250; |
|
|
|
// If the target's still inside the shove cone, ensure we hit him |
|
Vector vecForward, vecEnd; |
|
AngleVectors( GetAbsAngles(), &vecForward ); |
|
float flDistSqr = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr(); |
|
Vector2D v2LOS = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() ).AsVector2D(); |
|
Vector2DNormalize(v2LOS); |
|
float flDot = DotProduct2D (v2LOS, vecForward.AsVector2D() ); |
|
if ( flDistSqr < (ANTLIONGUARD_MELEE1_RANGE*ANTLIONGUARD_MELEE1_RANGE) && flDot >= ANTLIONGUARD_MELEE1_CONE ) |
|
{ |
|
vecEnd = pTarget->WorldSpaceCenter(); |
|
} |
|
else |
|
{ |
|
vecEnd = WorldSpaceCenter() + ( BodyDirection3D() * ANTLIONGUARD_MELEE1_RANGE ); |
|
} |
|
|
|
// Use the melee trace to ensure we hit everything there |
|
trace_t tr; |
|
CTakeDamageInfo dmgInfo( this, this, damage, DMG_SLASH ); |
|
CTraceFilterMelee traceFilter( this, COLLISION_GROUP_NONE, &dmgInfo, 1.0, true ); |
|
Ray_t ray; |
|
ray.Init( WorldSpaceCenter(), vecEnd, Vector(-16,-16,-16), Vector(16,16,16) ); |
|
enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr ); |
|
pHurt = tr.m_pEnt; |
|
|
|
// Knock things around |
|
ImpactShock( tr.endpos, 100.0f, 250.0f ); |
|
|
|
if ( pHurt ) |
|
{ |
|
Vector traceDir = ( tr.endpos - tr.startpos ); |
|
VectorNormalize( traceDir ); |
|
|
|
// Generate enough force to make a 75kg guy move away at 600 in/sec |
|
Vector vecForce = traceDir * ImpulseScale( 75, 600 ); |
|
CTakeDamageInfo info( this, this, vecForce, tr.endpos, damage, DMG_CLUB ); |
|
pHurt->TakeDamage( info ); |
|
|
|
m_hShoveTarget = NULL; |
|
|
|
EmitSound( "NPC_AntlionGuard.Shove" ); |
|
|
|
// If the player, throw him around |
|
if ( pHurt->IsPlayer() ) |
|
{ |
|
//Punch the view |
|
pHurt->ViewPunch( QAngle(20,0,-20) ); |
|
|
|
//Shake the screen |
|
UTIL_ScreenShake( pHurt->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START ); |
|
|
|
//Red damage indicator |
|
color32 red = {128,0,0,128}; |
|
UTIL_ScreenFade( pHurt, red, 1.0f, 0.1f, FFADE_IN ); |
|
|
|
Vector forward, up; |
|
AngleVectors( GetLocalAngles(), &forward, NULL, &up ); |
|
pHurt->ApplyAbsVelocityImpulse( forward * 400 + up * 150 ); |
|
|
|
// in the episodes, the cavern guard poisons the player |
|
#if HL2_EPISODIC |
|
// If I am a cavern guard attacking the player, and he still lives, then poison him too. |
|
if ( m_bInCavern && pHurt->IsPlayer() && pHurt->IsAlive() && pHurt->m_iHealth > ANTLIONGUARD_POISON_TO) |
|
{ |
|
// That didn't finish them. Take them down to one point with poison damage. It'll heal. |
|
pHurt->TakeDamage( CTakeDamageInfo( this, this, pHurt->m_iHealth - ANTLIONGUARD_POISON_TO, DMG_POISON ) ); |
|
} |
|
#endif |
|
|
|
} |
|
else |
|
{ |
|
CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pHurt ); |
|
|
|
if ( pVictim ) |
|
{ |
|
if ( NPC_Rollermine_IsRollermine( pVictim ) ) |
|
{ |
|
Pickup_OnPhysGunDrop( pVictim, NULL, LAUNCHED_BY_CANNON ); |
|
} |
|
|
|
// FIXME: This causes NPCs that are not physically motivated to hop into the air strangely -- jdw |
|
// pVictim->ApplyAbsVelocityImpulse( BodyDirection2D() * 400 + Vector( 0, 0, 250 ) ); |
|
} |
|
|
|
m_hShoveTarget = NULL; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
class CTraceFilterCharge : public CTraceFilterEntitiesOnly |
|
{ |
|
public: |
|
// It does have a base, but we'll never network anything below here.. |
|
DECLARE_CLASS_NOBASE( CTraceFilterCharge ); |
|
|
|
CTraceFilterCharge( const IHandleEntity *passentity, int collisionGroup, CNPC_AntlionGuard *pAttacker ) |
|
: m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_pAttacker(pAttacker) |
|
{ |
|
} |
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( !StandardFilterRules( pHandleEntity, contentsMask ) ) |
|
return false; |
|
|
|
if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) |
|
return false; |
|
|
|
// Don't test if the game code tells us we should ignore this collision... |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
|
|
if ( pEntity ) |
|
{ |
|
if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) ) |
|
return false; |
|
|
|
if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) ) |
|
return false; |
|
|
|
if ( pEntity->m_takedamage == DAMAGE_NO ) |
|
return false; |
|
|
|
// Translate the vehicle into its driver for damage |
|
if ( pEntity->GetServerVehicle() != NULL ) |
|
{ |
|
CBaseEntity *pDriver = pEntity->GetServerVehicle()->GetPassenger(); |
|
|
|
if ( pDriver != NULL ) |
|
{ |
|
pEntity = pDriver; |
|
} |
|
} |
|
|
|
Vector attackDir = pEntity->WorldSpaceCenter() - m_pAttacker->WorldSpaceCenter(); |
|
VectorNormalize( attackDir ); |
|
|
|
float flDamage = ( pEntity->IsPlayer() ) ? sk_antlionguard_dmg_shove.GetFloat() : 250;; |
|
|
|
CTakeDamageInfo info( m_pAttacker, m_pAttacker, flDamage, DMG_CRUSH ); |
|
CalculateMeleeDamageForce( &info, attackDir, info.GetAttacker()->WorldSpaceCenter(), 4.0f ); |
|
|
|
CBaseCombatCharacter *pVictimBCC = pEntity->MyCombatCharacterPointer(); |
|
|
|
// Only do these comparisons between NPCs |
|
if ( pVictimBCC ) |
|
{ |
|
// Can only damage other NPCs that we hate |
|
if ( m_pAttacker->IRelationType( pEntity ) == D_HT ) |
|
{ |
|
pEntity->TakeDamage( info ); |
|
return true; |
|
} |
|
} |
|
else |
|
{ |
|
// Otherwise just damage passive objects in our way |
|
pEntity->TakeDamage( info ); |
|
Pickup_ForcePlayerToDropThisObject( pEntity ); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public: |
|
const IHandleEntity *m_pPassEnt; |
|
int m_collisionGroup; |
|
CNPC_AntlionGuard *m_pAttacker; |
|
}; |
|
|
|
#define MIN_FOOTSTEP_NEAR_DIST Square( 80*12.0f )// ft |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Plays a footstep sound with temporary distance fades |
|
// Input : bHeavy - Larger back hoof is considered a "heavy" step |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::Footstep( bool bHeavy ) |
|
{ |
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
Assert( pPlayer != NULL ); |
|
if ( pPlayer == NULL ) |
|
return; |
|
|
|
float flDistanceToPlayerSqr = ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); |
|
float flNearVolume = RemapValClamped( flDistanceToPlayerSqr, Square(10*12.0f), MIN_FOOTSTEP_NEAR_DIST, VOL_NORM, 0.0f ); |
|
|
|
EmitSound_t soundParams; |
|
CPASAttenuationFilter filter( this ); |
|
|
|
if ( bHeavy ) |
|
{ |
|
if ( flNearVolume > 0.0f ) |
|
{ |
|
soundParams.m_pSoundName = "NPC_AntlionGuard.NearStepHeavy"; |
|
soundParams.m_flVolume = flNearVolume; |
|
soundParams.m_nFlags = SND_CHANGE_VOL; |
|
EmitSound( filter, entindex(), soundParams ); |
|
} |
|
|
|
EmitSound( "NPC_AntlionGuard.FarStepHeavy" ); |
|
} |
|
else |
|
{ |
|
if ( flNearVolume > 0.0f ) |
|
{ |
|
soundParams.m_pSoundName = "NPC_AntlionGuard.NearStepLight"; |
|
soundParams.m_flVolume = flNearVolume; |
|
soundParams.m_nFlags = SND_CHANGE_VOL; |
|
EmitSound( filter, entindex(), soundParams ); |
|
} |
|
|
|
EmitSound( "NPC_AntlionGuard.FarStepLight" ); |
|
} |
|
} |
|
|
|
float GetCurrentGravity( void ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pObject - |
|
// *pOut - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::GetPhysicsShoveDir( CBaseEntity *pObject, float flMass, Vector *pOut ) |
|
{ |
|
const Vector vecStart = pObject->WorldSpaceCenter(); |
|
const Vector vecTarget = GetEnemy()->WorldSpaceCenter(); |
|
|
|
const float flBaseSpeed = 800.0f; |
|
float flSpeed = RemapValClamped( flMass, 0.0f, 150.0f, flBaseSpeed * 2.0f, flBaseSpeed ); |
|
|
|
// Try the most direct route |
|
Vector vecToss = VecCheckThrow( this, vecStart, vecTarget, flSpeed, 1.0f ); |
|
|
|
// If this failed then try a little faster (flattens the arc) |
|
if ( vecToss == vec3_origin ) |
|
{ |
|
vecToss = VecCheckThrow( this, vecStart, vecTarget, flSpeed * 1.25f, 1.0f ); |
|
if ( vecToss == vec3_origin ) |
|
{ |
|
const float flGravity = GetCurrentGravity(); |
|
|
|
vecToss = (vecTarget - vecStart); |
|
|
|
// throw at a constant time |
|
float time = vecToss.Length( ) / flSpeed; |
|
vecToss = vecToss * (1.0f / time); |
|
|
|
// adjust upward toss to compensate for gravity loss |
|
vecToss.z += flGravity * time * 0.5f; |
|
} |
|
} |
|
|
|
// Save out the result |
|
if ( pOut ) |
|
{ |
|
*pOut = vecToss; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pEvent - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
if ( pEvent->event == AE_ANTLIONGUARD_CHARGE_EARLYOUT ) |
|
{ |
|
// Robin: Removed this because it usually made him look less intelligent, not more. |
|
// This code left here so we don't get warnings in the console. |
|
|
|
/* |
|
// Cancel the charge if it's no longer valid |
|
if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), false, true ) == false ) |
|
{ |
|
SetSchedule( SCHED_ANTLIONGUARD_CHARGE_CANCEL ); |
|
} |
|
*/ |
|
|
|
return; |
|
} |
|
|
|
// Don't handle anim events after death |
|
if ( m_NPCState == NPC_STATE_DEAD ) |
|
{ |
|
BaseClass::HandleAnimEvent( pEvent ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_SHOVE_PHYSOBJECT ) |
|
{ |
|
if ( m_hPhysicsTarget == NULL ) |
|
{ |
|
// Disrupt other objects near us anyway |
|
ImpactShock( WorldSpaceCenter(), 150, 250.0f ); |
|
return; |
|
} |
|
|
|
// If we have no enemy, we don't really have a direction to throw the object |
|
// in. But, we still want to clobber it so the animevent doesn't happen fruitlessly, |
|
// and we want to clear the condition flags and other state regarding the m_hPhysicsTarget. |
|
// So, skip the code relevant to computing a launch vector specific to the object I'm |
|
// striking, but use the ImpactShock call further below to punch everything in the neighborhood. |
|
CBaseEntity *pEnemy = GetEnemy(); |
|
if ( pEnemy != NULL ) |
|
{ |
|
//Setup the throw velocity |
|
IPhysicsObject *physObj = m_hPhysicsTarget->VPhysicsGetObject(); |
|
Vector vecShoveVel = ( pEnemy->GetAbsOrigin() - m_hPhysicsTarget->WorldSpaceCenter() ); |
|
float flTargetDist = VectorNormalize( vecShoveVel ); |
|
|
|
// Must still be close enough to our target |
|
Vector shoveDir = m_hPhysicsTarget->WorldSpaceCenter() - WorldSpaceCenter(); |
|
float shoveDist = VectorNormalize( shoveDir ); |
|
if ( shoveDist > 300.0f ) |
|
{ |
|
// Pick a new target next time (this one foiled us!) |
|
RememberFailedPhysicsTarget( m_hPhysicsTarget ); |
|
m_hPhysicsTarget = NULL; |
|
return; |
|
} |
|
|
|
// Toss this if we're episodic |
|
if ( hl2_episodic.GetBool() ) |
|
{ |
|
Vector vecTargetDir = vecShoveVel; |
|
|
|
// Get our shove direction |
|
GetPhysicsShoveDir( m_hPhysicsTarget, physObj->GetMass(), &vecShoveVel ); |
|
|
|
// If the player isn't looking at me, and isn't reachable, be more forgiving about hitting them |
|
if ( HasCondition( COND_ENEMY_UNREACHABLE ) && HasCondition( COND_ENEMY_FACING_ME ) == false ) |
|
{ |
|
// Build an arc around the top of the target that we'll offset our aim by |
|
Vector vecOffset; |
|
float flSin, flCos; |
|
float flRad = random->RandomFloat( 0, M_PI / 6.0f ); // +/- 30 deg |
|
if ( random->RandomInt( 0, 1 ) ) |
|
flRad *= -1.0f; |
|
|
|
SinCos( flRad, &flSin, &flCos ); |
|
|
|
// Rotate the 2-d circle to be "facing" along our shot direction |
|
Vector vecArc; |
|
QAngle vecAngles; |
|
VectorAngles( vecTargetDir, vecAngles ); |
|
VectorRotate( Vector( 0.0f, flCos, flSin ), vecAngles, vecArc ); |
|
|
|
// Find the radius by which to avoid the player |
|
float flOffsetRadius = ( m_hPhysicsTarget->CollisionProp()->BoundingRadius() + GetEnemy()->CollisionProp()->BoundingRadius() ) * 1.5f; |
|
|
|
// Add this to our velocity to offset it |
|
vecShoveVel += ( vecArc * flOffsetRadius ); |
|
} |
|
|
|
// Factor out mass |
|
vecShoveVel *= physObj->GetMass(); |
|
|
|
// FIXME: We need to restore this on the object at some point if we do this! |
|
float flDragCoef = 0.0f; |
|
physObj->SetDragCoefficient( &flDragCoef, &flDragCoef ); |
|
} |
|
else |
|
{ |
|
if ( flTargetDist < 512 ) |
|
flTargetDist = 512; |
|
|
|
if ( flTargetDist > 1024 ) |
|
flTargetDist = 1024; |
|
|
|
vecShoveVel *= flTargetDist * 3 * physObj->GetMass(); //FIXME: Scale by skill |
|
vecShoveVel[2] += physObj->GetMass() * 350.0f; |
|
} |
|
|
|
if ( NPC_Rollermine_IsRollermine( m_hPhysicsTarget ) ) |
|
{ |
|
Pickup_OnPhysGunDrop( m_hPhysicsTarget, NULL, LAUNCHED_BY_CANNON ); |
|
} |
|
|
|
//Send it flying |
|
AngularImpulse angVel( random->RandomFloat(-180, 180), 100, random->RandomFloat(-360, 360) ); |
|
physObj->ApplyForceCenter( vecShoveVel ); |
|
physObj->AddVelocity( NULL, &angVel ); |
|
} |
|
|
|
//Display dust |
|
Vector vecRandom = RandomVector( -1, 1); |
|
VectorNormalize( vecRandom ); |
|
g_pEffects->Dust( m_hPhysicsTarget->WorldSpaceCenter(), vecRandom, 64.0f, 32 ); |
|
|
|
// If it's being held by the player, break that bond |
|
Pickup_ForcePlayerToDropThisObject( m_hPhysicsTarget ); |
|
|
|
EmitSound( "NPC_AntlionGuard.HitHard" ); |
|
|
|
//Clear the state information, we're done |
|
ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID ); |
|
|
|
// Disrupt other objects near it, including the m_hPhysicsTarget if we had no valid enemy |
|
ImpactShock( m_hPhysicsTarget->WorldSpaceCenter(), 150, 250.0f, pEnemy != NULL ? m_hPhysicsTarget : NULL ); |
|
|
|
// Notify any squad members that we're no longer monopolizing this object |
|
if ( GetSquad() != NULL ) |
|
{ |
|
GetSquad()->BroadcastInteraction( g_interactionAntlionGuardShovedPhysicsObject, (void *)((CBaseEntity *)m_hPhysicsTarget), this ); |
|
} |
|
|
|
m_hPhysicsTarget = NULL; |
|
m_FailedPhysicsTargets.RemoveAll(); |
|
|
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_CHARGE_HIT ) |
|
{ |
|
UTIL_ScreenShake( GetAbsOrigin(), 32.0f, 4.0f, 1.0f, 512, SHAKE_START ); |
|
EmitSound( "NPC_AntlionGuard.HitHard" ); |
|
|
|
Vector startPos = GetAbsOrigin(); |
|
float checkSize = ( CollisionProp()->BoundingRadius() + 8.0f ); |
|
Vector endPos = startPos + ( BodyDirection3D() * checkSize ); |
|
|
|
CTraceFilterCharge traceFilter( this, COLLISION_GROUP_NONE, this ); |
|
|
|
Ray_t ray; |
|
ray.Init( startPos, endPos, GetHullMins(), GetHullMaxs() ); |
|
|
|
trace_t tr; |
|
enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr ); |
|
|
|
if ( g_debug_antlionguard.GetInt() == 1 ) |
|
{ |
|
Vector hullMaxs = GetHullMaxs(); |
|
hullMaxs.x += checkSize; |
|
|
|
NDebugOverlay::BoxDirection( startPos, GetHullMins(), hullMaxs, BodyDirection2D(), 100, 255, 255, 20, 1.0f ); |
|
} |
|
|
|
//NDebugOverlay::Box3D( startPos, endPos, BodyDirection2D(), |
|
if ( m_hChargeTarget && m_hChargeTarget->IsAlive() == false ) |
|
{ |
|
m_hChargeTarget = NULL; |
|
m_hChargeTargetPosition = NULL; |
|
} |
|
|
|
// Cause a shock wave from this point which will distrupt nearby physics objects |
|
ImpactShock( tr.endpos, 200, 500 ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_SHOVE ) |
|
{ |
|
EmitSound("NPC_AntlionGuard.StepLight", pEvent->eventtime ); |
|
Shove(); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_FOOTSTEP_LIGHT ) |
|
{ |
|
if ( HasSpawnFlags(SF_ANTLIONGUARD_INSIDE_FOOTSTEPS) ) |
|
{ |
|
#if HL2_EPISODIC |
|
Footstep( false ); |
|
#else |
|
EmitSound("NPC_AntlionGuard.Inside.StepLight", pEvent->eventtime ); |
|
#endif // HL2_EPISODIC |
|
} |
|
else |
|
{ |
|
#if HL2_EPISODIC |
|
Footstep( false ); |
|
#else |
|
EmitSound("NPC_AntlionGuard.StepLight", pEvent->eventtime ); |
|
#endif // HL2_EPISODIC |
|
} |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_FOOTSTEP_HEAVY ) |
|
{ |
|
if ( HasSpawnFlags(SF_ANTLIONGUARD_INSIDE_FOOTSTEPS) ) |
|
{ |
|
#if HL2_EPISODIC |
|
Footstep( true ); |
|
#else |
|
EmitSound( "NPC_AntlionGuard.Inside.StepHeavy", pEvent->eventtime ); |
|
#endif // HL2_EPISODIC |
|
} |
|
else |
|
{ |
|
#if HL2_EPISODIC |
|
Footstep( true ); |
|
#else |
|
EmitSound( "NPC_AntlionGuard.StepHeavy", pEvent->eventtime ); |
|
#endif // HL2_EPISODIC |
|
} |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_VOICE_GROWL ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = 0.0f; |
|
|
|
if ( random->RandomInt( 0, 10 ) < 6 ) |
|
{ |
|
duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardFastGrowl, ARRAYSIZE(envAntlionGuardFastGrowl) ); |
|
} |
|
else |
|
{ |
|
duration = 1.0f; |
|
EmitSound( "NPC_AntlionGuard.FrustratedRoar" ); |
|
ENVELOPE_CONTROLLER.SoundFadeOut( m_pGrowlHighSound, 0.5f, false ); |
|
} |
|
|
|
m_flAngerNoiseTime = gpGlobals->curtime + duration + random->RandomFloat( 2.0f, 4.0f ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + duration - 0.2f; |
|
|
|
EmitSound( "NPC_AntlionGuard.Anger" ); |
|
return; |
|
} |
|
|
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_VOICE_BARK ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardBark1, ARRAYSIZE(envAntlionGuardBark1) ); |
|
ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pConfusedSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardBark2, ARRAYSIZE(envAntlionGuardBark2) ); |
|
|
|
m_flAngerNoiseTime = gpGlobals->curtime + duration + random->RandomFloat( 2.0f, 4.0f ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + duration - 0.2f; |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_VOICE_ROAR ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardFastGrowl, ARRAYSIZE(envAntlionGuardFastGrowl) ); |
|
|
|
m_flAngerNoiseTime = gpGlobals->curtime + duration + random->RandomFloat( 2.0f, 4.0f ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + duration - 0.2f; |
|
|
|
EmitSound( "NPC_AntlionGuard.Roar" ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_VOICE_PAIN ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pConfusedSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardPain1, ARRAYSIZE(envAntlionGuardPain1) ); |
|
ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardBark2, ARRAYSIZE(envAntlionGuardBark2) ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + duration - 0.2f; |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_VOICE_SQUEEZE ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardSqueeze, ARRAYSIZE(envAntlionGuardSqueeze) ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.6f, random->RandomFloat( 2.0f, 4.0f ) ); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 60, 80 ), random->RandomFloat( 2.0f, 4.0f ) ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + ( duration * 0.5f ); |
|
|
|
EmitSound( "NPC_AntlionGuard.Anger" ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_VOICE_SCRATCH ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = random->RandomFloat( 2.0f, 4.0f ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.6f, duration ); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 60, 80 ), duration ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + duration; |
|
|
|
EmitSound( "NPC_AntlionGuard.Anger" ); |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_VOICE_GRUNT ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pConfusedSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardGrunt, ARRAYSIZE(envAntlionGuardGrunt) ); |
|
ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pGrowlHighSound, SOUNDCTRL_CHANGE_VOLUME, envAntlionGuardGrunt2, ARRAYSIZE(envAntlionGuardGrunt2) ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, 0.1f ); |
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 0.1f ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + duration; |
|
return; |
|
} |
|
|
|
if ( pEvent->event == AE_ANTLIONGUARD_BURROW_OUT ) |
|
{ |
|
EmitSound( "NPC_Antlion.BurrowOut" ); |
|
|
|
//Shake the screen |
|
UTIL_ScreenShake( GetAbsOrigin(), 0.5f, 80.0f, 1.0f, 256.0f, SHAKE_START ); |
|
|
|
//Throw dust up |
|
UTIL_CreateAntlionDust( GetAbsOrigin() + Vector(0,0,24), GetLocalAngles() ); |
|
|
|
RemoveEffects( EF_NODRAW ); |
|
RemoveFlag( FL_NOTARGET ); |
|
|
|
if ( m_bCavernBreed ) |
|
{ |
|
if ( m_hCaveGlow[0] ) |
|
m_hCaveGlow[0]->TurnOn(); |
|
|
|
if ( m_hCaveGlow[1] ) |
|
m_hCaveGlow[1]->TurnOn(); |
|
} |
|
|
|
return; |
|
} |
|
|
|
BaseClass::HandleAnimEvent( pEvent ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::SetHeavyDamageAnim( const Vector &vecSource ) |
|
{ |
|
if ( !m_bInCavern ) |
|
{ |
|
Vector vFacing = BodyDirection2D(); |
|
Vector vDamageDir = ( vecSource - WorldSpaceCenter() ); |
|
|
|
vDamageDir.z = 0.0f; |
|
|
|
VectorNormalize( vDamageDir ); |
|
|
|
Vector vDamageRight, vDamageUp; |
|
VectorVectors( vDamageDir, vDamageRight, vDamageUp ); |
|
|
|
float damageDot = DotProduct( vFacing, vDamageDir ); |
|
float damageRightDot = DotProduct( vFacing, vDamageRight ); |
|
|
|
// See if it's in front |
|
if ( damageDot > 0.0f ) |
|
{ |
|
// See if it's right |
|
if ( damageRightDot > 0.0f ) |
|
{ |
|
m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_FR; |
|
} |
|
else |
|
{ |
|
m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_FL; |
|
} |
|
} |
|
else |
|
{ |
|
// Otherwise it's from behind |
|
if ( damageRightDot < 0.0f ) |
|
{ |
|
m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_RR; |
|
} |
|
else |
|
{ |
|
m_nFlinchActivity = ACT_ANTLIONGUARD_PHYSHIT_RL; |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
//----------------------------------------------------------------------------- |
|
int CNPC_AntlionGuard::OnTakeDamage_Alive( const CTakeDamageInfo &info ) |
|
{ |
|
CTakeDamageInfo dInfo = info; |
|
|
|
// Don't take damage from another antlion guard! |
|
if ( dInfo.GetAttacker() && dInfo.GetAttacker() != this && FClassnameIs( dInfo.GetAttacker(), "npc_antlionguard" ) ) |
|
return 0; |
|
|
|
if ( ( dInfo.GetDamageType() & DMG_CRUSH ) && !( dInfo.GetDamageType() & DMG_VEHICLE ) ) |
|
{ |
|
// Don't take damage from physics objects that weren't thrown by the player. |
|
CBaseEntity *pInflictor = dInfo.GetInflictor(); |
|
|
|
IPhysicsObject *pObj = pInflictor->VPhysicsGetObject(); |
|
if ( !pObj || !( pObj->GetGameFlags() & FVPHYSICS_WAS_THROWN ) ) |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
// Hack to make antlion guard harder in HARD |
|
if ( g_pGameRules->IsSkillLevel(SKILL_HARD) && !(info.GetDamageType() & DMG_CRUSH) ) |
|
{ |
|
dInfo.SetDamage( dInfo.GetDamage() * 0.75 ); |
|
} |
|
|
|
// Cap damage taken by crushing (otherwise we can get crushed oddly) |
|
if ( ( dInfo.GetDamageType() & DMG_CRUSH ) && dInfo.GetDamage() > 100 ) |
|
{ |
|
dInfo.SetDamage( 100 ); |
|
} |
|
|
|
// Only take damage from what we classify as heavy damages (explosions, refrigerators, etc) |
|
if ( IsHeavyDamage( dInfo ) ) |
|
{ |
|
// Always take a set amount of damage from a combine ball |
|
if ( info.GetInflictor() && UTIL_IsCombineBall( info.GetInflictor() ) ) |
|
{ |
|
dInfo.SetDamage( 50 ); |
|
} |
|
|
|
UTIL_ScreenShake( GetAbsOrigin(), 32.0f, 8.0f, 0.5f, 512, SHAKE_START ); |
|
|
|
// Set our response animation |
|
SetHeavyDamageAnim( dInfo.GetDamagePosition() ); |
|
|
|
// Explosive barrels don't carry through their attacker, so this |
|
// condition isn't set, and we still want to flinch. So we set it. |
|
SetCondition( COND_HEAVY_DAMAGE ); |
|
|
|
// TODO: Make this its own effect! |
|
CEffectData data; |
|
data.m_vOrigin = dInfo.GetDamagePosition(); |
|
data.m_vNormal = -dInfo.GetDamageForce(); |
|
VectorNormalize( data.m_vNormal ); |
|
DispatchEffect( "HunterDamage", data ); |
|
|
|
// Play a sound for a physics impact |
|
if ( dInfo.GetDamageType() & DMG_CRUSH ) |
|
{ |
|
EmitSound("NPC_AntlionGuard.ShellCrack"); |
|
} |
|
|
|
// Roar in pain |
|
EmitSound( "NPC_AntlionGuard.Pain_Roar" ); |
|
|
|
// TODO: This will require a more complete solution; for now let's shelve it! |
|
/* |
|
if ( dInfo.GetDamageType() & DMG_BLAST ) |
|
{ |
|
// Create the dust effect in place |
|
CParticleSystem *pParticle = (CParticleSystem *) CreateEntityByName( "info_particle_system" ); |
|
if ( pParticle != NULL ) |
|
{ |
|
// Setup our basic parameters |
|
pParticle->KeyValue( "start_active", "1" ); |
|
pParticle->KeyValue( "effect_name", "fire_medium_02_nosmoke" ); |
|
pParticle->SetParent( this ); |
|
pParticle->SetParentAttachment( "SetParentAttachment", "attach_glow2", true ); |
|
pParticle->SetLocalOrigin( Vector( -16, 24, 0 ) ); |
|
DispatchSpawn( pParticle ); |
|
if ( gpGlobals->curtime > 0.5f ) |
|
pParticle->Activate(); |
|
|
|
pParticle->SetThink( &CBaseEntity::SUB_Remove ); |
|
pParticle->SetNextThink( gpGlobals->curtime + random->RandomFloat( 2.0f, 3.0f ) ); |
|
} |
|
} |
|
*/ |
|
} |
|
|
|
int nPreHealth = GetHealth(); |
|
int nDamageTaken = BaseClass::OnTakeDamage_Alive( dInfo ); |
|
|
|
// See if we've crossed a measured phase in our health and flinch from that to show we do take damage |
|
if ( !m_bInCavern && HasCondition( COND_HEAVY_DAMAGE ) == false && ( info.GetDamageType() & DMG_BULLET ) ) |
|
{ |
|
bool bTakeHeavyDamage = false; |
|
|
|
// Do an early flinch so that players understand the guard can be hurt by |
|
if ( ( (float) GetHealth() / (float) GetMaxHealth() ) > 0.9f ) |
|
{ |
|
float flPrePerc = (float) nPreHealth / (float) GetMaxHealth(); |
|
float flPostPerc = (float) GetHealth() / (float) GetMaxHealth(); |
|
if ( flPrePerc >= 0.95f && flPostPerc < 0.95f ) |
|
{ |
|
bTakeHeavyDamage = true; |
|
} |
|
} |
|
|
|
// Otherwise see if we've passed a measured point in our health |
|
if ( bTakeHeavyDamage == false ) |
|
{ |
|
const float flNumDamagePhases = 5.0f; |
|
|
|
float flDenom = ( (float) GetMaxHealth() / flNumDamagePhases ); |
|
int nPreDamagePhase = ceil( (float) nPreHealth / flDenom ); |
|
int nPostDamagePhase = ceil( (float) GetHealth() / flDenom ); |
|
if ( nPreDamagePhase != nPostDamagePhase ) |
|
{ |
|
bTakeHeavyDamage = true; |
|
} |
|
} |
|
|
|
// Flinch if we should |
|
if ( bTakeHeavyDamage ) |
|
{ |
|
// Set our response animation |
|
SetHeavyDamageAnim( dInfo.GetDamagePosition() ); |
|
|
|
// Roar in pain |
|
EmitSound( "NPC_AntlionGuard.Pain_Roar" ); |
|
|
|
// Flinch! |
|
SetCondition( COND_HEAVY_DAMAGE ); |
|
} |
|
} |
|
|
|
return nDamageTaken; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pAttacker - |
|
// flDamage - |
|
// &vecDir - |
|
// *ptr - |
|
// bitsDamageType - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) |
|
{ |
|
CTakeDamageInfo info = inputInfo; |
|
|
|
// Bullets are weak against us, buckshot less so |
|
if ( info.GetDamageType() & DMG_BUCKSHOT ) |
|
{ |
|
info.ScaleDamage( 0.5f ); |
|
} |
|
else if ( info.GetDamageType() & DMG_BULLET ) |
|
{ |
|
info.ScaleDamage( 0.25f ); |
|
} |
|
|
|
// Make sure we haven't rounded down to a minimal amount |
|
if ( info.GetDamage() < 1.0f ) |
|
{ |
|
info.SetDamage( 1.0f ); |
|
} |
|
|
|
BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::StartTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY: |
|
SetIdealActivity( (Activity) m_nFlinchActivity ); |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_SUMMON: |
|
SummonAntlions(); |
|
m_OnSummon.FireOutput( this, this, 0 ); |
|
TaskComplete(); |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT: |
|
{ |
|
if ( ( m_hPhysicsTarget == NULL ) || ( GetEnemy() == NULL ) ) |
|
{ |
|
TaskFail( "Tried to shove a NULL physics target!\n" ); |
|
break; |
|
} |
|
|
|
//Get the direction and distance to our thrown object |
|
Vector dirToTarget = ( m_hPhysicsTarget->WorldSpaceCenter() - WorldSpaceCenter() ); |
|
float distToTarget = VectorNormalize( dirToTarget ); |
|
dirToTarget.z = 0; |
|
|
|
//Validate it's still close enough to shove |
|
//FIXME: Real numbers |
|
if ( distToTarget > 256.0f ) |
|
{ |
|
RememberFailedPhysicsTarget( m_hPhysicsTarget ); |
|
m_hPhysicsTarget = NULL; |
|
|
|
TaskFail( "Shove target moved\n" ); |
|
break; |
|
} |
|
|
|
//Validate its offset from our facing |
|
float targetYaw = UTIL_VecToYaw( dirToTarget ); |
|
float offset = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( GetLocalAngles().y ) ); |
|
|
|
if ( fabs( offset ) > 55 ) |
|
{ |
|
RememberFailedPhysicsTarget( m_hPhysicsTarget ); |
|
m_hPhysicsTarget = NULL; |
|
|
|
TaskFail( "Shove target off-center\n" ); |
|
break; |
|
} |
|
|
|
//Blend properly |
|
SetPoseParameter( m_poseThrow, offset ); |
|
|
|
//Start playing the animation |
|
SetActivity( ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT ); |
|
} |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_FIND_PHYSOBJECT: |
|
{ |
|
if ( m_bInCavern && !m_bPreferPhysicsAttack ) |
|
{ |
|
TaskFail( "Cavern guard is not allowed to use physics attacks." ); |
|
} |
|
|
|
// Force the antlion guard to find a physobject |
|
m_flPhysicsCheckTime = 0; |
|
UpdatePhysicsTarget( false, (100*12) ); |
|
if ( m_hPhysicsTarget ) |
|
{ |
|
TaskComplete(); |
|
} |
|
else |
|
{ |
|
TaskFail( "Failed to find a physobject.\n" ); |
|
} |
|
} |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT: |
|
{ |
|
if ( ( m_hPhysicsTarget == NULL ) || ( GetEnemy() == NULL ) ) |
|
{ |
|
TaskFail( "Tried to find a path to NULL physics target!\n" ); |
|
break; |
|
} |
|
|
|
Vector vecGoalPos = m_vecPhysicsHitPosition; |
|
AI_NavGoal_t goal( GOALTYPE_LOCATION, vecGoalPos, ACT_RUN ); |
|
|
|
if ( GetNavigator()->SetGoal( goal ) ) |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 1 ) |
|
{ |
|
NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 0, 255, 0, true, 2.0f ); |
|
NDebugOverlay::Line( vecGoalPos, m_hPhysicsTarget->WorldSpaceCenter(), 0, 255, 0, true, 2.0f ); |
|
NDebugOverlay::Line( m_hPhysicsTarget->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 0, 255, 0, true, 2.0f ); |
|
} |
|
|
|
// Face the enemy |
|
GetNavigator()->SetArrivalDirection( GetEnemy() ); |
|
TaskComplete(); |
|
} |
|
else |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 1 ) |
|
{ |
|
NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 255, 0, 0, true, 2.0f ); |
|
NDebugOverlay::Line( vecGoalPos, m_hPhysicsTarget->WorldSpaceCenter(), 255, 0, 0, true, 2.0f ); |
|
NDebugOverlay::Line( m_hPhysicsTarget->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 255, 0, 0, true, 2.0f ); |
|
} |
|
|
|
RememberFailedPhysicsTarget( m_hPhysicsTarget ); |
|
m_hPhysicsTarget = NULL; |
|
TaskFail( "Unable to navigate to physics attack target!\n" ); |
|
break; |
|
} |
|
|
|
//!!!HACKHACK - this is a hack that covers a bug in antlion guard physics target selection! (Tracker #77601) |
|
// This piece of code (combined with some code in GatherConditions) COVERS THE BUG by escaping the schedule |
|
// if 30 seconds have passed (it should never take this long for the guard to get to an object and hit it). |
|
// It's too scary to figure out why this particular antlion guard can't get to its object, but we're shipping |
|
// like, tomorrow. (sjb) 8/22/2007 |
|
m_flWaitFinished = gpGlobals->curtime + 30.0f; |
|
} |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_CHARGE: |
|
{ |
|
// HACK: Because the guard stops running his normal blended movement |
|
// here, he also needs to remove his blended movement layers! |
|
GetMotor()->MoveStop(); |
|
|
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_START ); |
|
m_bDecidedNotToStop = false; |
|
} |
|
break; |
|
|
|
|
|
case TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION: |
|
{ |
|
if ( !m_hChargeTargetPosition ) |
|
{ |
|
TaskFail( "Tried to find a charge position without one specified.\n" ); |
|
break; |
|
} |
|
|
|
// Move to the charge position |
|
AI_NavGoal_t goal( GOALTYPE_LOCATION, m_hChargeTargetPosition->GetAbsOrigin(), ACT_RUN ); |
|
if ( GetNavigator()->SetGoal( goal ) ) |
|
{ |
|
// We want to face towards the charge target |
|
Vector vecDir = m_hChargeTarget->GetAbsOrigin() - m_hChargeTargetPosition->GetAbsOrigin(); |
|
VectorNormalize( vecDir ); |
|
vecDir.z = 0; |
|
GetNavigator()->SetArrivalDirection( vecDir ); |
|
TaskComplete(); |
|
} |
|
else |
|
{ |
|
m_hChargeTarget = NULL; |
|
m_hChargeTargetPosition = NULL; |
|
TaskFail( FAIL_NO_ROUTE ); |
|
} |
|
} |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE: |
|
{ |
|
if ( !GetEnemy() ) |
|
{ |
|
TaskFail( FAIL_NO_ENEMY ); |
|
break; |
|
} |
|
|
|
// Find the nearest node to the enemy |
|
int node = GetNavigator()->GetNetwork()->NearestNodeToPoint( this, GetEnemy()->GetAbsOrigin(), false ); |
|
CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( node ); |
|
if( pNode == NULL ) |
|
{ |
|
TaskFail( FAIL_NO_ROUTE ); |
|
break; |
|
} |
|
|
|
Vector vecNodePos = pNode->GetPosition( GetHullType() ); |
|
AI_NavGoal_t goal( GOALTYPE_LOCATION, vecNodePos, ACT_RUN ); |
|
if ( GetNavigator()->SetGoal( goal ) ) |
|
{ |
|
GetNavigator()->SetArrivalDirection( GetEnemy() ); |
|
TaskComplete(); |
|
break; |
|
} |
|
|
|
|
|
TaskFail( FAIL_NO_ROUTE ); |
|
break; |
|
} |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_GET_CHASE_PATH_ENEMY_TOLERANCE: |
|
{ |
|
// Chase the enemy, but allow local navigation to succeed if it gets within the goal tolerance |
|
GetNavigator()->SetLocalSucceedOnWithinTolerance( true ); |
|
|
|
if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) ) |
|
{ |
|
TaskComplete(); |
|
} |
|
else |
|
{ |
|
RememberUnreachable(GetEnemy()); |
|
TaskFail(FAIL_NO_ROUTE); |
|
} |
|
|
|
GetNavigator()->SetLocalSucceedOnWithinTolerance( false ); |
|
} |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_OPPORTUNITY_THROW: |
|
{ |
|
// If we've got some live antlions, look for a physics object to throw |
|
if ( m_iNumLiveAntlions >= 2 && RandomFloat(0,1) > 0.5 ) |
|
{ |
|
m_FailedPhysicsTargets.RemoveAll(); |
|
UpdatePhysicsTarget( false, m_bPreferPhysicsAttack ? (100*12) : ANTLIONGUARD_FARTHEST_PHYSICS_OBJECT ); |
|
if ( HasCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ) && !m_bInCavern ) |
|
{ |
|
SetSchedule( SCHED_ANTLIONGUARD_PHYSICS_ATTACK ); |
|
} |
|
} |
|
|
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::StartTask(pTask); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Calculate & apply damage & force for a charge to a target. |
|
// Done outside of the guard because we need to do this inside a trace filter. |
|
//----------------------------------------------------------------------------- |
|
void ApplyChargeDamage( CBaseEntity *pAntlionGuard, CBaseEntity *pTarget, float flDamage ) |
|
{ |
|
Vector attackDir = ( pTarget->WorldSpaceCenter() - pAntlionGuard->WorldSpaceCenter() ); |
|
VectorNormalize( attackDir ); |
|
Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter(); |
|
|
|
// Generate enough force to make a 75kg guy move away at 700 in/sec |
|
Vector vecForce = attackDir * ImpulseScale( 75, 700 ); |
|
|
|
// Deal the damage |
|
CTakeDamageInfo info( pAntlionGuard, pAntlionGuard, vecForce, offset, flDamage, DMG_CLUB ); |
|
pTarget->TakeDamage( info ); |
|
|
|
#if HL2_EPISODIC |
|
// If I am a cavern guard attacking the player, and he still lives, then poison him too. |
|
Assert( dynamic_cast<CNPC_AntlionGuard *>(pAntlionGuard) ); |
|
|
|
if ( static_cast<CNPC_AntlionGuard *>(pAntlionGuard)->IsInCavern() && pTarget->IsPlayer() && pTarget->IsAlive() && pTarget->m_iHealth > ANTLIONGUARD_POISON_TO) |
|
{ |
|
// That didn't finish them. Take them down to one point with poison damage. It'll heal. |
|
pTarget->TakeDamage( CTakeDamageInfo( pAntlionGuard, pAntlionGuard, pTarget->m_iHealth - ANTLIONGUARD_POISON_TO, DMG_POISON ) ); |
|
} |
|
#endif |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A simple trace filter class to skip small moveable physics objects |
|
//----------------------------------------------------------------------------- |
|
class CTraceFilterSkipPhysics : public CTraceFilter |
|
{ |
|
public: |
|
// It does have a base, but we'll never network anything below here.. |
|
DECLARE_CLASS_NOBASE( CTraceFilterSkipPhysics ); |
|
|
|
CTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass ) |
|
: m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass) |
|
{ |
|
} |
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( !StandardFilterRules( pHandleEntity, contentsMask ) ) |
|
return false; |
|
|
|
if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) |
|
return false; |
|
|
|
// Don't test if the game code tells us we should ignore this collision... |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( pEntity ) |
|
{ |
|
if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) ) |
|
return false; |
|
|
|
if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) ) |
|
return false; |
|
|
|
// don't test small moveable physics objects (unless it's an NPC) |
|
if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
{ |
|
IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); |
|
Assert(pPhysics); |
|
if ( pPhysics->IsMoveable() && pPhysics->GetMass() < m_minMass ) |
|
return false; |
|
} |
|
|
|
// If we hit an antlion, don't stop, but kill it |
|
if ( pEntity->Classify() == CLASS_ANTLION ) |
|
{ |
|
CBaseEntity *pGuard = (CBaseEntity*)EntityFromEntityHandle( m_pPassEnt ); |
|
ApplyChargeDamage( pGuard, pEntity, pEntity->GetHealth() ); |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
|
|
private: |
|
const IHandleEntity *m_pPassEnt; |
|
int m_collisionGroup; |
|
float m_minMass; |
|
}; |
|
|
|
inline void TraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin, |
|
const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore, |
|
int collisionGroup, trace_t *ptr, float minMass ) |
|
{ |
|
Ray_t ray; |
|
ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax ); |
|
CTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass ); |
|
enginetrace->TraceRay( ray, mask, &traceFilter, ptr ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if our charge target is right in front of the guard |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity ) |
|
{ |
|
if ( !GetEnemy() ) |
|
return false; |
|
|
|
if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) ) |
|
{ |
|
Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); |
|
vecLOS.z = 0; |
|
VectorNormalize( vecLOS ); |
|
Vector vBodyDir = BodyDirection2D(); |
|
if ( DotProduct( vecLOS, vBodyDir ) > 0.8 ) |
|
{ |
|
// He's in front of me, and close. Make sure he's not behind a wall. |
|
trace_t tr; |
|
UTIL_TraceLine( WorldSpaceCenter(), GetEnemy()->EyePosition(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.m_pEnt == GetEnemy() ) |
|
{ |
|
*pEntity = tr.m_pEnt; |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: While charging, look ahead and see if we're going to run into anything. |
|
// If we are, start the gesture so it looks like we're anticipating the hit. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::ChargeLookAhead( void ) |
|
{ |
|
trace_t tr; |
|
Vector vecForward; |
|
GetVectors( &vecForward, NULL, NULL ); |
|
Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 ); |
|
Vector testHullMins = GetHullMins(); |
|
testHullMins.z += (StepHeight() * 2); |
|
TraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 ); |
|
|
|
//NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f ); |
|
//NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f ); |
|
|
|
if ( tr.fraction != 1.0 ) |
|
{ |
|
// Start playing the hit animation |
|
AddGesture( ACT_ANTLIONGUARD_CHARGE_ANTICIPATION ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles the guard charging into something. Returns true if it hit the world. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity ) |
|
{ |
|
// Cause a shock wave from this point which will disrupt nearby physics objects |
|
ImpactShock( vecImpact, 128, 350 ); |
|
|
|
// Did we hit anything interesting? |
|
if ( !pEntity || pEntity->IsWorld() ) |
|
{ |
|
// Robin: Due to some of the finicky details in the motor, the guard will hit |
|
// the world when it is blocked by our enemy when trying to step up |
|
// during a moveprobe. To get around this, we see if the enemy's within |
|
// a volume in front of the guard when we hit the world, and if he is, |
|
// we hit him anyway. |
|
EnemyIsRightInFrontOfMe( &pEntity ); |
|
|
|
// Did we manage to find him? If not, increment our charge miss count and abort. |
|
if ( pEntity->IsWorld() ) |
|
{ |
|
m_iChargeMisses++; |
|
return true; |
|
} |
|
} |
|
|
|
// Hit anything we don't like |
|
if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) ) |
|
{ |
|
EmitSound( "NPC_AntlionGuard.Shove" ); |
|
|
|
if ( !IsPlayingGesture( ACT_ANTLIONGUARD_CHARGE_HIT ) ) |
|
{ |
|
RestartGesture( ACT_ANTLIONGUARD_CHARGE_HIT ); |
|
} |
|
|
|
ChargeDamage( pEntity ); |
|
|
|
pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) ); |
|
|
|
if ( !pEntity->IsAlive() && GetEnemy() == pEntity ) |
|
{ |
|
SetEnemy( NULL ); |
|
} |
|
|
|
SetNextAttack( gpGlobals->curtime + 2.0f ); |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP ); |
|
|
|
// We've hit something, so clear our miss count |
|
m_iChargeMisses = 0; |
|
return false; |
|
} |
|
|
|
// Hit something we don't hate. If it's not moveable, crash into it. |
|
if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH ) |
|
return true; |
|
|
|
// If it's a vphysics object that's too heavy, crash into it too. |
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
{ |
|
IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); |
|
if ( pPhysics ) |
|
{ |
|
// If the object is being held by the player, knock it out of his hands |
|
if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) |
|
{ |
|
Pickup_ForcePlayerToDropThisObject( pEntity ); |
|
return false; |
|
} |
|
|
|
if ( (!pPhysics->IsMoveable() || pPhysics->GetMass() > VPhysicsGetObject()->GetMass() * 0.5f ) ) |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
|
|
/* |
|
|
|
ROBIN: Wrote & then removed this. If we want to have large rocks that the guard |
|
should smack around, then we should enable it. |
|
|
|
else |
|
{ |
|
// If we hit a physics prop, smack the crap out of it. (large rocks) |
|
// Factor the object mass into it, because we want to move it no matter how heavy it is. |
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
{ |
|
CTakeDamageInfo info( this, this, 250, DMG_BLAST ); |
|
info.SetDamagePosition( vecImpact ); |
|
float flForce = ImpulseScale( pEntity->VPhysicsGetObject()->GetMass(), 250 ); |
|
flForce *= random->RandomFloat( 0.85, 1.15 ); |
|
|
|
// Calculate the vector and stuff it into the takedamageinfo |
|
Vector vecForce = BodyDirection3D(); |
|
VectorNormalize( vecForce ); |
|
vecForce *= flForce; |
|
vecForce *= phys_pushscale.GetFloat(); |
|
info.SetDamageForce( vecForce ); |
|
|
|
pEntity->VPhysicsTakeDamage( info ); |
|
} |
|
} |
|
*/ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CNPC_AntlionGuard::ChargeSteer( void ) |
|
{ |
|
trace_t tr; |
|
Vector testPos, steer, forward, right; |
|
QAngle angles; |
|
const float testLength = m_flGroundSpeed * 0.15f; |
|
|
|
//Get our facing |
|
GetVectors( &forward, &right, NULL ); |
|
|
|
steer = forward; |
|
|
|
const float faceYaw = UTIL_VecToYaw( forward ); |
|
|
|
//Offset right |
|
VectorAngles( forward, angles ); |
|
angles[YAW] += 45.0f; |
|
AngleVectors( angles, &forward ); |
|
|
|
//Probe out |
|
testPos = GetAbsOrigin() + ( forward * testLength ); |
|
|
|
//Offset by step height |
|
Vector testHullMins = GetHullMins(); |
|
testHullMins.z += (StepHeight() * 2); |
|
|
|
//Probe |
|
TraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f ); |
|
|
|
//Debug info |
|
if ( g_debug_antlionguard.GetInt() == 1 ) |
|
{ |
|
if ( tr.fraction == 1.0f ) |
|
{ |
|
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f ); |
|
} |
|
} |
|
|
|
//Add in this component |
|
steer += ( right * 0.5f ) * ( 1.0f - tr.fraction ); |
|
|
|
//Offset left |
|
angles[YAW] -= 90.0f; |
|
AngleVectors( angles, &forward ); |
|
|
|
//Probe out |
|
testPos = GetAbsOrigin() + ( forward * testLength ); |
|
|
|
// Probe |
|
TraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f ); |
|
|
|
//Debug |
|
if ( g_debug_antlionguard.GetInt() == 1 ) |
|
{ |
|
if ( tr.fraction == 1.0f ) |
|
{ |
|
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f ); |
|
} |
|
} |
|
|
|
//Add in this component |
|
steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction ); |
|
|
|
//Debug |
|
if ( g_debug_antlionguard.GetInt() == 1 ) |
|
{ |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f ); |
|
NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f ); |
|
|
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f ); |
|
NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f ); |
|
} |
|
|
|
return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::RunTask( const Task_t *pTask ) |
|
{ |
|
switch (pTask->iTask) |
|
{ |
|
case TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY: |
|
|
|
AutoMovement( ); |
|
|
|
if ( IsActivityFinished() ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT: |
|
|
|
if ( IsActivityFinished() ) |
|
{ |
|
TaskComplete(); |
|
} |
|
|
|
break; |
|
|
|
/* |
|
case TASK_RUN_PATH: |
|
{ |
|
|
|
|
|
} |
|
break; |
|
*/ |
|
|
|
case TASK_ANTLIONGUARD_CHARGE: |
|
{ |
|
Activity eActivity = GetActivity(); |
|
|
|
// See if we're trying to stop after hitting/missing our target |
|
if ( eActivity == ACT_ANTLIONGUARD_CHARGE_STOP || eActivity == ACT_ANTLIONGUARD_CHARGE_CRASH ) |
|
{ |
|
if ( IsActivityFinished() ) |
|
{ |
|
TaskComplete(); |
|
return; |
|
} |
|
|
|
// Still in the process of slowing down. Run movement until it's done. |
|
AutoMovement(); |
|
return; |
|
} |
|
|
|
// Check for manual transition |
|
if ( ( eActivity == ACT_ANTLIONGUARD_CHARGE_START ) && ( IsActivityFinished() ) ) |
|
{ |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_RUN ); |
|
} |
|
|
|
// See if we're still running |
|
if ( eActivity == ACT_ANTLIONGUARD_CHARGE_RUN || eActivity == ACT_ANTLIONGUARD_CHARGE_START ) |
|
{ |
|
if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) ) |
|
{ |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP ); |
|
return; |
|
} |
|
else |
|
{ |
|
if ( GetEnemy() != NULL ) |
|
{ |
|
Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); |
|
VectorNormalize( goalDir ); |
|
|
|
if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f ) |
|
{ |
|
if ( !m_bDecidedNotToStop ) |
|
{ |
|
// We've missed the target. Randomly decide not to stop, which will cause |
|
// the guard to just try and swing around for another pass. |
|
m_bDecidedNotToStop = true; |
|
if ( RandomFloat(0,1) > 0.3 ) |
|
{ |
|
m_iChargeMisses++; |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
m_bDecidedNotToStop = false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Steer towards our target |
|
float idealYaw; |
|
if ( GetEnemy() == NULL ) |
|
{ |
|
idealYaw = GetMotor()->GetIdealYaw(); |
|
} |
|
else |
|
{ |
|
idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() ); |
|
} |
|
|
|
// Add in our steering offset |
|
idealYaw += ChargeSteer(); |
|
|
|
// Turn to face |
|
GetMotor()->SetIdealYawAndUpdate( idealYaw ); |
|
|
|
// See if we're going to run into anything soon |
|
ChargeLookAhead(); |
|
|
|
// Let our animations simply move us forward. Keep the result |
|
// of the movement so we know whether we've hit our target. |
|
AIMoveTrace_t moveTrace; |
|
if ( AutoMovement( GetEnemy(), &moveTrace ) == false ) |
|
{ |
|
// Only stop if we hit the world |
|
if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) ) |
|
{ |
|
// If we're starting up, this is an error |
|
if ( eActivity == ACT_ANTLIONGUARD_CHARGE_START ) |
|
{ |
|
TaskFail( "Unable to make initial movement of charge\n" ); |
|
return; |
|
} |
|
|
|
// Crash unless we're trying to stop already |
|
if ( eActivity != ACT_ANTLIONGUARD_CHARGE_STOP ) |
|
{ |
|
if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin ) |
|
{ |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP ); |
|
} |
|
else |
|
{ |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_CRASH ); |
|
} |
|
} |
|
} |
|
else if ( moveTrace.pObstruction ) |
|
{ |
|
// If we hit an antlion, don't stop, but kill it |
|
if ( moveTrace.pObstruction->Classify() == CLASS_ANTLION ) |
|
{ |
|
if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) ) |
|
{ |
|
// Crash unless we're trying to stop already |
|
if ( eActivity != ACT_ANTLIONGUARD_CHARGE_STOP ) |
|
{ |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP ); |
|
} |
|
} |
|
else |
|
{ |
|
ApplyChargeDamage( this, moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
break; |
|
|
|
case TASK_WAIT_FOR_MOVEMENT: |
|
{ |
|
// the cavern antlion can clothesline gordon |
|
if ( m_bInCavern ) |
|
{ |
|
|
|
// See if we're going to run into anything soon |
|
ChargeLookAhead(); |
|
|
|
if ( HasCondition(COND_CAN_MELEE_ATTACK1) ) |
|
{ |
|
|
|
|
|
CBaseEntity *pEntity = GetEnemy(); |
|
|
|
if (pEntity && pEntity->IsPlayer()) |
|
{ |
|
EmitSound( "NPC_AntlionGuard.Shove" ); |
|
|
|
if ( !IsPlayingGesture( ACT_ANTLIONGUARD_CHARGE_HIT ) ) |
|
{ |
|
RestartGesture( ACT_ANTLIONGUARD_CHARGE_HIT ); |
|
} |
|
|
|
ChargeDamage( pEntity ); |
|
|
|
pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 200 ) ); |
|
|
|
if ( !pEntity->IsAlive() && GetEnemy() == pEntity ) |
|
{ |
|
SetEnemy( NULL ); |
|
} |
|
|
|
SetNextAttack( gpGlobals->curtime + 2.0f ); |
|
SetActivity( ACT_ANTLIONGUARD_CHARGE_STOP ); |
|
|
|
// We've hit something, so clear our miss count |
|
m_iChargeMisses = 0; |
|
|
|
AutoMovement(); |
|
TaskComplete(); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
BaseClass::RunTask( pTask ); |
|
|
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::RunTask(pTask); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Summon antlions around the guard |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::SummonAntlions( void ) |
|
{ |
|
// We want to spawn them around the guard |
|
Vector vecForward, vecRight; |
|
AngleVectors( QAngle(0,GetAbsAngles().y,0), &vecForward, &vecRight, NULL ); |
|
|
|
// Spawn positions |
|
struct spawnpos_t |
|
{ |
|
float flForward; |
|
float flRight; |
|
}; |
|
|
|
spawnpos_t sAntlionSpawnPositions[] = |
|
{ |
|
{ 0, 200 }, |
|
{ 0, -200 }, |
|
{ 128, 128 }, |
|
{ 128, -128 }, |
|
{ -128, 128 }, |
|
{ -128, -128 }, |
|
{ 200, 0 }, |
|
{ -200, 0 }, |
|
}; |
|
|
|
// Only spawn up to our max count |
|
int iSpawnPoint = 0; |
|
for ( int i = 0; (m_iNumLiveAntlions < ANTLIONGUARD_SUMMON_COUNT) && (iSpawnPoint < ARRAYSIZE(sAntlionSpawnPositions)); i++ ) |
|
{ |
|
// Determine spawn position for the antlion |
|
Vector vecSpawn = GetAbsOrigin() + ( sAntlionSpawnPositions[iSpawnPoint].flForward * vecForward ) + ( sAntlionSpawnPositions[iSpawnPoint].flRight * vecRight ); |
|
iSpawnPoint++; |
|
// Randomise it a little |
|
vecSpawn.x += RandomFloat( -64, 64 ); |
|
vecSpawn.y += RandomFloat( -64, 64 ); |
|
vecSpawn.z += 64; |
|
|
|
// Make sure it's clear, and make sure we hit something |
|
trace_t tr; |
|
UTIL_TraceHull( vecSpawn, vecSpawn - Vector(0,0,128), NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), MASK_NPCSOLID, NULL, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.startsolid || tr.allsolid || tr.fraction == 1.0 ) |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 2 ) |
|
{ |
|
NDebugOverlay::Box( tr.endpos, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 255, 0, 0, true, 5.0f ); |
|
} |
|
continue; |
|
} |
|
|
|
// Ensure it's dirt or sand |
|
const surfacedata_t *pdata = physprops->GetSurfaceData( tr.surface.surfaceProps ); |
|
if ( ( pdata->game.material != CHAR_TEX_DIRT ) && ( pdata->game.material != CHAR_TEX_SAND ) ) |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 2 ) |
|
{ |
|
NDebugOverlay::Box( tr.endpos, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 255, 128, 128, true, 5.0f ); |
|
} |
|
continue; |
|
} |
|
|
|
// Make sure the guard can see it |
|
trace_t tr_vis; |
|
UTIL_TraceLine( WorldSpaceCenter(), tr.endpos, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr_vis ); |
|
if ( tr_vis.fraction != 1.0 ) |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 2 ) |
|
{ |
|
NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 255, 0, 0, true, 5.0f ); |
|
} |
|
continue; |
|
} |
|
|
|
CAI_BaseNPC *pent = (CAI_BaseNPC*)CreateEntityByName( "npc_antlion" ); |
|
if ( !pent ) |
|
break; |
|
|
|
CNPC_Antlion *pAntlion = assert_cast<CNPC_Antlion*>(pent); |
|
|
|
if ( g_debug_antlionguard.GetInt() == 2 ) |
|
{ |
|
NDebugOverlay::Box( tr.endpos, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 0, 255, 0, true, 5.0f ); |
|
NDebugOverlay::Line( WorldSpaceCenter(), tr.endpos, 0, 255, 0, true, 5.0f ); |
|
} |
|
|
|
vecSpawn = tr.endpos; |
|
pAntlion->SetAbsOrigin( vecSpawn ); |
|
|
|
// Start facing our enemy if we have one, otherwise just match the guard. |
|
Vector vecFacing = vecForward; |
|
if ( GetEnemy() ) |
|
{ |
|
vecFacing = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); |
|
VectorNormalize( vecFacing ); |
|
} |
|
QAngle vecAngles; |
|
VectorAngles( vecFacing, vecAngles ); |
|
pAntlion->SetAbsAngles( vecAngles ); |
|
|
|
pAntlion->AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); |
|
pAntlion->AddSpawnFlags( SF_NPC_FADE_CORPSE ); |
|
|
|
// Make the antlion fire my input when he dies |
|
pAntlion->KeyValue( "OnDeath", UTIL_VarArgs("%s,SummonedAntlionDied,,0,-1", STRING(GetEntityName())) ); |
|
|
|
// Start the antlion burrowed, and tell him to come up |
|
pAntlion->m_bStartBurrowed = true; |
|
DispatchSpawn( pAntlion ); |
|
pAntlion->Activate(); |
|
g_EventQueue.AddEvent( pAntlion, "Unburrow", RandomFloat(0.1, 1.0), this, this ); |
|
|
|
// Add it to our squad |
|
if ( GetSquad() != NULL ) |
|
{ |
|
GetSquad()->AddToSquad( pAntlion ); |
|
} |
|
|
|
// Set the antlion's enemy to our enemy |
|
if ( GetEnemy() ) |
|
{ |
|
pAntlion->SetEnemy( GetEnemy() ); |
|
pAntlion->SetState( NPC_STATE_COMBAT ); |
|
pAntlion->UpdateEnemyMemory( GetEnemy(), GetEnemy()->GetAbsOrigin() ); |
|
} |
|
|
|
m_iNumLiveAntlions++; |
|
} |
|
|
|
if ( g_debug_antlionguard.GetInt() == 2 ) |
|
{ |
|
Msg("Guard summoned antlion count: %d\n", m_iNumLiveAntlions ); |
|
} |
|
|
|
if ( m_iNumLiveAntlions > 2 ) |
|
{ |
|
m_flNextSummonTime = gpGlobals->curtime + RandomFloat( 15,20 ); |
|
} |
|
else |
|
{ |
|
m_flNextSummonTime = gpGlobals->curtime + RandomFloat( 10,15 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::FoundEnemy( void ) |
|
{ |
|
m_flAngerNoiseTime = gpGlobals->curtime + 2.0f; |
|
SetState( NPC_STATE_COMBAT ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::LostEnemy( void ) |
|
{ |
|
m_flSearchNoiseTime = gpGlobals->curtime + 2.0f; |
|
SetState( NPC_STATE_ALERT ); |
|
|
|
m_OnLostPlayer.FireOutput( this, this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputSetShoveTarget( inputdata_t &inputdata ) |
|
{ |
|
if ( IsAlive() == false ) |
|
return; |
|
|
|
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller ); |
|
|
|
if ( pTarget == NULL ) |
|
{ |
|
Warning( "**Guard %s cannot find shove target %s\n", GetClassname(), inputdata.value.String() ); |
|
m_hShoveTarget = NULL; |
|
return; |
|
} |
|
|
|
m_hShoveTarget = pTarget; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputSetChargeTarget( inputdata_t &inputdata ) |
|
{ |
|
if ( !IsAlive() ) |
|
return; |
|
|
|
// Pull the target & position out of the string |
|
char parseString[255]; |
|
Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString)); |
|
|
|
// Get charge target name |
|
char *pszParam = strtok(parseString," "); |
|
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, pszParam, NULL, inputdata.pActivator, inputdata.pCaller ); |
|
if ( !pTarget ) |
|
{ |
|
Warning( "ERROR: Guard %s cannot find charge target '%s'\n", STRING(GetEntityName()), pszParam ); |
|
return; |
|
} |
|
|
|
// Get the charge position name |
|
pszParam = strtok(NULL," "); |
|
CBaseEntity *pPosition = gEntList.FindEntityByName( NULL, pszParam, NULL, inputdata.pActivator, inputdata.pCaller ); |
|
if ( !pPosition ) |
|
{ |
|
Warning( "ERROR: Guard %s cannot find charge position '%s'\nMake sure you've specified the parameters as [target start]!\n", STRING(GetEntityName()), pszParam ); |
|
return; |
|
} |
|
|
|
// Make sure we don't stack charge targets |
|
if ( m_hChargeTarget ) |
|
{ |
|
if ( GetEnemy() == m_hChargeTarget ) |
|
{ |
|
SetEnemy( NULL ); |
|
} |
|
} |
|
|
|
SetCondition( COND_ANTLIONGUARD_HAS_CHARGE_TARGET ); |
|
m_hChargeTarget = pTarget; |
|
m_hChargeTargetPosition = pPosition; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputClearChargeTarget( inputdata_t &inputdata ) |
|
{ |
|
m_hChargeTarget = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : baseAct - |
|
// Output : Activity |
|
//----------------------------------------------------------------------------- |
|
Activity CNPC_AntlionGuard::NPC_TranslateActivity( Activity baseAct ) |
|
{ |
|
//See which run to use |
|
if ( ( baseAct == ACT_RUN ) && IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) ) |
|
return (Activity) ACT_ANTLIONGUARD_CHARGE_RUN; |
|
|
|
// Do extra code if we're trying to close on an enemy in a confined space (unless scripted) |
|
if ( hl2_episodic.GetBool() && m_bInCavern && baseAct == ACT_RUN && IsInAScript() == false ) |
|
return (Activity) ACT_ANTLIONGUARD_CHARGE_RUN; |
|
|
|
if ( ( baseAct == ACT_RUN ) && ( m_iHealth <= (m_iMaxHealth/4) ) ) |
|
return (Activity) ACT_ANTLIONGUARD_RUN_HURT; |
|
|
|
return baseAct; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::ShouldWatchEnemy( void ) |
|
{ |
|
Activity nActivity = GetActivity(); |
|
|
|
if ( ( nActivity == ACT_ANTLIONGUARD_SEARCH ) || |
|
( nActivity == ACT_ANTLIONGUARD_PEEK_ENTER ) || |
|
( nActivity == ACT_ANTLIONGUARD_PEEK_EXIT ) || |
|
( nActivity == ACT_ANTLIONGUARD_PEEK1 ) || |
|
( nActivity == ACT_ANTLIONGUARD_PEEK_SIGHTED ) || |
|
( nActivity == ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT ) || |
|
( nActivity == ACT_ANTLIONGUARD_PHYSHIT_FR ) || |
|
( nActivity == ACT_ANTLIONGUARD_PHYSHIT_FL ) || |
|
( nActivity == ACT_ANTLIONGUARD_PHYSHIT_RR ) || |
|
( nActivity == ACT_ANTLIONGUARD_PHYSHIT_RL ) || |
|
( nActivity == ACT_ANTLIONGUARD_CHARGE_CRASH ) || |
|
( nActivity == ACT_ANTLIONGUARD_CHARGE_HIT ) || |
|
( nActivity == ACT_ANTLIONGUARD_CHARGE_ANTICIPATION ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::UpdateHead( void ) |
|
{ |
|
float yaw = GetPoseParameter( m_poseHead_Yaw ); |
|
float pitch = GetPoseParameter( m_poseHead_Pitch ); |
|
|
|
// If we should be watching our enemy, turn our head |
|
if ( ShouldWatchEnemy() && ( GetEnemy() != NULL ) ) |
|
{ |
|
Vector enemyDir = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter(); |
|
VectorNormalize( enemyDir ); |
|
|
|
float angle = VecToYaw( BodyDirection3D() ); |
|
float angleDiff = VecToYaw( enemyDir ); |
|
angleDiff = UTIL_AngleDiff( angleDiff, angle + yaw ); |
|
|
|
SetPoseParameter( m_poseHead_Yaw, UTIL_Approach( yaw + angleDiff, yaw, 50 ) ); |
|
|
|
angle = UTIL_VecToPitch( BodyDirection3D() ); |
|
angleDiff = UTIL_VecToPitch( enemyDir ); |
|
angleDiff = UTIL_AngleDiff( angleDiff, angle + pitch ); |
|
|
|
SetPoseParameter( m_poseHead_Pitch, UTIL_Approach( pitch + angleDiff, pitch, 50 ) ); |
|
} |
|
else |
|
{ |
|
// Otherwise turn the head back to its normal position |
|
SetPoseParameter( m_poseHead_Yaw, UTIL_Approach( 0, yaw, 10 ) ); |
|
SetPoseParameter( m_poseHead_Pitch, UTIL_Approach( 0, pitch, 10 ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::MaintainPhysicsTarget( void ) |
|
{ |
|
if ( m_hPhysicsTarget == NULL || GetEnemy() == NULL ) |
|
return; |
|
|
|
// Update our current target to make sure it's still valid |
|
float flTargetDistSqr = ( m_hPhysicsTarget->WorldSpaceCenter() - m_vecPhysicsTargetStartPos ).LengthSqr(); |
|
bool bTargetMoved = ( flTargetDistSqr > Square(30*12.0f) ); |
|
bool bEnemyCloser = ( ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() <= flTargetDistSqr ); |
|
|
|
// Make sure this hasn't moved too far or that the player is now closer |
|
if ( bTargetMoved || bEnemyCloser ) |
|
{ |
|
ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID ); |
|
m_hPhysicsTarget = NULL; |
|
return; |
|
} |
|
else |
|
{ |
|
SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID ); |
|
} |
|
|
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::Cross3D( m_hPhysicsTarget->WorldSpaceCenter(), -Vector(32,32,32), Vector(32,32,32), 255, 255, 255, true, 1.0f ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::UpdatePhysicsTarget( bool bPreferObjectsAlongTargetVector, float flRadius ) |
|
{ |
|
if ( GetEnemy() == NULL ) |
|
return; |
|
|
|
// Already have a target, don't bother looking |
|
if ( m_hPhysicsTarget != NULL ) |
|
return; |
|
|
|
// Too soon to check again |
|
if ( m_flPhysicsCheckTime > gpGlobals->curtime ) |
|
return; |
|
|
|
// Attempt to find a valid shove target |
|
PhysicsObjectCriteria_t criteria; |
|
criteria.pTarget = GetEnemy(); |
|
criteria.vecCenter = GetEnemy()->GetAbsOrigin(); |
|
criteria.flRadius = flRadius; |
|
criteria.flTargetCone = ANTLIONGUARD_OBJECTFINDING_FOV; |
|
criteria.bPreferObjectsAlongTargetVector = bPreferObjectsAlongTargetVector; |
|
criteria.flNearRadius = (20*12); // TODO: It may preferable to disable this for legacy products as well -- jdw |
|
|
|
m_hPhysicsTarget = FindPhysicsObjectTarget( criteria ); |
|
|
|
// Found one, so interrupt us if we care |
|
if ( m_hPhysicsTarget != NULL ) |
|
{ |
|
SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
m_vecPhysicsTargetStartPos = m_hPhysicsTarget->WorldSpaceCenter(); |
|
} |
|
|
|
// Don't search again for another second |
|
m_flPhysicsCheckTime = gpGlobals->curtime + 1.0f; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Let the probe know I can run through small debris |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity ) |
|
{ |
|
if ( m_iszPhysicsPropClass != pEntity->m_iClassname ) |
|
return BaseClass::ShouldProbeCollideAgainstEntity( pEntity ); |
|
|
|
if ( m_hPhysicsTarget == pEntity ) |
|
return false; |
|
|
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
{ |
|
IPhysicsObject *pPhysObj = pEntity->VPhysicsGetObject(); |
|
|
|
if( pPhysObj && pPhysObj->GetMass() <= ANTLIONGUARD_MAX_OBJECT_MASS ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
return BaseClass::ShouldProbeCollideAgainstEntity( pEntity ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::PrescheduleThink( void ) |
|
{ |
|
BaseClass::PrescheduleThink(); |
|
|
|
// Don't do anything after death |
|
if ( m_NPCState == NPC_STATE_DEAD ) |
|
return; |
|
|
|
// If we're burrowed, then don't do any of this |
|
if ( m_bIsBurrowed ) |
|
return; |
|
|
|
// Automatically update our physics target when chasing enemies |
|
if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY ) || |
|
IsCurSchedule( SCHED_ANTLIONGUARD_PATROL_RUN ) || |
|
IsCurSchedule( SCHED_ANTLIONGUARD_CANT_ATTACK ) || |
|
IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE ) ) |
|
{ |
|
bool bCheckAlongLine = ( IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY ) || IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE ) ); |
|
UpdatePhysicsTarget( bCheckAlongLine ); |
|
} |
|
else if ( !IsCurSchedule( SCHED_ANTLIONGUARD_PHYSICS_ATTACK ) ) |
|
{ |
|
ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
m_hPhysicsTarget = NULL; |
|
} |
|
|
|
UpdateHead(); |
|
|
|
if ( ( m_flGroundSpeed <= 0.0f ) ) |
|
{ |
|
if ( m_bStopped == false ) |
|
{ |
|
StartSounds(); |
|
|
|
float duration = random->RandomFloat( 2.0f, 8.0f ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.0f, duration ); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 40, 60 ), duration ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, duration ); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pGrowlIdleSound, random->RandomInt( 120, 140 ), duration ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + duration - (duration*0.75f); |
|
} |
|
|
|
m_bStopped = true; |
|
|
|
if ( m_flBreathTime < gpGlobals->curtime ) |
|
{ |
|
StartSounds(); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, random->RandomFloat( 0.2f, 0.3f ), random->RandomFloat( 0.5f, 1.0f ) ); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pGrowlIdleSound, random->RandomInt( 80, 120 ), random->RandomFloat( 0.5f, 1.0f ) ); |
|
|
|
m_flBreathTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 8.0f ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_bStopped ) |
|
{ |
|
StartSounds(); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pBreathSound, 0.6f, random->RandomFloat( 2.0f, 4.0f ) ); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pBreathSound, random->RandomInt( 140, 160 ), random->RandomFloat( 2.0f, 4.0f ) ); |
|
|
|
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pGrowlIdleSound, 0.0f, 1.0f ); |
|
ENVELOPE_CONTROLLER.SoundChangePitch( m_pGrowlIdleSound, random->RandomInt( 90, 110 ), 0.2f ); |
|
} |
|
|
|
|
|
m_bStopped = false; |
|
} |
|
|
|
// Put danger sounds out in front of us |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
CSoundEnt::InsertSound( SOUND_DANGER, WorldSpaceCenter() + ( BodyDirection3D() * 128 * (i+1) ), 128, 0.1f, this ); |
|
} |
|
|
|
#if ANTLIONGUARD_BLOOD_EFFECTS |
|
// compute and if necessary transmit the bleeding level for the particle effect |
|
m_iBleedingLevel = GetBleedingLevel(); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::GatherConditions( void ) |
|
{ |
|
BaseClass::GatherConditions(); |
|
|
|
if ( CanSummon(false) ) |
|
{ |
|
SetCondition( COND_ANTLIONGUARD_CAN_SUMMON ); |
|
} |
|
else |
|
{ |
|
ClearCondition( COND_ANTLIONGUARD_CAN_SUMMON ); |
|
} |
|
|
|
// Make sure our physics target is still valid |
|
MaintainPhysicsTarget(); |
|
|
|
if( IsCurSchedule(SCHED_ANTLIONGUARD_PHYSICS_ATTACK) ) |
|
{ |
|
if( gpGlobals->curtime > m_flWaitFinished ) |
|
{ |
|
ClearCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
SetCondition( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID ); |
|
m_hPhysicsTarget = NULL; |
|
} |
|
} |
|
|
|
// See if we can charge the target |
|
if ( GetEnemy() ) |
|
{ |
|
if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) ) |
|
{ |
|
SetCondition( COND_ANTLIONGUARD_CAN_CHARGE ); |
|
} |
|
else |
|
{ |
|
ClearCondition( COND_ANTLIONGUARD_CAN_CHARGE ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::StopLoopingSounds() |
|
{ |
|
//Stop all sounds |
|
ENVELOPE_CONTROLLER.SoundDestroy( m_pGrowlHighSound ); |
|
ENVELOPE_CONTROLLER.SoundDestroy( m_pGrowlIdleSound ); |
|
ENVELOPE_CONTROLLER.SoundDestroy( m_pBreathSound ); |
|
ENVELOPE_CONTROLLER.SoundDestroy( m_pConfusedSound ); |
|
|
|
|
|
m_pGrowlHighSound = NULL; |
|
m_pGrowlIdleSound = NULL; |
|
m_pBreathSound = NULL; |
|
m_pConfusedSound = NULL; |
|
|
|
BaseClass::StopLoopingSounds(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputUnburrow( inputdata_t &inputdata ) |
|
{ |
|
if ( IsAlive() == false ) |
|
return; |
|
|
|
if ( m_bIsBurrowed == false ) |
|
return; |
|
|
|
m_spawnflags &= ~SF_NPC_GAG; |
|
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
|
|
m_takedamage = DAMAGE_YES; |
|
|
|
SetSchedule( SCHED_ANTLIONGUARD_UNBURROW ); |
|
|
|
m_bIsBurrowed = false; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : Returns true is entity was remembered as unreachable. |
|
// After a time delay reachability is checked |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
bool CNPC_AntlionGuard::IsUnreachable(CBaseEntity *pEntity) |
|
{ |
|
float UNREACHABLE_DIST_TOLERANCE_SQ = (240 * 240); |
|
|
|
// Note that it's ok to remove elements while I'm iterating |
|
// as long as I iterate backwards and remove them using FastRemove |
|
for (int i=m_UnreachableEnts.Size()-1;i>=0;i--) |
|
{ |
|
// Remove any dead elements |
|
if (m_UnreachableEnts[i].hUnreachableEnt == NULL) |
|
{ |
|
m_UnreachableEnts.FastRemove(i); |
|
} |
|
else if (pEntity == m_UnreachableEnts[i].hUnreachableEnt) |
|
{ |
|
// Test for reachability on any elements that have timed out |
|
if ( gpGlobals->curtime > m_UnreachableEnts[i].fExpireTime || |
|
pEntity->GetAbsOrigin().DistToSqr(m_UnreachableEnts[i].vLocationWhenUnreachable) > UNREACHABLE_DIST_TOLERANCE_SQ) |
|
{ |
|
m_UnreachableEnts.FastRemove(i); |
|
return false; |
|
} |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return the point at which the guard wants to stand on to knock the physics object at the target entity |
|
// Input : *pObject - Object to be shoved. |
|
// *pTarget - Target to be shoved at. |
|
// *vecTrajectory - Trajectory to our target |
|
// *flClearDistance - Distance behind the entity we're clear to use |
|
// Output : Position at which to attempt to strike the object |
|
//----------------------------------------------------------------------------- |
|
Vector CNPC_AntlionGuard::GetPhysicsHitPosition( CBaseEntity *pObject, CBaseEntity *pTarget, Vector *vecTrajectory, float *flClearDistance ) |
|
{ |
|
// Get the trajectory we want to knock the object along |
|
Vector vecToTarget = pTarget->WorldSpaceCenter() - pObject->WorldSpaceCenter(); |
|
VectorNormalize( vecToTarget ); |
|
vecToTarget.z = 0; |
|
|
|
// Get the distance we want to be from the object when we hit it |
|
IPhysicsObject *pPhys = pObject->VPhysicsGetObject(); |
|
Vector extent = physcollision->CollideGetExtent( pPhys->GetCollide(), pObject->GetAbsOrigin(), pObject->GetAbsAngles(), -vecToTarget ); |
|
float flDist = ( extent - pObject->WorldSpaceCenter() ).Length() + CollisionProp()->BoundingRadius() + 32.0f; |
|
|
|
if ( vecTrajectory != NULL ) |
|
{ |
|
*vecTrajectory = vecToTarget; |
|
} |
|
|
|
if ( flClearDistance != NULL ) |
|
{ |
|
*flClearDistance = flDist; |
|
} |
|
|
|
// Position at which we'd like to be |
|
return (pObject->WorldSpaceCenter() + ( vecToTarget * -flDist )); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if we're able to stand on the ground at this point |
|
// Input : &vecPos - Position to try |
|
// *pOut - Result position (only valid if we return true) |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
inline bool CNPC_AntlionGuard::CanStandAtPoint( const Vector &vecPos, Vector *pOut ) |
|
{ |
|
Vector vecStart = vecPos + Vector( 0, 0, (4*12) ); |
|
Vector vecEnd = vecPos - Vector( 0, 0, (4*12) ); |
|
trace_t tr; |
|
bool bTraceCleared = false; |
|
|
|
// Start high and try to go lower, looking for the ground between here and there |
|
// We do this first because it's more likely to succeed in the typical guard arenas (with open terrain) |
|
UTIL_TraceHull( vecStart, vecEnd, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.startsolid && !tr.allsolid ) |
|
{ |
|
// We started in solid but didn't end up there, see if we can stand where we ended up |
|
UTIL_TraceHull( tr.endpos, tr.endpos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
// Must not be in solid |
|
bTraceCleared = ( !tr.allsolid && !tr.startsolid ); |
|
} |
|
else |
|
{ |
|
// Must not be in solid and must have found a floor (otherwise we're potentially hanging over a ledge) |
|
bTraceCleared = ( tr.allsolid == false && tr.fraction < 1.0f ); |
|
} |
|
|
|
// Either we're clear or we're still unlucky |
|
if ( bTraceCleared == false ) |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 15.0f ); |
|
} |
|
return false; |
|
} |
|
|
|
if ( pOut ) |
|
{ |
|
*pOut = tr.endpos; |
|
} |
|
|
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::Box( (*pOut), GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 15.0f ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determines whether or not the guard can stand in a position to strike a specified object |
|
// Input : *pShoveObject - Object being shoved |
|
// *pTarget - Target we're shoving the object at |
|
// *pOut - The position we decide to stand at |
|
// Output : Returns true if the guard can stand and deliver. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::CanStandAtShoveTarget( CBaseEntity *pShoveObject, CBaseEntity *pTarget, Vector *pOut ) |
|
{ |
|
// Get the position we want to be at to swing at the object |
|
float flClearDistance; |
|
Vector vecTrajectory; |
|
Vector vecHitPosition = GetPhysicsHitPosition( pShoveObject, pTarget, &vecTrajectory, &flClearDistance ); |
|
Vector vecStandPosition; |
|
|
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::HorzArrow( pShoveObject->WorldSpaceCenter(), pShoveObject->WorldSpaceCenter() + vecTrajectory * 64.0f, 16.0f, 255, 255, 0, 16, true, 15.0f ); |
|
} |
|
|
|
// If we failed, try around the sides |
|
if ( CanStandAtPoint( vecHitPosition, &vecStandPosition ) == false ) |
|
{ |
|
// Get the angle (in reverse) to the target |
|
float flRad = atan2( -vecTrajectory.y, -vecTrajectory.x ); |
|
float flRadOffset = DEG2RAD( 45.0f ); |
|
|
|
// Build an offset vector, rotating around the base |
|
Vector vecSkewTrajectory; |
|
SinCos( flRad + flRadOffset, &vecSkewTrajectory.y, &vecSkewTrajectory.x ); |
|
vecSkewTrajectory.z = 0.0f; |
|
|
|
// Try to the side |
|
if ( CanStandAtPoint( ( pShoveObject->WorldSpaceCenter() + ( vecSkewTrajectory * flClearDistance ) ), &vecStandPosition ) == false ) |
|
{ |
|
// Try the other side |
|
SinCos( flRad - flRadOffset, &vecSkewTrajectory.y, &vecSkewTrajectory.x ); |
|
vecSkewTrajectory.z = 0.0f; |
|
if ( CanStandAtPoint( ( pShoveObject->WorldSpaceCenter() + ( vecSkewTrajectory * flClearDistance ) ), &vecStandPosition ) == false ) |
|
return false; |
|
} |
|
} |
|
|
|
// Found it, report it |
|
if ( pOut != NULL ) |
|
{ |
|
*pOut = vecStandPosition; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Iterate through a number of lists depending on our criteria |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CNPC_AntlionGuard::GetNextShoveTarget( CBaseEntity *pLastEntity, AISightIter_t &iter ) |
|
{ |
|
// Try to find scripted items first |
|
if ( m_strShoveTargets != NULL_STRING ) |
|
{ |
|
CBaseEntity *pFound = gEntList.FindEntityByName( pLastEntity, m_strShoveTargets ); |
|
if ( pFound ) |
|
return pFound; |
|
} |
|
|
|
// Failing that, use our senses |
|
if ( iter != (AISightIter_t)(-1) ) |
|
return GetSenses()->GetNextSeenEntity( &iter ); |
|
|
|
return GetSenses()->GetFirstSeenEntity( &iter, SEEN_MISC ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Search for a physics item to swat at the player |
|
// Output : Returns the object we're going to swat. |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CNPC_AntlionGuard::FindPhysicsObjectTarget( const PhysicsObjectCriteria_t &criteria ) |
|
{ |
|
// Must have a valid target entity |
|
if ( criteria.pTarget == NULL ) |
|
return NULL; |
|
|
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::Circle( GetAbsOrigin(), QAngle( -90, 0, 0 ), criteria.flRadius, 255, 0, 0, 8, true, 2.0f ); |
|
} |
|
|
|
// Get the vector to our target, from ourself |
|
Vector vecDirToTarget = criteria.pTarget->GetAbsOrigin() - GetAbsOrigin(); |
|
VectorNormalize( vecDirToTarget ); |
|
vecDirToTarget.z = 0; |
|
|
|
// Cost is determined by distance to the object, modified by how "in line" it is with our target direction of travel |
|
// Use the distance to the player as the base cost for throwing an object (avoids pushing things too close to the player) |
|
float flLeastCost = ( criteria.bPreferObjectsAlongTargetVector ) ? ( criteria.pTarget->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() : Square( criteria.flRadius ); |
|
float flCost; |
|
|
|
AISightIter_t iter = (AISightIter_t)(-1); |
|
CBaseEntity *pObject = NULL; |
|
CBaseEntity *pNearest = NULL; |
|
Vector vecBestHitPosition = vec3_origin; |
|
|
|
// Look through the list of sensed objects for possible targets |
|
while( ( pObject = GetNextShoveTarget( pObject, iter ) ) != NULL ) |
|
{ |
|
// If we couldn't shove this object last time, don't try again |
|
if ( m_FailedPhysicsTargets.Find( pObject ) != m_FailedPhysicsTargets.InvalidIndex() ) |
|
continue; |
|
|
|
// Ignore things less than half a foot in diameter |
|
if ( pObject->CollisionProp()->BoundingRadius() < 6.0f ) |
|
continue; |
|
|
|
IPhysicsObject *pPhysObj = pObject->VPhysicsGetObject(); |
|
if ( pPhysObj == NULL ) |
|
continue; |
|
|
|
// Ignore motion disabled props |
|
if ( pPhysObj->IsMoveable() == false ) |
|
continue; |
|
|
|
// Ignore things lighter than 5kg |
|
if ( pPhysObj->GetMass() < 5.0f ) |
|
continue; |
|
|
|
// Ignore objects moving too quickly (they'll be too hard to catch otherwise) |
|
Vector velocity; |
|
pPhysObj->GetVelocity( &velocity, NULL ); |
|
if ( velocity.LengthSqr() > (16*16) ) |
|
continue; |
|
|
|
// Get the direction from us to the physics object |
|
Vector vecDirToObject = pObject->WorldSpaceCenter() - GetAbsOrigin(); |
|
VectorNormalize( vecDirToObject ); |
|
vecDirToObject.z = 0; |
|
|
|
Vector vecObjCenter = pObject->WorldSpaceCenter(); |
|
float flDistSqr = 0.0f; |
|
float flDot = 0.0f; |
|
|
|
// If we want to find things along the vector to the target, do so |
|
if ( criteria.bPreferObjectsAlongTargetVector ) |
|
{ |
|
// Object must be closer than our target |
|
if ( ( GetAbsOrigin() - vecObjCenter ).LengthSqr() > ( GetAbsOrigin() - criteria.pTarget->GetAbsOrigin() ).LengthSqr() ) |
|
continue; |
|
|
|
// Calculate a "cost" to this object |
|
flDistSqr = ( GetAbsOrigin() - vecObjCenter ).LengthSqr(); |
|
flDot = DotProduct( vecDirToTarget, vecDirToObject ); |
|
|
|
// Ignore things outside our allowed cone |
|
if ( flDot < criteria.flTargetCone ) |
|
continue; |
|
|
|
// The more perpendicular we are, the higher the cost |
|
float flCostScale = RemapValClamped( flDot, 1.0f, criteria.flTargetCone, 1.0f, 4.0f ); |
|
flCost = flDistSqr * flCostScale; |
|
} |
|
else |
|
{ |
|
// Straight distance cost |
|
flCost = ( criteria.vecCenter - vecObjCenter ).LengthSqr(); |
|
} |
|
|
|
// This must be a less costly object to use |
|
if ( flCost >= flLeastCost ) |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::Box( vecObjCenter, -Vector(16,16,16), Vector(16,16,16), 255, 0, 0, 0, 2.0f ); |
|
} |
|
|
|
continue; |
|
} |
|
|
|
// Check for a (roughly) clear trajectory path from the object to target |
|
trace_t tr; |
|
UTIL_TraceLine( vecObjCenter, criteria.pTarget->BodyTarget( vecObjCenter ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
// See how close to our target we got (we still look good hurling things that won't necessarily hit) |
|
if ( ( tr.endpos - criteria.pTarget->WorldSpaceCenter() ).LengthSqr() > Square(criteria.flNearRadius) ) |
|
continue; |
|
|
|
// Must be able to stand at a position to hit the object |
|
Vector vecHitPosition; |
|
if ( CanStandAtShoveTarget( pObject, criteria.pTarget, &vecHitPosition ) == false ) |
|
{ |
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::HorzArrow( GetAbsOrigin(), pObject->WorldSpaceCenter(), 32.0f, 255, 0, 0, 64, true, 2.0f ); |
|
} |
|
continue; |
|
} |
|
|
|
// Take this as the best object so far |
|
pNearest = pObject; |
|
flLeastCost = flCost; |
|
vecBestHitPosition = vecHitPosition; |
|
|
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::HorzArrow( GetAbsOrigin(), pObject->WorldSpaceCenter(), 16.0f, 255, 255, 0, 0, true, 2.0f ); |
|
} |
|
} |
|
|
|
// Set extra info if we've succeeded |
|
if ( pNearest != NULL ) |
|
{ |
|
m_vecPhysicsHitPosition = vecBestHitPosition; |
|
|
|
if ( g_debug_antlionguard.GetInt() == 3 ) |
|
{ |
|
NDebugOverlay::HorzArrow( GetAbsOrigin(), pNearest->WorldSpaceCenter(), 32.0f, 0, 255, 0, 128, true, 2.0f ); |
|
} |
|
} |
|
|
|
return pNearest; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Allows for modification of the interrupt mask for the current schedule. |
|
// In the most cases the base implementation should be called first. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::BuildScheduleTestBits( void ) |
|
{ |
|
BaseClass::BuildScheduleTestBits(); |
|
|
|
// Interrupt if we can shove something |
|
if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHASE_ENEMY ) ) |
|
{ |
|
SetCustomInterruptCondition( COND_ANTLIONGUARD_PHYSICS_TARGET ); |
|
SetCustomInterruptCondition( COND_ANTLIONGUARD_CAN_SUMMON ); |
|
} |
|
|
|
// Interrupt if we've been given a charge target |
|
if ( IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) == false ) |
|
{ |
|
SetCustomInterruptCondition( COND_ANTLIONGUARD_HAS_CHARGE_TARGET ); |
|
} |
|
|
|
// Once we commit to doing this, just do it! |
|
if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) ) |
|
{ |
|
ClearCustomInterruptCondition( COND_ENEMY_OCCLUDED ); |
|
} |
|
|
|
// Always take heavy damage |
|
SetCustomInterruptCondition( COND_HEAVY_DAMAGE ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &origin - |
|
// radius - |
|
// magnitude - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::ImpactShock( const Vector &origin, float radius, float magnitude, CBaseEntity *pIgnored ) |
|
{ |
|
// Also do a local physics explosion to push objects away |
|
float adjustedDamage, flDist; |
|
Vector vecSpot; |
|
float falloff = 1.0f / 2.5f; |
|
|
|
CBaseEntity *pEntity = NULL; |
|
|
|
// Find anything within our radius |
|
while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, origin, radius ) ) != NULL ) |
|
{ |
|
// Don't affect the ignored target |
|
if ( pEntity == pIgnored ) |
|
continue; |
|
if ( pEntity == this ) |
|
continue; |
|
|
|
// UNDONE: Ask the object if it should get force if it's not MOVETYPE_VPHYSICS? |
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || ( pEntity->VPhysicsGetObject() && pEntity->IsPlayer() == false ) ) |
|
{ |
|
vecSpot = pEntity->BodyTarget( GetAbsOrigin() ); |
|
|
|
// decrease damage for an ent that's farther from the bomb. |
|
flDist = ( GetAbsOrigin() - vecSpot ).Length(); |
|
|
|
if ( radius == 0 || flDist <= radius ) |
|
{ |
|
adjustedDamage = flDist * falloff; |
|
adjustedDamage = magnitude - adjustedDamage; |
|
|
|
if ( adjustedDamage < 1 ) |
|
{ |
|
adjustedDamage = 1; |
|
} |
|
|
|
CTakeDamageInfo info( this, this, adjustedDamage, DMG_BLAST ); |
|
CalculateExplosiveDamageForce( &info, (vecSpot - GetAbsOrigin()), GetAbsOrigin() ); |
|
|
|
pEntity->VPhysicsTakeDamage( info ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTarget - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::ChargeDamage( CBaseEntity *pTarget ) |
|
{ |
|
if ( pTarget == NULL ) |
|
return; |
|
|
|
CBasePlayer *pPlayer = ToBasePlayer( pTarget ); |
|
|
|
if ( pPlayer != NULL ) |
|
{ |
|
//Kick the player angles |
|
pPlayer->ViewPunch( QAngle( 20, 20, -30 ) ); |
|
|
|
Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter(); |
|
VectorNormalize( dir ); |
|
dir.z = 0.0f; |
|
|
|
Vector vecNewVelocity = dir * 250.0f; |
|
vecNewVelocity[2] += 128.0f; |
|
pPlayer->SetAbsVelocity( vecNewVelocity ); |
|
|
|
color32 red = {128,0,0,128}; |
|
UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN ); |
|
} |
|
|
|
// Player takes less damage |
|
float flDamage = ( pPlayer == NULL ) ? 250 : sk_antlionguard_dmg_charge.GetFloat(); |
|
|
|
// If it's being held by the player, break that bond |
|
Pickup_ForcePlayerToDropThisObject( pTarget ); |
|
|
|
// Calculate the physics force |
|
ApplyChargeDamage( this, pTarget, flDamage ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputRagdoll( inputdata_t &inputdata ) |
|
{ |
|
if ( IsAlive() == false ) |
|
return; |
|
|
|
//Set us to nearly dead so the velocity from death is minimal |
|
SetHealth( 1 ); |
|
|
|
CTakeDamageInfo info( this, this, GetHealth(), DMG_CRUSH ); |
|
BaseClass::TakeDamage( info ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: make m_bPreferPhysicsAttack true |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputEnablePreferPhysicsAttack( inputdata_t &inputdata ) |
|
{ |
|
m_bPreferPhysicsAttack = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: make m_bPreferPhysicsAttack false |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputDisablePreferPhysicsAttack( inputdata_t &inputdata ) |
|
{ |
|
m_bPreferPhysicsAttack = false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CNPC_AntlionGuard::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) |
|
{ |
|
// Figure out what to do next |
|
if ( failedSchedule == SCHED_ANTLIONGUARD_CHASE_ENEMY && HasCondition( COND_ENEMY_UNREACHABLE ) ) |
|
return SelectUnreachableSchedule(); |
|
|
|
return BaseClass::SelectFailSchedule( failedSchedule,failedTask, taskFailCode ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : scheduleType - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CNPC_AntlionGuard::TranslateSchedule( int scheduleType ) |
|
{ |
|
switch( scheduleType ) |
|
{ |
|
case SCHED_CHASE_ENEMY: |
|
return SCHED_ANTLIONGUARD_CHASE_ENEMY; |
|
break; |
|
} |
|
|
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::StartSounds( void ) |
|
{ |
|
//Initialize the additive sound channels |
|
CPASAttenuationFilter filter( this ); |
|
|
|
if ( m_pGrowlHighSound == NULL ) |
|
{ |
|
m_pGrowlHighSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_VOICE, "NPC_AntlionGuard.GrowlHigh", ATTN_NORM ); |
|
|
|
if ( m_pGrowlHighSound ) |
|
{ |
|
ENVELOPE_CONTROLLER.Play( m_pGrowlHighSound,0.0f, 100 ); |
|
} |
|
} |
|
|
|
if ( m_pGrowlIdleSound == NULL ) |
|
{ |
|
m_pGrowlIdleSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_STATIC, "NPC_AntlionGuard.GrowlIdle", ATTN_NORM ); |
|
|
|
if ( m_pGrowlIdleSound ) |
|
{ |
|
ENVELOPE_CONTROLLER.Play( m_pGrowlIdleSound,0.0f, 100 ); |
|
} |
|
} |
|
|
|
if ( m_pBreathSound == NULL ) |
|
{ |
|
m_pBreathSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_ITEM, "NPC_AntlionGuard.BreathSound", ATTN_NORM ); |
|
|
|
if ( m_pBreathSound ) |
|
{ |
|
ENVELOPE_CONTROLLER.Play( m_pBreathSound, 0.0f, 100 ); |
|
} |
|
} |
|
|
|
if ( m_pConfusedSound == NULL ) |
|
{ |
|
m_pConfusedSound = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_WEAPON,"NPC_AntlionGuard.Confused", ATTN_NORM ); |
|
|
|
if ( m_pConfusedSound ) |
|
{ |
|
ENVELOPE_CONTROLLER.Play( m_pConfusedSound, 0.0f, 100 ); |
|
} |
|
} |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputEnableBark( inputdata_t &inputdata ) |
|
{ |
|
m_bBarkEnabled = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputDisableBark( inputdata_t &inputdata ) |
|
{ |
|
m_bBarkEnabled = false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
EmitSound( "NPC_AntlionGuard.Die" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
BaseClass::Event_Killed( info ); |
|
|
|
// Tell all of my antlions to burrow away, 'cos they fear the Freeman |
|
if ( m_iNumLiveAntlions ) |
|
{ |
|
CBaseEntity *pSearch = NULL; |
|
|
|
// Iterate through all antlions and see if there are any orphans |
|
while ( ( pSearch = gEntList.FindEntityByClassname( pSearch, "npc_antlion" ) ) != NULL ) |
|
{ |
|
CNPC_Antlion *pAntlion = assert_cast<CNPC_Antlion *>(pSearch); |
|
|
|
// See if it's a live orphan |
|
if ( pAntlion && pAntlion->GetOwnerEntity() == NULL && pAntlion->IsAlive() ) |
|
{ |
|
g_EventQueue.AddEvent( pAntlion, "BurrowAway", RandomFloat(0.1, 2.0), this, this ); |
|
} |
|
} |
|
} |
|
|
|
DestroyGlows(); |
|
|
|
// If I'm bleeding, stop due to decreased pressure of hemolymph after |
|
// cessation of aortic contraction |
|
#if ANTLIONGUARD_BLOOD_EFFECTS |
|
m_iBleedingLevel = 0; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Don't become a ragdoll until we've finished our death anim |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::CanBecomeRagdoll( void ) |
|
{ |
|
if ( IsCurSchedule( SCHED_DIE ) ) |
|
return true; |
|
|
|
return hl2_episodic.GetBool(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &force - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::BecomeRagdollOnClient( const Vector &force ) |
|
{ |
|
if ( !CanBecomeRagdoll() ) |
|
return false; |
|
|
|
EmitSound( "NPC_AntlionGuard.Fallover" ); |
|
|
|
// Become server-side ragdoll if we're flagged to do it |
|
if ( m_spawnflags & SF_ANTLIONGUARD_SERVERSIDE_RAGDOLL ) |
|
{ |
|
CTakeDamageInfo info; |
|
|
|
// Fake the info |
|
info.SetDamageType( DMG_GENERIC ); |
|
info.SetDamageForce( force ); |
|
info.SetDamagePosition( WorldSpaceCenter() ); |
|
|
|
CBaseEntity *pRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_NONE ); |
|
|
|
// Transfer our name to the new ragdoll |
|
pRagdoll->SetName( GetEntityName() ); |
|
pRagdoll->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); |
|
|
|
// Get rid of our old body |
|
UTIL_Remove(this); |
|
|
|
return true; |
|
} |
|
|
|
return BaseClass::BecomeRagdollOnClient( force ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Override how we face our target as we move |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ) |
|
{ |
|
Vector vecFacePosition = vec3_origin; |
|
CBaseEntity *pFaceTarget = NULL; |
|
bool bFaceTarget = false; |
|
|
|
// FIXME: this will break scripted sequences that walk when they have an enemy |
|
if ( m_hChargeTarget ) |
|
{ |
|
vecFacePosition = m_hChargeTarget->GetAbsOrigin(); |
|
pFaceTarget = m_hChargeTarget; |
|
bFaceTarget = true; |
|
} |
|
#ifdef HL2_EPISODIC |
|
else if ( GetEnemy() && IsCurSchedule( SCHED_ANTLIONGUARD_CANT_ATTACK ) ) |
|
{ |
|
// Always face our enemy when randomly patrolling around |
|
vecFacePosition = GetEnemy()->EyePosition(); |
|
pFaceTarget = GetEnemy(); |
|
bFaceTarget = true; |
|
} |
|
#endif // HL2_EPISODIC |
|
else if ( GetEnemy() && GetNavigator()->GetMovementActivity() == ACT_RUN ) |
|
{ |
|
Vector vecEnemyLKP = GetEnemyLKP(); |
|
|
|
// Only start facing when we're close enough |
|
if ( ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < 512 ) || IsCurSchedule( SCHED_ANTLIONGUARD_PATROL_RUN ) ) |
|
{ |
|
vecFacePosition = vecEnemyLKP; |
|
pFaceTarget = GetEnemy(); |
|
bFaceTarget = true; |
|
} |
|
} |
|
|
|
// Face |
|
if ( bFaceTarget ) |
|
{ |
|
AddFacingTarget( pFaceTarget, vecFacePosition, 1.0, 0.2 ); |
|
} |
|
|
|
return BaseClass::OverrideMoveFacing( move, flInterval ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::IsHeavyDamage( const CTakeDamageInfo &info ) |
|
{ |
|
// Struck by blast |
|
if ( info.GetDamageType() & DMG_BLAST ) |
|
{ |
|
if ( info.GetDamage() > MIN_BLAST_DAMAGE ) |
|
return true; |
|
} |
|
|
|
// Struck by large object |
|
if ( info.GetDamageType() & DMG_CRUSH ) |
|
{ |
|
IPhysicsObject *pPhysObject = info.GetInflictor()->VPhysicsGetObject(); |
|
|
|
if ( ( pPhysObject != NULL ) && ( pPhysObject->GetGameFlags() & FVPHYSICS_WAS_THROWN ) ) |
|
{ |
|
// Always take hits from a combine ball |
|
if ( UTIL_IsAR2CombineBall( info.GetInflictor() ) ) |
|
return true; |
|
|
|
// If we're under half health, stop being interrupted by heavy damage |
|
if ( GetHealth() < (GetMaxHealth() * 0.25) ) |
|
return false; |
|
|
|
// Ignore physics damages that don't do much damage |
|
if ( info.GetDamage() < MIN_CRUSH_DAMAGE ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::IsLightDamage( const CTakeDamageInfo &info ) |
|
{ |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pChild - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::InputSummonedAntlionDied( inputdata_t &inputdata ) |
|
{ |
|
m_iNumLiveAntlions--; |
|
Assert( m_iNumLiveAntlions >= 0 ); |
|
|
|
if ( g_debug_antlionguard.GetInt() == 2 ) |
|
{ |
|
Msg("Guard summoned antlion count: %d\n", m_iNumLiveAntlions ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Filter out sounds we don't care about |
|
// Input : *pSound - sound to test against |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::QueryHearSound( CSound *pSound ) |
|
{ |
|
// Don't bother with danger sounds from antlions or other guards |
|
if ( pSound->SoundType() == SOUND_DANGER && ( pSound->m_hOwner != NULL && pSound->m_hOwner->Classify() == CLASS_ANTLION ) ) |
|
return false; |
|
|
|
return BaseClass::QueryHearSound( pSound ); |
|
} |
|
|
|
|
|
#if HL2_EPISODIC |
|
//--------------------------------------------------------- |
|
// Prevent the cavern guard from using stopping paths, as it occasionally forces him off the navmesh. |
|
//--------------------------------------------------------- |
|
bool CNPC_AntlionGuard::CNavigator::GetStoppingPath( CAI_WaypointList *pClippedWaypoints ) |
|
{ |
|
if (GetOuter()->m_bInCavern) |
|
{ |
|
return false; |
|
} |
|
else |
|
{ |
|
return BaseClass::GetStoppingPath( pClippedWaypoints ); |
|
} |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTarget - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::RememberFailedPhysicsTarget( CBaseEntity *pTarget ) |
|
{ |
|
// Already in the list? |
|
if ( m_FailedPhysicsTargets.Find( pTarget ) != m_FailedPhysicsTargets.InvalidIndex() ) |
|
return true; |
|
|
|
// We're not holding on to any more |
|
if ( ( m_FailedPhysicsTargets.Count() + 1 ) > MAX_FAILED_PHYSOBJECTS ) |
|
return false; |
|
|
|
m_FailedPhysicsTargets.AddToTail( pTarget ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle squad or NPC interactions |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_AntlionGuard::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender ) |
|
{ |
|
// Don't chase targets that other guards in our squad may be going after! |
|
if ( interactionType == g_interactionAntlionGuardFoundPhysicsObject ) |
|
{ |
|
RememberFailedPhysicsTarget( (CBaseEntity *) data ); |
|
return true; |
|
} |
|
|
|
// Mark a shoved object as being free to pursue again |
|
if ( interactionType == g_interactionAntlionGuardShovedPhysicsObject ) |
|
{ |
|
CBaseEntity *pObject = (CBaseEntity *) data; |
|
m_FailedPhysicsTargets.FindAndRemove( pObject ); |
|
return true; |
|
} |
|
|
|
return BaseClass::HandleInteraction( interactionType, data, sender ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Cache whatever pose parameters we intend to use |
|
//----------------------------------------------------------------------------- |
|
void CNPC_AntlionGuard::PopulatePoseParameters( void ) |
|
{ |
|
m_poseThrow = LookupPoseParameter("throw"); |
|
m_poseHead_Pitch = LookupPoseParameter("head_pitch"); |
|
m_poseHead_Yaw = LookupPoseParameter("head_yaw" ); |
|
|
|
BaseClass::PopulatePoseParameters(); |
|
} |
|
|
|
#if ANTLIONGUARD_BLOOD_EFFECTS |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return desired level for the continuous bleeding effect (not the |
|
// individual blood spurts you see per bullet hit) |
|
// Return 0 for don't bleed, |
|
// 1 for mild bleeding |
|
// 2 for severe bleeding |
|
//----------------------------------------------------------------------------- |
|
unsigned char CNPC_AntlionGuard::GetBleedingLevel( void ) const |
|
{ |
|
if ( m_iHealth > ( m_iMaxHealth >> 1 ) ) |
|
{ // greater than 50% |
|
return 0; |
|
} |
|
else if ( m_iHealth > ( m_iMaxHealth >> 2 ) ) |
|
{ // less than 50% but greater than 25% |
|
return 1; |
|
} |
|
else |
|
{ |
|
return 2; |
|
} |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Schedules |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
AI_BEGIN_CUSTOM_NPC( npc_antlionguard, CNPC_AntlionGuard ) |
|
|
|
// Interactions |
|
DECLARE_INTERACTION( g_interactionAntlionGuardFoundPhysicsObject ) |
|
DECLARE_INTERACTION( g_interactionAntlionGuardShovedPhysicsObject ) |
|
|
|
// Squadslots |
|
DECLARE_SQUADSLOT( SQUAD_SLOT_ANTLIONGUARD_CHARGE ) |
|
|
|
//Tasks |
|
DECLARE_TASK( TASK_ANTLIONGUARD_CHARGE ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_SUMMON ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_GET_CHASE_PATH_ENEMY_TOLERANCE ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_OPPORTUNITY_THROW ) |
|
DECLARE_TASK( TASK_ANTLIONGUARD_FIND_PHYSOBJECT ) |
|
|
|
//Activities |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_SEARCH ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_FLINCH ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_ENTER ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_EXIT ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK1 ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_BARK ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PEEK_SIGHTED ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_START ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_CANCEL ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_RUN ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_CRASH ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_STOP ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_HIT ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_CHARGE_ANTICIPATION ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_FLINCH_LIGHT ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_UNBURROW ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_ROAR ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_RUN_HURT ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_FR ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_FL ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_RR ) |
|
DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_RL ) |
|
|
|
//Adrian: events go here |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_CHARGE_HIT ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_SHOVE_PHYSOBJECT ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_SHOVE ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_FOOTSTEP_LIGHT ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_FOOTSTEP_HEAVY ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_CHARGE_EARLYOUT ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_GROWL ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_BARK ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_PAIN ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_SQUEEZE ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_SCRATCH ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_GRUNT ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_BURROW_OUT ) |
|
DECLARE_ANIMEVENT( AE_ANTLIONGUARD_VOICE_ROAR ) |
|
|
|
DECLARE_CONDITION( COND_ANTLIONGUARD_PHYSICS_TARGET ) |
|
DECLARE_CONDITION( COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID ) |
|
DECLARE_CONDITION( COND_ANTLIONGUARD_HAS_CHARGE_TARGET ) |
|
DECLARE_CONDITION( COND_ANTLIONGUARD_CAN_SUMMON ) |
|
DECLARE_CONDITION( COND_ANTLIONGUARD_CAN_CHARGE ) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_SUMMON |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_SUMMON, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_BARK" |
|
" TASK_ANTLIONGUARD_SUMMON 0" |
|
" TASK_ANTLIONGUARD_OPPORTUNITY_THROW 0" |
|
" " |
|
" Interrupts" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_CHARGE |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CHARGE, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CHASE_ENEMY" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_ANTLIONGUARD_CHARGE 0" |
|
"" |
|
" Interrupts" |
|
" COND_TASK_FAILED" |
|
" COND_HEAVY_DAMAGE" |
|
|
|
// These are deliberately left out so they can be detected during the |
|
// charge Task and correctly play the charge stop animation. |
|
//" COND_NEW_ENEMY" |
|
//" COND_ENEMY_DEAD" |
|
//" COND_LOST_ENEMY" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_CHARGE_TARGET |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CHARGE_TARGET, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_ANTLIONGUARD_CHARGE 0" |
|
"" |
|
" Interrupts" |
|
" COND_TASK_FAILED" |
|
" COND_ENEMY_DEAD" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_CHARGE_SMASH |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CHARGE_CRASH, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_CHARGE_CRASH" |
|
"" |
|
" Interrupts" |
|
" COND_TASK_FAILED" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_PHYSICS_ATTACK |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_PHYSICS_ATTACK, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CHASE_ENEMY" |
|
" TASK_ANTLIONGUARD_GET_PATH_TO_PHYSOBJECT 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_ANTLIONGUARD_SHOVE_PHYSOBJECT 0" |
|
"" |
|
" Interrupts" |
|
" COND_TASK_FAILED" |
|
" COND_ENEMY_DEAD" |
|
" COND_LOST_ENEMY" |
|
" COND_NEW_ENEMY" |
|
" COND_ANTLIONGUARD_PHYSICS_TARGET_INVALID" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//================================================== |
|
// SCHED_FORCE_ANTLIONGUARD_PHYSICS_ATTACK |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_FORCE_ANTLIONGUARD_PHYSICS_ATTACK, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CANT_ATTACK" |
|
" TASK_ANTLIONGUARD_FIND_PHYSOBJECT 0" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_PHYSICS_ATTACK" |
|
"" |
|
" Interrupts" |
|
" COND_ANTLIONGUARD_PHYSICS_TARGET" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_CANT_ATTACK |
|
// If we're here, the guard can't chase enemy, can't find a physobject to attack with, and can't summon |
|
//================================================== |
|
|
|
#ifdef HL2_EPISODIC |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CANT_ATTACK, |
|
|
|
" Tasks" |
|
" TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 5 seconds trying to build a path if stuck |
|
" TASK_GET_PATH_TO_RANDOM_NODE 1024" |
|
" TASK_WALK_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_WAIT_PVS 0" |
|
"" |
|
" Interrupts" |
|
" COND_GIVE_WAY" |
|
" COND_NEW_ENEMY" |
|
" COND_ANTLIONGUARD_PHYSICS_TARGET" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
#else |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CANT_ATTACK, |
|
|
|
" Tasks" |
|
" TASK_WAIT 5" |
|
"" |
|
" Interrupts" |
|
) |
|
|
|
#endif |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_PHYSICS_DAMAGE_HEAVY, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_RESET_ACTIVITY 0" |
|
" TASK_ANTLIONGUARD_SET_FLINCH_ACTIVITY 0" |
|
"" |
|
" Interrupts" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_UNBURROW |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_UNBURROW, |
|
|
|
" Tasks" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_UNBURROW" |
|
"" |
|
" Interrupts" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_CHARGE_CANCEL |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CHARGE_CANCEL, |
|
|
|
" Tasks" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_CHARGE_CANCEL" |
|
"" |
|
" Interrupts" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_FIND_CHARGE_POSITION, |
|
|
|
" Tasks" |
|
" TASK_ANTLIONGUARD_GET_PATH_TO_CHARGE_POSITION 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" " |
|
" Interrupts" |
|
" COND_ENEMY_DEAD" |
|
" COND_GIVE_WAY" |
|
" COND_TASK_FAILED" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CHASE_ENEMY_TOLERANCE, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_PATROL_RUN" |
|
" TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE 500" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_FACE_ENEMY 0" |
|
"" |
|
" Interrupts" |
|
" COND_TASK_FAILED" |
|
" COND_CAN_MELEE_ATTACK1" |
|
" COND_GIVE_WAY" |
|
" COND_NEW_ENEMY" |
|
" COND_ANTLIONGUARD_CAN_SUMMON" |
|
" COND_ANTLIONGUARD_PHYSICS_TARGET" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_ANTLIONGUARD_CAN_CHARGE" |
|
); |
|
|
|
//========================================================= |
|
// > PATROL_RUN |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_PATROL_RUN, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CANT_ATTACK" |
|
" TASK_SET_ROUTE_SEARCH_TIME 3" // Spend 3 seconds trying to build a path if stuck |
|
" TASK_ANTLIONGUARD_GET_PATH_TO_NEAREST_NODE 500" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
"" |
|
" Interrupts" |
|
" COND_TASK_FAILED" |
|
" COND_CAN_MELEE_ATTACK1" |
|
" COND_GIVE_WAY" |
|
" COND_NEW_ENEMY" |
|
" COND_ANTLIONGUARD_PHYSICS_TARGET" |
|
" COND_ANTLIONGUARD_CAN_SUMMON" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_ANTLIONGUARD_CAN_CHARGE" |
|
); |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_ROAR |
|
//================================================== |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_ROAR, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_ANTLIONGUARD_ROAR" |
|
" " |
|
" Interrupts" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//================================================== |
|
// SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY |
|
//================================================== |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_TAKE_COVER_FROM_ENEMY, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGUARD_CANT_ATTACK" |
|
" TASK_FIND_COVER_FROM_ENEMY 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_STOP_MOVING 0" |
|
"" |
|
" Interrupts" |
|
" COND_TASK_FAILED" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_ANTLIONGUARD_PHYSICS_TARGET" |
|
" COND_ANTLIONGUARD_CAN_SUMMON" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//========================================================= |
|
// SCHED_ANTLIONGUARD_CHASE_ENEMY |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ANTLIONGUARD_CHASE_ENEMY, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_GET_CHASE_PATH_TO_ENEMY 300" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_FACE_ENEMY 0" |
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_ENEMY_UNREACHABLE" |
|
" COND_CAN_RANGE_ATTACK1" |
|
// " COND_CAN_MELEE_ATTACK1" |
|
" COND_CAN_RANGE_ATTACK2" |
|
" COND_CAN_MELEE_ATTACK2" |
|
" COND_TOO_CLOSE_TO_ATTACK" |
|
" COND_TASK_FAILED" |
|
" COND_LOST_ENEMY" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_ANTLIONGUARD_CAN_CHARGE" |
|
) |
|
|
|
AI_END_CUSTOM_NPC()
|
|
|