#using scripts\codescripts\struct;

#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\dev_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons\_weapon_utils;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          

#precache( "string", "MP_CANNOT_LOCKON_TO_TARGET" );

#precache( "fx", "killstreaks/fx_heli_chaff" );



	
#namespace heatseekingmissile;

function init_shared()
{
	game["locking_on_sound"] = "uin_alert_lockon_start";
	game["locked_on_sound"] = "uin_alert_lockon";
	
	callback::on_spawned( &on_player_spawned );

	level.fx_flare = "killstreaks/fx_heli_chaff";

	//Dvar is used with the dev gui so as to let the player target friendly vehicles with heat-seekers.
	/#
		SetDvar("scr_freelock", "0");
	#/
}

function on_player_spawned()
{
	self endon( "disconnect" );

	self ClearIRTarget();
	thread StingerToggleLoop();
	//thread TraceConstantTest();
	self thread StingerFiredNotify();
}

function ClearIRTarget()
{
	self notify( "stop_lockon_sound" );
	self notify( "stop_locked_sound" );
	self.stingerlocksound = undefined;
	self StopRumble( "stinger_lock_rumble" );

	self.stingerLockStartTime = 0;
	self.stingerLockStarted = false;
	self.stingerLockFinalized = false;
	self.stingerLockDetected = false;
	if( isdefined(self.stingerTarget) )
	{
		self.stingerTarget notify( "missile_unlocked" );
		self LockingOn(self.stingerTarget, false);
		self LockedOn(self.stingerTarget, false);
	}
	self.stingerTarget = undefined;

	self WeaponLockFree();
	self WeaponLockTargetTooClose( false );
	self WeaponLockNoClearance( false );

	self StopLocalSound( game["locking_on_sound"] );
	self StopLocalSound( game["locked_on_sound"] );

	self DestroyLockOnCanceledMessage();
}


function StingerFiredNotify()
{
	self endon( "disconnect" );
	self endon ( "death" );

	while ( true )
	{
		self waittill( "missile_fire", missile, weapon );

		/# thread debug_missile( missile ); #/

		if ( weapon.lockonType == "Legacy Single" )
		{
			if( isdefined(self.stingerTarget) && self.stingerLockFinalized )
			{
				self.stingerTarget notify( "stinger_fired_at_me", missile, weapon, self );
			}
		}
	}
}

/#
function debug_missile( missile )
{
	level notify( "debug_missile" );
	level endon( "debug_missile" );
	
	level.debug_missile_dots = [];
	
	while( 1 )
	{
		if ( GetDvarInt( "scr_debug_missile", 0 ) == 0 )
		{
			wait 0.5;
			continue;
		}

		if ( isdefined( missile ) )
		{
			missile_info = SpawnStruct();
			missile_info.origin = missile.origin;
			target = missile Missile_GetTarget();
			missile_info.targetEntNum = ( isdefined( target ) ? target GetEntityNumber() : undefined );
			if ( !isdefined( level.debug_missile_dots ) ) level.debug_missile_dots = []; else if ( !IsArray( level.debug_missile_dots ) ) level.debug_missile_dots = array( level.debug_missile_dots ); level.debug_missile_dots[level.debug_missile_dots.size]=missile_info;;
		}
		
		foreach( missile_info in level.debug_missile_dots )
		{
			dot_color = ( isdefined( missile_info.targetEntNum ) ? ( 1, 0, 0 ) : ( 0, 1, 0 ) );
			dev::debug_sphere( missile_info.origin, 10, dot_color, 0.66, 1 );
		}
		
		{wait(.05);};
	}
}
#/

function StingerWaitForAds()
{
	while( !self PlayerStingerAds() )
	{
		{wait(.05);};

		currentWeapon = self GetCurrentWeapon();
		if ( currentWeapon.lockonType != "Legacy Single" )
		{
			return false;
		}
	}
	
	return true;
}

function StingerToggleLoop()
{
	self endon( "disconnect" );
	self endon ( "death" );
	
	for (;;)
	{
		self waittill( "weapon_change", weapon );

		while ( weapon.lockonType == "Legacy Single" )
		{
			if ( self GetWeaponAmmoClip( weapon ) == 0 )
			{
				{wait(.05);};
				weapon = self GetCurrentWeapon();
				continue;
			}
		
			if ( !StingerWaitForAds() )
			{
				break;
			}

			self thread StingerIRTLoop( weapon );

			while( self PlayerStingerAds() )
			{
				{wait(.05);};
			}

			self notify( "stinger_IRT_off" );
			self ClearIRTarget();

			weapon = self GetCurrentWeapon();
		}
	}
}

function StingerIRTLoop( weapon )
{
	self endon( "disconnect" );
	self endon( "death" );
	self endon( "stinger_IRT_off" );

	lockLength = self getLockOnSpeed();

	for (;;)
	{
		{wait(.05);};

		if ( !(self HasWeapon(weapon)) )//don't have the weapon any longer.....
			break;

		//-------------------------
		// Four possible states:
		//      No missile in the tube, so CLU will not search for targets.
		//		CLU has a lock.
		//		CLU is locking on to a target.
		//		CLU is searching for a target to begin locking on to.
		//-------------------------

		if ( self.stingerLockFinalized )
		{
			passed = SoftSightTest();
			if ( !passed )
				continue;

			if ( ! self IsStillValidTarget( self.stingerTarget, weapon )  || self InsideStingerReticleLocked( self.stingerTarget, weapon ) == false )
			{
				self SetWeaponLockOnPercent( weapon, 0 );
				self ClearIRTarget();
				continue;
			}

			if ( !self.stingerTarget.locked_on )
			{
				self.stingerTarget notify( "missile_lock", self, self GetCurrentWeapon() );
			}
			
			self LockingOn(self.stingerTarget, false);
			self LockedOn(self.stingerTarget, true);
			if ( isdefined( weapon ) )
			{
				heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
			}

			thread LoopLocalLockSound( game["locked_on_sound"], 0.75 );

			
			//print3D( self.stingerTarget.origin, "* LOCKED!", (.2, 1, .3), 1, 5 );
			continue;
		}

		if ( self.stingerLockStarted )
		{
			if ( !self IsStillValidTarget( self.stingerTarget, weapon ) || self InsideStingerReticleLocked( self.stingerTarget, weapon ) == false )
			{
				self SetWeaponLockOnPercent( weapon, 0 );
				self ClearIRTarget();
				continue;
			}

			//print3D( self.stingerTarget.origin, "* locking...!", (.2, 1, .3), 1, 5 );
			
			self LockingOn(self.stingerTarget, true);
			self LockedOn(self.stingerTarget, false);
			if ( isdefined( weapon ) )
			{
				heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
			}

			passed = SoftSightTest();
			if ( !passed )
				continue;

			timePassed = getTime() - self.stingerLockStartTime;
			
			if ( isdefined( weapon ) )
			{
				self SetWeaponLockOnPercent( weapon, ( ( timePassed / lockLength ) * 100 ) );
				heatseekingmissile::setFriendlyFlags( weapon, self.stingerTarget );
			}
			
			if ( timePassed < lockLength )
				continue;

			assert( isdefined( self.stingerTarget ) );
			self notify( "stop_lockon_sound" );
			self.stingerLockFinalized = true;
			self WeaponLockFinalize( self.stingerTarget );

			continue;
		}
		

		bestTarget = self GetBestStingerTarget( weapon );
		if ( !isdefined( bestTarget ) || ( isdefined( self.stingerTarget ) && self.stingerTarget != bestTarget ) )
		{
			self DestroyLockOnCanceledMessage();
			if ( self.stingerLockDetected == true ) 
			{
				self WeaponLockFree();
				self.stingerLockDetected = false;
			}
			continue;
		}

		if ( !( self LockSightTest( bestTarget ) ) )
		{
			self DestroyLockOnCanceledMessage();
			continue;
		}

		//check for delay allowing helicopters to enter the play area
		if( isdefined( bestTarget.lockOnDelay ) && bestTarget.lockOnDelay )
		{
			self DisplayLockOnCanceledMessage();
			continue;
		}

		if( !TargetWithinRangeOfPlaySpace( bestTarget ) )
		{
			self DisplayLockOnCanceledMessage();
			continue;
		}
		
		self DestroyLockOnCanceledMessage();
		
		if ( self InsideStingerReticleLocked( bestTarget, weapon ) == false )
		{
			if ( self.stingerLockDetected == false ) 
			{
				self WeaponLockDetect( bestTarget );
			}
			self.stingerLockDetected = true;
			if ( isdefined( weapon ) )
			{
				heatseekingmissile::setFriendlyFlags( weapon, bestTarget );
			}
			continue;
		}
		
		self.stingerLockDetected = false;

		InitLockField( bestTarget );
		
		self.stingerTarget = bestTarget;
		self.stingerLockStartTime = getTime();
		self.stingerLockStarted = true;
		self.stingerLostSightlineTime = 0;

		self WeaponLockStart( bestTarget );

		self thread LoopLocalSeekSound( game["locking_on_sound"], 0.6 );
	}
}

function TargetWithinRangeOfPlaySpace( target )
{
	
/#
	// for tuning
	if ( GetDvarInt( "scr_missilelock_playspace_extra_radius_override_enabled", 0 ) > 0 )
	{
		extraRadiusDvar = GetDvarInt( "scr_missilelock_playspace_extra_radius", 5000 );
		if ( extraRadiusDvar != (isdefined(level.missileLockPlaySpaceCheckExtraRadius)?level.missileLockPlaySpaceCheckExtraRadius:0) )
		{
			level.missileLockPlaySpaceCheckExtraRadius = extraRadiusDvar;
			level.missileLockPlaySpaceCheckRadiusSqr = undefined;
		}
	}
#/
	
	// only allow targetting of targets that are within the play space by a specified radius
	// use level.missileLockPlaySpaceCheckExtraRadius to set it per level
	if ( level.missileLockPlaySpaceCheckEnabled === true )
	{
		if ( !isdefined( target ) )
			return false;
		
		if ( !isdefined( level.playSpaceCenter ) )
			level.playSpaceCenter = util::GetPlaySpaceCenter();
		
		if ( !isdefined( level.missileLockPlaySpaceCheckRadiusSqr ) )
			level.missileLockPlaySpaceCheckRadiusSqr = ( (( util::GetPlaySpaceMaxWidth() * 0.5 ) + level.missileLockPlaySpaceCheckExtraRadius) * (( util::GetPlaySpaceMaxWidth() * 0.5 ) + level.missileLockPlaySpaceCheckExtraRadius) );
		
		if ( Distance2DSquared( target.origin, level.playSpaceCenter ) > level.missileLockPlaySpaceCheckRadiusSqr )
			return false;
	}

	return true;
}

function DestroyLockOnCanceledMessage()
{
	if( isdefined( self.LockOnCanceledMessage ) )
		self.LockOnCanceledMessage destroy();
}

function DisplayLockOnCanceledMessage()
{
	if( isdefined( self.LockOnCanceledMessage ) )
		return;

	self.LockOnCanceledMessage = newclienthudelem( self );
	self.LockOnCanceledMessage.fontScale = 1.25;
	self.LockOnCanceledMessage.x = 0;
	self.LockOnCanceledMessage.y = 50; 
	self.LockOnCanceledMessage.alignX = "center";
	self.LockOnCanceledMessage.alignY = "top";
	self.LockOnCanceledMessage.horzAlign = "center";
	self.LockOnCanceledMessage.vertAlign = "top";
	self.LockOnCanceledMessage.foreground = true;
	self.LockOnCanceledMessage.hidewhendead = false;
	self.LockOnCanceledMessage.hidewheninmenu = true;
	self.LockOnCanceledMessage.archived = false;
	self.LockOnCanceledMessage.alpha = 1.0;
	self.LockOnCanceledMessage SetText( &"MP_CANNOT_LOCKON_TO_TARGET" );
}

function GetBestStingerTarget( weapon )
{
	targetsAll = [];
	
	if ( isdefined( self.get_stinger_target_override ) )
	{
		targetsAll = self [ [ self.get_stinger_target_override ] ]();
	}
	else
	{
		targetsAll = target_getArray();
	}
	
	targetsValid = [];

	for ( idx = 0; idx < targetsAll.size; idx++ )
	{
		/#
		//This variable is set and managed by the 'dev_friendly_lock' function, which works with the dev_gui
		if( GetDvarString( "scr_freelock") == "1" )
		{
			//If the dev_gui dvar is set, only check if the target is in the reticule. 
			if( self InsideStingerReticleNoLock( targetsAll[idx], weapon ) )
			{
				targetsValid[targetsValid.size] = targetsAll[idx];
			}
			continue;
		}
		#/		

		target = targetsAll[idx];

		if ( level.teamBased || level.use_team_based_logic_for_locking_on === true ) //team based game modes
		{
			if ( isdefined(target.team) && target.team != self.team) 
			{
				if ( self InsideStingerReticleDetect( target, weapon ) )
				{
					if ( !isdefined( self.is_valid_target_for_stinger_override ) || self [ [ self.is_valid_target_for_stinger_override ] ]( target ) )
					{
						hascamo = isdefined( target.camo_state ) && ( target.camo_state == 1 ) && !self hasPerk( "specialty_showenemyequipment" );
						if( !hascamo )
							targetsValid[targetsValid.size] = target;
					}
				}
			}
		}
		else
		{
			if( self InsideStingerReticleDetect( target, weapon ) ) //Free for all
			{
				if( ( isdefined( target.owner ) && self != target.owner ) || ( isplayer( target ) && self != target ) )
				{
					if ( !isdefined( self.is_valid_target_for_stinger_override ) || self [ [ self.is_valid_target_for_stinger_override ] ]( target ) )
						targetsValid[targetsValid.size] = target;
				}
			}
		}
	}

	if ( targetsValid.size == 0 )
		return undefined;

	bestTarget = targetsValid[0];
	if ( targetsValid.size > 1 )
	{
		closestRatio = 0.0;

		foreach( target in targetsValid ) 
		{
			ratio = RatioDistanceFromScreenCenter( target, weapon );
			if ( ratio > closestRatio )
			{
				closestRatio = ratio;
				bestTarget = target;
			}
		}
	}
	
	return bestTarget;
}

function CalcLockOnRadius( target, weapon )
{
	radius = self getLockOnRadius();
	
	if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
	{
		radius = weapon.lockOnScreenRadius;
	}	
	
	if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
	{
		dist2 = DistanceSquared( target.origin, self.origin );
		if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange ) 
			radius = radius * level.lockOnCloseRadiusScaler;
	}
	
	return radius;
}

function CalcLockOnLossRadius( target, weapon )
{
	radius = self getLockOnLossRadius();
	
	if( isdefined( weapon ) && isdefined( weapon.lockOnScreenRadius ) && ( weapon.lockOnScreenRadius > radius ) )
	{
		radius = weapon.lockOnScreenRadius;
	}

	if( isdefined( level.lockOnCloseRange ) && isdefined( level.lockOnCloseRadiusScaler ) )
	{
		dist2 = DistanceSquared( target.origin, self.origin );
		if( dist2 < level.lockOnCloseRange * level.lockOnCloseRange ) 
			radius = radius * level.lockOnCloseRadiusScaler;
	}
	return radius;
}
	
function RatioDistanceFromScreenCenter( target, weapon )
{
	radius = CalcLockOnRadius( target, weapon );
	return Target_ScaleMinMaxRadius( target, self, 65, 0, radius );
}

function InsideStingerReticleDetect( target, weapon )
{
	radius = CalcLockOnRadius( target, weapon );
	return target_isincircle( target, self, 65, radius );
}

function InsideStingerReticleNoLock( target, weapon )
{
	radius = CalcLockOnRadius( target, weapon );
	return target_isincircle( target, self, 65, radius );
}

function InsideStingerReticleLocked( target, weapon )
{
	radius = CalcLockOnLossRadius( target, weapon );
	return target_isincircle( target, self, 65, radius );
}

function IsStillValidTarget( ent, weapon )
{
	if ( ! isdefined( ent ) )
		return false;
	
	if ( isdefined( self.is_still_valid_target_for_stinger_override ) )
		return self [ [ self.is_still_valid_target_for_stinger_override ] ]( ent, weapon );

	if ( ! target_isTarget( ent ) && !( isdefined( ent.allowContinuedLockonAfterInvis ) && ent.allowContinuedLockonAfterInvis ) )
		return false;

	if ( ! InsideStingerReticleDetect( ent, weapon ) )
		return false;

	return true;
}

function PlayerStingerAds()
{
	return ( self PlayerAds() == 1.0 );
}

function LoopLocalSeekSound( alias, interval )
{
	self endon ( "stop_lockon_sound" );
	self endon( "disconnect" );
	self endon ( "death" );
	
	for (;;)
	{
		self PlaySoundForLocalPlayer( alias );
		self PlayRumbleOnEntity( "stinger_lock_rumble" );

		wait interval/2;
	}
}

function PlaySoundForLocalPlayer( alias )
{
	if ( self IsInVehicle() )
	{
		sound_target = self GetVehicleOccupied();
		if ( isdefined( sound_target ) )
		{
			sound_target PlaySoundToPlayer( alias, self );
		}
	}
	else
	{
		self playLocalSound( alias );
	}
}

function LoopLocalLockSound( alias, interval )
{
	self endon ( "stop_locked_sound" );
	self endon( "disconnect" );
	self endon ( "death" );
	
	if ( isdefined( self.stingerlocksound ) )
		return;

	self.stingerlocksound = true;
	

	for (;;)
	{
		// TODO make lock loop audio work correctly  CDC
		
		self PlaySoundForLocalPlayer( alias );
		self PlayRumbleOnEntity( "stinger_lock_rumble" );
		wait interval/6;

		self PlaySoundForLocalPlayer( alias );
		self PlayRumbleOnEntity( "stinger_lock_rumble" );
		wait interval/6;

		self PlaySoundForLocalPlayer( alias );
		self PlayRumbleOnEntity( "stinger_lock_rumble" );
		wait interval/6;

		self StopRumble( "stinger_lock_rumble" );
	}
	self.stingerlocksound = undefined;
}
	
function LockSightTest( target )
{
	cameraPos = self getplayercamerapos();
	
	if ( !isdefined( target ) ) //targets can disapear during targeting.
		return false;
	
	if( isdefined( target.parent ) )
		passed = BulletTracePassed( cameraPos, target.origin, false, target, target.parent );
	else
		passed = BulletTracePassed( cameraPos, target.origin, false, target );
	if ( passed )
		return true;

	front = target GetPointInBounds( 1, 0, 0 );
	if( isdefined( target.parent ) )
		passed = BulletTracePassed( cameraPos, front, false, target, target.parent );
	else
		passed = BulletTracePassed( cameraPos, front, false, target );
	if ( passed )
		return true;

	back = target GetPointInBounds( -1, 0, 0 );
	if( isdefined( target.parent ) )
		passed = BulletTracePassed( cameraPos, back, false, target, target.parent );
	else
		passed = BulletTracePassed( cameraPos, back, false, target );
	if ( passed )
		return true;

	return false;
}

function SoftSightTest()
{
	LOST_SIGHT_LIMIT = 500;

	if ( self LockSightTest( self.stingerTarget ) )
	{
		self.stingerLostSightlineTime = 0;
		return true;
	}

	if ( self.stingerLostSightlineTime == 0 )
		self.stingerLostSightlineTime = getTime();

	timePassed = GetTime() - self.stingerLostSightlineTime;
	//PrintLn( "Losing sight of target [", timePassed, "]..." );

	if ( timePassed >= LOST_SIGHT_LIMIT )
	{
		//PrintLn( "Lost sight of target." );
		self ClearIRTarget();
		return false;
	}
	
	return true;
}

function InitLockField( target )
{
	if ( isdefined( target.locking_on ) )
		return;
		
	target.locking_on = 0;
	target.locked_on = 0;
	target.locking_on_hacking = 0;
}

function LockingOn( target, lock )
{
	Assert( isdefined( target.locking_on ) );
	
	clientNum = self getEntityNumber();
	if ( lock )
	{
		target notify( "locking on" );
		target.locking_on |= ( 1 << clientNum );
		
		self thread watchClearLockingOn( target, clientNum );
	}
	else
	{
		self notify( "locking_on_cleared" );
		target.locking_on &= ~( 1 << clientNum );
	}
}

function watchClearLockingOn( target, clientNum )
{
	target endon("death");
	self endon( "locking_on_cleared" );
	
	self util::waittill_any( "death", "disconnect" );
	
	target.locking_on &= ~( 1 << clientNum );
}

function LockedOn( target, lock )
{
	Assert( isdefined( target.locked_on ) );
	
	clientNum = self getEntityNumber();
	if ( lock )
	{
		target.locked_on |= ( 1 << clientNum );
		
		self thread watchClearLockedOn( target, clientNum );
	}
	else
	{
		self notify( "locked_on_cleared" );
		target.locked_on &= ~( 1 << clientNum );
	}
}


function TargetingHacking( target, lock )
{
	Assert( isdefined( target.locking_on_hacking ) );
	
	clientNum = self getEntityNumber();
	if ( lock )
	{
		target notify( "locking on hacking" );
		target.locking_on_hacking |= ( 1 << clientNum );
		
		self thread watchClearHacking( target, clientNum );
	}
	else
	{
		self notify( "locking_on_hacking_cleared" );
		target.locking_on_hacking &= ~( 1 << clientNum );
	}
}

function watchClearHacking( target, clientNum )
{
	target endon("death");
	self endon( "locking_on_hacking_cleared" );
	
	self util::waittill_any( "death", "disconnect" );
	
	target.locking_on_hacking &= ~( 1 << clientNum );
}


function setFriendlyFlags( weapon, target )
{
	if ( !self isinvehicle() )
	{
		self setFriendlyHacking( weapon, target );
		self setFriendlyTargetting( weapon, target );
		self setFriendlyTargetLocked( weapon, target );

		if ( isdefined( level.killstreakMaxHealthFunction ) )
		{
			if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) )
			{
				killstreakEndTime = level.vtol.killstreakEndTime;
				if ( isdefined( killstreakEndTime ) )
				{
					self settargetedentityendtime( weapon, killstreakEndTime );
				}
			}
			else if ( isdefined( target.killstreakEndTime ) )
			{
				self settargetedentityendtime( weapon, target.killstreakEndTime );
			}
			else if ( isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakEndTime ) )
			{
				self settargetedentityendtime( weapon, target.parentStruct.killstreakEndTime );
			}
			else
			{
				self settargetedentityendtime( weapon, 0 );
			}
			self settargetedmissilesremaining( weapon, 0 );

			killstreakType = target.killstreakType;
			if ( !isdefined( target.killstreakType ) && isdefined( target.parentstruct ) && isdefined( target.parentStruct.killstreakType ) )
			{
				killstreakType  = target.parentStruct.killstreakType;
			}
			else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol.killstreakType ) )
			{
				killstreakType = level.vtol.killstreakType;
			}
			
			if ( isdefined ( killstreakType ) && isdefined( level.killstreakbundle[killstreakType] ) )
			{
				if ( isdefined( target.forceOneMissile ) )
				{
					self settargetedmissilesremaining( weapon, 1 );
				}
				else if ( isdefined( target.useVTOLTime ) && isdefined( level.vtol ) && isdefined( level.vtol.totalRocketHits ) && isdefined( level.vtol.missileToDestroy ) )
				{
					self settargetedmissilesremaining( weapon, level.vtol.missileToDestroy - level.vtol.totalRocketHits );
				}
				else
				{
					maxHealth = [[level.killstreakMaxHealthFunction]]( killstreakType );
					damageTaken = target.damageTaken;
					if ( !isdefined( damageTaken )  && isdefined( target.parentstruct )  )
					{
						damageTaken = target.parentstruct.damageTaken;
					}
					if ( isdefined( target.missileTrackDamage ) ) 
					{
						damageTaken = target.missileTrackDamage;
					}
					
					if ( isdefined( damageTaken ) && isdefined( maxHealth ) )
					{
						damagePerRocket = ( maxHealth / level.killstreakbundle[killstreakType].ksRocketsToKill ) + 1;
						remainingHealth = maxHealth - damageTaken;
						if ( remaininghealth > 0 )
						{
							missilesRemaining = int( ceil( remainingHealth / damageperrocket ) );
							if ( isdefined( target.numflares ) && target.numflares > 0 )
							{
								missilesRemaining += target.numFlares;
							}
							if ( isdefined( target.flak_drone ) )
							{
								missilesRemaining += 1;
							}
							
							self settargetedmissilesremaining( weapon, missilesRemaining );
						}
					}
				}
			}
		}
	}
}

function setFriendlyHacking( weapon, target )
{
	if ( level.teambased ) 
	{
		friendlyHackingMask = target.locking_on_hacking;
		
		if ( isdefined( friendlyHackingMask ) )
		{
			friendlyHacking = false;
			clientNum = self getEntityNumber();
			friendlyHackingMask &= ~( 1 << clientNum );
			if ( friendlyHackingMask != 0 )
			{
				friendlyHacking = true;
			}
			self SetWeaponFriendlyHacking( weapon, friendlyHacking );
		}
	}
}

function setFriendlyTargetting( weapon, target )
{
	if ( level.teambased ) 
	{
		friendlyTargetingMask = target.locking_on;
		
		if ( isdefined( friendlyTargetingMask ) )
		{
			friendlyTargeting = false;
			clientNum = self getEntityNumber();
			friendlyTargetingMask &= ~( 1 << clientNum );
			if ( friendlyTargetingMask != 0 )
			{
				friendlyTargeting = true;
			}
			self SetWeaponFriendlyTargeting( weapon, friendlyTargeting );
		}
	}
}

function setFriendlyTargetLocked( weapon, target )
{
	if ( level.teambased ) 
	{
		friendlyTargetLocked = false;
		friendlyLockingOnMask = target.locked_on;
		
		if ( isdefined( friendlyLockingOnMask ) )
		{
			friendlyTargetLocked = false;
			clientNum = self getEntityNumber();
			friendlyLockingOnMask &= ~( 1 << clientNum );
			if ( friendlylockingonMask != 0 )
			{
				friendlyTargetLocked = true;
			}
		}
		if ( friendlyTargetLocked == false )
		{
			friendlyTargetLocked = target MissileTarget_isOtherPlayerMissileIncoming( self );
		}
		self SetWeaponFriendlyTargetLocked( weapon, friendlyTargetLocked );
	}
}

function watchClearLockedOn( target, clientNum )
{
	self endon( "locked_on_cleared" );
	
	self util::waittill_any( "death", "disconnect" );

	if ( isdefined( target ) )
	{
		target.locked_on &= ~( 1 << clientNum );
	}
}

function MissileTarget_LockOnMonitor( player, endon1, endon2 )
{
	self endon( "death" );
	
	if ( isdefined(endon1) )
		self endon( endon1 );
	if ( isdefined(endon2) )
		self endon( endon2 );

	for( ;; )
	{

		if( target_isTarget( self ) )
		{	
			if ( self MissileTarget_isMissileIncoming() )
			{
				self clientfield::set( "heli_warn_fired", 1 );
				self clientfield::set( "heli_warn_locked", 0 );
				self clientfield::set( "heli_warn_targeted", 0 );
			}	
			else if( isdefined(self.locked_on) && self.locked_on )
			{
				self clientfield::set( "heli_warn_locked", 1 );
				self clientfield::set( "heli_warn_fired", 0 );
				self clientfield::set( "heli_warn_targeted", 0 );
			}
			else if( isdefined(self.locking_on) && self.locking_on )
			{
				self clientfield::set( "heli_warn_targeted", 1 );
				self clientfield::set( "heli_warn_fired", 0 );
				self clientfield::set( "heli_warn_locked", 0 );
			}
			else
			{
				self clientfield::set( "heli_warn_fired", 0 );
				self clientfield::set( "heli_warn_targeted", 0 );
				self clientfield::set( "heli_warn_locked", 0 );
			}
		}
		
		wait( 0.1 );
	}
}

function _incomingMissile( missile, attacker )
{
	if ( !isdefined(self.incoming_missile) )
	{
		self.incoming_missile = 0;
	}
	if ( !isdefined(self.incoming_missile_owner) )
	{
		self.incoming_missile_owner = [];
	}
	if ( !isdefined( self.incoming_missile_owner[attacker.entnum] ) )
	{
		self.incoming_missile_owner[attacker.entnum] = 0;
	}	
	
	self.incoming_missile++;
	self.incoming_missile_owner[attacker.entnum]++;
	
	attacker LockedOn(self, true);
	
	self thread _incomingMissileTracker( missile, attacker );
}

function _incomingMissileTracker( missile, attacker )
{
	self endon("death");
	
	attacker_entnum = attacker.entnum;
	
	missile waittill("death");
	
	self.incoming_missile--;
	self.incoming_missile_owner[attacker_entnum]--;
	if ( self.incoming_missile_owner[attacker_entnum] == 0 )
	{
		self.incoming_missile_owner[attacker_entnum] = undefined;
	}
	
	if ( isdefined( attacker ) )
	{
		attacker LockedOn( self, false );
	}
	
	assert( self.incoming_missile >= 0 );
}

function MissileTarget_isMissileIncoming()
{
	if ( !isdefined(self.incoming_missile) )
		return false;
		
	if ( self.incoming_missile )
		return true;
	
	return false;
}

function MissileTarget_isOtherPlayerMissileIncoming( attacker )
{
	if ( !isdefined(self.incoming_missile_owner) )
		return false;
	
	if ( self.incoming_missile_owner.size == 0 )
		return false;
	
	if ( self.incoming_missile_owner.size == 1 && isdefined( self.incoming_missile_owner[attacker.entnum] ) )
	    return false;
	
	return true;
}

function MissileTarget_HandleIncomingMissile(responseFunc, endon1, endon2, allowDirectDamage )
{
	level endon( "game_ended" );
	self endon( "death" );
	if ( isdefined(endon1) )
		self endon( endon1 );
	if ( isdefined(endon2) )
		self endon( endon2 );

	for( ;; )
	{
		self waittill( "stinger_fired_at_me", missile, weapon, attacker );
			
		_incomingMissile(missile, attacker);
		
		if ( isdefined(responseFunc) )
			[[responseFunc]]( missile, attacker, weapon, endon1, endon2, allowDirectDamage );
	}
}

function MissileTarget_ProximityDetonateIncomingMissile( endon1, endon2, allowDirectDamage )
{
	MissileTarget_HandleIncomingMissile(&MissileTarget_ProximityDetonate, endon1, endon2, allowDirectDamage );
}

function _missileDetonate( attacker, weapon, range, minDamage, maxDamage, allowDirectDamage )
{
	origin = self.origin;
	
	target = self Missile_GetTarget();

	self detonate();

	// guarantee a locked_on target is directly damaged if allowed and reasonably close enough
	if ( ( allowDirectDamage === true ) && isdefined( target ) && isdefined( target.origin ) )
	{
		minDistSq = (isdefined(target.locked_missile_min_distsq)?target.locked_missile_min_distsq:( (range) * (range) )); // sanity check distance for "bad" direct hits
		distSq = DistanceSquared( self.origin, target.origin );
		if ( distSq < minDistSq )
		{
			target DoDamage( maxDamage, origin, attacker, self, "none", "MOD_PROJECTILE", 0, weapon );
		}
	}

	radiusDamage( origin, range, maxDamage, minDamage, attacker, "MOD_PROJECTILE_SPLASH", weapon );
}

function MissileTarget_ProximityDetonate( missile, attacker, weapon, endon1, endon2, allowDirectDamage )
{
	level endon( "game_ended" );
	missile endon ( "death" );
	if ( isdefined(endon1) )
		self endon( endon1 );
	if ( isdefined(endon2) )
		self endon( endon2 );
	
	minDist = DistanceSquared( missile.origin, self.origin );
	lastCenter = self.origin;
	
	missile Missile_SetTarget( self, (isdefined(Target_GetOffset( self ))?Target_GetOffset( self ):( 0, 0, 0 )) );

	if ( isdefined( self.missileTargetMissDistance ) )
	{
		missedDistanceSq = self.missileTargetMissDistance * self.missileTargetMissDistance;
	}
	else
	{
		missedDistanceSq = 500 * 500;
	}
	flareDistanceSq = 3500 * 3500;
	
	for ( ;; )
	{
		// target already destroyed
		if ( !isdefined( self ) )
			center = lastCenter;
		else
			center = self.origin;
			
		lastCenter = center;		
		
		curDist = DistanceSquared( missile.origin, center );
		
		if( curDist < flareDistanceSq && isdefined(self.numFlares) && self.numFlares > 0 )
		{
			self.numFlares--;			

			self thread MissileTarget_PlayFlareFx();
			self challenges::trackAssists( attacker, 0, true );
			newTarget = self MissileTarget_DeployFlares(missile.origin, missile.angles);
			
			missile Missile_SetTarget( newTarget, (isdefined(Target_GetOffset( newTarget ))?Target_GetOffset( newTarget ):( 0, 0, 0 )) );
			missileTarget = newTarget;
			
			return;
		}		
		
		if ( curDist < minDist )
			minDist = curDist;
		
		if ( curDist > minDist )
		{			
			if ( curDist > missedDistanceSq )
				return;

			missile thread _missileDetonate( attacker, weapon, 500, 600, 600, allowDirectDamage );
			return;
		}
		
		{wait(.05);};
	}	
}

function MissileTarget_PlayFlareFx()
{
	if ( !isdefined( self ) )
		return;
	
	flare_fx = level.fx_flare;
	
	if ( isdefined( self.fx_flare ) )
	{
		flare_fx = self.fx_flare;
	}
	if( isdefined( self.flare_ent ) )
	{
		PlayFXOnTag( flare_fx, self.flare_ent, "tag_origin" );
	}
	else
	{
		PlayFXOnTag( flare_fx, self, "tag_origin" );
	}
	
	if ( isdefined( self.owner ) )
	{
		self playsoundtoplayer ( "veh_huey_chaff_drop_plr", self.owner );
	}
	self PlaySound ( "veh_huey_chaff_explo_npc" );
}

function MissileTarget_DeployFlares(origin, angles) // self == missile target
{
	vec_toForward = anglesToForward( self.angles );
	vec_toRight = AnglesToRight( self.angles );
	
	vec_toMissileForward = anglesToForward( angles );

	delta = self.origin - origin;
	dot = VectorDot(vec_toMissileForward,vec_toRight);
	
	sign = 1;
	if ( dot > 0 ) 
		sign = -1;
		
	// out to one side or the other slightly backwards
	flare_dir = VectorNormalize(VectorScale( vec_toForward, -0.5 ) + VectorScale( vec_toRight, sign ));
	velocity = VectorScale( flare_dir, RandomIntRange(200, 400));
	velocity = (velocity[0], velocity[1], velocity[2] - RandomIntRange(10, 100) );

	flareOrigin = self.origin;
	flareOrigin = flareOrigin + VectorScale( flare_dir, RandomIntRange(600, 800));
	
	// some height will allow a missle going twards a low hovering plane to 
	// have enough radius to turn to the new target
	flareOrigin = flareOrigin + ( 0, 0, 500 );
	
	if ( isdefined( self.flareOffset ) )
		flareOrigin = flareOrigin + self.flareOffset;
	
	flareObject = spawn( "script_origin", flareOrigin );
	flareObject.angles = self.angles;
	
	flareObject SetModel( "tag_origin" );
	flareObject MoveGravity( velocity, 5.0 );
	
	flareObject thread util::deleteAfterTime( 5.0 );
/#
	self thread debug_tracker( flareObject );
#/
	return flareObject;
}

/#
function debug_tracker( target )
{
	target endon( "death");
	
	while(1)
	{
		dev::debug_sphere( target.origin, 10, (1,0,0), 1, 1 );
		{wait(.05);};
	}
}
#/