//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "ai_basenpc.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_motor.h"
#include "ai_memory.h"
#include "ai_route.h"
#include "soundent.h"
#include "game.h"
#include "npcevent.h"
#include "entitylist.h"
#include "ai_task.h"
#include "activitylist.h"
#include "engine/IEngineSound.h"
#include "npc_BaseZombie.h"
#include "movevars_shared.h"
#include "IEffects.h"
#include "props.h"
#include "physics_npc_solver.h"
#include "physics_prop_ragdoll.h"

#ifdef HL2_EPISODIC
#include "episodic/ai_behavior_passenger_zombie.h"
#endif	// HL2_EPISODIC

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define FASTZOMBIE_IDLE_PITCH			35
#define FASTZOMBIE_MIN_PITCH			70
#define FASTZOMBIE_MAX_PITCH			130
#define FASTZOMBIE_SOUND_UPDATE_FREQ	0.5

#define FASTZOMBIE_MAXLEAP_Z		128

#define FASTZOMBIE_EXCITE_DIST 480.0

#define FASTZOMBIE_BASE_FREQ 1.5

// If flying at an enemy, and this close or closer, start playing the maul animation!!
#define FASTZOMBIE_MAUL_RANGE	300

#ifdef HL2_EPISODIC

int AE_PASSENGER_PHYSICS_PUSH;
int AE_FASTZOMBIE_VEHICLE_LEAP;
int AE_FASTZOMBIE_VEHICLE_SS_DIE;	// Killed while doing scripted sequence on vehicle

#endif // HL2_EPISODIC

enum
{
	COND_FASTZOMBIE_CLIMB_TOUCH	= LAST_BASE_ZOMBIE_CONDITION,
};

envelopePoint_t envFastZombieVolumeJump[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	0.0f, 0.0f,
		1.0f, 1.2f,
	},
};

envelopePoint_t envFastZombieVolumePain[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	0.0f, 0.0f,
		1.0f, 1.0f,
	},
};

envelopePoint_t envFastZombieInverseVolumePain[] =
{
	{	0.0f, 0.0f,
		0.1f, 0.1f,
	},
	{	1.0f, 1.0f,
		1.0f, 1.0f,
	},
};

envelopePoint_t envFastZombieVolumeJumpPostApex[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	0.0f, 0.0f,
		1.0f, 1.2f,
	},
};

envelopePoint_t envFastZombieVolumeClimb[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	0.0f, 0.0f,
		0.2f, 0.2f,
	},
};

envelopePoint_t envFastZombieMoanVolumeFast[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	0.0f, 0.0f,
		0.2f, 0.3f,
	},
};

envelopePoint_t envFastZombieMoanVolume[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	1.0f, 1.0f,
		0.2f, 0.2f,
	},
	{	0.0f, 0.0f,
		1.0f, 0.4f,
	},
};

envelopePoint_t envFastZombieFootstepVolume[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	0.7f, 0.7f,
		0.2f, 0.2f,
	},
};

envelopePoint_t envFastZombieVolumeFrenzy[] =
{
	{	1.0f, 1.0f,
		0.1f, 0.1f,
	},
	{	0.0f, 0.0f,
		2.0f, 2.0f,
	},
};


//=========================================================
// animation events
//=========================================================
int AE_FASTZOMBIE_LEAP;
int AE_FASTZOMBIE_GALLOP_LEFT;
int AE_FASTZOMBIE_GALLOP_RIGHT;
int AE_FASTZOMBIE_CLIMB_LEFT;
int AE_FASTZOMBIE_CLIMB_RIGHT;

//=========================================================
// tasks
//=========================================================
enum 
{
	TASK_FASTZOMBIE_DO_ATTACK = LAST_SHARED_TASK + 100,	// again, my !!!HACKHACK
	TASK_FASTZOMBIE_LAND_RECOVER,
	TASK_FASTZOMBIE_UNSTICK_JUMP,
	TASK_FASTZOMBIE_JUMP_BACK,
	TASK_FASTZOMBIE_VERIFY_ATTACK,
};

//=========================================================
// activities
//=========================================================
int ACT_FASTZOMBIE_LEAP_SOAR;
int ACT_FASTZOMBIE_LEAP_STRIKE;
int ACT_FASTZOMBIE_LAND_RIGHT;
int ACT_FASTZOMBIE_LAND_LEFT;
int ACT_FASTZOMBIE_FRENZY;
int ACT_FASTZOMBIE_BIG_SLASH;

//=========================================================
// schedules
//=========================================================
enum
{
	SCHED_FASTZOMBIE_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE + 100, // hack to get past the base zombie's schedules
	SCHED_FASTZOMBIE_UNSTICK_JUMP,
	SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP,
	SCHED_FASTZOMBIE_MELEE_ATTACK1,
	SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1,
};



//=========================================================
//=========================================================
class CFastZombie : public CNPC_BaseZombie
{
	DECLARE_CLASS( CFastZombie, CNPC_BaseZombie );

public:
	void Spawn( void );
	void Precache( void );

	void SetZombieModel( void );
	bool CanSwatPhysicsObjects( void ) { return false; }

	int	TranslateSchedule( int scheduleType );

	Activity NPC_TranslateActivity( Activity baseAct );

	void LeapAttackTouch( CBaseEntity *pOther );
	void ClimbTouch( CBaseEntity *pOther );

	void StartTask( const Task_t *pTask );
	void RunTask( const Task_t *pTask );
	int SelectSchedule( void );
	void OnScheduleChange( void );

	void PrescheduleThink( void );

	float InnateRange1MaxRange( void );
	int RangeAttack1Conditions( float flDot, float flDist );
	int MeleeAttack1Conditions( float flDot, float flDist );

	virtual float GetClawAttackRange() const { return 50; }

	bool ShouldPlayFootstepMoan( void ) { return false; }

	void HandleAnimEvent( animevent_t *pEvent );

	void PostNPCInit( void );

	void LeapAttack( void );
	void LeapAttackSound( void );

	void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce );

	bool IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const;
	bool MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost );
	bool ShouldFailNav( bool bMovementFailed );

	int	SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );

	const char *GetMoanSound( int nSound );

	void OnChangeActivity( Activity NewActivity );
	void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );
	void Event_Killed( const CTakeDamageInfo &info );
	bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold );

	virtual Vector GetAutoAimCenter() { return WorldSpaceCenter() - Vector( 0, 0, 12.0f ); }

	void PainSound( const CTakeDamageInfo &info );
	void DeathSound( const CTakeDamageInfo &info ); 
	void AlertSound( void );
	void IdleSound( void );
	void AttackSound( void );
	void AttackHitSound( void );
	void AttackMissSound( void );
	void FootstepSound( bool fRightFoot );
	void FootscuffSound( bool fRightFoot ) {}; // fast guy doesn't scuff
	void StopLoopingSounds( void );

	void SoundInit( void );
	void SetIdleSoundState( void );
	void SetAngrySoundState( void );

	void BuildScheduleTestBits( void );

	void BeginNavJump( void );
	void EndNavJump( void );

	bool IsNavJumping( void ) { return m_fIsNavJumping; }
	void OnNavJumpHitApex( void );

	void BeginAttackJump( void );
	void EndAttackJump( void );

	float		MaxYawSpeed( void );

	virtual const char *GetHeadcrabClassname( void );
	virtual const char *GetHeadcrabModel( void );
	virtual const char *GetLegsModel( void );
	virtual const char *GetTorsoModel( void );

//=============================================================================
#ifdef HL2_EPISODIC

public:
	virtual bool	CreateBehaviors( void );
	virtual void	VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
	virtual	void	UpdateEfficiency( bool bInPVS );
	virtual bool	IsInAVehicle( void );
	void			InputAttachToVehicle( inputdata_t &inputdata );
	void			VehicleLeapAttackTouch( CBaseEntity *pOther );

private:
	void			VehicleLeapAttack( void );
	bool			CanEnterVehicle( CPropJeepEpisodic *pVehicle );

	CAI_PassengerBehaviorZombie		m_PassengerBehavior;

#endif	// HL2_EPISODIC
//=============================================================================

protected:

	static const char *pMoanSounds[];

	// Sound stuff
	float			m_flDistFactor; 
	unsigned char	m_iClimbCount; // counts rungs climbed (for sound)
	bool			m_fIsNavJumping;
	bool			m_fIsAttackJumping;
	bool			m_fHitApex;
	mutable float	m_flJumpDist;

	bool			m_fHasScreamed;

private:
	float	m_flNextMeleeAttack;
	bool	m_fJustJumped;
	float	m_flJumpStartAltitude;
	float	m_flTimeUpdateSound;

	CSoundPatch	*m_pLayer2; // used for climbing ladders, and when jumping (pre apex)

public:
	DEFINE_CUSTOM_AI;
	DECLARE_DATADESC();
};

LINK_ENTITY_TO_CLASS( npc_fastzombie, CFastZombie );
LINK_ENTITY_TO_CLASS( npc_fastzombie_torso, CFastZombie );


BEGIN_DATADESC( CFastZombie )

	DEFINE_FIELD( m_flDistFactor, FIELD_FLOAT ),
	DEFINE_FIELD( m_iClimbCount, FIELD_CHARACTER ),
	DEFINE_FIELD( m_fIsNavJumping, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_fIsAttackJumping, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_fHitApex, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flJumpDist, FIELD_FLOAT ),
	DEFINE_FIELD( m_fHasScreamed, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flNextMeleeAttack, FIELD_TIME ),
	DEFINE_FIELD( m_fJustJumped, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flJumpStartAltitude, FIELD_FLOAT ),
	DEFINE_FIELD( m_flTimeUpdateSound, FIELD_TIME ),

	// Function Pointers
	DEFINE_ENTITYFUNC( LeapAttackTouch ),
	DEFINE_ENTITYFUNC( ClimbTouch ),
	DEFINE_SOUNDPATCH( m_pLayer2 ),

#ifdef HL2_EPISODIC
	DEFINE_ENTITYFUNC( VehicleLeapAttackTouch ),
	DEFINE_INPUTFUNC( FIELD_STRING, "AttachToVehicle", InputAttachToVehicle ),
#endif	// HL2_EPISODIC

END_DATADESC()


const char *CFastZombie::pMoanSounds[] =
{
	"NPC_FastZombie.Moan1",
};

//-----------------------------------------------------------------------------
// The model we use for our legs when we get blowed up.
//-----------------------------------------------------------------------------
static const char *s_pLegsModel = "models/gibs/fast_zombie_legs.mdl";


//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CFastZombie::Precache( void )
{
	PrecacheModel("models/zombie/fast.mdl");
#ifdef HL2_EPISODIC
	PrecacheModel("models/zombie/Fast_torso.mdl");
	PrecacheScriptSound( "NPC_FastZombie.CarEnter1" );
	PrecacheScriptSound( "NPC_FastZombie.CarEnter2" );
	PrecacheScriptSound( "NPC_FastZombie.CarEnter3" );
	PrecacheScriptSound( "NPC_FastZombie.CarEnter4" );
	PrecacheScriptSound( "NPC_FastZombie.CarScream" );
#endif
	PrecacheModel( "models/gibs/fast_zombie_torso.mdl" );
	PrecacheModel( "models/gibs/fast_zombie_legs.mdl" );
	
	PrecacheScriptSound( "NPC_FastZombie.LeapAttack" );
	PrecacheScriptSound( "NPC_FastZombie.FootstepRight" );
	PrecacheScriptSound( "NPC_FastZombie.FootstepLeft" );
	PrecacheScriptSound( "NPC_FastZombie.AttackHit" );
	PrecacheScriptSound( "NPC_FastZombie.AttackMiss" );
	PrecacheScriptSound( "NPC_FastZombie.LeapAttack" );
	PrecacheScriptSound( "NPC_FastZombie.Attack" );
	PrecacheScriptSound( "NPC_FastZombie.Idle" );
	PrecacheScriptSound( "NPC_FastZombie.AlertFar" );
	PrecacheScriptSound( "NPC_FastZombie.AlertNear" );
	PrecacheScriptSound( "NPC_FastZombie.GallopLeft" );
	PrecacheScriptSound( "NPC_FastZombie.GallopRight" );
	PrecacheScriptSound( "NPC_FastZombie.Scream" );
	PrecacheScriptSound( "NPC_FastZombie.RangeAttack" );
	PrecacheScriptSound( "NPC_FastZombie.Frenzy" );
	PrecacheScriptSound( "NPC_FastZombie.NoSound" );
	PrecacheScriptSound( "NPC_FastZombie.Die" );

	PrecacheScriptSound( "NPC_FastZombie.Gurgle" );

	PrecacheScriptSound( "NPC_FastZombie.Moan1" );

	BaseClass::Precache();
}

//---------------------------------------------------------
//---------------------------------------------------------
void CFastZombie::OnScheduleChange( void )
{
	if ( m_flNextMeleeAttack > gpGlobals->curtime + 1 )
	{
		// Allow melee attacks again.
		m_flNextMeleeAttack = gpGlobals->curtime + 0.5;
	}

	BaseClass::OnScheduleChange();
}

//---------------------------------------------------------
//---------------------------------------------------------
int CFastZombie::SelectSchedule ( void )
{

// ========================================================
#ifdef HL2_EPISODIC

	// Defer all decisions to the behavior if it's running
	if ( m_PassengerBehavior.CanSelectSchedule() )
	{
		DeferSchedulingToBehavior( &m_PassengerBehavior );
		return BaseClass::SelectSchedule();
	}

#endif //HL2_EPISODIC
// ========================================================

	if ( HasCondition( COND_ZOMBIE_RELEASECRAB ) )
	{
		// Death waits for no man. Or zombie. Or something.
		return SCHED_ZOMBIE_RELEASECRAB;
	}

	if ( HasCondition( COND_FASTZOMBIE_CLIMB_TOUCH ) )
	{
		return SCHED_FASTZOMBIE_UNSTICK_JUMP;
	}

	switch ( m_NPCState )
	{
	case NPC_STATE_COMBAT:
		if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) )
		{
			// Set state to alert and recurse!
			SetState( NPC_STATE_ALERT );
			return SelectSchedule();
		}
		break;

	case NPC_STATE_ALERT:
		if ( HasCondition( COND_LOST_ENEMY ) || ( HasCondition( COND_ENEMY_UNREACHABLE ) && MustCloseToAttack() ) )
		{
			ClearCondition( COND_LOST_ENEMY );
			ClearCondition( COND_ENEMY_UNREACHABLE );
			SetEnemy( NULL );

#ifdef DEBUG_ZOMBIES
			DevMsg("Wandering\n");
#endif

			// Just lost track of our enemy. 
			// Wander around a bit so we don't look like a dingus.
			return SCHED_ZOMBIE_WANDER_MEDIUM;
		}
		break;
	}

	return BaseClass::SelectSchedule();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFastZombie::PrescheduleThink( void )
{
	BaseClass::PrescheduleThink();

	if( GetGroundEntity() && GetGroundEntity()->Classify() == CLASS_HEADCRAB )
	{
		// Kill!
		CTakeDamageInfo info;
		info.SetDamage( GetGroundEntity()->GetHealth() );
		info.SetAttacker( this );
		info.SetInflictor( this );
		info.SetDamageType( DMG_GENERIC );
		GetGroundEntity()->TakeDamage( info );
	}

 	if( m_pMoanSound && gpGlobals->curtime > m_flTimeUpdateSound )
	{
		// Manage the snorting sound, pitch up for closer.
		float flDistNoBBox;

		if( GetEnemy() && m_NPCState == NPC_STATE_COMBAT )
		{
			flDistNoBBox = ( GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter() ).Length();
			flDistNoBBox -= WorldAlignSize().x;
		}
		else
		{
			// Calm down!
			flDistNoBBox = FASTZOMBIE_EXCITE_DIST;
			m_flTimeUpdateSound += 1.0;
		}

		if( flDistNoBBox >= FASTZOMBIE_EXCITE_DIST && m_flDistFactor != 1.0 )
		{
			// Go back to normal pitch.
			m_flDistFactor = 1.0;

			ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_IDLE_PITCH, FASTZOMBIE_SOUND_UPDATE_FREQ );
		}
		else if( flDistNoBBox < FASTZOMBIE_EXCITE_DIST )
		{
			// Zombie is close! Recalculate pitch.
			int iPitch;

			m_flDistFactor = MIN( 1.0, 1 - flDistNoBBox / FASTZOMBIE_EXCITE_DIST ); 
			iPitch = FASTZOMBIE_MIN_PITCH + ( ( FASTZOMBIE_MAX_PITCH - FASTZOMBIE_MIN_PITCH ) * m_flDistFactor); 
			ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, iPitch, FASTZOMBIE_SOUND_UPDATE_FREQ );
		}

		m_flTimeUpdateSound = gpGlobals->curtime + FASTZOMBIE_SOUND_UPDATE_FREQ;
	}

	// Crudely detect the apex of our jump
	if( IsNavJumping() && !m_fHitApex && GetAbsVelocity().z <= 0.0 )
	{
		OnNavJumpHitApex();
	}

	if( IsCurSchedule(SCHED_FASTZOMBIE_RANGE_ATTACK1, false) )
	{
		// Think more frequently when flying quickly through the 
		// air, to update the server's location more often.
		SetNextThink(gpGlobals->curtime);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Startup all of the sound patches that the fast zombie uses.
//
//
//-----------------------------------------------------------------------------
void CFastZombie::SoundInit( void )
{
	if( !m_pMoanSound )
	{
		// !!!HACKHACK - kickstart the moan sound. (sjb)
		MoanSound( envFastZombieMoanVolume, ARRAYSIZE( envFastZombieMoanVolume ) );

		// Clear the commands that the base class gave the moaning sound channel.
		ENVELOPE_CONTROLLER.CommandClear( m_pMoanSound );
	}

	CPASAttenuationFilter filter( this );

	if( !m_pLayer2 )
	{
		// Set up layer2
		m_pLayer2 = ENVELOPE_CONTROLLER.SoundCreate( filter, entindex(), CHAN_VOICE, "NPC_FastZombie.Gurgle", ATTN_NORM );

		// Start silent.
		ENVELOPE_CONTROLLER.Play( m_pLayer2, 0.0, 100 );
	}

	SetIdleSoundState();
}

//-----------------------------------------------------------------------------
// Purpose: Make the zombie sound calm.
//-----------------------------------------------------------------------------
void CFastZombie::SetIdleSoundState( void )
{
	// Main looping sound
	if ( m_pMoanSound )
	{
		ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_IDLE_PITCH, 1.0 );
		ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0.75, 1.0 );
	}

	// Second Layer
	if ( m_pLayer2 )
	{
		ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, 100, 1.0 );
		ENVELOPE_CONTROLLER.SoundChangeVolume( m_pLayer2, 0.0, 1.0 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Make the zombie sound pizzled
//-----------------------------------------------------------------------------
void CFastZombie::SetAngrySoundState( void )
{
	if (( !m_pMoanSound ) || ( !m_pLayer2 ))
	{
		return;
	}

	EmitSound( "NPC_FastZombie.LeapAttack" );

	// Main looping sound
	ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_MIN_PITCH, 0.5 );
	ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1.0, 0.5 );

	// Second Layer
	ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, 100, 1.0 );
	ENVELOPE_CONTROLLER.SoundChangeVolume( m_pLayer2, 0.0, 1.0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CFastZombie::Spawn( void )
{
	Precache();

	m_fJustJumped = false;

	m_fIsTorso = m_fIsHeadless = false;

	if( FClassnameIs( this, "npc_fastzombie" ) )
	{
		m_fIsTorso = false;
	}
	else
	{
		// This was placed as an npc_fastzombie_torso
		m_fIsTorso = true;
	}

#ifdef HL2_EPISODIC
	SetBloodColor( BLOOD_COLOR_ZOMBIE );
#else
	SetBloodColor( BLOOD_COLOR_YELLOW );
#endif // HL2_EPISODIC

	m_iHealth			= 50;
	m_flFieldOfView		= 0.2;

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_JUMP | bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 /* | bits_CAP_INNATE_MELEE_ATTACK1 */);

	if ( m_fIsTorso == true )
	{
		CapabilitiesRemove( bits_CAP_MOVE_JUMP | bits_CAP_INNATE_RANGE_ATTACK1 );
	}

	m_flNextAttack = gpGlobals->curtime;

	m_pLayer2 = NULL;
	m_iClimbCount = 0;

	EndNavJump();

	m_flDistFactor = 1.0;

	BaseClass::Spawn();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFastZombie::PostNPCInit( void )
{
	SoundInit();

	m_flTimeUpdateSound = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails.
//-----------------------------------------------------------------------------
const char *CFastZombie::GetHeadcrabClassname( void )
{
	return "npc_headcrab_fast";
}

const char *CFastZombie::GetHeadcrabModel( void )
{
	return "models/headcrab.mdl";
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CFastZombie::MaxYawSpeed( void )
{
	switch( GetActivity() )
	{
	case ACT_TURN_LEFT:
	case ACT_TURN_RIGHT:
		return 120;
		break;

	case ACT_RUN:
		return 160;
		break;

	case ACT_WALK:
	case ACT_IDLE:
		return 25;
		break;
		
	default:
		return 20;
		break;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CFastZombie::SetZombieModel( void )
{
	Hull_t lastHull = GetHullType();

	if ( m_fIsTorso )
	{
		SetModel( "models/zombie/fast_torso.mdl" );
		SetHullType(HULL_TINY);
	}
	else
	{
		SetModel( "models/zombie/fast.mdl" );
		SetHullType(HULL_HUMAN);
	}

	SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless );

	SetHullSizeNormal( true );
	SetDefaultEyeOffset();
	SetActivity( ACT_IDLE );

	// hull changed size, notify vphysics
	// UNDONE: Solve this generally, systematically so other
	// NPCs can change size
	if ( lastHull != GetHullType() )
	{
		if ( VPhysicsGetObject() )
		{
			SetupVPhysicsHull();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns the model to use for our legs ragdoll when we are blown in twain.
//-----------------------------------------------------------------------------
const char *CFastZombie::GetLegsModel( void )
{
	return s_pLegsModel;
}

const char *CFastZombie::GetTorsoModel( void )
{
	return "models/gibs/fast_zombie_torso.mdl";
}


//-----------------------------------------------------------------------------
// Purpose: See if I can swat the player
//
//
//-----------------------------------------------------------------------------
int CFastZombie::MeleeAttack1Conditions( float flDot, float flDist )
{
	if ( !GetEnemy() )
	{
		return COND_NONE;
	}

	if( !(GetFlags() & FL_ONGROUND) )
	{
		// Have to be on the ground!
		return COND_NONE;
	}

	if( gpGlobals->curtime < m_flNextMeleeAttack )
	{
		return COND_NONE;
	}
	
	int baseResult = BaseClass::MeleeAttack1Conditions( flDot, flDist );

	// @TODO (toml 07-21-04): follow up with Steve to find out why fz was explicitly not using these conditions
	if ( baseResult == COND_TOO_FAR_TO_ATTACK || baseResult == COND_NOT_FACING_ATTACK )
	{
		return COND_NONE;
	}

	return baseResult;
}

//-----------------------------------------------------------------------------
// Purpose: Returns a moan sound for this class of zombie.
//-----------------------------------------------------------------------------
const char *CFastZombie::GetMoanSound( int nSound )
{
	return pMoanSounds[ nSound % ARRAYSIZE( pMoanSounds ) ];
}

//-----------------------------------------------------------------------------
// Purpose: Sound of a footstep
//-----------------------------------------------------------------------------
void CFastZombie::FootstepSound( bool fRightFoot )
{
	if( fRightFoot )
	{
		EmitSound( "NPC_FastZombie.FootstepRight" );
	}
	else
	{
		EmitSound( "NPC_FastZombie.FootstepLeft" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Play a random attack hit sound
//-----------------------------------------------------------------------------
void CFastZombie::AttackHitSound( void )
{
	EmitSound( "NPC_FastZombie.AttackHit" );
}

//-----------------------------------------------------------------------------
// Purpose: Play a random attack miss sound
//-----------------------------------------------------------------------------
void CFastZombie::AttackMissSound( void )
{
	// Play a random attack miss sound
	EmitSound( "NPC_FastZombie.AttackMiss" );
}

//-----------------------------------------------------------------------------
// Purpose: Play a random attack sound.
//-----------------------------------------------------------------------------
void CFastZombie::LeapAttackSound( void )
{
	EmitSound( "NPC_FastZombie.LeapAttack" );
}

//-----------------------------------------------------------------------------
// Purpose: Play a random attack sound.
//-----------------------------------------------------------------------------
void CFastZombie::AttackSound( void )
{
	EmitSound( "NPC_FastZombie.Attack" );
}

//-----------------------------------------------------------------------------
// Purpose: Play a random idle sound.
//-----------------------------------------------------------------------------
void CFastZombie::IdleSound( void )
{
	EmitSound( "NPC_FastZombie.Idle" );
	MakeAISpookySound( 360.0f );
}

//-----------------------------------------------------------------------------
// Purpose: Play a random pain sound.
//-----------------------------------------------------------------------------
void CFastZombie::PainSound( const CTakeDamageInfo &info )
{
	if ( m_pLayer2 )
		ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumePain, ARRAYSIZE(envFastZombieVolumePain) );
	if ( m_pMoanSound )
		ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pMoanSound, SOUNDCTRL_CHANGE_VOLUME, envFastZombieInverseVolumePain, ARRAYSIZE(envFastZombieInverseVolumePain) );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFastZombie::DeathSound( const CTakeDamageInfo &info ) 
{
	EmitSound( "NPC_FastZombie.Die" );
}

//-----------------------------------------------------------------------------
// Purpose: Play a random alert sound.
//-----------------------------------------------------------------------------
void CFastZombie::AlertSound( void )
{
	CBaseEntity *pPlayer = AI_GetSinglePlayer();

	if( pPlayer )
	{
		// Measure how far the player is, and play the appropriate type of alert sound. 
		// Doesn't matter if I'm getting mad at a different character, the player is the
		// one that hears the sound.
		float flDist;

		flDist = ( GetAbsOrigin() - pPlayer->GetAbsOrigin() ).Length();

		if( flDist > 512 )
		{
			EmitSound( "NPC_FastZombie.AlertFar" );
		}
		else
		{
			EmitSound( "NPC_FastZombie.AlertNear" );
		}
	}

}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#define FASTZOMBIE_MINLEAP			200
#define FASTZOMBIE_MAXLEAP			300
float CFastZombie::InnateRange1MaxRange( void ) 
{ 
	return FASTZOMBIE_MAXLEAP; 
}


//-----------------------------------------------------------------------------
// Purpose: See if I can make my leaping attack!!
//
//
//-----------------------------------------------------------------------------
int CFastZombie::RangeAttack1Conditions( float flDot, float flDist )
{

	if (GetEnemy() == NULL)
	{
		return( COND_NONE );
	}

	if( !(GetFlags() & FL_ONGROUND) )
	{
		return COND_NONE;
	}

	if( gpGlobals->curtime < m_flNextAttack )
	{
		return( COND_NONE );
	}

	// make sure the enemy isn't on a roof and I'm in the streets (Ravenholm)
	float flZDist;
	flZDist = fabs( GetEnemy()->GetLocalOrigin().z - GetLocalOrigin().z );
	if( flZDist > FASTZOMBIE_MAXLEAP_Z )
	{
		return COND_TOO_FAR_TO_ATTACK;
	}

	if( flDist > InnateRange1MaxRange() )
	{
		return COND_TOO_FAR_TO_ATTACK;
	}

	if( flDist < FASTZOMBIE_MINLEAP )
	{
		return COND_NONE;
	}

	if (flDot < 0.8) 
	{
		return COND_NONE;
	}

	if ( !IsMoving() )
	{
		// I Have to be running!!!
		return COND_NONE;
	}

	// Don't jump at the player unless he's facing me.
	// This allows the player to get away if he turns and sprints
	CBasePlayer *pPlayer = static_cast<CBasePlayer*>( GetEnemy() );

	if( pPlayer )
	{
		// If the enemy is a player, don't attack from behind!
		if( !pPlayer->FInViewCone( this ) )
		{
			return COND_NONE;
		}
	}

	// Drumroll please!
	// The final check! Is the path from my position to halfway between me
	// and the player clear?
	trace_t tr;
	Vector vecDirToEnemy;

	vecDirToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter();
	Vector vecHullMin( -16, -16, -16 );
	Vector vecHullMax( 16, 16, 16 );

	// only check half the distance. (the first part of the jump)
	vecDirToEnemy = vecDirToEnemy * 0.5;

	AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + vecDirToEnemy, vecHullMin, vecHullMax, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

	if( tr.fraction != 1.0 )
	{
		// There's some sort of obstacle pretty much right in front of me.
		return COND_NONE;
	}

	return COND_CAN_RANGE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose:
//
//
//-----------------------------------------------------------------------------
void CFastZombie::HandleAnimEvent( animevent_t *pEvent )
{
	if ( pEvent->event == AE_FASTZOMBIE_CLIMB_LEFT || pEvent->event == AE_FASTZOMBIE_CLIMB_RIGHT )
	{
		if( ++m_iClimbCount % 3 == 0 )
		{
			ENVELOPE_CONTROLLER.SoundChangePitch( m_pLayer2, random->RandomFloat( 100, 150 ), 0.0 );
			ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumeClimb, ARRAYSIZE(envFastZombieVolumeClimb) );
		}

		return;
	}

	if ( pEvent->event == AE_FASTZOMBIE_LEAP )
	{
		LeapAttack();
		return;
	}
	
	if ( pEvent->event == AE_FASTZOMBIE_GALLOP_LEFT )
	{
		EmitSound( "NPC_FastZombie.GallopLeft" );
		return;
	}

	if ( pEvent->event == AE_FASTZOMBIE_GALLOP_RIGHT )
	{
		EmitSound( "NPC_FastZombie.GallopRight" );
		return;
	}
	
	if ( pEvent->event == AE_ZOMBIE_ATTACK_RIGHT )
	{
		Vector right;
		AngleVectors( GetLocalAngles(), NULL, &right, NULL );
		right = right * -50;

		QAngle angle( -3, -5, -3  );
		ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_RIGHT_HAND );
		return;
	}

	if ( pEvent->event == AE_ZOMBIE_ATTACK_LEFT )
	{
		Vector right;
		AngleVectors( GetLocalAngles(), NULL, &right, NULL );
		right = right * 50;
		QAngle angle( -3, 5, -3 );
		ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_LEFT_HAND );
		return;
	}

//=============================================================================
#ifdef HL2_EPISODIC

	// Do the leap attack
	if ( pEvent->event == AE_FASTZOMBIE_VEHICLE_LEAP )
	{
		VehicleLeapAttack();
		return;
	}

	// Die while doing an SS in a vehicle
	if ( pEvent->event == AE_FASTZOMBIE_VEHICLE_SS_DIE )
	{
		if ( IsInAVehicle() )
		{
			// Get the vehicle's present speed as a baseline
			Vector vecVelocity = vec3_origin;
			CBaseEntity *pVehicle = m_PassengerBehavior.GetTargetVehicle();
			if ( pVehicle )
			{
				pVehicle->GetVelocity( &vecVelocity, NULL );
			}

			// TODO: We need to make this content driven -- jdw
			Vector vecForward, vecRight, vecUp;
			GetVectors( &vecForward, &vecRight, &vecUp );

			vecVelocity += ( vecForward * -2500.0f ) + ( vecRight * 200.0f ) + ( vecUp * 300 );
			
			// Always kill
			float flDamage = GetMaxHealth() + 10;

			// Take the damage and die
			CTakeDamageInfo info( this, this, vecVelocity * 25.0f, WorldSpaceCenter(), flDamage, (DMG_CRUSH|DMG_VEHICLE) );
			TakeDamage( info );
		}
		return;
	}

#endif // HL2_EPISODIC
//=============================================================================

	BaseClass::HandleAnimEvent( pEvent );
}


//-----------------------------------------------------------------------------
// Purpose: Jump at the enemy!! (stole this from the headcrab)
//
//
//-----------------------------------------------------------------------------
void CFastZombie::LeapAttack( void )
{
	SetGroundEntity( NULL );

	BeginAttackJump();

	LeapAttackSound();

	//
	// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
	//
	UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));

	Vector vecJumpDir;
	CBaseEntity *pEnemy = GetEnemy();

	if ( pEnemy )
	{
		Vector vecEnemyPos = pEnemy->WorldSpaceCenter();

		float gravity = GetCurrentGravity();
		if ( gravity <= 1 )
		{
			gravity = 1;
		}

		//
		// How fast does the zombie need to travel to reach my enemy's eyes given gravity?
		//
		float height = ( vecEnemyPos.z - GetAbsOrigin().z );

		if ( height < 16 )
		{
			height = 16;
		}
		else if ( height > 120 )
		{
			height = 120;
		}
		float speed = sqrt( 2 * gravity * height );
		float time = speed / gravity;

		//
		// Scale the sideways velocity to get there at the right time
		//
		vecJumpDir = vecEnemyPos - GetAbsOrigin();
		vecJumpDir = vecJumpDir / time;

		//
		// Speed to offset gravity at the desired height.
		//
		vecJumpDir.z = speed;

		//
		// Don't jump too far/fast.
		//
#define CLAMP 1000.0
		float distance = vecJumpDir.Length();
		if ( distance > CLAMP )
		{
			vecJumpDir = vecJumpDir * ( CLAMP / distance );
		}

		// try speeding up a bit.
		SetAbsVelocity( vecJumpDir );
		m_flNextAttack = gpGlobals->curtime + 2;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFastZombie::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_FASTZOMBIE_VERIFY_ATTACK:
		// Simply ensure that the zombie still has a valid melee attack
		if( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
		{
			TaskComplete();
		}
		else
		{
			TaskFail("");
		}
		break;

	case TASK_FASTZOMBIE_JUMP_BACK:
		{
			SetActivity( ACT_IDLE );

			SetGroundEntity( NULL );

			BeginAttackJump();

			Vector forward;
			AngleVectors( GetLocalAngles(), &forward );

			//
			// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
			//
			UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));

			ApplyAbsVelocityImpulse( forward * -200 + Vector( 0, 0, 200 ) );
		}
		break;

	case TASK_FASTZOMBIE_UNSTICK_JUMP:
		{
			SetGroundEntity( NULL );

			// Call begin attack jump. A little bit later if we fail to pathfind, we check
			// this value to see if we just jumped. If so, we assume we've jumped 
			// to someplace that's not pathing friendly, and so must jump again to get out.
			BeginAttackJump();

			//
			// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
			//
			UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));

			CBaseEntity *pEnemy = GetEnemy();
			Vector vecJumpDir;

			if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN )
			{
				// Jump off the pipe backwards!
				Vector forward;

				GetVectors( &forward, NULL, NULL );

				ApplyAbsVelocityImpulse( forward * -200 );
			}
			else if( pEnemy )
			{
				vecJumpDir = pEnemy->GetLocalOrigin() - GetLocalOrigin();
				VectorNormalize( vecJumpDir );
				vecJumpDir.z = 0;

				ApplyAbsVelocityImpulse( vecJumpDir * 300 + Vector( 0, 0, 200 ) );
			}
			else
			{
				DevMsg("UNHANDLED CASE! Stuck Fast Zombie with no enemy!\n");
			}
		}
		break;

	case TASK_WAIT_FOR_MOVEMENT:
		// If we're waiting for movement, that means that pathfinding succeeded, and
		// we're about to be moving. So we aren't stuck. So clear this flag. 
		m_fJustJumped = false;

		BaseClass::StartTask( pTask );
		break;

	case TASK_FACE_ENEMY:
		{
			// We don't use the base class implementation of this, because GetTurnActivity
			// stomps our landing scrabble animations (sjb)
			Vector flEnemyLKP = GetEnemyLKP();
			GetMotor()->SetIdealYawToTarget( flEnemyLKP );
		}
		break;

	case TASK_FASTZOMBIE_LAND_RECOVER:
		{
			// Set the ideal yaw
			Vector flEnemyLKP = GetEnemyLKP();
			GetMotor()->SetIdealYawToTarget( flEnemyLKP );

			// figure out which way to turn.
			float flDeltaYaw = GetMotor()->DeltaIdealYaw();

			if( flDeltaYaw < 0 )
			{
				SetIdealActivity( (Activity)ACT_FASTZOMBIE_LAND_RIGHT );
			}
			else
			{
				SetIdealActivity( (Activity)ACT_FASTZOMBIE_LAND_LEFT );
			}


			TaskComplete();
		}
		break;

	case TASK_RANGE_ATTACK1:

		// Make melee attacks impossible until we land!
		m_flNextMeleeAttack = gpGlobals->curtime + 60;

		SetTouch( &CFastZombie::LeapAttackTouch );
		break;

	case TASK_FASTZOMBIE_DO_ATTACK:
		SetActivity( (Activity)ACT_FASTZOMBIE_LEAP_SOAR );
		break;

	default:
		BaseClass::StartTask( pTask );
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFastZombie::RunTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_FASTZOMBIE_JUMP_BACK:
	case TASK_FASTZOMBIE_UNSTICK_JUMP:
		if( GetFlags() & FL_ONGROUND )
		{
			TaskComplete();
		}
		break;

	case TASK_RANGE_ATTACK1:
		if( ( GetFlags() & FL_ONGROUND ) || ( m_pfnTouch == NULL ) )
		{
			// All done when you touch the ground, or if our touch function has somehow cleared.
			TaskComplete();

			// Allow melee attacks again.
			m_flNextMeleeAttack = gpGlobals->curtime + 0.5;
			return;
		}
		break;

	default:
		BaseClass::RunTask( pTask );
		break;
	}
}


//---------------------------------------------------------
//---------------------------------------------------------
int CFastZombie::TranslateSchedule( int scheduleType )
{
	switch( scheduleType )
	{
	case SCHED_RANGE_ATTACK1:
		{
			// Scream right now, cause in half a second, we're gonna jump!!
	
			if( !m_fHasScreamed )
			{
				// Only play that over-the-top attack scream once per combat state.
				EmitSound( "NPC_FastZombie.Scream" );
				m_fHasScreamed = true;
			}
			else
			{
				EmitSound( "NPC_FastZombie.RangeAttack" );
			}

			return SCHED_FASTZOMBIE_RANGE_ATTACK1;
		}
		break;

	case SCHED_MELEE_ATTACK1:
		if ( m_fIsTorso == true )
		{
			return SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1;
		}
		else
		{
			return SCHED_FASTZOMBIE_MELEE_ATTACK1;
		}
		break;

	case SCHED_FASTZOMBIE_UNSTICK_JUMP:
		if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT )
		{
			return SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP;
		}
		else
		{
			return SCHED_FASTZOMBIE_UNSTICK_JUMP;
		}
		break;
	case SCHED_MOVE_TO_WEAPON_RANGE:
		{
			float flZDist = fabs( GetEnemy()->GetLocalOrigin().z - GetLocalOrigin().z );
			if ( flZDist > FASTZOMBIE_MAXLEAP_Z )
				return SCHED_CHASE_ENEMY;
			else // fall through to default
				return BaseClass::TranslateSchedule( scheduleType );
			break;
		}

	default:
		return BaseClass::TranslateSchedule( scheduleType );
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
Activity CFastZombie::NPC_TranslateActivity( Activity baseAct )
{
	if ( baseAct == ACT_CLIMB_DOWN )
		return ACT_CLIMB_UP;
	
	return BaseClass::NPC_TranslateActivity( baseAct );
}

//---------------------------------------------------------
//---------------------------------------------------------
void CFastZombie::LeapAttackTouch( CBaseEntity *pOther )
{
	if ( !pOther->IsSolid() )
	{
		// Touching a trigger or something.
		return;
	}

	// Stop the zombie and knock the player back
	Vector vecNewVelocity( 0, 0, GetAbsVelocity().z );
	SetAbsVelocity( vecNewVelocity );

	Vector forward;
	AngleVectors( GetLocalAngles(), &forward );
	forward *= 500;
	QAngle qaPunch( 15, random->RandomInt(-5,5), random->RandomInt(-5,5) );
	
	ClawAttack( GetClawAttackRange(), 5, qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS );

	SetTouch( NULL );
}

//-----------------------------------------------------------------------------
// Purpose: Lets us know if we touch the player while we're climbing.
//-----------------------------------------------------------------------------
void CFastZombie::ClimbTouch( CBaseEntity *pOther )
{
	if ( pOther->IsPlayer() )
	{
		// If I hit the player, shove him aside.
		Vector vecDir = pOther->WorldSpaceCenter() - WorldSpaceCenter();
		vecDir.z = 0.0; // planar
		VectorNormalize( vecDir );

		if( IsXbox() )
		{
			vecDir *= 400.0f;
		}
		else
		{
			vecDir *= 200.0f;
		}

		pOther->VelocityPunch( vecDir );

		if ( GetActivity() != ACT_CLIMB_DISMOUNT || 
			 ( pOther->GetGroundEntity() == NULL &&
			   GetNavigator()->IsGoalActive() &&
			   pOther->GetAbsOrigin().z - GetNavigator()->GetCurWaypointPos().z < -1.0 ) )
		{
			SetCondition( COND_FASTZOMBIE_CLIMB_TOUCH );
		}

		SetTouch( NULL );
	}
	else if ( dynamic_cast<CPhysicsProp *>(pOther) )
	{
		NPCPhysics_CreateSolver( this, pOther, true, 5.0 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Shuts down our looping sounds.
//-----------------------------------------------------------------------------
void CFastZombie::StopLoopingSounds( void )
{
	if ( m_pMoanSound )
	{
		ENVELOPE_CONTROLLER.SoundDestroy( m_pMoanSound );
		m_pMoanSound = NULL;
	}

	if ( m_pLayer2 )
	{
		ENVELOPE_CONTROLLER.SoundDestroy( m_pLayer2 );
		m_pLayer2 = NULL;
	}

	BaseClass::StopLoopingSounds();
}


//-----------------------------------------------------------------------------
// Purpose: Fast zombie cannot range attack when he's a torso!
//-----------------------------------------------------------------------------
void CFastZombie::BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce )
{
	CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK1 );
	CapabilitiesRemove( bits_CAP_MOVE_JUMP );
	CapabilitiesRemove( bits_CAP_MOVE_CLIMB );

	ReleaseHeadcrab( EyePosition(), vecLegsForce * 0.5, true, true, true );

	BaseClass::BecomeTorso( vecTorsoForce, vecLegsForce );
}

//-----------------------------------------------------------------------------
// Purpose: Returns true if a reasonable jumping distance
// Input  :
// Output :
//-----------------------------------------------------------------------------
bool CFastZombie::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
{
	const float MAX_JUMP_RISE		= 220.0f;
	const float MAX_JUMP_DISTANCE	= 512.0f;
	const float MAX_JUMP_DROP		= 384.0f;

	if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
	{
		// Hang onto the jump distance. The AI is going to want it.
		m_flJumpDist = (startPos - endPos).Length();

		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

bool CFastZombie::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
{
	float delta = vecEnd.z - vecStart.z;

	float multiplier = 1;
	if ( moveType == bits_CAP_MOVE_JUMP )
	{
		multiplier = ( delta < 0 ) ? 0.5 : 1.5;
	}
	else if ( moveType == bits_CAP_MOVE_CLIMB )
	{
		multiplier = ( delta > 0 ) ? 0.5 : 4.0;
	}

	*pCost *= multiplier;

	return ( multiplier != 1 );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

bool CFastZombie::ShouldFailNav( bool bMovementFailed )
{
	if ( !BaseClass::ShouldFailNav( bMovementFailed ) )
	{
		DevMsg( 2, "Fast zombie in scripted sequence probably hit bad node configuration at %s\n", VecToString( GetAbsOrigin() ) );
		
		if ( GetNavigator()->GetPath()->CurWaypointNavType() == NAV_JUMP && GetNavigator()->RefindPathToGoal( false ) )
		{
			return false;
		}
		DevMsg( 2, "Fast zombie failed to get to scripted sequence\n" );
	}

	return true;
}


//---------------------------------------------------------
// Purpose: Notifier that lets us know when the fast
//			zombie has hit the apex of a navigational jump.
//---------------------------------------------------------
void CFastZombie::OnNavJumpHitApex( void )
{
	m_fHitApex = true;	// stop subsequent notifications
}

//---------------------------------------------------------
// Purpose: Overridden to detect when the zombie goes into
//			and out of his climb state and his navigation
//			jump state.
//---------------------------------------------------------
void CFastZombie::OnChangeActivity( Activity NewActivity )
{
	if ( NewActivity == ACT_FASTZOMBIE_FRENZY )
	{
		// Scream!!!!
		EmitSound( "NPC_FastZombie.Frenzy" );
		SetPlaybackRate( random->RandomFloat( .9, 1.1 ) );	
	}

	if( NewActivity == ACT_JUMP )
	{
		BeginNavJump();
	}
	else if( GetActivity() == ACT_JUMP )
	{
		EndNavJump();
	}

	if ( NewActivity == ACT_LAND )
	{
		m_flNextAttack = gpGlobals->curtime + 1.0;
	}

	if ( NewActivity == ACT_GLIDE )
	{
		// Started a jump.
		BeginNavJump();
	}
	else if ( GetActivity() == ACT_GLIDE )
	{
		// Landed a jump
		EndNavJump();

		if ( m_pMoanSound )
			ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, FASTZOMBIE_MIN_PITCH, 0.3 );
	}

	if ( NewActivity == ACT_CLIMB_UP )
	{
		// Started a climb!
		if ( m_pMoanSound )
			ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0.0, 0.2 );

		SetTouch( &CFastZombie::ClimbTouch );
	}
	else if ( GetActivity() == ACT_CLIMB_DISMOUNT || ( GetActivity() == ACT_CLIMB_UP && NewActivity != ACT_CLIMB_DISMOUNT ) )
	{
		// Ended a climb
		if ( m_pMoanSound )
			ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1.0, 0.2 );

		SetTouch( NULL );
	}

	BaseClass::OnChangeActivity( NewActivity );
}


//=========================================================
// 
//=========================================================
int CFastZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
{
	if ( m_fJustJumped )
	{
		// Assume we failed cause we jumped to a bad place.
		m_fJustJumped = false;
		return SCHED_FASTZOMBIE_UNSTICK_JUMP;
	}

	return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
}

//=========================================================
// Purpose: Do some record keeping for jumps made for 
//			navigational purposes (i.e., not attack jumps)
//=========================================================
void CFastZombie::BeginNavJump( void )
{
	m_fIsNavJumping = true;
	m_fHitApex = false;

	ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pLayer2, SOUNDCTRL_CHANGE_VOLUME, envFastZombieVolumeJump, ARRAYSIZE(envFastZombieVolumeJump) );
}

//=========================================================
// 
//=========================================================
void CFastZombie::EndNavJump( void )
{
	m_fIsNavJumping = false;
	m_fHitApex = false;
}

//=========================================================
// 
//=========================================================
void CFastZombie::BeginAttackJump( void )
{
	// Set this to true. A little bit later if we fail to pathfind, we check
	// this value to see if we just jumped. If so, we assume we've jumped 
	// to someplace that's not pathing friendly, and so must jump again to get out.
	m_fJustJumped = true;

	m_flJumpStartAltitude = GetLocalOrigin().z;
}

//=========================================================
// 
//=========================================================
void CFastZombie::EndAttackJump( void )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFastZombie::BuildScheduleTestBits( void )
{
	// FIXME: This is probably the desired call to make, but it opts into an untested base class path, we'll need to
	//		  revisit this and figure out if we want that. -- jdw
	// BaseClass::BuildScheduleTestBits();
	//
	// For now, make sure our active behavior gets a chance to add its own bits
	if ( GetRunningBehavior() )
		GetRunningBehavior()->BridgeBuildScheduleTestBits(); 

#ifdef HL2_EPISODIC
	SetCustomInterruptCondition( COND_PROVOKED );
#endif	// HL2_EPISODIC

	// Any schedule that makes us climb should break if we touch player
	if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT)
	{
		SetCustomInterruptCondition( COND_FASTZOMBIE_CLIMB_TOUCH );
	}
	else
	{
		ClearCustomInterruptCondition( COND_FASTZOMBIE_CLIMB_TOUCH );
	}
}

//=========================================================
// 
//=========================================================
void CFastZombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
{
	if( NewState == NPC_STATE_COMBAT )
	{
		SetAngrySoundState();
	}
	else if( (m_pMoanSound) && ( NewState == NPC_STATE_IDLE || NewState == NPC_STATE_ALERT ) ) ///!!!HACKHACK - sjb
	{
		// Don't make this sound while we're slumped
		if ( IsSlumped() == false )
		{
			// Set it up so that if the zombie goes into combat state sometime down the road
			// that he'll be able to scream.
			m_fHasScreamed = false;

			SetIdleSoundState();
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFastZombie::Event_Killed( const CTakeDamageInfo &info )
{
	// Shut up my screaming sounds.
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "NPC_FastZombie.NoSound" );

	CTakeDamageInfo dInfo = info;

#if 0

	// Become a server-side ragdoll and create a constraint at the hand
	if ( m_PassengerBehavior.GetPassengerState() == PASSENGER_STATE_INSIDE )
	{
		IPhysicsObject *pVehiclePhys = m_PassengerBehavior.GetTargetVehicle()->GetServerVehicle()->GetVehicleEnt()->VPhysicsGetObject();
		CBaseAnimating *pVehicleAnimating = m_PassengerBehavior.GetTargetVehicle()->GetServerVehicle()->GetVehicleEnt()->GetBaseAnimating();
		int nRightHandBone = 31;//GetBaseAnimating()->LookupBone( "ValveBiped.Bip01_R_Finger2" );
		Vector vecRightHandPos;
		QAngle vecRightHandAngle;
		GetAttachment( LookupAttachment( "Blood_Right" ), vecRightHandPos, vecRightHandAngle );
		//CTakeDamageInfo dInfo( GetEnemy(), GetEnemy(), RandomVector( -200, 200 ), WorldSpaceCenter(), 50.0f, DMG_CRUSH );
		dInfo.SetDamageType( info.GetDamageType() | DMG_REMOVENORAGDOLL );
		dInfo.ScaleDamageForce( 10.0f );
		CBaseEntity *pRagdoll = CreateServerRagdoll( GetBaseAnimating(), 0, info, COLLISION_GROUP_DEBRIS );

		/*
		GetBaseAnimating()->GetBonePosition( nRightHandBone, vecRightHandPos, vecRightHandAngle );

		CBaseEntity *pRagdoll = CreateServerRagdollAttached(	GetBaseAnimating(), 
																vec3_origin, 
																-1, 
																COLLISION_GROUP_DEBRIS, 
																pVehiclePhys,
																pVehicleAnimating, 
																0, 
																vecRightHandPos,
																nRightHandBone,	
																vec3_origin );*/

	}
#endif

	BaseClass::Event_Killed( dInfo );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CFastZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
{
	if( m_fIsTorso )
	{
		// Already split.
		return false;
	}

	// Break in half IF:
	// 
	// Take half or more of max health in DMG_BLAST
	if( (info.GetDamageType() & DMG_BLAST) && m_iHealth <= 0 )
	{
		return true;
	}

	return false;
}

//=============================================================================
#ifdef HL2_EPISODIC

//-----------------------------------------------------------------------------
// Purpose: Add the passenger behavior to our repertoire
//-----------------------------------------------------------------------------
bool CFastZombie::CreateBehaviors( void )
{
	AddBehavior( &m_PassengerBehavior );

	return BaseClass::CreateBehaviors();
}

//-----------------------------------------------------------------------------
// Purpose: Get on the vehicle!
//-----------------------------------------------------------------------------
void CFastZombie::InputAttachToVehicle( inputdata_t &inputdata )
{
	// Interrupt us
	SetCondition( COND_PROVOKED );

	// Find the target vehicle
	CBaseEntity *pEntity = FindNamedEntity( inputdata.value.String() );
	CPropJeepEpisodic *pVehicle = dynamic_cast<CPropJeepEpisodic *>(pEntity);

	// Get in the car if it's valid
	if ( pVehicle && CanEnterVehicle( pVehicle ) )
	{
		// Set her into a "passenger" behavior
		m_PassengerBehavior.Enable( pVehicle );
		m_PassengerBehavior.AttachToVehicle();
	}

	RemoveSpawnFlags( SF_NPC_GAG );
}

//-----------------------------------------------------------------------------
// Purpose: Passed along from the vehicle's callback list
//-----------------------------------------------------------------------------
void CFastZombie::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	// Only do the override while riding on a vehicle
	if ( m_PassengerBehavior.CanSelectSchedule() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE )
	{
		int damageType = 0;
		float flDamage = CalculatePhysicsImpactDamage( index, pEvent, gZombiePassengerImpactDamageTable, 1.0, true, damageType );

		if ( flDamage > 0  )
		{
			Vector damagePos;
			pEvent->pInternalData->GetContactPoint( damagePos );
			Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
			CTakeDamageInfo info( this, this, damageForce, damagePos, flDamage, (damageType|DMG_VEHICLE) );
			TakeDamage( info );
		}
		return;
	}

	BaseClass::VPhysicsCollision( index, pEvent );
}

//-----------------------------------------------------------------------------
// Purpose: FIXME: Fold this into LeapAttack using different jump targets!
//-----------------------------------------------------------------------------
void CFastZombie::VehicleLeapAttack( void )
{
	CBaseEntity *pEnemy = GetEnemy();
	if ( pEnemy == NULL )
		return;

	Vector vecEnemyPos;
	UTIL_PredictedPosition( pEnemy, 1.0f, &vecEnemyPos );

	// Move
	SetGroundEntity( NULL );
	BeginAttackJump();
	LeapAttackSound();

	// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
	UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));

	// FIXME: This should be the exact position we'll enter at, but this approximates it generally
	//vecEnemyPos[2] += 16;

	Vector vecMins = GetHullMins();
	Vector vecMaxs = GetHullMaxs();
	Vector vecJumpDir = VecCheckToss( this, GetAbsOrigin(), vecEnemyPos, 0.1f, 1.0f, false, &vecMins, &vecMaxs );

	SetAbsVelocity( vecJumpDir );
	m_flNextAttack = gpGlobals->curtime + 2.0f;
	SetTouch( &CFastZombie::VehicleLeapAttackTouch );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CFastZombie::CanEnterVehicle( CPropJeepEpisodic *pVehicle )
{
	if ( pVehicle == NULL )
		return false;

	return pVehicle->NPC_CanEnterVehicle( this, false );
}

//-----------------------------------------------------------------------------
// Purpose: FIXME: Move into behavior?
// Input  : *pOther - 
//-----------------------------------------------------------------------------
void CFastZombie::VehicleLeapAttackTouch( CBaseEntity *pOther )
{
	if ( pOther->GetServerVehicle() )
	{
		m_PassengerBehavior.AttachToVehicle();

		// HACK: Stop us cold
		SetLocalVelocity( vec3_origin );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Determine whether we're in a vehicle or not
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CFastZombie::IsInAVehicle( void )
{
	// Must be active and getting in/out of vehicle
	if ( m_PassengerBehavior.IsEnabled() && m_PassengerBehavior.GetPassengerState() != PASSENGER_STATE_OUTSIDE )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Override our efficiency so that we don't jitter when we're in the middle
//			of our enter/exit animations.
// Input  : bInPVS - Whether we're in the PVS or not
//-----------------------------------------------------------------------------
void CFastZombie::UpdateEfficiency( bool bInPVS )
{ 
	// If we're transitioning and in the PVS, we override our efficiency
	if ( IsInAVehicle() && bInPVS )
	{
		PassengerState_e nState = m_PassengerBehavior.GetPassengerState();
		if ( nState == PASSENGER_STATE_ENTERING || nState == PASSENGER_STATE_EXITING )
		{
			SetEfficiency( AIE_NORMAL );
			return;
		}
	}

	// Do the default behavior
	BaseClass::UpdateEfficiency( bInPVS );
}

#endif	// HL2_EPISODIC
//=============================================================================

//-----------------------------------------------------------------------------

AI_BEGIN_CUSTOM_NPC( npc_fastzombie, CFastZombie )

	DECLARE_ACTIVITY( ACT_FASTZOMBIE_LEAP_SOAR )
	DECLARE_ACTIVITY( ACT_FASTZOMBIE_LEAP_STRIKE )
	DECLARE_ACTIVITY( ACT_FASTZOMBIE_LAND_RIGHT )
	DECLARE_ACTIVITY( ACT_FASTZOMBIE_LAND_LEFT )
	DECLARE_ACTIVITY( ACT_FASTZOMBIE_FRENZY )
	DECLARE_ACTIVITY( ACT_FASTZOMBIE_BIG_SLASH )
	
	DECLARE_TASK( TASK_FASTZOMBIE_DO_ATTACK )
	DECLARE_TASK( TASK_FASTZOMBIE_LAND_RECOVER )
	DECLARE_TASK( TASK_FASTZOMBIE_UNSTICK_JUMP )
	DECLARE_TASK( TASK_FASTZOMBIE_JUMP_BACK )
	DECLARE_TASK( TASK_FASTZOMBIE_VERIFY_ATTACK )

	DECLARE_CONDITION( COND_FASTZOMBIE_CLIMB_TOUCH )

	//Adrian: events go here
	DECLARE_ANIMEVENT( AE_FASTZOMBIE_LEAP )
	DECLARE_ANIMEVENT( AE_FASTZOMBIE_GALLOP_LEFT )
	DECLARE_ANIMEVENT( AE_FASTZOMBIE_GALLOP_RIGHT )
	DECLARE_ANIMEVENT( AE_FASTZOMBIE_CLIMB_LEFT )
	DECLARE_ANIMEVENT( AE_FASTZOMBIE_CLIMB_RIGHT )

#ifdef HL2_EPISODIC
	// FIXME: Move!
	DECLARE_ANIMEVENT( AE_PASSENGER_PHYSICS_PUSH )
	DECLARE_ANIMEVENT( AE_FASTZOMBIE_VEHICLE_LEAP )
	DECLARE_ANIMEVENT( AE_FASTZOMBIE_VEHICLE_SS_DIE )
#endif	// HL2_EPISODIC

	//=========================================================
	// 
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_FASTZOMBIE_RANGE_ATTACK1,

		"	Tasks"
		"		TASK_PLAY_SEQUENCE				ACTIVITY:ACT_RANGE_ATTACK1"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_FASTZOMBIE_LEAP_STRIKE"
		"		TASK_RANGE_ATTACK1				0"
		"		TASK_WAIT						0.1"
		"		TASK_FASTZOMBIE_LAND_RECOVER	0" // essentially just figure out which way to turn.
		"		TASK_FACE_ENEMY					0"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// I have landed somewhere that's pathfinding-unfriendly
	// just try to jump out.
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_FASTZOMBIE_UNSTICK_JUMP,

		"	Tasks"
		"		TASK_FASTZOMBIE_UNSTICK_JUMP	0"
		"	"
		"	Interrupts"
	)

	//=========================================================
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP,

		"	Tasks"
		"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"
		"		TASK_FASTZOMBIE_UNSTICK_JUMP	0"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// > Melee_Attack1
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_FASTZOMBIE_MELEE_ATTACK1,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_FACE_ENEMY					0"
		"		TASK_MELEE_ATTACK1				0"
		"		TASK_MELEE_ATTACK1				0"
		"		TASK_PLAY_SEQUENCE				ACTIVITY:ACT_FASTZOMBIE_FRENZY"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_CHASE_ENEMY"
		"		TASK_FASTZOMBIE_VERIFY_ATTACK	0"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY	ACTIVITY:ACT_FASTZOMBIE_BIG_SLASH"

		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_OCCLUDED"
	);

	//=========================================================
	// > Melee_Attack1
	//=========================================================
	DEFINE_SCHEDULE
		(
		SCHED_FASTZOMBIE_TORSO_MELEE_ATTACK1,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_FACE_ENEMY					0"
		"		TASK_MELEE_ATTACK1				0"
		"		TASK_MELEE_ATTACK1				0"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_CHASE_ENEMY"
		"		TASK_FASTZOMBIE_VERIFY_ATTACK	0"

		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_OCCLUDED"
		);

AI_END_CUSTOM_NPC()