//========= 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	"hl1_ai_basenpc.h"
#include	"ai_senses.h"

// Animation events
#define LEECH_AE_ATTACK		1
#define LEECH_AE_FLOP		2

//#define DEBUG_BEAMS 0

ConVar sk_leech_health( "sk_leech_health", "2" );
ConVar sk_leech_dmg_bite( "sk_leech_dmg_bite", "2" );

// Movement constants

#define		LEECH_ACCELERATE		10
#define		LEECH_CHECK_DIST		45
#define		LEECH_SWIM_SPEED		50
#define		LEECH_SWIM_ACCEL		80
#define		LEECH_SWIM_DECEL		10
#define		LEECH_TURN_RATE			70
#define		LEECH_SIZEX				10
#define		LEECH_FRAMETIME			0.1

class CNPC_Leech : public CHL1BaseNPC
{
	DECLARE_CLASS( CNPC_Leech, CHL1BaseNPC );
public:

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

	static const char *pAlertSounds[];

	void SwimThink( void );
	void DeadThink( void );

	void SwitchLeechState( void );
	float ObstacleDistance( CBaseEntity *pTarget );
	void UpdateMotion( void );

	void RecalculateWaterlevel( void );
	void Touch( CBaseEntity *pOther );

	Disposition_t IRelationType(CBaseEntity *pTarget);

	void HandleAnimEvent( animevent_t *pEvent );

	void AttackSound( void );
	void AlertSound( void );

	void Activate( void );

	Class_T	Classify( void ) { return CLASS_INSECT; };

	void Event_Killed( const CTakeDamageInfo &info );


	bool ShouldGib(	const CTakeDamageInfo &info );

	
/*	// Base entity functions
	void Killed( entvars_t *pevAttacker, int iGib );
	int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );
*/

private:
	// UNDONE: Remove unused boid vars, do group behavior
	float	m_flTurning;// is this boid turning?
	bool	m_fPathBlocked;// TRUE if there is an obstacle ahead
	float	m_flAccelerate;
	float	m_obstacle;
	float	m_top;
	float	m_bottom;
	float	m_height;
	float	m_waterTime;
	float	m_sideTime;		// Timer to randomly check clearance on sides
	float	m_zTime;
	float	m_stateTime;
	float	m_attackSoundTime;
	Vector  m_oldOrigin;
};

LINK_ENTITY_TO_CLASS( monster_leech, CNPC_Leech );

BEGIN_DATADESC( CNPC_Leech )
	DEFINE_FIELD( m_flTurning, FIELD_FLOAT ),
	DEFINE_FIELD( m_fPathBlocked, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flAccelerate, FIELD_FLOAT ),
	DEFINE_FIELD( m_obstacle, FIELD_FLOAT ),
	DEFINE_FIELD( m_top, FIELD_FLOAT ),
	DEFINE_FIELD( m_bottom, FIELD_FLOAT ),
	DEFINE_FIELD( m_height, FIELD_FLOAT ),
	DEFINE_FIELD( m_waterTime, FIELD_TIME ),
	DEFINE_FIELD( m_sideTime, FIELD_TIME ),
	DEFINE_FIELD( m_zTime, FIELD_TIME ),
	DEFINE_FIELD( m_stateTime, FIELD_TIME ),
	DEFINE_FIELD( m_attackSoundTime, FIELD_TIME ),
	DEFINE_FIELD( m_oldOrigin, FIELD_VECTOR ),

	DEFINE_THINKFUNC( SwimThink ),
	DEFINE_THINKFUNC( DeadThink ),
END_DATADESC()


bool CNPC_Leech::ShouldGib(	const CTakeDamageInfo &info )
{
	return false;
}

void CNPC_Leech::Spawn( void )
{
	Precache();
	SetModel( "models/leech.mdl" );

	SetHullType(HULL_TINY_CENTERED); 
	SetHullSizeNormal();

	UTIL_SetSize( this, Vector(-1,-1,0), Vector(1,1,2));

	Vector vecSurroundingMins(-8,-8,0);
	Vector vecSurroundingMaxs(8,8,2);
	CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );

	// Don't push the minz down too much or the water check will fail because this entity is really point-sized
	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_FLY );
	AddFlag( FL_SWIM );
	m_iHealth	= sk_leech_health.GetInt();

	m_flFieldOfView		= -0.5;	// 180 degree FOV
	SetDistLook( 750 );
	NPCInit();
	SetThink( &CNPC_Leech::SwimThink );
	SetUse( NULL );
	SetTouch( NULL );
	SetViewOffset( vec3_origin );

	m_flTurning = 0;
	m_fPathBlocked = FALSE;
	SetActivity( ACT_SWIM );
	SetState( NPC_STATE_IDLE );
	m_stateTime = gpGlobals->curtime + random->RandomFloat( 1, 5 );

	SetRenderColor( 255, 255, 255, 255 );

	m_bloodColor		= DONT_BLEED;
	SetCollisionGroup( COLLISION_GROUP_DEBRIS );
}

void CNPC_Leech::Activate( void )
{
	RecalculateWaterlevel();

	BaseClass::Activate();
}

void CNPC_Leech::DeadThink( void )
{
	if ( IsSequenceFinished() )
	{
		if ( GetActivity() == ACT_DIEFORWARD )
		{
			SetThink( NULL );
			StopAnimation();
			return;
		}
		else if ( GetFlags() & FL_ONGROUND )
		{
			AddSolidFlags( FSOLID_NOT_SOLID );
			SetActivity( ACT_DIEFORWARD );
		}
	}
	StudioFrameAdvance();
	SetNextThink( gpGlobals->curtime + 0.1 );

	// Apply damage velocity, but keep out of the walls
	if ( GetAbsVelocity().x != 0 || GetAbsVelocity().y != 0 )
	{
		trace_t tr;

		// Look 0.5 seconds ahead
		UTIL_TraceLine( GetLocalOrigin(), GetLocalOrigin() + GetAbsVelocity() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
		if (tr.fraction != 1.0)
		{
			Vector vVelocity = GetAbsVelocity();

			vVelocity.x = 0;
			vVelocity.y = 0;

			SetAbsVelocity( vVelocity );
		}
	}
}


Disposition_t CNPC_Leech::IRelationType( CBaseEntity *pTarget )
{
	if ( pTarget->IsPlayer() )
		return D_HT;
	
	return BaseClass::IRelationType( pTarget );
}

void CNPC_Leech::Touch( CBaseEntity *pOther )
{
	if ( !pOther->IsPlayer() )
		 return;

	if ( pOther == GetTouchTrace().m_pEnt )
	{
		if ( pOther->GetAbsVelocity() == vec3_origin )
			 return;

		SetBaseVelocity( pOther->GetAbsVelocity() );
		AddFlag( FL_BASEVELOCITY );
	}
}

void CNPC_Leech::HandleAnimEvent( animevent_t *pEvent )
{
	CBaseEntity *pEnemy = GetEnemy();

	switch( pEvent->event )
	{
	case LEECH_AE_FLOP:
		// Play flop sound
		break;

	case LEECH_AE_ATTACK:
		AttackSound();
		
		if ( pEnemy != NULL )
		{
			Vector dir, face;
	
			AngleVectors( GetAbsAngles(), &face );
			
			face.z = 0;
			dir = (pEnemy->GetLocalOrigin() - GetLocalOrigin() );
			dir.z = 0;
			
			VectorNormalize( dir );
			VectorNormalize( face );
						
			if ( DotProduct(dir, face) > 0.9 )		// Only take damage if the leech is facing the prey
			{
				CTakeDamageInfo info( this, this, sk_leech_dmg_bite.GetInt(), DMG_SLASH );
				CalculateMeleeDamageForce( &info, dir, pEnemy->GetAbsOrigin() );
				pEnemy->TakeDamage( info );
			}
		}
		m_stateTime -= 2;
		break;
	

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


void CNPC_Leech::Precache( void )
{
	PrecacheModel("models/leech.mdl");

	PrecacheScriptSound( "Leech.Attack" );
	PrecacheScriptSound( "Leech.Alert" );
}


void CNPC_Leech::AttackSound( void )
{
	if ( gpGlobals->curtime > m_attackSoundTime )
	{
		CPASAttenuationFilter filter( this );

		EmitSound(filter, entindex(), "Leech.Attack" );
		m_attackSoundTime = gpGlobals->curtime + 0.5;
	}
}


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

void CNPC_Leech::SwitchLeechState( void )
{
	m_stateTime = gpGlobals->curtime + random->RandomFloat( 3, 6 );
	if ( m_NPCState == NPC_STATE_COMBAT )
	{
		SetEnemy ( NULL );
		SetState( NPC_STATE_IDLE );
		// We may be up against the player, so redo the side checks
		m_sideTime = 0;
	}
	else
	{
		GetSenses()->Look( GetSenses()->GetDistLook() );
		CBaseEntity *pEnemy = BestEnemy();
		if ( pEnemy && pEnemy->GetWaterLevel() != 0 )
		{
			SetEnemy ( pEnemy );
			SetState( NPC_STATE_COMBAT );
			m_stateTime = gpGlobals->curtime + random->RandomFloat( 18, 25 );
			AlertSound();
		}
	}
}

void CNPC_Leech::RecalculateWaterlevel( void )
{
	// Calculate boundaries
	Vector vecTest = GetLocalOrigin() - Vector(0,0,400);

	trace_t tr;

	UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
	
	if ( tr.fraction != 1.0 )
		m_bottom = tr.endpos.z + 1;
	else
		m_bottom = vecTest.z;

	m_top = UTIL_WaterLevel( GetLocalOrigin(), GetLocalOrigin().z, GetLocalOrigin().z + 400 ) - 1;

#if DEBUG_BEAMS
	NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + Vector( 0, 0, m_bottom ), 0, 255, 0, false, 0.1f );
	NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + Vector( 0, 0, m_top ), 0, 255, 255, false, 0.1f );
#endif

	// Chop off 20% of the outside range
	float newBottom = m_bottom * 0.8 + m_top * 0.2;
	m_top = m_bottom * 0.2 + m_top * 0.8;
	m_bottom = newBottom;
	m_height = random->RandomFloat( m_bottom, m_top );
	m_waterTime = gpGlobals->curtime + random->RandomFloat( 5, 7 );
}

void CNPC_Leech::SwimThink( void )
{
	trace_t			tr;
	float			flLeftSide;
	float			flRightSide;
	float			targetSpeed;
	float			targetYaw = 0;
	CBaseEntity		*pTarget;

	/*if ( !UTIL_FindClientInPVS( edict() ) )
	{
		m_flNextThink = gpGlobals->curtime + random->RandomFloat( 1.0f, 1.5f );
		SetAbsVelocity( vec3_origin );
		return;
	}
	else*/
		SetNextThink( gpGlobals->curtime + 0.1 );

	targetSpeed = LEECH_SWIM_SPEED;

	if ( m_waterTime < gpGlobals->curtime )
		RecalculateWaterlevel();

	if ( m_stateTime < gpGlobals->curtime )
		SwitchLeechState();

	ClearCondition( COND_CAN_MELEE_ATTACK1 );

	switch( m_NPCState )
	{
	case NPC_STATE_COMBAT:
		pTarget = GetEnemy();
		if ( !pTarget )
			SwitchLeechState();
		else
		{
			// Chase the enemy's eyes
			m_height = pTarget->GetLocalOrigin().z + pTarget->GetViewOffset().z - 5;
			// Clip to viable water area
			if ( m_height < m_bottom )
				m_height = m_bottom;
			else if ( m_height > m_top )
				m_height = m_top;
			Vector location = pTarget->GetLocalOrigin() - GetLocalOrigin();
			location.z += (pTarget->GetViewOffset().z);
			if ( location.Length() < 80 )
				SetCondition( COND_CAN_MELEE_ATTACK1 );
			// Turn towards target ent
			targetYaw = UTIL_VecToYaw( location );

			QAngle vTestAngle = GetAbsAngles();
			
			targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( GetAbsAngles().y ) );

			if ( targetYaw < (-LEECH_TURN_RATE) )
				targetYaw = (-LEECH_TURN_RATE);
			else if ( targetYaw > (LEECH_TURN_RATE) )
				targetYaw = (LEECH_TURN_RATE);
			else
				targetSpeed *= 2;
		}

		break;

	default:
		if ( m_zTime < gpGlobals->curtime )
		{
			float newHeight = random->RandomFloat( m_bottom, m_top );
			m_height = 0.5 * m_height + 0.5 * newHeight;
			m_zTime = gpGlobals->curtime + random->RandomFloat( 1, 4 );
		}
		if ( random->RandomInt( 0, 100 ) < 10 )
			targetYaw = random->RandomInt( -30, 30 );
		pTarget = NULL;
		// oldorigin test
		if ( ( GetLocalOrigin() - m_oldOrigin ).Length() < 1 )
		{
			// If leech didn't move, there must be something blocking it, so try to turn
			m_sideTime = 0;
		}

		break;
	}

	m_obstacle = ObstacleDistance( pTarget );
	m_oldOrigin = GetLocalOrigin();
	if ( m_obstacle < 0.1 )
		m_obstacle = 0.1;

	Vector vForward, vRight;

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

	// is the way ahead clear?
	if ( m_obstacle == 1.0 )
	{
		// if the leech is turning, stop the trend.
		if ( m_flTurning != 0 )
		{
			m_flTurning = 0;
		}

		m_fPathBlocked = FALSE;
		m_flSpeed = UTIL_Approach( targetSpeed, m_flSpeed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME );
		SetAbsVelocity( vForward * m_flSpeed );

	}
	else
	{
		m_obstacle = 1.0 / m_obstacle;
		// IF we get this far in the function, the leader's path is blocked!
		m_fPathBlocked = TRUE;

		if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid
		{
			Vector vecTest;
			// measure clearance on left and right to pick the best dir to turn
			vecTest = GetLocalOrigin() + ( vRight * LEECH_SIZEX) + ( vForward * LEECH_CHECK_DIST);
			UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
			flRightSide = tr.fraction;

			vecTest = GetLocalOrigin() + ( vRight * -LEECH_SIZEX) + ( vForward * LEECH_CHECK_DIST);
			UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
			
			flLeftSide = tr.fraction;

			// turn left, right or random depending on clearance ratio
			float delta = (flRightSide - flLeftSide);
			if ( delta > 0.1 || (delta > -0.1 && random->RandomInt( 0,100 ) < 50 ) )
				m_flTurning = -LEECH_TURN_RATE;
			else
				m_flTurning = LEECH_TURN_RATE;
		}

		m_flSpeed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), m_flSpeed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle );
		SetAbsVelocity( vForward * m_flSpeed );
	}
	
	GetMotor()->SetIdealYaw( m_flTurning + targetYaw );
	UpdateMotion();
}

//
// ObstacleDistance - returns normalized distance to obstacle
//
float CNPC_Leech::ObstacleDistance( CBaseEntity *pTarget )
{
	trace_t			tr;
	Vector			vecTest;
	Vector			vForward, vRight;

	// use VELOCITY, not angles, not all boids point the direction they are flying
	//Vector vecDir = UTIL_VecToAngles( pev->velocity );
	QAngle tmp = GetAbsAngles();
	tmp.x = -tmp.x;
	AngleVectors ( tmp, &vForward, &vRight, NULL );

	// check for obstacle ahead
	vecTest = GetLocalOrigin() + vForward * LEECH_CHECK_DIST;
	UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);

	if ( tr.startsolid )
	{
		m_flSpeed = -LEECH_SWIM_SPEED * 0.5;
	}

	if ( tr.fraction != 1.0 )
	{
		if ( (pTarget == NULL || tr.m_pEnt != pTarget ) )
		{
			return tr.fraction;
		}
		else
		{
			if ( fabs( m_height - GetLocalOrigin().z ) > 10 )
				return tr.fraction;
		}
	}

	if ( m_sideTime < gpGlobals->curtime )
	{
		// extra wide checks
		vecTest = GetLocalOrigin() + vRight * LEECH_SIZEX * 2 + vForward * LEECH_CHECK_DIST;
		UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);

		if (tr.fraction != 1.0)
			return tr.fraction;

		vecTest = GetLocalOrigin() - vRight * LEECH_SIZEX * 2 + vForward * LEECH_CHECK_DIST;
		UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
		if (tr.fraction != 1.0)
			return tr.fraction;

		// Didn't hit either side, so stop testing for another 0.5 - 1 seconds
		m_sideTime = gpGlobals->curtime + random->RandomFloat(0.5,1);
	}

	return 1.0;
}

void CNPC_Leech::UpdateMotion( void )
{
	float flapspeed = ( m_flSpeed - m_flAccelerate) / LEECH_ACCELERATE;
	m_flAccelerate = m_flAccelerate * 0.8 + m_flSpeed * 0.2;

	if (flapspeed < 0) 
		flapspeed = -flapspeed;
	flapspeed += 1.0;
	if (flapspeed < 0.5) 
		flapspeed = 0.5;
	if (flapspeed > 1.9) 
		flapspeed = 1.9;

	m_flPlaybackRate = flapspeed;

	QAngle vAngularVelocity = GetLocalAngularVelocity();
	QAngle vAngles = GetLocalAngles();

	if ( !m_fPathBlocked )
		vAngularVelocity.y = GetMotor()->GetIdealYaw();
	else
		vAngularVelocity.y = GetMotor()->GetIdealYaw() * m_obstacle;

	if ( vAngularVelocity.y > 150 )
		SetIdealActivity( ACT_TURN_LEFT );
	else if ( vAngularVelocity.y < -150 )
		SetIdealActivity( ACT_TURN_RIGHT );
	else
		SetIdealActivity( ACT_SWIM );

	// lean
	float targetPitch, delta;
	delta = m_height - GetLocalOrigin().z;

/*	if ( delta < -10 )
		targetPitch = -30;
	else if ( delta > 10 )
		targetPitch = 30;
	else*/
		targetPitch = 0;

	vAngles.x = UTIL_Approach( targetPitch, vAngles.x, 60 * LEECH_FRAMETIME );

	// bank
	vAngularVelocity.z = - ( vAngles.z + (vAngularVelocity.y * 0.25));

	if ( m_NPCState == NPC_STATE_COMBAT && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
		SetIdealActivity( ACT_MELEE_ATTACK1 );

	// Out of water check
	if ( !GetWaterLevel() )
	{
		SetMoveType( MOVETYPE_FLYGRAVITY );
		SetIdealActivity( ACT_HOP );
		SetAbsVelocity( vec3_origin );

		// Animation will intersect the floor if either of these is non-zero
		vAngles.z = 0;
		vAngles.x = 0;

		m_flPlaybackRate = random->RandomFloat( 0.8, 1.2 );
	}
	else if ( GetMoveType() == MOVETYPE_FLYGRAVITY )
	{
		SetMoveType( MOVETYPE_FLY );
		SetGroundEntity( NULL );

	//  TODO
		RecalculateWaterlevel();
		m_waterTime = gpGlobals->curtime + 2;	// Recalc again soon, water may be rising
	}

	if ( GetActivity() != GetIdealActivity() )
	{
		SetActivity ( GetIdealActivity() );
	}
	StudioFrameAdvance();
	
	DispatchAnimEvents ( this );

	SetLocalAngles( vAngles );
	SetLocalAngularVelocity( vAngularVelocity );

	Vector vForward, vRight;

	AngleVectors( vAngles, &vForward, &vRight, NULL );

#if DEBUG_BEAMS
	if ( m_fPathBlocked )
	{
		float color = m_obstacle * 30;
		if ( m_obstacle == 1.0 )
			color = 0;
		if ( color > 255 )
			color = 255;
		 NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vForward * LEECH_CHECK_DIST, 255, color, color, false, 0.1f );
	}
	else
		 NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vForward * LEECH_CHECK_DIST, 255, 255, 0, false, 0.1f );
		 
	NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vRight * (vAngularVelocity.y*0.25), 0, 0, 255, false, 0.1f );
#endif

}

void CNPC_Leech::Event_Killed( const CTakeDamageInfo &info )
{
	Vector			vecSplatDir;
	trace_t			tr;

	//ALERT(at_aiconsole, "Leech: killed\n");
	// tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality.
	CBaseEntity *pOwner = GetOwnerEntity();
	if (pOwner)
		pOwner->DeathNotice( this );

	// When we hit the ground, play the "death_end" activity
	if ( GetWaterLevel() )
	{
		QAngle qAngles = GetAbsAngles();
		QAngle qAngularVel = GetLocalAngularVelocity();
		Vector  vOrigin = GetLocalOrigin();

		qAngles.z = 0;
		qAngles.x = 0;

		vOrigin.z += 1;

		SetAbsVelocity( vec3_origin );

		if ( random->RandomInt( 0, 99 ) < 70 )
			 qAngularVel.y = random->RandomInt( -720, 720 );

		SetAbsAngles( qAngles );
		SetLocalAngularVelocity( qAngularVel );
		SetAbsOrigin( vOrigin );

		
		SetGravity ( 0.02 );
		SetGroundEntity( NULL );
		SetActivity( ACT_DIESIMPLE );
	}
	else
		SetActivity( ACT_DIEFORWARD );
	
	SetMoveType( MOVETYPE_FLYGRAVITY );
	m_takedamage = DAMAGE_NO;
	
	SetThink( &CNPC_Leech::DeadThink );
}