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

namespace HIVEHORNETF2
{

// Models
const string HORNETF_MDL = "mapobjects/hivehornet.mdl";
const string SF_BUZZ1	= "hornet/ag_buzz1.wav";
const string SF_BUZZ2	= "hornet/ag_buzz2.wav";
const string SF_BUZZ3	= "hornet/ag_buzz3.wav";
const string SF_HHIT1	= "hornet/ag_hornethit1.wav";
const string SF_HHIT2	= "hornet/ag_hornethit2.wav";
const string SF_HHIT3	= "hornet/ag_hornethit3.wav";

// from hornet.h
const uint HORNET_TYPE_REDF	= 0;
const uint HORNET_TYPE_ORANGEF	= 1;
const float HORNET_RED_SPEEDF	= 600.0f;
const float HORNET_ORANGE_SPEEDF = 800.0f;
const float HORNET_BUZZ_VOLUMEF	= 0.8f;

class hivehornetf2 : ScriptBaseMonsterEntity
{
	private float m_flStopAttack;
	private float m_flStartTrack;
	private float m_flNextTrack;
	private int m_iHornetType;
	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_SLIDEBOX;
		pev.takedamage		= DAMAGE_NO;
		//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 );

		self.m_flFieldOfView = 0.4;

		if ( Math.RandomLong( 1, 5 ) <= 2 )
		{
			m_iHornetType = HORNET_TYPE_REDF;
			m_flFlySpeed = HORNET_RED_SPEEDF;
		}
		else
		{
			m_iHornetType = HORNET_TYPE_ORANGEF;
			m_flFlySpeed = HORNET_ORANGE_SPEEDF;
		}

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

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

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

		m_flStartTrack = g_Engine.time + 0.1f;
		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_BIOWEAPON;
			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()
	{
		IgniteTrail2();

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

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

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

		//pev.solid |= SOLID_BBOX;

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

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

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

			switch ( m_iHornetType )
			{
			case HORNET_TYPE_REDF:
				message2.WriteByte( 14 ); // r
				message2.WriteByte( 39 ); // g
				message2.WriteByte( 179 ); // b
				break;
			case HORNET_TYPE_ORANGEF:
				message2.WriteByte( 0 ); // r
				message2.WriteByte( 128 ); // g
				message2.WriteByte( 255 ); // b
				break;
			}
		
			message2.WriteByte( 102 ); // brightness
		message2.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;

		pev.nextthink = g_Engine.time + 0.1f;

		//self.StudioFrameAdvance();

		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();
		}

		auto 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, SF_BUZZ1, HORNET_BUZZ_VOLUMEF, ATTN_NORM); break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, SF_BUZZ2, HORNET_BUZZ_VOLUMEF, ATTN_NORM); break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, SF_BUZZ3, HORNET_BUZZ_VOLUMEF, 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_REDF:
				pev.velocity = pev.velocity * ( m_flFlySpeed * flDelta );
				m_flNextTrack = g_Engine.time + 0.1;
				break;
			case HORNET_TYPE_ORANGEF:
				pev.velocity = pev.velocity * ( m_flFlySpeed * flDelta );
				m_flNextTrack = g_Engine.time + 0.1;
				break;
		}

		pev.angles = Math.VecToAngles( pev.velocity );
		g_EntityFuncs.SetOrigin( self, self.pev.origin );
		//pev.solid = SOLID_SLIDEBOX;
	}

	// Tracking Hornet hit something
	void TrackTouch( CBaseEntity@ pOther )
	{
		//if ( pOther is null || pOther.edict() is pev.owner || pOther.pev.modelindex == pev.modelindex || !pOther.IsMonster() || ( pOther.IsMonster() || pOther.IsPlayer() ) && IRelationship( pOther ) <= R_NO )
		if ( pOther is null || pOther.edict() is pev.owner || pOther.pev.modelindex == pev.modelindex || pOther.IsBSPModel() )
		{
			// bumped into the guy that shot it.
			//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.velocity.z *= -1;

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

			return;
		}

		DieTouch( pOther );
	}

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

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

		if ( pOther !is null && pOther.pev.takedamage != 0.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, SF_HHIT1, 1, ATTN_NORM);	break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, SF_HHIT2, 1, ATTN_NORM);	break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, SF_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.edict() is pev.owner || pOther.pev.modelindex == pev.modelindex )
		{
			return;
		}

		if ( pOther !is null && pOther.pev.takedamage != 0.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, SF_HHIT1, 1, ATTN_NORM);	break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, SF_HHIT2, 1, ATTN_NORM);	break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, SF_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!
	}

	/*CBaseEntity@ BestVisibleEnemy()
	{
		CBaseEntity@ pReturn = self.BestVisibleEnemy();

		if( pReturn !is null and pReturn.IsAlive() and pReturn.edict() !is pev.owner and !pReturn.pev.FlagBitSet(FL_NOTARGET) 
		and pReturn.pev.classname != "playerhornet" and pReturn.pev.sequence != 0 and ( pReturn.pev.origin - self.pev.origin ).Length() >= 1024 )
			return g_EntityFuncs.Instance( self.pev.enemy );

		return @pReturn;

		CBaseEntity@ pReturn = null;
		
		while( (@pReturn = g_EntityFuncs.FindEntityInSphere(pReturn, self.pev.origin, 1024, "*", "classname")) !is null )
		{
			if( pReturn !is null and pReturn.IsAlive() and pReturn.edict() !is pev.owner and !pReturn.pev.FlagBitSet(FL_NOTARGET) and pReturn.pev.classname != "playerhornet" and pReturn.pev.classname != "hornet" 
			and pReturn.pev.sequence != 0 and ( pReturn.pev.origin - self.pev.origin ).Length() >= 1024 )
			{
				return pReturn;
			}
		}

		return null;
	}*/
}

void Register()
{
	g_CustomEntityFuncs.RegisterCustomEntity( "HIVEHORNETF2::hivehornetf2", "hivehornetf2" );
}

}