text: //========= 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	"ai_senses.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    "te.h"
  #include    "hl1_npc_hornet.h"
  
  int iHornetTrail;
  int iHornetPuff;
  
  LINK_ENTITY_TO_CLASS( hornet, CNPC_Hornet );
  
  extern ConVar sk_npc_dmg_hornet;
  extern ConVar sk_plr_dmg_hornet;
  
  BEGIN_DATADESC( CNPC_Hornet )
  	DEFINE_FIELD( m_flStopAttack, FIELD_TIME ),
  	DEFINE_FIELD( m_iHornetType, FIELD_INTEGER ),
  	DEFINE_FIELD( m_flFlySpeed, FIELD_FLOAT ),
  	DEFINE_FIELD( m_flDamage, FIELD_INTEGER ),
  	DEFINE_FIELD( m_vecEnemyLKP, FIELD_POSITION_VECTOR ),
  
  
  	DEFINE_ENTITYFUNC( DieTouch ),
  	DEFINE_THINKFUNC( StartDart ),
  	DEFINE_THINKFUNC( StartTrack ),
  	DEFINE_ENTITYFUNC( DartTouch ),
  	DEFINE_ENTITYFUNC( TrackTouch ),
  	DEFINE_THINKFUNC( TrackTarget ),
  END_DATADESC()
  
  //=========================================================
  //=========================================================
  void CNPC_Hornet::Spawn( void )
  {
  	Precache();
  
  	SetMoveType( MOVETYPE_FLY );
  	SetSolid( SOLID_BBOX );
  	m_takedamage	= DAMAGE_YES;
  	AddFlag( FL_NPC );
  	m_iHealth		= 1;// weak!
  	m_bloodColor	= DONT_BLEED;
  	
  	if ( g_pGameRules->IsMultiplayer() )
  	{
  		// hornets don't live as long in multiplayer
  		m_flStopAttack = gpGlobals->curtime + 3.5;
  	}
  	else
  	{
  		m_flStopAttack	= gpGlobals->curtime + 5.0;
  	}
  
  	m_flFieldOfView = 0.9; // +- 25 degrees
  
  	if ( random->RandomInt ( 1, 5 ) <= 2 )
  	{
  		m_iHornetType = HORNET_TYPE_RED;
  		m_flFlySpeed = HORNET_RED_SPEED;
  	}
  	else
  	{
  		m_iHornetType = HORNET_TYPE_ORANGE;
  		m_flFlySpeed = HORNET_ORANGE_SPEED;
  	}
  
  	SetModel( "models/hornet.mdl" );
  	UTIL_SetSize( this, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) );
  
  	SetTouch( &CNPC_Hornet::DieTouch );
  	SetThink( &CNPC_Hornet::StartTrack );
  
  	if ( GetOwnerEntity() && (GetOwnerEntity()->GetFlags() & FL_CLIENT) )
  	{
  		m_flDamage = sk_plr_dmg_hornet.GetFloat();
  	}
  	else
  	{
  		// no real owner, or owner isn't a client. 
  		m_flDamage = sk_npc_dmg_hornet.GetFloat();
  	}
  	
  	SetNextThink( gpGlobals->curtime + 0.1f );
  	ResetSequenceInfo();
  
  	m_vecEnemyLKP = vec3_origin;
  }
  
  
  void CNPC_Hornet::Precache()
  {
  	PrecacheModel("models/hornet.mdl");
  
  	iHornetPuff = PrecacheModel( "sprites/muz1.vmt" );
  	iHornetTrail = PrecacheModel("sprites/laserbeam.vmt");
  
  	PrecacheScriptSound( "Hornet.Die" );
  	PrecacheScriptSound( "Hornet.Buzz" );
  }	
  
  //=========================================================
  // hornets will never get mad at each other, no matter who the owner is.
  //=========================================================
  Disposition_t CNPC_Hornet::IRelationType( CBaseEntity *pTarget )
  {
  	if ( pTarget->GetModelIndex() == GetModelIndex() )
  	{
  		return D_NU;
  	}
  
  	return BaseClass::IRelationType( pTarget );
  }
  
  //=========================================================
  // ID's Hornet as their owner
  //=========================================================
  Class_T CNPC_Hornet::Classify ( void )
  {
  	if ( GetOwnerEntity() && (GetOwnerEntity()->GetFlags() & FL_CLIENT) )
  	{
  		return CLASS_PLAYER_BIOWEAPON;
  	}
  
  	return	CLASS_ALIEN_BIOWEAPON;
  }
  
  //=========================================================
  // StartDart - starts a hornet out just flying straight.
  //=========================================================
  void CNPC_Hornet::StartDart ( void )
  {
  	IgniteTrail();
  
  	SetTouch( &CNPC_Hornet::DartTouch );
  
  	SetThink( &CBaseEntity::SUB_Remove );
  	SetNextThink( gpGlobals->curtime + 4 );
  }
  
  
  void CNPC_Hornet::DieTouch ( CBaseEntity *pOther )
  {
  	if ( !pOther || !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) )
  	{
  		return;
  	}
  
  	CPASAttenuationFilter filter( this );
  	EmitSound( filter, entindex(), "Hornet.Die" );
  			
  	CTakeDamageInfo info( this, GetOwnerEntity(), m_flDamage, DMG_BULLET );
  	CalculateBulletDamageForce( &info, GetAmmoDef()->Index("Hornet"), GetAbsVelocity(), GetAbsOrigin() );
  	pOther->TakeDamage( info );
  
  	m_takedamage	= DAMAGE_NO;
  
  	AddEffects( EF_NODRAW );
  
  	AddSolidFlags( FSOLID_NOT_SOLID );// intangible
  
  	UTIL_Remove( this );
  	SetTouch( NULL );
  }
  
  
  //=========================================================
  // StartTrack - starts a hornet out tracking its target
  //=========================================================
  void CNPC_Hornet:: StartTrack ( void )
  {
  	IgniteTrail();
  
  	SetTouch( &CNPC_Hornet::TrackTouch );
  	SetThink( &CNPC_Hornet::TrackTarget );
  
  	SetNextThink( gpGlobals->curtime + 0.1f );
  }
  
  void TE_BeamFollow( IRecipientFilter& filter, float delay,
  	int iEntIndex, int modelIndex, int haloIndex, float life, float width, float endWidth, 
  	float fadeLength,float r, float g, float b, float a );
  
  void CNPC_Hornet::IgniteTrail( void )
  {
  	Vector vColor;
  
  	if ( m_iHornetType == HORNET_TYPE_RED )
  		 vColor = Vector ( 179, 39, 14 );
  	else
  		 vColor = Vector ( 255, 128, 0 );
  
  	CBroadcastRecipientFilter filter;
  	TE_BeamFollow( filter, 0.0,
  		entindex(),
  		iHornetTrail,
  		0,
  		1,
  		2,
  		0.5,
  		0.5,
  		vColor.x,
  		vColor.y,
  		vColor.z,
  		128 );
  }
  
  
  unsigned int CNPC_Hornet::PhysicsSolidMaskForEntity( void ) const
  {
  	unsigned int iMask = BaseClass::PhysicsSolidMaskForEntity();
  
  	iMask &= ~CONTENTS_MONSTERCLIP;
  
  	return iMask;
  }
  
  //=========================================================
  // Tracking Hornet hit something
  //=========================================================
  void CNPC_Hornet::TrackTouch ( CBaseEntity *pOther )
  {
  	if ( !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) )
  	{
  		return;
  	}
  
  	if ( pOther == GetOwnerEntity() || pOther->GetModelIndex() == GetModelIndex() )
  	{// bumped into the guy that shot it.
  		//SetSolid( SOLID_NOT );
  		return;
  	}
  
  	int nRelationship = IRelationType( pOther );
  	if ( (nRelationship == D_FR || nRelationship == D_NU || nRelationship == D_LI) )
  	{
  		// hit something we don't want to hurt, so turn around.
  		Vector vecVel = GetAbsVelocity();
  
  		VectorNormalize( vecVel );
  
  		vecVel.x *= -1;
  		vecVel.y *= -1;
  
  		SetAbsOrigin( GetAbsOrigin() + vecVel * 4 ); // bounce the hornet off a bit.
  		SetAbsVelocity( vecVel * m_flFlySpeed );
  
  		return;
  	}
  
  	DieTouch( pOther );
  }
  
  void CNPC_Hornet::DartTouch( CBaseEntity *pOther )
  {
  	DieTouch( pOther );
  }
  
  //=========================================================
  // Hornet is flying, gently tracking target
  //=========================================================
  void CNPC_Hornet::TrackTarget ( void )
  {
  	Vector	vecFlightDir;
  	Vector	vecDirToEnemy;
  	float	flDelta;
  
  	StudioFrameAdvance( );
  
  	if (gpGlobals->curtime > m_flStopAttack)
  	{
  		SetTouch( NULL );
  		SetThink( &CBaseEntity::SUB_Remove );
  		SetNextThink( gpGlobals->curtime + 0.1f );
  		return;
  	}
  
  	// UNDONE: The player pointer should come back after returning from another level
  	if ( GetEnemy() == NULL )
  	{// enemy is dead.
  		GetSenses()->Look( 1024 );
  		SetEnemy( BestEnemy() );
  	}
  	
  	if ( GetEnemy() != NULL && FVisible( GetEnemy() ))
  	{
  		m_vecEnemyLKP = GetEnemy()->BodyTarget( GetAbsOrigin() );
  	}
  	else
  	{
  		m_vecEnemyLKP = m_vecEnemyLKP + GetAbsVelocity() * m_flFlySpeed * 0.1;
  	}
  
  	vecDirToEnemy = m_vecEnemyLKP - GetAbsOrigin();
  	VectorNormalize( vecDirToEnemy );
  
  	if ( GetAbsVelocity().Length() < 0.1 )
  		vecFlightDir = vecDirToEnemy;
  	else 
  	{
  		vecFlightDir = GetAbsVelocity();
  		VectorNormalize( vecFlightDir );
  	}
  
  	SetAbsVelocity( vecFlightDir + vecDirToEnemy );
  
  	// measure how far the turn is, the wider the turn, the slow we'll go this time.
  	flDelta = DotProduct ( vecFlightDir, vecDirToEnemy );
  	
  	if ( flDelta < 0.5 )
  	{// hafta turn wide again. play sound
  		CPASAttenuationFilter filter( this );
  		EmitSound( filter, entindex(), "Hornet.Buzz" );
  	}
  
  	if ( flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED )
  	{// no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far.
  		flDelta = 0.25;
  	}
  
  	Vector vecVel = vecFlightDir + vecDirToEnemy;
  	VectorNormalize( vecVel );
  
  	if ( GetOwnerEntity() && (GetOwnerEntity()->GetFlags() & FL_NPC) )
  	{
  		// random pattern only applies to hornets fired by monsters, not players. 
  
  		vecVel.x += random->RandomFloat ( -0.10, 0.10 );// scramble the flight dir a bit.
  		vecVel.y += random->RandomFloat ( -0.10, 0.10 );
  		vecVel.z += random->RandomFloat ( -0.10, 0.10 );
  	}
  	
  	switch ( m_iHornetType )
  	{
  		case HORNET_TYPE_RED:
  			SetAbsVelocity( vecVel * ( m_flFlySpeed * flDelta ) );// scale the dir by the ( speed * width of turn )
  			SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.3 ) );
  			break;
  		default:
  			Assert( false );	//fall through if release
  		case HORNET_TYPE_ORANGE:
  			SetAbsVelocity( vecVel * m_flFlySpeed );// do not have to slow down to turn.
  			SetNextThink( gpGlobals->curtime + 0.1f );// fixed think time
  			break;
  	}
  
  	QAngle angNewAngles;
  	VectorAngles( GetAbsVelocity(), angNewAngles );
  	SetAbsAngles( angNewAngles );
  	
  	SetSolid( SOLID_BBOX );
  
  	// if hornet is close to the enemy, jet in a straight line for a half second.
  	// (only in the single player game)
  	if ( GetEnemy() != NULL && !g_pGameRules->IsMultiplayer() )
  	{
  		if ( flDelta >= 0.4 && ( GetAbsOrigin() - m_vecEnemyLKP ).Length() <= 300 )
  		{
  			CPVSFilter filter( GetAbsOrigin() );
  			te->Sprite( filter, 0.0,
  				&GetAbsOrigin(), // pos
  				iHornetPuff,	// model
  				0.2,				//size
  				128				// brightness
  			);
  
  			CPASAttenuationFilter filter2( this );
  			EmitSound( filter2, entindex(), "Hornet.Buzz" );
  			SetAbsVelocity( GetAbsVelocity() * 2 );
  			SetNextThink( gpGlobals->curtime + 1.0f );
  			// don't attack again
  			m_flStopAttack = gpGlobals->curtime;
  		}
  	}
  }
  