// Half-Life Hivehand (with faster recharge) -- Maybe with some... modifications I would like to add in the near future.

#include "sven_scripted_utils"

namespace SVEN_HORNETGUN
{

enum hgun_e
{
	HGUN_IDLE1 = 0,
	HGUN_FIDGETSWAY,
	HGUN_FIDGETSHAKE,
	HGUN_DOWN,
	HGUN_UP,
	HGUN_SHOOT
};

enum firemode_e
{
	FIREMODE_TRACK = 0,
	FIREMODE_FAST
};

// Models
const string P_MODEL		= "models/p_hgun.mdl";
const string V_MODEL		= "models/v_hgun.mdl";
const string W_MODEL		= "models/w_hgun.mdl";

const string PROJ_MDL		= "models/hornet.mdl";
const string PUKE_MDL		= "models/v_hgun_puke.mdl";

// Sprites
const string SPR_MUZZLE			= "sprites/muz4.spr";
const string SPR_BIGSPIT		= "sprites/hunger/redbspit.spr";
const string SPR_TINYSPIT		= "sprites/hunger/redtspit.spr";
const string SPR_EXPLODE		= "sprites/sven_weapons/bloodsplat2_alpha.spr";
const string SPR_EXPLODE_SPRAY	= "sprites/blood.spr";
const string SPR_EXPLODE_OVERLAY = "sprites/explode1.spr";

const string SPR_DIR		= "sven_weapons/";

// Sounds
array<string> HornetSoundEvents = { 
		"agrunt/ag_fire1.wav",
		"agrunt/ag_fire2.wav",
		"agrunt/ag_fire3.wav",
		"hornet/ag_buzz1.wav",
		"hornet/ag_buzz2.wav",
		"hornet/ag_buzz3.wav",
		"hornet/ag_hornethit1.wav",
		"hornet/ag_hornethit2.wav",
		"hornet/ag_hornethit3.wav",
		"weapons/hgun_puke.wav",
		"debris/bustflesh1.wav",
		"debris/bustflesh2.wav",
		"roach/rch_smash.wav"
};

// Set MAX_CARRY to 8 if you want vanilla values. 100 for Sven Co-op values.
const int MAX_CARRY 			= SVEN_MAX_CARRY_HORNET;
const int DEFAULT_GIVE 			= MAX_CARRY;
const int WEIGHT 				= 10;
uint SLOT						= 3;
uint POSITION					= 8;
string AMMO_TYPE 				= SVEN_AMMO_HORNET;

const int HORNET_TYPE_RED		= 0;
const int 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 float HORNET_DAMAGE		= g_EngineFuncs.CVarGetFloat( "sk_hornet_pdmg" );
const float HORNET_PUKE_DMG		= g_EngineFuncs.CVarGetFloat( "sk_plr_spore" );

float RECHARGE_SP 				= 0.5;
float RECHARGE_MP 				= 0.3;
float RECHARGE_SVEN 			= 0.6;

// Fast recharge
const CCVar@ g_SvenScriptedHornetRecharge = CCVar( "sven_hornet_mp", 1, "", ConCommandFlag::AdminOnly ); // as_command sven_hornet_mp 1. Default Value is 1. 1 = Faster recharge (Half-Life MP: 25th Anniversary). 0 = Default recharge (Half-Life SP). 2 = Slow recharge (Sven Co-op). WIP - Add Sven Co-op stuff.

class weapon_hornetgun_as : ScriptBasePlayerWeaponEntity, SvenScriptedWeaponUtils
{
	private CBasePlayer@ m_pPlayer
	{
		get const { return cast<CBasePlayer>(self.m_hPlayer.GetEntity()); }
		set       { self.m_hPlayer = EHandle(@value); }
	}
	
	private int m_iFirePhase;
	private float m_flRechargeTime;

	void Spawn()
	{
		self.Precache();
		g_EntityFuncs.SetModel( self, W_MODEL );
		self.m_iDefaultAmmo = DEFAULT_GIVE;
		m_iFirePhase = 0;

		self.FallInit();
	}

	void Precache()
	{
		self.PrecacheCustomModels();
		g_Game.PrecacheModel( P_MODEL );
		g_Game.PrecacheModel( V_MODEL );
		g_Game.PrecacheModel( W_MODEL );
		
		g_Game.PrecacheModel( PROJ_MDL );
		g_Game.PrecacheModel( PUKE_MDL );
		
		g_Game.PrecacheModel( SPR_MUZZLE );
		g_Game.PrecacheModel( SPR_TINYSPIT );
		g_Game.PrecacheModel( SPR_BIGSPIT );
		
		g_Game.PrecacheModel( SPR_EXPLODE );
		g_Game.PrecacheModel( SPR_EXPLODE_SPRAY );
		g_Game.PrecacheModel( SPR_EXPLODE_OVERLAY );
		
		g_Game.PrecacheOther( "proj_hornet_as" );
		g_Game.PrecacheOther( "proj_hornet_puke_as" );
		
		for( uint i = 0; i < HornetSoundEvents.length(); i++ )
		{
			g_SoundSystem.PrecacheSound( HornetSoundEvents[i] );
			g_Game.PrecacheGeneric( "sound/" + HornetSoundEvents[i] );
		}
		
		g_Game.PrecacheGeneric( "sprites/" + SPR_DIR + self.pev.classname + ".txt" );
	}

	bool GetItemInfo( ItemInfo& out info )
	{
		info.iMaxAmmo1		= MAX_CARRY;
		info.iMaxAmmo2		= -1;
		info.iMaxClip		= WEAPON_NOCLIP;
		info.iSlot			= SLOT;
		info.iPosition		= POSITION;	//5 - Custom RPG, 6 - Custom Tau, 7 - Custom Gluon, 8 - Custom Hornet. NOTE: Take notes from custom maps. Could cause some conflicting problems.
		info.iWeight		= WEIGHT;
		info.iFlags			= ITEM_FLAG_SELECTONEMPTY | ITEM_FLAG_NOAUTOSWITCHEMPTY | ITEM_FLAG_NOAUTORELOAD;
		info.iId 			= g_ItemRegistry.GetIdForName(self.pev.classname);

		return true;
	}

	bool AddToPlayer(CBasePlayer@ pPlayer)
	{
		if (!BaseClass.AddToPlayer(pPlayer))
			return false;

		pPlayer.m_rgAmmo( self.PrimaryAmmoIndex(), MAX_CARRY );
		
		NetworkMessage message(MSG_ONE, NetworkMessages::WeapPickup, pPlayer.edict());
			message.WriteLong(g_ItemRegistry.GetIdForName(self.pev.classname));
		message.End();

		return true;
	}

	bool Deploy()
	{
		switch ( g_SvenScriptedHornetRecharge.GetInt() )
		{
			case 0: m_flRechargeTime = g_Engine.time + RECHARGE_SP; break;
			case 1: m_flRechargeTime = g_Engine.time + RECHARGE_MP; break;
			case 2: m_flRechargeTime = g_Engine.time + RECHARGE_SVEN; break;
			default: m_flRechargeTime = g_Engine.time + RECHARGE_SP; break;
		}
		
		bool bResult = self.DefaultDeploy( self.GetV_Model( V_MODEL ), self.GetP_Model( P_MODEL ), HGUN_UP, "hive" );
		self.m_flTimeWeaponIdle = WeaponTimeBase() + 1.0;
		return bResult;
	}
	
	void Holster( int skiplocal /* = 0 */ )
	{
		self.m_fInReload = false;

		SetThink(null);

		self.SendWeaponAnim( HGUN_DOWN );

		if( m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType) <= 0 )
			m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType, 1);

		BaseClass.Holster(skiplocal);
	}

	void PrimaryAttack()
	{
		Reload();

		int ammo = m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType);
		
		if( ammo <= 0 )
			return;

		Math.MakeVectors( m_pPlayer.pev.v_angle );
		
		Vector vecSrc = m_pPlayer.GetGunPosition() + g_Engine.v_forward * 16 + g_Engine.v_right * 8 + g_Engine.v_up * -12;
		
		NetworkMessage HornetMuzzle( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, vecSrc );
			HornetMuzzle.WriteByte(TE_SPRITE);
			HornetMuzzle.WriteCoord(vecSrc.x);
			HornetMuzzle.WriteCoord(vecSrc.y);
			HornetMuzzle.WriteCoord(vecSrc.z);
			HornetMuzzle.WriteShort( g_EngineFuncs.ModelIndex(SPR_MUZZLE) );
			HornetMuzzle.WriteByte(4);
			HornetMuzzle.WriteByte(128);
		HornetMuzzle.End();

		CBaseEntity@ cbeHornet = g_EntityFuncs.Create( "proj_hornet_as", vecSrc, m_pPlayer.pev.v_angle, false, m_pPlayer.edict() );
		proj_hornet_as@ pHornet = cast<proj_hornet_as@>(CastToScriptClass(cbeHornet));
		
		pHornet.pev.velocity = g_Engine.v_forward * 300;
		
		@pHornet.pev.owner = m_pPlayer.edict(); //just to make sure you don't clip
		m_flRechargeTime = g_Engine.time + 0.5f;
		
		ammo--;
		m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType, ammo);

		m_pPlayer.m_iWeaponVolume = NORMAL_GUN_VOLUME;
		m_pPlayer.m_iWeaponFlash = DIM_GUN_FLASH;

		m_pPlayer.pev.punchangle.x = Math.RandomLong(0, 2);
		self.SendWeaponAnim( HGUN_SHOOT );

		switch( Math.RandomLong(0, 2) )
		{
			case 0: g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, HornetSoundEvents[0], 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
			case 1: g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, HornetSoundEvents[1], 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
			case 2: g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, HornetSoundEvents[2], 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
		}

		// player "shoot" animation
		m_pPlayer.SetAnimation( PLAYER_ATTACK1 );

		self.m_flNextPrimaryAttack = g_Engine.time + 0.25f;

		if( self.m_flNextPrimaryAttack < g_Engine.time )
			self.m_flNextPrimaryAttack = g_Engine.time + 0.25f;

		self.m_flTimeWeaponIdle = g_Engine.time + g_PlayerFuncs.SharedRandomFloat( m_pPlayer.random_seed, 3, 7 );
	}

	void SecondaryAttack()
	{
		Reload();

		int ammo = m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType);
		
		if( ammo <= 0 )
			return;

		//Wouldn't be a bad idea to completely predict these, since they fly so fast...
		Vector vecSrc;

		Math.MakeVectors( m_pPlayer.pev.v_angle );

		vecSrc = m_pPlayer.GetGunPosition() + g_Engine.v_forward * 16 + g_Engine.v_right * 8 + g_Engine.v_up * -12;

		NetworkMessage HornetMuzzle( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, vecSrc );
			HornetMuzzle.WriteByte(TE_SPRITE);
			HornetMuzzle.WriteCoord(vecSrc.x);
			HornetMuzzle.WriteCoord(vecSrc.y);
			HornetMuzzle.WriteCoord(vecSrc.z);
			HornetMuzzle.WriteShort( g_EngineFuncs.ModelIndex(SPR_MUZZLE) );
			HornetMuzzle.WriteByte(4);
			HornetMuzzle.WriteByte(128);
		HornetMuzzle.End();
		
		m_iFirePhase++;
		
		switch( m_iFirePhase )
		{
			case 1: vecSrc = vecSrc + g_Engine.v_up * 8; break;
			case 2: vecSrc = vecSrc + g_Engine.v_up * 8; vecSrc = vecSrc + g_Engine.v_right * 8; break;
			case 3: vecSrc = vecSrc + g_Engine.v_right * 8; break;
			case 4: vecSrc = vecSrc + g_Engine.v_up * -8; vecSrc = vecSrc + g_Engine.v_right * 8; break;
			case 5: vecSrc = vecSrc + g_Engine.v_up * -8; break;
			case 6: vecSrc = vecSrc + g_Engine.v_up * -8; vecSrc = vecSrc + g_Engine.v_right * -8; break;
			case 7: vecSrc = vecSrc + g_Engine.v_right * -8; break;
			case 8: vecSrc = vecSrc + g_Engine.v_up * 8; vecSrc = vecSrc + g_Engine.v_right * -8; m_iFirePhase = 0; break;
		}
		
		CBaseEntity@ cbeHornet = g_EntityFuncs.Create( "proj_hornet_as", vecSrc, m_pPlayer.pev.v_angle, false, m_pPlayer.edict() );
		proj_hornet_as@ pHornet = cast<proj_hornet_as@>(CastToScriptClass(cbeHornet));
		
		pHornet.pev.velocity = g_Engine.v_forward * 1200;
		pHornet.pev.angles = Math.VecToAngles( pHornet.pev.velocity );
		
		pHornet.SetThink( ThinkFunction( pHornet.StartDart ) );
		
		m_flRechargeTime = g_Engine.time + 0.5f;

		m_pPlayer.pev.punchangle.x = Math.RandomLong(0, 2);
		self.SendWeaponAnim( HGUN_SHOOT );

		switch( Math.RandomLong(0, 2) )
		{
			case 0: g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, HornetSoundEvents[0], 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
			case 1: g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, HornetSoundEvents[1], 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
			case 2: g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, HornetSoundEvents[2], 1.0, ATTN_NORM, 0, PITCH_NORM ); break;
		}

		ammo--;
		m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType, ammo);
		m_pPlayer.m_iWeaponVolume = NORMAL_GUN_VOLUME;
		m_pPlayer.m_iWeaponFlash = DIM_GUN_FLASH;

		// player "shoot" animation
		m_pPlayer.SetAnimation( PLAYER_ATTACK1 );

		self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = self.m_flNextTertiaryAttack = g_Engine.time + 0.1f;
		self.m_flTimeWeaponIdle = g_Engine.time + g_PlayerFuncs.SharedRandomFloat( m_pPlayer.random_seed, 3, 7 );
	}

	void TertiaryAttack()
	{
		Reload();
		
		int ammo = m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType);
		
		if( ammo < 10 )
			return;
		
		Math.MakeVectors( m_pPlayer.pev.v_angle );

		Vector vecSrc = m_pPlayer.GetGunPosition() + g_Engine.v_forward * 16 + g_Engine.v_right * 8 + g_Engine.v_up * -12;

		NetworkMessage HornetMuzzle( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, vecSrc );
			HornetMuzzle.WriteByte(TE_SPRITE);
			HornetMuzzle.WriteCoord(vecSrc.x);
			HornetMuzzle.WriteCoord(vecSrc.y);
			HornetMuzzle.WriteCoord(vecSrc.z);
			HornetMuzzle.WriteShort( g_EngineFuncs.ModelIndex(SPR_MUZZLE) );
			HornetMuzzle.WriteByte(4);
			HornetMuzzle.WriteByte(128);
		HornetMuzzle.End();
		
		CBaseEntity @cbePuke = g_EntityFuncs.Create( "proj_hornet_puke_as", vecSrc, m_pPlayer.pev.v_angle, false, m_pPlayer.edict() );
		CHornetPuke@ pPuke = cast<CHornetPuke@>(CastToScriptClass(cbePuke));
		
		pPuke.pev.velocity = g_Engine.v_forward * 1200;
		pPuke.pev.angles = Math.VecToAngles( pPuke.pev.velocity );
		@pPuke.pev.owner = m_pPlayer.edict(); //just to make sure you don't clip
		m_flRechargeTime = g_Engine.time + 1.5f;

		m_pPlayer.pev.punchangle.x = -3;
		self.SendWeaponAnim( HGUN_SHOOT );	// There's no tertiary attack animation, so use standard firing animation as a failsafe measure. Just have the punch angles for now.
		
		g_SoundSystem.EmitSoundDyn( m_pPlayer.edict(), CHAN_WEAPON, HornetSoundEvents[9], 1.0, ATTN_NORM, 0, 95 + Math.RandomLong(0, 10) );
		
		ammo -= 10;
		
		m_pPlayer.m_rgAmmo( self.m_iPrimaryAmmoType, ammo );
		m_pPlayer.m_iWeaponVolume = NORMAL_GUN_VOLUME;
		m_pPlayer.m_iWeaponFlash = DIM_GUN_FLASH;

		// player "shoot" animation
		m_pPlayer.SetAnimation( PLAYER_ATTACK1 );
		
		self.m_flNextPrimaryAttack = self.m_flNextSecondaryAttack = self.m_flNextTertiaryAttack = g_Engine.time + 0.75f;
		self.m_flTimeWeaponIdle = g_Engine.time + g_PlayerFuncs.SharedRandomFloat( m_pPlayer.random_seed, 10, 15 );
	}
	
	void Reload()
	{
		int ammoCount = m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType);

		if( m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType) >= MAX_CARRY )
			return;
			
		while ( ammoCount < MAX_CARRY && m_flRechargeTime < g_Engine.time )
		{
			++ammoCount;
			
			switch ( g_SvenScriptedHornetRecharge.GetInt() )
			{
				case 0: m_flRechargeTime += RECHARGE_SP; break;
				case 1: m_flRechargeTime += RECHARGE_MP; break;
				case 2: m_flRechargeTime += RECHARGE_SVEN; break;
				default: m_flRechargeTime += RECHARGE_SP; break;
			}
		}

		m_pPlayer.m_rgAmmo(self.m_iPrimaryAmmoType, ammoCount);
	}
	
	void WeaponIdle()
	{
		Reload();

		if( self.m_flTimeWeaponIdle > g_Engine.time )
			return;

		int iAnim;
		float flRand = g_PlayerFuncs.SharedRandomFloat( m_pPlayer.random_seed, 0, 1 );
		if( flRand <= 0.75f )
		{
			iAnim = HGUN_IDLE1;
			self.m_flTimeWeaponIdle = g_Engine.time + 30.0f / 16 * (2);
		}
		else if( flRand <= 0.875f )
		{
			iAnim = HGUN_FIDGETSWAY;
			self.m_flTimeWeaponIdle = g_Engine.time + 40.0f / 16.0f;
		}
		else
		{
			iAnim = HGUN_FIDGETSHAKE;
			self.m_flTimeWeaponIdle = g_Engine.time + 35.0f / 16.0f;
		}

		self.SendWeaponAnim( iAnim );
	}
}

class proj_hornet_as : ScriptBaseMonsterEntity //Player Custom Hornets
{
	private float m_flStopAttack, m_flFlySpeed;
	private int	m_iHornetType, m_iHornetTrail, m_iHornetPuff;
	
	//=========================================================
	// 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()
	{
		Precache();

		pev.movetype	= MOVETYPE_FLY;
		pev.solid		= SOLID_BBOX;
		pev.takedamage  = DAMAGE_YES;
		pev.flags		|= FL_MONSTER;
		pev.health		= 1;// weak!

		// hornets don't live as long in multiplayer
		m_flStopAttack = g_Engine.time + 3.5;
		//m_flStopAttack = g_Engine.time + 5.0;

		self.m_flFieldOfView = 0.9; // +- 25 degrees

		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, PROJ_MDL );
		g_EntityFuncs.SetSize( self.pev, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) );

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

		edict_t@ pSoundEnt = pev.owner;
		if ( pSoundEnt is null )
			@pSoundEnt = self.edict();

		pev.dmg = HORNET_DAMAGE; //It will only check Player damage as it's only used by players anyway.
		
		pev.nextthink = g_Engine.time + 0.1;
		self.ResetSequenceInfo();
	}

	void Precache()
	{
		g_Game.PrecacheModel( PROJ_MDL );

		for( uint i = 0; i < HornetSoundEvents.length(); i++ )
		{
			g_SoundSystem.PrecacheSound( HornetSoundEvents[i] );
			g_Game.PrecacheGeneric( "sound/" + HornetSoundEvents[i] );
		}

		m_iHornetPuff = g_Game.PrecacheModel( "sprites/muz1.spr" );
		m_iHornetTrail = g_Game.PrecacheModel( "sprites/laserbeam.spr" );
	}	

	//=========================================================
	// hornets will never get mad at each other, no matter who the owner is.
	//=========================================================
	int IRelationship( CBaseEntity@ pTarget )
	{
		if ( pTarget.pev.modelindex == pev.modelindex )
		{
			return R_NO;
		}
		
		return self.IRelationship( pTarget );
	}
	
	// ID's Hornet as their owner
	int Classify( void )
	{
		if ( pev.owner && pev.owner.vars.flags & FL_CLIENT )
		{
			return CLASS_PLAYER_BIOWEAPON;
		}

		return	CLASS_ALIEN_BIOWEAPON;
	}
	
	CBaseEntity@ FindClosestEnemy( float fRadius )
	{
		CBaseEntity@ ent = null;
		CBaseEntity@ enemy = null;
		float iNearest = fRadius;

		do
		{
			@ent = g_EntityFuncs.FindEntityInSphere( ent, self.pev.origin, fRadius, "*", "classname" ); 
			
			if ( ent is null || !ent.IsAlive() || ( !ent.IsPlayer() && !ent.IsMonster() && ent.Classify() == CLASS_PLAYER_BIOWEAPON || ent.Classify() == CLASS_ALIEN_BIOWEAPON ) )
				continue;
	
			if ( ent.pev.classname == "squadmaker" )
				continue;
	
			if ( ent.pev.classname == "monstermaker" )
				continue;
				
			if ( ent.pev.classname == "hornet" )	// Don't attack other hornets.
				continue;
		
			if ( ent.pev.classname == "franklinhornet" )
				continue;

			if ( ent.pev.classname == "proj_hornet_as" )
				continue;
				
			if ( ent.pev.classname == "monster_turret" || ent.pev.classname == "monster_miniturret" )
			{
				if ( ent.pev.sequence == 0 )
					continue;
			}
			
			if ( ent.entindex() == self.entindex() )
				continue;

			if ( ent.edict() is pev.owner )
				continue;

			int rel = self.IRelationship( ent );
			
			if ( rel == R_AL || rel == R_NO )
				continue;

			float iDist = ( ent.pev.origin - self.pev.origin ).Length();
			if ( iDist < iNearest )
			{
				iNearest = iDist;
				@enemy = ent;
			}
		}
		
		while ( ent !is null );
		
		if ( enemy !is null )	
			g_Game.AlertMessage( at_console, "new enemy %1, relationship %2\n", enemy.GetClassname(), self.IRelationship(enemy) );
		
		return enemy;
	}
	
	// StartTrack - starts a hornet out tracking its target
	void StartTrack()
	{
		IgniteTrail();

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

		pev.nextthink = g_Engine.time + 0.1;
	}

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

		SetTouch( TouchFunction( DartTouch ) );
		SetThink( ThinkFunction( StopTrack ) );
		
		pev.nextthink = g_Engine.time + 4;
	}
	
	void IgniteTrail()
	{
		// trail
		NetworkMessage message( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY );
			message.WriteByte( TE_BEAMFOLLOW );
			message.WriteShort( self.entindex() );
			message.WriteShort( m_iHornetTrail );
			message.WriteByte( 10 );
			message.WriteByte( 2 );
			switch ( m_iHornetType )
			{
			case HORNET_TYPE_RED:
				message.WriteByte( 179 );	// r, was 179
				message.WriteByte( 39 );	// g, was 39
				message.WriteByte( 14 );	// b, was 14
				break;
			case HORNET_TYPE_ORANGE:
				message.WriteByte( 255 );		// r, was 255	
				message.WriteByte( 128 );	// g, was 128
				message.WriteByte( 0 );	// b, was 0
				break;
			}
			message.WriteByte( 128 );
		message.End();
	}
	
	// Hornet is flying, gently tracking target
	void TrackTarget()
	{
		Vector	vecFlightDir;
		Vector	vecDirToEnemy;
		float	flDelta;

		self.StudioFrameAdvance();

		if ( g_Engine.time > m_flStopAttack )
		{
			SetTouch( null );
			g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[12], 1.0, ATTN_NORM, 0, 95 + Math.RandomLong(0, 5) );
			SetThink( ThinkFunction( StopTrack ) );
			pev.nextthink = g_Engine.time + 0.1;
			return;
		}

		if ( !self.m_hEnemy.IsValid() )
		{
			self.m_hEnemy = FindClosestEnemy( 512 );
		}
		
		if ( self.m_hEnemy.IsValid() && self.FVisible( self.m_hEnemy.GetEntity(), true ))
		{
			self.m_vecEnemyLKP = self.m_hEnemy.GetEntity().BodyTarget( pev.origin );
		}
		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, HornetSoundEvents[3], HORNET_BUZZ_VOLUME, ATTN_NORM);	break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[4], HORNET_BUZZ_VOLUME, ATTN_NORM);	break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[5], HORNET_BUZZ_VOLUME, ATTN_NORM);	break;
			}
		}

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

		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 );// scale the dir by the ( speed * width of turn )
				pev.nextthink = g_Engine.time + Math.RandomFloat( 0.1, 0.3 );
				break;
			case HORNET_TYPE_ORANGE:
				pev.velocity = pev.velocity * m_flFlySpeed;// do not have to slow down to turn.
				pev.nextthink = g_Engine.time + 0.1;// fixed think time
				break;
		}

		pev.angles = Math.VecToAngles (pev.velocity);

		pev.solid = SOLID_BBOX;

		// if hornet is close to the enemy, jet in a straight line for a half second.
		if ( self.m_hEnemy.IsValid() )
		{
			if ( flDelta >= 0.4f && ( pev.origin - self.m_vecEnemyLKP ).Length() <= 300.0f )
			{
				NetworkMessage message( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
					message.WriteByte( TE_SPRITE );
					message.WriteCoord( pev.origin.x );	// pos
					message.WriteCoord( pev.origin.y );
					message.WriteCoord( pev.origin.z );
					message.WriteShort( m_iHornetPuff );	// model
					//message.WriteByte( 0 );			// life * 10
					message.WriteByte( 2 );				// size * 10
					message.WriteByte( 128 );			// brightness
				message.End();

				switch ( Math.RandomLong( 0, 2 ) )
				{
					case 0:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[3], HORNET_BUZZ_VOLUME, ATTN_NORM);	break;
					case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[4], HORNET_BUZZ_VOLUME, ATTN_NORM);	break;
					case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[5], HORNET_BUZZ_VOLUME, ATTN_NORM);	break;
				}
				pev.velocity = pev.velocity * 2;
				pev.nextthink = g_Engine.time + 1.0;
				// don't attack again
				m_flStopAttack = g_Engine.time;
			}
		}
	}
	
	// Tracking Hornet hit something
	void TrackTouch( CBaseEntity@ pOther )
	{
		if ( pOther.edict() is pev.owner || pOther.pev.modelindex == pev.modelindex || pOther.pev.ClassNameIs("proj_hornet_puke_as") || pOther.pev.ClassNameIs("proj_hornet_as") || pOther.pev.ClassNameIs("hornet") || pOther.pev.ClassNameIs("franklinhornet") )
		{
			// bumped into the guy that shot it.
			pev.solid = SOLID_NOT;
			return;
		}

		if ( IRelationship( pOther ) <= R_NO || ( pOther.IsBSPModel() && !pOther.IsBreakable() ) )
		{
			// 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;

			return;
		}

		DieTouch( pOther );
	}
	
	void DartTouch( CBaseEntity@ pOther )
	{
		if ( pOther.edict() is pev.owner || pOther.pev.modelindex == pev.modelindex || pOther.pev.ClassNameIs("proj_hornet_puke_as") || pOther.pev.ClassNameIs("proj_hornet_as") || pOther.pev.ClassNameIs("hornet") || pOther.pev.ClassNameIs("franklinhornet") )
		{
			// bumped into the guy that shot it.
			pev.solid = SOLID_NOT;
			return;
		}
		
		DieTouch( pOther );
	}
	
	void DieTouch( CBaseEntity@ pOther )
	{
		if ( pOther !is null  && pOther.pev.takedamage != 0.0f )
		{
			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, HornetSoundEvents[6], 1, ATTN_NORM);	break;
				case 1:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[7], 1, ATTN_NORM);	break;
				case 2:	g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[8], 1, ATTN_NORM);	break;
			}
				
			// do the damage
			pOther.TakeDamage( pev, pev.owner.vars, pev.dmg, DMG_BULLET );
		}
		else
		{
			g_SoundSystem.EmitSoundDyn( self.edict(), CHAN_VOICE, HornetSoundEvents[12], 1.0, ATTN_NORM, 0, 95 + Math.RandomLong(0, 5) );
		}
		// 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!
	}
}

class CHornetPuke : ScriptBaseEntity
{
	private int m_iSpitSprite, m_iTrail, m_iExplode, m_iExplodeSpray, m_iExplodeOverlay;
	private float m_flIgniteTime;
	
	void Spawn()
	{
		Precache();
		
		pev.movetype = MOVETYPE_BOUNCE;
		pev.solid = SOLID_BBOX;
		
		pev.sequence = 0;
		pev.animtime = g_Engine.time;
		pev.framerate = 1.0;

		g_EntityFuncs.SetModel( self, PUKE_MDL );
		g_EntityFuncs.SetSize( self.pev, g_vecZero, g_vecZero );
		g_EntityFuncs.SetOrigin( self, self.pev.origin );
		
		Math.MakeVectors(pev.angles);
		
		m_iExplode = g_EngineFuncs.ModelIndex( SPR_EXPLODE );
		m_iExplodeSpray = g_EngineFuncs.ModelIndex( SPR_EXPLODE_SPRAY );
		m_iSpitSprite = m_iTrail = g_EngineFuncs.ModelIndex( SPR_TINYSPIT );
		m_iExplodeOverlay = g_EngineFuncs.ModelIndex( SPR_EXPLODE_OVERLAY );
		
		SetTouch( TouchFunction( this.PukeTouch ) );
		SetThink( ThinkFunction( this.PukeFly ) );
		
		pev.scale = 1.5;
		pev.gravity = 1.25;
		pev.friction = 0.3;
		
		pev.dmg = HORNET_PUKE_DMG - 25.0;
		
		m_flIgniteTime = g_Engine.time;
		
		pev.nextthink = g_Engine.time + 0.03;
	}
	
	void Precache()
	{
		m_iExplode = g_Game.PrecacheModel( SPR_EXPLODE );
		m_iExplodeSpray = g_Game.PrecacheModel( SPR_EXPLODE_SPRAY );
		m_iSpitSprite = m_iTrail = g_Game.PrecacheModel( SPR_TINYSPIT );
		m_iExplodeOverlay = g_Game.PrecacheModel( SPR_EXPLODE_OVERLAY );
		
		for( uint i = 0; i < HornetSoundEvents.length(); i++ )
		{
			g_SoundSystem.PrecacheSound( HornetSoundEvents[i] );
			g_Game.PrecacheGeneric( "sound/" + HornetSoundEvents[i] );
		}
	}
	
	void PukeFly()
	{
		const float flDelay = 5.0;
		
		GetSoundEntInstance().InsertSound( bits_SOUND_DANGER, self.pev.origin, NORMAL_EXPLOSION_VOLUME, 0.3f, self ); 
		
		if ( g_Engine.time <= m_flIgniteTime + flDelay )
		{
			Vector velocity = pev.velocity.Normalize();

			NetworkMessage PukeFlyTrail( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
				PukeFlyTrail.WriteByte( TE_SPRITE_SPRAY );
				PukeFlyTrail.WriteCoord(pev.origin.x);
				PukeFlyTrail.WriteCoord(pev.origin.y);
				PukeFlyTrail.WriteCoord(pev.origin.z);
				PukeFlyTrail.WriteCoord(velocity.x);
				PukeFlyTrail.WriteCoord(velocity.y);
				PukeFlyTrail.WriteCoord(velocity.z);
				PukeFlyTrail.WriteShort( m_iTrail );
				PukeFlyTrail.WriteByte( 2 );
				PukeFlyTrail.WriteByte( 20 );
				PukeFlyTrail.WriteByte( 80 );
			PukeFlyTrail.End();
		}
		else
		{
			SetTouch( TouchFunction( this.PukeTouch ) );
		}
		
		self.pev.angles = Math.VecToAngles( self.pev.velocity );
		self.pev.nextthink = g_Engine.time + 0.03;
	}

	void PukeTouch( CBaseEntity@ pOther )
	{
		if ( g_EngineFuncs.PointContents( self.pev.origin ) == CONTENTS_SKY )
		{
			g_EntityFuncs.Remove( self );
			return;
		}
		
		TraceResult tr;
		Vector vecSpot = self.pev.origin - self.pev.velocity.Normalize() * 32;
		Vector vecEnd = self.pev.origin + self.pev.velocity.Normalize() * 64;
		
		g_Utility.TraceLine( vecSpot, vecEnd, ignore_monsters, self.edict(), tr );
	
		CBaseEntity@ pEntity = g_EntityFuncs.Instance( tr.pHit );
		
		g_Utility.DecalTrace( tr, DECAL_SPORESPLAT1 + Math.RandomLong( 0, 2 ) );
		
		tr = g_Utility.GetGlobalTrace();
		
		// Pull out of the wall a bit
		if( tr.flFraction != 1.0 )
			pev.origin = tr.vecEndPos + (tr.vecPlaneNormal * 24.0);

		NetworkMessage PukeSpray( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			PukeSpray.WriteByte( TE_SPRITE_SPRAY );
			PukeSpray.WriteCoord(pev.origin.x);
			PukeSpray.WriteCoord(pev.origin.y);
			PukeSpray.WriteCoord(pev.origin.z);
			PukeSpray.WriteCoord(tr.vecPlaneNormal.x);
			PukeSpray.WriteCoord(tr.vecPlaneNormal.y);
			PukeSpray.WriteCoord(tr.vecPlaneNormal.z);
			PukeSpray.WriteShort( m_iSpitSprite );
			PukeSpray.WriteByte(100);
			PukeSpray.WriteByte(40);
			PukeSpray.WriteByte(180);
		PukeSpray.End();
		
		NetworkMessage PukeExplode( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			PukeExplode.WriteByte(TE_BLOODSPRITE);
			PukeExplode.WriteCoord(pev.origin.x);
			PukeExplode.WriteCoord(pev.origin.y);
			PukeExplode.WriteCoord(pev.origin.z);
			PukeExplode.WriteShort(m_iExplode);
			PukeExplode.WriteShort(m_iExplodeSpray);
			PukeExplode.WriteByte(100);
			PukeExplode.WriteByte(25);
		PukeExplode.End();
	
		NetworkMessage PukeExplodeOverlay( MSG_BROADCAST, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			PukeExplodeOverlay.WriteByte(TE_EXPLOSION);
			PukeExplodeOverlay.WriteCoord(pev.origin.x);
			PukeExplodeOverlay.WriteCoord(pev.origin.y);
			PukeExplodeOverlay.WriteCoord(pev.origin.z);
			PukeExplodeOverlay.WriteShort( m_iExplodeOverlay );
			PukeExplodeOverlay.WriteByte( int( ( pev.dmg - 25 ) * 0.50f) );
			PukeExplodeOverlay.WriteByte(25);
			PukeExplodeOverlay.WriteByte( TE_EXPLFLAG_NODLIGHTS|TE_EXPLFLAG_NOSOUND|TE_EXPLFLAG_NOPARTICLES );
		PukeExplodeOverlay.End();
	
		NetworkMessage PukeTrail( MSG_PVS, NetworkMessages::SVC_TEMPENTITY, pev.origin );
			PukeTrail.WriteByte( TE_SPRITE_SPRAY );
			PukeTrail.WriteCoord(pev.origin.x);
			PukeTrail.WriteCoord(pev.origin.y);
			PukeTrail.WriteCoord(pev.origin.z);
			PukeTrail.WriteCoord(Math.RandomFloat(-1, 1));
			PukeTrail.WriteCoord(1);
			PukeTrail.WriteCoord(Math.RandomFloat(-1, 1));
			PukeTrail.WriteShort( m_iTrail );
			PukeTrail.WriteByte(2);
			PukeTrail.WriteByte(20);
			PukeTrail.WriteByte(80);
		PukeTrail.End();
		
		entvars_t@ pevOwner = self.pev.owner.vars;
		g_WeaponFuncs.RadiusDamage( self.pev.origin, self.pev, pevOwner, self.pev.dmg, 200, CLASS_PLAYER, DMG_BLAST | DMG_ACID ); // Do not damage players.
		
		switch ( Math.RandomLong( 0, 1 ) )
		{
			case 0: g_SoundSystem.EmitSound( self.edict(), CHAN_AUTO, HornetSoundEvents[10], 1.0, ATTN_NORM ); break;
			case 1: g_SoundSystem.EmitSound( self.edict(), CHAN_AUTO, HornetSoundEvents[11], 1.0, ATTN_NORM ); break;
		}
		
		array<CBaseEntity@> cbeHornet(10);
		array<proj_hornet_as@> pHornet(10);
		array<Vector> vecHornets(10);
		array<Vector> vecAngles(10);
		array<Vector> vecScatter(10);
		array<int> m_iScatterX(10);
		array<int> m_iScatterY(10);
		array<int> m_iScatterZ(10);
		
		for ( int i = 0; i < 10; ++i )
		{
			vecHornets[0] = Vector( -100, 0, 100 );
			vecHornets[1] = Vector( 100, 0, 100 );
			vecHornets[2] = Vector( -100, 0, 150 );
			vecHornets[4] = Vector( 100, 0, 150 );
			vecHornets[5] = Vector( -100, 0, 100 );
			vecHornets[6] = Vector( 100, 0, 100 );
			vecHornets[7] = Vector( -100, 0, 200 );
			vecHornets[8] = Vector( 100, 0, 150 );
			vecHornets[9] = Vector( -100, 0, 200 );
			
			vecAngles[i] = Math.VecToAngles(vecHornets[i]);
			
			m_iScatterX[0] = Math.RandomLong(-8,8);
			m_iScatterX[1] = Math.RandomLong(-20,20);
			m_iScatterX[2] = Math.RandomLong(-16,16);
			m_iScatterX[3] = Math.RandomLong(-32,32);
			m_iScatterX[4] = Math.RandomLong(-36,36);
			m_iScatterX[5] = Math.RandomLong(-128,128);
			m_iScatterX[6] = Math.RandomLong(-36,36);
			m_iScatterX[7] = Math.RandomLong(-96,96);
			m_iScatterX[8] = Math.RandomLong(-48,48);
			m_iScatterX[9] = Math.RandomLong(-64,64);
			
			m_iScatterY[0] = Math.RandomLong(-36,36);
			m_iScatterY[1] = Math.RandomLong(-32,32);
			m_iScatterY[2] = Math.RandomLong(-48,48);
			m_iScatterY[3] = Math.RandomLong(-96,96);
			m_iScatterY[4] = Math.RandomLong(-128,128);
			m_iScatterY[5] = Math.RandomLong(-64,64);
			m_iScatterY[6] = Math.RandomLong(-20,20);
			m_iScatterY[7] = Math.RandomLong(-8,8);
			m_iScatterY[8] = Math.RandomLong(-16,16);
			m_iScatterY[9] = Math.RandomLong(-72,72);
			
			m_iScatterZ[0] = Math.RandomLong(4,16);
			m_iScatterZ[1] = Math.RandomLong(2,20);
			m_iScatterZ[2] = Math.RandomLong(24,96);
			m_iScatterZ[3] = Math.RandomLong(15,75);
			m_iScatterZ[4] = Math.RandomLong(8,24);
			m_iScatterZ[5] = Math.RandomLong(16,32);
			m_iScatterZ[6] = Math.RandomLong(32,64);
			m_iScatterZ[7] = Math.RandomLong(20,80);
			m_iScatterZ[8] = Math.RandomLong(24,48);
			m_iScatterZ[9] = Math.RandomLong(10,96);
			
			vecScatter[i] = Vector( m_iScatterX[i], m_iScatterY[i], m_iScatterZ[i] );
			
			@cbeHornet[i] = g_EntityFuncs.Create( "proj_hornet_as", pev.origin + vecScatter[i], vecAngles[i], false, self.pev.owner );
			@pHornet[i] = cast<proj_hornet_as@>(CastToScriptClass(cbeHornet[i]));
			pHornet[i].pev.angles = vecAngles[i];
			pHornet[i].pev.velocity = g_Engine.v_up * pHornet[i].pev.angles;
		}

		GetSoundEntInstance().InsertSound( bits_SOUND_COMBAT, pev.origin, NORMAL_EXPLOSION_VOLUME, 3, self );
		
		self.pev.effects |= EF_NODRAW;
		self.pev.velocity = g_vecZero;
		
		if( pOther.pev.takedamage != DAMAGE_NO )
		{
			g_WeaponFuncs.ClearMultiDamage();
			pOther.TraceAttack( pevOwner, self.pev.dmg / 2, g_Engine.v_forward, tr, DMG_CLUB | DMG_ALWAYSGIB ); //Direct damage
			g_WeaponFuncs.ApplyMultiDamage( self.pev, pevOwner);
		}
		
		g_EntityFuncs.Remove( self );
	}
	
	void Killed( entvars_t@ pevAttacker, int iGib )
	{
		entvars_t@ pevOwner = self.pev.owner.vars;
		
		g_EntityFuncs.Remove( self );
	}
	
	void SUB_Remove()
	{
		self.SUB_Remove();
	}
}

string GetName()
{
	return "weapon_hornetgun_as";
}

void Register()
{
	if ( !g_CustomEntityFuncs.IsCustomEntity( GetName() ) )
	{
		g_CustomEntityFuncs.RegisterCustomEntity( "SVEN_HORNETGUN::proj_hornet_as", "proj_hornet_as" );
		g_CustomEntityFuncs.RegisterCustomEntity( "SVEN_HORNETGUN::CHornetPuke", "proj_hornet_puke_as" );
		g_CustomEntityFuncs.RegisterCustomEntity( "SVEN_HORNETGUN::weapon_hornetgun_as", GetName() );
		g_ItemRegistry.RegisterWeapon( GetName(), SPR_DIR, "hornets" );
	}
}

} //namespace END