/***
*
*	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
*   This source code contains proprietary and confidential information of
*   Valve LLC and its suppliers.  Access to this code is restricted to
*   persons who have executed a written SDK license with Valve.  Any access,
*   use or distribution of this code by or to any unlicensed person is illegal.
*
****/
//=========================================================
// Houndeye - spooky sonic dog. 
//=========================================================

#include	"extdll.h"
#include	"util.h"
#include	"cbase.h"
#include	"monsters.h"
#include	"schedule.h"
#include	"animation.h"
#include	"nodes.h"
#include	"squadmonster.h"
#include	"soundent.h"
#include	"game.h"

extern CGraph WorldGraph;

// 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_MAX_ATTACK_RADIUS		384
#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

//=========================================================
// monster-specific tasks
//=========================================================
enum
{
	TASK_HOUND_CLOSE_EYE = LAST_COMMON_TASK + 1,
	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_COMMON_SCHEDULE + 1,
	SCHED_HOUND_HOP_RETREAT,
	SCHED_HOUND_FAIL
};

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

class CHoundeye : public CSquadMonster
{
public:
	void Spawn( void );
	void Precache( void );
	int Classify( void );
	void HandleAnimEvent( MonsterEvent_t *pEvent );
	void SetYawSpeed( void );
	void WarmUpSound( void );
	void AlertSound( void );
	void DeathSound( void );
	void WarnSound( void );
	void PainSound( void );
	void IdleSound( void );
	void StartTask( Task_t *pTask );
	void RunTask( Task_t *pTask );
	void SonicAttack( void );
	void PrescheduleThink( void );
	void SetActivity( Activity NewActivity );
	void WriteBeamColor( void );
	BOOL CheckRangeAttack1( float flDot, float flDist );
	BOOL FValidateHintType( short sHint );
	BOOL FCanActiveIdle( void );
	Schedule_t *GetScheduleOfType( int Type );
	Schedule_t *GetSchedule( void );

	int Save( CSave &save );
	int Restore( CRestore &restore );

	CUSTOM_SCHEDULES
	static TYPEDESCRIPTION m_SaveData[];

	int m_iSpriteTexture;
	BOOL m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down
	BOOL m_fDontBlink;// don't try to open/close eye if this bit is set!
	Vector	m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members.
};

LINK_ENTITY_TO_CLASS( monster_houndeye, CHoundeye )

TYPEDESCRIPTION	CHoundeye::m_SaveData[] =
{
	DEFINE_FIELD( CHoundeye, m_iSpriteTexture, FIELD_INTEGER ),
	DEFINE_FIELD( CHoundeye, m_fAsleep, FIELD_BOOLEAN ),
	DEFINE_FIELD( CHoundeye, m_fDontBlink, FIELD_BOOLEAN ),
	DEFINE_FIELD( CHoundeye, m_vecPackCenter, FIELD_POSITION_VECTOR ),
};

IMPLEMENT_SAVERESTORE( CHoundeye, CSquadMonster )

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

//=========================================================
//  FValidateHintType 
//=========================================================
BOOL CHoundeye::FValidateHintType( short sHint )
{
	int i;

	static short sHoundHints[] =
	{
		HINT_WORLD_MACHINERY,
		HINT_WORLD_BLINKING_LIGHT,
		HINT_WORLD_HUMAN_BLOOD,
		HINT_WORLD_ALIEN_BLOOD,
	};

	for( i = 0; i < ARRAYSIZE( sHoundHints ); i++ )
	{
		if( sHoundHints[i] == sHint )
		{
			return TRUE;
		}
	}

	ALERT( at_aiconsole, "Couldn't validate hint type" );
	return FALSE;
}

//=========================================================
// FCanActiveIdle
//=========================================================
BOOL CHoundeye::FCanActiveIdle( void )
{
	if( InSquad() )
	{
		CSquadMonster *pSquadLeader = MySquadLeader();

		for( int i = 0; i < MAX_SQUAD_MEMBERS; i++ )
		{
			CSquadMonster *pMember = pSquadLeader->MySquadMember( i );

			if( pMember != NULL && pMember != this && pMember->m_iHintNode != NO_NODE )
			{
				// someone else in the group is active idling right now!
				return FALSE;
			}
		}

		return TRUE;
	}

	return TRUE;
}

//=========================================================
// CheckRangeAttack1 - overridden for houndeyes so that they
// try to get within half of their max attack radius before
// attacking, so as to increase their chances of doing damage.
//=========================================================
BOOL CHoundeye::CheckRangeAttack1( float flDot, float flDist )
{
	if( flDist <= ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ) && flDot >= 0.3 )
	{
		return TRUE;
	}
	return FALSE;
}

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

	ys = 90;

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

	pev->yaw_speed = ys;
}

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

	if( NewActivity == m_Activity )
		return;

	if( m_MonsterState == MONSTERSTATE_COMBAT && NewActivity == ACT_IDLE && RANDOM_LONG( 0, 1 ) )
	{
		// play pissed idle.
		iSequence = LookupSequence( "madidle" );

		m_Activity = 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
		m_IdealActivity = m_Activity;

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

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CHoundeye::HandleAnimEvent( MonsterEvent_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 = g_psv_gravity->value;

				pev->flags &= ~FL_ONGROUND;

				pev->velocity = gpGlobals->v_forward * -200;
				pev->velocity.z += ( 0.6 * flGravity ) * 0.5;
				break;
			}
		case HOUND_AE_THUMP:
			// emit the shockwaves
			SonicAttack();
			break;
		case HOUND_AE_ANGERSOUND1:
			EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM );
			break;
		case HOUND_AE_ANGERSOUND2:
			EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain1.wav", 1, ATTN_NORM );
			break;
		case HOUND_AE_CLOSE_EYE:
			if( !m_fDontBlink )
			{
				pev->skin = HOUNDEYE_EYE_FRAMES - 1;
			}
			break;
		default:
			CSquadMonster::HandleAnimEvent( pEvent );
			break;
	}
}

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

	SET_MODEL( ENT( pev ), "models/houndeye.mdl" );
	UTIL_SetSize( pev, Vector( -16, -16, 0 ), Vector( 16, 16, 36 ) );

	pev->solid		= SOLID_SLIDEBOX;
	pev->movetype		= MOVETYPE_STEP;
	m_bloodColor		= BLOOD_COLOR_YELLOW;
	pev->effects		= 0;
	pev->health		= gSkillData.houndeyeHealth;
	pev->yaw_speed		= 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim?
	m_flFieldOfView		= 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
	m_MonsterState		= MONSTERSTATE_NONE;
	m_fAsleep		= FALSE; // everyone spawns awake
	m_fDontBlink		= FALSE;
	m_afCapability		|= bits_CAP_SQUAD;

	MonsterInit();
}

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

	PRECACHE_SOUND( "houndeye/he_alert1.wav" );
	PRECACHE_SOUND( "houndeye/he_alert2.wav" );
	PRECACHE_SOUND( "houndeye/he_alert3.wav" );

	PRECACHE_SOUND( "houndeye/he_die1.wav" );
	PRECACHE_SOUND( "houndeye/he_die2.wav" );
	PRECACHE_SOUND( "houndeye/he_die3.wav" );

	PRECACHE_SOUND( "houndeye/he_idle1.wav" );
	PRECACHE_SOUND( "houndeye/he_idle2.wav" );
	PRECACHE_SOUND( "houndeye/he_idle3.wav" );

	PRECACHE_SOUND( "houndeye/he_hunt1.wav" );
	PRECACHE_SOUND( "houndeye/he_hunt2.wav" );
	PRECACHE_SOUND( "houndeye/he_hunt3.wav" );

	PRECACHE_SOUND( "houndeye/he_pain1.wav" );
	PRECACHE_SOUND( "houndeye/he_pain3.wav" );
	PRECACHE_SOUND( "houndeye/he_pain4.wav" );
	PRECACHE_SOUND( "houndeye/he_pain5.wav" );

	PRECACHE_SOUND( "houndeye/he_attack1.wav" );
	PRECACHE_SOUND( "houndeye/he_attack3.wav" );

	PRECACHE_SOUND( "houndeye/he_blast1.wav" );
	PRECACHE_SOUND( "houndeye/he_blast2.wav" );
	PRECACHE_SOUND( "houndeye/he_blast3.wav" );

	m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" );
}	

//=========================================================
// IdleSound
//=========================================================
void CHoundeye::IdleSound( void )
{
	switch( RANDOM_LONG( 0, 2 ) )
	{
	case 0:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_idle1.wav", 1, ATTN_NORM );	
		break;
	case 1:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_idle2.wav", 1, ATTN_NORM );	
		break;
	case 2:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_idle3.wav", 1, ATTN_NORM );	
		break;
	}
}

//=========================================================
// IdleSound
//=========================================================
void CHoundeye::WarmUpSound( void )
{
	switch( RANDOM_LONG( 0, 1 ) )
	{
	case 0:
		EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_attack1.wav", 0.7, ATTN_NORM );	
		break;
	case 1:
		EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_attack3.wav", 0.7, ATTN_NORM );	
		break;
	}
}

//=========================================================
// WarnSound 
//=========================================================
void CHoundeye::WarnSound( void )
{
	switch( RANDOM_LONG( 0, 2 ) )
	{
	case 0:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_hunt1.wav", 1, ATTN_NORM );	
		break;
	case 1:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_hunt2.wav", 1, ATTN_NORM );	
		break;
	case 2:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_hunt3.wav", 1, ATTN_NORM );	
		break;
	}
}

//=========================================================
// AlertSound 
//=========================================================
void CHoundeye::AlertSound( void )
{
	if( InSquad() && !IsLeader() )
	{
		return; // only leader makes ALERT sound.
	}

	switch( RANDOM_LONG( 0, 2 ) )
	{
	case 0:	
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM );	
		break;
	case 1:	
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM );	
		break;
	case 2:	
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM );	
		break;
	}
}

//=========================================================
// DeathSound 
//=========================================================
void CHoundeye::DeathSound( void )
{
	switch( RANDOM_LONG( 0, 2 ) )
	{
	case 0:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_die1.wav", 1, ATTN_NORM );	
		break;
	case 1:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_die2.wav", 1, ATTN_NORM );	
		break;
	case 2:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_die3.wav", 1, ATTN_NORM );	
		break;
	}
}

//=========================================================
// PainSound 
//=========================================================
void CHoundeye::PainSound( void )
{
	switch( RANDOM_LONG( 0, 2 ) )
	{
	case 0:
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM );	
		break;
	case 1:	
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain4.wav", 1, ATTN_NORM );	
		break;
	case 2:	
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain5.wav", 1, ATTN_NORM );	
		break;
	}
}

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

	if( InSquad() )
	{
		switch( SquadCount() )
		{
		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:
			ALERT( at_aiconsole, "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 );
}

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

	switch( RANDOM_LONG( 0, 2 ) )
	{
	case 0:
		EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_blast1.wav", 1, ATTN_NORM );
		break;
	case 1:
		EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_blast2.wav", 1, ATTN_NORM );
		break;
	case 2:
		EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_blast3.wav", 1, ATTN_NORM );
		break;
	}

	// blast circles
	MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin );
		WRITE_BYTE( TE_BEAMCYLINDER );
		WRITE_COORD( pev->origin.x );
		WRITE_COORD( pev->origin.y );
		WRITE_COORD( pev->origin.z + 16 );
		WRITE_COORD( pev->origin.x );
		WRITE_COORD( pev->origin.y );
		WRITE_COORD( pev->origin.z + 16 + HOUNDEYE_MAX_ATTACK_RADIUS / .2 ); // reach damage radius over .3 seconds
		WRITE_SHORT( m_iSpriteTexture );
		WRITE_BYTE( 0 ); // startframe
		WRITE_BYTE( 0 ); // framerate
		WRITE_BYTE( 2 ); // life
		WRITE_BYTE( 16 );  // width
		WRITE_BYTE( 0 );   // noise

		WriteBeamColor();

		WRITE_BYTE( 255 ); //brightness
		WRITE_BYTE( 0 );		// speed
	MESSAGE_END();

	MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin );
		WRITE_BYTE( TE_BEAMCYLINDER );
		WRITE_COORD( pev->origin.x );
		WRITE_COORD( pev->origin.y );
		WRITE_COORD( pev->origin.z + 16 );
		WRITE_COORD( pev->origin.x );
		WRITE_COORD( pev->origin.y );
		WRITE_COORD( pev->origin.z + 16 + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2 ) / .2 ); // reach damage radius over .3 seconds
		WRITE_SHORT( m_iSpriteTexture );
		WRITE_BYTE( 0 ); // startframe
		WRITE_BYTE( 0 ); // framerate
		WRITE_BYTE( 2 ); // life
		WRITE_BYTE( 16 );  // width
		WRITE_BYTE( 0 );   // noise

		WriteBeamColor();

		WRITE_BYTE( 255 ); //brightness
		WRITE_BYTE( 0 );		// speed
	MESSAGE_END();

	CBaseEntity *pEntity = NULL;
	// iterate on all entities in the vicinity.
	while( ( pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, HOUNDEYE_MAX_ATTACK_RADIUS ) ) != NULL )
	{
		if( pEntity->pev->takedamage != DAMAGE_NO )
		{
			if( !FClassnameIs( pEntity->pev, "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( SquadCount() > 1 )
				{
					// squad gets attack bonus.
					flAdjustedDamage = gSkillData.houndeyeDmgBlast + gSkillData.houndeyeDmgBlast * ( HOUNDEYE_SQUAD_BONUS * ( SquadCount() - 1 ) );
				}
				else
				{
					// solo
					flAdjustedDamage = gSkillData.houndeyeDmgBlast;
				}

				flDist = ( pEntity->Center() - pev->origin ).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->pev, "func_breakable" ) && !FClassnameIs( pEntity->pev, "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 )
				{
					pEntity->TakeDamage( pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB );
				}
			}
		}
	}
}

//=========================================================
// start task
//=========================================================
void CHoundeye::StartTask( Task_t *pTask )
{
	m_iTaskStatus = TASKSTATUS_RUNNING;

	switch( pTask->iTask )
	{
	case TASK_HOUND_FALL_ASLEEP:
		{
			m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!)
			m_iTaskStatus = TASKSTATUS_COMPLETE;
			break;
		}
	case TASK_HOUND_WAKE_UP:
		{
			m_fAsleep = FALSE; // signal that hound is standing again
			m_iTaskStatus = TASKSTATUS_COMPLETE;
			break;
		}
	case TASK_HOUND_OPEN_EYE:
		{
			m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye
			m_iTaskStatus = TASKSTATUS_COMPLETE;
			break;
		}
	case TASK_HOUND_CLOSE_EYE:
		{
			pev->skin = 0;
			m_fDontBlink = TRUE; // tell blink code to leave the eye alone.
			break;
		}
	case TASK_HOUND_THREAT_DISPLAY:
		{
			m_IdealActivity = ACT_IDLE_ANGRY;
			break;
		}
	case TASK_HOUND_HOP_BACK:
		{
			m_IdealActivity = ACT_LEAP;
			break;
		}
	case TASK_RANGE_ATTACK1:
		{
			m_IdealActivity = ACT_RANGE_ATTACK1;

/*
			if( InSquad() )
			{
				// see if there is a battery to connect to. 
				CSquadMonster *pSquad = m_pSquadLeader;

				while( pSquad )
				{
					if( pSquad->m_iMySlot == bits_SLOT_HOUND_BATTERY )
					{
						// draw a beam.
						MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
							WRITE_BYTE( TE_BEAMENTS );
							WRITE_SHORT( ENTINDEX( this->edict() ) );
							WRITE_SHORT( ENTINDEX( pSquad->edict() ) );
							WRITE_SHORT( m_iSpriteTexture );
							WRITE_BYTE( 0 ); // framestart
							WRITE_BYTE( 0 ); // framerate
							WRITE_BYTE( 10 ); // life
							WRITE_BYTE( 40 );  // width
							WRITE_BYTE( 10 );   // noise
							WRITE_BYTE( 0 );   // r, g, b
							WRITE_BYTE( 50 );   // r, g, b
							WRITE_BYTE( 250);   // r, g, b
							WRITE_BYTE( 255 );	// brightness
							WRITE_BYTE( 30 );		// speed
						MESSAGE_END();
						break;
					}

					pSquad = pSquad->m_pSquadNext;
				}
			}
*/
			break;
		}
	case TASK_SPECIAL_ATTACK1:
		{
			m_IdealActivity = ACT_SPECIAL_ATTACK1;
			break;
		}
	case TASK_GUARD:
		{
			m_IdealActivity = ACT_GUARD;
			break;
		}
	default: 
		{
			CSquadMonster::StartTask( pTask );
			break;
		}
	}
}

//=========================================================
// RunTask 
//=========================================================
void CHoundeye::RunTask( Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_HOUND_THREAT_DISPLAY:
		{
			MakeIdealYaw( m_vecEnemyLKP );
			ChangeYaw( pev->yaw_speed );

			if( m_fSequenceFinished )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_HOUND_CLOSE_EYE:
		{
			if( pev->skin < HOUNDEYE_EYE_FRAMES - 1 )
			{
				pev->skin++;
			}
			break;
		}
	case TASK_HOUND_HOP_BACK:
		{
			if( m_fSequenceFinished )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_SPECIAL_ATTACK1:
		{
			pev->skin = RANDOM_LONG( 0, HOUNDEYE_EYE_FRAMES - 1 );

			MakeIdealYaw( m_vecEnemyLKP );
			ChangeYaw( pev->yaw_speed );

			float life;
			life = ( ( 255 - pev->frame ) / ( pev->framerate * m_flFrameRate ) );
			if( life < 0.1 )
				life = 0.1;

			MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin );
				WRITE_BYTE( TE_IMPLOSION );
				WRITE_COORD( pev->origin.x );
				WRITE_COORD( pev->origin.y );
				WRITE_COORD( pev->origin.z + 16 );
				WRITE_BYTE( 50 * life + 100 );  // radius
				WRITE_BYTE( pev->frame / 25.0 ); // count
				WRITE_BYTE( life * 10 ); // life
			MESSAGE_END();

			if( m_fSequenceFinished )
			{
				SonicAttack(); 
				TaskComplete();
			}
			break;
		}
	default:
		{
			CSquadMonster::RunTask( pTask );
			break;
		}
	}
}

//=========================================================
// PrescheduleThink
//=========================================================
void CHoundeye::PrescheduleThink( void )
{
	// if the hound is mad and is running, make hunt noises.
	if( m_MonsterState == MONSTERSTATE_COMBAT && m_Activity == ACT_RUN && RANDOM_FLOAT( 0, 1 ) < 0.2 )
	{
		WarnSound();
	}

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

	// if you are the leader, average the origins of each pack member to get an approximate center.
	if( IsLeader() )
	{
		CSquadMonster *pSquadMember;
		int iSquadCount = 0;

		for( int i = 0; i < MAX_SQUAD_MEMBERS; i++ )
		{
			pSquadMember = MySquadMember( i );

			if( pSquadMember )
			{
				iSquadCount++;
				m_vecPackCenter = m_vecPackCenter + pSquadMember->pev->origin;
			}
		}

		m_vecPackCenter = m_vecPackCenter / iSquadCount;
	}
}

//=========================================================
// AI Schedules Specific to this monster
//=========================================================
Task_t tlHoundGuardPack[] =
{
	{ TASK_STOP_MOVING,			(float)0		},
	{ TASK_GUARD,				(float)0		},
};

Schedule_t	slHoundGuardPack[] =
{
	{ 
		tlHoundGuardPack,
		ARRAYSIZE ( tlHoundGuardPack ), 
		bits_COND_SEE_HATE		|
		bits_COND_LIGHT_DAMAGE	|
		bits_COND_HEAVY_DAMAGE	|
		bits_COND_PROVOKED		|
		bits_COND_HEAR_SOUND,

		bits_SOUND_COMBAT		|// sound flags
		bits_SOUND_WORLD		|
		bits_SOUND_MEAT			|
		bits_SOUND_PLAYER,
		"GuardPack"
	},
};

// primary range attack
Task_t	tlHoundYell1[] =
{
	{ TASK_STOP_MOVING,			(float)0					},
	{ TASK_FACE_IDEAL,			(float)0					},
	{ TASK_RANGE_ATTACK1,		(float)0					},
	{ TASK_SET_SCHEDULE,		(float)SCHED_HOUND_AGITATED	},
};

Task_t	tlHoundYell2[] =
{
	{ TASK_STOP_MOVING,			(float)0					},
	{ TASK_FACE_IDEAL,			(float)0					},
	{ TASK_RANGE_ATTACK1,		(float)0					},
};

Schedule_t	slHoundRangeAttack[] =
{
	{ 
		tlHoundYell1,
		ARRAYSIZE ( tlHoundYell1 ), 
		bits_COND_LIGHT_DAMAGE	|
		bits_COND_HEAVY_DAMAGE,
		0,
		"HoundRangeAttack1"
	},
	{ 
		tlHoundYell2,
		ARRAYSIZE ( tlHoundYell2 ), 
		bits_COND_LIGHT_DAMAGE	|
		bits_COND_HEAVY_DAMAGE,
		0,
		"HoundRangeAttack2"
	},
};

// lie down and fall asleep
Task_t	tlHoundSleep[] =
{
	{ TASK_STOP_MOVING,			(float)0		},
	{ TASK_SET_ACTIVITY,		(float)ACT_IDLE			},
	{ TASK_WAIT_RANDOM,			(float)5				},
	{ TASK_PLAY_SEQUENCE,		(float)ACT_CROUCH		},
	{ TASK_SET_ACTIVITY,		(float)ACT_CROUCHIDLE	},
	{ TASK_HOUND_FALL_ASLEEP,	(float)0				},
	{ TASK_WAIT_RANDOM,			(float)25				},
	{ TASK_HOUND_CLOSE_EYE,		(float)0				},
	//{ TASK_WAIT,				(float)10				},
	//{ TASK_WAIT_RANDOM,			(float)10				},
};

Schedule_t	slHoundSleep[] =
{
	{
		tlHoundSleep,
		ARRAYSIZE ( tlHoundSleep ), 
		bits_COND_HEAR_SOUND	|
		bits_COND_LIGHT_DAMAGE	|
		bits_COND_HEAVY_DAMAGE	|
		bits_COND_NEW_ENEMY,

		bits_SOUND_COMBAT		|
		bits_SOUND_PLAYER		|
		bits_SOUND_WORLD,
		"Hound Sleep"
	},
};

// wake and stand up lazily
Task_t	tlHoundWakeLazy[] =
{
	{ TASK_STOP_MOVING,			(float)0			},
	{ TASK_HOUND_OPEN_EYE,		(float)0			},
	{ TASK_WAIT_RANDOM,			(float)2.5			},
	{ TASK_PLAY_SEQUENCE,		(float)ACT_STAND	},
	{ TASK_HOUND_WAKE_UP,		(float)0			},
};

Schedule_t	slHoundWakeLazy[] =
{
	{
		tlHoundWakeLazy,
		ARRAYSIZE ( tlHoundWakeLazy ),
		0,
		0,
		"WakeLazy"
	},
};

// wake and stand up with great urgency!
Task_t	tlHoundWakeUrgent[] =
{
	{ TASK_HOUND_OPEN_EYE,		(float)0			},
	{ TASK_PLAY_SEQUENCE,		(float)ACT_HOP		},
	{ TASK_FACE_IDEAL,			(float)0			},
	{ TASK_HOUND_WAKE_UP,		(float)0			},
};

Schedule_t	slHoundWakeUrgent[] =
{
	{
		tlHoundWakeUrgent,
		ARRAYSIZE ( tlHoundWakeUrgent ),
		0,
		0,
		"WakeUrgent"
	},
};

Task_t	tlHoundSpecialAttack1[] =
{
	{ TASK_STOP_MOVING,			0				},
	{ TASK_FACE_IDEAL,			(float)0		},
	{ TASK_SPECIAL_ATTACK1,		(float)0		},
	{ TASK_PLAY_SEQUENCE,		(float)ACT_IDLE_ANGRY },
};

Schedule_t	slHoundSpecialAttack1[] =
{
	{ 
		tlHoundSpecialAttack1,
		ARRAYSIZE ( tlHoundSpecialAttack1 ), 
		bits_COND_NEW_ENEMY			|
		bits_COND_LIGHT_DAMAGE		|
		bits_COND_HEAVY_DAMAGE		|
		bits_COND_ENEMY_OCCLUDED,
		
		0,
		"Hound Special Attack1"
	},
};

Task_t	tlHoundAgitated[] =
{
	{ TASK_STOP_MOVING,				0		},
	{ TASK_HOUND_THREAT_DISPLAY,	0		},
};

Schedule_t	slHoundAgitated[] =
{
	{ 
		tlHoundAgitated,
		ARRAYSIZE ( tlHoundAgitated ), 
		bits_COND_NEW_ENEMY			|
		bits_COND_LIGHT_DAMAGE		|
		bits_COND_HEAVY_DAMAGE,
		0,
		"Hound Agitated"
	},
};

Task_t	tlHoundHopRetreat[] =
{
	{ TASK_STOP_MOVING,				0											},
	{ TASK_HOUND_HOP_BACK,			0											},
	{ TASK_SET_SCHEDULE,			(float)SCHED_TAKE_COVER_FROM_ENEMY	},
};

Schedule_t	slHoundHopRetreat[] =
{
	{ 
		tlHoundHopRetreat,
		ARRAYSIZE ( tlHoundHopRetreat ), 
		0,
		0,
		"Hound Hop Retreat"
	},
};

// hound fails in combat with client in the PVS
Task_t	tlHoundCombatFailPVS[] =
{
	{ TASK_STOP_MOVING,				0			},
	{ TASK_HOUND_THREAT_DISPLAY,	0			},
	{ TASK_WAIT_FACE_ENEMY,			(float)1	},
};

Schedule_t	slHoundCombatFailPVS[] =
{
	{ 
		tlHoundCombatFailPVS,
		ARRAYSIZE ( tlHoundCombatFailPVS ), 
		bits_COND_NEW_ENEMY			|
		bits_COND_LIGHT_DAMAGE		|
		bits_COND_HEAVY_DAMAGE,
		0,
		"HoundCombatFailPVS"
	},
};

// hound fails in combat with no client in the PVS. Don't keep peeping!
Task_t	tlHoundCombatFailNoPVS[] =
{
	{ TASK_STOP_MOVING,				0				},
	{ TASK_HOUND_THREAT_DISPLAY,	0				},
	{ TASK_WAIT_FACE_ENEMY,			(float)2		},
	{ TASK_SET_ACTIVITY,			(float)ACT_IDLE	},
	{ TASK_WAIT_PVS,				0				},
};

Schedule_t	slHoundCombatFailNoPVS[] =
{
	{ 
		tlHoundCombatFailNoPVS,
		ARRAYSIZE ( tlHoundCombatFailNoPVS ), 
		bits_COND_NEW_ENEMY			|
		bits_COND_LIGHT_DAMAGE		|
		bits_COND_HEAVY_DAMAGE,
		0,
		"HoundCombatFailNoPVS"
	},
};

DEFINE_CUSTOM_SCHEDULES( CHoundeye )
{
	slHoundGuardPack,
	slHoundRangeAttack,
	&slHoundRangeAttack[ 1 ],
	slHoundSleep,
	slHoundWakeLazy,
	slHoundWakeUrgent,
	slHoundSpecialAttack1,
	slHoundAgitated,
	slHoundHopRetreat,
	slHoundCombatFailPVS,
	slHoundCombatFailNoPVS,
};

IMPLEMENT_CUSTOM_SCHEDULES( CHoundeye, CSquadMonster )

//=========================================================
// GetScheduleOfType 
//=========================================================
Schedule_t *CHoundeye::GetScheduleOfType( int Type ) 
{
	if( m_fAsleep )
	{
		// if the hound is sleeping, must wake and stand!
		if( HasConditions( bits_COND_HEAR_SOUND ) )
		{
			CSound *pWakeSound;

			pWakeSound = PBestSound();
			ASSERT( pWakeSound != NULL );
			if( pWakeSound )
			{
				MakeIdealYaw( pWakeSound->m_vecOrigin );

				if( FLSoundVolume( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME )
				{
					// awakened by a loud sound
					return &slHoundWakeUrgent[0];
				}
			}
			// sound was not loud enough to scare the bejesus out of houndeye
			return &slHoundWakeLazy[0];
		}
		else if( HasConditions( bits_COND_NEW_ENEMY ) )
		{
			// get up fast, to fight.
			return &slHoundWakeUrgent[0];
		}
		else
		{
			// hound is waking up on its own
			return &slHoundWakeLazy[0];
		}
	}
	switch( Type )
	{
	case SCHED_IDLE_STAND:
		{
			// we may want to sleep instead of stand!
			if( InSquad() && !IsLeader() && !m_fAsleep && RANDOM_LONG( 0, 29 ) < 1 )
			{
				return &slHoundSleep[0];
			}
			else
			{
				return CSquadMonster::GetScheduleOfType( Type );
			}
		}
	case SCHED_RANGE_ATTACK1:
		{
			return &slHoundRangeAttack[0];
/*
			if( InSquad() )
			{
				return &slHoundRangeAttack[RANDOM_LONG( 0, 1 )];
			}

			return &slHoundRangeAttack[1];
*/
		}
	case SCHED_SPECIAL_ATTACK1:
		{
			return &slHoundSpecialAttack1[0];
		}
	case SCHED_GUARD:
		{
			return &slHoundGuardPack[0];
		}
	case SCHED_HOUND_AGITATED:
		{
			return &slHoundAgitated[0];
		}
	case SCHED_HOUND_HOP_RETREAT:
		{
			return &slHoundHopRetreat[0];
		}
	case SCHED_FAIL:
		{
			if( m_MonsterState == MONSTERSTATE_COMBAT )
			{
				if( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) )
				{
					// client in PVS
					return &slHoundCombatFailPVS[0];
				}
				else
				{
					// client has taken off! 
					return &slHoundCombatFailNoPVS[0];
				}
			}
			else
			{
				return CSquadMonster::GetScheduleOfType( Type );
			}
		}
	default:
		{
			return CSquadMonster::GetScheduleOfType( Type );
		}
	}
}

//=========================================================
// GetSchedule 
//=========================================================
Schedule_t *CHoundeye::GetSchedule( void )
{
	switch( m_MonsterState )
	{
	case MONSTERSTATE_COMBAT:
		{
			// dead enemy
			if( HasConditions( bits_COND_ENEMY_DEAD ) )
			{
				// call base class, all code to handle dead enemies is centralized there.
				return CBaseMonster::GetSchedule();
			}

			if( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) )
			{
				if( RANDOM_FLOAT( 0, 1 ) <= 0.4 )
				{
					TraceResult tr;
					UTIL_MakeVectors( pev->angles );
					UTIL_TraceHull( pev->origin, pev->origin + gpGlobals->v_forward * -128, dont_ignore_monsters, head_hull, ENT( pev ), &tr );

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

				return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY );
			}

			if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) )
			{
				if( OccupySlot( bits_SLOTS_HOUND_ATTACK ) )
				{
					return GetScheduleOfType( SCHED_RANGE_ATTACK1 );
				}

				return GetScheduleOfType( SCHED_HOUND_AGITATED );
			}
			break;
		}
	default:
			break;
	}

	return CSquadMonster::GetSchedule();
}