//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Houndeye - a spooky sonic dog.
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "npc_houndeye.h"
#include "ai_default.h"
#include "ai_node.h"
#include "ai_route.h"
#include "AI_Navigator.h"
#include "AI_Motor.h"
#include "AI_Squad.h"
#include "AI_TacticalServices.h"
#include "soundent.h"
#include "EntityList.h"
#include "game.h"
#include "activitylist.h"
#include "hl2_shareddefs.h"
#include "grenade_energy.h"
#include "energy_wave.h"
#include "ai_interactions.h"
#include "ndebugoverlay.h"
#include "NPCEvent.h"
#include "player.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"

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

#define	HOUNDEYE_MAX_ATTACK_RADIUS		500
#define	HOUNDEYE_MIN_ATTACK_RADIUS		100

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

ConVar	sk_Houndeye_health( "sk_Houndeye_health","0");
ConVar	sk_Houndeye_dmg_blast( "sk_Houndeye_dmg_blast","0");

//=========================================================
// Interactions
//=========================================================
int	g_interactionHoundeyeGroupAttack					= 0;
int	g_interactionHoundeyeGroupRetreat					= 0;
int	g_interactionHoundeyeGroupRalley					= 0;

//=========================================================
// Specialized 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,

	TASK_HOUND_GET_PATH_TO_CIRCLE,
	TASK_HOUND_REVERSE_STRAFE_DIR,
};

//-----------------------------------------------------------------------------
// Custom Conditions
//-----------------------------------------------------------------------------
enum Houndeye_Conds
{
	COND_HOUND_GROUP_ATTACK = LAST_SHARED_CONDITION,
	COND_HOUND_GROUP_RETREAT,
	COND_HOUND_GROUP_RALLEY,
};

//=========================================================
// Specialized Shedules
//=========================================================
enum
{
	SCHED_HOUND_AGITATED = LAST_SHARED_SCHEDULE,
	SCHED_HOUND_HOP_RETREAT,
	SCHED_HOUND_RANGE_ATTACK1,

	SCHED_HOUND_ATTACK_STRAFE,
	SCHED_HOUND_ATTACK_STRAFE_REVERSE,
	SCHED_HOUND_GROUP_ATTACK,
	SCHED_HOUND_GROUP_RETREAT,
	SCHED_HOUND_CHASE_ENEMY,
	SCHED_HOUND_COVER_WAIT,
	SCHED_HOUND_GROUP_RALLEY,
};

//=========================================================
// Specialized activities
//=========================================================
int	ACT_HOUND_GUARD;

//=========================================================
// 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
#define		HOUND_AE_LEAP_HIT		8

//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_Houndeye::InitCustomSchedules(void) 
{
	INIT_CUSTOM_AI(CNPC_Houndeye);

	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_CLOSE_EYE);
	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_OPEN_EYE);
	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_THREAT_DISPLAY);
	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_FALL_ASLEEP);
	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_WAKE_UP);
	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_HOP_BACK);

	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_GET_PATH_TO_CIRCLE);
	ADD_CUSTOM_TASK(CNPC_Houndeye,	TASK_HOUND_REVERSE_STRAFE_DIR);
	
	ADD_CUSTOM_CONDITION(CNPC_Houndeye,	COND_HOUND_GROUP_ATTACK);
	ADD_CUSTOM_CONDITION(CNPC_Houndeye,	COND_HOUND_GROUP_RETREAT);

	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_HOP_RETREAT);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_RANGE_ATTACK1);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_ATTACK_STRAFE);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_ATTACK_STRAFE_REVERSE);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_GROUP_ATTACK);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_GROUP_RETREAT);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_CHASE_ENEMY);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_COVER_WAIT);
	ADD_CUSTOM_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_GROUP_RALLEY);
	
	ADD_CUSTOM_ACTIVITY(CNPC_Houndeye,	ACT_HOUND_GUARD);

	g_interactionHoundeyeGroupAttack				= CBaseCombatCharacter::GetInteractionID();
	g_interactionHoundeyeGroupRetreat				= CBaseCombatCharacter::GetInteractionID();
	g_interactionHoundeyeGroupRalley				= CBaseCombatCharacter::GetInteractionID();

	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_HOP_RETREAT);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_RANGE_ATTACK1);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_ATTACK_STRAFE);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_ATTACK_STRAFE_REVERSE);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_GROUP_ATTACK);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_GROUP_RETREAT);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_CHASE_ENEMY);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_COVER_WAIT);
	AI_LOAD_SCHEDULE(CNPC_Houndeye,	SCHED_HOUND_GROUP_RALLEY);
}

LINK_ENTITY_TO_CLASS( npc_houndeye, CNPC_Houndeye );
IMPLEMENT_CUSTOM_AI( npc_houndeye, CNPC_Houndeye );

BEGIN_DATADESC( CNPC_Houndeye )

	DEFINE_FIELD( m_fAsleep,					FIELD_BOOLEAN ),
	DEFINE_FIELD( m_fDontBlink,				FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flNextSecondaryAttack,	FIELD_TIME ),
	DEFINE_FIELD( m_bLoopClockwise,			FIELD_BOOLEAN ),
	DEFINE_FIELD( m_pEnergyWave,				FIELD_CLASSPTR ),
	DEFINE_FIELD( m_flEndEnergyWaveTime,		FIELD_TIME ),	

END_DATADESC()

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

//-----------------------------------------------------------------------------
// Purpose: 
// Input  :
// Output :
//-----------------------------------------------------------------------------
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;
	AI_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_HOUNDEYE)
		{
			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;
}

//-----------------------------------------------------------------------------
// Purpose: Overidden for human grunts because they  hear the DANGER sound
// Input  :
// Output :
//-----------------------------------------------------------------------------
int CNPC_Houndeye::GetSoundInterests( void )
{
	return	SOUND_WORLD		|
			SOUND_COMBAT	|
			SOUND_PLAYER	|
			SOUND_DANGER;
}

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

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

//=========================================================
// 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 = sv_gravity.GetFloat();

				SetGroundEntity( NULL );

				Vector forward;
				AngleVectors( GetLocalAngles(), &forward );
				Vector vecNewVelocity = forward * -200;
				//jump up 36 inches
				vecNewVelocity.z += sqrt( 2 * flGravity * 36 );
				SetAbsVelocity( vecNewVelocity );
				break;
			}

		case HOUND_AE_THUMP:
			// emit the shockwaves
			SonicAttack();
			m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 5.0, 8.0 );
			break;

		case HOUND_AE_ANGERSOUND1:
			{
				EmitSound( "NPC_Houndeye.Anger1" );
			}
			break;

		case HOUND_AE_ANGERSOUND2:
			{
			EmitSound( "NPC_Houndeye.Anger2" );
			}
			break;

		case HOUND_AE_CLOSE_EYE:
			if ( !m_fDontBlink )
			{
			//<<TEMP>>	pev->skin = HOUNDEYE_EYE_FRAMES - 1;
			}
			break;

		case HOUND_AE_LEAP_HIT:
			{
				//<<TEMP>>return;//<<TEMP>>
				SetGroundEntity( NULL );

				//
				// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
				//
				UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));
				Vector vecJumpDir;
				if ( GetEnemy() != NULL )
				{
					Vector vecEnemyEyePos = GetEnemy()->EyePosition();

					float gravity = sv_gravity.GetFloat();
					if ( gravity <= 1 )
					{
						gravity = 1;
					}

					//
					// How fast does the houndeye need to travel to reach my enemy's eyes given gravity?
					//
					float height = ( vecEnemyEyePos.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 = vecEnemyEyePos - GetAbsOrigin();
					vecJumpDir = vecJumpDir / time;

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

					//
					// Don't jump too far/fast.
					//
					float distance = vecJumpDir.Length();
					if ( distance > 650 )
					{
						vecJumpDir = vecJumpDir * ( 650.0 / distance );
					}
				}
				else
				{
					Vector forward, up;
					AngleVectors( GetLocalAngles(), &forward, NULL, &up );
					//
					// Jump hop, don't care where.
					//
					vecJumpDir = Vector( forward.x, forward.y, up.z ) * 350;
				}

				SetAbsVelocity( vecJumpDir );
				m_flNextAttack = gpGlobals->curtime + 2;
				break;
			}
		default:
			BaseClass::HandleAnimEvent( pEvent );
			break;
	}
}

//=========================================================
// Spawn
//=========================================================
void CNPC_Houndeye::Spawn()
{
	Precache( );

	SetModel("models/houndeye.mdl");
	SetHullType(HULL_WIDE_SHORT);
	SetHullSizeNormal();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	SetBloodColor( BLOOD_COLOR_YELLOW );
	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;
	CapabilitiesAdd( bits_CAP_SQUAD );
	CapabilitiesAdd( bits_CAP_MOVE_GROUND );
	CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 );
	CapabilitiesAdd( bits_CAP_TURN_HEAD );

	m_flNextSecondaryAttack = 0;
	m_bLoopClockwise		= random->RandomInt(0,1) ? true : false;

	m_pEnergyWave			= NULL;
	m_flEndEnergyWaveTime	= 0;

	SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE ); 

	NPCInit();
}

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

	PrecacheScriptSound( "NPC_Houndeye.Anger1" );
	PrecacheScriptSound( "NPC_Houndeye.Anger2" );
	PrecacheScriptSound( "NPC_Houndeye.SpeakSentence" );
	PrecacheScriptSound( "NPC_Houndeye.Idle" );
	PrecacheScriptSound( "NPC_Houndeye.WarmUp" );
	PrecacheScriptSound( "NPC_Houndeye.Warn" );
	PrecacheScriptSound( "NPC_Houndeye.Alert" );
	PrecacheScriptSound( "NPC_Houndeye.Die" );
	PrecacheScriptSound( "NPC_Houndeye.Pain" );
	PrecacheScriptSound( "NPC_Houndeye.Retreat" );
	PrecacheScriptSound( "NPC_Houndeye.SonicAttack" );

	PrecacheScriptSound( "NPC_Houndeye.GroupAttack" );
	PrecacheScriptSound( "NPC_Houndeye.GroupFollow" );


	UTIL_PrecacheOther("grenade_energy");
	BaseClass::Precache();
}	

//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_Houndeye::SpeakSentence( int sentenceType )
{
	if (gpGlobals->curtime > m_flSoundWaitTime)
	{
		EmitSound( "NPC_Houndeye.SpeakSentence" );
		m_flSoundWaitTime = gpGlobals->curtime + 1.0;
	}
}

//=========================================================
// IdleSound
//=========================================================
void CNPC_Houndeye::IdleSound ( void )
{
	if (!FOkToMakeSound())
	{
		return;
	}
	CPASAttenuationFilter filter( this,"NPC_Houndeye.Idle" );
	EmitSound( filter, entindex(),"NPC_Houndeye.Idle" );
}

//=========================================================
// IdleSound
//=========================================================
void CNPC_Houndeye::WarmUpSound ( void )
{
	EmitSound( "NPC_Houndeye.WarmUp" );
}

//=========================================================
// WarnSound 
//=========================================================
void CNPC_Houndeye::WarnSound ( void )
{
	EmitSound( "NPC_Houndeye.Warn" );
}

//=========================================================
// AlertSound 
//=========================================================
void CNPC_Houndeye::AlertSound ( void )
{
	// only first squad member makes ALERT sound.
	if ( m_pSquad && !m_pSquad->IsLeader( this ) )
	{
		return; 
	}

	EmitSound( "NPC_Houndeye.Alert" );
}

//=========================================================
// DeathSound 
//=========================================================
void CNPC_Houndeye::DeathSound ( void )
{
	EmitSound( "NPC_Houndeye.Die" );
}

//=========================================================
// PainSound 
//=========================================================
void CNPC_Houndeye::PainSound ( void )
{
	EmitSound( "NPC_Houndeye.Pain" );
}

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

	if ( m_pSquad )
	{
		switch ( m_pSquad->NumMembers() )
		{
		case 2:
			// no case for 0 or 1, cause those are impossible for monsters in Squads.
			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:
			DevWarning( 2, "Unsupported Houndeye SquadSize!\n" );
			bRed	= 188;
			bGreen	= 220;
			bBlue	= 255;
			break;
		}
	}
	else
	{
		// solo houndeye - weakest beam
		bRed	= 188;
		bGreen	= 220;
		bBlue	= 255;
	}
	
	WRITE_BYTE( bRed   );
	WRITE_BYTE( bGreen );
	WRITE_BYTE( bBlue  );
}

//-----------------------------------------------------------------------------
// Purpose: Plays the engine sound.
//-----------------------------------------------------------------------------
void CNPC_Houndeye::NPCThink(void)
{
	if (m_pEnergyWave)
	{
		if (gpGlobals->curtime > m_flEndEnergyWaveTime)
		{
			UTIL_Remove(m_pEnergyWave);
			m_pEnergyWave = NULL;
		}
	}

	// -----------------------------------------------------
	//  Update collision group
	//		While I'm running I'm allowed to penetrate
	//		other houndeyes
	// -----------------------------------------------------
	Vector vVelocity;
	GetVelocity( &vVelocity, NULL );
	if (vVelocity.Length() > 10)
	{
		SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE );
	}
	else 
	{
		// Don't go solid if resting in another houndeye 
		trace_t tr;
		AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,1), 
						GetHullMins(), GetHullMaxs(),
						MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
		if (!tr.startsolid)
		{
			SetCollisionGroup( COLLISION_GROUP_NONE );
		}
		else
		{
			SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE );
		}
	}
/*
	if (GetCollisionGroup() == HL2COLLISION_GROUP_HOUNDEYE)
	{
		NDebugOverlay::Box(GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 0);
	}
	else
	{
		NDebugOverlay::Box(GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 0);
	}
*/
	BaseClass::NPCThink();
}

//------------------------------------------------------------------------------
// Purpose : Broadcast retreat occasionally when hurt
// Input   :
// Output  :
//------------------------------------------------------------------------------
int CNPC_Houndeye::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	if (m_pSquad && random->RandomInt(0,10) == 10)
	{
		EmitSound( "NPC_Houndeye.Retreat" );
		m_flSoundWaitTime = gpGlobals->curtime + 1.0;

		m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRetreat, NULL, this );
	}
	
	return BaseClass::OnTakeDamage_Alive( info );
}

//------------------------------------------------------------------------------
// Purpose : Broadcast retreat when member of squad killed
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_Houndeye::Event_Killed( const CTakeDamageInfo &info )
{
	EmitSound( "NPC_Houndeye.Retreat" );
	m_flSoundWaitTime = gpGlobals->curtime + 1.0;

	if (m_pSquad)
	{
		m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRetreat, NULL, this );
	}

	BaseClass::Event_Killed( info );
}

//=========================================================
// SonicAttack
//=========================================================
void CNPC_Houndeye::SonicAttack ( void )
{
	EmitSound( "NPC_Houndeye.SonicAttack" );

	if (m_pEnergyWave)
	{
		UTIL_Remove(m_pEnergyWave);
	}
	Vector vFacingDir = EyeDirection3D( );
	m_pEnergyWave = (CEnergyWave*)Create( "energy_wave", EyePosition(), GetLocalAngles() );
	m_flEndEnergyWaveTime = gpGlobals->curtime + 1; //<<TEMP>> magic
	m_pEnergyWave->SetAbsVelocity( 100*vFacingDir );

	CBaseEntity *pEntity = NULL;
	// iterate on all entities in the vicinity.
	for ( CEntitySphereQuery sphere( GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() )
	{
		if (pEntity->Classify()	== CLASS_HOUNDEYE)
		{
			continue;
		}

		if (pEntity->GetFlags() & FL_NOTARGET)
		{
			continue;
		}

		IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();

		if ( pEntity->m_takedamage != DAMAGE_NO || pPhysicsObject)
		{
			// --------------------------
			// Adjust damage by distance
			// --------------------------
			float flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).Length();
			float flDamageAdjuster = 1-( flDist / HOUNDEYE_MAX_ATTACK_RADIUS );

			// --------------------------
			// Adjust damage by direction
			// --------------------------
			Vector forward;
			AngleVectors( GetAbsAngles(), &forward );
			Vector vEntDir		= (pEntity->GetAbsOrigin() - GetAbsOrigin());
			VectorNormalize(vEntDir);
			float flDotPr		= DotProduct(forward,vEntDir);
			flDamageAdjuster   *= flDotPr;

			if (flDamageAdjuster < 0)
			{
				continue;
			}

			// --------------------------
			// Adjust damage by visibility
			// --------------------------
			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.
					flDamageAdjuster *= 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
					continue;
				}
			}

			// ------------------------------
			//  Apply the damage
			// ------------------------------
			if (pEntity->m_takedamage != DAMAGE_NO)
			{
				CTakeDamageInfo info( this, this, flDamageAdjuster * sk_Houndeye_dmg_blast.GetFloat(), DMG_SONIC | DMG_ALWAYSGIB );
				CalculateExplosiveDamageForce( &info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin() );

				pEntity->TakeDamage( info );

				// Throw the player
				if ( pEntity->IsPlayer() )
				{
					Vector forward;
					AngleVectors( GetLocalAngles(), &forward );

					Vector vecVelocity = pEntity->GetAbsVelocity();
					vecVelocity	+= forward * 250 * flDamageAdjuster;
					vecVelocity.z = 300 * flDamageAdjuster;
					pEntity->SetAbsVelocity( vecVelocity );
					pEntity->ViewPunch( QAngle(random->RandomInt(-20,20), 0, random->RandomInt(-20,20)) );
				}
			}
			// ------------------------------
			//  Apply physics foces
			// ------------------------------
			IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
			if (pPhysicsObject)
			{
				float flForce	= flDamageAdjuster * 8000;
				pPhysicsObject->ApplyForceCenter( (vEntDir+Vector(0,0,0.2)) * flForce );
				pPhysicsObject->ApplyTorqueCenter( vEntDir * flForce );
			}
		}
	}
}
		
//=========================================================
// start task
//=========================================================
void CNPC_Houndeye::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_HOUND_GET_PATH_TO_CIRCLE:
	{
		if (GetEnemy() == NULL)
		{
			TaskFail(FAIL_NO_ENEMY);
		}
		else 
		{
			Vector vTargetPos = GetEnemyLKP();
			vTargetPos.z	= GetFloorZ(vTargetPos);

			if (GetNavigator()->SetRadialGoal(vTargetPos, random->RandomInt(50,500), 90, 175, m_bLoopClockwise))
			{
				TaskComplete();
				return;
			}
			TaskFail(FAIL_NO_ROUTE);
		}
		break;
	}
	case TASK_HOUND_REVERSE_STRAFE_DIR:
	{
		// Try the other direction
		m_bLoopClockwise = (m_bLoopClockwise) ? false : true;
		TaskComplete();
		break;
	}

	// Override to set appropriate distances
	case TASK_GET_PATH_TO_ENEMY_LOS:
	{
		float			flMaxRange	= HOUNDEYE_MAX_ATTACK_RADIUS * 0.9;
		float			flMinRange	= HOUNDEYE_MIN_ATTACK_RADIUS;
		Vector 			posLos;
		bool			foundLos	= false;
		
		if (GetEnemy() != NULL)
		{
			foundLos = GetTacticalServices()->FindLos(GetEnemyLKP(),GetEnemy()->EyePosition(), flMinRange, flMaxRange, 0.0, &posLos);
		}
		else
		{
			TaskFail(FAIL_NO_TARGET);
			return;
		}

		if (foundLos)
		{
			GetNavigator()->SetGoal( AI_NavGoal_t( posLos, ACT_RUN, AIN_HULL_TOLERANCE ) );
		}
		else
		{
			TaskFail(FAIL_NO_SHOOT);
		}
		break;
	}

	case TASK_HOUND_FALL_ASLEEP:
		{
			m_fAsleep = true; // signal that hound is lying down (must stand again before doing anything else!)
			TaskComplete( true );
			break;
		}
	case TASK_HOUND_WAKE_UP:
		{
			m_fAsleep = false; // signal that hound is standing again
			TaskComplete( true );
			break;
		}
	case TASK_HOUND_OPEN_EYE:
		{
			m_fDontBlink = false; // turn blinking back on and that code will automatically open the eye
			TaskComplete( true );
			break;
		}
	case TASK_HOUND_CLOSE_EYE:
		{
//<<TEMP>>			pev->skin = 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;
		}
	default: 
		{
			BaseClass::StartTask(pTask);
			break;
		}
	}
}

//=========================================================
// RunTask 
//=========================================================
void CNPC_Houndeye::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_HOUND_THREAT_DISPLAY:
		{
			GetMotor()->SetIdealYawToTargetAndUpdate( GetEnemyLKP(), AI_KEEP_YAW_SPEED );

			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			
			break;
		}
	case TASK_HOUND_CLOSE_EYE:
		{
			/*//<<TEMP>>
			if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 )
			{
				pev->skin++;
			}
			*/
			break;
		}
	case TASK_HOUND_HOP_BACK:
		{
			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			break;
		}
	default:
		{
			BaseClass::RunTask(pTask);
			break;
		}
	}
}


//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
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 )
	{
		/*//<<TEMP>>//<<TEMP>>
		if ( ( pev->skin == 0 ) && random->RandomInt(0,0x7F) == 0 )
		{// start blinking!
			pev->skin = HOUNDEYE_EYE_FRAMES - 1;
		}
		else if ( pev->skin != 0 )
		{// already blinking
			pev->skin--;
		}
		*/
	}
}

//-----------------------------------------------------------------------------
// Purpose: Override base class activiites
// Input  :
// Output :
//-----------------------------------------------------------------------------
Activity CNPC_Houndeye::NPC_TranslateActivity( Activity eNewActivity )
{
	if ( eNewActivity == ACT_IDLE && m_NPCState == NPC_STATE_COMBAT )
	{
		return ACT_IDLE_ANGRY;
	}
	return eNewActivity;
}

//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
int CNPC_Houndeye::TranslateSchedule( int scheduleType )
{
	switch( scheduleType )
	{
	case SCHED_IDLE_STAND:
		{
			return BaseClass::TranslateSchedule( scheduleType );
		}
	case SCHED_RANGE_ATTACK1:
		{
			return SCHED_HOUND_RANGE_ATTACK1;
		}
	case SCHED_CHASE_ENEMY_FAILED:
		{
			return SCHED_COMBAT_FACE;
		}
	default:
		{
			return BaseClass::TranslateSchedule ( scheduleType );
		}
	}
}

//------------------------------------------------------------------------------
// Purpose : Is anyone in the squad currently attacking
// Input   :
// Output  :
//------------------------------------------------------------------------------
bool CNPC_Houndeye::IsAnyoneInSquadAttacking( void )
{
	if (!m_pSquad)
	{
		return false;
	}

	AISquadIter_t iter;
	for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
	{
		if (pSquadMember->IsCurSchedule(SCHED_HOUND_RANGE_ATTACK1))
		{
			return true;
		}
	}
	return false;
}

//=========================================================
// SelectSchedule
//=========================================================
int CNPC_Houndeye::SelectSchedule( void )
{
	switch	( m_NPCState )
	{
	case NPC_STATE_IDLE:
	case NPC_STATE_ALERT:
		{
			if ( HasCondition(COND_LIGHT_DAMAGE) ||
				 HasCondition(COND_HEAVY_DAMAGE) )
			{
				return SCHED_TAKE_COVER_FROM_ORIGIN;
			}
			break;
		}
	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 a group attack was requested attack even if attack conditions not met
			if ( HasCondition( COND_HOUND_GROUP_ATTACK ))
			{
				// Check that I'm not standing in another hound eye 
				// before attacking
				trace_t tr;
				AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,1), 
								GetHullMins(), GetHullMaxs(),
								MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
				if (!tr.startsolid)
				{
					return SCHED_HOUND_GROUP_ATTACK;
				}

				// Otherwise attack as soon as I can
				else
				{
					m_flNextAttack = gpGlobals->curtime;
					SCHED_HOUND_ATTACK_STRAFE;
				}
			}

			// If a group retread was requested 
			if ( HasCondition( COND_HOUND_GROUP_RETREAT ))
			{
				return SCHED_HOUND_GROUP_RETREAT;
			}

			if ( HasCondition( COND_LIGHT_DAMAGE ) | 
				 HasCondition( COND_HEAVY_DAMAGE ) )
			{
				if ( random->RandomFloat( 0 , 1 ) <= 0.4 )
				{
					trace_t tr;
					Vector forward;
					AngleVectors( GetAbsAngles(), &forward );
					AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + forward * -128, 
						GetHullMins(), GetHullMaxs(),
						MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

					if ( tr.fraction == 1.0 )
					{
						// it's clear behind, so the hound will jump
						return SCHED_HOUND_HOP_RETREAT;
					}
				}

				return SCHED_TAKE_COVER_FROM_ENEMY;
			}

			// If a group rally was requested 
			if ( HasCondition( COND_HOUND_GROUP_RALLEY ))
			{
				return SCHED_HOUND_GROUP_RALLEY;
			}

			if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
			{
				if (m_pSquad && random->RandomInt(0,4) == 0)
				{
					if (!IsAnyoneInSquadAttacking())
					{
						EmitSound( "NPC_Houndeye.GroupAttack" );
						
						m_flSoundWaitTime = gpGlobals->curtime + 1.0;

						m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupAttack, NULL, this );
						return SCHED_HOUND_GROUP_ATTACK;
					}
				}
				//<<TEMP>>comment
				SetCollisionGroup( COLLISION_GROUP_NONE );
				return SCHED_RANGE_ATTACK1;
			}
			else
			{
				if (m_pSquad && random->RandomInt(0,5) == 0)
				{
					if (!IsAnyoneInSquadAttacking())
					{
						EmitSound( "NPC_Houndeye.GroupFollow" );

						m_flSoundWaitTime = gpGlobals->curtime + 1.0;

						m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRalley, NULL, this );
						return SCHED_HOUND_ATTACK_STRAFE;
					}
				}
				return SCHED_HOUND_ATTACK_STRAFE;
			}
			break;
		}
	}

	return BaseClass::SelectSchedule();
}

//-----------------------------------------------------------------------------
// Purpose:  This is a generic function (to be implemented by sub-classes) to
//			 handle specific interactions between different types of characters
//			 (For example the barnacle grabbing an NPC)
// Input  :  Constant for the type of interaction
// Output :	 true  - if sub-class has a response for the interaction
//			 false - if sub-class has no response
//-----------------------------------------------------------------------------
bool CNPC_Houndeye::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
{
	if (interactionType == g_interactionHoundeyeGroupAttack)
	{
		SetCondition(COND_HOUND_GROUP_ATTACK);
		return true;
	}
	else if (interactionType == g_interactionHoundeyeGroupRetreat)
	{
		SetCondition(COND_HOUND_GROUP_RETREAT);
		return true;
	}
	else if (interactionType == g_interactionHoundeyeGroupRalley)
	{
		SetCondition(COND_HOUND_GROUP_RALLEY);
		SetTarget(sourceEnt);
		m_bLoopClockwise = false;
		return true;
	}

	return false;
}


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

//=========================================================
// SCHED_HOUND_ATTACK_STRAFE
//
//  Run a cirle around my enemy
//=========================================================
AI_DEFINE_SCHEDULE 
(
	SCHED_HOUND_ATTACK_STRAFE  ,

	"	Tasks "
	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HOUND_ATTACK_STRAFE_REVERSE"
	"		TASK_HOUND_GET_PATH_TO_CIRCLE	0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	""
	"	Interrupts "
	"		COND_WEAPON_SIGHT_OCCLUDED"
	"		COND_NEW_ENEMY"
	"		COND_ENEMY_DEAD"
	"		COND_HEAVY_DAMAGE"
	"		COND_CAN_RANGE_ATTACK1"
	"		COND_HOUND_GROUP_ATTACK"
	"		COND_HOUND_GROUP_RETREAT"
);

//=========================================================
// SCHED_HOUND_ATTACK_STRAFE_REVERSE
//
//  Run a cirle around my enemy
//=========================================================
AI_DEFINE_SCHEDULE 
(
	SCHED_HOUND_ATTACK_STRAFE_REVERSE  ,

	"	Tasks "
	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HOUND_CHASE_ENEMY"
	"		TASK_HOUND_REVERSE_STRAFE_DIR	0"
	"		TASK_HOUND_GET_PATH_TO_CIRCLE	0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	""
	"	Interrupts "
	"		COND_WEAPON_SIGHT_OCCLUDED"
	"		COND_NEW_ENEMY"
	"		COND_ENEMY_DEAD"
	"		COND_HEAVY_DAMAGE"
	"		COND_CAN_RANGE_ATTACK1"
	"		COND_HOUND_GROUP_ATTACK"
	"		COND_HOUND_GROUP_RETREAT"
);

//========================================================
// SCHED_HOUND_CHASE_ENEMY
//=========================================================
AI_DEFINE_SCHEDULE
(
	SCHED_HOUND_CHASE_ENEMY,

	"	Tasks"
	"		TASK_SET_TOLERANCE_DISTANCE		30	 "
	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
	"		TASK_GET_PATH_TO_ENEMY			0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_ENEMY_DEAD"
	"		COND_CAN_RANGE_ATTACK1"
	"		COND_HOUND_GROUP_ATTACK"
	"		COND_HOUND_GROUP_RETREAT"
);

//=========================================================
// SCHED_HOUND_GROUP_ATTACK
//
//  Face enemy, pause, then attack
//=========================================================
AI_DEFINE_SCHEDULE 
(
	SCHED_HOUND_GROUP_ATTACK  ,

	"	Tasks "
	"		TASK_STOP_MOVING			0"
	"		TASK_FACE_ENEMY				0"
	"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE_ANGRY"
	"		TASK_SPEAK_SENTENCE			0"
	"		TASK_WAIT					1"
	"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_HOUND_RANGE_ATTACK1"
	""
	"	Interrupts "
	"		COND_NEW_ENEMY"
	"		COND_ENEMY_DEAD"
	"		COND_HEAVY_DAMAGE"
);	
	
//=========================================================
// > SCHED_HOUND_GROUP_RETREAT
//
//		Take cover from enemy! 
//=========================================================
AI_DEFINE_SCHEDULE
(
	SCHED_HOUND_GROUP_RETREAT,

	"	Tasks"
	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HOUND_ATTACK_STRAFE"
	"		TASK_STOP_MOVING				0"
	"		TASK_WAIT						0.2"
	"		TASK_SET_TOLERANCE_DISTANCE		24"
	"		TASK_FIND_COVER_FROM_ENEMY		0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	"		TASK_REMEMBER					MEMORY:INCOVER"
	"		TASK_FACE_ENEMY					0"
	"		TASK_SET_ACTIVITY				ACTIVITY:ACT_IDLE"	// Translated to cover
	"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_HOUND_COVER_WAIT"
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
);

//=========================================================
// > SCHED_HOUND_GROUP_RALLEY
//
//		Run to rally hound! 
//=========================================================
AI_DEFINE_SCHEDULE
(
	SCHED_HOUND_GROUP_RALLEY,

	"	Tasks"
	"		TASK_SET_TOLERANCE_DISTANCE		30"
	"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_HOUND_ATTACK_STRAFE"
	"		TASK_GET_PATH_TO_TARGET			0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_ENEMY_DEAD"
	"		COND_HEAVY_DAMAGE"
	"		COND_HOUND_GROUP_ATTACK"
	"		COND_HOUND_GROUP_RETREAT"
);

//=========================================================
// > SCHED_HOUND_COVER_WAIT
//
//		Wait in cover till enemy see's me or I take damage
//=========================================================
AI_DEFINE_SCHEDULE
(
	SCHED_HOUND_COVER_WAIT,

	"	Tasks"
	"		TASK_WAIT						2"
	""
	"	Interrupts"
	"		COND_SEE_ENEMY"
	"		COND_LIGHT_DAMAGE"
);				

//=========================================================
// > SCHED_HOUND_RANGE_ATTACK1
//=========================================================
AI_DEFINE_SCHEDULE
(
	SCHED_HOUND_RANGE_ATTACK1,

	"	Tasks"
	"		 TASK_STOP_MOVING			0"
	"		 TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE_ANGRY"
	"		 TASK_FACE_IDEAL			0"
	"		 TASK_RANGE_ATTACK1			0"
	""
	"	Interrupts"
	//"		COND_LIGHT_DAMAGE"	// don't interupt on small damage
	"		COND_HEAVY_DAMAGE"
);

//=========================================================
// > SCHED_HOUND_HOP_RETREAT
//=========================================================
AI_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"
);
