/*
* Hornet projectile
* https://github.com/ValveSoftware/halflife/blob/master/dlls/hornet.cpp
*
* Based on GeckonCZ's Cyberfranklin hornet code, derivated from hornet.cpp
*/

namespace HIVEHORNET
{

// Models
const string HORNET_MDL	= "models/hornet.mdl";
const string S_BUZZ1	= "hornet/ag_buzz1.wav";
const string S_BUZZ2	= "hornet/ag_buzz2.wav";
const string S_BUZZ3	= "hornet/ag_buzz3.wav";
const string S_HHIT1	= "hornet/ag_hornethit1.wav";
const string S_HHIT2	= "hornet/ag_hornethit2.wav";
const string S_HHIT3	= "hornet/ag_hornethit3.wav";

// from hornet.h
const uint HORNET_TYPE_RED	= 0;
const uint HORNET_TYPE_ORANGE	= 1;
const float HORNET_RED_SPEED	= 600.0f;
const float HORNET_ORANGE_SPEED	= 800.0f;
const float HORNET_BUZZ_VOLUME	= 0.8f;
const int DAMAGE_TRACK		= 10;
const int DAMAGE_DART		= 15;

class hivehornet : ScriptBaseMonsterEntity
{
	private float m_flStartTrack;
	private float m_flNextTrack;
	private float m_flStopAttack;
	private int m_iHornetType;
	private int m_iTouchStage;
	private float m_flFlySpeed;
	private int m_iMyClass;
	private EHandle m_hOwner;
	private int m_iClassSelection;

	// don't let hornets gib, ever.
	int TakeDamage( entvars_t@ pevInflictor, entvars_t@ pevAttacker, float flDamage, int bitsDamageType )
	{
		// filter these bits a little.
		bitsDamageType &= ~ ( DMG_ALWAYSGIB );
		bitsDamageType |= DMG_NEVERGIB;

		return BaseClass.TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
	}

	void Spawn()
	{
		pev.movetype		= MOVETYPE_BOUNCEMISSILE;
		pev.solid 		= SOLID_BBOX;
		pev.takedamage		= DAMAGE_NO;
		//pev.gravity		= 0.001;
		//pev.friction		= 1.0f;
		//pev.health		= 1; // weak!
		pev.flags 		|= FL_NOTARGET | FL_MONSTER;

		// hornets don't live as long in multiplayer
		m_flStopAttack = g_Engine.time + ( 5.0f );
		m_flStartTrack = g_Engine.time + 0.1f;

		self.m_flFieldOfView = 0.4;

		if ( Math.RandomLong( 1, 5 ) <= 2 )
		{
			m_iHornetType = HORNET_TYPE_RED;
			m_flFlySpeed = HORNET_RED_SPEED;
		}
		else
		{
			m_iHornetType = HORNET_TYPE_ORANGE;
			m_flFlySpeed = HORNET_ORANGE_SPEED;
		}

		g_EntityFuncs.SetModel( self, "models/mapobjects/hivehornet.mdl" );
		g_EntityFuncs.SetOrigin( self, self.pev.origin );
		//g_EntityFuncs.SetSize( self.pev, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) );

		SetTouch( TouchFunction( DieTouch ) );
		SetThink( ThinkFunction( StartTrack ) );

		pev.dmg = g_EngineFuncs.CVarGetFloat('sk_hornet_pdmg');

		pev.nextthink = g_Engine.time + 0.1f;
	}

	int IRelationship( CBaseEntity@ pTarget )
	{
		if ( pTarget.pev.modelindex == self.pev.modelindex )
		{
			return R_NO;
		}
		
		return self.IRelationship( pTarget );
	}

	int Classify( void )
	{
		CBaseEntity@ pOwner = g_EntityFuncs.Instance( self.pev.owner );

		if( pOwner is null )
			return CLASS_PLAYER_BIOWEAPON;

		switch( pOwner.Classify() )
		{
			case CLASS_PLAYER_ALLY:
				return CLASS_PLAYER_ALLY;
			case CLASS_HUMAN_PASSIVE:
				return CLASS_PLAYER_ALLY;
			case CLASS_TEAM1:
				return CLASS_TEAM1;
			case CLASS_TEAM2:
				return CLASS_TEAM2;
			case CLASS_TEAM3:
				return CLASS_TEAM3;
			case CLASS_TEAM4:
				return CLASS_TEAM4;
			case CLASS_ALIEN_MILITARY: // They're allies but return BIOWEAPON so other monsters don't attack hornets -Mikk
				return CLASS_ALIEN_MILITARY;
			case CLASS_HUMAN_MILITARY:
				return CLASS_HUMAN_MILITARY;
			case CLASS_XRACE_SHOCK:
				return CLASS_XRACE_SHOCK;
			default:
				return CLASS_PLAYER_ALLY;
		}
	}

	void StopTrack()
	{
		//self.SUB_Remove();
		g_EntityFuncs.Remove(self);
	}

	// StartTrack - starts a hornet out tracking its target
	void StartTrack()
	{
		IgniteTrail();

		SetTouch( TouchFunction( TrackTouch ) );
		SetThink( ThinkFunction( TrackTarget ) );

		pev.nextthink = g_Engine.time + 0.1f;
	}

	// StartDart - starts a hornet out just flying straight.
	void StartDart()
	{
		IgniteTrail();

		//pev.solid = SOLID_BBOX;

		SetTouch( TouchFunction( DartTouch ) );
		SetThink( ThinkFunction( StopTrack ) );

		pev.nextthink = g_Engine.time + 8.0f;
	}

	void IgniteTrail()
	{
		// trail
		NetworkMessage message( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY );
			message.WriteByte( TE_BEAMFOLLOW );
			message.WriteShort( self.entindex() ); // entity
			message.WriteShort( m_iHornetTrail ); // model
			message.WriteByte( 20 ); // life
			message.WriteByte( 2 ); // width

			switch ( m_iHornetType )
			{
			case HORNET_TYPE_RED:
				message.WriteByte( 179 ); // r
				message.WriteByte( 39 ); // g
				message.WriteByte( 14 ); // b
				break;
			case HORNET_TYPE_ORANGE:
				message.WriteByte( 255 ); // r
				message.WriteByte( 128 ); // g
				message.WriteByte( 0 ); // b
				break;
			}
		
			message.WriteByte( 102 ); // brightness
		message.End();
	}

	void DartThink()
	{
		if( g_Engine.time > m_flStopAttack )
		{
			SetTouch( null );
			SetThink( ThinkFunction( StopTrack ) );
			return;
		}

		pev.nextthink = g_Engine.time + 0.1f;
	}

	// Hornet is flying, gently tracking target
	void TrackTarget()
	{
		Vector	vecFlightDir;
		Vector	vecDirToEnemy;
		float	flDelta;

		//self.StudioFrameAdvance();

		pev.nextthink = g_Engine.time + 0.1f;

		if( g_Engine.time < m_flNextTrack || g_Engine.time < m_flStartTrack )
		{
			return;
		}

		if ( g_EngineFuncs.PointContents( self.pev.origin ) == CONTENTS_SKY )
		{
			g_EntityFuncs.Remove( self );
			return;
		}

		if ( g_Engine.time > m_flStopAttack )
		{
			SetTouch( null );
			SetThink( ThinkFunction( StopTrack ) );
			return;
		}

		if( !self.m_hEnemy.IsValid() )
		{
			self.Look( 1024 );
			self.m_hEnemy = self.BestVisibleEnemy();
		}

		if( m_iTouchStage == 3 && self.m_flFieldOfView != -1.0 )
		{
			self.m_flFieldOfView = -1.0;
		}

		CBaseEntity@ enemy = self.m_hEnemy.GetEntity();

		if ( self.FInViewCone( enemy ) && self.FVisible( enemy, true ) )
		{
			self.m_vecEnemyLKP = enemy.BodyTarget( pev.origin );
			self.m_flFieldOfView = -1.0;
		}
		else
		{
			self.m_vecEnemyLKP = self.m_vecEnemyLKP + pev.velocity * m_flFlySpeed * 0.1;
		}

		vecDirToEnemy = ( self.m_vecEnemyLKP - pev.origin ).Normalize();

		if (pev.velocity.Length() < 0.1)
			vecFlightDir = vecDirToEnemy;
		else 
			vecFlightDir = pev.velocity.Normalize();

		// 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
			switch ( Math.RandomLong( 0, 2 ) )
			{
				case 0:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_BUZZ1, HORNET_BUZZ_VOLUME, ATTN_NORM); break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_BUZZ2, HORNET_BUZZ_VOLUME, ATTN_NORM); break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_BUZZ3, HORNET_BUZZ_VOLUME, ATTN_NORM); break;
			}
		}

		if ( flDelta <= 0 )
		{
			// 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;
		}

		pev.velocity = ( vecFlightDir + vecDirToEnemy ).Normalize();

		//if ( pev.owner !is null && (pev.owner.vars.flags & FL_MONSTER) != 0 )
		{
			// random pattern applies to hornets fired by monsters
			pev.velocity.x += Math.RandomFloat( -0.10, 0.10 ); // scramble the flight dir a bit.
			pev.velocity.y += Math.RandomFloat( -0.10, 0.10 );
			pev.velocity.z += Math.RandomFloat( -0.10, 0.10 );
		}

		switch ( m_iHornetType )
		{
			case HORNET_TYPE_RED:
				pev.velocity = pev.velocity * ( m_flFlySpeed * flDelta );
				m_flNextTrack = g_Engine.time + 0.1;
				break;
			case HORNET_TYPE_ORANGE:
				pev.velocity = pev.velocity * ( m_flFlySpeed * flDelta );
				m_flNextTrack = g_Engine.time + 0.1;
				break;
		}

		pev.angles = Math.VecToAngles( pev.velocity );
		if( self.pev.solid != SOLID_BBOX )
		{
			pev.solid = SOLID_BBOX;
		}
	}

	// Tracking Hornet hit something
	void TrackTouch( CBaseEntity@ pOther )
	{
		if ( pOther is null || pOther.edict() is pev.owner || pOther.pev.modelindex == pev.modelindex || pOther.IsBSPModel() )
		{
			m_iTouchStage++;
			pev.solid = SOLID_NOT;
			return;
		}

		if ( IRelationship( pOther ) <= R_NO )
		{
			// hit something we don't want to hurt, so turn around.
			//pev.velocity = pev.velocity.Normalize();

			//pev.velocity.x *= -1;
			//pev.velocity.y *= -1;

			//pev.origin = pev.origin + pev.velocity * 4; // bounce the hornet off a bit.
			//pev.velocity = pev.velocity * m_flFlySpeed;

			m_iTouchStage++;
			pev.solid = SOLID_NOT;
			return;
		}

		DieTouch( pOther );
	}

	void DartTouch( CBaseEntity@ pOther )
	{
		DieTouch2( pOther );
	}

	void DieTouch( CBaseEntity@ pOther )
	{
		if( pOther is null || pOther.edict() is pev.owner || pOther.pev.modelindex == pev.modelindex )
		{
			//pev.solid = SOLID_NOT;
			return;
		}

		if ( pOther !is null && pOther.pev.takedamage != 0 )
		{
			entvars_t@ pevOwner;
			if( self.pev.owner !is null )
				@pevOwner = @self.pev.owner.vars;
			else
				@pevOwner = self.pev;

			// buzz when you plug someone
			switch( Math.RandomLong ( 0,2 ) )
			{
				case 0:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_HHIT1, 1, ATTN_NORM);	break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_HHIT2, 1, ATTN_NORM);	break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_HHIT3, 1, ATTN_NORM);	break;
			}

			// do the damage
			pOther.TakeDamage( pevOwner, pev.owner.vars, g_EngineFuncs.CVarGetFloat('sk_hornet_pdmg'), DMG_BULLET );
		}

		// GeckoN: modelindex is const, let's apply EF_NODRAW instead
		//pev.modelindex = 0;// so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid
		pev.effects |= EF_NODRAW;
		pev.solid = SOLID_NOT;

		SetThink( ThinkFunction( StopTrack ) );
		pev.nextthink = g_Engine.time + 1; // stick around long enough for the sound to finish!
	}

	void DieTouch2( CBaseEntity@ pOther )
	{
		if ( pOther !is null && pOther.pev.takedamage != 0 )
		{
			entvars_t@ pevOwner;
			if( self.pev.owner !is null )
				@pevOwner = @self.pev.owner.vars;
			else
				@pevOwner = self.pev;

			// buzz when you plug someone
			switch( Math.RandomLong ( 0,2 ) )
			{
				case 0:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_HHIT1, 1, ATTN_NORM);	break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_HHIT2, 1, ATTN_NORM);	break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, S_HHIT3, 1, ATTN_NORM);	break;
			}
			
			// do the damage
			pOther.TakeDamage( pevOwner, pev.owner.vars, g_EngineFuncs.CVarGetFloat('sk_hornet_pdmg') * 1.5, DMG_BULLET );
		}

		// GeckoN: modelindex is const, let's apply EF_NODRAW instead
		//pev.modelindex = 0;// so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid
		pev.effects |= EF_NODRAW;
		pev.solid = SOLID_NOT;

		SetThink( ThinkFunction( StopTrack ) );
		pev.nextthink = g_Engine.time + 1; // stick around long enough for the sound to finish!
	}
}

void Register()
{
	g_CustomEntityFuncs.RegisterCustomEntity( "HIVEHORNET::hivehornet", "hivehornet" );
}

}