/*
	Explosive Drone
	Based on Tracking Drone and Tridrone code
	Description: A piece of equipment the sticks to a surface and tracks then explodes near a player when he gets close
	
	TASK: The model is a variable grenade, but the FX are based on an old drone model. Look at _variable_grenade.gsc -> tracking_grenade_think( attacker ) to start to attack the problem. It uses a different tracking algorthm,
			so there will be some work to merge that into this.
*/

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

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

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

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

CONST_ACTIVATION_TIME = 3;
CONST_Mine_TriggerRadius = 192;
CONST_Mine_TriggerHeight = 192;

CONST_TRACKING_TIME = 3000;
CONST_EXPLODE_PAUSE = 0.5;
CONST_REMOTE_DETONATE_HOLD_TIME = 1;

message_alpha = 1;
message_fade_time = 0.1;
explosive_drone_pickup_string = &"MP_PICKUP_EXPLOSIVE_DRONE";



watchExplosiveDroneUsage()	// self == player
{
	self endon( "spawned_player" );
	self endon( "disconnect" );
	self endon( "death" );
	self endon( "faux_spawn" );
	
	if ( !IsDefined( level.explosiveDroneSettings ) )
	{
		explosiveDroneInit();
	}

	while ( true )
	{
		self waittill( "grenade_fire", grenade, weapname );
		
		shortWeaponName = maps\mp\_utility::strip_suffix( weapname, "_lefthand" );

		if ( shortWeaponName == "explosive_drone_mp" )
		{
			grenade.team = self.team;
			if(!IsDefined(grenade.owner))
			{
				grenade.owner = self;
			}
			
			if(!IsDefined(grenade.weaponname))
			{
				grenade.weaponname = weapname;
			}
			grenade thread explosiveDroneLink();
		}
	}
}

explosiveDroneLink() // self == grenade
{
	self thread watchForStick();
	
	// grenade may not have its model immediately after "grenade_fire" so wait before linking to it
	wait( 0.1 );

	if ( IsDefined ( self ) )
	{
		self.explosiveDrone = Spawn( "script_model", self.origin );
		self.explosiveDrone.targetname = "explosive_drone_head_model";
		self.explosiveDrone SetModel( level.explosiveDroneSettings.modelBase );
		self.explosiveDrone.oldContents = self.explosiveDrone SetContents( 0 );
		self.explosiveDrone LinkTo( self, "tag_spike", (0,0,0), (0,0,0) );
		self.explosiveDrone.owner = self.owner;
		
		explosive_drone_model = self.explosiveDrone;
		
		explosive_drone_model thread cleanup_on_grenade_death( self );
		
		self thread monitorSpikeDestroy();

		self thread monitorHeadDestroy();
	}
}

cleanup_on_grenade_death( grenade )
{
	grenade waittill( "death" );
	
	if ( IsDefined( self ) )
		self Delete();
}

explosiveGrenadeDeath( attacker, weapon, meansOfDeath, damage )
{
	if ( IsDefined( self ) )
	{
		self notify("death");
	
		// play explosion effect and do radius damage if attacker is defined
		// play spark effect if no attacker
		if (IsDefined (self.explosiveDrone) )
		{
			self.explosiveDrone deleteExplosiveDrone();
		}
	
		self delete();
	}
}

explosiveHeadDeath( attacker, weapon, meansOfDeath, damage )
{	
	if (IsDefined (self) )
    {
		self delete();
	}	
}

explosiveDroneInit()
{
	level.explosiveDroneMaxPerPlayer = 1;

	level.explosiveDroneSettings = SpawnStruct();
	level.explosiveDroneSettings.timeOut =				20.0;	
	level.explosiveDroneSettings.ExplosiveTimeOut =		30.0;	
	level.explosiveDroneSettings.health =				60; // keep it from dying anywhere in code	
	level.explosiveDroneSettings.maxHealth =			60; // this is what we check against for death	
	level.explosiveDroneSettings.vehicleInfo =			"vehicle_tracking_drone_mp";
	level.explosiveDroneSettings.modelBase =			"npc_drone_explosive_main";
	level.explosiveDroneSettings.fxId_sparks =			Loadfx( "vfx/sparks/direct_hack_stun" );
	level.explosiveDroneSettings.fxId_laser_glow =		LoadFx( "vfx/lights/tracking_drone_laser_blue" );
	level.explosiveDroneSettings.fxId_explode =			LoadFX( "vfx/explosion/explosive_drone_explosion" );	
	level.explosiveDroneSettings.fxId_LethalExplode =	LoadFX( "vfx/explosion/explosive_drone_explosion" );	
//	level.explosiveDroneSettings.fxId_warning =			LoadFX( "vfx/lights/light_tracking_drone_blink_warning" );
	level.explosiveDroneSettings.fxId_enemy_light =		LoadFX( "vfx/lights/light_explosive_drone_beacon_enemy" );
	level.explosiveDroneSettings.fxId_friendly_light =	LoadFX( "vfx/lights/light_explosive_drone_beacon_friendly" );
//	level.explosiveDroneSettings.fxId_thruster_down =	LoadFX( "vfx/distortion/tracking_drone_distortion_down" );
//	level.explosiveDroneSettings.fxId_thruster_up =		LoadFX( "vfx/distortion/tracking_drone_distortion_up" );
	level.explosiveDroneSettings.fxId_engine_distort =	LoadFX( "vfx/distortion/tracking_drone_distortion_hemi" );
	level.explosiveDroneSettings.fxId_launch_thruster = LoadFX( "vfx/trail/explosive_drone_thruster_large" );
	level.explosiveDroneSettings.fxId_position_thruster = LoadFX( "vfx/trail/explosive_drone_thruster_small");
	level.explosiveDroneSettings.sound_explode =		"wpn_explosive_drone_exp";	
	level.explosiveDroneSettings.sound_lock =			"wpn_explosive_drone_lock";	
	level.explosiveDroneSettings.sound_launch =			"wpn_explosive_drone_open";	
	//level.explosiveDroneSettings.dome = LoadFX( "vfx/unique/orbital_dome_ground_friendly" );

	foreach ( player in level.players )
	{
		player.is_being_tracked = false;
	}
	
	level thread onExplosivePlayerConnect();
	
	level.explosiveDroneTimeout = level.explosiveDroneSettings.timeOut;
	level.explosiveDroneTimeout = level.explosiveDroneSettings.ExplosiveTimeOut;
	level.explosiveDroneDebugPosition = 0;
	level.explosiveDroneDebugPositionForward = BALL_DRONE_FORWARD_OFFSET;
	level.explosiveDroneDebugPositionHeight = BALL_DRONE_SIDE_OFFSET;

	/*
	/#
	SetDevDvarIfUninitialized( "scr_explosivedrone_timeout", 20.0 );
	SetDevDvarIfUninitialized( "scr_explosivedrone_debug_position", 0 );
	SetDevDvarIfUninitialized( "scr_explosivedrone_debug_position_forward", 70.0 );
	SetDevDvarIfUninitialized( "scr_explosivedrone_debug_position_height", 70.0 );
	SetDevDvarIfUninitialized( "scr_explosivedrone_debug_spawn_position_forward", 70.0 );
	SetDevDvarIfUninitialized( "scr_explosivedrone_debug_spawn_position_height", 70.0 );
	SetDevDvarIfUninitialized( "scr_explosivedrone_max_per_player", 1 );
	
	level thread checkDvars();
	*/
}

/*

checkDvars()
{
	while(true)
	{
		level.explosiveDroneTimeout = GetDvarFloat( "scr_explosivedrone_timeout", level.explosiveDroneSettings.timeOut );
		level.explosiveDroneDebugPosition = GetDvarInt( "scr_explosiveDrone_debug_position" );
		level.explosiveDroneDebugPositionForward = GetDvarFloat( "scr_explosiveDrone_debug_position_forward" );
		level.explosiveDroneDebugPositionHeight = GetDvarFloat( "scr_explosiveDrone_debug_position_height" );
		level.explosiveDroneDebugSpawnPositionForward = GetDvarFloat( "scr_explosiveDrone_debug_spawn_position_forward" );
		level.explosiveDroneDebugSpawnPositionHeight = GetDvarFloat( "scr_explosiveDrone_debug_spawn_position_height" );
		level.explosiveDroneMaxPerPlayer = GetDvarInt( "scr_explosiveDrone_debug_position" );
		wait( 1.0 );
	}
}

*/

tryUseExplosiveDrone( grenade ) // self == player
{		
	
	numIncomingVehicles = 1;
	if ( self isUsingRemote() )
	{
		return false;
	}	
	else if ( exceededMaxExplosiveDrones( ) )
	{
		self IPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
		return false;
	}
	else if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
	{
		self IPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
		return false;
	}
	
	// Force init (this is for test bots)
	if ( !IsDefined( self.explosiveDroneArray ) )
	{
		self.explosiveDroneArray = [];
	}
	
	// Limit the player to a max of 2 drones
	if ( self.explosiveDroneArray.size )
	{
		self.explosiveDroneArray = array_removeUndefined( self.explosiveDroneArray );
		
		if ( self.explosiveDroneArray.size >= level.explosiveDroneMaxPerPlayer )
		{					
			if ( IsDefined( self.explosiveDroneArray[0] ) )
			{
				self.explosiveDroneArray[0] thread explosiveDrone_leave();			
			}
		}
	}

	// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
	incrementFauxVehicleCount();
	
	explosiveDrone = grenade createExplosiveDrone();
	
	if( !IsDefined( explosiveDrone ) )
	{
		// decrement the faux vehicle count since this failed to spawn
		decrementFauxVehicleCount();

		return false;	
	}
	
	self playsound( level.explosiveDroneSettings.sound_launch );
	
	self playsound( level.explosiveDroneSettings.sound_lock );
	
	self.explosiveDroneArray[ self.explosiveDroneArray.size ] =  explosiveDrone;
	self thread startExplosiveDrone( explosiveDrone );
	
	PlayFXOnTag(level.explosiveDroneSettings.fxId_launch_thruster, explosiveDrone, "TAG_THRUSTER_BTM");
	
	grenade notify( "mine_selfdestruct" );
	
	return explosiveDrone;
}


createExplosiveDrone( bRecreate, startPosition, startAngles, DroneType ) // self == grenade
{		
	if ( !IsDefined( bRecreate ) )
	{
		bRecreate = false;
	}
	
	if( !bRecreate )
	{
		startAng = self.angles;
		forward = AnglesToForward( startAng );
		side = AnglesToRight( startAng );
		
		forwardOffset = forward * BALL_DRONE_SPAWN_FORWARD_OFFSET;
	
		sideOffset = side * BALL_DRONE_SPAWN_SIDE_OFFSET;
		
		heightOffset = BALL_DRONE_SPAWN_STAND_UP_OFFSET;
		
		/#
		if( level.explosiveDroneDebugPosition )
		{
			targetOffset = (0, 0, level.explosiveDroneDebugSpawnPositionHeight );
			targetOffset += forward * level.explosiveDroneDebugSpawnPositionForward;			
		}
		#/		

		if ( IsDefined( self.explosiveDrone ) )
		{
			startPos = self.explosiveDrone.origin;
			startAng = self.explosiveDrone.angles;
			
			self.explosiveDrone deleteExplosiveDrone();
			self addToDeleteSpike();
		}
		else
		{
			startPos = self.origin;
		}
		
		/*playerStartPos = self.origin;
		trace = BulletTrace( playerStartPos, startPos, false );
		// make sure we aren't starting in geo
		attempts = 3;
		while( trace[ "surfacetype" ] != "none" && attempts > 0 )
		{
			startPos = self.origin + ( VectorNormalize( playerStartPos - trace[ "position" ] ) * 5 );
			trace = BulletTrace( playerStartPos, startPos, false );
			attempts--;
			wait( 0.05 );
		}
		if( attempts <= 0 )
			return;*/
		
	
		// make sure there are path nodes in the level
		/*if(!IsDefined(GetNodesOnPath(startPos, self.origin)))
		{
			self SetWeaponAmmoStock( weaponName, self GetWeaponAmmoStock( weaponName ) + 1 );
			self IPrintLnBold( &"MP_TRACKING_DRONE_UNAVAILABLE" );
			return;		
		}*/
		
		
	}
	else
	{
		startPos = startPosition;
		targetPos = startPosition;
		startAng = startAngles;
	}
	
	dir = AnglesToUp( self.angles );
	
	startPos += ( dir * 10 );

	drone = SpawnHelicopter( self.owner, startPos, startAng, level.explosiveDroneSettings.vehicleInfo, level.explosiveDroneSettings.modelBase );
	if( !IsDefined( drone ) )
		return;
	// TEST: insert launch FX here to possibly deal with FX launch not being straight down
	drone.type = "explosive_drone";
	
	//drone EnableAimAssist();
	
	// make expendable if it is non-lethal drone
	drone make_entity_sentient_mp( self.owner.team);
	drone MakeVehicleNotCollideWithPlayers( true );
	
	drone addToExplosiveDroneList();
	drone thread removeFromExplosiveDroneListOnDeath();

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

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

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

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

	drone.targetPos = startPos;

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

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

	drone thread maps\mp\gametypes\_damage::setEntityDamageCallback( drone.maxHealth, undefined, ::onExplosiveDroneDeath, undefined, false );
	drone thread explosiveDrone_watchDisable();
	drone thread explosiveDrone_watchDeath();
	drone thread explosiveDrone_watchTimeout();
	drone thread explosiveDrone_watchOwnerLoss();
	drone thread explosiveDrone_watchOwnerDeath();
	drone thread explosiveDrone_watchRoundEnd();
	drone thread explosiveDrone_watchHostMigration();

	drone thread ExplosiveDrone_enemy_lightFX();
	drone thread ExplosiveDrone_friendly_lightFX();
	drone thread drone_thrusterFXExplosive();

	
	return drone;
}

addToDeleteSpike()
{
	MAX_SPIKE_LIST = 5;
	if ( !IsDefined( level.spikeList ) )
	{
		level.spikeList = [];
		level.spikeListIndex = 0;
	}
	
	if ( level.spikeList.size >= MAX_SPIKE_LIST )
	{
		if ( IsDefined( level.spikeList[ level.spikeListIndex ] ) )
		{
			level.spikeList[ level.spikeListIndex ] Delete();
		}
	}
		
	level.spikeList[ level.spikeListIndex ] = self;
	level.spikeListIndex = ( level.spikeListIndex + 1 ) % MAX_SPIKE_LIST;
}

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

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

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

	self endon( "death" );
	self.owner endon( "faux_spawn" );


	foreach( player in level.players )
	{
		if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team != self.team )
		{
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			
		}

	}
}

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

	self endon( "death" );
	self.owner endon( "faux_spawn" );

	foreach( player in level.players )
	{
		if( IsDefined( player ) && IsSentient( player ) && IsSentient( self ) && player.team == self.team )
		{
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
		}
	}

	self thread watchConnectedPlayFXExplosive();
	self thread watchJoinedTeamPlayFXExplosive();
}

// Currently not working well. Need to grab from _variable_grenade.gsc -> tracking_grenade_think( attacker ) -> tracking_grenade_thrust_effect when this is ready for primetime.

drone_thrusterFXExplosive()
{
	self endon( "death" );
	self endon( "disconnect" );
	self.owner endon( "faux_spawn" );
	
	while ( true )
	{
		foreach( player in level.players )
		{
			self thread drone_thrusterFX_bottom_threaded( player );
			self thread drone_thrusterFX_side_threaded( player );
		}
		
		wait(1.1);
	}

	// This might not be necesscary since the new players should register in the level.players above
//	while( true )
//	{
//		level waittill( "connected", player );
//		player waittill( "spawned_player" );
//		
//		wait(0.1);
//		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
//		{
//			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
//		}
//		wait(0.1);
//		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
//		{
//			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
//		}
//		wait(0.1);
//		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
//		{
//			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
//		}
//		wait(0.1);
//		{
//			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_engine_distort, self, "TAG_THRUSTER_BTM", player );
//		}
//		wait(0.25);
//	}
}

drone_thrusterFX_side_threaded( player )
{
		self endon( "death" );
		self endon( "disconnect" );
		self.owner endon( "faux_spawn" );
		
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_X_nY_Z", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_X_nY_nZ", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_nX_nY_Z", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_nX_nY_nZ", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_nX_Y_nZ", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_nX_Y_Z", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_X_Y_Z", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUST_SIDE_X_Y_nZ", player );
		}
}


drone_thrusterFX_bottom_threaded( player )
{
		self endon( "death" );
		self endon( "disconnect" );
		self.owner endon( "faux_spawn" );
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_engine_distort))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_engine_distort, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.10);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_engine_distort))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_engine_distort, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.10);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_engine_distort))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_engine_distort, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.10);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_engine_distort))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_engine_distort, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.10);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_engine_distort))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_engine_distort, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.10);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_position_thruster))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_position_thruster, self, "TAG_THRUSTER_BTM", player );
		}
		wait(0.1);
		if(isDefined(player) && isDefined(self) && isdefined(level.explosiveDroneSettings.fxId_engine_distort))
		{
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_engine_distort, self, "TAG_THRUSTER_BTM", player );
		}
}

watchConnectedPlayFXExplosive() // self == drone
{
	self endon( "death" );
	self.owner endon( "faux_spawn" );

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

		if( IsDefined( player ) && player.team == self.team )
		{
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
		}
	}
}

watchJoinedTeamPlayFXExplosive() // self == drone
{
	self endon( "death" );
	self.owner endon( "faux_spawn" );

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

		if( IsDefined( player ) && player.team == self.team )
		{
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_friendly_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
			wait 0.15;
			PlayFXOnTagForClients( level.explosiveDroneSettings.fxId_enemy_light, self, "TAG_BEACON", player );
		}
	}
}
	
startExplosiveDrone( drone ) // self == player
{			
	level endon( "game_ended" );
	drone endon( "death" );

	//	begin following player	
	drone thread explosiveDrone_followTarget();
	
	drone thread createKillCamEntity();
	
	if(IsDefined(drone.type))
	{
		if(drone.type == "explosive_drone")
		{
			drone thread CheckForExplosiveGoalExplosive();
			
			/*
			 
			 	result = self waittill_any_return( "goal", "near_goal", "hit_goal" );
			 
			 */
		
		}

	}
}
CheckForExplosiveGoalExplosive() // self == drone
{
	level endon( "game_ended" );
	level endon( "host_migration_begin" );
	self endon( "death" );
	self endon( "leaving" );
	
	starttime = gettime();
	
	self thread blowUpAtEndofTrackingTime(starttime);
//	
//	self waittill_any( "goal", "near_goal", "hit_goal");
//
//	wait(CONST_TRACKING_TIME);	
//	
//	self notify("exploding");
//	self thread BlowUpDroneSequenceExplosive();
	
//	while((gettime () - starttime) < CONST_TRACKING_TIME)
//	{
//		
//		if( IsDefined(self.trackedPlayer) && self.trackedPlayer != self.owner && isReallyAlive(self.trackedPlayer))
//		{
//			DistanceToTargetSq = DistanceSquared(self.trackedPlayer.origin, self.origin);
//			if(DistanceToTargetSq <= 16384)
//			{
//				self notify("exploding");
//				self thread BlowUpDroneSequenceExplosive();
//				starttime = gettime() + CONST_TRACKING_TIME;
//			}
//			else if ((gettime () - starttime) > CONST_TRACKING_TIME)
//			{
//				self notify("exploding");
//				self thread BlowUpDroneSequenceExplosive();
//			}
//		}
//		
//		wait(0.1);
//	}
	
}

blowUpAtEndofTrackingTime(starttime)
{
	level endon( "game_ended" );
	level endon( "host_migration_begin" );
	self endon( "death" );
	self endon( "leaving" );
	
	
	while ((gettime () - starttime) < CONST_TRACKING_TIME)
	{
		waitframe();
	}
	if(IsDefined(self))
	{
		self notify("exploding");
		self thread BlowUpDroneSequenceExplosive();
	}
}

BlowUpDroneSequenceExplosive()
{
	// play sound 
	// blink lights
	StoredOwner = undefined;
	if ( IsDefined( self ) )
	{
		
		if(isdefined(self.owner))
		{
			StoredOwner = self.owner;
		}
		//self thread TurnOnDangerLightsExplosive();
		self playsound( level.explosiveDroneSettings.sound_lock );
	
		//explosiveDrone_stopMovement();
		wait(CONST_EXPLODE_PAUSE);
	}

	if ( IsDefined( self ) )
	{	
		self PlaySound( "wpn_explosive_drone_exp" );
		up_v = AnglesToUp(self.angles);
		forward_v = AnglesToForward(self.angles);
		
		PlayFx( level.explosiveDroneSettings.fxId_LethalExplode, self.origin, forward_v, up_v );
		if(isdefined(StoredOwner))
		{
			self RadiusDamage(self.origin, 256, 130, 55, StoredOwner, "MOD_EXPLOSIVE", "explosive_drone_mp");
		}
		else
		{
			self RadiusDamage(self.origin, 256, 130, 55, undefined, "MOD_EXPLOSIVE", "explosive_drone_mp");
		}
		
		self notify("death");
	}
}
TurnOnDangerLightsExplosive()
{
	if( IsDefined( self ))
	{
		//StopFXOnTag( level.explosiveDroneSettings.fxId_enemy_light, self, "tag_fx");
		//StopFXOnTag( level.explosiveDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_0");
		//StopFXOnTag( level.explosiveDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_1");
		//StopFXOnTag( level.explosiveDroneSettings.fxId_enemy_light, self, "tag_fx_beacon_2");
		//StopFXOnTag( level.explosiveDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_0");
		//StopFXOnTag( level.explosiveDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_1");
		//StopFXOnTag( level.explosiveDroneSettings.fxId_friendly_light, self, "tag_fx_beacon_2");
	}
	wait(0.05);
	if( IsDefined( self ))
	{
		//StopFXOnTag( level.explosiveDroneSettings.fxId_enemy_light, self, "tag_fx");
		//PlayFXOnTag( level.explosiveDroneSettings.fxId_warning, self, "tag_fx_beacon_0");
		//PlayFXOnTag( level.explosiveDroneSettings.fxId_warning, self, "tag_fx_beacon_1");
	}
	wait(0.15);
	if( IsDefined( self ))
	{
		//PlayFXOnTag( level.explosiveDroneSettings.fxId_warning, self, "tag_fx_beacon_2");
	}

}
explosiveDrone_followTarget() // self == drone
{
	level endon( "game_ended" );
	level endon( "host_migration_begin" );
	self endon( "death" );
	self endon( "leaving" );
	self endon( "exploding" );

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

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

	self Vehicle_SetSpeed( self.followSpeed, 10, 10 );
	self.previousTrackedPlayer = self.owner;
	self.trackedPlayer = undefined;
	while( true )
	{
		if( IsDefined( self.stunned ) && self.stunned )
		{
			wait( 0.5 );
			continue;
		}
		
		if( IsDefined( self.owner ) && IsAlive( self.owner ) )
		{
			// Try to find a target to track
			maxRangeSquared = self.maxTrackingRange * self.maxTrackingRange;
			closestDistanceSquared = maxRangeSquared;
			
			
			if(!IsDefined(self.trackedPlayer) || self.trackedPlayer == self.owner)
			{
				foreach( player in level.players )
				{
					if( IsDefined( player ) && IsAlive(player) && player != self.owner && ( !level.teamBased || player.team != self.team ) && !player _hasPerk( "specialty_blindeye" ) )
					{
						currentDistanceSquared = DistanceSquared(self.origin, player.origin);
						if(currentDistanceSquared < closestDistanceSquared)
						{
							closestDistanceSquared = currentDistanceSquared;
							self.trackedPlayer = player;	

							self thread watchPlayerDeathDisconnectExplosive(player);
						}
					}
				}
			}
	
			// Destroy explosive drone if there are no current enemies to track
			if ( !IsDefined( self.trackedPlayer ) )
			{
				self thread explosiveDroneExplode();
			}
			
			// Track current target
			if( IsDefined( self.trackedPlayer ))
			{
				explosiveDrone_moveToPlayer(self.trackedPlayer);
			}
			
			// Set previous tracked target
			if(self.trackedPlayer != self.previousTrackedPlayer )
			{
				stopHighlightingPlayerExplosive(self.previousTrackedPlayer );
				self.previousTrackedPlayer = self.trackedPlayer;
			}
		}
		wait( 1 );
	}
}

watchPlayerDeathDisconnectExplosive(trackedPlayer) // self == drone
{
	trackedPlayer waittill_any( "death", "disconnect", "faux_spawn", "joined_team" );
	
	if(trackedPlayer.is_being_tracked == true)
	{
		self thread explosiveDrone_leave();
	}
	else
	{
		self.trackedPlayer = undefined;
	}
}


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

	self notify( "explosiveDrone_moveToPlayer" );
	self endon( "explosiveDrone_moveToPlayer" );

	
	// collect the ideal offsets from the player
	forwardOffset = -1 * BALL_DRONE_FORWARD_OFFSET;
	
	sideOffset = BALL_DRONE_SIDE_OFFSET;
	
	heightOffset = BALL_DRONE_STAND_UP_OFFSET;
	switch( playerToMoveTo getStance() )
	{
		case "stand":
			heightOffset = BALL_DRONE_STAND_UP_OFFSET;
			break;
		case "crouch":
			heightOffset = BALL_DRONE_CROUCH_UP_OFFSET;
			break;
		case "prone":
			heightOffset = BALL_DRONE_PRONE_UP_OFFSET;
			break;
	}
	
	targetOffset	= (sideOffset, forwardOffset, heightOffset);

/#
	if( level.explosiveDroneDebugPosition )
	{
		targetOffset = (0, -1*level.explosiveDroneDebugPositionForward, level.explosiveDroneDebugPositionHeight );
	}
#/

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

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

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

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

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

	newOwner.explosiveDroneArray[ newOwner.explosiveDroneArray.size ] = explosiveDrone;
	newOwner thread startExplosiveDrone( explosiveDrone );
	
	if( IsDefined( level.explosiveDroneSettings.fxId_sparks ) )
	{
		//StopFXOnTag( level.explosiveDroneSettings.fxId_sparks, self, self.fx_tag0 );
	}
	
	self removeExplosiveDrone();
	
	return true;
}

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

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

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

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

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

startHighlightingPlayerExplosive(playerToStart) // self == drone
{
	self.laserTag LaserOn("explosive_drone_laser");
	playfxontag(level.explosiveDroneSettings.fxId_laser_glow, self.laserTag, "tag_laser");
	
	if( IsDefined( level.explosiveDroneSettings.sound_lock ) )
	{
		self PlaySound( level.explosiveDroneSettings.sound_lock );
	}
	
	playerToStart setPerk( "specialty_radararrow", true, false );
			
	if ( playerToStart.is_being_tracked == false )
	{
		playerToStart.is_being_tracked = true;
		playerToStart.TrackedByPlayer = self.owner;
	}
}

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

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

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

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

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

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

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

	self waittill( "death" );	

	self thread explosiveDroneDestroyed();
}


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

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

	wait( timeout );

	self thread explosiveDrone_leave();
}


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

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

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

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

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

		self thread explosiveDrone_leave();
	}
}

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

	self notify( "explosiveDrone_watchTargetDisconnect" );
	self endon( "explosiveDrone_watchTargetDisconnect" );

	self.trackedPlayer waittill( "disconnect" );

	stopHighlightingPlayerExplosive( self.trackedPlayer );
	explosiveDrone_moveToPlayer( self.owner );
}

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

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

	//	leave
	self thread explosiveDrone_leave();
}

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

	level waittill( "host_migration_begin" );

	stopHighlightingPlayerExplosive( self.trackedPlayer );

	maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();

	self thread explosiveDrone_changeOwner( self.owner );
}

explosiveDrone_leave() // self == drone
{
	self endon( "death" );
	self notify( "leaving" );
	
	stopHighlightingPlayerExplosive( self.trackedPlayer );

	explosiveDroneExplode();
}

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

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

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

explosiveDrone_grenade_watchDisable()
{
	self endon( "death" );
	self.owner endon( "disconnect" );
	level endon( "game_ended" );
	
	self.stunned = false;
	
	while( true )
	{
		self waittill( "emp_damage", attacker, duration );
		
		self thread explosiveDrone_grenade_stunned();
	}
}



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


explosiveDrone_grenade_stunned() // self == grenade
{
	self notify( "explosiveDrone_stunned" );
	self endon( "explosiveDrone_stunned" );

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

	wait( STUNNED_TIME );

	explosiveDrone_grenade_stunEnd();

}

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

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

	wait( STUNNED_TIME );

	explosiveDrone_stunEnd();

}


explosiveDrone_grenade_stunBegin() // self == grenade
{
	if(self.stunned)
		return;
	
	self.stunned = true;
	
	if( IsDefined( level.explosiveDroneSettings.fxId_sparks ) )
	{
		PlayFXOnTag( level.explosiveDroneSettings.fxId_sparks, self,  "TAG_BEACON" );
	}

}


explosiveDrone_stunBegin()
{
	if(self.stunned)
		return;
	
	self.stunned = true;
	
	if( IsDefined( level.explosiveDroneSettings.fxId_sparks ) )
	{
		PlayFXOnTag( level.explosiveDroneSettings.fxId_sparks, self,  "TAG_BEACON" );
	}
	
	thread stopHighlightingPlayerExplosive(self.trackedPlayer);
	
	self.trackedPlayer = undefined;
	self.previousTrackedPlayer = self.owner;
	
	self thread explosiveDrone_stopMovement();	
}

explosiveDrone_grenade_stunEnd() // self == grenade
{
	if( IsDefined( level.explosiveDroneSettings.fxId_sparks ) )
	{
		KillFXOnTag( level.explosiveDroneSettings.fxId_sparks, self,  "TAG_BEACON" );
	}
	
	self.stunned = false;
	self.inactive = false;
}

explosiveDrone_stunEnd()
{
	if( IsDefined( level.explosiveDroneSettings.fxId_sparks ) )
	{
		KillFXOnTag( level.explosiveDroneSettings.fxId_sparks, self,  "TAG_BEACON" );
	}
	
	self.stunned = false;
	self.inactive = false;
}

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

	explosiveDroneExplode();
}

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

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

	self notify( "exploding" );

	self removeExplosiveDrone();
}

deleteExplosiveDrone() // self == drone
{
	if( IsDefined( self.attractor ) )
	{
		Missile_DeleteAttractor( self.attractor );
	}
	
	self removeKillCamEntity();
		
	self delete();		
}

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

	if( IsDefined( self.owner ) && IsDefined( self.owner.explosiveDrone ) )
	{
		self.owner.explosiveDrone = undefined;	
	}
	
	self deleteExplosiveDrone();
}

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

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

addToExplosiveDroneList()
{
	level.explosiveDrones[ self GetEntityNumber() ] = self;	
}

removeFromExplosiveDroneListOnDeath()
{
	entNum = self GetEntityNumber();

	self waittill ( "death" );

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

exceededMaxExplosiveDrones(  )
{
	if( IsDefined(level.explosiveDrones) && level.explosiveDrones.size >= maxVehiclesAllowed() )
		return true;	
	else
		return false;	
}

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

ExplosiveDroneProximityTrigger() // self == grenade
{
	self endon( "mine_destroyed" );
	self endon( "mine_selfdestruct" );
	self endon( "death" );
	self.owner endon( "disconnect" );
	self.owner endon( "faux_spawn" );
	
	wait( CONST_ACTIVATION_TIME ); //arming time
	
	if ( IsDefined ( self ) && IsDefined( self.explosiveDrone ) )
	{
		// set icon

		teamIconoffset = self.explosiveDrone GetTagOrigin("TAG_BEACON") - self GetTagOrigin("TAG_BEACON") + (0,0,10);
		if ( level.teamBased )
			self maps\mp\_entityheadicons::setTeamHeadIcon( self.owner.team, teamIconoffset, "TAG_BEACON" );
		else
			self maps\mp\_entityheadicons::setPlayerHeadIcon( self.owner, teamIconoffset, "TAG_BEACON" );
	
		trigger = Spawn( "trigger_radius", (self.origin+(0,0,((CONST_Mine_TriggerHeight/2)*-1))), 0, CONST_Mine_TriggerRadius, CONST_Mine_TriggerHeight );
		trigger.owner = self;
		
		self thread ExplosiveDroneDeleteTrigger( trigger );
		self thread watchForPickup( trigger );
		
		
		player = undefined;
		while ( IsDefined( self ) && IsDefined( self.explosiveDrone) )
		{
			trigger waittill( "trigger", player );
	
			if ( !IsDefined( player ) )
			{
				wait( 0.1 );
				continue;
			}
			
			if ( player _hasPerk( "specialty_blindeye" ))
			{
				wait( 0.1 );
				continue;
			}
			
			if( isDefined( self.explosiveDrone) && !player SightConeTrace( self.explosiveDrone GetTagOrigin( "TAG_BEACON" ), self.explosiveDrone ) )
			{
				wait( 0.1 );
				continue;
			}
			
			if( IsDefined( self.explosiveDrone ) )
			{
				start = self.explosiveDrone GetTagOrigin( "TAG_BEACON" );
				end = player GetEye();
				if ( !BulletTracePassed( start, end, false, self.explosiveDrone ) )
				{
					wait( 0.1 );
					continue;
				}
			}
			
			if ( isReallyAlive( player ) && player != self.owner && ( !level.teamBased || player.team != self.owner.team ) && !self.stunned)
			{
				player tryUseExplosiveDrone( self );
			}
		}
	}
}

ExplosiveDroneDeleteTrigger( trigger )
{
	self waittill_any( "mine_triggered", "mine_destroyed", "mine_selfdestruct", "death" );
	
	if( IsDefined( self.entityHeadIcon ) )
	{
		self notify( "kill_entity_headicon_thread" );
		self.entityHeadIcon destroy();
	}
	
	trigger delete();
}

showDebugRadius(owner)
{
	effect = SpawnFx( level.explosiveDroneSettings.dome, owner.origin );
	TriggerFx( effect );
	
	self waittill( "death" );
	
	effect delete();
}

	
endOnPlayerSpawn()
{
	self.owner waittill_any("spawned_player", "faux_spawn", "delete_explosive_drones");
	
	self explosiveGrenadeDeath();
}

// The spike in the ground counts as a "grenade", so we clean up when appropriate
monitorSpikeDestroy()
{
	self.owner endon( "death" );
	self.owner endon( "disconnect" );
	self.owner endon( "faux_spawn" );
	
	//self waittill_any("mine_selfdestruct", "mp_exo_repulsor_repel");
	self waittill_any("mine_selfdestruct");
	
	self explosiveGrenadeDeath();
}

monitorHeadDestroy()
{
	self.owner endon( "death" );
	self.owner endon( "disconnect" );
	self.owner endon( "faux_spawn" );
	
	while(IsDefined(self.explosiveDrone))
	{
		wait(0.15);
	}
	
	if(isDefined(self))
	{
		self PlaySound( "wpn_explosive_drone_exp" );
		up_v = AnglesToUp(self.angles);
		forward_v = AnglesToForward(self.angles);
		
		PlayFx(level.explosiveDroneSettings.fxId_LethalExplode, self.origin, forward_v, up_v);
		self RadiusDamage(self.origin, 256, 130, 55, self.owner, "MOD_EXPLOSIVE", "explosive_drone_mp");
		
		self notify("death");
	}
	
	self explosiveGrenadeDeath();
}

startGrenadeLightFX() // self = grenade/spike
{
	// looping fx, threaded so it does not change if we get more players on the server

	self endon( "death" );	
	self.owner endon( "death" );
	self.owner endon( "disconnect" );
	
	ExplosiveDroneFXLoopWaitTime = 4 * 0.15;
	
	while ( IsDefined( self.explosiveDrone ) )
	{
		foreach( player in level.players )
		{
			if( IsDefined( player ) && IsSentient( player ) &&  player.team == self.team && IsDefined(self.explosiveDrone))
			{
				self thread FXblink( level.explosiveDroneSettings.fxId_friendly_light, self.explosiveDrone, "TAG_BEACON", player );
			}
			
			if( IsDefined( player ) && IsSentient( player ) && player.team != self.team  && IsDefined(self.explosiveDrone))
			{		
				self thread FXblink( level.explosiveDroneSettings.fxId_enemy_light, self.explosiveDrone, "TAG_BEACON", player );	
			}
		}
		wait ExplosiveDroneFXLoopWaitTime;
	}

}

FXblink( blink_fx, explosiveDrone, tag, player_to_show ) // self == grenade/spike
{
	for(i=0 ; i <= 4 && IsDefined(explosiveDrone) ; i++)
	{
		if ( IsDefined(player_to_show) && IsDefined(explosiveDrone) && IsDefined( self.stunned ) && !self.stunned)
		{
			PlayFXOnTagForClients( blink_fx, explosiveDrone, tag, player_to_show );
			wait 0.15;
		}	
	}
}

watchForStick() //  self == grenade
{
	self endon( "death" );
	self.owner endon( "death" );	
	self.owner endon( "disconnect" );
	
	stickReturnParms = undefined;

	stickReturnParms = self waittill_any_return_parms( "missile_stuck","mp_exo_repulsor_repel" );
	
	while ( !IsDefined( self.explosiveDrone ) )
	{
		waitframe();
	}
	
	if (IsDefined(stickReturnParms[1])) //handle sticking to drones and moving objects
	{
		if( stickReturnParms[1].classname == "script_model")
		{
			self PlaySound( "wpn_explosive_drone_exp" );
			up_v = AnglesToUp(self.angles);
			forward_v = AnglesToForward(self.angles);
			
			PlayFx(level.explosiveDroneSettings.fxId_LethalExplode, self.origin, forward_v, up_v);
			
			self RadiusDamage(self.origin, 256, 130, 55, self.owner, "MOD_EXPLOSIVE", "explosive_drone_mp");

			self thread maps\mp\_explosive_drone::explosiveGrenadeDeath();
			//self delete();
		}
	}
			
	if( IsDefined(self) ) // in case mp_exo_repulsor_repel got us here, or we destroyed ourselves above.
	{
		self.explosiveDrone SetContents( self.explosiveDrone.oldContents );
		
		self thread ExplosiveDroneProximityTrigger();
		self thread endOnPlayerSpawn();
		self thread explosiveDrone_grenade_watchDisable();
		self thread startGrenadeLightFX();
	
		self thread maps\mp\gametypes\_damage::setEntityDamageCallback( 100, undefined, ::explosiveGrenadeDeath, undefined, false );
		
		self.explosiveDrone thread maps\mp\gametypes\_damage::setEntityDamageCallback( 100, undefined, ::explosiveHeadDeath, undefined, false );
		
		self thread maps\mp\gametypes\_weapons::stickyHandleMovers( "mine_selfdestruct" );
		

	}
}

createKillCamEntity()	//self == drone
{		
	killCamOffset = (0,0,0);
	
	self.killCamEnt = Spawn( "script_model", self.origin );
	self.killCamEnt SetScriptMoverKillCam( "explosive" );
	self.killCamEnt LinkTo ( self, "TAG_THRUSTER_BTM", killCamOffset, (0,0,0) );
	self.killCamEnt SetContents( 0 );
	self.killCamEnt.startTime = getTime();
}
	
removeKillCamEntity()	//self == drone
{
	if( IsDefined( self.killCamEnt ) )
	{
		self.killCamEnt delete();	
	}
}

watchForPickup(trigger) //  self == grenade
{
	self.owner endon( "disconnect" );
	self.owner endon( "faux_spawn" );
	level endon( "game_ended" );
	self endon( "death" );
	self.owner endon( "death" );
		
	self.explosiveDrone MakeUsable();
	self.explosiveDrone SetHintString( explosive_drone_pickup_string );
	self.explosiveDrone SetHintStringVisibleOnlyToOwner( true );

	pickupRadiusSq = GetDvarFloat( "player_useRadius", 128 );
	pickupRadiusSq = pickupRadiusSq * pickupRadiusSq;
	
	while ( true )
	{
		if ( !IsDefined( self ) || !IsDefined( trigger ) )
		{
			break;
		}
		
		inRange = IsDefined( self.explosiveDrone ) && DistanceSquared( self.owner geteye(), self.explosiveDrone.origin ) <= pickupRadiusSq;
		
		if( self.owner IsTouching( trigger ) && inRange )
		{
			timeUsed = 0;
			
			while ( self.owner UseButtonPressed() )
			{
				if( !isReallyAlive( self.owner ))
					break;
					
				if ( !self.owner IsTouching( trigger ) )
					break;
				
				if ( self.owner FragButtonPressed() || self.owner SecondaryOffhandButtonPressed() || IsDefined( self.owner.throwingGrenade ) )
					break;
				
				if ( self.owner IsUsingTurret() || self.owner isUsingRemote() )
					break;
	
				if ( IsDefined( self.owner.isCapturingCrate ) && self.owner.isCapturingCrate )
					break;
				
				if ( IsDefined( self.owner.empGrenaded ) && self.owner.empGrenaded )
					break;
				
				if( IsDefined( self.owner.using_remote_turret ) && self.owner.using_remote_turret )
					break;
	
				timeUsed += 0.05;
				if( timeUsed > 0.75 )
				{				
					self.owner SetWeaponAmmoStock( self.weaponname, self.owner GetWeaponAmmoStock( self.weaponname ) + 1 );
					
					self.explosiveDrone deleteExplosiveDrone();
					
					self delete();
					
					break;
				}
				waitframe();
			}
		}
		waitframe();			
	}
}


