source-engine/game/server/hl2/npc_antliongrub.cpp

980 lines
28 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= 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 );
}