//========= Copyright © 1996-2005, 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 );
}
