/*
	Tracking Drone
	Author: Ben Retan
	Description: A piece of equipment that tracks and highlights enemy players
*/

#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
#include maps\mp\gametypes\_hostmigration;

STUNNED_TIME = 10.0;
Z_OFFSET = ( 0, 0, 90 );

BALL_DRONE_SPAWN_STAND_UP_OFFSET		= 80;
BALL_DRONE_SPAWN_CROUCH_UP_OFFSET		= 65;
BALL_DRONE_SPAWN_PRONE_UP_OFFSET		= 37;
BALL_DRONE_SPAWN_FORWARD_OFFSET			= 50;
BALL_DRONE_SPAWN_SIDE_OFFSET			= 0;

BALL_DRONE_STAND_UP_OFFSET		= 65;
BALL_DRONE_CROUCH_UP_OFFSET		= 50;
BALL_DRONE_PRONE_UP_OFFSET		= 22;
BALL_DRONE_FORWARD_OFFSET		= 65;
BALL_DRONE_SIDE_OFFSET			= 0;

HORDE_BALL_DRONE_STAND_UP_OFFSET		= 105;
HORDE_BALL_DRONE_CROUCH_UP_OFFSET		= 75;
HORDE_BALL_DRONE_PRONE_UP_OFFSET		= 45;
HORDE_BALL_DRONE_FORWARD_OFFSET			= 0;
HORDE_BALL_DRONE_SIDE_OFFSET			= 30;

watchTrackingDroneUsage()
{
	self endon( "spawned_player" );
	self endon( "disconnect" );
	self endon( "death" );
	self endon( "faux_spawn" );
	
	if(!IsDefined(level.trackingDroneSettings))
	{
		trackingDroneInit();
	}

	while ( 1 )
	{
		self waittill( "grenade_fire", grenade, weapname );
		
		shortWeaponName = maps\mp\_utility::strip_suffix( weapname, "_lefthand" );
		
		if ( shortWeaponName == "tracking_drone_mp" )
		{
			grenade thread destroy_tracking_drone_in_water();
			
			wait(0.5);
			
			if ( !IsRemovedEntity( grenade ) && IsDefined( grenade ) )
			{
				self.trackingDroneStartPosition = grenade.origin;
				self.trackingDroneStartAngles = grenade.angles;
				
				grenade deleteTrackingDrone();
				
				if ( !prevent_tracking_drone_in_water( self.trackingDroneStartPosition ) )
				{
					tryUseTrackingDrone( weapname );
				}
			}
		}
	}	
}

trackingDroneInit()
{	
	level.trackingDroneMaxPerPlayer = 1;
	
	level.trackingDroneSettings = SpawnStruct();
	level.trackingDroneSettings.timeOut =				20.0;	
	level.trackingDroneSettings.ExplosiveTimeOut =		30.0;	
	level.trackingDroneSettings.health =				999999; // keep it from dying anywhere in code	
	level.trackingDroneSettings.maxHealth =				60; // this is what we check against for death	
	level.trackingDroneSettings.vehicleInfo =			"vehicle_tracking_drone_mp";
	level.trackingDroneSettings.modelBase =				"npc_drone_tracking";
	level.trackingDroneSettings.fxId_sparks =			Loadfx( "vfx/sparks/direct_hack_stun" );
	level.trackingDroneSettings.fxId_laser_glow =		Loadfx( "vfx/lights/tracking_drone_laser_blue" );
	level.trackingDroneSettings.fxId_explode =			LoadFX( "vfx/explosion/tracking_drone_explosion" );	
	level.trackingDroneSettings.fxId_LethalExplode =	LoadFX( "vfx/explosion/frag_grenade_default" );	
	level.trackingDroneSettings.fxId_warning =			LoadFX( "vfx/lights/light_tracking_drone_blink_warning" );
	level.trackingDroneSettings.fxId_enemy_light =		LoadFX( "vfx/lights/light_tracking_drone_blink_enemy" );
	level.trackingDroneSettings.fxId_friendly_light =	LoadFX( "vfx/lights/light_tracking_drone_blink_friendly" );
	level.trackingDroneSettings.fxId_thruster_down =	LoadFX( "vfx/distortion/tracking_drone_distortion_down" );
	level.trackingDroneSettings.fxId_thruster_up =		LoadFX( "vfx/distortion/tracking_drone_distortion_up" );
	level.trackingDroneSettings.fxId_engine_distort =	LoadFX( "vfx/distortion/tracking_drone_distortion_hemi" );
	level.trackingDroneSettings.sound_explode =			"veh_tracking_drone_explode";	
	level.trackingDroneSettings.sound_lock =			"veh_tracking_drone_lock_lp";	

	level.trackingDrones = [];
	
	foreach(player in level.players)
	{
		player.is_being_tracked = false;
	}
	
	level thread onTrackingPlayerConnect();
	
	level.trackingDroneTimeout = level.trackingDroneSettings.timeOut;
	level.explosiveDroneTimeout = level.trackingDroneSettings.ExplosiveTimeOut;
	level.trackingDroneDebugPosition = 0;
	level.trackingDroneDebugPositionForward = BALL_DRONE_FORWARD_OFFSET;
	level.trackingDroneDebugPositionHeight = BALL_DRONE_SIDE_OFFSET;
}


tryUseTrackingDrone( weaponName ) // self == player
{
	numIncomingVehicles = 1;
	if( self isUsingRemote() )
	{
		return false;
	}	
	else if( exceededMaxTrackingDrones( ) )
	{
		self IPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
		return false;	
	}
	else if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
	{
		self IPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
		return false;
	}
	
	// Force init (this is for test bots)
	if(!IsDefined(self.trackingDroneArray))
	{
		self.trackingDroneArray = [];
	}
	
	// Limit the player to a max of 2 drones
	if ( self.trackingDroneArray.size )
	{
		self.trackingDroneArray = array_removeUndefined( self.trackingDroneArray );
		
		if( self.trackingDroneArray.size >= level.trackingDroneMaxPerPlayer )
		{					
			if(isDefined(self.trackingDroneArray[0]))
			{
				self.trackingDroneArray[0] thread trackingDrone_leave();			
			}
		}
	}

	// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
	incrementFauxVehicleCount();

	trackingDrone = createTrackingDrone( weaponName );
	
	if( !IsDefined( trackingDrone ) )
	{
		// decrement the faux vehicle count since this failed to spawn
		decrementFauxVehicleCount();

		return false;	
	}
	trackingDrone.weaponName = weaponName;
	
	self.trackingDroneArray[ self.trackingDroneArray.size ] = trackingDrone;
	level.trackingDrones = array_removeUndefined( level.trackingDrones );
	level.trackingDrones[level.trackingDrones.size] = trackingDrone;
	self thread startTrackingDrone( trackingDrone );

	return true;
}


createTrackingDrone( weaponName, bRecreate, startPosition, startAngles, DroneType ) // self == player
{	
	if(!IsDefined(bRecreate))
	{
		bRecreate = false;
	}
	
	if(!bRecreate)
	{	
		origin = self GetEye();
		forward = AnglesToForward( self GetPlayerAngles() );
		
		startAng = self GetPlayerAngles();
		forward = AnglesToForward( startAng );
		side = AnglesToRight(startAng );
		
		forwardOffset = forward * BALL_DRONE_SPAWN_FORWARD_OFFSET;
	
		sideOffset = side * BALL_DRONE_SPAWN_SIDE_OFFSET;
		
		heightOffset = BALL_DRONE_SPAWN_STAND_UP_OFFSET;
		switch( self getStance() )
		{
			case "stand":
				heightOffset = BALL_DRONE_SPAWN_STAND_UP_OFFSET;
				break;
			case "crouch":
				heightOffset = BALL_DRONE_SPAWN_CROUCH_UP_OFFSET;
				break;
			case "prone":
				heightOffset = BALL_DRONE_SPAWN_PRONE_UP_OFFSET;
				break;
		}
		
		targetOffset	= (0, 0, heightOffset);
		targetOffset 	+= sideOffset;
		targetOffset	+= forwardOffset;
		
		/#
		if( level.trackingDroneDebugPosition )
		{
			targetOffset = (0, 0, level.trackingDroneDebugSpawnPositionHeight );
			targetOffset += forward * level.trackingDroneDebugSpawnPositionForward;			
		}
		#/		

		if(IsDefined(self.trackingDroneStartPosition) && IsDefined(self.trackingDroneStartAngles))
		{
			startPos = self.trackingDroneStartPosition;
			startAng = self.trackingDroneStartAngles;
		}
		else
		{
			startPos = self.origin + targetOffset;
		}
		
		/*playerStartPos = self.origin;
		trace = BulletTrace( playerStartPos, startPos, false );
		// make sure we aren't starting in geo
		attempts = 3;
		while( trace[ "surfacetype" ] != "none" && attempts > 0 )
		{
			startPos = self.origin + ( VectorNormalize( playerStartPos - trace[ "position" ] ) * 5 );
			trace = BulletTrace( playerStartPos, startPos, false );
			attempts--;
			wait( 0.05 );
		}
		if( attempts <= 0 )
			return;*/
		
	
		// make sure there are path nodes in the level
		/*if(!IsDefined(GetNodesOnPath(startPos, self.origin)))
		{
			self SetWeaponAmmoStock( weaponName, self GetWeaponAmmoStock( weaponName ) + 1 );
			self IPrintLnBold( &"MP_TRACKING_DRONE_UNAVAILABLE" );
			return;		
		}*/
	}
	else
	{
		startPos = startPosition;
		targetPos = startPosition;
		startAng = startAngles;
	}
	/*
	if(DroneType == "explosive_drone")
	{
		drone = SpawnHelicopter( self, startPos, startAng, level.trackingDroneSettings.vehicleInfo, "vehicle_pdrone" );
		if( !IsDefined( drone ) )
		return;
	}
	else
	{
		drone = SpawnHelicopter( self, startPos, startAng, level.trackingDroneSettings.vehicleInfo, level.trackingDroneSettings.modelBase );
		if( !IsDefined( drone ) )
			return;
	}
	*/
	owner = self;
	if ( isdefined ( level.isHorde ) && level.isHorde )
		owner = level.player;
	
		drone = SpawnHelicopter( owner, startPos, startAng, level.trackingDroneSettings.vehicleInfo, level.trackingDroneSettings.modelBase );
		if( !IsDefined( drone ) )
			return;
	
	if(IsDefined(DroneType))
	{
		drone.type = "explosive_drone";
		drone SetModel("vehicle_pdrone");
	}
	else
	{
		drone.type = "tracking_drone";
	}
	
	//drone EnableAimAssist();
	
	// make expendable if it is non-lethal drone
	drone make_entity_sentient_mp( self.team);
	drone MakeVehicleNotCollideWithPlayers( true );
	
	drone addToTrackingDroneList();
	drone thread removeFromTrackingDroneListOnDeath();

	drone.health = level.trackingDroneSettings.health;
	drone.maxHealth = level.trackingDroneSettings.maxHealth;
	drone.damageTaken = 0; // how much damage has it taken

	drone.speed = 20;
	drone.followSpeed = 20;
	drone.owner = self;
	drone.team = self.team;
	
	drone Vehicle_SetSpeed( drone.speed, 10, 10 );
	drone SetYawSpeed( 120, 90 );
	drone SetNearGoalNotifyDist( 64 );
	drone SetHoverParams( 4, 5, 5 );
	
	drone.fx_tag0 = undefined;
	if(IsDefined(drone.type))
	{
		if(drone.type == "tracking_drone")
		{
			drone.fx_tag0 = "fx_joint_0";
		}
		else if(drone.type == "explosive_drone")
		{
			drone.fx_tag0 = "TAG_EYE";
		}
	}
	
	
	
	// set icon
	if ( level.teamBased )
		drone maps\mp\_entityheadicons::setTeamHeadIcon( self.team, (0,0,25), drone.fx_tag0 );
	else
		drone maps\mp\_entityheadicons::setPlayerHeadIcon( self.owner, (0,0,25), drone.fx_tag0);

	// tracking
	drone.maxTrackingRange = 2000;
	drone.maxLaserRange = 300;
	drone.trackedPlayer = undefined;

	maxPitch = 45;
	maxRoll = 45;
	drone SetMaxPitchRoll( maxPitch, maxRoll );	

	drone.targetPos = startPos;

	drone.attract_strength = 10000;
	drone.attract_range = 150;
	drone.attractor = Missile_CreateAttractorEnt( drone, drone.attract_strength, drone.attract_range );

	drone.hasDodged = false;
	drone.stunned = false;
	drone.inactive = false;

	drone thread maps\mp\gametypes\_damage::setEntityDamageCallback( drone.maxHealth, undefined, ::onTrackingDroneDeath, undefined, false );
	drone thread trackingDrone_watchDisable();
	drone thread trackingDrone_watchDeath();
	drone thread trackingDrone_watchOwnerLoss();
	drone thread trackingDrone_watchOwnerDeath();
	drone thread trackingDrone_watchRoundEnd();
	drone thread trackingDrone_watchHostMigration();
	if ( !isdefined ( level.isHorde ) )
		drone thread trackingDrone_watchTimeout();
//	if(drone.type == "explosive_drone")
//	{
//		drone thread ExplosiveDrone_enemy_lightFX();
//		drone thread ExplosiveDrone_friendly_lightFX();
//	}
//	else if(drone.type == "tracking_drone")
//	{
//		drone thread trackingDrone_enemy_lightFX();
//		drone thread trackingDrone_friendly_lightFX();
//		drone thread drone_thrusterFX();
//	}
	if(drone.type == "tracking_drone")
	{
		drone thread trackingDrone_enemy_lightFX();
		drone thread trackingDrone_friendly_lightFX();
		drone thread drone_thrusterFX();
	}
	
	
	return drone;
}

idleTargetMover( ent ) // self == player
{
	self endon( "disconnect" );
	level endon( "game_ended" );
	ent endon( "death" );

	// keep the idleTarget entity behind the player so the turret is always default looking back there
	forward = AnglesToForward( self.angles );
	while( true )
	{
		if( isReallyAlive( self ) && !self isUsingRemote() && AnglesToForward( self.angles ) != forward )
		{
			forward = AnglesToForward( self.angles );
			pos = self.origin + ( forward * -100 ) + ( 0, 0, 40 ); 
			ent MoveTo( pos, 0.5 );
		}
		wait( 0.5 );
	}
}

trackingDrone_lightFX( fx, player ) // self == drone
{
	player endon("disconnect");
	
	PlayFXOnTagForClients( fx, self, "fx_light_1", player );
	wait 0.05;
	PlayFXOnTagForClients( fx, self, "fx_light_2", player );
	wait 0.05;
	PlayFXOnTagForClients( fx, self, "fx_light_3", player );
	wait 0.05;
	PlayFXOnTagForClients( fx, self, "fx_light_4", player );
}

trackingDrone_enemy_lightFX() // self == drone
{
	// looping fx

	self endon( "death" );

	foreach( player in level.players )
	{
		if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team != self.team )
		{
			self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_enemy_light, player );
			wait(0.2);
		}

	}
}

trackingDrone_friendly_lightFX() // self == drone
{
	// looping fx

	self endon( "death" );

	foreach( player in level.players )
	{
		if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team == self.team )
		{
			self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_friendly_light, player );
			wait(0.2);
		}
	}

	self thread watchConnectedPlayFX();
	self thread watchJoinedTeamPlayFX();
}

drone_thrusterFX()
{
	self endon( "death" );

	foreach( player in level.players )
	{
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_F", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_K", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_L", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_R", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_engine_distort))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_engine_distort, self, "TAG_WEAPON", player );
		}
		wait(0.25);
		/*PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_F", player );
		wait(0.1);
		PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_K", player );
		wait(0.1);
		PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_L", player );
		wait(0.1);
		PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_R", player );*/
	}
	
	while( true )
	{
		level waittill( "connected", player );
		player waittill( "spawned_player" );
		
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_F", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_K", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_L", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.trackingDroneSettings.fxId_thruster_down))
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_down, self, "fx_thruster_down_R", player );
		}
		wait(0.1);
		{
			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_engine_distort, self, "TAG_WEAPON", player );
		}
		wait(0.25);
		/*PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_F", player );
		wait(0.1);
		PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_K", player );
		wait(0.1);
		PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_L", player );
		wait(0.1);
		PlayFXOnTagForClients( level.trackingDroneSettings.fxId_thruster_up, self, "fx_thruster_up_R", player );*/
	}
}

watchConnectedPlayFX() // self == drone
{
	self endon( "death" );

	// play fx for late comers
	while( true )
	{
		level waittill( "connected", player );
		player waittill( "spawned_player" );

		if( IsDefined( player ) && player.team == self.team )
		{
			self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_friendly_light, player );
			wait(0.2);
		}
	}
}

watchJoinedTeamPlayFX() // self == drone
{
	self endon( "death" );

	// play fx for team changers
	while( true )
	{
		level waittill( "joined_team", player );
		player waittill( "spawned_player" );

		if( IsDefined( player ) && player.team == self.team )
		{
			self childthread trackingDrone_lightFX( level.trackingDroneSettings.fxId_friendly_light, player );
			wait(0.2);
		}
	}
}

///////////////////// TEMP FX FOR TEMP EXPLOSIVE DRONE MESH  ///////////////////////////////////////////
////
//ExplosiveDrone_enemy_lightFX() // self == drone
//{
//	// looping fx
//
//	self endon( "death" );
//
//	foreach( player in level.players )
//	{
//		if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team != self.team )
//		{
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_0", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_1", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_2", player );
//		}
//
//	}
//}
//
//ExplosiveDrone_friendly_lightFX() // self == drone
//{
//	// looping fx
//
//	self endon( "death" );
//
//	foreach( player in level.players )
//	{
//		if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team == self.team )
//		{
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2", player );
//		}
//	}
//
//	self thread watchConnectedExplosivePlayFX();
//	self thread watchJoinedTeamExplosivePlayFX();
//}
//
//watchConnectedExplosivePlayFX() // self == drone
//{
//	self endon( "death" );
//
//	// play fx for late comers
//	while( true )
//	{
//		level waittill( "connected", player );
//		player waittill( "spawned_player" );
//
//		if( IsDefined( player ) && player.team == self.team )
//		{
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2", player );
//		}
//	}
//}
//
//watchJoinedTeamExplosivePlayFX() // self == drone
//{
//	self endon( "death" );
//
//	// play fx for team changers
//	while( true )
//	{
//		level waittill( "joined_team", player );
//		player waittill( "spawned_player" );
//
//		if( IsDefined( player ) && player.team == self.team )
//		{
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1", player );
//			wait 0.15;
//			PlayFXOnTagForClients( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2", player );
//		}
//	}
//}
/////
///////////////////// TEMP FX FOR TEMP EXPLOSIVE DRONE MESH  ///////////////////////////////////////////


startTrackingDrone( drone ) // self == player
{			
	level endon( "game_ended" );
	drone endon( "death" );

	//	begin following player	
	drone thread trackingDrone_followTarget();
	drone thread aud_drone_start_jets();
	
	if(IsDefined(drone.type))
	{
		if(drone.type == "explosive_drone")
		{
			drone thread CheckForExplosiveGoal();
			
			/*
			 
			 	result = self waittill_any_return( "goal", "near_goal", "hit_goal" );
			 
			 */
		
		}
		// begin highlighting target
		else if(drone.type == "tracking_drone" && !isdefined ( level.isHorde ) )
		{
			drone thread trackingDrone_highlightTarget();
		}
	}
}
CheckForExplosiveGoal()
{
	level endon( "game_ended" );
	level endon( "host_migration_begin" );
	self endon( "death" );
	self endon( "leaving" );
	
	while(true)
	{
		self waittill_any( "goal", "near_goal", "hit_goal");
		{
			if(self.trackedPlayer != self.owner && isReallyAlive(self.trackedPlayer))
			{
				DistanceToTargetSq = DistanceSquared(self.trackedPlayer.origin, self.origin);
				if(DistanceToTargetSq <= 16384)
				{
					self notify("exploding");
					self thread BlowUpDroneSequence();
					break;
				}
			}
		}
	}
}
BlowUpDroneSequence()
{
	Time = 2;
	// play sound 
	// blink lights
	StoredOwner = undefined;
	if(isdefined(self.owner))
	{
		StoredOwner = self.owner;
	}
	if( IsDefined( self ))
	{
		self thread TurnOnDangerLights();
		self playsound("drone_warning_beap");
	}
	
	
	wait(Time);
	if(isDefined(self))
	{
		self PlaySound("drone_bomb_explosion");
		up_v = AnglesToUp(self.angles);
		forward_v = AnglesToForward(self.angles);
		
		PlayFx(level.trackingDroneSettings.fxId_LethalExplode, self.origin, forward_v, up_v);
		if(isdefined(StoredOwner))
		{
			self RadiusDamage(self.origin, 256, 1000, 25, StoredOwner, "MOD_EXPLOSIVE", "killstreak_missile_strike_mp");
		}
		else
		{
			self RadiusDamage(self.origin, 256, 1000, 25, undefined, "MOD_EXPLOSIVE", "killstreak_missile_strike_mp");
		}
		
		self notify("death");
	}
}
TurnOnDangerLights()
{
	if( IsDefined( self ))
	{
		StopFXOnTag( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_0");
		StopFXOnTag( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_1");
		StopFXOnTag( level.trackingDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_2");
		StopFXOnTag( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0");
		StopFXOnTag( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1");
		StopFXOnTag( level.trackingDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2");
	}
	wait(0.05);
	if( IsDefined( self ))
	{
		PlayFXOnTag( level.trackingDroneSettings.fxId_warning, self, "tag_fx_beacon_0");
		PlayFXOnTag( level.trackingDroneSettings.fxId_warning, self, "tag_fx_beacon_1");
	}
	wait(0.15);
	if( IsDefined( self ))
	{
		PlayFXOnTag( level.trackingDroneSettings.fxId_warning, self, "tag_fx_beacon_2");
	}
}
trackingDrone_followTarget() // self == drone
{
	level endon( "game_ended" );
	level endon( "host_migration_begin" );
	self endon( "death" );
	self endon( "leaving" );
	self endon( "exploding" );

	if( !IsDefined( self.owner ) )
	{
		self thread trackingDrone_leave();
		return;
	}

	self.owner endon( "disconnect" );	
	self endon( "owner_gone" );

	self Vehicle_SetSpeed( self.followSpeed, 10, 10 );
	self.previousTrackedPlayer = self.owner;
	self.trackedPlayer = undefined;
	if ( isdefined ( level.isHorde ) && level.isHorde )
		self.trackedPlayer = self.owner;
	while( true )
	{
		if( IsDefined( self.stunned ) && self.stunned )
		{
			wait( 0.5 );
			continue;
		}
		
		if( IsDefined( self.owner ) && IsAlive( self.owner ) )
		{
			// Try to find a target to track
			maxRangeSquared = self.maxTrackingRange * self.maxTrackingRange;
			closestDistanceSquared = maxRangeSquared;
			
			if ( !isdefined ( level.isHorde ) )
			{
				if(!IsDefined(self.trackedPlayer) || self.trackedPlayer == self.owner)
				{
					foreach( player in level.players )
					{
						if( IsDefined( player ) && IsAlive(player) && player.team != self.team && !player _hasPerk( "specialty_blindeye" ) )
						{
							currentDistanceSquared = DistanceSquared(self.origin, player.origin);
							if(currentDistanceSquared < closestDistanceSquared)
							{
								closestDistanceSquared = currentDistanceSquared;
								self.trackedPlayer = player;	
	
								self thread watchPlayerDeathDisconnect(player);
							}
						}
					}
				}
			}
	
			// Track the owner if there are no current enemies to track
			if(!IsDefined(self.trackedPlayer))
			{
				self.trackedPlayer = self.owner;
			}
			
			// Track current target
			if( IsDefined( self.trackedPlayer ))
			{
				trackingDrone_moveToPlayer(self.trackedPlayer);
			}
			
			// Set previous tracked target
			if(self.trackedPlayer != self.previousTrackedPlayer )
			{
				stopHighlightingPlayer(self.previousTrackedPlayer );
				self.previousTrackedPlayer = self.trackedPlayer;
			}
		}
		wait( 1 );
	}
}

watchPlayerDeathDisconnect(trackedPlayer) // self == drone
{
	self endon( "death" );
	self endon( "leaving" );
	self endon( "exploding" );
	
	trackedPlayer waittill_any( "death", "disconnect", "faux_spawn", "joined_team" );
	
	if( IsDefined(trackedPlayer) )
	{
	if(trackedPlayer.is_being_tracked == true)
	{
		if(!IsAlive(trackedPlayer))
			trackedPlayer.died_being_tracked = true;
		self thread trackingDrone_leave();
	}
	else
	{
		self.trackedPlayer = undefined;
		}
	}
}


trackingDrone_moveToPlayer( playerToMoveTo ) // self == drone
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "leaving" );
	self.owner endon( "death" );
	self.owner endon( "disconnect" );
	self endon( "owner_gone" );

	self notify( "trackingDrone_moveToPlayer" );
	self endon( "trackingDrone_moveToPlayer" );

	
	// collect the ideal offsets from the player
	forwardOffset = 0;
	sideOffset = 0;
	heightOffset = 0;

	if( isdefined ( level.isHorde ) && level.isHorde )
	{
		forwardOffset = -1 * HORDE_BALL_DRONE_FORWARD_OFFSET;
		sideOffset = HORDE_BALL_DRONE_SIDE_OFFSET;

		switch( playerToMoveTo getStance() )
		{
			case "stand":
				heightOffset = HORDE_BALL_DRONE_STAND_UP_OFFSET;
				break;
			case "crouch":
				heightOffset = HORDE_BALL_DRONE_CROUCH_UP_OFFSET;
				break;
			case "prone":
				heightOffset = HORDE_BALL_DRONE_PRONE_UP_OFFSET;
				break;
		}
	}
	else
	{
		forwardOffset = -1 * BALL_DRONE_FORWARD_OFFSET;
		sideOffset = BALL_DRONE_SIDE_OFFSET;

		switch( playerToMoveTo getStance() )
		{
			case "stand":
				heightOffset = BALL_DRONE_STAND_UP_OFFSET;
				break;
			case "crouch":
				heightOffset = BALL_DRONE_CROUCH_UP_OFFSET;
				break;
			case "prone":
				heightOffset = BALL_DRONE_PRONE_UP_OFFSET;
				break;
		}
	}
	
	targetOffset	= (sideOffset, forwardOffset, heightOffset);

/#
	if( level.trackingDroneDebugPosition )
	{
		targetOffset = (0, -1*level.trackingDroneDebugPositionForward, level.trackingDroneDebugPositionHeight );
	}
#/

	// ask code to navigate us as close as possible to the offset from the owner, set us as in-transit and start a thread waiting for us to get to the goal
	self SetDroneGoalPos( playerToMoveTo, targetOffset );
	self.inTransit = true;
	self thread trackingDrone_watchForGoal();
	self thread trackingDrone_watchTargetDisconnect();
}

trackingDrone_stopMovement()
{
	// ask code to navigate us as close as possible to the offset from the owner, set us as in-transit and start a thread waiting for us to get to the goal
	self SetVehGoalPos( self.origin, 1 );
	self.inTransit = false;
	self.inactive = true;
}

trackingDrone_changeOwner(newOwner) // self = drone
{
	// Change ownership of drone
	// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
	incrementFauxVehicleCount();

	trackingDrone = newOwner createTrackingDrone( self.weaponName, true, self.origin, self.angles );
	if( !IsDefined( trackingDrone ) )
	{
		// decrement the faux vehicle count since this failed to spawn
		decrementFauxVehicleCount();

		return false;	
	}
	
	// Create array if it does not exist
	if(!IsDefined(newOwner.trackingDroneArray))
	{
		newOwner.trackingDroneArray = [];
	}

	newOwner.trackingDroneArray[ newOwner.trackingDroneArray.size ] = trackingDrone;
	level.trackingDrones = array_removeUndefined( level.trackingDrones );
	level.trackingDrones[level.trackingDrones.size] = trackingDrone;
	newOwner thread startTrackingDrone( trackingDrone );
	
	if( IsDefined( level.trackingDroneSettings.fxId_sparks ) )
	{
		StopFXOnTag( level.trackingDroneSettings.fxId_sparks, self, self.fx_tag0 );
	}
	
	self removeTrackingDrone();
	
	return true;
}

trackingDrone_highlightTarget() // self == drone
{		
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "leaving" );

	if( !IsDefined( self.owner ) )
	{
		self thread trackingDrone_leave();
		return;
	}

	self.owner endon( "disconnect" );	
	self.owner endon( "joined_team" );
	self.owner endon( "joined_spectators" );
	
	// Create the laser
	self.laserTag = Spawn( "script_model", self.origin );
	self.laserTag SetModel( "tag_laser" );	
	
	while( true )
	{		
		if(IsDefined(self.trackedPlayer))
		{
			self.laserTag.origin = self GetTagOrigin("tag_weapon");
			randomRange = 20;
			randomOffset = ( randomFloat( randomRange ), randomFloat( randomRange ), randomFloat( randomRange ) ) - ( 10, 10, 10 );
			
			heightOffset = BALL_DRONE_STAND_UP_OFFSET;
			switch( self.trackedPlayer getStance() )
			{
				case "stand":
					heightOffset = BALL_DRONE_STAND_UP_OFFSET;
					break;
				case "crouch":
					heightOffset = BALL_DRONE_CROUCH_UP_OFFSET;
					break;
				case "prone":
					heightOffset = BALL_DRONE_PRONE_UP_OFFSET;
					break;
		}	

			self.laserTag.angles = VectorToAngles( ( self.trackedPlayer.origin + ( 0, 0, heightOffset - 20 ) + randomOffset ) - self.origin );
		}	

		if( IsDefined( self.stunned ) && self.stunned )
		{
			wait( 0.5 );
			continue;
		}		
		
		// Highlight enemies
		traceEntity = undefined;
		if(IsDefined(self.trackedPlayer))
		{
			traceData = BulletTrace( self.origin, self.trackedPlayer.origin, true, self );
		 	traceEntity = traceData["entity"];	
		}
		
		if(IsDefined(self.trackedPlayer) && 
		   self.trackedPlayer != self.owner &&
		   IsDefined(traceEntity) && 
		   traceEntity == self.trackedPlayer &&
		   DistanceSquared(self.origin, self.trackedPlayer.origin) < self.maxLaserRange * self.maxLaserRange)
		{
			if( self.trackedPlayer.is_being_tracked == false)
			{
				startHighlightingPlayer(self.trackedPlayer);
			}
		}
		else
		{
			if(IsDefined(self.trackedPlayer) && self.trackedPlayer.is_being_tracked == true)
			{
				stopHighlightingPlayer(self.trackedPlayer);
			}
		}
		wait( 0.05 );
	}
}

startHighlightingPlayer(playerToStart) // self == drone
{
	self.laserTag LaserOn("tracking_drone_laser");
	playfxontag(level.trackingDroneSettings.fxId_laser_glow, self.laserTag, "tag_laser");
	
	if( IsDefined( level.trackingDroneSettings.sound_lock ) )
	{
		self PlayLoopSound( level.trackingDroneSettings.sound_lock );
	}
	
	playerToStart setPerk( "specialty_radararrow", true, false );
			
	if ( playerToStart.is_being_tracked == false )
	{
		playerToStart.is_being_tracked = true;
		playerToStart.TrackedByPlayer = self.owner;
	}
}

stopHighlightingPlayer(playerToStop) // self == drone.trackedPlayer
{
	if( IsDefined( self.laserTag ) )
	{
		self.laserTag LaserOff();
		stopfxontag(level.trackingDroneSettings.fxId_laser_glow, self.laserTag, "tag_laser");
	}

	if(IsDefined(playerToStop))
	{
		if( IsDefined( level.trackingDroneSettings.sound_lock ) )
		{
			self StopLoopSound( );
		}
		
		if ( playerToStop HasPerk( "specialty_radararrow", true ) )
		{
			playerToStop unsetPerk( "specialty_radararrow", true );
		}
		
		playerToStop notify( "player_not_tracked" );
		playerToStop.is_being_tracked = false;
		playerToStop.TrackedByPlayer = undefined;
	}
}

onTrackingPlayerConnect()
{
	level endon( "game_ended" );
	
	while ( true )
	{
		level waittill( "connected", player );
		
		player.is_being_tracked = false;
		
		// possible for multiple players to come in on one frame, so double check that we have a value for all players
		foreach ( player in level.players )
		{
			if (!IsDefined(player.is_being_tracked))
				player.is_being_tracked = false;
		}
	}
}

/#
debugDrawDronePath()
{
	self endon( "death" );
	self endon( "hit_goal" );
	
	self notify( "debugDrawDronePath" );
	self endon( "debugDrawDronePath" );
	
	while( true )
	{
		nodePath = GetNodesOnPath( self.owner.origin, self.origin );
		if( IsDefined( nodePath ) )
		{
			for( i = 0; i < nodePath.size; i++ )
			{
				if( IsDefined( nodePath[ i + 1 ] ) )
				   Line( nodePath[ i ].origin + Z_OFFSET, nodePath[ i + 1 ].origin + Z_OFFSET, ( 1, 0, 0 ) );
			}
		}
		wait( 0.05 );
	}
}
#/
	
trackingDrone_watchForGoal() // self == drone
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "leaving" );
	self.owner endon( "death" );
	self.owner endon( "disconnect" );
	self endon( "owner_gone" );
	
	self notify( "trackingDrone_watchForGoal" );
	self endon( "trackingDrone_watchForGoal" );

	result = self waittill_any_return( "goal", "near_goal", "hit_goal" );
	self.inTransit = false;
	self.inactive = false;
	self notify( "hit_goal" );		
}

/* ============================
State Trackers
============================ */

trackingDrone_watchDeath() // self == drone
{
	level endon( "game_ended" );
	self endon( "gone" );

	self waittill( "death" );	

	self thread trackingDroneDestroyed();
}


trackingDrone_watchTimeout() // self == drone
{
	level endon ( "game_ended" );
	level endon( "host_migration_begin" );
	self endon( "death" );
	self.owner endon( "disconnect" );
	self endon( "owner_gone" );

	timeout = level.trackingDroneTimeout;
	if(self.type == "explosive_drone")
	{
		timeout = level.explosiveDroneTimeout;
	}

	wait( timeout );

	self thread trackingDrone_leave();
}


trackingDrone_watchOwnerLoss() // self == drone
{
	level endon ( "game_ended" );
	self endon( "death" );
	self endon( "leaving" );

	self.owner waittill_any( "disconnect", "joined_team", "joined_spectators" );	

	self notify( "owner_gone" );
	//	leave
	self thread trackingDrone_leave();
}

trackingDrone_watchOwnerDeath() // self == drone
{
	level endon ( "game_ended" );
	self endon( "death" );
	self endon( "leaving" );

	while( true )
	{
		self.owner waittill( "death" );	

		self thread trackingDrone_leave();
	}
}

trackingDrone_watchTargetDisconnect() // self == drone
{
	level endon( "game_ended" );
	level endon( "host_migration_begin" );
	self endon( "death" );
	self endon( "leaving" );
	self.owner endon( "death" );
	self.owner endon( "disconnect" );
	self endon( "owner_gone" );

	self notify( "trackingDrone_watchTargetDisconnect" );
	self endon( "trackingDrone_watchTargetDisconnect" );

	self.trackedPlayer waittill( "disconnect" );

	stopHighlightingPlayer( self.trackedPlayer );
	trackingDrone_moveToPlayer( self.owner );
}

trackingDrone_watchRoundEnd() // self == drone
{
	level endon ( "game_ended" );
	self endon( "death" );
	self endon( "leaving" );	
	self.owner endon( "disconnect" );
	self endon( "owner_gone" );

	level waittill_any( "round_end_finished", "game_ended" );

	//	leave
	self thread trackingDrone_leave();
}

trackingDrone_watchHostMigration() // self == drone
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "leaving" );
	self.owner endon( "death" );
	self.owner endon( "disconnect" );
	self endon( "owner_gone" );

	level waittill( "host_migration_begin" );

	stopHighlightingPlayer( self.trackedPlayer );
	
	trackingDrone_stopMovement();

	maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
	
	self thread trackingDrone_changeOwner( self.owner );
}

trackingDrone_leave() // self == drone
{
	self endon( "death" );
	self notify( "leaving" );
	
	stopHighlightingPlayer(self.trackedPlayer);

	trackingDroneExplode();
}

/* ============================
End State Trackers
============================ */

/* ============================
Damage and Death Monitors
============================ */

onTrackingDroneDeath( attacker, weapon, meansOfDeath, damage )
{
	self notify ( "death" );
}


trackingDrone_watchDisable()
{
	self endon( "death" );
	self.owner endon( "disconnect" );
	level endon( "game_ended" );
	
	while( true )
	{
		self waittill( "emp_damage", attacker, duration );
		
		self thread trackingDrone_stunned();
	}
}


trackingDrone_stunned() // self == drone
{
	self notify( "trackingDrone_stunned" );
	self endon( "trackingDrone_stunned" );

	self endon( "death" );
	self.owner endon( "disconnect" );
	level endon( "game_ended" );
	
	trackingDrone_stunBegin();

	wait( STUNNED_TIME );

	trackingDrone_stunEnd();

}

trackingDrone_stunBegin()
{
	if(self.stunned)
		return;
	
	self.stunned = true;
	
	if( IsDefined( level.trackingDroneSettings.fxId_sparks ) )
	{
		PlayFXOnTag( level.trackingDroneSettings.fxId_sparks, self,  self.fx_tag0 );
	}
	
	thread stopHighlightingPlayer(self.trackedPlayer);
	
	self.trackedPlayer = undefined;
	self.previousTrackedPlayer = self.owner;
	
	self thread trackingDrone_stopMovement();	
}

trackingDrone_stunEnd()
{
	if( IsDefined( level.trackingDroneSettings.fxId_sparks ) )
	{
		KillFXOnTag( level.trackingDroneSettings.fxId_sparks, self,  self.fx_tag0 );
	}
	
	self.stunned = false;
	self.inactive = false;
}

trackingDroneDestroyed() // self == drone
{
	if( !IsDefined( self ) )
		return;
	
	//self.owner IPrintLnBold( &"TRACKING_DRONE_DESTROYED" );
	
	// Turn off the highlighting
	stopHighlightingPlayer(self.trackedPlayer);
	
	// stop stuned behavior and vfx
	trackingDrone_stunEnd();
	
	// TODO: could put some drama here as it crashes

	trackingDroneExplode();
}

trackingDroneExplode() // self == drone
{
	if( IsDefined( level.trackingDroneSettings.fxId_explode ) )
	{
		PlayFX( level.trackingDroneSettings.fxId_explode, self.origin );
	}

	if( IsDefined( level.trackingDroneSettings.sound_explode ) )
	{
		self PlaySound( level.trackingDroneSettings.sound_explode );
	}

	self notify( "explode" );

	self removeTrackingDrone();
}


deleteTrackingDrone() // self == drone
{
	if ( !IsRemovedEntity( self ) && IsDefined( self ) )
	{
		if ( IsDefined( self.attractor ) )
		{
			Missile_DeleteAttractor( self.attractor );
		}
		self delete();
	}	
}

removeTrackingDrone() // self == drone
{	
	// decrement the faux vehicle count right before it is deleted this way we know for sure it is gone
	decrementFauxVehicleCount();

	if( IsDefined( self.owner ) && IsDefined( self.owner.trackingDrone ) )
		self.owner.trackingDrone = undefined;
	
	if ( IsDefined( self.laserTag ) )
	{
		self.laserTag Delete();
	}
	
	self deleteTrackingDrone();
}

/* ============================
End Damage and Death Monitors
============================ */

/* ============================
List and Count Management
============================ */

addToTrackingDroneList()
{
	level.trackingDrones[ self GetEntityNumber() ] = self;	
}

removeFromTrackingDroneListOnDeath()
{
	entNum = self GetEntityNumber();

	self waittill ( "death" );

	level.trackingDrones[ entNum ] = undefined;
	
	level.trackingDrones = array_removeUndefined( level.trackingDrones );
}

exceededMaxTrackingDrones(  )
{
	if( level.trackingDrones.size >= maxVehiclesAllowed() )
		return true;	
	else
		return false;	
}

/* ============================
End List and Count Management
============================ */


aud_drone_start_jets()
{
	self playloopsound( "veh_tracking_drone_jets_lp" );
}

destroy_tracking_drone_in_water()			// self = tracking drone grenade
{
	self endon( "death" );
	
	if ( !isDefined( level.water_triggers ) )
	{
		return;
	}
	
	while ( true )
	{
		foreach ( trig in level.water_triggers )
		{
			if ( self IsTouching( trig ) )
			{
				if ( IsDefined( level.trackingDroneSettings.fxId_explode ) )
				{
					PlayFX( level.trackingDroneSettings.fxId_explode, self.origin );
				}
				if ( IsDefined( level.trackingDroneSettings.sound_explode ) )
				{
					self PlaySound( level.trackingDroneSettings.sound_explode );
				}

				self deleteTrackingDrone();
			}
		}
		wait( 0.05 );
	}
}

prevent_tracking_drone_in_water( pos )
{
	if ( !isDefined( level.water_triggers ) )
	{
		return false;
	}
	
	foreach ( trig in level.water_triggers )
	{
		if ( IsPointInVolume( pos, trig ) )
		{
			return true;
		}
	}
	return false;
}
