//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:		Cute hound like Alien.
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "game.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_navigator.h"
#include "ai_route.h"
#include "ai_squad.h"
#include "ai_squadslot.h"
#include "ai_hint.h"
#include "npcevent.h"
#include "animation.h"
#include "hl1_npc_houndeye.h"
#include "gib.h"
#include "soundent.h"
#include "ndebugoverlay.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"

// houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional 
// squad member increases the BASE damage by 110%, per the spec.
#define HOUNDEYE_MAX_SQUAD_SIZE			4
#define	HOUNDEYE_SQUAD_BONUS			(float)1.1

#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye

#define HOUNDEYE_SOUND_STARTLE_VOLUME	128 // how loud a sound has to be to badly scare a sleeping houndeye

#define HOUNDEYE_TOP_MASS	 300.0f

ConVar sk_houndeye_health ( "sk_houndeye_health", "20" );
ConVar sk_houndeye_dmg_blast ( "sk_houndeye_dmg_blast", "15" );

static int s_iSquadIndex = 0;

//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define		HOUND_AE_WARN			1
#define		HOUND_AE_STARTATTACK	2
#define		HOUND_AE_THUMP			3
#define		HOUND_AE_ANGERSOUND1	4
#define		HOUND_AE_ANGERSOUND2	5
#define		HOUND_AE_HOPBACK		6
#define		HOUND_AE_CLOSE_EYE		7

BEGIN_DATADESC( CNPC_Houndeye )
	DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
	DEFINE_FIELD( m_fAsleep, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_fDontBlink, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_vecPackCenter, FIELD_POSITION_VECTOR ),
END_DATADESC()

LINK_ENTITY_TO_CLASS( monster_houndeye, CNPC_Houndeye );

//=========================================================
// monster-specific tasks
//=========================================================
enum
{
	TASK_HOUND_CLOSE_EYE = LAST_SHARED_TASK,
	TASK_HOUND_OPEN_EYE,
	TASK_HOUND_THREAT_DISPLAY,
	TASK_HOUND_FALL_ASLEEP,
	TASK_HOUND_WAKE_UP,
	TASK_HOUND_HOP_BACK,
};

//=========================================================
// monster-specific schedule types
//=========================================================
enum
{
	SCHED_HOUND_AGITATED = LAST_SHARED_SCHEDULE,
	SCHED_HOUND_HOP_RETREAT,
	SCHED_HOUND_YELL1,
	SCHED_HOUND_YELL2,
	SCHED_HOUND_RANGEATTACK,
	SCHED_HOUND_SLEEP,
	SCHED_HOUND_WAKE_LAZY,
	SCHED_HOUND_WAKE_URGENT,
	SCHED_HOUND_SPECIALATTACK,
	SCHED_HOUND_COMBAT_FAIL_PVS,
	SCHED_HOUND_COMBAT_FAIL_NOPVS,
//	SCHED_HOUND_FAIL,
};

enum HoundEyeSquadSlots
{	
	SQUAD_SLOTS_HOUND_ATTACK = LAST_SHARED_SQUADSLOT,
};


//=========================================================
// Spawn
//=========================================================
void CNPC_Houndeye::Spawn()
{
	Precache( );
	
	SetRenderColor( 255, 255, 255, 255 );

	SetModel( "models/houndeye.mdl" );
	
	SetHullType(HULL_TINY);
	SetHullSizeNormal();
	
	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	m_bloodColor		= BLOOD_COLOR_YELLOW;
	ClearEffects();
	m_iHealth			= sk_houndeye_health.GetFloat();
	m_flFieldOfView		= 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
	m_NPCState			= NPC_STATE_NONE;
	m_fAsleep			= FALSE; // everyone spawns awake
	m_fDontBlink		= FALSE;
	
	CapabilitiesClear();

	CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
	CapabilitiesAdd( bits_CAP_SQUAD);

	NPCInit();
}

//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Houndeye::Precache()
{
	PrecacheModel("models/houndeye.mdl");

	m_iSpriteTexture = PrecacheModel( "sprites/shockwave.vmt" );

	PrecacheScriptSound( "HoundEye.Idle" );
	PrecacheScriptSound( "HoundEye.Warn" );
	PrecacheScriptSound( "HoundEye.Hunt" );
	PrecacheScriptSound( "HoundEye.Alert" );
	PrecacheScriptSound( "HoundEye.Die" );
	PrecacheScriptSound( "HoundEye.Pain" );
	PrecacheScriptSound( "HoundEye.Anger1" );
	PrecacheScriptSound( "HoundEye.Anger2" );
	PrecacheScriptSound( "HoundEye.Sonic" );

	BaseClass::Precache();
}	

void CNPC_Houndeye::Event_Killed( const CTakeDamageInfo &info )
{
	// Close the eye to make death more obvious
	m_nSkin = 1;
	BaseClass::Event_Killed( info );
}

int CNPC_Houndeye::RangeAttack1Conditions ( float flDot, float flDist )
{
	// I'm not allowed to attack if standing in another hound eye 
	// (note houndeyes allowed to interpenetrate)
	trace_t tr;
	UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,0.1), 
					GetHullMins(), GetHullMaxs(),
					MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
	if (tr.startsolid)
	{
		CBaseEntity *pEntity = tr.m_pEnt;
		if (pEntity->Classify() == CLASS_ALIEN_MONSTER)
		{
			return( COND_NONE );
		}
	}

	// If I'm really close to my enemy allow me to attack if 
	// I'm facing regardless of next attack time
	if (flDist < 100 && flDot >= 0.3)
	{
		return COND_CAN_RANGE_ATTACK1;
	}
	if ( gpGlobals->curtime < m_flNextAttack )
	{
		return( COND_NONE );
	}
	if (flDist > ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ))
	{
		return COND_TOO_FAR_TO_ATTACK;
	}
	if (flDot < 0.3)
	{
		return COND_NOT_FACING_ATTACK;
	}
	return COND_CAN_RANGE_ATTACK1;
}

//=========================================================
// IdleSound
//=========================================================
void CNPC_Houndeye::IdleSound ( void )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "HoundEye.Idle" );	
}

//=========================================================
// IdleSound
//=========================================================
void CNPC_Houndeye::WarmUpSound ( void )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(),"HoundEye.Warn" );	
}

//=========================================================
// WarnSound 
//=========================================================
void CNPC_Houndeye::WarnSound ( void )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "HoundEye.Hunt" );	
}

//=========================================================
// AlertSound 
//=========================================================
void CNPC_Houndeye::AlertSound ( void )
{

	if ( m_pSquad && !m_pSquad->IsLeader( this ) )
		 return; // only leader makes ALERT sound.

	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "HoundEye.Alert" );
}

//=========================================================
// DeathSound 
//=========================================================
void CNPC_Houndeye::DeathSound( const CTakeDamageInfo &info )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "HoundEye.Die" );	
}

//=========================================================
// PainSound 
//=========================================================
void CNPC_Houndeye::PainSound ( const CTakeDamageInfo &info )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "HoundEye.Pain" );	
}

//=========================================================
// MaxYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
float CNPC_Houndeye::MaxYawSpeed  ( void )
{
	int flYS;

	flYS = 90;

	switch ( GetActivity() )
	{
	case ACT_CROUCHIDLE://sleeping!
		flYS = 0;
		break;
	case ACT_IDLE:	
		flYS = 60;
		break;
	case ACT_WALK:
		flYS = 90;
		break;
	case ACT_RUN:	
		flYS = 90;
		break;
	case ACT_TURN_LEFT:
	case ACT_TURN_RIGHT:
		flYS = 90;
		break;
	}

	return flYS;
}

//=========================================================
// Classify - indicates this monster's place in the 
// relationship table.
//=========================================================
Class_T	CNPC_Houndeye::Classify ( void )
{
	return CLASS_ALIEN_MONSTER;
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Houndeye::HandleAnimEvent( animevent_t *pEvent )
{
	switch ( pEvent->event )
	{
		case HOUND_AE_WARN:
			// do stuff for this event.
			WarnSound();
			break;

		case HOUND_AE_STARTATTACK:
			WarmUpSound();
			break;

		case HOUND_AE_HOPBACK:
			{
				float flGravity = GetCurrentGravity();
				Vector v_forward;
				GetVectors( &v_forward, NULL, NULL );

				SetGroundEntity( NULL );

				Vector vecVel = v_forward * -200;
				vecVel.z += ( 0.6 * flGravity ) * 0.5;
				SetAbsVelocity( vecVel );

				break;
			}

		case HOUND_AE_THUMP:
			// emit the shockwaves
			SonicAttack();
			break;

		case HOUND_AE_ANGERSOUND1:
			{
				CPASAttenuationFilter filter( this );
				EmitSound( filter, entindex(), "HoundEye.Anger1" );	
			}
			break;

		case HOUND_AE_ANGERSOUND2:
			{
				CPASAttenuationFilter filter2( this );
				EmitSound( filter2, entindex(), "HoundEye.Anger2" );	
			}
			break;

		case HOUND_AE_CLOSE_EYE:
			if ( !m_fDontBlink )
			{
				m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
			}
			break;

		default:
			BaseClass::HandleAnimEvent( pEvent );
			break;
	}
}

//=========================================================
// SonicAttack
//=========================================================
void CNPC_Houndeye::SonicAttack ( void )
{
	float		flAdjustedDamage;
	float		flDist;

	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "HoundEye.Sonic");

	CBroadcastRecipientFilter filter2;
	te->BeamRingPoint( filter2, 0.0, 
		GetAbsOrigin(),							//origin
		16,										//start radius
		HOUNDEYE_MAX_ATTACK_RADIUS,//end radius
		m_iSpriteTexture,						//texture
		0,										//halo index
		0,										//start frame
		0,										//framerate
		0.2,									//life
		24,									//width
		16,										//spread
		0,										//amplitude
		WriteBeamColor().x,						//r
		WriteBeamColor().y,						//g
		WriteBeamColor().z,						//b
		192,									//a
		0										//speed
		);

	CBroadcastRecipientFilter filter3;
	te->BeamRingPoint( filter3, 0.0, 
		GetAbsOrigin(),									//origin
		16,												//start radius
		HOUNDEYE_MAX_ATTACK_RADIUS / 2,											//end radius
		m_iSpriteTexture,								//texture
		0,												//halo index
		0,												//start frame
		0,												//framerate
		0.2,											//life
		24,											//width
		16,												//spread
		0,												//amplitude
		WriteBeamColor().x,								//r
		WriteBeamColor().y,								//g
		WriteBeamColor().z,								//b
		192,											//a
		0												//speed
		);
	
	CBaseEntity *pEntity = NULL;
	// iterate on all entities in the vicinity.
	while ((pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL)
	{
		if ( pEntity->m_takedamage  != DAMAGE_NO )
		{
			if ( !FClassnameIs(pEntity, "monster_houndeye") )
			{// houndeyes don't hurt other houndeyes with their attack

				// houndeyes do FULL damage if the ent in question is visible. Half damage otherwise.
				// This means that you must get out of the houndeye's attack range entirely to avoid damage.
				// Calculate full damage first

				if ( m_pSquad && m_pSquad->NumMembers() > 1 )
				{
					// squad gets attack bonus.
					flAdjustedDamage = sk_houndeye_dmg_blast.GetFloat() + sk_houndeye_dmg_blast.GetFloat() * ( HOUNDEYE_SQUAD_BONUS * ( m_pSquad->NumMembers() - 1 ) );
				}
				else
				{
					// solo
					flAdjustedDamage =sk_houndeye_dmg_blast.GetFloat();
				}

				flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).Length();

				flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage;

				if ( !FVisible( pEntity ) )
				{
					if ( pEntity->IsPlayer() )
					{
						// if this entity is a client, and is not in full view, inflict half damage. We do this so that players still 
						// take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients
						// so that monsters in other parts of the level don't take the damage and get pissed.
						flAdjustedDamage *= 0.5;
					}
					else if ( !FClassnameIs( pEntity, "func_breakable" ) && !FClassnameIs( pEntity, "func_pushable" ) ) 
					{
						// do not hurt nonclients through walls, but allow damage to be done to breakables
						flAdjustedDamage = 0;
					}
				}

				//ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage );

				if (flAdjustedDamage > 0 )
				{
					CTakeDamageInfo info( this, this, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB );
					CalculateExplosiveDamageForce( &info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin() );

					pEntity->TakeDamage( info );

					if ( (pEntity->GetAbsOrigin() - GetAbsOrigin()).Length2D() <= HOUNDEYE_MAX_ATTACK_RADIUS )
					{
						if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer()) ) 
						{
							IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();

							if ( pPhysObject )
							{
								float flMass = pPhysObject->GetMass();

								if ( flMass <= HOUNDEYE_TOP_MASS )
								{
									// Increase the vertical lift of the force
									Vector vecForce = info.GetDamageForce();
									vecForce.z *= 2.0f;
									info.SetDamageForce( vecForce );

									pEntity->VPhysicsTakeDamage( info );
								}
							}
						}
					}
				}
			}
		}
	}
}

//=========================================================
// WriteBeamColor - writes a color vector to the network 
// based on the size of the group. 
//=========================================================
Vector CNPC_Houndeye::WriteBeamColor ( void )
{
	BYTE	bRed, bGreen, bBlue;

	if ( m_pSquad )
	{
		switch ( m_pSquad->NumMembers() )
		{
		case 1:
			// solo houndeye - weakest beam
			bRed	= 188;
			bGreen	= 220;
			bBlue	= 255;
			break;
		case 2:
			bRed	= 101;
			bGreen	= 133;
			bBlue	= 221;
			break;
		case 3:
			bRed	= 67;
			bGreen	= 85;
			bBlue	= 255;
			break;
		case 4:
			bRed	= 62;
			bGreen	= 33;
			bBlue	= 211;
			break;
		default:
			Msg ( "Unsupported Houndeye SquadSize!\n" );
			bRed	= 188;
			bGreen	= 220;
			bBlue	= 255;
			break;
		}
	}
	else
	{
		// solo houndeye - weakest beam
		bRed	= 188;
		bGreen	= 220;
		bBlue	= 255;
	}
	

	return Vector ( bRed, bGreen, bBlue );
}

bool CNPC_Houndeye::ShouldGoToIdleState( void )
{
	if ( m_pSquad )
	{
		AISquadIter_t iter;
		for (CAI_BaseNPC *pMember = m_pSquad->GetFirstMember( &iter ); pMember; pMember = m_pSquad->GetNextMember( &iter ) )
		{
			if ( pMember != this && pMember->GetHintNode() && pMember->GetHintNode()->HintType() != NO_NODE )
				 return true;
		}

		return true;
	}

	return true;
}

bool CNPC_Houndeye::FValidateHintType ( CAI_Hint *pHint )
{
	switch( pHint->HintType() )
	{
		case HINT_HL1_WORLD_MACHINERY:
			return true;
			break;
		case HINT_HL1_WORLD_BLINKING_LIGHT:
			return true;
			break;
		case HINT_HL1_WORLD_HUMAN_BLOOD:
			return true;
			break;
		case HINT_HL1_WORLD_ALIEN_BLOOD:
			return true;
			break;
	}

	Msg ( "Couldn't validate hint type" );

	return false;
}

//=========================================================
// SetActivity 
//=========================================================
void CNPC_Houndeye::SetActivity ( Activity NewActivity )
{
	int	iSequence;

	if ( NewActivity == GetActivity() )
		return;

	if ( m_NPCState == NPC_STATE_COMBAT && NewActivity == ACT_IDLE && random->RandomInt( 0, 1 ) )
	{
		// play pissed idle.
		iSequence = LookupSequence( "madidle" );

		SetActivity( NewActivity ); // Go ahead and set this so it doesn't keep trying when the anim is not present
	
		// In case someone calls this with something other than the ideal activity
		SetIdealActivity( GetActivity() );

		// Set to the desired anim, or default anim if the desired is not present
		if ( iSequence > ACTIVITY_NOT_AVAILABLE )
		{
			SetSequence( iSequence );	// Set to the reset anim (if it's there)
			SetCycle( 0 );				// FIX: frame counter shouldn't be reset when its the same activity as before
			ResetSequenceInfo();
		}
	}
	else
	{
		BaseClass::SetActivity ( NewActivity );
	}
}

//=========================================================
// start task
//=========================================================
void CNPC_Houndeye::StartTask ( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_HOUND_FALL_ASLEEP:
		{
			m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!)
			TaskComplete();
			break;
		}
	case TASK_HOUND_WAKE_UP:
		{
			m_fAsleep = FALSE; // signal that hound is standing again
			TaskComplete();
			break;
		}
	case TASK_HOUND_OPEN_EYE:
		{
			m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye
			TaskComplete();
			break;
		}
	case TASK_HOUND_CLOSE_EYE:
		{
			m_nSkin = 0;
			m_fDontBlink = TRUE; // tell blink code to leave the eye alone.
			break;
		}
	case TASK_HOUND_THREAT_DISPLAY:
		{
			SetIdealActivity( ACT_IDLE_ANGRY );
			break;
		}
	case TASK_HOUND_HOP_BACK:
		{
			SetIdealActivity( ACT_LEAP );
			break;
		}
	case TASK_RANGE_ATTACK1:
		{
			SetIdealActivity( ACT_RANGE_ATTACK1 );
			break;
		}
	case TASK_SPECIAL_ATTACK1:
		{
			SetIdealActivity( ACT_SPECIAL_ATTACK1 );
			break;
		}
	default: 
		{
			BaseClass::StartTask(pTask);
			break;
		}
	}
}

//=========================================================
// RunTask 
//=========================================================
void CNPC_Houndeye::RunTask ( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_HOUND_THREAT_DISPLAY:
		{
			if ( GetEnemy() )
			{
				float idealYaw;
				idealYaw = UTIL_VecToYaw( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
				GetMotor()->SetIdealYawAndUpdate( idealYaw );
			}

			if ( IsSequenceFinished() )
			{
				TaskComplete();
			}
			
			break;
		}
	case TASK_HOUND_CLOSE_EYE:
		{
			if ( m_nSkin < HOUNDEYE_EYE_FRAMES - 1 )
				 m_nSkin++;
			break;
		}
	case TASK_HOUND_HOP_BACK:
		{
			if ( IsSequenceFinished() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_SPECIAL_ATTACK1:
		{
			m_nSkin = random->RandomInt(0, HOUNDEYE_EYE_FRAMES - 1);

			float idealYaw;
			idealYaw = UTIL_VecToYaw( GetNavigator()->GetGoalPos() );
			GetMotor()->SetIdealYawAndUpdate( idealYaw );
			
			float life;
			life = ((255 - GetCycle()) / ( m_flPlaybackRate * m_flPlaybackRate));
			if (life < 0.1)
			{
				life = 0.1;
			}

		/*	MessageBegin( MSG_PAS, SVC_TEMPENTITY, GetAbsOrigin() );
				WRITE_BYTE(  TE_IMPLOSION);
				WRITE_COORD( GetAbsOrigin().x);
				WRITE_COORD( GetAbsOrigin().y);
				WRITE_COORD( GetAbsOrigin().z + 16);
				WRITE_BYTE( 50 * life + 100);  // radius
				WRITE_BYTE( pev->frame / 25.0 ); // count
				WRITE_BYTE( life * 10 ); // life
			MessageEnd();*/
			
			if ( IsSequenceFinished() )
			{
				SonicAttack(); 
				TaskComplete();
			}

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

//=========================================================
// PrescheduleThink
//=========================================================
void CNPC_Houndeye::PrescheduleThink ( void )
{
	BaseClass::PrescheduleThink();
	
	// if the hound is mad and is running, make hunt noises.
	if ( m_NPCState == NPC_STATE_COMBAT && GetActivity() == ACT_RUN && random->RandomFloat( 0, 1 ) < 0.2 )
	{
		WarnSound();
	}

	// at random, initiate a blink if not already blinking or sleeping
	if ( !m_fDontBlink )
	{
		if ( ( m_nSkin == 0 ) && random->RandomInt(0,0x7F) == 0 )
		{// start blinking!
			m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
		}
		else if ( m_nSkin != 0 )
		{// already blinking
			m_nSkin--;
		}
	}

	// if you are the leader, average the origins of each pack member to get an approximate center.
	if ( m_pSquad && m_pSquad->IsLeader( this ) )
	{
		int iSquadCount = 0;

		AISquadIter_t iter;
		for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
		{
			iSquadCount++;
			m_vecPackCenter = m_vecPackCenter + pSquadMember->GetAbsOrigin();
		}

		m_vecPackCenter = m_vecPackCenter / iSquadCount;
	}
}

//=========================================================
// GetScheduleOfType 
//=========================================================
int CNPC_Houndeye::TranslateSchedule( int scheduleType ) 
{
	if ( m_fAsleep )
	{
		// if the hound is sleeping, must wake and stand!
		if ( HasCondition( COND_HEAR_DANGER ) ||  HasCondition( COND_HEAR_THUMPER ) || HasCondition( COND_HEAR_COMBAT ) || 
			 HasCondition( COND_HEAR_WORLD ) || HasCondition( COND_HEAR_PLAYER ) || HasCondition( COND_HEAR_BULLET_IMPACT ) )
		{
			CSound *pWakeSound;

			pWakeSound = GetBestSound();
			ASSERT( pWakeSound != NULL );
			if ( pWakeSound )
			{
				GetMotor()->SetIdealYawToTarget ( pWakeSound->GetSoundOrigin() );

				if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME )
				{
					// awakened by a loud sound
					return SCHED_HOUND_WAKE_URGENT;
				}
			}
			// sound was not loud enough to scare the bejesus out of houndeye
			return SCHED_HOUND_WAKE_LAZY;
		}
		else if ( HasCondition( COND_NEW_ENEMY ) )
		{
			// get up fast, to fight.
			return SCHED_HOUND_WAKE_URGENT;
		}

		else
		{
			// hound is waking up on its own
			return SCHED_HOUND_WAKE_LAZY;
		}
	}
	switch	( scheduleType )
	{
	case SCHED_IDLE_STAND:
		{
			// we may want to sleep instead of stand!
			if ( m_pSquad && !m_pSquad->IsLeader( this ) && !m_fAsleep && random->RandomInt( 0,29 ) < 1 )
			{
				return SCHED_HOUND_SLEEP;
			}
			else
			{
				return BaseClass::TranslateSchedule( scheduleType );
			}
		}

	case SCHED_RANGE_ATTACK1:
		return SCHED_HOUND_RANGEATTACK;
	case SCHED_SPECIAL_ATTACK1:
		return SCHED_HOUND_SPECIALATTACK;

	case SCHED_FAIL:
		{
			if ( m_NPCState == NPC_STATE_COMBAT )
			{
				if ( !FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
				{
					// client in PVS
					return SCHED_HOUND_COMBAT_FAIL_PVS;
				}
				else
				{
					// client has taken off! 
					return SCHED_HOUND_COMBAT_FAIL_NOPVS;
				}
			}
			else
			{
				return BaseClass::TranslateSchedule ( scheduleType );
			}
		}
	default:
		{
			return BaseClass::TranslateSchedule ( scheduleType );
		}
	}
}

int CNPC_Houndeye::SelectSchedule( void )
{
	switch	( m_NPCState )
	{
	case NPC_STATE_COMBAT:
		{
// dead enemy
			if ( HasCondition( COND_ENEMY_DEAD ) )
			{
				// call base class, all code to handle dead enemies is centralized there.
				return BaseClass::SelectSchedule();
			}

			if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
			{
				if ( random->RandomFloat( 0 , 1 ) <= 0.4 )
				{
					trace_t trace;
					Vector v_forward;
					GetVectors( &v_forward, NULL, NULL );
					UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + v_forward * -128, MASK_SOLID, &trace );
					
					if ( trace.fraction == 1.0 )
					{
						// it's clear behind, so the hound will jump
						return SCHED_HOUND_HOP_RETREAT;
					}
				}

				return SCHED_TAKE_COVER_FROM_ENEMY;
			}

			if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
			{
			// don't constraint attacks based on squad slots, just let em all go for it
//				if ( OccupyStrategySlot ( SQUAD_SLOTS_HOUND_ATTACK ) )
				return SCHED_RANGE_ATTACK1;
			}
			break;
		}
	}

	return BaseClass::SelectSchedule();
}


//=========================================================
// FLSoundVolume - subtracts the volume of the given sound
// from the distance the sound source is from the caller, 
// and returns that value, which is considered to be the 'local' 
// volume of the sound. 
//=========================================================
float CNPC_Houndeye::FLSoundVolume( CSound *pSound )
{
	return ( pSound->Volume() - ( ( pSound->GetSoundOrigin() - GetAbsOrigin() ).Length() ) );
}


//=========================================================
//
// SquadRecruit(), get some monsters of my classification and
// link them as a group.  returns the group size
//
//=========================================================
int CNPC_Houndeye::SquadRecruit( int searchRadius, int maxMembers )
{
	int squadCount;
	int iMyClass = Classify();// cache this monster's class

	if ( maxMembers < 2 )
		return 0;

	// I am my own leader
	squadCount = 1;

	CBaseEntity *pEntity = NULL;

	if ( m_SquadName != NULL_STRING )
	{
		// I have a netname, so unconditionally recruit everyone else with that name.
		pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );

		while ( pEntity && squadCount < maxMembers )
		{
			CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();

			if ( pRecruit )
			{
				if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass && pRecruit != this )
				{
					// minimum protection here against user error.in worldcraft. 
					if ( pRecruit->m_SquadName != NULL_STRING && FStrEq( STRING( m_SquadName ), STRING( pRecruit->m_SquadName ) ) )
					{
						pRecruit->InitSquad();
						squadCount++;
					}
				}
			}

			pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );
		}

		return squadCount;
	}
	else
	{
		char szSquadName[64];
		Q_snprintf( szSquadName, sizeof( szSquadName ), "squad%d\n", s_iSquadIndex );

		m_SquadName = MAKE_STRING( szSquadName );

		while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius ) ) != NULL && squadCount < maxMembers )
		{
			if ( !FClassnameIs ( pEntity, "monster_houndeye" ) )
				continue;

			CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();

			if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_hCine )
			{
				// Can we recruit this guy?
				if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass &&
					( (iMyClass != CLASS_ALIEN_MONSTER) || FClassnameIs( this, pRecruit->GetClassname() ) ) &&
					!pRecruit->m_SquadName )
				{
					trace_t tr;
					UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), pRecruit->GetAbsOrigin() + GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pRecruit, COLLISION_GROUP_NONE, &tr );// try to hit recruit with a traceline.

					if ( tr.fraction == 1.0 )
					{
						//We're ready to recruit people, so start a squad if I don't have one.
						if ( !m_pSquad )
						{
							InitSquad();
						}

						pRecruit->m_SquadName = m_SquadName;

						pRecruit->CapabilitiesAdd ( bits_CAP_SQUAD );
						pRecruit->InitSquad();

						squadCount++;
					}
				}
			}
		}

		if ( squadCount > 1 )
		{
			s_iSquadIndex++;
		}
	}

	return squadCount;
}



void CNPC_Houndeye::StartNPC ( void )
{
	if ( !m_pSquad )
	{
		if ( m_SquadName != NULL_STRING )
		{
			BaseClass::StartNPC();
			return;
		}
		else
		{
			int iSquadSize = SquadRecruit( 1024, 4 );

			if ( iSquadSize )
			{
				Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
			}
		}
	}

	BaseClass::StartNPC();
}



//------------------------------------------------------------------------------
//
// Schedules
//
//------------------------------------------------------------------------------

AI_BEGIN_CUSTOM_NPC( monster_houndeye, CNPC_Houndeye )

	DECLARE_TASK ( TASK_HOUND_CLOSE_EYE )
	DECLARE_TASK ( TASK_HOUND_OPEN_EYE )
	DECLARE_TASK ( TASK_HOUND_THREAT_DISPLAY )
	DECLARE_TASK ( TASK_HOUND_FALL_ASLEEP )
	DECLARE_TASK ( TASK_HOUND_WAKE_UP )
	DECLARE_TASK ( TASK_HOUND_HOP_BACK )

	//=========================================================
	// > SCHED_HOUND_RANGEATTACK
	//=========================================================
	DEFINE_SCHEDULE
		(
		SCHED_HOUND_RANGEATTACK,

		"	Tasks"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_HOUND_YELL1"
		"	"
		"	Interrupts"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		)

	//=========================================================
	// > SCHED_HOUND_AGITATED
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_AGITATED,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_HOUND_THREAT_DISPLAY	0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_HEAVY_DAMAGE"
	)

	//=========================================================
	// > SCHED_HOUND_HOP_RETREAT
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_HOP_RETREAT,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_HOUND_HOP_BACK			0"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HOUND_YELL1
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_YELL1,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_FACE_IDEAL				0"
		"		TASK_RANGE_ATTACK1			0"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_HOUND_AGITATED"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HOUND_YELL2
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_YELL2,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_FACE_IDEAL				0"
		"		TASK_RANGE_ATTACK1			0"
		"	"
		"	Interrupts"
	)


	//=========================================================
	// > SCHED_HOUND_SLEEP
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_SLEEP,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"
		"		TASK_WAIT_RANDOM			5"
		"		TASK_PLAY_SEQUENCE			ACTIVITY:ACT_CROUCH"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_CROUCHIDLE"
		"		TASK_HOUND_FALL_ASLEEP		0"
		"		TASK_WAIT_RANDOM			25"
			"	TASK_HOUND_CLOSE_EYE		0"
		"	"
		"	Interrupts"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_COMBAT"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_PLAYER"
		"		COND_HEAR_WORLD"
	)

	//=========================================================
	// > SCHED_HOUND_WAKE_LAZY
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_WAKE_LAZY,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_HOUND_OPEN_EYE			0"
		"		TASK_WAIT_RANDOM			2.5"
		"		TASK_PLAY_SEQUENCE			ACT_STAND"
		"		TASK_HOUND_WAKE_UP			0"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HOUND_WAKE_URGENT
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_WAKE_URGENT,

		"	Tasks"
		"		TASK_HOUND_OPEN_EYE			0"
		"		TASK_PLAY_SEQUENCE			ACT_HOP"
		"		TASK_FACE_IDEAL				0"
		"		TASK_HOUND_WAKE_UP			0"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HOUND_SPECIALATTACK
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_SPECIALATTACK,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_FACE_IDEAL				0"
		"		TASK_SPECIAL_ATTACK1		0"
		"		TASK_PLAY_SEQUENCE			ACTIVITY:ACT_IDLE_ANGRY"
		"	"
		"	Interrupts"
		"		"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_ENEMY_OCCLUDED"
	)

	//=========================================================
	// > SCHED_HOUND_COMBAT_FAIL_PVS
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_COMBAT_FAIL_PVS,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_HOUND_THREAT_DISPLAY	0"
		"		TASK_WAIT_FACE_ENEMY		1"
		"	"
		"	Interrupts"
		"		"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
	)

	//=========================================================
	// > SCHED_HOUND_COMBAT_FAIL_NOPVS
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HOUND_COMBAT_FAIL_NOPVS,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_HOUND_THREAT_DISPLAY	0"
		"		TASK_WAIT_FACE_ENEMY		1"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"
		"		TASK_WAIT_PVS				0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
	)

AI_END_CUSTOM_NPC()