//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $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	"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	"hl1_ai_basenpc.h"
#include	"ai_senses.h"
#include	"Sprite.h"
#include	"beam_shared.h"
#include	"logicrelay.h"
#include	"ai_navigator.h"


#define N_SCALE		15
#define N_SPHERES	20

ConVar sk_nihilanth_health( "sk_nihilanth_health", "800" );
ConVar sk_nihilanth_zap( "sk_nihilanth_zap", "30" );

class CNPC_Nihilanth : public CHL1BaseNPC
{
	DECLARE_CLASS( CNPC_Nihilanth, CHL1BaseNPC );
public:
	void Spawn( void );
	void Precache( void );

	Class_T Classify( void ) { return CLASS_ALIEN_MILITARY; };

	/*	void Killed( entvars_t *pevAttacker, int iGib );
	void GibMonster( void );

	void TargetSphere( USE_TYPE useType, float value );
	CBaseEntity *RandomTargetname( const char *szName );
	void MakeFriend( Vector vecPos );
	
	int  TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType );
	void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType);
	*/

	int	OnTakeDamage_Alive( const CTakeDamageInfo &info );
	void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
	bool ShouldGib( const CTakeDamageInfo &info ) { return false; }

	void PainSound( const CTakeDamageInfo &info );
	void DeathSound( const CTakeDamageInfo &info );
	
	void StartupThink( void );
	void NullThink( void );

	void HuntThink( void );
	void DyingThink( void );

	void Flight( void );
	void NextActivity( void );
	void FloatSequence( void );
	void HandleAnimEvent( animevent_t *pEvent );
	bool EmitSphere( void );

	void ShootBalls( void );
	bool AbsorbSphere( void );

	void MakeFriend( Vector vecStart );

	void InputTurnBabyOn( inputdata_t &inputdata );
	void InputTurnBabyOff( inputdata_t &inputdata );

	float m_flForce;

	float m_flNextPainSound;

	Vector m_velocity;
	Vector m_avelocity;

	Vector m_vecTarget;
	Vector m_posTarget;

	Vector m_vecDesired;
	Vector m_posDesired;

	float  m_flMinZ;
	float  m_flMaxZ;

	Vector m_vecGoal;

	float m_flLastSeen;
	float m_flPrevSeen;

	int m_irritation;

	int m_iLevel;
	int m_iTeleport;

	EHANDLE m_hRecharger;

	EHANDLE m_hSphere[N_SPHERES];
	int	m_iActiveSpheres;

	float m_flAdj;

	CSprite *m_pBall;

	char m_szRechargerTarget[64];
	char m_szDrawUse[64];
	char m_szTeleportUse[64];
	char m_szTeleportTouch[64];
	char m_szDeadUse[64];
	char m_szDeadTouch[64];

	float m_flShootEnd;
	float m_flShootTime;

	EHANDLE m_hFriend[3];

	bool m_bDead;

	DECLARE_DATADESC();
};

LINK_ENTITY_TO_CLASS( monster_nihilanth, CNPC_Nihilanth );

BEGIN_DATADESC( CNPC_Nihilanth )
	DEFINE_FIELD( m_flForce, FIELD_FLOAT ),
	DEFINE_FIELD( m_flNextPainSound, FIELD_TIME ),
	DEFINE_FIELD( m_velocity, FIELD_VECTOR ),
	DEFINE_FIELD( m_avelocity, FIELD_VECTOR ),
	DEFINE_FIELD( m_vecTarget, FIELD_VECTOR ),
	DEFINE_FIELD( m_posTarget, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vecDesired, FIELD_VECTOR ),
	DEFINE_FIELD( m_posDesired, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_flMinZ, FIELD_FLOAT ),
	DEFINE_FIELD( m_flMaxZ, FIELD_FLOAT ),
	DEFINE_FIELD( m_vecGoal, FIELD_VECTOR ),
	DEFINE_FIELD( m_flLastSeen, FIELD_TIME ),
	DEFINE_FIELD( m_flPrevSeen, FIELD_TIME ),
	DEFINE_FIELD( m_irritation, FIELD_INTEGER ),
	DEFINE_FIELD( m_iLevel, FIELD_INTEGER ),
	DEFINE_FIELD( m_iTeleport, FIELD_INTEGER ),
	DEFINE_FIELD( m_hRecharger, FIELD_EHANDLE ),
	DEFINE_ARRAY( m_hSphere, FIELD_EHANDLE, N_SPHERES ),
	DEFINE_FIELD( m_iActiveSpheres, FIELD_INTEGER ),
	DEFINE_FIELD( m_flAdj, FIELD_FLOAT ),
	DEFINE_FIELD( m_pBall, FIELD_CLASSPTR ),
	DEFINE_ARRAY( m_szRechargerTarget, FIELD_CHARACTER, 64 ),
	DEFINE_ARRAY( m_szDrawUse, FIELD_CHARACTER, 64 ),
	DEFINE_ARRAY( m_szTeleportUse, FIELD_CHARACTER, 64 ),
	DEFINE_ARRAY( m_szTeleportTouch, FIELD_CHARACTER, 64 ),
	DEFINE_ARRAY( m_szDeadUse, FIELD_CHARACTER, 64 ),
	DEFINE_ARRAY( m_szDeadTouch, FIELD_CHARACTER, 64 ),
	DEFINE_FIELD( m_flShootEnd, FIELD_TIME ),
	DEFINE_FIELD( m_flShootTime, FIELD_TIME ),
	DEFINE_ARRAY( m_hFriend, FIELD_EHANDLE, 3 ),
	DEFINE_FIELD( m_bDead, FIELD_BOOLEAN ),
	DEFINE_THINKFUNC( NullThink ),
	DEFINE_THINKFUNC( StartupThink ),
	DEFINE_THINKFUNC( HuntThink ),
	DEFINE_THINKFUNC( DyingThink ),

	DEFINE_INPUTFUNC( FIELD_VOID, "TurnBabyOn", InputTurnBabyOn ),
	DEFINE_INPUTFUNC( FIELD_VOID, "TurnBabyOff", InputTurnBabyOff ),

END_DATADESC()

class CNihilanthHVR : public CAI_BaseNPC
{
	DECLARE_CLASS( CNihilanthHVR, CAI_BaseNPC );
public:
	void Spawn( void );
	void Precache( void );

	void CircleInit( CBaseEntity *pTarget );
	void AbsorbInit( void );
	void GreenBallInit( void );
	

	void RemoveTouch( CBaseEntity *pOther );
	
	/*void Zap( void );
	void Teleport( void );*/
	
	void HoverThink( void );
	bool CircleTarget( Vector vecTarget );
	void BounceTouch( CBaseEntity *pOther );

	void ZapThink( void );
	void ZapInit( CBaseEntity *pEnemy );
	void ZapTouch( CBaseEntity *pOther );

	void TeleportThink( void );
	void TeleportTouch( CBaseEntity *pOther );

	void MovetoTarget( Vector vecTarget );

	void DissipateThink( void );

	CSprite *SpriteInit( const char *pSpriteName, CNihilanthHVR *pOwner );

	void TeleportInit( CNPC_Nihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch );

	float m_flIdealVel;
	Vector m_vecIdeal;
	CNPC_Nihilanth *m_pNihilanth;
	EHANDLE m_hTouch;
	
	
	float m_flBallScale;

	void SetSprite( CBaseEntity *pSprite )
	{
		m_hSprite = pSprite;	
	}

	CBaseEntity *GetSprite( void )
	{
		return m_hSprite.Get();
	}

	void SetBeam( CBaseEntity *pBeam )
	{
		m_hBeam = pBeam;	
	}

	CBaseEntity *GetBeam( void )
	{
		return m_hBeam.Get();
	}

private:
		
	EHANDLE m_hSprite;
	EHANDLE m_hBeam;


	DECLARE_DATADESC();
};

LINK_ENTITY_TO_CLASS( nihilanth_energy_ball, CNihilanthHVR );


BEGIN_DATADESC( CNihilanthHVR )
	DEFINE_FIELD( m_flIdealVel, FIELD_FLOAT ),
	DEFINE_FIELD( m_flBallScale, FIELD_FLOAT ),
	DEFINE_FIELD( m_vecIdeal, FIELD_VECTOR ),
	DEFINE_FIELD( m_pNihilanth, FIELD_CLASSPTR ),
	DEFINE_FIELD( m_hTouch, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hBeam, FIELD_EHANDLE ),

	DEFINE_THINKFUNC( HoverThink ),
	DEFINE_ENTITYFUNC( BounceTouch ),
	DEFINE_THINKFUNC( ZapThink ),
	DEFINE_ENTITYFUNC( ZapTouch ),
	DEFINE_THINKFUNC( DissipateThink ),
	DEFINE_THINKFUNC( TeleportThink ),
	DEFINE_ENTITYFUNC( TeleportTouch ),
	DEFINE_ENTITYFUNC( RemoveTouch ),
END_DATADESC()


//=========================================================
// Nihilanth, final Boss monster
//=========================================================

void CNPC_Nihilanth::Spawn( void )
{
	Precache( );
	// motor
	SetMoveType( MOVETYPE_FLY );
	SetSolid( SOLID_BBOX );

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

	UTIL_SetSize(this, Vector( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE ), Vector( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE ) );
	
	Vector vecSurroundingMins( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE );
	Vector vecSurroundingMaxs( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE );
	CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );

	UTIL_SetOrigin( this, GetAbsOrigin() - Vector( 0, 0, 64 ) );

	AddFlag( FL_NPC );
	m_takedamage		= DAMAGE_AIM;
	m_iHealth			= sk_nihilanth_health.GetFloat();
	SetViewOffset ( Vector( 0, 0, 300 ) );

	m_flFieldOfView = -1; // 360 degrees

	SetSequence( 0 );
	ResetSequenceInfo( );

	InitBoneControllers();

	SetThink( &CNPC_Nihilanth::StartupThink );
	SetNextThink( gpGlobals->curtime + 0.1 );

	m_vecDesired = Vector( 1, 0, 0 );
	m_posDesired = Vector( GetAbsOrigin().x, GetAbsOrigin().y, 512 );

	m_iLevel = 1; 
	m_iTeleport = 1;

	if (m_szRechargerTarget[0] == '\0')	Q_strncpy( m_szRechargerTarget, "n_recharger", sizeof( m_szRechargerTarget ) );
	if (m_szDrawUse[0] == '\0')			Q_strncpy( m_szDrawUse, "n_draw", sizeof( m_szDrawUse ) );
	if (m_szTeleportUse[0] == '\0')		Q_strncpy( m_szTeleportUse, "n_leaving", sizeof( m_szTeleportUse ) );
	if (m_szTeleportTouch[0] == '\0')	Q_strncpy( m_szTeleportTouch, "n_teleport", sizeof( m_szTeleportTouch ) );
	if (m_szDeadUse[0] == '\0')			Q_strncpy( m_szDeadUse, "n_dead", sizeof( m_szDeadUse ) );
	if (m_szDeadTouch[0] == '\0')		Q_strncpy( m_szDeadTouch, "n_ending", sizeof( m_szDeadTouch ) );

	SetBloodColor( BLOOD_COLOR_YELLOW );
}


void CNPC_Nihilanth::Precache( void )
{
	PrecacheModel("models/nihilanth.mdl");
	PrecacheModel("sprites/lgtning.vmt");
	UTIL_PrecacheOther( "nihilanth_energy_ball" );
	UTIL_PrecacheOther( "monster_alien_controller" );
	UTIL_PrecacheOther( "monster_alien_slave" );

	PrecacheScriptSound( "Nihilanth.PainLaugh" );
	PrecacheScriptSound( "Nihilanth.Pain" );
	PrecacheScriptSound( "Nihilanth.Die" );
	PrecacheScriptSound( "Nihilanth.FriendBeam" );
	PrecacheScriptSound( "Nihilanth.Attack" );
	PrecacheScriptSound( "Nihilanth.BallAttack" );
	PrecacheScriptSound( "Nihilanth.Recharge" );

}

void CNPC_Nihilanth::PainSound( const CTakeDamageInfo &info )
{
	if (m_flNextPainSound > gpGlobals->curtime)
		return;
	
	m_flNextPainSound = gpGlobals->curtime + random->RandomFloat( 2, 5 );

	if ( m_iHealth > sk_nihilanth_health.GetFloat() / 2 )
	{
		CPASAttenuationFilter filter( this );
		EmitSound( filter, entindex(), "Nihilanth.PainLaugh" ); 
	}
	else if (m_irritation >= 2)
	{
		CPASAttenuationFilter filter( this );
		EmitSound( filter, entindex(), "Nihilanth.Pain" ); 
	}
}	

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

int	CNPC_Nihilanth::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	if ( info.GetInflictor() == this )
		 return 0;

	if ( m_bDead )
		return 0;

	if ( info.GetDamage() >= m_iHealth )
	{
		m_iHealth = 1;
		if ( m_irritation != 3 )
			 return 0;
	}
	
	PainSound( info );

	m_iHealth -= info.GetDamage();

	if( m_iHealth < 0 )
	{
		m_iHealth = 1;
		m_bDead = true;
		m_takedamage = DAMAGE_NO;
	}

	return 0;
}

void CNPC_Nihilanth::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
	if (m_irritation == 3)
		m_irritation = 2;

	if (m_irritation == 2 && ptr->hitgroup == 0 && info.GetDamage() > 2)
		m_irritation = 3;

	if (m_irritation != 3)
	{
		Vector vecBlood = (ptr->endpos - GetAbsOrigin() );
		
		VectorNormalize( vecBlood );

		UTIL_BloodStream( ptr->endpos, vecBlood, BloodColor(), info.GetDamage() + (100 - 100 * (m_iHealth / sk_nihilanth_health.GetFloat() )));
	}

	// SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 5.0);// a little surface blood.
	AddMultiDamage( info, this );
}

bool CNPC_Nihilanth::EmitSphere( void )
{
	m_iActiveSpheres = 0;
	int empty = 0;

	for (int i = 0; i < N_SPHERES; i++)
	{
		if (m_hSphere[i] != NULL)
		{
			m_iActiveSpheres++;
		}
		else
		{
			empty = i;
		}
	}

	if (m_iActiveSpheres >= N_SPHERES)
		return false;

	Vector vecSrc = m_hRecharger->GetAbsOrigin();
	CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR,  "nihilanth_energy_ball" );
	
	
	pEntity->SetAbsOrigin( vecSrc );
	pEntity->SetAbsAngles( GetAbsAngles() );
	pEntity->SetOwnerEntity( this );
	pEntity->Spawn();

	pEntity->SetAbsVelocity( GetAbsOrigin() - vecSrc );
	pEntity->CircleInit( this );
	m_hSphere[empty] = pEntity;

	return true;
}

void CNPC_Nihilanth::NullThink( void )
{
	StudioFrameAdvance( );
	SetNextThink( gpGlobals->curtime + 0.5 );
}

void CNPC_Nihilanth::StartupThink( void )
{
	m_irritation = 0;
	m_flAdj = 512;

	CBaseEntity *pEntity;

	pEntity = gEntList.FindEntityByName( NULL, "n_min" );
	if (pEntity)
		m_flMinZ = pEntity->GetAbsOrigin().z;
	else
		m_flMinZ = -4096;

	pEntity = gEntList.FindEntityByName( NULL, "n_max" );
	if (pEntity)
		m_flMaxZ = pEntity->GetAbsOrigin().z;
	else
		m_flMaxZ = 4096;

	m_hRecharger = this;

	//TODO
	for (int i = 0; i < N_SPHERES; i++)
	{
		EmitSphere();
	}

	m_hRecharger = NULL;

	SetUse( NULL );
	SetThink( &CNPC_Nihilanth::HuntThink);
	
	SetNextThink( gpGlobals->curtime + 0.1 );
}

void CNPC_Nihilanth::InputTurnBabyOn( inputdata_t &inputdata )
{
	if ( m_irritation == 0 )
	{
		m_irritation = 1;
	}
}

void CNPC_Nihilanth::InputTurnBabyOff( inputdata_t &inputdata )
{
	CBaseEntity *pTouch = gEntList.FindEntityByName( NULL, m_szDeadTouch );
	
	if ( pTouch && GetEnemy() != NULL )
		 pTouch->Touch( GetEnemy() );
}

bool CNPC_Nihilanth::AbsorbSphere( void )
{
	for (int i = 0; i < N_SPHERES; i++)
	{
		if (m_hSphere[i] != NULL)
		{
			CNihilanthHVR *pSphere = (CNihilanthHVR *)m_hSphere[i].Get();
			pSphere->AbsorbInit();
			m_hSphere[i] = NULL;
			m_iActiveSpheres--;
			return TRUE;
		}
	}
	return FALSE;
}

void CNPC_Nihilanth::HuntThink( void )
{
	SetNextThink( gpGlobals->curtime + 0.1 );
	DispatchAnimEvents( this );
	StudioFrameAdvance( );

	ShootBalls();

	// if dead, force cancelation of current animation
	if ( m_bDead )
	{
		SetThink( &CNPC_Nihilanth::DyingThink );
		SetCycle( 1.0f );

		StudioFrameAdvance();
		return;
	}

	// ALERT( at_console, "health %.0f\n", pev->health );

	// if damaged, try to abosorb some spheres
	if ( m_iHealth < sk_nihilanth_health.GetFloat() && AbsorbSphere() )
	{
		m_iHealth += sk_nihilanth_health.GetFloat() / N_SPHERES;
	}

	// get new sequence
	if ( IsSequenceFinished() )
	{
		SetCycle( 0 );

		NextActivity( );
		ResetSequenceInfo( );
		m_flPlaybackRate = 2.0 - 1.0 * ( m_iHealth / sk_nihilanth_health.GetFloat() );
	}

	// look for current enemy	
	if ( GetEnemy() != NULL && m_hRecharger == NULL)
	{
		if (FVisible( GetEnemy() ))
		{
			if (m_flLastSeen < gpGlobals->curtime - 5)
				m_flPrevSeen = gpGlobals->curtime;
			
			m_flLastSeen = gpGlobals->curtime;
			m_posTarget = GetEnemy()->GetAbsOrigin();
			m_vecTarget = m_posTarget - GetAbsOrigin();
			
			VectorNormalize( m_vecTarget );

			m_vecDesired = m_vecTarget;
			m_posDesired = Vector( GetAbsOrigin().x, GetAbsOrigin().y, m_posTarget.z + m_flAdj );
		}
		else
		{
			m_flAdj = MIN( m_flAdj + 10, 1000 );
		}
	}

	// don't go too high
	if (m_posDesired.z > m_flMaxZ)
		m_posDesired.z = m_flMaxZ;

	// don't go too low
	if (m_posDesired.z < m_flMinZ)
		m_posDesired.z = m_flMinZ;

	Flight( );
}

void CNPC_Nihilanth::Flight( void )
{
	Vector vForward, vRight, vUp;

	QAngle vAngle = QAngle( GetAbsAngles().x + m_avelocity.x, GetAbsAngles().y + m_avelocity.y, GetAbsAngles().z + m_avelocity.z );

	AngleVectors( vAngle, &vForward, &vRight, &vUp );
	float flSide = DotProduct( m_vecDesired, vRight );

	if (flSide < 0)
	{
		if (m_avelocity.y < 180)
		{
			m_avelocity.y += 6; // 9 * (3.0/2.0);
		}
	}
	else
	{
		if (m_avelocity.y > -180)
		{
			m_avelocity.y -= 6; // 9 * (3.0/2.0);
		}
	}
	m_avelocity.y *= 0.98;

	// estimate where I'll be in two seconds
	Vector vecEst = GetAbsOrigin() + m_velocity * 2.0 + vUp * m_flForce * 20;

	// add immediate force
	AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );

	m_velocity.x += vUp.x * m_flForce;
	m_velocity.y += vUp.y * m_flForce;
	m_velocity.z += vUp.z * m_flForce;


	float flSpeed = m_velocity.Length();
	float flDir = DotProduct( Vector( vForward.x, vForward.y, 0 ), Vector( m_velocity.x, m_velocity.y, 0 ) );
	if (flDir < 0)
		flSpeed = -flSpeed;

	// sideways drag
	m_velocity.x = m_velocity.x * (1.0 - fabs( vRight.x ) * 0.05);
	m_velocity.y = m_velocity.y * (1.0 - fabs( vRight.y ) * 0.05);
	m_velocity.z = m_velocity.z * (1.0 - fabs( vRight.z ) * 0.05);

	// general drag
	m_velocity = m_velocity * 0.995;
	
	// apply power to stay correct height
	if (m_flForce < 100 && vecEst.z < m_posDesired.z) 
	{
		m_flForce += 10;
	}
	else if (m_flForce > -100 && vecEst.z > m_posDesired.z)
	{
		if (vecEst.z > m_posDesired.z) 
			m_flForce -= 10;
	}

	SetAbsVelocity( m_velocity );

	vAngle = QAngle( GetAbsAngles().x + m_avelocity.x * 0.1, GetAbsAngles().y + m_avelocity.y * 0.1, GetAbsAngles().z + m_avelocity.z * 0.1 );

	SetAbsAngles( vAngle );

	// ALERT( at_console, "%5.0f %5.0f : %4.0f : %3.0f : %2.0f\n", m_posDesired.z, pev->origin.z, m_velocity.z, m_avelocity.y, m_flForce ); 
}

void CNPC_Nihilanth::NextActivity( )
{
	Vector vForward, vRight, vUp;

	SetIdealActivity( ACT_DO_NOT_DISTURB );

	AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );

	if (m_irritation >= 2)
	{
		if (m_pBall == NULL)
		{
			m_pBall = CSprite::SpriteCreate( "sprites/tele1.vmt", GetAbsOrigin(), true );
			if (m_pBall)
			{
				m_pBall->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
				m_pBall->SetAttachment( this, 1 );
				m_pBall->SetScale( 4.0 );
				m_pBall->m_flSpriteFramerate = 10.0f;
				m_pBall->TurnOn( );
			}
		}

		if (m_pBall)
		{
			CBroadcastRecipientFilter filterlight;
			Vector vOrigin;
			QAngle vAngle;

			GetAttachment( 2, vOrigin, vAngle );

			te->DynamicLight( filterlight, 0.0, &vOrigin, 255, 192, 64, 0, 256, 20, 0 );
		}
	}
	

	if (( m_iHealth < sk_nihilanth_health.GetFloat() / 2 || m_iActiveSpheres < N_SPHERES / 2) && m_hRecharger == NULL && m_iLevel <= 9)
	{
		char szName[64];

		CBaseEntity *pEnt = NULL;
		CBaseEntity *pRecharger = NULL;
		float flDist = 8192;

		Q_snprintf(szName, sizeof( szName ), "%s%d", m_szRechargerTarget, m_iLevel );

		while ((pEnt = gEntList.FindEntityByName( pEnt, szName )) != NULL )
		{
			float flLocal = (pEnt->GetAbsOrigin() - GetAbsOrigin() ).Length();

			if ( flLocal < flDist )
			{
				flDist = flLocal;
				pRecharger = pEnt;
			}	
		}
		
		if (pRecharger)
		{
			m_hRecharger = pRecharger;
			m_posDesired = Vector( GetAbsOrigin().x, GetAbsOrigin().y, pRecharger->GetAbsOrigin().z );
			m_vecDesired = pRecharger->GetAbsOrigin() - m_posDesired;

			VectorNormalize( m_vecDesired );

			m_vecDesired.z = 0;
			
			VectorNormalize( m_vecDesired );
		}
		else
		{
			m_hRecharger = NULL;
			Msg( "nihilanth can't find %s\n", szName );

			m_iLevel++;

			if ( m_iLevel > 9 )
				 m_irritation = 2;
		}
	}

	float flDist = ( m_posDesired - GetAbsOrigin() ).Length();
	float flDot = DotProduct( m_vecDesired, vForward );

	if (m_hRecharger != NULL)
	{
		// at we at power up yet?
		if (flDist < 128.0)
		{
			int iseq = LookupSequence( "recharge" );

			if (iseq != GetSequence())
			{
				char szText[64];

				Q_snprintf( szText, sizeof( szText ), "%s%d", m_szDrawUse, m_iLevel );
				FireTargets( szText, this, this, USE_ON, 1.0 );

				Msg( "fireing %s\n", szText );
			}
			SetSequence ( LookupSequence( "recharge" ) );
		}
		else
		{
			FloatSequence( );
		}
		return;
	}

	if (GetEnemy() != NULL && !GetEnemy()->IsAlive())
	{
		SetEnemy( NULL );
	}

	if (m_flLastSeen + 15 < gpGlobals->curtime)
	{
		SetEnemy( NULL );
	}

	if ( GetEnemy() == NULL)
	{
		GetSenses()->Look( 4096 );
		SetEnemy( BestEnemy() );
	}

	if ( GetEnemy() != NULL && m_irritation != 0)
	{
		if (m_flLastSeen + 5 > gpGlobals->curtime && flDist < 256 && flDot > 0)
		{
			if (m_irritation >= 2 && m_iHealth < sk_nihilanth_health.GetFloat() / 2.0)
			{
				SetSequence( LookupSequence( "attack1_open" ) );
			}
			else 
			{
				if ( random->RandomInt(0, 1 ) == 0)
				{
					SetSequence( LookupSequence( "attack1" ) ); // zap
				}
				else
				{
					char szText[64];

					Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportTouch, m_iTeleport );
					CBaseEntity *pTouch = gEntList.FindEntityByName( NULL, szText );

					Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportUse, m_iTeleport );
					CBaseEntity *pTrigger = gEntList.FindEntityByName( NULL, szText );

					if (pTrigger != NULL || pTouch != NULL)
					{
						SetSequence( LookupSequence( "attack2" ) ); // teleport
					}
					else
					{
						m_iTeleport++;
						SetSequence( LookupSequence( "attack1" ) ); // zap
					}
				}
			}
			return;
		}
	}

	FloatSequence( );		
}

void CNPC_Nihilanth::MakeFriend( Vector vecStart )
{
	int i;

	for (i = 0; i < 3; i++)
	{
		if (m_hFriend[i] != NULL && !m_hFriend[i]->IsAlive())
		{
			if ( m_nRenderMode == kRenderNormal) // don't do it if they are already fading
				m_hFriend[i]->MyNPCPointer()->CorpseFade();

			m_hFriend[i] = NULL;
		}

		if (m_hFriend[i] == NULL)
		{
			if ( random->RandomInt( 0, 1 ) == 0)
			{
				int iNode = GetNavigator()->GetNetwork()->NearestNodeToPoint( vecStart );
				CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( iNode );

				if ( pNode && pNode->GetType() == NODE_AIR )
				{
					trace_t tr;
					Vector vNodeOrigin = pNode->GetOrigin();

					UTIL_TraceHull( vNodeOrigin + Vector( 0, 0, 32 ), vNodeOrigin + Vector( 0, 0, 32 ), Vector(-40,-40,   0),	Vector(40, 40, 100), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );

					if ( tr.startsolid == 0 )
						 m_hFriend[i] = Create("monster_alien_controller", vNodeOrigin, GetAbsAngles() );
				}
			}
			else
			{
				int iNode = GetNavigator()->GetNetwork()->NearestNodeToPoint( vecStart );
				CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( iNode );

				if ( pNode && ( pNode->GetType() == NODE_GROUND || pNode->GetType() == NODE_WATER ) )
				{
					trace_t tr;
					Vector vNodeOrigin = pNode->GetOrigin();

					UTIL_TraceHull( vNodeOrigin + Vector( 0, 0, 36 ), vNodeOrigin + Vector( 0, 0, 36 ), Vector( -15, -15, 0),	Vector( 20, 15, 72 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
					
					if (tr.startsolid == 0)
						m_hFriend[i] = Create("monster_alien_slave", vNodeOrigin, GetAbsAngles() );
				}
			}
			if (m_hFriend[i] != NULL)
			{
				CPASAttenuationFilter filter( this );
				EmitSound( filter, m_hFriend[i]->entindex(), "Nihilanth.FriendBeam" ); 
			}

			return;
		}
	}
}

void CNPC_Nihilanth::ShootBalls( void )
{
	if (m_flShootEnd > gpGlobals->curtime)
	{
		Vector vecHand;
		QAngle vecAngle;
		
		while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->curtime)
		{
			if ( GetEnemy() != NULL)
			{
				Vector vecSrc, vecDir;
				CNihilanthHVR *pEntity = NULL;

				GetAttachment( 3, vecHand, vecAngle );
				vecSrc = vecHand + GetAbsVelocity() * (m_flShootTime - gpGlobals->curtime);
				vecDir = m_posTarget - GetAbsOrigin();
				VectorNormalize( vecDir );
				vecSrc = vecSrc + vecDir * (gpGlobals->curtime - m_flShootTime);
				
				pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR,  "nihilanth_energy_ball" );

				pEntity->SetAbsOrigin( vecSrc );
				pEntity->SetAbsAngles( vecAngle );
				pEntity->SetOwnerEntity( this );
				pEntity->Spawn();

				pEntity->SetAbsVelocity( vecDir * 200.0 );
				pEntity->ZapInit( GetEnemy() );
				
				GetAttachment( 4, vecHand, vecAngle );
				vecSrc = vecHand + GetAbsVelocity() * (m_flShootTime - gpGlobals->curtime);
				vecDir = m_posTarget - GetAbsOrigin();
				VectorNormalize( vecDir );
				
				vecSrc = vecSrc + vecDir * (gpGlobals->curtime - m_flShootTime);
				
				pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR,  "nihilanth_energy_ball" );

				pEntity->SetAbsOrigin( vecSrc );
				pEntity->SetAbsAngles( vecAngle );
				pEntity->SetOwnerEntity( this );
				pEntity->Spawn();

				pEntity->SetAbsVelocity( vecDir * 200.0 );
				pEntity->ZapInit( GetEnemy() );

			}

			m_flShootTime += 0.2;
		}
	}
}

void CNPC_Nihilanth::FloatSequence( void )
{
	if (m_irritation >= 2)
	{
		SetSequence( LookupSequence( "float_open" ) );
	}
	else if (m_avelocity.y > 30)
	{
		SetSequence( LookupSequence( "walk_r" ) );
	}
	else if (m_avelocity.y < -30)
	{
		SetSequence( LookupSequence( "walk_l" ) );
	}
	else if (m_velocity.z > 30)
	{
		SetSequence( LookupSequence( "walk_u" ) );
	} 
	else if (m_velocity.z < -30)
	{
		SetSequence( LookupSequence( "walk_d" ) );
	}
	else
	{
		SetSequence( LookupSequence( "float" ) );
	}
}

void CNPC_Nihilanth::DyingThink( void )
{
	SetNextThink( gpGlobals->curtime + 0.1 );
	DispatchAnimEvents( this );
	StudioFrameAdvance( );

	if ( m_lifeState == LIFE_ALIVE )
	{
		CTakeDamageInfo info;
		DeathSound( info );
		m_lifeState = LIFE_DYING;

		m_posDesired.z = m_flMaxZ;
	}

	if ( GetAbsOrigin().z < m_flMaxZ && m_lifeState == LIFE_DEAD )
	{
		 SetAbsOrigin( Vector( GetAbsOrigin().x, GetAbsOrigin().y, m_flMaxZ ) );
		 SetAbsVelocity( Vector( 0, 0, 0 ) );
	}

	if ( m_lifeState == LIFE_DYING )
	{
		Flight( );

		if (fabs( GetAbsOrigin().z - m_flMaxZ ) < 16)
		{
			CBaseEntity *pTrigger = NULL;

			SetAbsVelocity( Vector( 0, 0, 0 ) );
			SetGravity( 0 );

			while( ( pTrigger = gEntList.FindEntityByName( pTrigger, m_szDeadUse ) ) != NULL )
			{
				CLogicRelay *pRelay = (CLogicRelay*)pTrigger;
				pRelay->m_OnTrigger.FireOutput( this, this );
			}

			m_lifeState = LIFE_DEAD;
		}
	}

	if ( IsSequenceFinished() )
	{
		QAngle qAngularVel = GetLocalAngularVelocity();

		qAngularVel.y += random->RandomFloat( -100, 100 );
	
		if ( qAngularVel.y < -100)
			 qAngularVel.y = -100;
		if ( qAngularVel.y > 100)
			 qAngularVel.y = 100;

		SetLocalAngularVelocity( qAngularVel );
		SetSequence( LookupSequence( "die1" ) );
	}

	if ( m_pBall )
	{
		if (m_pBall->GetBrightness() > 0)
		{
			m_pBall->SetBrightness( MAX( 0, m_pBall->GetBrightness() - 7 ), 0 );
		}
		else
		{
			UTIL_Remove( m_pBall );
			m_pBall = NULL;
		}
	}

	Vector vecDir, vecSrc;
	QAngle vecAngles;
	Vector vForward, vRight, vUp;

	AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );

	int iAttachment = random->RandomInt( 1, 4 );

	do {
		vecDir = Vector( random->RandomFloat( -1, 1 ), random->RandomFloat( -1, 1 ), random->RandomFloat( -1, 1 ) );
	} while (DotProduct( vecDir, vecDir) > 1.0);

	switch( random->RandomInt( 1, 4 ))
	{
	case 1: // head
		vecDir.z = fabs( vecDir.z ) * 0.5;
		vecDir = vecDir + 2 * vUp;
		break;
	case 2: // eyes
		if (DotProduct( vecDir, vForward ) < 0)
			vecDir = vecDir * -1;

		vecDir = vecDir + 2 * vForward;
		break;
	case 3: // left hand
		if (DotProduct( vecDir, vRight ) > 0)
			vecDir = vecDir * -1;
		vecDir = vecDir - 2 * vRight;
		break;
	case 4: // right hand
		if (DotProduct( vecDir, vRight ) < 0)
			vecDir = vecDir * -1;
		vecDir = vecDir + 2 * vRight;
		break;
	}

	GetAttachment( iAttachment, vecSrc, vecAngles );

	trace_t tr;

	UTIL_TraceLine( vecSrc, vecSrc + vecDir * 4096, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );

	CBeam *pBeam = CBeam::BeamCreate( "sprites/laserbeam.vmt", 16 );

	if ( pBeam == NULL )
		 return;

	pBeam->PointEntInit( tr.endpos, this );
	pBeam->SetEndAttachment( iAttachment );
	pBeam->SetColor( 64, 128, 255 );
	pBeam->SetFadeLength( 50 );
	pBeam->SetBrightness( 255 );
	pBeam->SetNoise( 12 );
	pBeam->SetScrollRate( 1.0 );
	pBeam->LiveForTime( 0.5 );
	
	GetAttachment( 2, vecSrc, vecAngles ); 
	CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
	
	pEntity->SetAbsOrigin( vecSrc );
	pEntity->SetAbsAngles( GetAbsAngles() );
	pEntity->SetOwnerEntity( this );
	pEntity->SetAbsVelocity( Vector ( random->RandomFloat( -0.7, 0.7 ), random->RandomFloat( -0.7, 0.7 ), 1.0 ) * 600.0 );
	pEntity->Spawn();

	pEntity->GreenBallInit();

	return;
}


void CNPC_Nihilanth::HandleAnimEvent( animevent_t *pEvent )
{
	switch( pEvent->event )
	{
	case 1:	// shoot 
		break;
	case 2:	// zen
		if ( GetEnemy() != NULL)
		{
			Vector vOrigin;
			QAngle vAngle;
			CPASAttenuationFilter filter( this );
		
			if ( random->RandomInt(0,4) == 0)
				EmitSound( filter, entindex(), "Nihilanth.Attack" ); 
			
			EmitSound( filter, entindex(), "Nihilanth.BallAttack" ); 

			GetAttachment( 2, vOrigin, vAngle);
			
			CBroadcastRecipientFilter filterlight;

			te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );

			GetAttachment( 3, vOrigin, vAngle);
			te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );
		
			m_flShootTime = gpGlobals->curtime;
			m_flShootEnd = gpGlobals->curtime + 1.0;
		}
		break;
	case 3:	// prayer
		if (GetEnemy() != NULL)
		{
			char szText[32];

			Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportTouch, m_iTeleport );
			CBaseEntity *pTouch = gEntList.FindEntityByName( NULL, szText );

			Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportUse, m_iTeleport );
			CBaseEntity *pTrigger = gEntList.FindEntityByName( NULL, szText );

			if (pTrigger != NULL || pTouch != NULL)
			{
				CPASAttenuationFilter filter( this );
				EmitSound( filter, entindex(), "Nihilanth.Attack" ); 

				Vector vecSrc;
				QAngle vecAngles;

				GetAttachment( 2, vecSrc, vecAngles ); 

				CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
				
				pEntity->SetAbsOrigin( vecSrc );
				pEntity->SetAbsAngles( vecAngles );
				pEntity->SetOwnerEntity( this );
				pEntity->Spawn();

				pEntity->TeleportInit( this, GetEnemy(), pTrigger, pTouch );

				pEntity->SetAbsVelocity( GetAbsOrigin() - vecSrc );
				pEntity->SetAbsVelocity( Vector( GetAbsVelocity().x, GetAbsVelocity().y, GetAbsVelocity().z * 0.2 ) );

			}
			else
			{
				Vector vOrigin;
				QAngle vAngle;

				m_iTeleport++; // unexpected failure

				CPASAttenuationFilter filter( this );
				EmitSound( filter, entindex(), "Nihilanth.BallAttack" ); 

				Msg( "nihilanth can't target %s\n", szText );

				GetAttachment( 2, vOrigin, vAngle);
			
				CBroadcastRecipientFilter filterlight;

				te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );

				GetAttachment( 3, vOrigin, vAngle);
				te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );

				m_flShootTime = gpGlobals->curtime;
				m_flShootEnd = gpGlobals->curtime + 1.0;
			}
		}
		break;
	case 4:	// get a sphere
		{
			if (m_hRecharger != NULL)
			{
				if (!EmitSphere( ))
				{
					m_hRecharger = NULL;
				}
			}
		}
		break;
	case 5:	// start up sphere machine
		{
			CPASAttenuationFilter filter( this );
			EmitSound( filter, entindex(), "Nihilanth.Recharge" ); 
		}
		break;
	case 6:
		if ( GetEnemy() != NULL)
		{
			Vector vecSrc;
			QAngle vecAngles;
			GetAttachment( 3, vecSrc, vecAngles ); 
						
			CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR,  "nihilanth_energy_ball" );

			pEntity->SetAbsOrigin( vecSrc );
			pEntity->SetAbsAngles( vecAngles );
			pEntity->SetOwnerEntity( this );
			pEntity->Spawn();

			pEntity->SetAbsVelocity( GetAbsOrigin() - vecSrc );
			pEntity->ZapInit( GetEnemy() );
		}
		break;
	case 7:
		/*
		Vector vecSrc, vecAngles;
		GetAttachment( 0, vecSrc, vecAngles ); 
		CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() );
		pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0;
		pEntity->GreenBallInit( );
		*/
		break;
	}
}


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



void CNihilanthHVR::Spawn( void )
{
	Precache( );
}


void CNihilanthHVR::Precache( void )
{
	PrecacheModel("sprites/flare6.vmt");
	PrecacheModel("sprites/nhth1.vmt");
	PrecacheModel("sprites/exit1.vmt");
	PrecacheModel("sprites/tele1.vmt");
	PrecacheModel("sprites/animglow01.vmt");
	PrecacheModel("sprites/xspark4.vmt");
	PrecacheModel("sprites/muzzleflash3.vmt");

	PrecacheModel("sprites/laserbeam.vmt");

	PrecacheScriptSound( "NihilanthHVR.Zap" );
	PrecacheScriptSound( "NihilanthHVR.TeleAttack" );
}

void CNihilanthHVR::CircleInit( CBaseEntity *pTarget )
{
	SetMoveType( MOVETYPE_FLY );
	SetSolid( SOLID_NONE );

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

	SetThink( &CNihilanthHVR::HoverThink );
	SetTouch( &CNihilanthHVR::BounceTouch );
	SetNextThink( gpGlobals->curtime + 0.1 );

	CSprite *pSprite = SpriteInit( "sprites/muzzleflash3.vmt", this );
	
	if ( pSprite )
	{
		m_flBallScale = 2.0f;
		pSprite->SetScale( 2.0 );
		pSprite->SetTransparency( kRenderTransAdd, 255, 224, 192, 255, kRenderFxNone );
	}
	
	SetTarget( pTarget );
}

CSprite *CNihilanthHVR::SpriteInit( const char *pSpriteName, CNihilanthHVR *pOwner )
{
	pOwner->SetSprite( CSprite::SpriteCreate( pSpriteName, pOwner->GetAbsOrigin(), true ) );

	CSprite *pSprite = (CSprite*)pOwner->GetSprite();

	if ( pSprite )
	{
		pSprite->SetAttachment( pOwner, 0 );
		pSprite->SetOwnerEntity( pOwner );
		pSprite->AnimateForTime( 5, 9999 );
	}

	return pSprite;
}

void CNihilanthHVR::HoverThink( void  )
{
	SetNextThink( gpGlobals->curtime + 0.1 );

	if ( GetTarget() != NULL )
	{
		CircleTarget( GetTarget()->GetAbsOrigin() + Vector( 0, 0, 16 * N_SCALE ) );
	}
	else
	{
		UTIL_Remove( GetSprite() );
		UTIL_Remove( this );
	}
}

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

	VectorNormalize( vecDir );

	const trace_t &tr = GetTouchTrace();

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

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

	m_vecIdeal = vecDir * m_vecIdeal.Length();
}

bool CNihilanthHVR::CircleTarget( Vector vecTarget )
{
	bool fClose = false;

	vecTarget = vecTarget + Vector( 0, 0, 64 );

	Vector vecDest = vecTarget;
	Vector vecEst = GetAbsOrigin() + GetAbsVelocity() * 0.5;
	Vector vecSrc = GetAbsOrigin();
	vecDest.z = 0;
	vecEst.z = 0;
	vecSrc.z = 0;
	float d1 = (vecDest - vecSrc).Length() - 24 * N_SCALE;
	float d2 = (vecDest - vecEst).Length() - 24 * N_SCALE;

	if ( m_vecIdeal == vec3_origin )
	{
		m_vecIdeal = GetAbsVelocity();
	}

	if (d1 < 0 && d2 <= d1)
	{
		// ALERT( at_console, "too close\n");
		Vector vTemp = (vecDest - vecSrc);

		VectorNormalize( vTemp );

		m_vecIdeal = m_vecIdeal - vTemp * 50;
	}

	else if (d1 > 0 && d2 >= d1)
	{
		Vector vTemp = (vecDest - vecSrc);

		VectorNormalize( vTemp );

		m_vecIdeal = m_vecIdeal + vTemp * 50;
	}

	SetLocalAngularVelocity( QAngle( GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, d1 * 20 ) );
	
	if (d1 < 32)
	{
		fClose = true;
	}

	m_vecIdeal = m_vecIdeal + Vector( random->RandomFloat( -2, 2 ), random->RandomFloat( -2, 2 ), random->RandomFloat( -2, 2 ));
	
	float flIdealZ = m_vecIdeal.z;
	
	m_vecIdeal = Vector( m_vecIdeal.x, m_vecIdeal.y, 0 );
	
	VectorNormalize( m_vecIdeal );
	
	m_vecIdeal = (m_vecIdeal * 200) + Vector( 0, 0, flIdealZ );
	
	// move up/down
	d1 = vecTarget.z - GetAbsOrigin().z;
	if (d1 > 0 && m_vecIdeal.z < 200)
		m_vecIdeal.z += 200;
	else if (d1 < 0 && m_vecIdeal.z > -200)
		m_vecIdeal.z -= 200;

	SetAbsVelocity( m_vecIdeal );

	// ALERT( at_console, "%.0f %.0f %.0f\n", m_vecIdeal.x, m_vecIdeal.y, m_vecIdeal.z );
	return fClose;
}


void CNihilanthHVR::ZapInit( CBaseEntity *pEnemy )
{
	SetMoveType( MOVETYPE_FLY );
	SetSolid( SOLID_BBOX );
	
	CSprite *pSprite = SpriteInit( "sprites/nhth1.vmt", this );
	
	if ( pSprite )
	{
		m_flBallScale = 2.0f;
		pSprite->SetScale( 2.0 );
		pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
	}
	

	Vector vVelocity = pEnemy->GetAbsOrigin() - GetAbsOrigin();
	VectorNormalize( vVelocity );
	SetAbsVelocity ( vVelocity * 300 );

	SetEnemy( pEnemy );
	SetThink( &CNihilanthHVR::ZapThink );
	SetTouch( &CNihilanthHVR::ZapTouch );
	SetNextThink( gpGlobals->curtime + 0.1 );

	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "NihilanthHVR.Zap" );
}

void CNihilanthHVR::ZapThink( void  )
{
	SetNextThink( gpGlobals->curtime + 0.05 );

	// check world boundaries
	if ( GetEnemy() == NULL ||  GetAbsOrigin().x < -4096 || GetAbsOrigin().x > 4096 || GetAbsOrigin().y < -4096 || GetAbsOrigin().y > 4096 || GetAbsOrigin().z < -4096 || GetAbsOrigin().z > 4096)
	{
		SetTouch( NULL );
		UTIL_Remove( GetSprite() );
		UTIL_Remove( this );
		return;
	}

	if ( GetAbsVelocity().Length() < 2000)
	{
		SetAbsVelocity( GetAbsVelocity() * 1.2 );
	}

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

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

		CBaseEntity *pEntity = tr.m_pEnt;

		if (pEntity != NULL && pEntity->m_takedamage )
		{
			ClearMultiDamage( );
			CTakeDamageInfo info( this, this, sk_nihilanth_zap.GetFloat(), DMG_SHOCK );
			CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
			pEntity->DispatchTraceAttack( info, GetAbsVelocity(), &tr  );
			ApplyMultiDamage();
		}

		CBeam *pBeam = CBeam::BeamCreate( "sprites/laserbeam.vmt", 2.0f );

		if ( pBeam == NULL )
			 return;

		pBeam->PointEntInit( tr.endpos, this );
		pBeam->SetColor( 64, 196, 255 );
		pBeam->SetBrightness( 255 );
		pBeam->SetNoise( 7.2 );
		pBeam->SetScrollRate( 10 );
		pBeam->LiveForTime( 0.1 );

		UTIL_EmitAmbientSound( GetSoundSourceIndex(), tr.endpos, "Controller.ElectroSound", 0.5, SNDLVL_NORM, 0, random->RandomInt( 140, 160 ) );

		SetTouch( NULL );
		GetSprite()->SetThink( &CBaseEntity::SUB_Remove );
		SetThink( &CBaseEntity::SUB_Remove );
		SetNextThink( gpGlobals->curtime + 0.2 );
		GetSprite()->SetNextThink( gpGlobals->curtime + 0.2 );
		return;
	}

	CBroadcastRecipientFilter filterlight;
	te->DynamicLight( filterlight, 0.0, &GetAbsOrigin(), 128, 128, 255, 0, 128, 10, 128 );
}


void CNihilanthHVR::ZapTouch( CBaseEntity *pOther )
{
	UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin(), "Controller.ElectroSound", 1.0, SNDLVL_NORM, 0, random->RandomInt( 90, 95 ) );

	RadiusDamage( CTakeDamageInfo( this, this, 50, DMG_SHOCK ), GetAbsOrigin(), 125,  CLASS_NONE, NULL );
	SetAbsVelocity( GetAbsVelocity() * 0 );

	SetTouch( NULL );
	UTIL_Remove( GetSprite() );
	UTIL_Remove( this );
	SetNextThink( gpGlobals->curtime + 0.2 );
}

void CNihilanthHVR::AbsorbInit( void  )
{
	CBroadcastRecipientFilter filter;

	SetThink( &CNihilanthHVR::DissipateThink );
	SetRenderColorA( 255 );

	SetBeam( CBeam::BeamCreate( "sprites/laserbeam.vmt", 8.0f ) );

	CBeam *pBeam = (CBeam*)GetBeam();

	if ( pBeam == NULL )
		 return;

	pBeam->EntsInit( this, GetTarget() );
	pBeam->SetEndAttachment( 1 );
	pBeam->SetColor( 255, 128, 64 );
	pBeam->SetBrightness( 255 );
	pBeam->SetNoise( 18 );
}

void CNihilanthHVR::DissipateThink( void  )
{
	CSprite *pSprite = (CSprite*)GetSprite();

	SetNextThink ( gpGlobals->curtime + 0.1 );

	if ( m_flBallScale > 5.0)
	{
		UTIL_Remove( this );
		UTIL_Remove( GetSprite() );
		UTIL_Remove( GetBeam() );
	}

	pSprite->SetBrightness( pSprite->GetBrightness() - 7, 0 );
	
	m_flBallScale += 0.1;
	pSprite->SetScale( m_flBallScale );

	if ( GetTarget() != NULL)
	{
		CircleTarget( GetTarget()->GetAbsOrigin() + Vector( 0, 0, 4096 ) );
	}
	else
	{
		UTIL_Remove( this );
		UTIL_Remove( GetSprite() );
		UTIL_Remove( GetBeam() );
	}

/*	MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
		WRITE_BYTE( TE_ELIGHT );
		WRITE_SHORT( entindex( ) );		// entity, attachment
		WRITE_COORD( pev->origin.x );		// origin
		WRITE_COORD( pev->origin.y );
		WRITE_COORD( pev->origin.z );
		WRITE_COORD( pev->renderamt );	// radius
		WRITE_BYTE( 255 );	// R
		WRITE_BYTE( 192 );	// G
		WRITE_BYTE( 64 );	// B
		WRITE_BYTE( 2 );	// life * 10
		WRITE_COORD( 0 ); // decay
	MESSAGE_END();*/
}

void CNihilanthHVR::TeleportInit( CNPC_Nihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch )
{
	SetMoveType( MOVETYPE_FLY );
	SetSolid( SOLID_BBOX );

	SetModel( "" );

	CSprite *pSprite = SpriteInit( "sprites/exit1.vmt", this );
	
	if ( pSprite )
	{
		m_flBallScale = 2.0f;
		pSprite->SetScale( 2.0 );
		pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
	}


	m_pNihilanth = pOwner;
	SetEnemy( pEnemy );
	SetTarget( pTarget );
	m_hTouch = pTouch;

	SetThink( &CNihilanthHVR::TeleportThink );
	SetTouch( &CNihilanthHVR::TeleportTouch );
	SetNextThink( gpGlobals->curtime + 0.1 );

	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "NihilanthHVR.TeleAttack" ); 
}

void CNihilanthHVR::MovetoTarget( Vector vecTarget )
{
	if ( m_vecIdeal == vec3_origin )
	{
		m_vecIdeal = GetAbsVelocity();
	}

	// accelerate
	float flSpeed = m_vecIdeal.Length();

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

	Vector vTemp = vecTarget - GetAbsOrigin();
	VectorNormalize( vTemp );

	m_vecIdeal = m_vecIdeal + vTemp * 300;
	SetAbsVelocity( m_vecIdeal );
}

void CNihilanthHVR::TeleportThink( void  )
{
	SetNextThink( gpGlobals->curtime + 0.1 );

	// check world boundaries
	if ( GetEnemy() == NULL || !GetEnemy()->IsAlive() || GetAbsOrigin().x < -4096 || GetAbsOrigin().x > 4096 || GetAbsOrigin().y < -4096 || GetAbsOrigin().y > 4096 || GetAbsOrigin().z < -4096 || GetAbsOrigin().z > 4096)
	{
		StopSound( entindex(), "NihilanthHVR.TeleAttack" );
		UTIL_Remove( this );
		UTIL_Remove( GetSprite() );
		return;
	}

	if (( GetEnemy()->WorldSpaceCenter() - GetAbsOrigin() ).Length() < 128)
	{
		StopSound( entindex(), "NihilanthHVR.TeleAttack" );
		UTIL_Remove( this );
		UTIL_Remove( GetSprite() );

		if ( GetTarget() != NULL)
		{
			 CLogicRelay *pRelay = (CLogicRelay*)GetTarget();
			 pRelay->m_OnTrigger.FireOutput( this, this );
		}

		if ( m_hTouch != NULL && GetEnemy() != NULL )
			m_hTouch->Touch( GetEnemy() );
	}
	else 
	{
		MovetoTarget( GetEnemy()->WorldSpaceCenter( ) );
	}

	CBroadcastRecipientFilter filterlight;
	te->DynamicLight( filterlight, 0.0, &GetAbsOrigin(), 0, 255, 0, 0, 256, 1.0, 256 );
}

void CNihilanthHVR::TeleportTouch( CBaseEntity *pOther )
{
	CBaseEntity *pEnemy = GetEnemy();

	if (pOther == pEnemy)
	{
		if (GetTarget() != NULL)
		{
			if ( GetTarget() != NULL)
			{
				 CLogicRelay *pRelay = (CLogicRelay*)GetTarget();
				 pRelay->m_OnTrigger.FireOutput( this, this );
			}
		}

		if (m_hTouch != NULL && pEnemy != NULL )
			m_hTouch->Touch( pEnemy );
	}
	else
	{
		m_pNihilanth->MakeFriend( GetAbsOrigin() );
	}

	SetTouch( NULL );
	StopSound( entindex(), "NihilanthHVR.TeleAttack" );
	UTIL_Remove( this );
	UTIL_Remove( GetSprite() );
}

void CNihilanthHVR::GreenBallInit( )
{
	SetMoveType( MOVETYPE_FLY );
	SetSolid( SOLID_BBOX );

	SetModel( "" );

	CSprite *pSprite = SpriteInit( "sprites/exit1.spr", this );
	
	if ( pSprite )
	{
		pSprite->SetScale( 1.0 );
		pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
	}

	SetTouch( &CNihilanthHVR::RemoveTouch );
}

void CNihilanthHVR::RemoveTouch( CBaseEntity *pOther )
{
	StopSound( entindex(), "NihilanthHVR.TeleAttack" );
	UTIL_Remove( this );
	UTIL_Remove( GetSprite() );
}