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

CONST_AIRSTRIKE_Z_OFFSET_FROM_ANCHOR 	= 750;
CONST_AIRSTRIKE_FLY_IN_DIST 			= 10000;
CONST_AIRSTRIKE_FLY_IN_TIME				= 3;
CONST_AIRSTRIKE_FLY_THROUGH_DIST		= 10000;
CONST_AIRSTRIKE_FLY_THROUGH_TIME		= 10;
CONST_AIRSTRIKE_WING_WIDTH				= 380;
CONST_AIRSTRIKE_ADDITIONAL_BOMBER_DIST	= 500;

init()
{
	level._effect[ "airstrike_ground" ] 	= LoadFX( "vfx/explosion/clusterbomb_explode" ); 
	level._effect[ "airstrike_bombs" ]  	= LoadFX ( "vfx/explosion/vfx_clusterbomb" );
	level._effect[ "airstrike_death" ]  = LoadFX ( "vfx/explosion/vehicle_warbird_explosion_midair" );
	level._effect[ "airstrike_engine" ] 	= LoadFX ( "vfx/fire/jet_afterburner" );
	level._effect[ "airstrike_wingtip" ] = LoadFX ( "vfx/trail/jet_contrail" );

	level.harriers = [];
	level.planes = [];
	level.artilleryDangerCenters = [];

	// airstrike danger area is the circle of radius artilleryDangerMaxRadius 
	// stretched by a factor of artilleryDangerOvalScale in the direction of the incoming airstrike,
	// moved by artilleryDangerForwardPush * artilleryDangerMaxRadius in the same direction.
	// use scr_Airstrikedebug to visualize.
	
	level.dangerMaxRadius["strafing_run_airstrike"] = 900;
	level.dangerMinRadius["strafing_run_airstrike"] = 750;
	level.dangerForwardPush["strafing_run_airstrike"] = 1;
	level.dangerOvalScale["strafing_run_airstrike"] = 6.0;
	
	level.killStreakFuncs["strafing_run_airstrike"] = ::tryUseStrafingRunAirstrike;
	
	level.killstreakWieldWeapons["stealth_bomb_mp"] = "strafing_run_airstrike";	
	level.killstreakWieldWeapons["airstrike_missile_mp"] = "strafing_run_airstrike";
	level.killstreakWieldWeapons["orbital_carepackage_pod_plane_mp"] = "strafing_run_airstrike";
}

tryUseStrafingRunAirstrike( lifeId, modules )
{
	return tryUseAirstrike( lifeId, "strafing_run_airstrike", modules );
}

tryUseAirstrike( lifeId, streakName, modules )
{
	if ( IsDefined( level.strafing_run_airstrike ) )
	{
		self iPrintLnBold( &"KILLSTREAKS_AIR_SPACE_TOO_CROWDED" );
		return false;	
	}
	
	result = self selectAirstrikeLocation( lifeId, streakName, modules );

	if ( !isDefined( result ) || !result )
		return false;
	
	return true;
}

/#
debugLocation( trace, location )
{
	level notify( "debug_airstrike" );
	level endon( "debug_airstrike" );

	while( true )
	{
		if( GetDvarInt( "scr_debugairstrike", 0 ) == 0 )
			return;

		Print3d( level.mapCenter, "Map Center", ( 1, 0, 0 ) );
		Print3d( level.mapCenter, "Map Center origin: " + level.mapCenter[0] + ", " + level.mapCenter[1] + ", " + level.mapCenter[2], ( 1, 0, 0 ) );

		Print3d( location, "Location", ( 1, 0, 0 ) );
		Print3d( location, "Location origin: " + location[0] + ", " + location[1] + ", " + location[2], ( 1, 0, 0 ) );

		Print3d( trace["position"], "Trace Position", ( 1, 0, 0 ) );
		Print3d( trace["position"], "Trace Position origin: " + trace["position"][0] + ", " + trace["position"][1] + ", " + trace["position"][2], ( 1, 0, 0 ) );

		Line( level.mapCenter, trace["position"], ( 0, 0, 1 ) );

		wait( 0.05 );
	}
}

debugFlyHeight( planeFlyHeight )
{
	level endon( "debug_airstrike" );

	while( true )
	{
		if( GetDvarInt( "scr_debugairstrike", 0 ) == 0 )
			return;

		anglesForward = AnglesToForward( level.players[0].angles );
		scalar = (anglesForward[0] * 200, anglesForward[1] * 200, anglesForward[2] );
		Print3d( level.players[0].origin + scalar, "Fly Height: " + planeFlyHeight, ( 1, 0, 0 ) );
		wait( 0.05 );
	}
}
#/

doAirstrike( lifeId, origin, yaw, owner, team, streakName, modules )
{	
	assert( isDefined( origin ) );
	assert( isDefined( yaw ) );

	if ( isDefined( level.airstrikeInProgress ) )
	{
		while ( isDefined( level.airstrikeInProgress ) )
			level waittill ( "begin_airstrike" );

		level.airstrikeInProgress = true;
		wait ( 2.0 );
	}

	if( !isDefined( owner ) )
		return;

	level.airstrikeInProgress = true;
	
	targetpos = dropSiteTrace( origin, owner );
	
	dangerCenter = spawnstruct();
	dangerCenter.origin = targetpos;
	dangerCenter.forward = anglesToForward( (0,yaw,0) );
	dangerCenter.streakName = streakName;
	dangerCenter.team = team;

	level.artilleryDangerCenters[ level.artilleryDangerCenters.size ] = dangerCenter;
	/# level thread debugArtilleryDangerCenters( streakName ); #/
	
	harrierEnt = callStrike( lifeId, owner, targetpos, yaw, streakName, modules );
	
	wait( 1.0 );
	level.airstrikeInProgress = undefined;
	owner notify ( "begin_airstrike" );
	level notify ( "begin_airstrike" );
	
	wait 7.5;

	found = false;
	newarray = [];
	for ( i = 0; i < level.artilleryDangerCenters.size; i++ )
	{
		if ( !found && level.artilleryDangerCenters[i].origin == targetpos )
		{
			found = true;
			continue;
		}
		
		newarray[ newarray.size ] = level.artilleryDangerCenters[i];
	}
	assert( found );
	assert( newarray.size == level.artilleryDangerCenters.size - 1 );
	level.artilleryDangerCenters = newarray;
}


clearProgress( delay )
{
	wait ( 2.0 );
	
	level.airstrikeInProgress = undefined;	
}


/#
debugArtilleryDangerCenters( streakName )
{
	level notify("debugArtilleryDangerCenters_thread");
	level endon("debugArtilleryDangerCenters_thread");
	
	if ( getdvarint("scr_airstrikedebug", 0) != 1 && getdvarint("scr_spawnpointdebug", 0) == 0 )
	{
		return;
	}
	
	while( level.artilleryDangerCenters.size > 0 )
	{
		for ( i = 0; i < level.artilleryDangerCenters.size; i++ )
		{
			origin = level.artilleryDangerCenters[i].origin;
			forward = level.artilleryDangerCenters[i].forward;
			
			origin += forward * level.dangerForwardPush[streakName] * level.dangerMaxRadius[streakName];
			
			previnnerpos = (0,0,0);
			prevouterpos = (0,0,0);
			for ( j = 0; j <= 40; j++ )
			{
				frac = (j * 1.0) / 40;
				angle = frac * 360;
				dir = anglesToForward((0,angle,0));
				forwardPart = vectordot( dir, forward ) * forward;
				perpendicularPart = dir - forwardPart;
				pos = forwardPart * level.dangerOvalScale[streakName] + perpendicularPart;
				innerpos = pos * level.dangerMinRadius[streakName];
				innerpos += origin;
				outerpos = pos * level.dangerMaxRadius[streakName];
				outerpos += origin;
				
				if ( j > 0 )
				{
					line( innerpos, previnnerpos, (1, 0, 0) );
					line( outerpos, prevouterpos, (1,.5,.5) );
				}
				
				previnnerpos = innerpos;
				prevouterpos = outerpos;
			}
		}
		wait .05;
	}
}
#/

getAirstrikeDanger( point )
{
	danger = 0;
	for ( i = 0; i < level.artilleryDangerCenters.size; i++ )
	{
		origin = level.artilleryDangerCenters[i].origin;
		forward = level.artilleryDangerCenters[i].forward;
		streakName = level.artilleryDangerCenters[i].streakName;
		
		danger += getSingleAirstrikeDanger( point, origin, forward, streakName );
	}
	return danger;
}

getSingleAirstrikeDanger( point, origin, forward, streakName )
{
	center = origin + level.dangerForwardPush[streakName] * level.dangerMaxRadius[streakName] * forward;
	
	diff = point - center;
	diff = (diff[0], diff[1], 0);
	
	forwardPart = vectorDot( diff, forward ) * forward;
	perpendicularPart = diff - forwardPart;
	
	circlePos = perpendicularPart + forwardPart / level.dangerOvalScale[streakName];
	
	/* /#
	if ( getdvar("scr_airstrikedebug") == "1" )
	{
		thread airstrikeLine( center, center + perpendicularPart, (1,1,1), 50 );
		thread airstrikeLine( center + perpendicularPart, center + circlePos, (1,1,1), 50 );
		thread airstrikeLine( center + circlePos, point, (.5,.5,.5), 50 );
	}
	#/ */
	
	distsq = lengthSquared( circlePos );
	
	if ( distsq > level.dangerMaxRadius[streakName] * level.dangerMaxRadius[streakName] )
		return 0;
	
	if ( distsq < level.dangerMinRadius[streakName] * level.dangerMinRadius[streakName] )
		return 1;
	
	dist = sqrt( distsq );
	distFrac = (dist - level.dangerMinRadius[streakName]) / (level.dangerMaxRadius[streakName] - level.dangerMinRadius[streakName]);
	
	assertEx( distFrac >= 0 && distFrac <= 1, distFrac );
	
	return 1 - distFrac;
}

pointIsInAirstrikeArea( point, targetpos, yaw, streakName )
{
	return distance2d( point, targetpos ) <= level.dangerMaxRadius[streakName] * 1.25;
	// TODO
	//return getSingleAirstrikeDanger( point, targetpos, yaw ) > 0;
}
			
radiusArtilleryShellshock(pos, radius, maxduration, minduration, team )
{
	players = level.players;
	
	foreach ( player in level.players )
	{
		if ( !isAlive( player ) )
			continue;
			
		if ( player.team == team || player.team == "spectator" )
			continue;
			
		playerPos = player.origin + (0,0,32);
		dist = distance( pos, playerPos );
		
		if ( dist > radius )
			continue;
			
		duration = int(maxduration + (minduration-maxduration)*dist/radius);		
		player thread artilleryShellshock( "default", duration );
	}
}

artilleryShellshock(type, duration)
{
	self endon ( "disconnect" );
	
	if (isdefined(self.beingArtilleryShellshocked) && self.beingArtilleryShellshocked)
		return;
	self.beingArtilleryShellshocked = true;
	
	self shellshock(type, duration);
	wait(duration + 1);
	
	self.beingArtilleryShellshocked = false;
}

/#
airstrikeLine( start, end, color, duration )
{
	frames = duration * 20;
	for ( i = 0; i < frames; i++ )
	{
		line(start,end,color);
		wait .05;
	}
}

traceBomb()
{
	self endon("death");
	prevpos = self.origin;
	while(1)
	{
		thread airstrikeLine( prevpos, self.origin, (.5,1,0), 40 );
		prevpos = self.origin;
		wait .2;
	}
}
#/

bomberDropCarepackges( plane, owner )
{
	plane endon( "death" );
	plane endon( "crashing" );

	dropInfo = SpawnStruct();
	dropInfo.usedNodes = [];
	dropInfo.crateTypes = [];

	waitillAirstrikeOverBombingArea( plane );
	
	wait 0.1;
	
	start = GetTime();
	dropInfo.crateTypes[0] = shootDownCarepackage( plane, owner, dropInfo );
	end = GetTime();
	waitTime = 0.1 - ( ( end - start ) / 1000 );
	if ( waitTime > 0 )
		wait waitTime;
	
	start = GetTime();
	dropInfo.crateTypes[1] = shootDownCarepackage( plane, owner, dropInfo );
	end = GetTime();
	waitTime = 0.1 - ( ( end - start ) / 1000 );
	if ( waitTime > 0 )
		wait waitTime;
	
	dropInfo.crateTypes[2] = shootDownCarepackage( plane, owner, dropInfo );
}
	
shootDownCarepackage( plane, owner, dropInfo )
{
	position = dropSiteTrace( plane.origin, plane );

	// gently influence the drop points to a safe node
	node = findCloseNode( position, plane, dropInfo, plane.dropSite, owner );
	
	if ( !IsDefined( node ) )
	{
		node = SpawnStruct();
		node.origin = position;
	}
	
	dropStart = plane.origin + ( 0, 0, -5 );
	
	return maps\mp\killstreaks\_orbital_carepackage::FirePod( "orbital_carepackage_pod_plane_mp", owner, node, "airdrop_assault", [], undefined, dropStart, dropInfo.crateTypes, false );
}

dropSiteTrace( org, ignoreEnt )
{
	start = org;
	end = start + ( 0, 0, -1000000 );

	trace = BulletTrace( start, end, false, ignoreEnt );
	// check if we've hit a flying scorestreak, if so continue the trace
	ent = trace["entity"];
	while ( IsDefined( ent ) && IsDefined( ent.vehicleType ) )
	{
		waitframe();
		start = trace["position"];
		trace = BulletTrace( start, end, false, ent );
		ent = trace["entity"];
	}
	
	return trace["position"];
}

withinOtherCarepackageNodes( position, dropInfo )
{
	ORBITAL_TRACE_CP_RADIUS = 26;
	
	dist = ORBITAL_TRACE_CP_RADIUS * 2;
	distSqMin = dist * dist;
	
	foreach ( node in dropInfo.usedNodes )
	{
		distSq = Distance2DSquared( node.origin, position );
		if ( distSq < distSqMin )
			return true;
	}
	
	return false;
}

findCloseNode( position, plane, dropInfo, dropSite, owner )
{
	numFrames = 4;
	numTracesPerFrame = 5;
	start = plane.origin;
	
	nodes = GetNodesInRadiusSorted( dropSite, 300, 0, 1000 );
	bestNode = undefined;
	foreach ( node in nodes )
	{
		if ( numFrames <= 0 )
			break;
				
		if ( !NodeExposedToSky( node, true ) )
			continue;
		
		if ( array_contains( dropInfo.usedNodes, node ) )
			continue;
		
		if ( withinOtherCarepackageNodes( node.origin, dropInfo ) )
			continue;
		
		end = node.origin + ( 0, 0, 5 );
		
		passEnt = owner;
		if ( !IsDefined( passEnt ) )
			passEnt = plane;
		
		dropInfo.usedNodes[dropInfo.usedNodes.size] = node;
		if ( BulletTracePassed( start, end, false, plane ) && maps\mp\killstreaks\_orbital_util::carepackageTrace( node.origin, passEnt, "carepackage" ) )
		{
			bestNode = node;
			break;
		}
		numTracesPerFrame--;
		
		if ( numTracesPerFrame <= 0 )
		{
			numFrames--;
			numTracesPerFrame = 5;
			waitframe();
		}
	}

	return bestNode;
}

doBomberStrike( lifeId, owner, bombsite, startPoint, direction, streakName, modules )
{
	if ( !isDefined( owner ) ) 
		return;
	
	if ( !array_contains( modules, "strafing_run_airstrike_two" ) )
	{
		thread spawnAirstrikePlane( lifeId, owner, bombsite, startPoint, direction, streakName, modules, true );
	}
	else
	{
		startPointStruct = SpawnStruct();
		getAdditionalBomberPlaneStarts( startPoint, direction, startPointStruct );
		thread spawnAirstrikePlane( lifeId, owner, bombsite, startPointStruct.startPoint1, direction, streakName, modules, true );
		wait 1;
		thread spawnAirstrikePlane( lifeId, owner, bombsite, startPointStruct.startPoint2, direction, streakName, modules, false );
	}
}

getAdditionalBomberPlaneStarts( startPoint, direction, startPointStruct )
{
	dirRight = AnglesToRight( direction );
	startPointStruct.startPoint1 = startPoint + ( dirRight * CONST_AIRSTRIKE_ADDITIONAL_BOMBER_DIST );
	startPointStruct.startPoint2 = startPoint + ( dirRight * -1 * CONST_AIRSTRIKE_ADDITIONAL_BOMBER_DIST );
}
	
spawnAirstrikePlane( lifeId, owner, bombsite, startPoint, direction, streakName, modules, doCoop )
{
	enemyIcon = "compass_objpoint_airstrike_busy";
	if ( array_contains( modules, "strafing_run_airstrike_stealth" ) )
		enemyIcon = "compass_objpoint_b2_airstrike_enemy";
	
	// Spawn the plane
	plane = Spawn( "script_model", startPoint );
	plane.angles = direction;
	plane SetModel( "vehicle_airplane_shrike" );

/#
	PrintLn( "Strafing Run: fly height = " + startPoint[2] + ", make nosight clip at least 800 units wide." );
#/
	
	plane.minimapIcon = spawnplane( owner, "script_model", startPoint, "compass_objpoint_airstrike_friendly", enemyIcon );
	plane.minimapIcon SetModel( "tag_origin" );
	plane.minimapIcon LinkToSynchronizedParent( plane, "tag_origin", ( 0, 0, 0 ), ( 0, 0, 0 ) );	
	
	plane.modules = modules;
	plane.vehicleType = "strafing_run";
	
	addPlaneToList( plane );
	plane SetCanDamage( true );
	plane SetCanRadiusDamage( true );
	plane thread maps\mp\gametypes\_damage::setEntityDamageCallback( 1000, undefined, ::onAirStrikeDeath, maps\mp\killstreaks\_aerial_utility::heli_ModifyDamage, true );
	if ( array_contains( plane.modules, "strafing_run_airstrike_flares" ) )
		plane thread airstrike_flares_monitor();
	plane thread handleDeath();
	/# plane thread planeDebugCrash();#/

	plane playLoopSound( "bombrun_jet_dist_loop" );
	plane.lifeId = lifeId;
	plane.owner = owner;
	plane.team = owner.team;
	plane.dropSite = bombsite;
	plane.enteringBombingArea = true;

	plane thread planeAnimatePath( bombsite );
	plane thread planePlayEffects();
	
	thread stealthBomber_killCam( plane, streakName );
	
	if ( array_contains( plane.modules, "strafing_run_airstrike_package" ) )
		thread bomberDropCarepackges( plane, owner );
	else
		thread bomberDropBombs( plane, owner );
	
	if ( level.teamBased && doCoop )
		level thread handleCoopJoining( plane, owner );

	plane endon( "death" );
	plane endon( "crashing" );
	
	waitillAirstrikeOverBombingArea( plane );
	plane.enteringBombingArea = false;
	
	plane waittill( "pathComplete" );

	level.strafing_run_airstrike = undefined;
	
	plane notify( "airstrike_complete" );
	
	removePlaneFromList( plane );
	
	plane waittillmatch( "airstrike", "end" );
	
	plane notify( "delete" );
	
	if ( IsDefined( plane.minimapIcon ) )
		plane.minimapIcon Delete();
	plane delete();
}

planeHandleHostMigration()
{
	self endon( "airstrike_complete" );
	self endon( "pathComplete" );
	
	while ( true )
	{
		level waittill( "host_migration_begin" );
		
		self ScriptModelPauseAnim( true );
		
		level waittill( "host_migration_end" );
		
		self ScriptModelPauseAnim( false );
	}
}

planeAnimatePath( dropSite ) // self == plane
{
	self endon( "airstrike_complete" );

	self ScriptModelPlayAnimDeltaMotion( "strafing_run_ks_flyby", "airstrike" );
	self thread planeHandleHostMigration();
	self.status = "flying_in";
	self.flyingSpeed = ( CONST_AIRSTRIKE_FLY_IN_DIST / CONST_AIRSTRIKE_FLY_IN_TIME );
	
	wait CONST_AIRSTRIKE_FLY_IN_TIME;
	
	self.status = "strike";
	self.flyingSpeed = ( CONST_AIRSTRIKE_FLY_THROUGH_DIST / CONST_AIRSTRIKE_FLY_THROUGH_TIME );
	
	wait CONST_AIRSTRIKE_FLY_THROUGH_TIME;
	
	self.status = "flying_out";
	self.flyingSpeed = ( CONST_AIRSTRIKE_FLY_IN_DIST / CONST_AIRSTRIKE_FLY_IN_TIME );
	
	wait CONST_AIRSTRIKE_FLY_IN_TIME - 0.1;
	
	self notify( "pathComplete" );
}

airstrike_flares_monitor() // self == plane
{
	self.numFlares = 4;
	self thread maps\mp\killstreaks\_aerial_utility::handleIncomingStinger();
}

onAirStrikeDeath( attacker, weapon, meansOfDeath, damage ) 
{
	self thread crashPlane();
	
	self maps\mp\gametypes\_damage::onKillstreakKilled( attacker, weapon, meansOfDeath, damage, "strafing_run_destroyed", undefined, "callout_destroyed_airstrike", true );
}

crashPlane() // self == plane
{
	self notify( "crashing" );
	self.crashed = true;
}

bomberDropBombs( plane, owner )
{
	plane endon( "airstrike_complete" );

	while ( !targetIsClose( plane, plane.dropSite, 5000 ) )
		wait ( 0.05 );

	showFx = true;
	sonicBoom = false;

	plane notify ( "start_bombing" );
	
	plane thread playBombFx();
	
	for ( dist = targetGetDist( plane, plane.dropSite ); dist < 5000; dist = targetGetDist( plane, plane.dropSite ) )
	{
		if ( dist < 1500 && !sonicBoom )
		{
			//plane playSound( "veh_b2_sonic_boom" );
			sonicBoom = true;
		}

		showFx = !showFx;
		if ( dist < 4500 )
			plane thread callStrike_bomb( plane.origin, owner, (0,0,0), showFx );
		wait ( 0.1 );
	}
	
	plane notify ( "stop_bombing" );
	
	level.strafing_run_airstrike = undefined;
}

playBombFx() // self == plane
{
	self endon( "stop_bombing" );
	self endon( "airstrike_complete" );
	
	self.bomb_tag_left = Spawn( "script_model", ( 0, 0, 0 ) );
	self.bomb_tag_left SetModel( "tag_origin" );
	self.bomb_tag_left LinkTo( self, "bombaydoor_left_jnt", ( 0, 0, 0 ), ( 0, -90, 0 ) );
	
	self.bomb_tag_right = Spawn( "script_model", ( 0, 0, 0 ) );
	self.bomb_tag_right SetModel( "tag_origin" );
	self.bomb_tag_right LinkTo( self, "bombaydoor_right_jnt", ( 0, 0, 0 ), ( 0, -90, 0 ) );

	for ( ;; )
	{
		playFxOnTag( getfx( "airstrike_bombs" ), self.bomb_tag_left, "tag_origin" );
		playFxOnTag( getfx( "airstrike_bombs" ), self.bomb_tag_right, "tag_origin" );
		
		wait ( 0.5 );
	}
}

stealthBomber_killCam( plane, streakName )
{
	plane endon( "airstrike_complete" );

	plane waittill ( "start_bombing" );

	planedir = anglesToForward( plane.angles );
	
	killCamEnt = spawn( "script_model", plane.origin + (0,0,100) - planedir * 200 );
	plane.killCamEnt = killCamEnt;
	plane.killCamEnt SetScriptMoverKillCam( "airstrike" );
	plane.airstrikeType = streakName;
	killCamEnt.startTime = gettime();
	killCamEnt thread deleteAfterTime( 16.0 );

	killCamEnt linkTo( plane, "tag_origin", (-256,768,768), ( 0,0,0 ) );
}

callStrike_bomb( coord, owner, offset, showFx )
{
	self endon( "airstrike_complete" );

	if ( !isDefined( owner ) || owner isEMPed() || owner isAirDenied() )
	{
		self notify( "stop_bombing" );
		return;
	}
	
	accuracyRadius = 512;
	
	randVec = ( 0, randomint( 360 ), 0 );
	bombPoint = coord + ( AnglesToForward( randVec ) * RandomFloat( accuracyRadius ) );
	trace = bulletTrace( bombPoint, bombPoint + (0,0,-10000), false, self );
	
	bombPoint = trace["position"];

	bombHeight = distance( coord, bombPoint );

	if ( bombHeight > 10000 )
		return;

	wait ( 0.85 * (bombHeight / 2000) );

	if ( !isDefined( owner ) || owner isEMPed() || owner isAirDenied() )
	{
		self notify( "stop_bombing" );
		return;
	}

	if ( showFx )
	{
		playFx( getfx( "airstrike_ground" ), bombPoint );
		level thread maps\mp\gametypes\_shellshock::stealthAirstrike_earthQuake( bombPoint );
	}

	thread playSoundInSpace( "bombrun_snap", bombPoint );
	radiusArtilleryShellshock( bombPoint, 512, 8, 4, owner.team );
	self RadiusDamage( bombPoint + (0,0,16), 896, 300, 50, owner, "MOD_EXPLOSIVE", "stealth_bomb_mp" );
	if( IsDefined( level.ishorde ) && level.ishorde && isdefined( level.flying_attack_drones ) )
	{
		foreach( drone in level.flying_attack_drones )
		{
			if( drone.origin[2] > ( bombPoint[2] - 24 ) && drone.origin[2] < ( bombPoint[2] + 1000 ) && Distance2DSquared( drone.origin, bombPoint ) < ( 90000 ))
				drone DoDamage( RandomIntRange( 50,300 ), bombPoint + (0,0,16), owner, owner, "MOD_EXPLOSIVE", "stealth_bomb_mp" );
		}
	}
}

handleDeath( player ) // self == plane
{
	level endon( "game_ended" );
	self endon( "delete" );

	self waittill_either( "death", "crashing" );
	
	forward = AnglesToForward( self.angles );
	PlayFX( getfx( "airstrike_death" ), self.origin, forward );
	
	playSoundinSpace( "bombrun_air_death", self.origin );
	
	self notify( "airstrike_complete" );
	
	removePlaneFromList( self );
	level.strafing_run_airstrike = undefined;

	if ( IsDefined( self.minimapIcon ) )
		self.minimapIcon Delete();
	
	self delete();
}

addPlaneToList( plane )
{
	level.planes[ level.planes.size ] = plane;
}

removePlaneFromList( plane )
{
	level.planes = array_remove( level.planes, plane );
}

deleteAfterTime( time )
{
	self endon ( "death" );
	wait time;
	
	self delete();
}

planePlayEffects() // self == plane
{
	self endon ( "airstrike_complete" );

	waitframe();
	playfxontag( getfx( "airstrike_engine" ), self, "tag_engine_right" );
	playfxontag( getfx( "airstrike_engine" ), self, "tag_engine_left" );
	playfxontag( getfx( "airstrike_wingtip" ), self, "tag_right_wingtip" );
	playfxontag( getfx( "airstrike_wingtip" ), self, "tag_left_wingtip" );
}

callStrike( lifeId, owner, coord, yaw, streakName, modules )
{	
	thread teamPlayerCardSplash( "used_strafing_run_airstrike", owner, owner.team );

	planeFlyHeight = getPlaneFlyHeight();
		
/#
	if( GetDvarInt( "scr_debugairstrike", 0 ) )
	{
		self thread debugFlyHeight( planeFlyHeight );
	}
#/
	
	owner endon("disconnect");
	
	direction = ( 0, yaw, 0 );
	startPoint = getFlightPath( coord, direction, planeFlyHeight );
	
	level thread doBomberStrike( lifeId, owner, coord, startPoint, direction, streakName, modules );
}

getPlaneFlyHeight()
{
	height_modifier = 0;
	if ( IsDefined( level.airstrikeoverrides) && IsDefined( level.airstrikeoverrides.spawnHeight ) )
		height_modifier = level.airstrikeoverrides.spawnHeight;
	
	heliAnchor = maps\mp\killstreaks\_aerial_utility::getHeliAnchor();
	return heliAnchor.origin[2] + CONST_AIRSTRIKE_Z_OFFSET_FROM_ANCHOR + height_modifier;
}

getFlightPath( coord, direction, planeFlyHeight )
{
	planeHalfDistance = ( getFlightDistance() ) / 2;
	
	startPoint = coord + ( AnglesToForward( direction ) * ( -1 * planeHalfDistance ) );
	startPoint *= (1,1,0);
	startPoint += ( 0, 0, planeFlyHeight );
	
	return startPoint;
}

getFlightDistance()
{
	return ( CONST_AIRSTRIKE_FLY_IN_DIST + CONST_AIRSTRIKE_FLY_IN_DIST + CONST_AIRSTRIKE_FLY_THROUGH_DIST );
}

targetGetDist( other, target )
{
	infront = targetisinfront( other, target );
	if( infront )
		dir = 1;
	else
		dir = -1;
	a = flat_origin( other.origin );
	b = a + ( AnglesToForward( flat_angle( other.angles ) ) * ( dir * 100000 ) );
	point = pointOnSegmentNearestToPoint(a,b, target);
	dist = distance(a,point);

	return dist;
}

targetisclose(other, target, closeDist)
{
	if ( !isDefined( closeDist ) )
		closeDist = 3000;
		
	infront = targetisinfront(other, target);
	if(infront)
		dir = 1;
	else
		dir = -1;
	a = flat_origin(other.origin);
	b = a + ( AnglesToForward( flat_angle( other.angles ) ) * ( dir * 100000 ) );
	point = pointOnSegmentNearestToPoint(a,b, target);
	dist = distance(a,point);
	if (dist < closeDist)
		return true;
	else
		return false;
}

targetisinfront(other, target)
{
	forwardvec = anglestoforward(flat_angle(other.angles));
	normalvec = vectorNormalize(flat_origin(target)-other.origin);
	dot = vectordot(forwardvec,normalvec); 
	if(dot > 0)
		return true;
	else
		return false;
}

waitForAirstrikeCancel()
{
	self endon( "location_selection_complete" );
	self endon( "disconnect" );
	
	self waittill( "stop_location_selection" );
	self setblurforplayer( 0, 0.3 );
	self SetClientOmnvar( "ui_map_location_blocked", 0 );
	
	if ( maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone() > 0 )
		self SwitchToWeapon( self getLastWeapon() );
	
	level.strafing_run_airstrike = undefined;
}

selectAirstrikeLocation( lifeId, streakname, modules )
{
	if( !IsDefined( level.mapSize ) ) // we're probably in a test map
	{
		level.mapSize = 1024;
	}
	
	targetSize = level.mapSize / 6.46875; // 138 in 720
	if ( level.splitscreen )
		targetSize *= 1.5;
	
	level.strafing_run_airstrike = true;
	chooseDirection = true;
	
	numPlanes = 1;
	if ( array_contains( modules, "strafing_run_airstrike_two" ) )
		numPlanes = 2;

	self SetClientOmnvar( "ui_map_location_use_carepackages", array_contains( modules, "strafing_run_airstrike_package" ) );
	self SetClientOmnvar( "ui_map_location_num_planes", numPlanes );
	self SetClientOmnvar( "ui_map_location_height", getPlaneFlyHeight() );
	self _beginLocationSelection( streakname, "map_artillery_selector", chooseDirection, targetSize );
	self thread waitForAirstrikeCancel();
	
	self endon( "stop_location_selection" );
	self endon( "disconnect" );
	
	dropSite = undefined;
	flyYaw = undefined;
	
	validFlightPath = false;
	while ( !validFlightPath )
	{
		// wait for the selection. randomize the yaw if we're not doing a precision airstrike.
		self waittill( "confirm_location", location, directionYaw );
		if ( !chooseDirection )
			directionYaw = 0;
		
		if ( validateFlightLocationAndDirection( location, directionYaw, modules, self ) )
		{
			dropSite = location;
			flyYaw = directionYaw;
			self SetClientOmnvar( "ui_map_location_use_carepackages", false );
			self SetClientOmnvar( "ui_map_location_num_planes", 0 );
			self SetClientOmnvar( "ui_map_location_height", 0 );
			break;
		}
		else
		{
			self thread showBlockedHUD();
		}
	}

	self setblurforplayer( 0, 0.3 );
	self notify( "location_selection_complete" );
	self SetClientOmnvar( "ui_map_location_blocked", 0 );

	self maps\mp\_matchdata::logKillstreakEvent( streakName, dropSite );
	
	self thread finishAirstrikeUsage( lifeId, [dropSite], [flyYaw], streakName, modules );
	return true;
}

showBlockedHUD()
{
	self endon( "location_selection_complete" );
	self endon( "disconnect" );
	self endon( "stop_location_selection" );
	
	self notify( "airstrikeShowBlockedHUD" );
	self endon( "airstrikeShowBlockedHUD" );
	
	if ( self GetClientOmnvar( "ui_map_location_blocked" ) == 0 )
		self PlayLocalSound( "recon_drn_cloak_notready" );
	
	self SetClientOmnvar( "ui_map_location_blocked", 1 );
	
	wait 1.5;
	
	self SetClientOmnvar( "ui_map_location_blocked", 0 );
}

validateFlightLocationAndDirection( loc, yaw, modules, owner )
{
	planeFlyHeight = getPlaneFlyHeight();
	numPlanes = 1;
	if ( array_contains( modules, "strafing_run_airstrike_two" ) )
		numPlanes = 2;
	
	return BombingRunTracePassed( loc, planeFlyHeight, yaw, numPlanes );
}

finishAirstrikeUsage( lifeId, locations, directions, streakName, modules )
{
	self notify( "used" );

	for ( i = 0; i < locations.size; i++ )
	{
		location = locations[i];
		directionYaw = directions[i];
		// find underside of top of skybox
		trace = bullettrace( level.mapCenter + (0,0,1000000), level.mapCenter, false, undefined );
		location = (location[0], location[1], trace["position"][2] - 514);
	
	/#
		if( GetDvarInt( "scr_debugairstrike", 0 ) )
		{
			self thread debugLocation( trace, location );
		}
	#/
			
		thread doAirstrike( lifeId, location, directionYaw, self, self.pers["team"], streakName, modules );
	}
}

waitillAirstrikeOverBombingArea( plane )
{
	plane endon( "airstrike_complete" );

	while ( !targetIsClose( plane, plane.dropSite, 200 ) )
		waitframe();
}

playerDelayControl() // self == player
{
	self endon( "disconnect" );
	
	self freezeControlsWrapper( true );
	wait( 0.5 );
	self freezeControlsWrapper( false );
}

PlayerDoRideKillstreak( plane )
{
	result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak( "coop", false, 0.5 );
	
	if ( result != "success" || !IsDefined( plane ) )
	{
		if ( result != "disconnect" )
		{
			if ( !IsDefined( plane ) )
				self thread playerRemoteKillstreakShowHud();
			self playerReset( false );
			self maps\mp\killstreaks\_coop_util::playerResetAfterCoopStreak();
		}

		self notify( "initRideKillstreak_complete", false );
		return;
	}
	
	self notify( "initRideKillstreak_complete", true );
}

handleCoopJoining( plane, player )
{
	team = player.team;
	
	if ( player.team == "allies" )
	{
		assistVO = "SE_1mc_orbitalsupport_buddyrequest";
		buddyVO = "SE_1mc_orbitalsupport_buddy";
	}
	else // == "axis"
	{
		assistVO = "AT_1mc_orbitalsupport_buddyrequest";
		buddyVO = "AT_1mc_orbitalsupport_buddy";
	}
	
	waittillOverPlayspace( plane );
	
	if ( !IsDefined( plane ) )
		return;
	
//	while ( true )
	{
		id = maps\mp\killstreaks\_coop_util::promptForStreakSupport( team, &"MP_JOIN_STRAFING_RUN", "strafing_run_airstrike_coop_offensive", assistVO, buddyVO, player );
		
		level thread watchForJoin( id, plane, player );
		
		result = waittillPromptComplete( plane, "buddyJoinedStreak" );
		
		maps\mp\killstreaks\_coop_util::stopPromptForStreakSupport( id );
		
		if ( !IsDefined( result) )
			return;
		
		result = waittillPromptComplete( plane, "airstrike_buddy_removed" );
		
		if ( !IsDefined( result ) )
			return;
	}
}

notifyCoopOver( plane )
{
	plane endon( "airstrike_complete" );

	if ( plane.enteringBombingArea )
		waitillAirstrikeOverBombingArea( plane );
	waittillLeftPlayspace( plane, 1.65 );

	plane notify( "coopJoinOver" );
}

waittillOverPlayspace( plane )
{
	timeSec = 1.65; // 0.2 sec weapon switch, 0.2 sec blackout, 1.25 coop join bar
	dir = AnglesToForward( plane.angles );
	
	while ( true )
	{
		waitframe();
		
		if ( !IsDefined( plane ) )
			return;
		
		distToEdge = plane.flyingSpeed * timeSec;
		start = plane.origin + ( dir * distToEdge );
		end = start + ( 0, 0, -10000 );
		trace = BulletTrace( start, end, false, plane );
		
		if ( trace["fraction"] == 1 )
			continue;
		
		pos = trace["position"];
		
		nodes = GetNodesInRadius( pos, 300, 0 );
		
		if ( nodes.size > 0 )
		{
//			Line( start, end, ( 0, 1, 0 ), 1, 0, 1000 );
			break;
		}
	}
}

waittillLeftPlayspace( plane, timeSec )
{
	plane endon( "airstrike_complete" );

	if ( !IsDefined( timeSec ) )
		timeSec = 0;
	
	while ( true )
	{
		waitframe();
		
		distToEdge = plane.flyingSpeed * timeSec;
		dir = AnglesToForward( plane.angles );
		start = plane.origin + ( dir * distToEdge );
		end = start + ( 0, 0, -10000 );
		trace = BulletTrace( start, end, false, plane );
		
		if ( trace["fraction"] == 1 )
			break;
		
		pos = trace["position"];
		
		nodes = GetNodesInRadius( pos, 300, 0 );
		
		if ( nodes.size == 0 )
		{
//			Line( start, end, ( 0, 1, 0 ), 1, 0, 1000 );
			break;
		}
	}
}

waittillFireMissile( plane, player )
{
	player endon( "airstrike_fire" );
	plane endon( "airstrike_complete" );
	
	if ( plane.enteringBombingArea )
		waitillAirstrikeOverBombingArea( plane );
	waittillLeftPlayspace( plane );
}

waittillPromptComplete( plane, text )
{
	plane endon( "airstrike_complete" );
	plane endon( "coopJoinOver" );
	
	plane waittill( text );
	
	return true;
}

watchForJoin( id, plane, primaryPlayer )
{
	buddy = waittillBuddyJoinedAirstrike( id, plane );
	if ( !IsDefined( buddy ) )
		return;
	
	plane notify( "buddyJoinedStreak" );
	level notify( "buddyGO" );
	
	buddy thread PlayerDoRideKillstreak( plane );
	buddy waittill( "initRideKillstreak_complete", success );
	if ( !success )
		return;
	
	buddy playerSaveAngles();
	
	buddy setUsingRemote( "strafing_run" );
	buddy NotifyOnPlayerCommand( "airstrike_fire", "+attack" );
	buddy NotifyOnPlayerCommand( "airstrike_fire", "+attack_akimbo_accessible" );
	missileStrikeTurret = SpawnTurret( "misc_turret", plane GetTagOrigin( "tag_origin" ), "sentry_minigun_mp" );
	missileStrikeTurret TurretFireDisable();
	missileStrikeTurret SetModel( "tag_turret" );
	missileStrikeTurret LinkToSynchronizedParent( plane, "tag_origin", ( 0, 0, 0 ), ( 70, 180, 0 ) );
	buddy PlayerLinkWeaponViewToDelta( missileStrikeTurret, "tag_player", 0, 180, 180, 5, 15, false );
	buddy PlayerLinkedSetViewZNear( false );
	buddy PlayerLinkedSetUseBaseAngleForViewClamp( true );
	buddy RemoteControlTurret( missileStrikeTurret, 60, 45 );

	MissileWeapon = buddy maps\mp\killstreaks\_missile_strike::BuildWeaponSettings( [] );
	MissileEyesInit( buddy, MissileWeapon, primaryPlayer );
	
	waittillFireMissile( plane, buddy );
	if ( IsDefined( buddy ) )
	{
		Earthquake( 0.4, 1, buddy GetViewOrigin(), 300 );
		fireMissile( buddy, missileStrikeTurret, MissileWeapon );
		
		if ( IsDefined( buddy ) )
		{
			buddy maps\mp\killstreaks\_coop_util::playerResetAfterCoopStreak();
			buddy NotifyOnPlayerCommandRemove( "airstrike_fire", "+attack" );
			buddy NotifyOnPlayerCommandRemove( "airstrike_fire", "+attack_akimbo_accessible" );
		}
	}
	missileStrikeTurret Delete();
}

waittillBuddyJoinedAirstrike( id, plane )
{
	plane endon( "airstrike_complete" );
	plane endon( "coopJoinOver" );
	
	thread notifyCoopOver( plane );
	
	buddy = maps\mp\killstreaks\_coop_util::waittillBuddyJoinedStreak( id );
	
	return buddy;
}

fireMissile( player, turret, MissileWeapon )
{
	start = turret GetTagOrigin( "tag_player" );
	dir = AnglesToForward( turret GetTagAngles( "tag_player" ) );
	end = start + ( dir * 10000 );
	rocket = MagicBullet( "airstrike_missile_mp", start, end, player );
	rocket.owner = player;
	waitframe();
	if ( !IsDefined( player ) )
		return;
	player Unlink();
	player RemoteControlTurretOff( turret );
	player SetClientOmnvar( "fov_scale", 65.0 / 15.0 );
	MissileEyesGo( player, rocket, MissileWeapon );
	if ( !IsDefined( player ) )
		return;
	player SetClientOmnvar( "fov_scale", 1.0 );
}

MissileEyesInit( player, MissileWeapon, primaryPlayer )
{
	player thread hudInit( MissileWeapon, primaryPlayer );
	
	player ThermalVisionFOFOverlayOn();

	if ( getDvarInt( "camera_thirdPerson" ) )
		player setThirdPersonDOF( false );
}

MissileEyesGo( player, rocket, MissileWeapon )
{
	player endon ( "joined_team" );
	player endon ( "joined_spectators" );
	player endon ( "player_control_strike_over" );
	player endon ( "disconnect" );
	MissileWeapon endon ( "ms_early_exit" );

	rocket thread maps\mp\killstreaks\_missile_strike::Rocket_CleanupOnDeath();
	player thread maps\mp\killstreaks\_missile_strike::Player_CleanupOnGameEnded( rocket, MissileWeapon );
	player thread maps\mp\killstreaks\_missile_strike::Player_CleanupOnTeamChange( rocket, MissileWeapon );

	player thread hudGo( rocket, MissileWeapon );
			
	player thread playerWaitReset( MissileWeapon );
	
	player CameraLinkTo( rocket, "tag_origin" );
	player ControlsLinkTo( rocket );
	
	player thread maps\mp\killstreaks\_missile_strike::playerWatchForEarlyExit( MissileWeapon );
	
	rocket waittill_notify_or_timeout( "death", 10 );  ///////////////// WAIT DEATH HERE ///////////////////////////////////////////
	
	MissileWeapon notify( "missile_strike_complete" );

	// missile strike needs its own stat
	// is defined check required because remote missile doesnt handle lifetime explosion gracefully
	// instantly deletes its self after an explode and death notify
	//if ( isDefined(rocket) )
		//player maps\mp\_matchdata::logKillstreakEvent( "predator_missile", rocket.origin );
}

playerWaitReset( MissileWeapon )
{
	MissileWeapon waittill_either( "missile_strike_complete", "ms_early_exit" );
	
	self playerReset();
}

playerReset( shouldWait )
{
	self endon( "disconnect" );
	
	if ( !IsDefined( shouldWait ) )
		shouldWait = true;
	
	self ControlsUnlink();
	self freezeControlsWrapper( true );
	self SetClientOmnvar( "fov_scale", 1.0 );
	
	self stopMissileBoostSounds(); // Played by code during the boost

	self maps\mp\killstreaks\_missile_strike::stopMissileBoostSounds(); // Played by code during the boost

	// If a player gets the final kill with a hellfire, level.gameEnded will already be true at this point
	if ( !level.gameEnded || isDefined( self.finalKill ) )
		self maps\mp\killstreaks\_aerial_utility::playerShowFullStatic();
	
	if ( shouldWait )
	{
		wait ( 0.5 );
		maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
	}

	self maps\mp\killstreaks\_missile_strike::remove_hud();
	
	self ThermalVisionFOFOverlayOff();
	
	self CameraUnlink();
		
	self freezeControlsWrapper( false );
	
	if ( self isUsingRemote() )
		self clearUsingRemote();
	
	self playerRestoreAngles();
}

stopMissileBoostSounds()
{
	self StopLocalSound( "bombrun_support_mstrike_boost_shot" );
	self StopLocalSound( "bombrun_support_mstrike_boost_boom" );
	self StopLocalSound( "bombrun_support_mstrike_boost_jet" );
}

hudInit( MissileWeapon, primaryPlayer ) // self == player
{
	self endon( "disconnect" );
	
	self SetClientOmnvar( "ui_predator_missile", 2 );
	self SetClientOmnvar( "ui_coop_primary_num", primaryPlayer GetEntityNumber() );
	
	waitframe(); // Give the handlers time to get in place before we set the rest
	
	self maps\mp\killstreaks\_missile_strike::hud_update_fire_text( undefined, MissileWeapon );
	self maps\mp\killstreaks\_aerial_utility::playerEnableStreakStatic();
}

hudGo( rocket, MissileWeapon ) // self == player
{
	self thread maps\mp\killstreaks\_missile_strike::targeting_hud_init();
	self thread maps\mp\killstreaks\_missile_strike::targeting_hud_think( rocket, MissileWeapon );
}

/#
planeDebugCrash()
{
	self endon( "airstrike_complete" );
	
	while ( true )
	{
		if ( GetDvar( "scr_airstrike_crash", "0" ) != "0" )
		{
			self thread crashPlane();
			SetDvar( "scr_airstrike_crash", "0" );
		}
		
		waitframe();
	}
}
#/