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.
979 lines
28 KiB
979 lines
28 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Antlion Grub - cannon fodder |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "gib.h" |
|
#include "Sprite.h" |
|
#include "te_effect_dispatch.h" |
|
#include "npc_antliongrub.h" |
|
#include "ai_utils.h" |
|
#include "particle_parse.h" |
|
#include "items.h" |
|
#include "item_dynamic_resupply.h" |
|
#include "npc_vortigaunt_episodic.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar sk_grubnugget_health_small( "sk_grubnugget_health_small", "1" ); |
|
ConVar sk_grubnugget_health_medium( "sk_grubnugget_health_medium", "4" ); |
|
ConVar sk_grubnugget_health_large( "sk_grubnugget_health_large", "6" ); |
|
ConVar sk_grubnugget_enabled( "sk_grubnugget_enabled", "1" ); |
|
|
|
#define ANTLIONGRUB_MODEL "models/antlion_grub.mdl" |
|
#define ANTLIONGRUB_SQUASHED_MODEL "models/antlion_grub_squashed.mdl" |
|
|
|
#define SF_ANTLIONGRUB_NO_AUTO_PLACEMENT (1<<0) |
|
|
|
|
|
enum GrubState_e |
|
{ |
|
GRUB_STATE_IDLE, |
|
GRUB_STATE_AGITATED, |
|
}; |
|
|
|
enum |
|
{ |
|
NUGGET_NONE, |
|
NUGGET_SMALL = 1, |
|
NUGGET_MEDIUM, |
|
NUGGET_LARGE |
|
}; |
|
|
|
// |
|
// Grub nugget |
|
// |
|
|
|
class CGrubNugget : public CItem |
|
{ |
|
public: |
|
DECLARE_CLASS( CGrubNugget, CItem ); |
|
|
|
virtual void Spawn( void ); |
|
virtual void Precache( void ); |
|
virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); |
|
virtual void Event_Killed( const CTakeDamageInfo &info ); |
|
virtual bool VPhysicsIsFlesh( void ); |
|
|
|
bool MyTouch( CBasePlayer *pPlayer ); |
|
void SetDenomination( int nSize ) { Assert( nSize <= NUGGET_LARGE && nSize >= NUGGET_SMALL ); m_nDenomination = nSize; } |
|
|
|
DECLARE_DATADESC(); |
|
|
|
private: |
|
int m_nDenomination; // Denotes size and health amount given |
|
}; |
|
|
|
BEGIN_DATADESC( CGrubNugget ) |
|
DEFINE_FIELD( m_nDenomination, FIELD_INTEGER ), |
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( item_grubnugget, CGrubNugget ); |
|
|
|
// |
|
// Simple grub |
|
// |
|
|
|
class CAntlionGrub : public CBaseAnimating |
|
{ |
|
public: |
|
DECLARE_CLASS( CAntlionGrub, CBaseAnimating ); |
|
|
|
virtual void Activate( void ); |
|
virtual void Spawn( void ); |
|
virtual void Precache( void ); |
|
virtual void UpdateOnRemove( void ); |
|
virtual void Event_Killed( const CTakeDamageInfo &info ); |
|
virtual int OnTakeDamage( const CTakeDamageInfo &info ); |
|
virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); |
|
|
|
void InputSquash( inputdata_t &data ); |
|
|
|
void IdleThink( void ); |
|
void FlinchThink( void ); |
|
void GrubTouch( CBaseEntity *pOther ); |
|
|
|
DECLARE_DATADESC(); |
|
|
|
protected: |
|
|
|
inline bool InPVS( void ); |
|
void SetNextThinkByDistance( void ); |
|
|
|
int GetNuggetDenomination( void ); |
|
void CreateNugget( void ); |
|
void MakeIdleSounds( void ); |
|
void MakeSquashDecals( const Vector &vecOrigin ); |
|
void AttachToSurface( void ); |
|
void CreateGlow( void ); |
|
void FadeGlow( void ); |
|
void Squash( CBaseEntity *pOther, bool bDealDamage, bool bSpawnBlood ); |
|
void SpawnSquashedGrub( void ); |
|
void InputAgitate( inputdata_t &inputdata ); |
|
|
|
inline bool ProbeSurface( const Vector &vecTestPos, const Vector &vecDir, Vector *vecResult, Vector *vecNormal ); |
|
|
|
CHandle<CSprite> m_hGlowSprite; |
|
int m_nGlowSpriteHandle; |
|
float m_flFlinchTime; |
|
float m_flNextIdleSoundTime; |
|
float m_flNextSquealSoundTime; |
|
bool m_bOutsidePVS; |
|
GrubState_e m_State; |
|
|
|
COutputEvent m_OnAgitated; |
|
COutputEvent m_OnDeath; |
|
COutputEvent m_OnDeathByPlayer; |
|
}; |
|
|
|
BEGIN_DATADESC( CAntlionGrub ) |
|
|
|
DEFINE_FIELD( m_hGlowSprite, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_flFlinchTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flNextIdleSoundTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flNextSquealSoundTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_State, FIELD_INTEGER ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "Agitate", InputAgitate ), |
|
|
|
DEFINE_OUTPUT( m_OnAgitated, "OnAgitated" ), |
|
DEFINE_OUTPUT( m_OnDeath, "OnDeath" ), |
|
DEFINE_OUTPUT( m_OnDeathByPlayer, "OnDeathByPlayer" ), |
|
|
|
// Functions |
|
DEFINE_ENTITYFUNC( GrubTouch ), |
|
DEFINE_ENTITYFUNC( IdleThink ), |
|
DEFINE_ENTITYFUNC( FlinchThink ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Squash", InputSquash ), |
|
|
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( npc_antlion_grub, CAntlionGrub ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::CreateGlow( void ) |
|
{ |
|
// Create the glow sprite |
|
m_hGlowSprite = CSprite::SpriteCreate( "sprites/grubflare1.vmt", GetLocalOrigin(), false ); |
|
Assert( m_hGlowSprite ); |
|
if ( m_hGlowSprite == NULL ) |
|
return; |
|
|
|
m_hGlowSprite->TurnOn(); |
|
m_hGlowSprite->SetTransparency( kRenderWorldGlow, 156, 169, 121, 164, kRenderFxNoDissipation ); |
|
m_hGlowSprite->SetScale( 0.5f ); |
|
m_hGlowSprite->SetGlowProxySize( 16.0f ); |
|
int nAttachment = LookupAttachment( "glow" ); |
|
m_hGlowSprite->SetParent( this, nAttachment ); |
|
m_hGlowSprite->SetLocalOrigin( vec3_origin ); |
|
|
|
// Don't uselessly animate, we're a static sprite! |
|
m_hGlowSprite->SetThink( NULL ); |
|
m_hGlowSprite->SetNextThink( TICK_NEVER_THINK ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::FadeGlow( void ) |
|
{ |
|
if ( m_hGlowSprite ) |
|
{ |
|
m_hGlowSprite->FadeAndDie( 0.25f ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::UpdateOnRemove( void ) |
|
{ |
|
FadeGlow(); |
|
|
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find what size of nugget to spawn |
|
//----------------------------------------------------------------------------- |
|
int CAntlionGrub::GetNuggetDenomination( void ) |
|
{ |
|
// Find the desired health perc we want to be at |
|
float flDesiredHealthPerc = DynamicResupply_GetDesiredHealthPercentage(); |
|
|
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
if ( pPlayer == NULL ) |
|
return -1; |
|
|
|
// Get the player's current health percentage |
|
float flPlayerHealthPerc = (float) pPlayer->GetHealth() / (float) pPlayer->GetMaxHealth(); |
|
|
|
// If we're already maxed out, return the small nugget |
|
if ( flPlayerHealthPerc >= flDesiredHealthPerc ) |
|
{ |
|
return NUGGET_SMALL; |
|
} |
|
|
|
// Find where we fall in the desired health's range |
|
float flPercDelta = flPlayerHealthPerc / flDesiredHealthPerc; |
|
|
|
// The larger to discrepancy, the higher the chance to move quickly to close it |
|
float flSeed = random->RandomFloat( 0.0f, 1.0f ); |
|
float flRandomPerc = Bias( flSeed, (1.0f-flPercDelta) ); |
|
|
|
int nDenomination; |
|
if ( flRandomPerc < 0.25f ) |
|
{ |
|
nDenomination = NUGGET_SMALL; |
|
} |
|
else if ( flRandomPerc < 0.625f ) |
|
{ |
|
nDenomination = NUGGET_MEDIUM; |
|
} |
|
else |
|
{ |
|
nDenomination = NUGGET_LARGE; |
|
} |
|
|
|
// Msg("Player: %.02f, Desired: %.02f, Seed: %.02f, Perc: %.02f, Result: %d\n", flPlayerHealthPerc, flDesiredHealthPerc, flSeed, flRandomPerc, nDenomination ); |
|
|
|
return nDenomination; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::CreateNugget( void ) |
|
{ |
|
CGrubNugget *pNugget = (CGrubNugget *) CreateEntityByName( "item_grubnugget" ); |
|
if ( pNugget == NULL ) |
|
return; |
|
|
|
Vector vecOrigin; |
|
Vector vecForward; |
|
GetAttachment( LookupAttachment( "glow" ), vecOrigin, &vecForward ); |
|
|
|
// Find out what size to make this nugget! |
|
int nDenomination = GetNuggetDenomination(); |
|
pNugget->SetDenomination( nDenomination ); |
|
|
|
pNugget->SetAbsOrigin( vecOrigin ); |
|
pNugget->SetAbsAngles( RandomAngle( 0, 360 ) ); |
|
DispatchSpawn( pNugget ); |
|
|
|
IPhysicsObject *pPhys = pNugget->VPhysicsGetObject(); |
|
if ( pPhys ) |
|
{ |
|
Vector vecForward; |
|
GetVectors( &vecForward, NULL, NULL ); |
|
|
|
Vector vecVelocity = RandomVector( -35.0f, 35.0f ) + ( vecForward * -RandomFloat( 50.0f, 75.0f ) ); |
|
AngularImpulse vecAngImpulse = RandomAngularImpulse( -100.0f, 100.0f ); |
|
|
|
pPhys->AddVelocity( &vecVelocity, &vecAngImpulse ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
// Fire our output only if the player is the one that killed us |
|
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) |
|
{ |
|
m_OnDeathByPlayer.FireOutput( info.GetAttacker(), info.GetAttacker() ); |
|
} |
|
|
|
m_OnDeath.FireOutput( info.GetAttacker(), info.GetAttacker() ); |
|
SendOnKilledGameEvent( info ); |
|
|
|
// Crush and crowbar damage hurt us more than others |
|
bool bSquashed = ( info.GetDamageType() & (DMG_CRUSH|DMG_CLUB)) ? true : false; |
|
Squash( info.GetAttacker(), false, bSquashed ); |
|
|
|
m_takedamage = DAMAGE_NO; |
|
|
|
if ( sk_grubnugget_enabled.GetBool() ) |
|
{ |
|
CreateNugget(); |
|
} |
|
|
|
// Go away |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
// we deliberately do not call BaseClass::EventKilled |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
//----------------------------------------------------------------------------- |
|
int CAntlionGrub::OnTakeDamage( const CTakeDamageInfo &info ) |
|
{ |
|
// Animate a flinch of pain if we're dying |
|
bool bSquashed = ( ( GetEffects() & EF_NODRAW ) != 0 ); |
|
if ( bSquashed == false ) |
|
{ |
|
SetSequence( SelectWeightedSequence( ACT_SMALL_FLINCH ) ); |
|
m_flFlinchTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f ); |
|
|
|
SetThink( &CAntlionGrub::FlinchThink ); |
|
SetNextThink( gpGlobals->curtime + 0.05f ); |
|
} |
|
|
|
return BaseClass::OnTakeDamage( info ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Whether or not we're in the PVS |
|
//----------------------------------------------------------------------------- |
|
inline bool CAntlionGrub::InPVS( void ) |
|
{ |
|
return ( UTIL_FindClientInPVS( edict() ) != NULL ) || (UTIL_ClientPVSIsExpanded() && UTIL_FindClientInVisibilityPVS( edict() )); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::SetNextThinkByDistance( void ) |
|
{ |
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
if ( pPlayer == NULL ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.5f, 3.0f ) ); |
|
return; |
|
} |
|
|
|
float flDistToPlayerSqr = ( GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr(); |
|
float scale = RemapValClamped( flDistToPlayerSqr, Square( 400 ), Square( 5000 ), 1.0f, 5.0f ); |
|
float time = random->RandomFloat( 1.0f, 3.0f ); |
|
SetNextThink( gpGlobals->curtime + ( time * scale ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::Spawn( void ) |
|
{ |
|
Precache(); |
|
BaseClass::Spawn(); |
|
|
|
SetModel( ANTLIONGRUB_MODEL ); |
|
|
|
// FIXME: This is a big perf hit with the number of grubs we're using! - jdw |
|
CreateGlow(); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
SetSolidFlags( FSOLID_TRIGGER ); |
|
SetMoveType( MOVETYPE_NONE ); |
|
SetCollisionGroup( COLLISION_GROUP_NONE ); |
|
AddEffects( EF_NOSHADOW ); |
|
|
|
CollisionProp()->UseTriggerBounds(true,1); |
|
|
|
SetTouch( &CAntlionGrub::GrubTouch ); |
|
|
|
SetHealth( 1 ); |
|
m_takedamage = DAMAGE_YES; |
|
|
|
// Stick to the nearest surface |
|
if ( HasSpawnFlags( SF_ANTLIONGRUB_NO_AUTO_PLACEMENT ) == false ) |
|
{ |
|
AttachToSurface(); |
|
} |
|
|
|
// At this point, alter our bounds to make sure we're within them |
|
Vector vecMins, vecMaxs; |
|
RotateAABB( EntityToWorldTransform(), CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), vecMins, vecMaxs ); |
|
|
|
UTIL_SetSize( this, vecMins, vecMaxs ); |
|
|
|
// Start our idle activity |
|
SetSequence( SelectWeightedSequence( ACT_IDLE ) ); |
|
SetCycle( random->RandomFloat( 0.0f, 1.0f ) ); |
|
ResetSequenceInfo(); |
|
|
|
m_State = GRUB_STATE_IDLE; |
|
|
|
// Reset |
|
m_flFlinchTime = 0.0f; |
|
m_flNextIdleSoundTime = gpGlobals->curtime + random->RandomFloat( 4.0f, 8.0f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
// Idly think |
|
SetThink( &CAntlionGrub::IdleThink ); |
|
SetNextThinkByDistance(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &vecTestPos - |
|
// *vecResult - |
|
// *flDist - |
|
// Output : inline bool |
|
//----------------------------------------------------------------------------- |
|
inline bool CAntlionGrub::ProbeSurface( const Vector &vecTestPos, const Vector &vecDir, Vector *vecResult, Vector *vecNormal ) |
|
{ |
|
// Trace down to find a surface |
|
trace_t tr; |
|
UTIL_TraceLine( vecTestPos, vecTestPos + (vecDir*256.0f), MASK_NPCSOLID&(~CONTENTS_MONSTER), this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( vecResult ) |
|
{ |
|
*vecResult = tr.endpos; |
|
} |
|
|
|
if ( vecNormal ) |
|
{ |
|
*vecNormal = tr.plane.normal; |
|
} |
|
|
|
return ( tr.fraction < 1.0f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Attaches the grub to the surface underneath its abdomen |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::AttachToSurface( void ) |
|
{ |
|
// Get our downward direction |
|
Vector vecForward, vecRight, vecDown; |
|
GetVectors( &vecForward, &vecRight, &vecDown ); |
|
vecDown.Negate(); |
|
|
|
Vector vecOffset = ( vecDown * -8.0f ); |
|
|
|
// Middle |
|
Vector vecMid, vecMidNormal; |
|
if ( ProbeSurface( WorldSpaceCenter() + vecOffset, vecDown, &vecMid, &vecMidNormal ) == false ) |
|
{ |
|
// A grub was left hanging in the air, it must not be near any valid surfaces! |
|
Warning("Antlion grub stranded in space at (%.02f, %.02f, %.02f) : REMOVED\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// Sit at the mid-point |
|
UTIL_SetOrigin( this, vecMid ); |
|
|
|
Vector vecPivot; |
|
Vector vecPivotNormal; |
|
|
|
bool bNegate = true; |
|
|
|
// First test our tail (more crucial that it doesn't interpenetrate with the world) |
|
if ( ProbeSurface( WorldSpaceCenter() - ( vecForward * 12.0f ) + vecOffset, vecDown, &vecPivot, &vecPivotNormal ) == false ) |
|
{ |
|
// If that didn't find a surface, try the head |
|
if ( ProbeSurface( WorldSpaceCenter() + ( vecForward * 12.0f ) + vecOffset, vecDown, &vecPivot, &vecPivotNormal ) == false ) |
|
{ |
|
// Worst case, just site at the middle |
|
UTIL_SetOrigin( this, vecMid ); |
|
|
|
QAngle vecAngles; |
|
VectorAngles( vecForward, vecMidNormal, vecAngles ); |
|
SetAbsAngles( vecAngles ); |
|
return; |
|
} |
|
|
|
bNegate = false; |
|
} |
|
|
|
// Find the line we'll lay on if these two points are connected by a line |
|
Vector vecLieDir = ( vecPivot - vecMid ); |
|
VectorNormalize( vecLieDir ); |
|
if ( bNegate ) |
|
{ |
|
// We need to try and maintain our facing |
|
vecLieDir.Negate(); |
|
} |
|
|
|
// Use the average of the surface normals to be our "up" direction |
|
Vector vecPseudoUp = ( vecMidNormal + vecPivotNormal ) * 0.5f; |
|
|
|
QAngle vecAngles; |
|
VectorAngles( vecLieDir, vecPseudoUp, vecAngles ); |
|
|
|
SetAbsAngles( vecAngles ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::MakeIdleSounds( void ) |
|
{ |
|
if ( m_State == GRUB_STATE_AGITATED ) |
|
{ |
|
if ( m_flNextSquealSoundTime < gpGlobals->curtime ) |
|
{ |
|
EmitSound( "NPC_Antlion_Grub.Stimulated" ); |
|
m_flNextSquealSoundTime = gpGlobals->curtime + random->RandomFloat( 1.5f, 3.0f ); |
|
m_flNextIdleSoundTime = gpGlobals->curtime + random->RandomFloat( 4.0f, 8.0f ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_flNextIdleSoundTime < gpGlobals->curtime ) |
|
{ |
|
EmitSound( "NPC_Antlion_Grub.Idle" ); |
|
m_flNextIdleSoundTime = gpGlobals->curtime + random->RandomFloat( 8.0f, 12.0f ); |
|
} |
|
} |
|
} |
|
|
|
#define DEBUG_GRUB_THINK_TIMES 0 |
|
|
|
#if DEBUG_GRUB_THINK_TIMES |
|
int nFrame = 0; |
|
int nNumThinks = 0; |
|
#endif // DEBUG_GRUB_THINK_TIMES |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Advance our thinks |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::IdleThink( void ) |
|
{ |
|
#if DEBUG_GRUB_THINK_TIMES |
|
// Test for a new frame |
|
if ( gpGlobals->framecount != nFrame ) |
|
{ |
|
if ( nNumThinks > 10 ) |
|
{ |
|
Msg("%d npc_antlion_grubs thinking per frame!\n", nNumThinks ); |
|
} |
|
|
|
nFrame = gpGlobals->framecount; |
|
nNumThinks = 0; |
|
} |
|
|
|
nNumThinks++; |
|
#endif // DEBUG_GRUB_THINK_TIMES |
|
|
|
// Check the PVS status |
|
if ( InPVS() == false ) |
|
{ |
|
// Push out into the future until they're in our PVS |
|
SetNextThinkByDistance(); |
|
m_bOutsidePVS = true; |
|
return; |
|
} |
|
|
|
// Stagger our sounds if we've just re-entered the PVS |
|
if ( m_bOutsidePVS ) |
|
{ |
|
m_flNextIdleSoundTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 4.0f ); |
|
m_bOutsidePVS = false; |
|
} |
|
|
|
// See how close the player is |
|
CBasePlayer *pPlayerEnt = AI_GetSinglePlayer(); |
|
float flDistToPlayerSqr = ( GetAbsOrigin() - pPlayerEnt->GetAbsOrigin() ).LengthSqr(); |
|
|
|
bool bFlinching = ( m_flFlinchTime > gpGlobals->curtime ); |
|
|
|
// If they're far enough away, just wait to think again |
|
if ( flDistToPlayerSqr > Square( 40*12 ) && bFlinching == false ) |
|
{ |
|
SetNextThinkByDistance(); |
|
return; |
|
} |
|
|
|
// At this range, the player agitates us with his presence |
|
bool bPlayerWithinAgitationRange = ( flDistToPlayerSqr <= Square( (6*12) ) ); |
|
bool bAgitated = (bPlayerWithinAgitationRange || bFlinching ); |
|
|
|
// If we're idle and the player has come close enough, get agry |
|
if ( ( m_State == GRUB_STATE_IDLE ) && bAgitated ) |
|
{ |
|
SetSequence( SelectWeightedSequence( ACT_SMALL_FLINCH ) ); |
|
m_State = GRUB_STATE_AGITATED; |
|
} |
|
else if ( IsSequenceFinished() ) |
|
{ |
|
// See if it's time to choose a new sequence |
|
ResetSequenceInfo(); |
|
SetCycle( 0.0f ); |
|
|
|
// If we're near enough, we want to play an "alert" animation |
|
if ( bAgitated ) |
|
{ |
|
SetSequence( SelectWeightedSequence( ACT_SMALL_FLINCH ) ); |
|
m_State = GRUB_STATE_AGITATED; |
|
} |
|
else |
|
{ |
|
// Just idle |
|
SetSequence( SelectWeightedSequence( ACT_IDLE ) ); |
|
m_State = GRUB_STATE_IDLE; |
|
} |
|
|
|
// Add some variation because we're often in large bunches |
|
SetPlaybackRate( random->RandomFloat( 0.8f, 1.2f ) ); |
|
} |
|
|
|
// Idle normally |
|
StudioFrameAdvance(); |
|
MakeIdleSounds(); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::FlinchThink( void ) |
|
{ |
|
StudioFrameAdvance(); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
// See if we're done |
|
if ( m_flFlinchTime < gpGlobals->curtime ) |
|
{ |
|
SetSequence( SelectWeightedSequence( ACT_IDLE ) ); |
|
SetThink( &CAntlionGrub::IdleThink ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::GrubTouch( CBaseEntity *pOther ) |
|
{ |
|
// We can be squished by the player, Vort, or flying heavy things. |
|
IPhysicsObject *pPhysOther = pOther->VPhysicsGetObject(); // bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0; |
|
if ( pOther->IsPlayer() || FClassnameIs(pOther,"npc_vortigaunt") || ( pPhysOther && (pPhysOther->GetGameFlags() & FVPHYSICS_WAS_THROWN )) ) |
|
{ |
|
m_OnAgitated.FireOutput( pOther, pOther ); |
|
Squash( pOther, true, true ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::Precache( void ) |
|
{ |
|
PrecacheModel( ANTLIONGRUB_MODEL ); |
|
PrecacheModel( ANTLIONGRUB_SQUASHED_MODEL ); |
|
|
|
m_nGlowSpriteHandle = PrecacheModel("sprites/grubflare1.vmt"); |
|
|
|
PrecacheScriptSound( "NPC_Antlion_Grub.Idle" ); |
|
PrecacheScriptSound( "NPC_Antlion_Grub.Alert" ); |
|
PrecacheScriptSound( "NPC_Antlion_Grub.Stimulated" ); |
|
PrecacheScriptSound( "NPC_Antlion_Grub.Die" ); |
|
PrecacheScriptSound( "NPC_Antlion_Grub.Squish" ); |
|
|
|
PrecacheParticleSystem( "GrubSquashBlood" ); |
|
PrecacheParticleSystem( "GrubBlood" ); |
|
|
|
UTIL_PrecacheOther( "item_grubnugget" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Squish the grub! |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::InputSquash( inputdata_t &data ) |
|
{ |
|
Squash( data.pActivator, true, true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::SpawnSquashedGrub( void ) |
|
{ |
|
// If we're already invisible, we're done |
|
if ( GetEffects() & EF_NODRAW ) |
|
return; |
|
|
|
Vector vecUp; |
|
GetVectors( NULL, NULL, &vecUp ); |
|
CBaseEntity *pGib = CreateRagGib( ANTLIONGRUB_SQUASHED_MODEL, GetAbsOrigin(), GetAbsAngles(), vecUp * 16.0f ); |
|
if ( pGib ) |
|
{ |
|
pGib->AddEffects( EF_NOSHADOW ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::MakeSquashDecals( const Vector &vecOrigin ) |
|
{ |
|
trace_t tr; |
|
Vector vecStart; |
|
Vector vecTraceDir; |
|
|
|
GetVectors( NULL, NULL, &vecTraceDir ); |
|
vecTraceDir.Negate(); |
|
|
|
for ( int i = 0 ; i < 8; i++ ) |
|
{ |
|
vecStart.x = vecOrigin.x + random->RandomFloat( -16.0f, 16.0f ); |
|
vecStart.y = vecOrigin.y + random->RandomFloat( -16.0f, 16.0f ); |
|
vecStart.z = vecOrigin.z + 4; |
|
|
|
UTIL_TraceLine( vecStart, vecStart + ( vecTraceDir * (5*12) ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction != 1.0 ) |
|
{ |
|
UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_YELLOW ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::Squash( CBaseEntity *pOther, bool bDealDamage, bool bSpawnBlood ) |
|
{ |
|
// If we're already squashed, then don't bother doing it again! |
|
if ( GetEffects() & EF_NODRAW ) |
|
return; |
|
|
|
SpawnSquashedGrub(); |
|
|
|
AddEffects( EF_NODRAW ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
// Stop being attached to us |
|
if ( m_hGlowSprite ) |
|
{ |
|
FadeGlow(); |
|
m_hGlowSprite->SetParent( NULL ); |
|
} |
|
|
|
EmitSound( "NPC_Antlion_Grub.Die" ); |
|
EmitSound( "NPC_Antlion_Grub.Squish" ); |
|
|
|
// if vort stepped on me, maybe he wants to say something |
|
if ( pOther && FClassnameIs( pOther, "npc_vortigaunt" ) ) |
|
{ |
|
Assert(dynamic_cast<CNPC_Vortigaunt *>(pOther)); |
|
static_cast<CNPC_Vortigaunt *>(pOther)->OnSquishedGrub(this); |
|
} |
|
|
|
SetTouch( NULL ); |
|
|
|
//if ( bSpawnBlood ) |
|
{ |
|
// Temp squash effect |
|
Vector vecForward, vecUp; |
|
AngleVectors( GetAbsAngles(), &vecForward, NULL, &vecUp ); |
|
|
|
// Start effects at either end of the grub |
|
Vector vecSplortPos = GetAbsOrigin() + vecForward * 14.0f; |
|
DispatchParticleEffect( "GrubSquashBlood", vecSplortPos, GetAbsAngles() ); |
|
|
|
vecSplortPos = GetAbsOrigin() - vecForward * 16.0f; |
|
Vector vecDir = -vecForward; |
|
QAngle vecAngles; |
|
VectorAngles( vecDir, vecAngles ); |
|
DispatchParticleEffect( "GrubSquashBlood", vecSplortPos, vecAngles ); |
|
|
|
MakeSquashDecals( GetAbsOrigin() + vecForward * 32.0f ); |
|
MakeSquashDecals( GetAbsOrigin() - vecForward * 32.0f ); |
|
} |
|
|
|
// Deal deadly damage to ourself |
|
if ( bDealDamage ) |
|
{ |
|
CTakeDamageInfo info( pOther, pOther, Vector( 0, 0, -1 ), GetAbsOrigin(), GetHealth()+1, DMG_CRUSH ); |
|
TakeDamage( info ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
// &vecDir - |
|
// *ptr - |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ) |
|
{ |
|
QAngle vecAngles; |
|
VectorAngles( -vecDir, vecAngles ); |
|
DispatchParticleEffect( "GrubBlood", ptr->endpos, vecAngles ); |
|
|
|
BaseClass::TraceAttack( info, vecDir, ptr ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Make the grub angry! |
|
//----------------------------------------------------------------------------- |
|
void CAntlionGrub::InputAgitate( inputdata_t &inputdata ) |
|
{ |
|
SetSequence( SelectWeightedSequence( ACT_SMALL_FLINCH ) ); |
|
m_State = GRUB_STATE_AGITATED; |
|
m_flNextSquealSoundTime = gpGlobals->curtime; |
|
|
|
m_flFlinchTime = gpGlobals->curtime + inputdata.value.Float(); |
|
|
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
|
|
// ===================================================================== |
|
// |
|
// Tasty grub nugget! |
|
// |
|
// ===================================================================== |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CGrubNugget::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
if ( m_nDenomination == NUGGET_LARGE ) |
|
{ |
|
SetModel( "models/grub_nugget_large.mdl" ); |
|
} |
|
else if ( m_nDenomination == NUGGET_MEDIUM ) |
|
{ |
|
SetModel( "models/grub_nugget_medium.mdl" ); |
|
} |
|
else |
|
{ |
|
SetModel( "models/grub_nugget_small.mdl" ); |
|
} |
|
|
|
// We're self-illuminating, so we don't take or give shadows |
|
AddEffects( EF_NOSHADOW|EF_NORECEIVESHADOW ); |
|
|
|
m_iHealth = 1; |
|
|
|
BaseClass::Spawn(); |
|
|
|
m_takedamage = DAMAGE_YES; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CGrubNugget::Precache( void ) |
|
{ |
|
PrecacheModel("models/grub_nugget_small.mdl"); |
|
PrecacheModel("models/grub_nugget_medium.mdl"); |
|
PrecacheModel("models/grub_nugget_large.mdl"); |
|
|
|
PrecacheScriptSound( "GrubNugget.Touch" ); |
|
PrecacheScriptSound( "NPC_Antlion_Grub.Explode" ); |
|
|
|
PrecacheParticleSystem( "antlion_spit_player" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Let us be picked up by the gravity gun, regardless of our material |
|
//----------------------------------------------------------------------------- |
|
bool CGrubNugget::VPhysicsIsFlesh( void ) |
|
{ |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pPlayer - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CGrubNugget::MyTouch( CBasePlayer *pPlayer ) |
|
{ |
|
//int nHealthToGive = sk_grubnugget_health.GetFloat() * m_nDenomination; |
|
int nHealthToGive; |
|
switch (m_nDenomination) |
|
{ |
|
case NUGGET_SMALL: |
|
nHealthToGive = sk_grubnugget_health_small.GetInt(); |
|
break; |
|
case NUGGET_LARGE: |
|
nHealthToGive = sk_grubnugget_health_large.GetInt(); |
|
break; |
|
default: |
|
nHealthToGive = sk_grubnugget_health_medium.GetInt(); |
|
} |
|
|
|
// Attempt to give the player health |
|
if ( pPlayer->TakeHealth( nHealthToGive, DMG_GENERIC ) == 0 ) |
|
return false; |
|
|
|
CSingleUserRecipientFilter user( pPlayer ); |
|
user.MakeReliable(); |
|
|
|
UserMessageBegin( user, "ItemPickup" ); |
|
WRITE_STRING( GetClassname() ); |
|
MessageEnd(); |
|
|
|
CPASAttenuationFilter filter( pPlayer, "GrubNugget.Touch" ); |
|
EmitSound( filter, pPlayer->entindex(), "GrubNugget.Touch" ); |
|
|
|
UTIL_Remove( this ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : index - |
|
// *pEvent - |
|
//----------------------------------------------------------------------------- |
|
void CGrubNugget::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
int damageType; |
|
float damage = CalculateDefaultPhysicsDamage( index, pEvent, 1.0f, true, damageType ); |
|
if ( damage > 5.0f ) |
|
{ |
|
CBaseEntity *pHitEntity = pEvent->pEntities[!index]; |
|
if ( pHitEntity == NULL ) |
|
{ |
|
// hit world |
|
pHitEntity = GetContainingEntity( INDEXENT(0) ); |
|
} |
|
|
|
Vector damagePos; |
|
pEvent->pInternalData->GetContactPoint( damagePos ); |
|
Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); |
|
if ( damageForce == vec3_origin ) |
|
{ |
|
// This can happen if this entity is motion disabled, and can't move. |
|
// Use the velocity of the entity that hit us instead. |
|
damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass(); |
|
} |
|
|
|
// FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision |
|
PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index ); |
|
} |
|
|
|
BaseClass::VPhysicsCollision( index, pEvent ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &info - |
|
//----------------------------------------------------------------------------- |
|
void CGrubNugget::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
AddEffects( EF_NODRAW ); |
|
DispatchParticleEffect( "antlion_spit_player", GetAbsOrigin(), QAngle( -90, 0, 0 ) ); |
|
EmitSound( "NPC_Antlion_Grub.Explode" ); |
|
|
|
BaseClass::Event_Killed( info ); |
|
}
|
|
|