/*
	Tri Drone
	Author: Keith Evans
	Description: Three proximity based mines with a small detection radius causing small damage hit and short flashbang fx
	can be stacked to increase effectiveness
*/

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

CONST_Mine_TriggerDelay_Perk = 3;
CONST_Mine_TriggerDelay = 1;
CONST_Mine_TimOut = 120;

CONST_Mine_TriggerRadius = 192;
CONST_Mine_TriggerHeight = 192;
CONST_Mine_DamageRadius = 192;
CONST_Mine_DamageMax = 60;
CONST_Mine_DamageMin = 60;

CONST_Mine_ClipSize = 3;

watchTriDroneUsage()
{
	self endon( "spawned_player" );
	self endon( "disconnect" );
	self endon( "death" );
	self endon( "faux_spawn" );
		
	triDroneAmmoInit();

	while ( 1 )
	{
		self waittill( "grenade_fire", grenade, weapname );
		if ( weapname == "tri_drone_mp")
		{
			if( !IsAlive( self ) )
			{
				grenade delete();
				return;
			}

			thread tryUseTriDrone( grenade );
		}
	}	
}

init()
{	
	PreCacheShellShock( "flashbang_mp" );
	precacheModel( "projectile_bouncing_betty_grenade_small" );
	precacheModel( "projectile_bouncing_betty_grenade_small_bombsquad" );
	
	level.triDroneSettings					= SpawnStruct();
	level.triDroneSettings.mineCountdown	= 1;
	level.triDroneSettings.blastRadius		= 132;
	level.triDroneSettings.droneBounceHeight = 128;
	level.triDroneSettings.droneMesh		= "projectile_bouncing_betty_grenade_small";
	level.triDroneSettings.droneBombSquadMesh = "projectile_bouncing_betty_grenade_small_bombsquad";
	level.triDroneSettings.droneExplosionFx = LoadFX( "vfx/explosion/frag_grenade_default" );
	level.triDroneSettings.beacon[ "enemy" ]	 = loadfx( "vfx/lights/light_c4_blink" );
	level.triDroneSettings.beacon[ "friendly" ]	 = loadfx( "vfx/lights/light_mine_blink_friendly" );
	level.triDroneSettings.dome = LoadFX( "vfx/unique/orbital_dome_ground_friendly" );
}

triDroneAmmoInit()
{
	if(!IsDefined(self.triDroneAmmo))
	{
		self.triDroneAmmo = 0;
		self thread showAmmoCount();
	}
	
	self.triDroneAmmo = 0;
	self.triDroneAmmo+=CONST_Mine_ClipSize;
	
	if(!IsDefined(self.triDroneDeployed))
		self.triDroneDeployed = [];
}

tryUseTriDrone( grenade ) // self == player
{
	// Force Init for repeated debug use )
	if(self.triDroneAmmo<=0)
	{
		triDroneAmmoInit();
	}

	if(self.triDroneAmmo>0)
	{
		thread LaunchTriDrone( grenade );
		self.triDroneAmmo--;
		
		if(self.triDroneAmmo>=1)
			self GiveWeapon("tri_drone_mp");
	}

	return true;
}

LaunchTriDrone( grenade )
{
	Mine =  self SpawnMine( grenade ) ;
	
	self.triDroneDeployed =array_add(self.triDroneDeployed, Mine);
	
	thread MonitorPlayerDeath( Mine );
}

ActivateGroupedTriDrones( Player )
{
	self endon( "death" );
	
	foreach(triDrone in Player.triDroneDeployed)
	{
		if(IsDefined(triDrone))
		{
			if(triDrone != self)
			{
				triDrone.trigger notify("trigger");	
				wait .25;
			}
		}
	}
}

RemoveGroupedTriDrone( )
{
	self.owner.triDroneDeployed = array_remove(self.owner.triDroneDeployed, self);	
}

MonitorPlayerDeath( Mine )
{
	Mine endon ("death");
	self waittill(	"death" );
	
	self.triDroneAmmo = 0;

	if(IsDefined(Mine.pickuptrigger))
		Mine.pickuptrigger delete();	
	
	Mine delete();
}


////=================================================================================================================//
////											TRI DRONE MECHANIC			           	   		                 	 //
////=================================================================================================================//

SpawnMine( grenade )
{
	
	grenade waittill( "missile_stuck" );
	
	Assert( isDefined( self ) );

	tracedown = bulletTrace( grenade.origin, grenade.origin - (0, 0, 4), false, grenade );
	
	traceup = bulletTrace( grenade.origin, grenade.origin + (0, 0, 4), false, grenade );
	
	ForwardAngles = AnglesToForward(grenade.angles);
	
	traceforward = bulletTrace( grenade.origin + (0, 0, 4), (grenade.origin + (ForwardAngles*4)), false, grenade );
	
	trace = undefined;
	IsUp = false;
	IsForward = false;
	
	if(traceforward["surfacetype"] != "none")
	{
		trace = traceforward;
		IsForward = true;
	}
	else if(traceup["surfacetype"] != "none")
	{
		trace = traceup;
		IsUp = true;
	}
	else if(tracedown["surfacetype"] != "none")
		trace = tracedown;
	else
		trace = tracedown;	//fallback

	SpawnOrigin = trace[ "position" ];

	if(SpawnOrigin == traceup[ "position" ] )
	{
		SpawnOrigin+=(0,0,-5);
	}
		
	TriDrone = Spawn( "script_model", SpawnOrigin );
	
	TriDrone.IsUp = IsUp;
	TriDrone.IsForward = IsForward;

	normal = vectornormalize( trace["normal"] );
	angles =  vectortoangles( normal );

	angles += ( 90, 0, 0 );

	TriDrone.angles = angles;

	TriDrone SetModel( level.triDroneSettings.droneMesh );
	TriDrone.owner = self;
	TriDrone SetOtherEnt(self);
	TriDrone.killCamOffset = ( 0, 0, 55 );
	TriDrone.killCamEnt = Spawn( "script_model", TriDrone.origin + TriDrone.killCamOffset );
	TriDrone.stunned = false;
	
	TriDrone.weaponname = "tri_drone_mp";
	
	grenade delete();
	
	//ProximityMine thread showDebugRadius();
	
	level.mines[level.mines.size] = TriDrone;
				
	TriDrone thread createBombSquadModel( level.triDroneSettings.droneBombSquadMesh, "tag_origin", self );
	TriDrone thread mineBeacon();
	TriDrone thread setTriDroneTeamHeadIcon( self.team );
	TriDrone thread mineDamageMonitor();
	TriDrone thread mineProximityTrigger( self );	
	TriDrone thread mineSelfDestruct();
	TriDrone thread deleteMineOnTeamSwitch( self );
	TriDrone thread handleEMP( self, "apm_mine" );

	return TriDrone;
}

showDebugRadius()
{
	effect["dome"] = SpawnFx( level.triDroneSettings.dome, self getTagOrigin( "tag_fx" ) );
	TriggerFx( effect["dome"] );
	
	self waittill( "death" );
	
	effect["dome"] delete();
}

showAmmoCount()
{
	self endon( "disconnect" );
	
	while(1)
	{
		if("tri_drone_mp" == self GetLethalWeapon())
		{
			self SetClientOmnvar( "ui_tri_drone_count", self.triDroneAmmo );
		}
		
		waitframe();
	}
}
	
createBombSquadModel( modelName, tagName, owner )
{
	apm_bombSquadModel = spawn( "script_model", (0,0,0) );
	apm_bombSquadModel hide();
	wait ( 0.05 );
	
	apm_bombSquadModel thread maps\mp\gametypes\_weapons::bombSquadVisibilityUpdater( owner );
	apm_bombSquadModel setModel( modelName );
	apm_bombSquadModel linkTo( self, tagName, (0,0,0), (0,0,0) );
	apm_bombSquadModel SetContents( 0 );
	
	self waittill ( "death" );

	if ( isDefined(self.trigger) )
		self.trigger delete();
	
	apm_bombSquadModel delete();
}

mineBeacon()  // self == apm_mine
{
	effect["friendly"] = SpawnFx( level.triDroneSettings.beacon["friendly"], self getTagOrigin( "tag_fx" ) );
	effect["enemy"] = SpawnFx( level.triDroneSettings.beacon["enemy"], self getTagOrigin( "tag_fx" ) );

	self thread mineBeaconTeamUpdater( effect );
	self waittill( "death" );
	
	effect["friendly"] delete();
	effect["enemy"] delete();
}


mineBeaconTeamUpdater( effect, effect_flare )  // self == apm_mine
{
	self endon ( "death" );
	
	ownerTeam = self.owner.team;
	
	// PlayFXOnTag fails if run on the same frame the parent entity was created
	wait ( 0.05 );
	
	TriggerFx( effect["friendly"] );
	TriggerFx( effect["enemy"] );

	for ( ;; )
	{
		effect["friendly"] Hide();
		effect["enemy"] Hide();

		foreach ( player in level.players )
		{
			if ( level.teamBased )
			{
				if ( player.team == ownerTeam )
				{
					effect["friendly"] ShowToPlayer( player );
				}
				else
				{
					effect["enemy"] ShowToPlayer( player );
				}
			}
			else
			{
				if ( player == self.owner )
				{
					effect["friendly"] ShowToPlayer( player );
				}
				else
				{
					effect["enemy"] ShowToPlayer( player );
				}
			}
		}
		
		level waittill_either ( "joined_team", "player_spawned" );
	}
}

setTriDroneTeamHeadIcon( team )
{
	self endon( "death" );
	wait .05;
	if ( level.teamBased )
	{
		if(self.IsUp==true || self.IsForward==true)
			self maps\mp\_entityheadicons::setTeamHeadIcon( team, ( 0, 0, 28 ), undefined, true );
		else
			self maps\mp\_entityheadicons::setTeamHeadIcon( team, ( 0, 0, 28 ) );
	}
	else if ( isDefined( self.owner ) )
	{
		if(self.IsUp==true)
			self maps\mp\_entityheadicons::setPlayerHeadIcon( self.owner, (28,0,28) );
		else
			self maps\mp\_entityheadicons::setPlayerHeadIcon( self.owner, (0,0,28) );
	}
}

mineDamageMonitor()  // self == apm_mine
{
	self endon( "mine_triggered" );
	self endon( "mine_selfdestruct" );
	self endon( "death" );

	self setcandamage( true );
	self.maxhealth = 100000;
	self.health = self.maxhealth;

	attacker = undefined;

	while ( 1 )
	{
		self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, iDFlags, weapon );
		
		if ( !isPlayer( attacker ) )
			continue;

		// don't allow people to destroy mines on their team if FF is off
		if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, attacker ) )
			continue;

		if( IsDefined( weapon ) )
		{
			shortWeapon = maps\mp\_utility::strip_suffix( weapon, "_lefthand" );

			switch( shortWeapon )
			{
			case "smoke_grenade_mp":
			case "smoke_grenade_var_mp":
				continue;
			}
		}
		break;
	}

	self notify( "mine_destroyed" );

	self RemoveGroupedTriDrone();
	
	if ( isDefined( type ) && ( isSubStr( type, "MOD_GRENADE" ) || isSubStr( type, "MOD_EXPLOSIVE" ) ) )
		self.wasChained = true;

	if ( isDefined( iDFlags ) && ( iDFlags & level.iDFLAGS_PENETRATION ) )
		self.wasDamagedFromBulletPenetration = true;

	self.wasDamaged = true;
	
	if( isPlayer( attacker ) )
	{
		attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "bouncing_betty" );
	}

	if ( level.teamBased )
	{
		// "destroyed_explosive" notify, for challenges
		if ( isdefined( attacker ) && isdefined( attacker.pers[ "team" ] ) && isdefined( self.owner ) && isdefined( self.owner.pers[ "team" ] ) )
		{
			if ( attacker.pers[ "team" ] != self.owner.pers[ "team" ] )
				attacker notify( "destroyed_explosive" );
		}
	}
	else
	{
		// checking isDefined attacker is defensive but it's too late in the project to risk issues by not having it
		if ( isDefined( self.owner ) && isDefined( attacker ) && attacker != self.owner )
			attacker notify( "destroyed_explosive" );		
	}
	
	self thread mineExplode( attacker );
}

mineExplode( attacker )
{
	if ( !IsDefined( self ) || !IsDefined(self.owner) )
		return;
	
	// using a passed in attacker means that the owner wasn't the one who detonated this, this way the correct credit will go to the correct player
	if( !IsDefined( attacker ) )
		attacker = self.owner;
			
	self PlaySound( "null" );
	
	tagOrigin = self GetTagOrigin( "tag_fx" );
	PlayFX( level.triDroneSettings.droneExplosionFx, tagOrigin );
	
	wait( 0.05 ); // needed or the effect doesn't play
	if ( !IsDefined( self ) || !IsDefined(self.owner) )
		return;
	
	self Hide();
	
	//cause damage
	self RadiusDamage( self.origin, CONST_Mine_DamageRadius, CONST_Mine_DamageMax, CONST_Mine_DamageMin, attacker, "MOD_EXPLOSIVE", "bouncingbetty_mp" );

	//flashbang fx
	foreach( player in level.players )
	{
		DistanceFromTriDrone = Distance(self.origin,player.origin);
		if(DistanceFromTriDrone < CONST_Mine_DamageRadius)
		{
			player ShellShock( "flashbang_mp",2.5 );
		}
	}
	
	if ( IsDefined( self.owner ) && IsDefined( level.leaderDialogOnPlayer_func ) )
		self.owner thread [[ level.leaderDialogOnPlayer_func ]]( "mine_destroyed", undefined, undefined, self.origin );

	wait( 0.2 );
	if ( !IsDefined( self ) || !IsDefined(self.owner) )
		return;

	self thread apm_mine_deleteKillCamEnt();
	 
	self notify ( "death" );
	
	if(IsDefined(self.pickuptrigger))
		self.pickuptrigger delete();

	self hide();	
}

apm_mine_deleteKillCamEnt()
{
	wait ( 3 );
	self.killCamEnt delete();
	self delete();
		
	level.mines = array_removeUndefined(level.mines);
	
}


equipmentWatchUse( )
{
	self endon( "spawned_player" );
	self endon( "disconnect" );
	self endon( "change_owner");
	
	self.pickupTrigger setCursorHint( "HINT_NOICON" );
	owner = self.pickupTrigger.owner;
	self equipmentEnableUse( owner );

	for ( ;; )
	{
		self.pickupTrigger waittill ( "trigger", owner );
		
		owner playLocalSound( "scavenger_pack_pickup" );
		
		owner.triDroneAmmo++;
		
		if(owner.triDroneAmmo==1)
			owner GiveWeapon("tri_drone_mp");
		
		if(IsDefined(self.pickuptrigger))
			self.pickuptrigger delete();
		
		self.killCamEnt delete();
		self delete();
		
		level.mines = array_removeUndefined(level.mines);
	}
}

equipmentEnableUse( owner )
{
	self notify( "equipmentWatchUse" );
	
	self endon( "spawned_player" );
	self endon( "disconnect" );
	self endon( "equipmentWatchUse" );
	self endon( "change_owner" );
	
	self.pickupTrigger setCursorHint( "HINT_NOICON" );

	self.pickupTrigger setHintString( &"MP_PICKUP_TRI_DRONE" );
	
	self.pickupTrigger setSelfUsable( owner );
}

equipmentDisableUse( owner )
{
	self.trigger setHintString( "" );
	
	self.trigger setSelfUnusuable();
}

mineProximityTrigger( Owner )
{
	self endon( "mine_destroyed" );
	self endon( "mine_selfdestruct" );
	self endon( "death" );
	
	wait( 2 ); //arming time
	
	self.pickupTrigger = Spawn( "script_origin", self.origin );
	self.pickupTrigger.owner = Owner;
	self thread equipmentWatchUse( );
	
	trigger = Spawn( "trigger_radius", (self.origin+(0,0,((CONST_Mine_TriggerHeight/2)*-1))), 0, CONST_Mine_TriggerRadius, CONST_Mine_TriggerHeight );
	trigger.owner = self;
	
	self.trigger = trigger;
	
	self thread mineDeleteTrigger( trigger );
	
	player = undefined;
	while ( 1 )
	{
		trigger waittill( "trigger", player );

		if(!IsDefined(player))
			break;
		
		if ( getdvarint( "scr_minesKillOwner" ) != 1 )
		{
			if ( isDefined( self.owner ) )
			{
				if ( player == self.owner )
					continue;
				if ( isdefined( player.owner ) && player.owner == self.owner )
					continue;
			}

			if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, player, 0 ) )
				continue;
		}
		
		
		if ( lengthsquared( player getEntityVelocity() ) < 10 )
			continue;

		if ( player damageConeTrace( self.origin, self ) > 0 )
		{
			//IPrintLnBold("YESLOS");
			break;
		}

	}

	self RemoveGroupedTriDrone();
	
	self notify( "mine_triggered" );
	
	self playsound( "claymore_activated" );
	
	//pop it up
	self playsound( "mine_betty_spin" );
	playFX( level.mine_launch, self.origin );
	
	forward = AnglesToUp( self.angles );
	explodePos = self.origin + ( forward * 64 );

	self MoveTo( explodePos, 0.75, 0, .25 );
	self.killCamEnt MoveTo( explodePos + self.killCamOffset, 0.75, 0, .25 );
	
	self RotateVelocity( (0, 750, 32), 0.7, 0, .65 );
	self thread playSpinnerFX();
		
	if ( IsPlayer( player ) && player _hasPerk( "specialty_class_engineer" ) )
	{
		player notify( "triggered_mine" );
		wait CONST_Mine_TriggerDelay_Perk;
	}
	else 
		wait CONST_Mine_TriggerDelay;

	//self thread ActivateGroupedTriDrones( Owner );
	
	self thread mineExplode();
}

playSpinnerFX()
{
	self endon( "death");
	
	timer = gettime() + 1000;
	
	while ( gettime() < timer )
	{
		wait .05;
		playFXontag( level.mine_spin, self, "tag_fx_spin1" );
		playFXontag( level.mine_spin, self, "tag_fx_spin3" );
		wait .05;
		playFXontag( level.mine_spin, self, "tag_fx_spin2" );
		playFXontag( level.mine_spin, self, "tag_fx_spin4" );
	}
}


mineDeleteTrigger( trigger )
{
	self waittill_any( "mine_triggered", "mine_destroyed", "death" );

	trigger delete();
}


mineSelfDestruct()
{
	self endon( "mine_triggered" );
	self endon( "mine_destroyed" );
	self endon( "death" );
	
	wait( CONST_Mine_TimOut );

	self notify( "mine_selfdestruct" );
	
	if ( IsDefined( self.killCamEnt ) )
		self.killCamEnt delete();
		
	PlayFX( level._effect[ "equipment_explode" ], self.origin );
	self delete();
}

deleteMineOnTeamSwitch( player )  // self == apm_mine
{
	level endon ( "game_ended" );
	player endon( "disconnect" );
	self endon( "death" );
	
	player waittill ( "joined_team" );
	self Delete();
	self notify( "death" );
}

handleEMP( owner, type ) // self == apm_plane || self == apm_mine
{
	self endon ( "death" );

	if ( owner isEMPed() )
	{
		self notify( "death" );
		
		if ( type == "apm_mine")
		{
			PlayFX( level._effect[ "equipment_explode" ], self.origin );
			self Delete();
		}
		return;
	}
	
	for ( ;; )
	{
		level waittill ( "emp_update" );
		
		if ( !owner isEMPed() )
			continue;
		
		if ( type == "apm_mine")
		{
			PlayFX( level._effect[ "equipment_explode" ], self.origin );
			self Delete();

		}
			
		self notify( "death" );
	}
}
