//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
//			events 
//
// $Workfile:     $
// $Date:         $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//

#include	"cbase.h"
#include	"ai_default.h"
#include	"ai_task.h"
#include	"ai_schedule.h"
#include	"ai_node.h"
#include	"ai_hull.h"
#include	"ai_hint.h"
#include	"ai_memory.h"
#include	"ai_route.h"
#include	"ai_motor.h"
//#include	"hl1_npc_controller.h"
#include	"ai_basenpc_flyer.h"
#include	"soundent.h"
#include	"game.h"
#include	"npcevent.h"
#include	"entitylist.h"
#include	"activitylist.h"
#include	"animation.h"
#include	"basecombatweapon.h"
#include	"IEffects.h"
#include	"vstdlib/random.h"
#include	"engine/IEngineSound.h"
#include	"ammodef.h"
#include	"Sprite.h"
#include	"ai_moveprobe.h"


//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define	CONTROLLER_AE_HEAD_OPEN		1
#define	CONTROLLER_AE_BALL_SHOOT	2
#define	CONTROLLER_AE_SMALL_SHOOT	3
#define CONTROLLER_AE_POWERUP_FULL	4
#define CONTROLLER_AE_POWERUP_HALF	5

#define CONTROLLER_FLINCH_DELAY			2		// at most one flinch every n secs

#define DIST_TO_CHECK	200

ConVar sk_controller_health ( "sk_controller_health", "60" );
ConVar sk_controller_dmgzap ( "sk_controller_dmgzap", "15" );
ConVar sk_controller_speedball ( "sk_controller_speedball", "650" );
ConVar sk_controller_dmgball ( "sk_controller_dmgball", "3" );

int ACT_CONTROLLER_UP;
int ACT_CONTROLLER_DOWN;
int ACT_CONTROLLER_LEFT;
int ACT_CONTROLLER_RIGHT;
int ACT_CONTROLLER_FORWARD;
int ACT_CONTROLLER_BACKWARD;

class CSprite;
class CNPC_Controller;

enum
{
	TASK_CONTROLLER_CHASE_ENEMY = LAST_SHARED_TASK,
	TASK_CONTROLLER_STRAFE,
	TASK_CONTROLLER_TAKECOVER,
	TASK_CONTROLLER_FAIL,
};

enum
{
	SCHED_CONTROLLER_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
	SCHED_CONTROLLER_STRAFE,
	SCHED_CONTROLLER_TAKECOVER,
	SCHED_CONTROLLER_FAIL,
};

class CControllerNavigator : public CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator>
{
	typedef CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator> BaseClass;
public:
	CControllerNavigator( CNPC_Controller *pOuter )
	 :	BaseClass( pOuter )
	{
	}

	bool ActivityIsLocomotive( Activity activity ) { return true; }
};

class CNPC_Controller : public CAI_BaseFlyingBot
{
public:

	DECLARE_CLASS( CNPC_Controller, CAI_BaseFlyingBot );
	DEFINE_CUSTOM_AI;
	DECLARE_DATADESC();

	void Spawn( void );
	void Precache( void );

	float MaxYawSpeed( void ) { return 120.0f; }
	Class_T	Classify ( void ) { return	CLASS_ALIEN_MILITARY; }

	void HandleAnimEvent( animevent_t *pEvent );

	void RunAI( void );

	int RangeAttack1Conditions ( float flDot, float flDist );	// balls
	int RangeAttack2Conditions ( float flDot, float flDist );	// head
	int MeleeAttack1Conditions ( float flDot, float flDist ) { return COND_NONE; }	
	int MeleeAttack2Conditions ( float flDot, float flDist ) { return COND_NONE; }

	int TranslateSchedule( int scheduleType );
	void StartTask ( const Task_t *pTask );
	void RunTask ( const Task_t *pTask );

	void Stop( void );
	bool OverridePathMove( float flInterval );
	bool OverrideMove( float flInterval );

	void MoveToTarget( float flInterval, const Vector &vecMoveTarget );

	Activity NPC_TranslateActivity( Activity eNewActivity );
	void SetActivity ( Activity NewActivity );
	bool ShouldAdvanceRoute( float flWaypointDist );
	int LookupFloat( );

	friend class CControllerNavigator;
	CAI_Navigator *CreateNavigator()
	{
		return new CControllerNavigator( this );
	}

	bool ShouldGib( const CTakeDamageInfo &info );
	bool HasAlienGibs( void ) { return true; }
	bool HasHumanGibs( void ) { return false; }

	float m_flShootTime;
	float m_flShootEnd;

	void PainSound( const CTakeDamageInfo &info );
	void AlertSound( void );
	void IdleSound( void );
	void AttackSound( void );
	void DeathSound( const CTakeDamageInfo &info );

	int OnTakeDamage_Alive( const CTakeDamageInfo &info );
	void Event_Killed( const CTakeDamageInfo &info );

	CSprite *m_pBall[2];	// hand balls
	int m_iBall[2];			// how bright it should be
	float m_iBallTime[2];	// when it should be that color
	int m_iBallCurrent[2];	// current brightness

	Vector m_vecEstVelocity;

	Vector m_velocity;
	bool m_fInCombat;

	void SetSequence( int nSequence );

	int IRelationPriority( CBaseEntity *pTarget );
};

class CNPC_ControllerHeadBall : public CAI_BaseNPC
{
public:
	DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );

	DECLARE_DATADESC();

	void Spawn( void );
	void Precache( void );

	void EXPORT HuntThink( void );
	void EXPORT KillThink( void );
	void EXPORT BounceTouch( CBaseEntity *pOther );
	void MovetoTarget( Vector vecTarget );

	float m_flSpawnTime;
	Vector m_vecIdeal;
	EHANDLE m_hOwner;

	CSprite *m_pSprite;
};

class CNPC_ControllerZapBall : public CAI_BaseNPC
{	
public:	
	DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );

	DECLARE_DATADESC();

	void Spawn( void );
	void Precache( void );

	void EXPORT AnimateThink( void );
	void EXPORT ExplodeTouch( CBaseEntity *pOther );

	void Kill( void );

	EHANDLE m_hOwner;
	float m_flSpawnTime;

	CSprite *m_pSprite;
};

LINK_ENTITY_TO_CLASS( monster_alien_controller, CNPC_Controller );

BEGIN_DATADESC( CNPC_Controller )

	DEFINE_ARRAY( m_pBall, FIELD_CLASSPTR, 2 ),
	DEFINE_ARRAY( m_iBall, FIELD_INTEGER, 2 ),
	DEFINE_ARRAY( m_iBallTime, FIELD_TIME, 2 ),
	DEFINE_ARRAY( m_iBallCurrent, FIELD_INTEGER, 2 ),
	DEFINE_FIELD( m_vecEstVelocity, FIELD_VECTOR ),
	DEFINE_FIELD( m_velocity, FIELD_VECTOR ),
	DEFINE_FIELD( m_fInCombat, FIELD_BOOLEAN ),

	DEFINE_FIELD( m_flShootTime, FIELD_TIME ),
	DEFINE_FIELD( m_flShootEnd, FIELD_TIME ),

END_DATADESC()


void CNPC_Controller::Spawn()
{
	Precache( );

	SetModel( "models/controller.mdl" );
	UTIL_SetSize( this, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ));

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );

	SetMoveType( MOVETYPE_STEP );
	SetGravity(0.001);


	m_bloodColor		= BLOOD_COLOR_GREEN;
	m_iHealth =			sk_controller_health.GetFloat();

	m_flFieldOfView		= VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result )
	m_NPCState			= NPC_STATE_NONE;

	SetRenderColor( 255, 255, 255, 255 );

	CapabilitiesClear();

	AddFlag( FL_FLY );
	SetNavType( NAV_FLY );

	CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_MOVE_SHOOT);

	NPCInit();

	
	SetDefaultEyeOffset();
}

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

	PrecacheModel( "sprites/xspark4.vmt");

	UTIL_PrecacheOther( "controller_energy_ball" );	
	UTIL_PrecacheOther( "controller_head_ball" );

	PrecacheScriptSound( "Controller.Pain" );
	PrecacheScriptSound( "Controller.Alert" );
	PrecacheScriptSound( "Controller.Die" );
	PrecacheScriptSound( "Controller.Idle" );
	PrecacheScriptSound( "Controller.Attack" );

}	

//=========================================================
// TakeDamage - 
//=========================================================
int CNPC_Controller::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	PainSound( info );
	return BaseClass::OnTakeDamage_Alive( info );
}

bool CNPC_Controller::ShouldGib( const CTakeDamageInfo &info )
{
	if ( info.GetDamageType() & DMG_NEVERGIB )
		 return false;

	if ( ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && m_iHealth < GIB_HEALTH_VALUE ) || ( info.GetDamageType() & DMG_ALWAYSGIB ) )
		 return true;
	
	return false;
	
}

int CNPC_Controller::IRelationPriority( CBaseEntity *pTarget )
{
	if ( pTarget->Classify() == CLASS_PLAYER )
	{
		 return BaseClass::IRelationPriority ( pTarget ) + 1;
	}

	return BaseClass::IRelationPriority( pTarget );
}

void CNPC_Controller::Event_Killed( const CTakeDamageInfo &info )
{
	if( ShouldGib(info) )
	{
		//remove the balls 
		if (m_pBall[0])
		{
			UTIL_Remove( m_pBall[0] );
			m_pBall[0] = NULL;
		}
		if (m_pBall[1])
		{
			UTIL_Remove( m_pBall[1] );
			m_pBall[1] = NULL;
		}
	}
	else
	{
		// fade balls
		if (m_pBall[0])
		{
			m_pBall[0]->FadeAndDie( 2 );
			m_pBall[0] = NULL;
		}
		if (m_pBall[1])
		{
			m_pBall[1]->FadeAndDie( 2 );
			m_pBall[1] = NULL;
		}
	}

	BaseClass::Event_Killed( info );
}

void CNPC_Controller::PainSound( const CTakeDamageInfo &info )
{
	if (random->RandomInt(0,5) < 2)
	{
		CPASAttenuationFilter filter( this );
		EmitSound( filter, entindex(), "Controller.Pain" );
	}
}

void CNPC_Controller::AlertSound( void )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "Controller.Alert" ); 
}

void CNPC_Controller::IdleSound( void )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "Controller.Idle" );
}

void CNPC_Controller::AttackSound( void )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "Controller.Attack" );
}

void CNPC_Controller::DeathSound( const CTakeDamageInfo &info )
{
	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "Controller.Die" );
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Controller::HandleAnimEvent( animevent_t *pEvent )
{
	switch( pEvent->event )
	{
		case CONTROLLER_AE_HEAD_OPEN:
		{
			Vector vecStart;
			QAngle angleGun;
			
			GetAttachment( 0, vecStart, angleGun );

			// BUGBUG - attach to attachment point!

			CBroadcastRecipientFilter filter;
			te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0, 1 /*radius*/, 0.2, -32 );
		
			m_iBall[0] = 192;
			m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
			m_iBall[1] = 255;
			m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;

		}
		break;

		case CONTROLLER_AE_BALL_SHOOT:
		{
			Vector vecStart;
			QAngle angleGun;
			
			GetAttachment( 1, vecStart, angleGun );

			CBroadcastRecipientFilter filter;
			te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0, 1 /*radius*/, 0.1, 32 );

			CAI_BaseNPC *pBall = (CAI_BaseNPC*)Create( "controller_head_ball", vecStart, angleGun );

			pBall->SetAbsVelocity( Vector(0,0,32) );
			pBall->SetEnemy( GetEnemy() );

//			DevMsg( 1, "controller shooting head ball\n" );

			m_iBall[0] = 0;
			m_iBall[1] = 0;
		}
		break;

		case CONTROLLER_AE_SMALL_SHOOT:
		{
			AttackSound( );
			m_flShootTime = gpGlobals->curtime;
			m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0;
		}
		break;
		case CONTROLLER_AE_POWERUP_FULL:
		{
			m_iBall[0] = 255;
			m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
			m_iBall[1] = 255;
			m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
		}
		break;
		case CONTROLLER_AE_POWERUP_HALF:
		{
			m_iBall[0] = 192;
			m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
			m_iBall[1] = 192;
			m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
		}
		break;
		default:
			BaseClass::HandleAnimEvent( pEvent );
			break;
	}
}


//=========================================================
// AI Schedules Specific to this monster
//=========================================================

AI_BEGIN_CUSTOM_NPC( monster_alien_controller, CNPC_Controller )

	//declare our tasks
	DECLARE_TASK( TASK_CONTROLLER_CHASE_ENEMY )
	DECLARE_TASK( TASK_CONTROLLER_STRAFE )
	DECLARE_TASK( TASK_CONTROLLER_TAKECOVER )
	DECLARE_TASK( TASK_CONTROLLER_FAIL )

	DECLARE_ACTIVITY( ACT_CONTROLLER_UP )
	DECLARE_ACTIVITY( ACT_CONTROLLER_DOWN )
	DECLARE_ACTIVITY( ACT_CONTROLLER_LEFT )
	DECLARE_ACTIVITY( ACT_CONTROLLER_RIGHT )
	DECLARE_ACTIVITY( ACT_CONTROLLER_FORWARD )
	DECLARE_ACTIVITY( ACT_CONTROLLER_BACKWARD )

	//=========================================================
	// > SCHED_CONTROLLER_CHASE_ENEMY
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_CONTROLLER_CHASE_ENEMY,

		"	Tasks"
		"		TASK_GET_PATH_TO_ENEMY			128"
		"		TASK_WAIT_FOR_MOVEMENT				0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_TASK_FAILED"


	)

	//=========================================================
	// > SCHED_CONTROLLER_STRAFE
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_CONTROLLER_STRAFE,

		"	Tasks"
		"		TASK_WAIT						0.2"
		"		TASK_GET_PATH_TO_ENEMY			128"
		"		TASK_WAIT_FOR_MOVEMENT			  0"
		"		TASK_WAIT						  1"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
	)

	//=========================================================
	// > SCHED_CONTROLLER_TAKECOVER
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_CONTROLLER_TAKECOVER,

		"	Tasks"
		"		TASK_WAIT						0.2"
		"		TASK_FIND_COVER_FROM_ENEMY		  0"
		"		TASK_WAIT_FOR_MOVEMENT			  0"
		"		TASK_WAIT						  1"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
	)

	//=========================================================
	// > SCHED_CONTROLLER_FAIL
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_CONTROLLER_FAIL,

		"	Tasks"
		"		TASK_STOP_MOVING					0"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_WAIT							2"
		"		TASK_WAIT_PVS						0"
	)

AI_END_CUSTOM_NPC()

//=========================================================
// StartTask
//=========================================================
void CNPC_Controller::StartTask( const Task_t *pTask )
{
	BaseClass::StartTask( pTask );
}

Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed )
{
	Vector vecTo = vecDst - vecSrc;

	float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed;
	float b = 0 * DotProduct(vecTo, vecMove); // why does this work?
	float c = DotProduct( vecTo, vecTo );

	float t;
	if (a == 0)
	{
		t = c / (flSpeed * flSpeed);
	}
	else
	{
		t = b * b - 4 * a * c;
		t = sqrt( t ) / (2.0 * a);
		float t1 = -b +t;
		float t2 = -b -t;

		if (t1 < 0 || t2 < t1)
			t = t2;
		else
			t = t1;
	}

	if (t < 0.1)
		t = 0.1;
	if (t > 10.0)
		t = 10.0;

	Vector vecHit = vecTo + vecMove * t;
	VectorNormalize( vecHit );
	return vecHit * flSpeed;
}


int CNPC_Controller::LookupFloat( )
{
	if (m_velocity.Length( ) < 32.0)
	{
		return ACT_CONTROLLER_UP;
	}

	Vector vecForward, vecRight, vecUp;
	AngleVectors( GetAbsAngles(), &vecForward, &vecRight, &vecUp );

	float x = DotProduct( vecForward, m_velocity );
	float y = DotProduct( vecRight, m_velocity );
	float z = DotProduct( vecUp, m_velocity );

	if (fabs(x) > fabs(y) && fabs(x) > fabs(z))
	{
		if (x > 0)
			return ACT_CONTROLLER_FORWARD;
		else
			return ACT_CONTROLLER_BACKWARD;
	}
	else if (fabs(y) > fabs(z))
	{
		if (y > 0)
			return ACT_CONTROLLER_RIGHT;
		else
			return ACT_CONTROLLER_LEFT;
	}
	else
	{
		if (z > 0)
			return ACT_CONTROLLER_UP;
		else
			return ACT_CONTROLLER_DOWN;
	}
}


//=========================================================
// RunTask 
//=========================================================
void CNPC_Controller::RunTask ( const Task_t *pTask )
{
	if (m_flShootEnd > gpGlobals->curtime)
	{
		Vector vecHand;
		QAngle vecAngle;
		
		GetAttachment( 2, vecHand, vecAngle );
	
		while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->curtime)
		{
			Vector vecSrc = vecHand + GetAbsVelocity() * (m_flShootTime - gpGlobals->curtime);
			Vector vecDir;
			
			if (GetEnemy() != NULL)
			{
				if (HasCondition( COND_SEE_ENEMY ))
				{
					m_vecEstVelocity = m_vecEstVelocity * 0.5 + GetEnemy()->GetAbsVelocity() * 0.5;
				}
				else
				{
					m_vecEstVelocity = m_vecEstVelocity * 0.8;
				}
				vecDir = Intersect( vecSrc, GetEnemy()->BodyTarget( GetAbsOrigin() ), m_vecEstVelocity, sk_controller_speedball.GetFloat() );
			
				float delta = 0.03490; // +-2 degree
				vecDir = vecDir + Vector( random->RandomFloat( -delta, delta ), random->RandomFloat( -delta, delta ), random->RandomFloat( -delta, delta ) ) * sk_controller_speedball.GetFloat();

				vecSrc = vecSrc + vecDir * (gpGlobals->curtime - m_flShootTime);
				CAI_BaseNPC *pBall = (CAI_BaseNPC*)Create( "controller_energy_ball", vecSrc, GetAbsAngles(), this );
				pBall->SetAbsVelocity( vecDir );

//				DevMsg( 2, "controller shooting energy ball\n" );
			}
			
			m_flShootTime += 0.2;
		}

		if (m_flShootTime > m_flShootEnd)
		{
			m_iBall[0] = 64;
			m_iBallTime[0] = m_flShootEnd;
			m_iBall[1] = 64;
			m_iBallTime[1] = m_flShootEnd;
			m_fInCombat = FALSE;
		}
	}

	switch ( pTask->iTask )
	{
	case TASK_WAIT_FOR_MOVEMENT:
	case TASK_WAIT:
	case TASK_WAIT_FACE_ENEMY:
	case TASK_WAIT_PVS:
		{
			if( GetEnemy() )
			{
				float idealYaw = UTIL_VecToYaw( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
				GetMotor()->SetIdealYawAndUpdate( idealYaw );
			}

			if ( IsSequenceFinished() || GetActivity() == ACT_IDLE)
			{
				m_fInCombat = false;
			}

			BaseClass::RunTask ( pTask );

			if (!m_fInCombat)
			{
				if( HasCondition( COND_CAN_RANGE_ATTACK1 ))
				{
					SetActivity( ACT_RANGE_ATTACK1 );
					SetCycle( 0 ); 
					ResetSequenceInfo( );
					m_fInCombat = true;
				}
				else if( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
				{
					SetActivity( ACT_RANGE_ATTACK2 );
					SetCycle( 0 );
					ResetSequenceInfo( );
					m_fInCombat = true;
				}
				else
				{
					int iFloatActivity = LookupFloat();
					if( IsSequenceFinished() || iFloatActivity != GetActivity() )
					{
						SetActivity( (Activity)iFloatActivity );
					}
				}
			}
		}
		break;
	default: 
		BaseClass::RunTask ( pTask );
		break;
	}
}

void CNPC_Controller::SetSequence( int nSequence )
{
	BaseClass::SetSequence( nSequence );
}


//=========================================================
//=========================================================
int CNPC_Controller::TranslateSchedule( int scheduleType )
{
	switch	( scheduleType )
	{
	case SCHED_CHASE_ENEMY:
		return SCHED_CONTROLLER_CHASE_ENEMY;
	case SCHED_RANGE_ATTACK1:
		return SCHED_CONTROLLER_STRAFE;
	case SCHED_RANGE_ATTACK2:
	case SCHED_MELEE_ATTACK1:
	case SCHED_MELEE_ATTACK2:
	case SCHED_TAKE_COVER_FROM_ENEMY:
		return SCHED_CONTROLLER_TAKECOVER;
	case SCHED_FAIL:
		return SCHED_CONTROLLER_FAIL;

	default:
		break;
	}

	return BaseClass::TranslateSchedule( scheduleType );
}


//=========================================================
// CheckRangeAttack1  - shoot a bigass energy ball out of their head
//=========================================================
int CNPC_Controller::RangeAttack1Conditions ( float flDot, float flDist )
{
	if( flDist > 2048 )
	{
		return COND_TOO_FAR_TO_ATTACK;
	}

	if( flDist <= 256 )
	{
		return COND_TOO_CLOSE_TO_ATTACK;
	}

//	if( flDot <= 0.5 )
//	{
//		return COND_NOT_FACING_ATTACK;
//	}

	return COND_CAN_RANGE_ATTACK1;
}

//=========================================================
// CheckRangeAttack1  - head
//=========================================================
int CNPC_Controller::RangeAttack2Conditions ( float flDot, float flDist )
{
		if( flDist > 2048 )
	{
		return COND_TOO_FAR_TO_ATTACK;
	}

	if( flDist <= 64 )
	{
		return COND_TOO_CLOSE_TO_ATTACK;
	}

//	if( flDot <= 0.5 )
//	{
//		return COND_NOT_FACING_ATTACK;
//	}

	return COND_CAN_RANGE_ATTACK2;
}

//=========================================================
//=========================================================
Activity CNPC_Controller::NPC_TranslateActivity( Activity eNewActivity )
{
	switch ( eNewActivity)
	{
	case ACT_IDLE:
		return (Activity)LookupFloat();
		break;

	default:
		return BaseClass::NPC_TranslateActivity( eNewActivity );
	}
}

//=========================================================
// SetActivity  - 
//=========================================================
void CNPC_Controller::SetActivity ( Activity NewActivity )
{
	BaseClass::SetActivity( NewActivity );
	m_flGroundSpeed = 100;
}

//=========================================================
// RunAI
//=========================================================
void CNPC_Controller::RunAI( void )
{
	BaseClass::RunAI();

	Vector vecStart;
	QAngle angleGun;

	//some kind of hack in hl1 ?
//	if ( HasMemory( bits_MEMORY_KILLED ) )
	//use this instead
	if( !IsAlive() )
		return;

	for (int i = 0; i < 2; i++)
	{
		if (m_pBall[i] == NULL)
		{
			m_pBall[i] = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), TRUE );
			m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation );
			m_pBall[i]->SetAttachment( this, (i + 3) );
			m_pBall[i]->SetScale( 1.0 );
		}

		float t = m_iBallTime[i] - gpGlobals->curtime;
		if (t > 0.1)
			t = 0.1 / t;
		else
			t = 1.0;

		m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t;

		m_pBall[i]->SetBrightness( m_iBallCurrent[i] );

		GetAttachment( i + 2, vecStart, angleGun );
		m_pBall[i]->SetAbsOrigin( vecStart );

		CBroadcastRecipientFilter filter;
		GetAttachment( i + 3, vecStart, angleGun );
		te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0/*exponent*/, m_iBallCurrent[i] / 8 /*radius*/, 0.5, 0 );
	}
}

//=========================================================
// Stop  - 
//=========================================================
void CNPC_Controller::Stop( void ) 
{ 
	SetIdealActivity( GetStoppedActivity() );
}

//-----------------------------------------------------------------------------
// Purpose: Handles movement towards the last move target.
// Input  : flInterval - 
//-----------------------------------------------------------------------------
bool CNPC_Controller::OverridePathMove( float flInterval )
{
	CBaseEntity *pMoveTarget = (GetTarget()) ? GetTarget() : GetEnemy();
	Vector waypointDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();

	float flWaypointDist = waypointDir.Length2D();
	VectorNormalize(waypointDir);

	// cut corner?
	if (flWaypointDist < 128)
	{
		if (m_flGroundSpeed > 100)
			m_flGroundSpeed -= 40;
	}
	else
	{
		if (m_flGroundSpeed < 400)
			m_flGroundSpeed += 10;
	}

	m_velocity = m_velocity * 0.8 + m_flGroundSpeed * waypointDir * 0.5;
	SetAbsVelocity( m_velocity );

	// -----------------------------------------------------------------
	// Check route is blocked
	// ------------------------------------------------------------------
	Vector checkPos = GetLocalOrigin() + (waypointDir * (m_flGroundSpeed * flInterval));

	AIMoveTrace_t moveTrace;
	GetMoveProbe()->MoveLimit( NAV_FLY, GetLocalOrigin(), checkPos, MASK_NPCSOLID|CONTENTS_WATER,
		pMoveTarget, &moveTrace);
	if (IsMoveBlocked( moveTrace ))
	{
		TaskFail(FAIL_NO_ROUTE);
		GetNavigator()->ClearGoal();
		return true;
	}

	// ----------------------------------------------
	
	Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();
	
	if ( ProgressFlyPath( flInterval, pMoveTarget, MASK_NPCSOLID, false, 64 ) == AINPP_COMPLETE )
	{
		{
			m_vLastPatrolDir = lastPatrolDir;
			VectorNormalize(m_vLastPatrolDir);
		}
		return true;
	}
	return false;
}

bool CNPC_Controller::OverrideMove( float flInterval )
{
	if (m_flGroundSpeed == 0)
	{
		m_flGroundSpeed = 100;
	}

	// ----------------------------------------------
	//	Select move target 
	// ----------------------------------------------
	CBaseEntity *pMoveTarget = NULL;
	if (GetTarget() != NULL )
	{
		pMoveTarget = GetTarget();
	}
	else if (GetEnemy() != NULL )
	{
		pMoveTarget		= GetEnemy();
	}

	// ----------------------------------------------
	//	Select move target position 
	// ----------------------------------------------
	Vector vMoveTargetPos(0,0,0);
	if (GetTarget())
	{
		vMoveTargetPos = GetTarget()->GetAbsOrigin();
	}
	else if (GetEnemy() != NULL)
	{
		vMoveTargetPos = GetEnemy()->GetAbsOrigin();
	}

	// -----------------------------------------
	//  See if we can fly there directly
	// -----------------------------------------
	if (pMoveTarget /*|| HaveInspectTarget()*/)
	{
		trace_t tr;

		if (pMoveTarget)
		{
			UTIL_TraceEntity( this, GetAbsOrigin(), vMoveTargetPos, 
				MASK_NPCSOLID_BRUSHONLY, pMoveTarget, GetCollisionGroup(), &tr);
		}
		else
		{
			UTIL_TraceEntity( this, GetAbsOrigin(), vMoveTargetPos, MASK_NPCSOLID_BRUSHONLY, &tr);
		}
/*
		float fTargetDist = (1-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
		if (fTargetDist > 50)
		{
			//SetCondition( COND_SCANNER_FLY_BLOCKED );
		}
		else
		{
			//SetCondition( COND_SCANNER_FLY_CLEAR );
		}
*/
	}

	// -----------------------------------------------------------------
	// If I have a route, keep it updated and move toward target
	// ------------------------------------------------------------------
	if (GetNavigator()->IsGoalActive())
	{
		if ( OverridePathMove( flInterval ) )
			return true;
	}
	else
	{
		//do nothing
		Stop();
		TaskComplete();
	}

	return true;
}

void CNPC_Controller::MoveToTarget( float flInterval, const Vector &vecMoveTarget )
{
	const float	myAccel	 = 300.0;
	const float	myDecay	 = 9.0;
	
	//TurnHeadToTarget( flInterval, MoveTarget );
	MoveToLocation( flInterval, vecMoveTarget, myAccel, (2 * myAccel), myDecay );
}

//=========================================================
// Controller bouncy ball attack
//=========================================================

LINK_ENTITY_TO_CLASS( controller_head_ball, CNPC_ControllerHeadBall );

BEGIN_DATADESC( CNPC_ControllerHeadBall )

	DEFINE_THINKFUNC( HuntThink ),
	DEFINE_THINKFUNC( KillThink ),
	DEFINE_ENTITYFUNC( BounceTouch ),

	DEFINE_FIELD( m_pSprite, FIELD_CLASSPTR ),

	DEFINE_FIELD( m_flSpawnTime, FIELD_TIME ),
	DEFINE_FIELD( m_vecIdeal, FIELD_VECTOR ),
	DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),

END_DATADESC()


void CNPC_ControllerHeadBall::Spawn( void )
{
	Precache( );
	// motor
	SetMoveType( MOVETYPE_FLY );
	SetSolid( SOLID_BBOX );
	SetSize( vec3_origin, vec3_origin );

	m_pSprite = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), FALSE );
	m_pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
	m_pSprite->SetAttachment( this, 0 );
	m_pSprite->SetScale( 2.0 );

	UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
	UTIL_SetOrigin( this, GetAbsOrigin() );

	SetThink( &CNPC_ControllerHeadBall::HuntThink );
	SetTouch( &CNPC_ControllerHeadBall::BounceTouch );

//	m_vecIdeal = vec3_origin;	//(0,0,0)

	SetNextThink( gpGlobals->curtime + 0.1 );

	m_hOwner = GetOwnerEntity();

	m_flSpawnTime = gpGlobals->curtime;
}


void CNPC_ControllerHeadBall::Precache( void )
{
	PrecacheModel( "sprites/xspark4.vmt");
}

extern short		g_sModelIndexLaser;	

void CNPC_ControllerHeadBall::HuntThink( void  )
{
	SetNextThink( gpGlobals->curtime + 0.1 );

	if( !m_pSprite )
	{
		Assert(0);
		return;
	}

	m_pSprite->SetBrightness( m_pSprite->GetBrightness() - 5, 0.1f );

	CBroadcastRecipientFilter filter;
	te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 255, 255, 255, 0, m_pSprite->GetBrightness() / 16, 0.2, 0 );

	// check world boundaries
	if (gpGlobals->curtime - m_flSpawnTime > 5 || m_pSprite->GetBrightness() < 64 /*|| GetEnemy() == NULL || m_hOwner == NULL*/ || !IsInWorld() )
	{
		SetTouch( NULL );
		SetThink( &CNPC_ControllerHeadBall::KillThink );
		SetNextThink( gpGlobals->curtime );
		return;
	}

	if( !GetEnemy() )
		return;

	MovetoTarget( GetEnemy()->GetAbsOrigin() );

	if ((GetEnemy()->WorldSpaceCenter() - GetAbsOrigin()).Length() < 64)
	{
		trace_t tr;

		UTIL_TraceLine( GetAbsOrigin(), GetEnemy()->WorldSpaceCenter(), MASK_ALL, this, COLLISION_GROUP_NONE, &tr );

		CBaseEntity *pEntity = tr.m_pEnt;
		if (pEntity != NULL && pEntity->m_takedamage == DAMAGE_YES)
		{
			ClearMultiDamage( );
			Vector dir = GetAbsVelocity();
			VectorNormalize( dir );
			CTakeDamageInfo info( this, this, sk_controller_dmgball.GetFloat(), DMG_SHOCK );
			CalculateMeleeDamageForce( &info, dir, tr.endpos );
			pEntity->DispatchTraceAttack( info, dir, &tr );
			ApplyMultiDamage();

			int haloindex = 0;
			int fadelength = 0;
			int amplitude = 0;
			const Vector vecEnd = tr.endpos;
			te->BeamEntPoint( filter, 0.0, entindex(), NULL, 0, &(tr.m_pEnt->GetAbsOrigin()), 
				g_sModelIndexLaser, haloindex /* no halo */, 0, 10, 3, 20, 20, fadelength, 
				amplitude, 255, 255, 255, 255, 10 );

		}

		UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin(), "Controller.ElectroSound", 0.5, SNDLVL_NORM, 0, 100 );

		SetNextAttack( gpGlobals->curtime + 3.0 );

		SetThink( &CNPC_ControllerHeadBall::KillThink );
		SetNextThink( gpGlobals->curtime + 0.3 );
	}
}

void CNPC_ControllerHeadBall::MovetoTarget( Vector vecTarget )
{
	// accelerate
	float flSpeed = m_vecIdeal.Length();
	if (flSpeed == 0)
	{
		m_vecIdeal = GetAbsVelocity();
		flSpeed = m_vecIdeal.Length();
	}

	if (flSpeed > 400)
	{
		VectorNormalize( m_vecIdeal );
		m_vecIdeal = m_vecIdeal * 400;
	}

	Vector t = vecTarget - GetAbsOrigin();
	VectorNormalize(t);
	m_vecIdeal = m_vecIdeal + t * 100;
	SetAbsVelocity(m_vecIdeal);
}

void CNPC_ControllerHeadBall::BounceTouch( CBaseEntity *pOther )
{
	Vector vecDir = m_vecIdeal;
	VectorNormalize( vecDir );

	trace_t tr;
	tr = CBaseEntity::GetTouchTrace( );

	float n = -DotProduct(tr.plane.normal, vecDir);

	vecDir = 2.0 * tr.plane.normal * n + vecDir;

	m_vecIdeal = vecDir * m_vecIdeal.Length();
}

void CNPC_ControllerHeadBall::KillThink( void )
{
	UTIL_Remove( m_pSprite );
	UTIL_Remove( this );
}


//=========================================================
// Controller Zap attack
//=========================================================

LINK_ENTITY_TO_CLASS( controller_energy_ball, CNPC_ControllerZapBall );

BEGIN_DATADESC( CNPC_ControllerZapBall )

	DEFINE_THINKFUNC( AnimateThink ),
	DEFINE_ENTITYFUNC( ExplodeTouch ),

	DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
	DEFINE_FIELD( m_flSpawnTime, FIELD_TIME ),
	DEFINE_FIELD( m_pSprite, FIELD_CLASSPTR ),

END_DATADESC()


void CNPC_ControllerZapBall::Spawn( void )
{
	Precache( );
	// motor
	SetMoveType( MOVETYPE_FLY );
//	SetSolid( SOLID_CUSTOM );
	SetSolid( SOLID_BBOX );
	SetSize( vec3_origin, vec3_origin );

	m_pSprite = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), FALSE );
	m_pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
	m_pSprite->SetAttachment( this, 0 );
	m_pSprite->SetScale( 0.5 );

	UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
	UTIL_SetOrigin( this, GetAbsOrigin() );

	SetThink( &CNPC_ControllerZapBall::AnimateThink );
	SetTouch( &CNPC_ControllerZapBall::ExplodeTouch );

	m_hOwner = GetOwnerEntity();

	m_flSpawnTime = gpGlobals->curtime; // keep track of when ball spawned
	SetNextThink( gpGlobals->curtime + 0.1 );
}


void CNPC_ControllerZapBall::Precache( void )
{
	PrecacheModel( "sprites/xspark4.vmt");
}


void CNPC_ControllerZapBall::AnimateThink( void  )
{
	SetNextThink( gpGlobals->curtime + 0.1 );
	
	SetCycle( ((int)GetCycle() + 1) % 11 );

	if (gpGlobals->curtime - m_flSpawnTime > 5 || GetAbsVelocity().Length() < 10)
	{
		SetTouch( NULL );
		Kill();
	}
}


void CNPC_ControllerZapBall::ExplodeTouch( CBaseEntity *pOther )
{
	if (m_takedamage = DAMAGE_YES )
	{
		trace_t tr;
		tr = GetTouchTrace( );

		ClearMultiDamage( );

		Vector vecAttackDir = GetAbsVelocity();
		VectorNormalize( vecAttackDir );

		if (m_hOwner != NULL)
		{
			CTakeDamageInfo info( this, m_hOwner, sk_controller_dmgball.GetFloat(), DMG_ENERGYBEAM );
			CalculateMeleeDamageForce( &info, vecAttackDir, tr.endpos );
			pOther->DispatchTraceAttack( info, vecAttackDir, &tr );
		}
		else
		{
			CTakeDamageInfo info( this, this, sk_controller_dmgball.GetFloat(), DMG_ENERGYBEAM );
			CalculateMeleeDamageForce( &info, vecAttackDir, tr.endpos );
			pOther->DispatchTraceAttack( info, vecAttackDir, &tr );
		}

		ApplyMultiDamage();

	//	void UTIL_EmitAmbientSound( CBaseEntity *entity, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime /*= 0.0f*/ )

		UTIL_EmitAmbientSound( GetSoundSourceIndex(), tr.endpos, "Controller.ElectroSound", 0.3, SNDLVL_NORM, 0, random->RandomInt( 90, 99 ) );
	}

	Kill();
}

void CNPC_ControllerZapBall::Kill( void )
{
	UTIL_Remove( m_pSprite );
	UTIL_Remove( this );
}