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

EMP_GRENADE_TIME = 3.5;
CONST_default_height_z = 2032;

CONST_streak_static_disabled 	= 0;
CONST_streak_static_enabled 	= 1;
CONST_streak_static_damage 		= 2;
CONST_streak_static_range1 		= 3;
CONST_streak_static_range2 		= 4;
CONST_streak_static_range3 		= 5;
CONST_streak_static_range4 		= 6;
CONST_streak_static_fullnotext	= 7;

CONST_VEHICLE_SPAWNFLAG = 16;

//===========================================
// 				init
//===========================================
init()
{
	if(GetDvarInt("virtuallobbyactive", 0))
		return;

/#
	if ( GetDvar( "scr_scorestreak_skip_aerial", "0" ) != "0" )
		return;
#/
	
	level.helis 		= [];
	level.littleBirds 	= [];
	
	level.heli_leave_nodes = getEntOrStructArray( "heli_leave", "targetname" );
	level.heli_crash_nodes = getEntOrStructArray( "heli_crash_start", "targetname" );
	
	assertEx( level.heli_leave_nodes.size, "No \"heli_leave\" nodes found in map!" );
	assertEx( level.heli_crash_nodes.size, "No \"heli_crash_start\" nodes found in map!" );
	
	level.chopper_fx["explode"]["death"]		= [];
	level.chopper_fx["explode"]["air_death"]	= [];
	level.chopper_fx["damage"]["light_smoke"] 	= loadfx ("vfx/trail/smoke_trail_white_heli_emitter");
	level.chopper_fx["damage"]["heavy_smoke"] 	= loadfx ("vfx/trail/smoke_trail_black_heli_emitter");
	level.chopper_fx["damage"]["on_fire"] 		= loadfx ("vfx/fire/helicopter_damaged_fire_m");
	level.chopper_fx["explode"]["large"] 		= loadfx ("fx/explosions/helicopter_explosion_secondary_small");
//	level.chopper_fx["light"]["left"] 			= loadfx( "vfx/lights/aircraft_light_wingtip_green" );
//	level.chopper_fx["light"]["right"] 			= loadfx( "vfx/lights/aircraft_light_wingtip_red" );
//	level.chopper_fx["light"]["belly"] 			= loadfx( "vfx/lights/aircraft_light_red_blink" );
//	level.chopper_fx["light"]["tail"] 			= loadfx( "vfx/lights/aircraft_light_white_blink" );
	level.chopper_fx["rocketlaunch"]["warbird"] = LoadFX( "vfx/muzzleflash/rocket_launch_air_to_ground" );
	
	level.heli_sound["allies"]["hit"] 			= "warbird_death_explo";
	level.heli_sound["axis"]["hit"] 			= "warbird_death_explo";
//	level.heli_sound["allies"]["hitsecondary"] 	= "cobra_helicopter_secondary_exp";
//	level.heli_sound["axis"]["hitsecondary"] 	= "cobra_helicopter_secondary_exp";
	level.heli_sound["allies"]["spinloop"] 		= "warbird_death_spin_loop";
	level.heli_sound["axis"]["spinloop"] 		= "warbird_death_spin_loop";
//	level.heli_sound["allies"]["spinstart"] 	= "cobra_helicopter_dying_layer";
//	level.heli_sound["axis"]["spinstart"] 		= "cobra_helicopter_dying_layer";
	level.heli_sound["allies"]["crash"] 		= "warbird_air_death";
	level.heli_sound["axis"]["crash"] 			= "warbird_air_death";
	
	level._effect[ "flare" ] = loadfx( "vfx/lensflare/flares_warbird" );
	
	level.heli_attract_strength 		= 1000;
	level.heli_attract_range 			= 4096;
	level.heli_maxhealth 				= 2000;
	level.heli_targeting_delay 			= 0.5;
}


//===========================================
// 				makeHeliType
//===========================================
makeHeliType( heliType, deathFx, lightFXFunc )
{
	level.chopper_fx["explode"]["death"][ heliType ] = loadFx( deathFX );
	level.lightFxFunc[ heliType ] = lightFXFunc;
}


//===========================================
// 				addAirExplosion
//===========================================
addAirExplosion( heliType, explodeFx )
{
	level.chopper_fx["explode"]["air_death"][ heliType ] = loadFx( explodeFx );
}


//===========================================
// 				addToHeliList
//===========================================
addToHeliList()
{
	level.helis[self getEntityNumber()] = self;	
}


//===========================================
// 			removeFromHeliList
//===========================================
removeFromHeliList( entityNumber )
{
	level.helis[entityNumber] = undefined;
}


//===========================================
// 			addToLittleBirdList
//===========================================
addToLittleBirdList( lbType )
{	
	level.littleBirds[self GetEntityNumber()] = self;	
}


//===========================================
// 		removeFromLittleBirdListOnDeath
//===========================================
removeFromLittleBirdListOnDeath( lbType )
{
	entNum = self GetEntityNumber();

	self waittill ( "death" );
	
	level.littleBirds[entNum] = undefined;
}


//===========================================
// 		exceededMaxLittlebirds
//===========================================
exceededMaxLittlebirds( streakName )
{
	if( level.littleBirds.size >= 4 )
	{
		return true;	
	}
	else
	{
		return false;	
	}
}


//===========================================
// 		heli_leave_on_disconnect
//===========================================
heli_leave_on_disconnect( owner )
{
	self endon ( "death" );
	self endon ( "helicopter_done" );

	owner waittill( "disconnect" );
	
	self thread heli_leave();
}


//===========================================
// 		heli_leave_on_changeTeams
//===========================================
heli_leave_on_changeTeams( owner )
{
	self endon ( "death" );
	self endon ( "helicopter_done" );

	owner waittill_any( "joined_team", "joined_spectators" );
	
	self thread heli_leave();
}


//===========================================
// 		heli_ModifyDamage
//===========================================
heli_ModifyDamage( attacker, weapon, type, damage )
{
	modifiedDamage = self maps\mp\gametypes\_damage::modifyDamage( attacker, weapon, type, damage );
	
	if( modifiedDamage > 0 )
		self heli_staticDamage( weapon, type, modifiedDamage );
	
//	self thread heli_addRecentDamage( modifiedDamage );
	
	return modifiedDamage;
}


//===========================================
// 		heli_addRecentDamage
//===========================================
heli_addRecentDamage( damage )
{
	self endon( "death" );

	self.recentDamageAmount += damage;

	wait ( 4.0 );
	
	self.recentDamageAmount -= damage;
}


//===========================================
// 		heli_leave_on_timeout
//===========================================
heli_leave_on_timeout( timeOut )
{
	self endon ( "death" );
	self endon ( "helicopter_done" );
	
	maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeOut );
	
	self thread heli_leave();
}


//===========================================
// 			heli_leave_on_gameended
//===========================================
heli_leave_on_gameended( owner )
{
	self endon ( "death" );
	self endon ( "helicopter_done" );

	level waittill ( "game_ended" );
	
	self thread heli_leave();	
}


//===========================================
// 		heli_leave_on_timeout
//===========================================
heli_leave( leavePos )
{
	self notify( "leaving" );
	
	self.isLeaving = true;

	self ClearLookAtEnt();
	
	leaveNode = undefined;
	
	if( !isDefined( leavePos ) )
	{
		leaveNode = heli_pick_fly_node( level.heli_leave_nodes );
		leavePos = leaveNode.origin;
	}

	// make sure it doesn't fly away backwards
	endEnt = Spawn( "script_origin", leavePos );
	
	if( IsDefined( endEnt ) )
	{
		self SetLookAtEnt( endEnt );
		endEnt thread wait_and_delete( 3.0 );
	}

	self heli_reset();
	self Vehicle_SetSpeed( 100, 45 );	
	if ( IsDefined( leaveNode ) )
	{
		if ( IsDefined( leaveNode.target ) )
		{
			self heli_fly_simple_path( leaveNode );
		}
		else
		{
			self _setVehGoalPos( leaveNode.origin, false );
			self waittillmatch( "goal" );
		}
	}
	else
	{
		self _setVehGoalPos( leavePos, false );
		self waittillmatch( "goal" );
	}
	self notify( "death" );
	
	// give "death" notify time to process
	wait ( 0.05 );

	if( IsDefined( self.killCamEnt ) )
		self.killCamEnt delete();

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

	self delete();
}


//===========================================
// 			heli_pick_fly_node
//===========================================
heli_pick_fly_node( nodes )
{
//	nodes = array_randomize( nodes );
	start = self.origin;
	nextNode = undefined;
	
/#
	if ( GetDvar( "scr_heli_pick_fly_node_debug", "0" ) != "0" )
	{
		foreach ( node in nodes )
			Sphere( node.origin, 100, ( 0, 1, 0 ), false, 300 );
	}
#/

	for ( i = 0; i < nodes.size; i++ )
	{
		end = nodes[i].origin;
		if ( flyNodeOrgTracePassed( start, end, self ) )
		{
			dir = end - start;
			dist = Distance( start, end );
			dirRight = RotateVector( dir, ( 0, 90, 0 ) );
			startWing = start + ( dirRight * 100 );
			endWing = startWing + ( dir * dist );
			if ( flyNodeOrgTracePassed( startWing, endWing, self ) )
			{
				dirLeft = RotateVector( dir , ( 0, -90, 0 ) );
				startWing = start + ( dirLeft * 100 );
				endWing = startWing + ( dir * dist );
				if ( flyNodeOrgTracePassed( startWing, endWing, self ) )
				{
					return nodes[i];
				}
			}
		}
	}
	
	return nodes[ RandomInt( nodes.size ) ];
}

flyNodeOrgTracePassed( start, end, ignoreEnt )
{
	trace = BulletTrace( start, end, false, ignoreEnt, false, false, true, false, false );
	passed = ( trace["fraction"] >= 1 );
/#
	if ( GetDvar( "scr_heli_pick_fly_node_debug", "0" ) != "0" )
	{
		if ( passed )
			Line( start, end, ( 0, 1, 0 ), 1, false, 300 );
		else
			Line( start, end, ( 1, 0, 0 ), 1, false, 300 );
	}
	
	return passed;
#/
}


//===========================================
// 			wait_and_delete
//===========================================
wait_and_delete( waitTime )
{
	self endon( "death" );
	level endon( "game_ended" );
	
	wait( waitTime );
	
	self delete();
}


//===========================================
// 			deleteAfterTime
//===========================================
deleteAfterTime( delay )
{
	wait ( delay );
	
	self delete();
}


//===========================================
// 				heli_reset
//===========================================
heli_reset()
{
	// resets helicopter's motion values
	self clearTargetYaw();
	self clearGoalYaw();
	self Vehicle_SetSpeed( 60, 25 );	
	self setyawspeed( 100, 45, 45 );
	self setmaxpitchroll( 30, 30 );
	self setneargoalnotifydist( 100 );
	self setturningability(1.0);
}


//===========================================
// 			_setVehGoalPos
//===========================================
_setVehGoalPos( goalPosition, shouldStop )
{
	if( !isDefined( shouldStop ) )
		shouldStop = false;
	
	self SetVehGoalPos( goalPosition, shouldStop );
}


//===========================================
// 			heli_flares_monitor
//===========================================
heli_flares_monitor( extraFlares )
{	
	switch( self.heliType )
	{
		default:
			self.numFlares = 1;
			break;
	}
	
	if( IsDefined( extraFlares ) )
		self.numFlares += extraFlares;
	
	self thread handleIncomingStinger();
}



//===========================================
// 			handleIncomingStinger
//===========================================
handleIncomingStinger( functionOverride )
{
	level endon ( "game_ended" );
	self endon ( "death" );
	self endon ( "crashing" );
	self endon ( "leaving" );
	self endon ( "helicopter_done" );	

	for( ;; )
	{
		level waittill ( "stinger_fired", player, missiles );
		
		if( !maps\mp\_stingerm7::anyStingerMissileLockedOn( missiles, self ) )
			continue;
		
		if( !IsDefined( missiles ) )
			continue;
		
		if( IsDefined( functionOverride ) )
		{
			level thread [[ functionOverride ]]( missiles, player, player.team );
		}
		else
		{
			level thread watchMissileProximity( missiles, player, player.team );
		}
	}	
}


//===========================================
// 			watchMissileProximity
//===========================================
watchMissileProximity( missiles, player, missileTeam ) // self == level
{
	foreach( missile in missiles )
		missile thread missileWatchProximity( player, missileTeam, missile.lockedStingerTarget );
}


//===========================================
// 			missileWatchProximity
//===========================================
missileWatchProximity( player, missileTeam, missileTarget )
{
	self endon( "death" );
	missileTarget endon( "death" );

	flaresTime = 5.0;
	flaresDistance = 4000;
	
	while( true )
	{
		if( !isDefined( missileTarget ) )
			break;
			
		center = missileTarget GetPointInBounds( 0, 0, 0 );

		curDist = distance( self.origin, center );
		
		if( IsDefined( missileTarget.player ) )
			missileTarget.player thread doProximityAlarm( self, missileTarget );

		if( curDist < flaresDistance )
		{
			if( missileTarget.numFlares > 0 || IsDefined( missileTarget.flaresTarget ) )
			{
				if ( IsDefined( missileTarget.owner ) && IsWarbird( missileTarget ) )
				{
					if ( missileTarget.numFlares == 2 )
						missileTarget.owner SetClientOmnvar( "ui_warbird_flares", 1 );
					else if ( missileTarget.numFlares == 1 )
						missileTarget.owner SetClientOmnvar( "ui_warbird_flares", 2 );
					missileTarget.owner PlayLocalSound( "paladin_deploy_flares" );
				}
				newTarget = missileTarget deployFlares( flaresTime );
				PlayFXOnTag( getfx( "flare" ), newTarget, "tag_origin" );
				if( !IsDefined( missileTarget.flaresTarget ) )
				{
					missileTarget.numFlares--;
					level thread handleFlaresTimer( missileTarget, newTarget, flaresTime );
				}
				self Missile_SetTargetEnt( newTarget );
				return;
			}
		}		
		wait ( 0.05 );
	}	
}


//===========================================
// 			deployFlares
//===========================================
deployFlares( time ) // self == heli/plane
{
	org = (self GetTagOrigin("tag_origin")) + ( 0, 0, -50 );
	flareObject = Spawn( "script_model", org );
	flareObject SetModel( "tag_origin" );
	flareObject.angles = self.angles;
	
	if ( !IsDefined( self.flaresDeployedYaw ) )
		self.flaresDeployedYaw = RandomFloatRange( -180, 180 );
	else
		self.flaresDeployedYaw = self.flaresDeployedYaw + 90;
	
	vel = AnglesToForward( ( self.angles[0], self.flaresDeployedYaw, self.angles[2] ) );
	vel = vehicleModifyFlareVector( vel );
	
	flareObject MoveGravity( vel, time );
		
	flareObject thread deleteAfterTime( time );

	return flareObject;
}


vehicleModifyFlareVector( velocityVec ) // self == heli/plane
{
	if ( self.vehicleType == "warbird" )
	{
		return ( VectorNormalize( velocityVec + ( 0, 0, -0.2 ) ) * 300 );
	}
	else if ( self.vehicleType == "paladin" )
	{
		return ( VectorNormalize( velocityVec + ( 0, 0, -0.5 ) ) * 2000 );
	}
	else // strafing run
	{
		return ( VectorNormalize( velocityVec + ( 0, 0, -0.4 ) ) * 1000 );
	}
}


//===========================================
// 			handleFlaresTimer
//===========================================
handleFlaresTimer( missileTarget, flaresTarget, flaresTime )
{
	missileTarget endon( "death" );
		
	missileTarget.flaresTarget = flaresTarget;
	
	wait flaresTime;
	
	missileTarget.flaresTarget = undefined;
	
	if ( IsDefined( missileTarget.owner ) && IsWarbird( missileTarget ) )
		missileTarget.owner SetClientOmnvar( "ui_warbird_flares", 0 );
}


//===========================================
// 				HasTag
//===========================================
HasTag( model, tag )
{
	partCount = GetNumParts( model );
	for ( i = 0; i < partCount; i++ )
	{
		if( toLower( GetPartName( model, i)) == toLower( tag ))
			return true;
	}
	return false;
}


//===========================================
// 				IsWarbird
//===========================================
IsWarbird( heli )
{
	return IsDefined( heli.heli_type ) && ( heli.heli_type == "warbird" );
}


//===========================================
// 			doProximityAlarm
//===========================================
doProximityAlarm( missile, heli )
{
	self endon( "disconnect" );
	
	if ( shouldStopProximityAlarm( missile, heli ) || IsDefined( heli.incomingMissileSound ) )
		return;
	
	if ( IsWarbird( heli ) )
		self SetClientOmnvar( "ui_warbird_flares", 3 );
	
	self PlayLocalSound( "mp_aerial_enemy_locked" );
	heli.incomingMissileSound = true;
	
	while( true )
	{
		if( shouldStopProximityAlarm( missile, heli ) )
		{
			self StopLocalSound( "mp_aerial_enemy_locked" );
			heli.incomingMissileSound = undefined;
			return;
		}
		
		waitframe();
	}
}

//===========================================
// 		playerFakeShootPaintMissile
//===========================================
playerFakeShootPaintMissile( soundEnt ) // self == player
{
	dir = VectorNormalize( AnglesToForward( self GetPlayerAngles() ) );
	right = VectorNormalize( AnglesToRight( self GetPlayerAngles() ) );
	start = self GetEye() + ( dir * 100 );
	end = start + ( dir * 20000 );
	trace = BulletTrace( start, end, false );
	
	if ( trace[ "fraction" ] == 1 )
		return;
	
	Earthquake( 0.1, 1, self GetEye(), 500 );
	
	start = self GetEye() + right * -1 * 50;
	end = trace["position"];
	
	rocket = MagicBullet( "paint_missile_killstreak_mp", start, end, self );
	rocket.owner = self;
	rocket thread watchPaintGrenade();
	self thread playerFireSounds( soundEnt, "paladin_threat_bomb_shot_2d", "paladin_threat_bomb_shot_3d" );
}

playerFakeShootPaintGrenadeAtTarget( soundEnt, startPos, targetPos, stunPlayers, vehicle ) // self == player
{
	GRENADE_SPEED = 5000;
	
	Earthquake( 0.2, 1, self GetViewOrigin(), 300 );
			
	grenadeForward = VectorNormalize( targetPos - startPos );
	grenadeVelocity = grenadeForward * GRENADE_SPEED;
	
	grenade = MagicGrenadeManual( "paint_grenade_killstreak_mp", startPos, grenadeVelocity, 2, self );
	grenade.owner = self;
	grenade thread watchPaintGrenade( stunPlayers, vehicle );
	
	self thread playerFireSounds( soundEnt, "recon_drn_launcher_shot_plr", "recon_drn_launcher_shot_npc" );
	self playRumbleOnEntity( "damage_heavy" );
}

playerFakeShootEmpGrenadeAtTarget( soundEnt, startPos, targetPos )
{
	GRENADE_SPEED = 5000;
	
	Earthquake( 0.2, 1, self GetViewOrigin(), 300 );
	
	grenadeForward = VectorNormalize( targetPos - startPos );
	grenadeVelocity = grenadeForward * GRENADE_SPEED;
	
	grenade = MagicGrenadeManual( "emp_grenade_killstreak_mp", startPos, grenadeVelocity, 2, self );
	grenade.owner = self;
	
	self thread playerFireSounds( soundEnt, "recon_drn_launcher_shot_plr", "recon_drn_launcher_shot_npc" );
	self playRumbleOnEntity( "damage_heavy" );
}

playerFireSounds( soundEnt, sound2d, sound3d )
{
	if ( IsDefined( sound3d ) )
		soundEnt PlaySoundOnMovingEnt( sound3d );
	
/#
	if ( GetDvar( "scr_paladin_stay_on_ground", "0" ) != "0" )
		return;
#/
	if ( IsDefined( sound2d ) )
		self PlayLocalSound( sound2d );
}

watchPaintGrenade( stunPlayers, vehicle ) 
{
	if ( !IsDefined( stunPlayers ) )
		stunPlayers = false;
	
	owner = self.owner;
	owner endon( "disconnect" );
	owner endon( "death" );

	self waittill( "explode", position );
	
	if ( owner isEMPed() && IsDefined( level.empEquipmentDisabled ) && level.empEquipmentDisabled )
		return;
	
	detectionGrenadeThink( position, owner, stunPlayers, vehicle );
}

detectionGrenadeThink( position, owner, stunPlayers, vehicle )
{
	if ( !IsDefined( stunPlayers ) )
		stunPlayers = false;
	
	assert( isDefined( owner ) );
	
	foreach ( guy in level.players )
	{
		if ( !IsDefined( guy ) || !isReallyAlive( guy ) || !IsAlliedSentient( owner, guy ) )
		{
			continue;
		}
		
		thread maps\mp\_threatdetection::detection_grenade_hud_effect( guy, position, 1.0, 400 );
		thread maps\mp\_threatdetection::detection_highlight_hud_effect( guy, 5 );
	}
	
	teamPlayers = getPlayersOnTeam( owner.team );

	foreach( guy in level.players )
	{
		if ( !IsDefined( guy ) || !isReallyAlive( guy ) || IsAlliedSentient( owner, guy ) || guy _hasPerk( "specialty_coldblooded" ) )
		{
			continue;
		}
		
		if( Distance( guy.origin, position ) < 400 )
		{		
			guy maps\mp\_threatdetection::addThreatEvent( teamPlayers, 5, "PAINT_GRENADE", true, false );
			owner maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "paint" );
			guy thread detectionGrenadeWatch( owner, 5 );
			guy notify( "paint_marked_target", owner );
			if ( stunPlayers )
				maps\mp\gametypes\_weapons::flashbangPlayer( guy, position, owner );
			if( isdefined( vehicle ) && vehicle.VehName == "recon_uav" ) 
				owner maps\mp\gametypes\_missions::processChallenge( "ch_streak_recon");
		}
	}
}

detectionGrenadeWatch( owner, time ) // self == victim
{
	level endon( "game_ended" );
	self notify( "detectionGrenadeWatch" );
	self endon( "detectionGrenadeWatch" );
	
	// Award XP on tag, unless already tagged by this player
	if ( !IsDefined( self.tagMarkedBy ) || self.tagMarkedBy != owner )
	{
		owner thread maps\mp\_events::killStreakTagEvent();
		owner playRumbleOnEntity( "damage_heavy" );
	}
	
	// Mark the target
	self DesignateFoFTarget( true );
	self.tagMarkedBy = owner;
	
	self waittill_any_timeout( time, "death", "disconnect" );
	
	// Unmark the target
	if ( IsDefined( self ) )
	{
		self DesignateFoFTarget( false );
		self.tagMarkedBy = undefined;
	}
		
}

//===========================================
// 			getPlayersOnTeam
//===========================================
getPlayersOnTeam( team ) // optional: self == player
{
	teammates = [];
	foreach( player in level.players )
	{
		if( player.hasSpawned && isAlive( player ) && team == player.team && ( !IsPlayer( self ) || player != self ) )
		{
			teammates[ teammates.size ] = player;
		}
	}
	
	return teammates;
}


//===========================================
// 		shouldStopProximityAlarm
//===========================================
shouldStopProximityAlarm( missile, heli )
{
	return ( !IsDefined( heli ) || !IsDefined( heli.player ) || !IsDefined( missile ) || IsDefined( heli.flaresTarget ) || !isReallyAlive( self ) || IsDefined( heli.crashed ) || IsDefined( heli.isCrashing ) );
}


//===========================================
// 			heli_staticDamage
//===========================================
heli_staticDamage( weapon, damageType, modifiedDamage )
{
	if( modifiedDamage > 0 && IsDefined( self.owner ) )
	{
		self.owner thread maps\mp\killstreaks\_aerial_utility::playerShowStreakStaticForDamage();
	}
	
	if( modifiedDamage > 0 && IsDefined( self.warbirdBuddyTurret ) && IsDefined( self.warbirdBuddyTurret.owner ) )
	{
		self.warbirdBuddyTurret.owner thread maps\mp\killstreaks\_aerial_utility::playerShowStreakStaticForDamage();
	}
}

//===========================================
// 			heli_monitorEMP
//===========================================
heli_monitorEMP()
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "crashing" );
	self endon( "leaving" );
	
	while( true )
	{
		self waittill( "emp_damage" );
		
		self thread heli_EMPGrenaded();
	}
}

//===========================================
// 			heli_EMPGrenaded
//===========================================
heli_EMPGrenaded()
{
	self notify( "heli_EMPGrenaded" );
	self endon( "heli_EMPGrenaded" );

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

	self.empGrenaded = true;
	
	if( IsDefined( self.mgTurretLeft ) )
		self.mgTurretLeft notify( "stop_shooting" );
	
	if( IsDefined( self.mgTurretRight ) )
		self.mgTurretRight notify( "stop_shooting" );

	wait( EMP_GRENADE_TIME );

	self.empGrenaded = false;
	
	if( IsDefined( self.mgTurretLeft ) )
		self.mgTurretLeft notify( "turretstatechange" );
	
	if( IsDefined( self.mgTurretRight ) )
		self.mgTurretRight notify( "turretstatechange" );
}


//===========================================
// 			heli_existance
//===========================================
heli_existance()
{
	entityNumber = self getEntityNumber();
	
	self waittill_any( "death", "crashing", "leaving" );

	self removeFromHeliList( entityNumber );
	
	self notify( "helicopter_done" );	
}


//===========================================
// 				heli_crash
//===========================================
heli_crash()
{
	self notify( "crashing" );
	
	self ClearLookAtEnt();
	self.isCrashing = true;

	crashNode = heli_pick_fly_node( level.heli_crash_nodes );
	
	if( IsDefined( self.mgTurretLeft ) )
		self.mgTurretLeft notify( "stop_shooting" );
	
	if( IsDefined( self.mgTurretRight ) )
		self.mgTurretRight notify( "stop_shooting" );
	
	self thread heli_spin( 180 );
	self thread heli_secondary_explosions();
	self Vehicle_SetSpeed( 100, 45 );
	if ( IsDefined( crashNode.target ) )
	{
		self heli_fly_simple_path( crashNode );
	}
	else
	{
		self _setVehGoalPos( crashNode.origin, false );
		self waittillmatch( "goal" );
	}	
	self thread heli_explode();
}


//===========================================
// 			heli_secondary_explosions
//===========================================
heli_secondary_explosions()
{
	teamname = self.team;
	
	playFxOnTag( level.chopper_fx["explode"]["large"], self, "tag_engine_left" );
	if ( IsDefined( level.heli_sound[teamname]["hitsecondary"] ) )
		self playSound ( level.heli_sound[teamname]["hitsecondary"] );

	wait ( 3.0 );

	if ( !isDefined( self ) )
		return;
         
	playFxOnTag( level.chopper_fx["explode"]["large"], self, "tag_engine_left" );
	if ( IsDefined( level.heli_sound[teamname]["hitsecondary"] ) )
		self playSound ( level.heli_sound[teamname]["hitsecondary"] );
}


//===========================================
// 				heli_spin
//===========================================
heli_spin( speed )
{
	self endon( "death" );
	
	teamname = self.team;

	// play hit sound immediately so players know they got it
	self playSound ( level.heli_sound[teamname]["hit"] );
	
	// play heli crashing spinning sound
	self thread spinSoundShortly();
	
	// spins until death
	self setyawspeed( speed, speed, speed );
	while ( isdefined( self ) )
	{
		self settargetyaw( self.angles[1]+(speed*0.9) );
		wait ( 1 );
	}
}


//===========================================
// 				spinSoundShortly
//===========================================
spinSoundShortly()
{
	self endon("death");
	
	wait .25;
	teamname = self.team;
	self stopLoopSound();
	wait .05;
	self playLoopSound( level.heli_sound[teamname]["spinloop"] );
	wait .05;
	if ( IsDefined( level.heli_sound[teamname]["spinstart"] ) )
		self playLoopSound( level.heli_sound[teamname]["spinstart"] );
}


//===========================================
// 				heli_explode
//===========================================
heli_explode( altStyle )
{
	self notify( "death" );
	
	if( isDefined( altStyle ) && isDefined( level.chopper_fx["explode"]["air_death"][self.heli_type] ) )
	{
		deathAngles = self getTagAngles( "tag_deathfx" );
		
		playFx( level.chopper_fx["explode"]["air_death"][self.heli_type], self getTagOrigin( "tag_deathfx" ), anglesToForward( deathAngles ), anglesToUp( deathAngles ) );
	}
	else
	{
		org = self.origin;	
		forward = ( self.origin + ( 0, 0, 1 ) ) - self.origin;
		playFx( level.chopper_fx["explode"]["death"][self.heli_type], org, forward );
	}
	
	// play heli explosion sound
	teamname = self.team;
	self playSound( level.heli_sound[teamname]["crash"] );

	// give "death" notify time to process
	wait ( 0.05 );
	
	if( IsDefined( self.killCamEnt ) )
		self.killCamEnt delete();

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

	self delete();
}


//===========================================
// 			heli_fly_simple_path
//===========================================
heli_fly_simple_path( startNode )
{
	self endon ( "death" );
	self endon ( "leaving" );

	// only one thread instance allowed
	self notify( "flying");
	self endon( "flying" );
	
	heli_reset();
	
	currentNode = startNode;
	while ( isDefined( currentNode.target ) )
	{
		nextNode = getEntOrStruct( currentNode.target, "targetname" );
		assertEx( isDefined( nextNode ), "Next node in path is undefined, but has targetname. Bad Node Position: " + currentNode.origin );
		
		if( isDefined( currentNode.script_airspeed ) && isDefined( currentNode.script_accel ) )
		{
			heli_speed = currentNode.script_airspeed;
			heli_accel = currentNode.script_accel;
		}
		else
		{
			heli_speed = 30 + randomInt(20);
			heli_accel = 15 + randomInt(15);
		}
		
		if( isDefined( self.isAttacking ) && self.isAttacking )
		{
			wait ( 0.05 );
			continue;
		}
		
		if( isDefined( self.isPerformingManeuver  ) && self.isPerformingManeuver )
		{
			wait ( 0.05 );
			continue;
		}

		self Vehicle_SetSpeed( heli_speed, heli_accel );
		
		// end of the path
		if ( !isDefined( nextNode.target ) )
		{
			self _setVehGoalPos( nextNode.origin+(self.zOffset), false );
			self waittill( "near_goal" );
		}
		else
		{
			self _setVehGoalPos( nextNode.origin+(self.zOffset), false );
			self waittill( "near_goal" );

			self setGoalYaw( nextNode.angles[ 1 ] );

			self waittillmatch( "goal" );
		}

		currentNode = nextNode;
	}
	
	printLn( currentNode.origin );
	printLn( self.origin );
}


//===========================================
// 			handle_player_starting_aerial_view
//===========================================
handle_player_starting_aerial_view()
{
	self notify( "player_start_aerial_view" );
}


//===========================================
// 			handle_player_ending_aerial_view
//===========================================
handle_player_ending_aerial_view()
{
	self notify( "player_stop_aerial_view" );
}


//===========================================
// 				getHeliAnchor
//===========================================
getHeliAnchor()
{
	if ( IsDefined( level.heliAnchor ) )
		return level.heliAnchor;
	
	warbirdAnchor = getEntOrStruct( "warbird_anchor", "targetname" );
	
	if ( !IsDefined( warbirdAnchor ) )
	{
		PrintLn( "WARNING: need a struct or entity with targetname warbird_anchor in the center of the map." );
		warbirdAnchor					= SpawnStruct();
		warbirdAnchor.origin			= ( 0, 0, CONST_default_height_z );
		warbirdAnchor.targetname		= "warbird_anchor";
	}
	
	if ( !IsDefined( warbirdAnchor.script_noteworthy ) )
		warbirdAnchor.script_noteworthy = 3500;
	
	level.heliAnchor = warbirdAnchor;
	
	return level.heliAnchor;
}


//===========================================
// 		playerHandleBoundaryStatic
//===========================================
playerHandleBoundaryStatic( vehicle, endonString1, endonString2 )
{
	if ( IsDefined( endonString1 ) )
		self endon( endonString1 );
	if ( IsDefined( endonString2 ) )
		self endon( endonString2 );
	
	outOfBoundsTriggers = GetEntArray( "remote_heli_range", "targetname" );
	if ( !IsDefined( vehicle.vehicleType ) || outOfBoundsTriggers.size == 0 )
	{
		self playerHandleBoundaryStaticRadius( vehicle, endonString1, endonString2 );
		return;
	}
	
	while ( true )
	{
		leaving = vehicle vehicleTouchingAnyTrigger( outOfBoundsTriggers );
		
		if ( leaving )
		{
			self thread playerStartOutOfBoundsStatic( vehicle, endonString1, endonString2 );
			while ( true )
			{
				waitframe();
				if ( !IsDefined( vehicle.alwaysStaticOut ) || !vehicle.alwaysStaticOut )
				{
					back = !vehicle vehicleTouchingAnyTrigger( outOfBoundsTriggers );
					if ( back )
					{
						vehicle notify( "staticDone" );
						self thread playerStaticToNormal( vehicle, endonString1, endonString2 );
						break;
					}
				}
			}
		}
		
		waitframe();
	}
}

vehicleTouchingAnyTrigger( triggers ) // self == vehicle
{
	foreach ( trigger in triggers )
	{
		if ( self IsTouching( trigger ) )
			return true;
	}
	
	return false;
}

playerStaticToNormal( vehicle, endonString1, endonString2 )
{
	if ( IsDefined( endonString1 ) )
		self endon( endonString1 );
	if ( IsDefined( endonString2 ) )
		self endon( endonString2 );
	
	vehicle endon( "staticStarting" );
	vehicle.staticLevel--;
	
	while ( vehicle.staticLevel > 0 )
	{
		self playerShowStreakStaticForRange( vehicle.staticLevel );	
		if ( IsDefined( vehicle.buddy ) )
			vehicle.buddy playerShowStreakStaticForRange( vehicle.staticLevel );
		wait 0.5;
		vehicle.staticLevel--;
	}
	
	self playerShowStreakStaticForRange( 0 );
	if ( IsDefined( vehicle.buddy ) )
		vehicle.buddy playerShowStreakStaticForRange( 0 );
}

playerStartOutOfBoundsStatic( vehicle, endonString1, endonString2 )
{
	if ( IsDefined( endonString1 ) )
		self endon( endonString1 );
	if ( IsDefined( endonString2 ) )
		self endon( endonString2 );
	
	vehicle notify( "staticStarting" );
	vehicle endon( "staticDone" );
	
	if ( !IsDefined( vehicle.staticLevel ) || vehicle.staticLevel == 0 )
		vehicle.staticLevel = 1;
	
	while ( vehicle.staticLevel < 4 )
	{
		self playerShowStreakStaticForRange( vehicle.staticLevel );
		if ( IsDefined( vehicle.buddy ) )
			vehicle.buddy playerShowStreakStaticForRange( vehicle.staticLevel );

		if ( IsDefined( vehicle.PlayerAttachPoint ) )
    		vehicle.PlayerAttachPoint PlaySound( "mp_warbird_outofbounds_warning" );	
		
    	if ( IsDefined( vehicle.staticLevelWaitTime ) )
    		wait vehicle.staticLevelWaitTime;
    	else
			wait 2;		
		vehicle.staticLevel++;
	}
	
	vehicle notify( "outOfBounds" );
}

playerHandleBoundaryStaticRadius( vehicle, endonString1, endonString2 )
{
	if ( IsDefined( endonString1 ) )
		self endon( endonString1 );
	if ( IsDefined( endonString2 ) )
		self endon( endonString2 );
	
	WarbirdAnchor		= getHeliAnchor();
	levelBoundaryRadius = Int( WarbirdAnchor.script_noteworthy );
	
	while ( true )
	{
		DistanceFromCenter = Distance( WarbirdAnchor.origin, vehicle.origin );
		
		if ( DistanceFromCenter < levelBoundaryRadius )
		{
			self playerShowStreakStaticForRange( 0 );
		}
		else if ( DistanceFromCenter > levelBoundaryRadius && DistanceFromCenter < levelBoundaryRadius + 500 )
	    {
			self playerShowStreakStaticForRange( 1 );
	    	if ( IsDefined( vehicle.PlayerAttachPoint ) )
	    		vehicle.PlayerAttachPoint PlaySound( "mp_warbird_outofbounds_warning" );	
	    }
		else if ( DistanceFromCenter > levelBoundaryRadius + 500 && DistanceFromCenter < levelBoundaryRadius + 1000 )
	    {
			self playerShowStreakStaticForRange( 2 );
	    	if ( IsDefined( vehicle.PlayerAttachPoint ) )
	    		vehicle.PlayerAttachPoint PlaySound( "mp_warbird_outofbounds_warning" );	
	    }
		else if ( DistanceFromCenter > levelBoundaryRadius + 1000 && DistanceFromCenter < levelBoundaryRadius + 1500 )
	    {
			self playerShowStreakStaticForRange( 3 );
	    	if ( IsDefined( vehicle.PlayerAttachPoint ) )
	    		vehicle.PlayerAttachPoint PlaySound( "mp_warbird_outofbounds_warning" );	
	    }
		else
		{
			self playerShowStreakStaticForRange( 4 );
			vehicle notify( "outOfBounds" );
		}
		
		wait 0.5;
	}
}

playerEnableStreakStatic()
{	
	self notify( "playerUpdateStreakStatic" );
	self SetClientOmnvar( "ui_streak_overlay_state", CONST_streak_static_enabled );
}

playerDisableStreakStatic()
{
	self notify( "playerUpdateStreakStatic" );
	self SetClientOmnvar( "ui_streak_overlay_state", CONST_streak_static_disabled );
}

playerShowFullStatic()
{
	self notify( "playerUpdateStreakStatic" );
	self SetClientOmnvar( "ui_streak_overlay_state", CONST_streak_static_fullnotext );
}

playerShowStreakStaticForDamage()
{
	self endon( "disconnect" );
	
	// Only allow damage if in the base 'enabled' state
	if ( self GetClientOmnvar( "ui_streak_overlay_state" ) != CONST_streak_static_enabled )
		return;
	
	self notify( "playerUpdateStreakStatic" );
	self endon( "playerUpdateStreakStatic" );
	
	self SetClientOmnvar( "ui_streak_overlay_state", CONST_streak_static_damage );
	wait( 1 );
	self SetClientOmnvar( "ui_streak_overlay_state", CONST_streak_static_enabled );
}

playerShowStreakStaticForRange( rangeIndex )
{
	assert( rangeIndex >= 0 && rangeIndex <= 4 );
	
	targetIndex = CONST_streak_static_enabled;
	switch( rangeIndex )
	{
		case 0: targetIndex = CONST_streak_static_enabled; break;
		case 1: targetIndex = CONST_streak_static_range1; break;
		case 2: targetIndex = CONST_streak_static_range2; break;
		case 3: targetIndex = CONST_streak_static_range3; break;
		case 4: targetIndex = CONST_streak_static_range4; break;
		default:
			AssertMsg( "Unhandled range for playerShowStreakStaticForRange" );
	}
	
	self notify( "playerUpdateStreakStatic" );
	self SetClientOmnvar( "ui_streak_overlay_state", targetIndex );
}

getEntOrStruct( name, type )
{
	ent = GetEnt( name, type );
	if ( IsDefined( ent ) )
		return ent;
	return getstruct( name, type );
}

getEntOrStructArray( name, type )
{
	structArray = getstructarray( name, type );
	entArray = GetEntArray( name, type );
	if ( entArray.size > 0 )
		structArray = array_combine( structArray, entArray );
	return structArray;
}

//===========================================
// 		playerHandleKillVehicle
//===========================================

playerHandleKillVehicle( vehicle, endonString1, endonString2 )
{
	if ( IsDefined( endonString1 ) )
		self endon( endonString1 );
	if ( IsDefined( endonString2 ) )
		self endon( endonString2 );
	
	if( !isDefined( level.vehicle_kill_triggers ) )
		return;
	
	while ( true )
	{
		inDeathTrigger = vehicle vehicleTouchingAnyTrigger( level.vehicle_kill_triggers );
		
		if ( inDeathTrigger )
		{
			vehicle notify( "death" );
		}
		
		waitframe();
	}
}

setup_kill_drone_trig( name, type )
{
	if ( isdefined( name ) && isdefined( type ) )
	{
		ents = getentarray( name, type );
		array_thread( ents, ::setup_kill_drone_trig_proc );
	}
	else if( self isVehicleKillTrigger() )
		self setup_kill_drone_trig_proc();
}

setup_kill_drone_trig_proc()
{
	if( self isVehicleKillTrigger()  )
	{		 
		if( !isDefined( level.vehicle_kill_triggers ) )
			level.vehicle_kill_triggers = [];
		
		level.vehicle_kill_triggers[ level.vehicle_kill_triggers.size ] = self;
	}
}

isVehicleKillTrigger()
{
	if( isDefined( self.classname ) && IsSubStr( self.classname, "trigger_multiple" ) && isDefined( self.spawnflags ) && (self.spawnflags & CONST_VEHICLE_SPAWNFLAG )  )
	{
		return true;
	}
	
	return false;
}

vehicleIsCloaked() // self == vehicle
{
	return ( IsDefined( self.cloakstate ) && self.cloakstate < 1 );
}

thermalVision( endonString, adsAperature, adsFocalDistance, normalAperature, normalFocalDistance, focusSpeed, aperatureSpeed )
{
	self endon( endonString );
	
	inverted = false;
	
	disableOrbitalThermal( self );	
	self VisionSetThermalForPlayer( "default", 0.25 );
	self SetClientOmnvar( "ui_killstreak_optic", false );
	
	if ( IsBot( self ) )
		return;
	
	self notifyOnPlayerCommand( "switch thermal", "+actionslot 1" );
	self thread playerCleanupThermalVisionCommands( endonString );
	
	while ( true )
	{
		self waittill ( "switch thermal" );
		
		if ( !inverted )
		{
			enableOrbitalThermal( self, endonString, adsAperature, adsFocalDistance, normalAperature, normalFocalDistance, focusSpeed, aperatureSpeed );
			self SetClientOmnvar( "ui_killstreak_optic", true );
			self PlayLocalSound( "paladin_toggle_flir_plr" );
		}
		else
		{
			disableOrbitalThermal( self );
			self SetClientOmnvar( "ui_killstreak_optic", false );
			self PlayLocalSound( "paladin_toggle_flir_plr" );
		}
		
		inverted = !inverted;
	}
}

playerCleanupThermalVisionCommands( endonString )
{
	self endon( "disconnect" );
	
	self waittill( endonString );
	
	self NotifyOnPlayerCommandRemove( "switch thermal", "+actionslot 1" );
}

disableOrbitalThermal( ent )
{	
	ent ThermalVisionOff();
	ent notify ( "thermal_vision_off" );
	ent DisablePhysicalDepthOfFieldScripting();
	
	ent.orbitalThermalMode = false;
}

enableOrbitalThermal( ent, endonString, adsAperature, adsFocalDistance, normalAperature, normalFocalDistance, focusSpeed, aperatureSpeed )
{
	ent endon( "disconnect" );
	ent endon( "death" );
	ent endon( "faux_spawn" );
	ent endon( endonString );
		
	// Should be initialized in opticsthermal_think() in _opticsthermal.gsc
	if ( !IsDefined( ent.opticsThermalEnabled ) )
		ent.opticsThermalEnabled = false;
	if ( !IsDefined( ent.orbitalThermalMode ) )
		ent.orbitalThermalMode = false;
		
	ent.orbitalThermalMode = true;	
	while ( ent.opticsThermalEnabled )
		wait 0.05;
	
	ent ThermalVisionOn();
	ent EnablePhysicalDepthOfFieldScripting(3);
	ent thread setThermalDOF( endonString, adsAperature, adsFocalDistance, normalAperature, normalFocalDistance, focusSpeed, aperatureSpeed );
}

setThermalDOF( endonString, adsAperature, adsFocalDistance, normalAperature, normalFocalDistance, focusSpeed, aperatureSpeed )
{
	self endon ( endonString );
	self endon( "disconnect" );
	self endon( "thermal_vision_off" );
		
	while( 1 )
	{
		adsAmt = self playerAds();
		aperature = float_lerp( normalAperature, adsAperature, adsAmt );
		focalDistance = float_lerp( normalFocalDistance, adsFocalDistance, adsAmt );
		self SetPhysicalDepthOfField( aperature, focalDistance, focusSpeed, aperatureSpeed );
		wait 0.1;
	}
}

float_lerp( start, end, percent )
{
     return (start + percent*(end - start));
}

patchHeliLoopNode( nodeOrigin, newNodeOrigin )
{
	checkedNodes = [];
	nextNode = getEntOrStruct( "heli_loop_start", "targetname" );
	
	while ( true )
	{
		if ( array_contains( checkedNodes, nextNode ) )
			break;
		
		if ( nextNode.origin == nodeOrigin )
		{
			nextNode.origin = newNodeOrigin;
			return;
		}
		
		checkedNodes[checkedNodes.size] = nextNode;
		
		nextNode = getEntOrStruct( nextNode.target, "targetname" );
	}
	
	AssertMsg( "Could not patch node with origin " + nodeOrigin + " because it can't be found." );
}