#using scripts\codescripts\struct;

#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\system_shared;
#using scripts\shared\weapons\_weaponobjects;
#using scripts\shared\util_shared;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	   	                                                                                                                                                                                                                                  	                                                                                                                                                                                                                                                                                                                                                                                                                                                   

#precache( "fx", "weapon/fx_prox_grenade_scan_blue" );
#precache( "fx", "weapon/fx_prox_grenade_wrn_grn" );
#precache( "fx", "weapon/fx_prox_grenade_scan_orng" );
#precache( "fx", "weapon/fx_prox_grenade_wrn_red" );
#precache( "fx", "weapon/fx_prox_grenade_impact_player_spwner" );
#precache( "fx", "weapon/fx_prox_grenade_elec_jump" );

#namespace proximity_grenade;

function init_shared()
{
	level._effect["prox_grenade_friendly_default"] = "weapon/fx_prox_grenade_scan_blue";
	level._effect["prox_grenade_friendly_warning"] = "weapon/fx_prox_grenade_wrn_grn";
	level._effect["prox_grenade_enemy_default"] = "weapon/fx_prox_grenade_scan_orng";
	level._effect["prox_grenade_enemy_warning"] = "weapon/fx_prox_grenade_wrn_red";
	level._effect["prox_grenade_player_shock"] = "weapon/fx_prox_grenade_impact_player_spwner";
	level._effect["prox_grenade_chain_bolt"] = "weapon/fx_prox_grenade_elec_jump";

	level.proximityGrenadeDetectionRadius = GetDvarInt( "scr_proximityGrenadeDetectionRadius", 180 );
	level.proximityGrenadeDuration = GetDvarFloat( "scr_proximityGrenadeDuration", 1.2 );
	level.proximityGrenadeGracePeriod = GetDvarFloat( "scr_proximityGrenadeGracePeriod", 0.05 );
	level.proximityGrenadeDOTDamageAmount = GetDvarInt( "scr_proximityGrenadeDOTDamageAmount", 1 );
	level.proximityGrenadeDOTDamageAmountHardcore = GetDvarInt( "scr_proximityGrenadeDOTDamageAmountHardcore", 1 );
	level.proximityGrenadeDOTDamageTime = GetDvarFloat( "scr_proximityGrenadeDOTDamageTime", 0.2 );
	level.proximityGrenadeDOTDamageInstances = GetDvarInt( "scr_proximityGrenadeDOTDamageInstances", 4 );
	level.proximityGrenadeActivationTime = GetDvarFloat( "scr_proximityGrenadeActivationTime", .1 );
	level.proximityChainDebug = GetDvarInt( "scr_proximityChainDebug", 0 );
	level.proximityChainGracePeriod = GetDvarInt( "scr_proximityChainGracePeriod", 2500 );
	level.proximityChainBoltSpeed = GetDvarFloat( "scr_proximityChainBoltSpeed", 400.0 );
	level.proximityGrenadeProtectedTime = GetDvarFloat( "scr_proximityGrenadeProtectedTime", 0.45 );

	level.poisonFXDuration = 6;
	
	level thread register();
	
	callback::on_spawned( &on_player_spawned );
	
	callback::add_weapon_damage( GetWeapon( "proximity_grenade" ), &on_damage );

	/#
	level thread updateDvars();
	#/
}

//******************************************************************
//                                                                 *s
//                                                                 *
//******************************************************************
function register()
{
	clientfield::register( "toplayer", "tazered", 1, 1, "int" );
}

function updateDvars()
{
	while(1)
	{
		level.proximityGrenadeDetectionRadius = GetDvarInt( "scr_proximityGrenadeDetectionRadius", level.proximityGrenadeDetectionRadius );
		level.proximityGrenadeDuration = GetDvarFloat( "scr_proximityGrenadeDuration", 1.5 );
		level.proximityGrenadeGracePeriod = GetDvarFloat( "scr_proximityGrenadeGracePeriod", level.proximityGrenadeGracePeriod );
		level.proximityGrenadeDOTDamageAmount = GetDvarInt( "scr_proximityGrenadeDOTDamageAmount", level.proximityGrenadeDOTDamageAmount );
		level.proximityGrenadeDOTDamageAmountHardcore = GetDvarInt( "scr_proximityGrenadeDOTDamageAmountHardcore", level.proximityGrenadeDOTDamageAmountHardcore );
		level.proximityGrenadeDOTDamageTime = GetDvarFloat( "scr_proximityGrenadeDOTDamageTime", level.proximityGrenadeDOTDamageTime );
		level.proximityGrenadeDOTDamageInstances = GetDvarInt( "scr_proximityGrenadeDOTDamageInstances", level.proximityGrenadeDOTDamageInstances );
		level.proximityGrenadeActivationTime = GetDvarFloat( "scr_proximityGrenadeActivationTime", level.proximityGrenadeActivationTime );
		level.proximityChainDebug = GetDvarInt( "scr_proximityChainDebug", level.proximityChainDebug );
		level.proximityChainGracePeriod = GetDvarInt( "scr_proximityChainGracePeriod", level.proximityChainGracePeriod );
		level.proximityChainBoltSpeed = GetDvarFloat( "scr_proximityChainBoltSpeed", level.proximityChainBoltSpeed );
		level.proximityGrenadeProtectedTime = GetDvarFloat( "scr_proximityGrenadeProtectedTime", level.proximityGrenadeProtectedTime );

		wait(1.0);
	}
}

function createProximityGrenadeWatcher() // self == player
{
	watcher = self weaponobjects::createProximityWeaponObjectWatcher( "proximity_grenade", self.team );
	watcher.watchForFire = true;
	watcher.hackable = true;
	watcher.hackerToolRadius = level.equipmentHackerToolRadius;
	watcher.hackerToolTimeMs = level.equipmentHackerToolTimeMs;
	watcher.headIcon = false;
	watcher.activateFx = true;
	watcher.ownerGetsAssist = true;
	watcher.ignoreDirection = true;
	watcher.immediateDetonation = true;
	watcher.detectionGracePeriod = level.proximityGrenadeGracePeriod;
	watcher.detonateRadius = level.proximityGrenadeDetectionRadius;
	watcher.onStun = &weaponobjects::weaponStun;
	watcher.stunTime = 1;
	watcher.onDetonateCallback =&proximityDetonate;
	watcher.activationDelay = level.proximityGrenadeActivationTime;
	watcher.activateSound = "wpn_claymore_alert";
	watcher.immunespecialty = "specialty_immunetriggershock";
	
	watcher.onSpawn =&onSpawnProximityGrenadeWeaponObject;
}

function createGadgetProximityGrenadeWatcher() // self == player
{
	watcher = self weaponobjects::createProximityWeaponObjectWatcher( "gadget_sticky_proximity", self.team );
	watcher.watchForFire = true;
	watcher.hackable = true;
	watcher.hackerToolRadius = level.equipmentHackerToolRadius;
	watcher.hackerToolTimeMs = level.equipmentHackerToolTimeMs;
	watcher.headIcon = false;
	watcher.activateFx = true;
	watcher.ownerGetsAssist = true;
	watcher.ignoreDirection = true;
	watcher.immediateDetonation = true;
	watcher.detectionGracePeriod = level.proximityGrenadeGracePeriod;
	watcher.detonateRadius = level.proximityGrenadeDetectionRadius;
	watcher.onStun = &weaponobjects::weaponStun;
	watcher.stunTime = 1;
	watcher.onDetonateCallback =&proximityDetonate;
	watcher.activationDelay = level.proximityGrenadeActivationTime;
	watcher.activateSound = "wpn_claymore_alert";

	watcher.onSpawn =&onSpawnProximityGrenadeWeaponObject;
}

function onSpawnProximityGrenadeWeaponObject( watcher, owner ) // self == weapon object
{
	self thread setupKillCamEnt();
	owner AddWeaponStat( self.weapon, "used", 1 );
	
	if ( IsDefined( self.weapon ) && self.weapon.proximityDetonation > 0 )
	{
		watcher.detonateRadius = self.weapon.proximityDetonation;
	}
	
	weaponobjects::onSpawnProximityWeaponObject( watcher, owner );
	
	self trackOnOwner( self.owner );
}

function trackOnOwner( owner )
{
	if ( level.trackProximityGrenadesOnOwner === true )
	{
		if ( !isdefined( owner ) )
			return;
		
		if ( !isdefined( owner.activeProximityGrenades ) )
		{
			owner.activeProximityGrenades = [];
		}
		else
		{
			ArrayRemoveValue( owner.activeProximityGrenades, undefined );
		}
		
		owner.activeProximityGrenades[ owner.activeProximityGrenades.size ] = self;
	}
}

function setupKillCamEnt() // self == grenade
{
	self endon( "death" );

	self util::waitTillNotMoving();

	self.killCamEnt = spawn( "script_model", self.origin + (0,0,8 ) );
	self thread cleanupKillCamEntOnDeath();
}

function cleanupKillCamEntOnDeath() // self == grenade
{
	self waittill( "death" );

	self.killCamEnt util::deleteAfterTime( 4 + level.proximityGrenadeDOTDamageTime * level.proximityGrenadeDOTDamageInstances );
}

function proximityDetonate( attacker, weapon, target )
{
	if ( isdefined( weapon ) && weapon.isValid )
	{
		if ( isdefined( attacker ) )
		{
			if ( self.owner util::IsEnemyPlayer( attacker ) )
			{
				attacker challenges::destroyedExplosive( weapon );
				scoreevents::processScoreEvent( "destroyed_proxy", attacker, self.owner, weapon );
			}
		}
	}
	
	weaponobjects::weaponDetonate( attacker, weapon );
}

function proximityGrenadeDamagePlayer( eAttacker, eInflictor, killCamEnt, weapon, meansOfDeath, damage, proximityChain )
{	
	// eAttacker is the owner of the charge
	// eInflictor is originally the charge, and when on a chain, it;s the player that passed on the chain to us
	// killCamEnt is the killCam placed at the charge	
	self thread damagePlayerInRadius( eInflictor.origin, eAttacker, killCamEnt );
	
	if ( weapon.chainEventRadius > 0 && !self hasPerk ("specialty_proximityprotection") )
	{
		self thread proximityGrenadeChain( eAttacker, eInflictor, killCamEnt, weapon, meansOfDeath, damage, proximityChain, 0 );
	}
}

function getProximityChain()
{
	if ( !isdefined( level.proximityChains ) )
	{
		level.proximityChains = [];
	}
	
	// we use level.proximityChains because we need them to survive players and the on_damage callback
	foreach( chain in level.proximityChains )
	{
		if ( !chainIsActive( chain ) )
		{
			return chain;
		}
	}
	
	chain = spawnstruct();
	level.proximityChains[level.proximityChains.size] = chain;
	
	return chain;
}

function chainIsActive( chain )
{
	// a chain is active as long as it is still being used
	if ( isdefined( chain.activeEndTime ) && chain.activeEndTime > GetTime() )
	{
		return true;
	}
	
	return false;
}

function cleanUpProximityChainEnt() // self is a temp entity to keep track of the original chain. it goes away when all chains are finished.
{
	self.cleanUp = true;
	
	any_active = true;
	
	while( any_active )
	{
		wait( 1 );
		
		if ( !isdefined( self ) )
		{
			return;
		}
		
		any_active = false;
		
		foreach( proximityChain in self.chains )
		{			
			if ( proximityChain.activeEndTime > GetTime() )
			{
				any_active = true;
				break;
			}
		}		
	}
	
	if ( isdefined( self ) )
	{
		self delete();
	}	
}

function isInChain( player )
{
	player_num = player GetEntityNumber();
	
	return isdefined( self.chain_players[player_num] );	
}

function addPlayerToChain( player )
{
	player_num = player GetEntityNumber();
	
	self.chain_players[player_num] = player;
}

function proximityGrenadeChain( eAttacker, eInflictor, killCamEnt, weapon, meansOfDeath, damage, proximityChain, delay )
{
	self endon( "disconnect" );
	self endon( "death" );	
	
	eAttacker endon( "disconnect" );	
	
	if ( !isdefined( proximityChain ) )
	{	
		// this is a new chain started at the on_damage callback
		proximityChain = getProximityChain();
		proximityChain.chainEventNum = 0;		
		
		if ( !isdefined( eInflictor.proximityChainEnt ) )
		{
			// this is the first chain from proximity grenade explosion started at the on_damage callback
			eInflictor.proximityChainEnt = spawn( "script_origin", self.origin );
			eInflictor.proximityChainEnt.chains = [];
			eInflictor.proximityChainEnt.chain_players = [];			
		}
		
		proximityChain.proximityChainEnt = eInflictor.proximityChainEnt;
		proximityChain.proximityChainEnt.chains[proximityChain.proximityChainEnt.chains.size] = proximityChain;		
	}
	
	proximityChain.chainEventNum += 1;	
	
	if ( proximityChain.chainEventNum >= weapon.chainEventMax )
	{
		// this chain is maxed
		return;
	}
	
	chainEventRadiusSq = weapon.chainEventRadius * weapon.chainEventRadius;
	
	endTime = GetTime() + weapon.chainEventTime;
	
	proximityChain.proximityChainEnt addPlayerToChain( self );
	proximityChain.activeEndTime = endTime + (delay * 1000) + level.proximityChainGracePeriod; // allow an interval to avoid reusing this proximityChain, in case it's still being used.
	
	if ( delay > 0 )
	{
		// yield after incrementing chainEventNum, and updating the chain active state	
		wait( delay );
	}
	
	if( !isdefined( proximityChain.proximityChainEnt.cleanUp ) )
	{
		proximityChain.proximityChainEnt thread cleanUpProximityChainEnt();
	}
	
	// we just been chain shocked, we will look for other players to continue the chain
	while( 1 )
	{
		currentTime = GetTime();
		
		if ( endTime < currentTime )
		{
			return;
		}
		
		closestPlayers = ArraySort( level.players, self.origin, true );
		
		foreach( player in closestPlayers )
		{
			{wait(.05);};
			
			if ( proximityChain.chainEventNum >= weapon.chainEventMax )
			{
				// this chain is maxed
				return;
			}
			
			if ( !isdefined( player ) || !IsAlive( player ) || player == self )
			{
				continue;
			}
			
			if ( player.sessionstate != "playing" )
			{
				continue;
			}
			
			distanceSq = DistanceSquared( player.origin, self.origin );
			
			if ( distanceSq > chainEventRadiusSq )
			{
				break;
			}
			
			if ( proximityChain.proximityChainEnt isInChain( player ) )
			{
				continue;
			}
			
			if ( level.proximityChainDebug || weaponobjects::friendlyFireCheck( eAttacker, player ) )
			{
				if ( level.proximityChainDebug || !player hasPerk ("specialty_proximityprotection") )
				{
					// found a player to pass the chain to					
					
					self thread chainPlayer( eAttacker, killCamEnt, weapon, meansOfDeath, damage, proximityChain, player, distanceSq );					
				}
			}			
		}
		
		{wait(.05);};
	}
}

function chainPlayer( eAttacker, killCamEnt, weapon, meansOfDeath, damage, proximityChain, player, distanceSq )
{
	waitTime = 0.25;
	
	speedSq = level.proximityChainBoltSpeed * level.proximityChainBoltSpeed;
	if ( speedSq > 100 && distanceSq > 1 )
	{
		waitTime = distanceSq / speedSq;
	}
	
	player thread proximityGrenadeChain( eAttacker, self, killCamEnt, weapon, meansOfDeath, damage, proximityChain, waitTime );
	
	{wait(.05);};
	
	if ( level.proximityChainDebug )
	{
		/#
			color = (1, 1, 1);
			alpha = 1;
			depth = 0;
			time = 200;
			util::debug_line(self.origin + (0,0,50), player.origin + (0,0,50), color, alpha, depth, time );
		#/						
	}
	
	self tesla_play_arc_fx( player, waitTime );
	
	player thread damagePlayerInRadius( self.origin, eAttacker, killCamEnt );	
}

function tesla_play_arc_fx( target, waitTime )
{
	if ( !IsDefined( self ) || !IsDefined( target ) )
	{	
		return;
	}
	
	tag = "J_SpineUpper";
	target_tag = "J_SpineUpper";
		
	origin = self GetTagOrigin( tag );
	target_origin = target GetTagOrigin( target_tag );
	
	distance_squared = 128 * 128;

	if ( DistanceSquared( origin, target_origin ) < distance_squared )
	{
		//( "TESLA: Not playing arcing FX. Enemies too close." );		
		return;
	}
	
	fxOrg = spawn( "script_model", origin );
	fxOrg SetModel( "tag_origin" );

	fx = PlayFxOnTag( level._effect["prox_grenade_chain_bolt"], fxOrg, "tag_origin" );
	playsoundatposition( "wpn_tesla_bounce", fxOrg.origin );
	
	fxOrg MoveTo( target_origin, waitTime );
	fxOrg waittill( "movedone" );
	fxOrg delete();
}

/#
function debugChainSphere()
{
	util::debug_sphere( self.origin + (0,0,50), 20, (1,1,1), 1, 0 );
}
#/
	
function watchProximityGrenadeHitPlayer( owner ) // self = grenade
{	
	self endon( "death" );
	
	self SetOwner( owner );
	self SetTeam( owner.team );

	while( 1 )
	{
		self waittill("grenade_bounce", pos, normal, ent, surface);
	
		if ( isdefined(ent) && isplayer( ent ) && surface != "riotshield" )
		{
			if ( ( level.teambased && ent.team == self.owner.team )) 
			{
				continue;
			}

			self proximityDetonate(self.owner, self.weapon );
			return;
		}
	}
}	

function performHudEffects( position, distanceToGrenade )
{
	forwardVec = VectorNormalize( AnglesToForward( self.angles ) );
	rightVec = VectorNormalize( AnglesToRight( self.angles ) );
	explosionVec = VectorNormalize( position - self.origin );
	
	fDot = VectorDot( explosionVec, forwardVec );
	rDot = VectorDot( explosionVec, rightVec );

	fAngle = ACos( fDot );
	rAngle = ACos( rDot );
}

function damagePlayerInRadius( position, eAttacker, killCamEnt ) // self = player in radius
{
	self notify( "proximityGrenadeDamageStart" );
	self endon( "proximityGrenadeDamageStart" );
	self endon( "disconnect" );
	self endon( "death" );
	eAttacker endon( "disconnect" );	
	
	PlayFxOnTag( level._effect["prox_grenade_player_shock"], self, "J_SpineUpper" );	
	g_time = GetTime();

	if ( self util::mayApplyScreenEffect() )
	{
		if ( !self hasPerk ("specialty_proximityprotection") )
		{
			self.lastShockedBy = eAttacker;
			self.shockEndTime = getTime() + ( level.proximityGrenadeDuration * 1000 );
			self shellshock("proximity_grenade", level.proximityGrenadeDuration, false );
		}
		
		self clientfield::set_to_player( "tazered", 1 );
	}
	
	self playrumbleonentity("proximity_grenade");
	self PlaySound( "wpn_taser_mine_zap" );

	if ( !self hasPerk ("specialty_proximityprotection") )
	{
		self thread watch_death();
		
		if ( !isdefined( killCamEnt )  )
		{
			killCamEnt = spawn( "script_model", position + (0,0,8) );		
		}
		
		killCamEnt.soundMod = "taser_spike";
		killCamEnt util::deleteAfterTime( 3 + level.proximityGrenadeDOTDamageTime * level.proximityGrenadeDOTDamageInstances);	
		self util::show_hud( 0 );
		
		damage = level.proximityGrenadeDOTDamageAmount;
		
		if( level.hardcoreMode )
		{
			damage = level.proximityGrenadeDOTDamageAmountHardcore;
		}

		for ( i = 0; i < level.proximityGrenadeDOTDamageInstances; i++ )
		{		
			assert( isdefined( eAttacker ) );
			
			if ( !isdefined( killCamEnt )  )
			{
				killCamEnt = spawn( "script_model", position + (0,0,8) );		
				killCamEnt.soundMod = "taser_spike";
				killCamEnt util::deleteAfterTime( 3 + level.proximityGrenadeDOTDamageTime * ( level.proximityGrenadeDOTDamageInstances - i ) );
			}		
	
			self DoDamage( damage, position, eAttacker, killCamEnt, "none", "MOD_GAS", 0, GetWeapon( "proximity_grenade_aoe" ) );
			
			wait( level.proximityGrenadeDOTDamageTime );
		}
		
		if (GetTime() - g_time < (level.proximityGrenadeDuration*1000) )
		{
			wait ( ( GetTime() - g_time) / 1000);
		}
	
		//self shellshock("proximity_grenade_exit", 0.6, false );
		self util::show_hud( 1 );
	}
	else
	{
		wait( level.proximityGrenadeProtectedTime );
	}
	
	self clientfield::set_to_player( "tazered", 0 );
}

function proximityDeathWait( owner )
{
	self waittill( "death" );
	
	self notify( "deleteSound" );
}

function deleteEntOnOwnerDeath( owner )
{
	self thread deleteEntOnTimeout();
	self thread deleteEntAfterTime();
	self endon( "delete" );
	owner waittill( "death" );
	self notify( "deleteSound" );
}

function deleteEntAfterTime()
{
	self endon( "delete" );
	wait( 10.0 );
	self notify( "deleteSound" );
}

function deleteEntOnTimeout()
{
	self endon( "delete" );
	self waittill( "deleteSound" );
	self delete();
}

function watch_death() // self == player
{	
	self endon( "disconnect" );
	self notify( "proximity_cleanup" );	
	self endon( "proximity_cleanup" );
	
	// fail safe stuff for if the player dies
	self waittill("death");
	self StopRumble( "proximity_grenade" );
	self setblur(0,0);
	self util::show_hud( 1 );
	self clientfield::set_to_player( "tazered", 0 );
	//self setEMPJammed( false );
}

function on_player_spawned()
{
	self thread createProximityGrenadeWatcher();
	self thread createGadgetProximityGrenadeWatcher();
	self thread begin_other_grenade_tracking();
}

function begin_other_grenade_tracking()
{
	self endon( "death" );
	self endon( "disconnect" );
	
	self notify( "proximityTrackingStart" );	
	self endon( "proximityTrackingStart" );
	
	for (;;)
	{
		self waittill ( "grenade_fire", grenade, weapon, cookTime );

		if ( grenade util::isHacked() )
		{
			continue;
		}
		
		if ( weapon.rootWeapon.name == "proximity_grenade" )
		{
			grenade thread watchProximityGrenadeHitPlayer( self );
		}
	}
}

function on_damage( eAttacker, eInflictor, weapon, meansOfDeath, damage )
{	
	self thread proximityGrenadeDamagePlayer( eAttacker, eInflictor, eInflictor.killCamEnt, weapon, meansOfDeath, damage, undefined );
}
