#include animscripts\Utility;
#include maps\_gameskill;
#include maps\_utility;
#include common_scripts\utility;
#include animscripts\SetPoseMovement;
#using_animtree( "generic_human" );


getTargetAngleOffset( target )
{
	pos = self getshootatpos() + ( 0, 0, -3 );// compensate for eye being higher than gun
	dir = ( pos[ 0 ] - target[ 0 ], pos[ 1 ] - target[ 1 ], pos[ 2 ] - target[ 2 ] );
	dir = VectorNormalize( dir );
	fact = dir[ 2 ] * - 1;
//	println ("offset "  + fact);
	return fact;
}

getSniperBurstDelayTime()
{
	if ( isPlayer( self.enemy ) )
		return randomFloatRange( self.enemy.gs.min_sniper_burst_delay_time, self.enemy.gs.max_sniper_burst_delay_time );
	else
		return randomFloatRange( anim.min_sniper_burst_delay_time, anim.max_sniper_burst_delay_time );
}

getRemainingBurstDelayTime()
{
	timeSoFar = ( gettime() - self.a.lastShootTime ) / 1000;
	delayTime = getBurstDelayTime();
	if ( delayTime > timeSoFar )
		return delayTime - timeSoFar;
	return 0;
}

getBurstDelayTime()
{
	if ( self usingSidearm() )
		return randomFloatRange( .15, .55 );
	else if ( weapon_pump_action_shotgun() )
		return randomFloatRange( 1.0, 1.7 );
	else if ( self isSniper() )
		return getSniperBurstDelayTime();
	else if ( self.fastBurst )
		return randomFloatRange( .1, .35 );
	else
		return randomFloatRange( .4, .9 );
}

burstDelay()
{
	if ( self.bulletsInClip )
	{
		if ( self.shootStyle == "full" && !self.fastBurst )
		{
			if ( self.a.lastShootTime == gettime() )
				wait .05;
			return;
		}

		delayTime = getRemainingBurstDelayTime();
		if ( delayTime )
			wait delayTime;
	}
}

cheatAmmoIfNecessary()
{
	assert( !self.bulletsInClip );

	if ( !isdefined( self.enemy ) )
		return false;

	if ( self.team != "allies" )
	{
		// cheat and finish off the player if we can.
		if ( !isPlayer( self.enemy ) )
			return false;
		//if ( self.enemy.health > self.enemy.maxHealth * level.healthOverlayCutoff )
		//	return false;

		if ( self.enemy ent_flag( "player_is_invulnerable" ) )
			return false;
	}

	if ( usingSidearm() || usingRocketLauncher() )
		return false;

	if ( gettime() - self.ammoCheatTime < self.ammoCheatInterval )
		return false;

	if ( !self canSee( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) > 256*256 )
		return false;

	self.bulletsInClip = int( weaponClipSize( self.weapon ) / 2 );

	if ( self.bulletsInClip > weaponClipSize( self.weapon ) )
		self.bulletsInClip = weaponClipSize( self.weapon );

	self.ammoCheatTime = gettime();

	return true;
}


dontShoot_totalTime = 3;
dontShoot_interval = 0.05;
dontShoot_loopCount = dontShoot_totalTime / dontShoot_interval;

aimButDontShoot()
{
	loopCount = int( dontShoot_loopCount );
	
	while ( loopCount > 0 )
	{
		assert( !isdefined( self.dontEverShoot ) || self.dontEverShoot != 0 );
		
		if ( isdefined( self.dontEverShoot ) || ( isdefined( self.enemy ) && isdefined( self.enemy.dontAttackMe ) ) )
			wait dontShoot_interval;
		else
			return false;
			
		loopCount--;
	}
	
	return true;
}

areAlliesInCone()
{
	// Do a forward cone check to make sure we aren't going to blast the crap out of allies
	shoot_from_pos = animscripts\shared::getShootFromPos();
	enemy_sight_pos = self GetEnemyEyePos();
	aim_dir = vectornormalize( enemy_sight_pos - shoot_from_pos );

	// 0.866 halfcone = cos( 30 ) = 60 degree wide cone
	// 0.966 halfcone = cos( 15 ) = 30 degree wide cone
	cos_halfcone = 0.966;
	if ( self isAllyInCone( shoot_from_pos, aim_dir, cos_halfcone, 512.0 ) )
	{
	   	return true;
	}

	return false;
}


shootUntilShootBehaviorChange()
{
	self endon( "shoot_behavior_change" );
	self endon( "stopShooting" );

	if ( self isLongRangeAI() )
	{
		if ( isDefined( self.enemy ) && isAI( self.enemy ) && distanceSquared( level._player.origin, self.enemy.origin ) < 384 * 384 )
			self.enemy animscripts\battlechatter_ai::addThreatEvent( "infantry", self, 1.0 );

		if ( usingRocketLauncher() && isSentient( self.enemy ) )
			wait( randomFloat( 2.0 ) );
	}

	if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) > squared( 400 ) )
		burstCount = randomintrange( 1, 5 );
	else
		burstCount = 10;

	while ( 1 )
	{
		burstDelay();// waits only if necessary
		
		// TODO: This sort of logic should really be in shoot_behavior. This thread is meant to be slave to shootent, shootpos, and shootstyle.
		if ( aimButDontShoot() )
			break;
		
		if ( self.shootStyle == "continuous" )
		{
			if ( areAlliesInCone() )
			{
				// Ally in front of us, don't fire!
				wait 1.0;
				continue;
			}

			self ContinuousFire( animArrayPickRandom( "continuous" ), true, 1.0 );
			self notify( "stop_continuous_fire" );
		}
		else if ( self.shootStyle == "full" )
		{
			// TODO: get rid of 'stopOnAnimationEnd', makes autofire not work if not enough fire notetracks
			self FireUntilOutOfAmmo( animArray( "fire" ), true, animscripts\shared::decideNumShotsForFull() );
		}
		else if ( self.shootStyle == "burst" || self.shootStyle == "semi" )
		{
			numShots = animscripts\shared::decideNumShotsForBurst();

			if ( numShots == 1 )
				self FireUntilOutOfAmmo( animArrayPickRandom( "single" ), true, numShots );
			else
				self FireUntilOutOfAmmo( animArray( self.shootStyle + numShots ), true, numShots );
		}
		else if ( self.shootStyle == "single" )
		{
			self FireUntilOutOfAmmo( animArrayPickRandom( "single" ), true, 1 );
		}
		else
		{
			assert( self.shootStyle == "none" );
			self waittill( "hell freezes over" );// waits for the endons to happen
		}

		if ( !self.bulletsInClip )
			break;

		burstCount--;
		if ( burstCount < 0 )
		{
			self.shouldReturnToCover = true;
			break;
		}
	}
}

getUniqueFlagNameIndex()
{
	anim.animFlagNameIndex++;
	return anim.animFlagNameIndex;
}

setupAim( transTime )
{
	assert( isDefined( transTime ) );
	
	self setAnim( %exposed_aiming, 1, .2 );
	self setAnimKnobLimited( animarray( "straight_level" ), 1, transTime );
	self setAnimKnobLimited( animArray( "add_aim_up" ), 1, transTime );
	self setAnimKnobLimited( animArray( "add_aim_down" ), 1, transTime );
	self setAnimKnobLimited( animArray( "add_aim_left" ), 1, transTime );
	self setAnimKnobLimited( animArray( "add_aim_right" ), 1, transTime );
}

startFireAndAimIdleThread()
{
	if ( !isdefined( self.a.aimIdleThread ) )
	{
		setupAim( 0.2 );
		self thread aimIdleThread();
		self thread animscripts\shared::trackShootEntOrPos();
	}
}

endFireAndAnimIdleThread()
{
	endAimIdleThread();
	self clearAnim( %add_fire, .1 );
	self notify( "stop tracking" );
}

showFireHideAimIdle()
{
	if ( isdefined( self.a.aimIdleThread ) )
		self setAnim( %add_idle, 0, .2 );
		
	self setAnim( %add_fire, 1, .1 );
}

hideFireShowAimIdle()
{
	if ( isdefined( self.a.aimIdleThread ) )
		self setAnim( %add_idle, 1, .2 );
		
	self setAnim( %add_fire, 0, .1 );
}

aimIdleThread( lean )
{
	self endon( "killanimscript" );
	self endon( "end_aim_idle_thread" );
	
	if ( isdefined( self.a.aimIdleThread ) )
		return;
	self.a.aimIdleThread = true;
	
	// wait a bit before starting idle since firing will end the idle thread
	wait 0.1;
	
	// this used to be setAnim, but it caused problems with turning on its parent nodes when they were supposed to be off (like during pistol pullout).
	self setAnimLimited( %add_idle, 1, .2 );
	
	for ( i = 0; ; i++ )
	{
		flagname = "idle" + i;

		if ( isdefined( self.a.leanAim ) )
			idleanim = animArrayPickRandom( "lean_idle" );
		else
			idleanim = animArrayPickRandom( "exposed_idle" );
		
		self setFlaggedAnimKnobLimitedRestart( flagname, idleanim, 1, 0.2 );
		
		self waittillmatch( flagname, "end" );
	}
	
	self clearAnim( %add_idle, .1 );
}

endAimIdleThread()
{
	self notify( "end_aim_idle_thread" );
	self.a.aimIdleThread = undefined;
	self clearAnim( %add_idle, .1 );
}

shotgunFireRate()
{
	if ( weapon_pump_action_shotgun() )
		return 1.0;

	if ( animscripts\weaponList::usingAutomaticWeapon() )
		return animscripts\weaponList::autoShootAnimRate() * 0.7;
	
	return 0.4;
}

ContinuousFire( fireAnim, stopOnAnimationEnd, duration )
{
	animName = "fireAnim_" + getUniqueFlagNameIndex();
	
	//prof_begin("FireUntilOutOfAmmo");

	//self thread maintain_continuous_fire();

	// reset our accuracy as we aim
	maps\_gameskill::resetMissTime();

	// first, wait until we're aimed right
	while ( !aimedAtShootEntOrPos() )
		wait .05;

	//prof_begin("FireUntilOutOfAmmo");		
	self showFireHideAimIdle();

	self setFlaggedAnimKnobRestart( animName, fireAnim, 1, .2, 1.0 );

	// Update the sight accuracy against the player.  Should be called before the volley starts.
	self updatePlayerSightAccuracy();

	//prof_end("FireUntilOutOfAmmo");

	//FireUntilOutOfAmmoInternal( animName, fireAnim, stopOnAnimationEnd, maxshots );

	self endon( "enemy" );// stop shooting if our enemy changes, because we have to reset our accuracy and stuff
	// stop shooting if the player becomes invulnerable, so we will call resetAccuracyAndPause again
	if ( isPlayer( self.enemy ) && ( self.shootStyle == "full" || self.shootStyle == "semi" ) )
		level endon( "player_becoming_invulnerable" );

	self thread NotifyOnAnimEnd( animName, "fireAnimEnd" );
	self endon( "fireAnimEnd" );


	if ( cointoss() )
	{
		// Miss! Set new pos
		//self.shootPos = self.shootPos + randomvector( 50 );
	}

	self.shootEnt = undefined;
	self StartContinuousFire();
	self thread StopContinuousFireOnExit();
	
	if ( animHasNoteTrack( fireAnim, "fire_continuous_start" ) && animHasNoteTrack( fireAnim, "fire_continuous_end" ) )
	{
		self waittillmatch( animName, "fire_continuous_start" );
		self thread StopContinuousFireOnNoteTrack( animName );		
	}	
	
	while ( 1 )
	{
		if ( !self.bulletsInClip )
		{
			if ( !cheatAmmoIfNecessary() )
			{
				break;
			}
		}
		if ( !aimedAtShootEntOrPos())
		{
			break;
		}

		//self shootAtShootEntOrPos();

		self.a.lastShootTime = gettime();

		// set accuracy at time of shoot rather than in a separate thread that is vulnerable to timing issues
		maps\_gameskill::set_accuracy_based_on_situation();

		self animscripts\shared::fire_straight();			
		
		wait 0.05;		
	}

	self hideFireShowAimIdle();
}

StopContinuousFireOnExit()
{
	waittill_any( "stop_continuous_fire", "shoot_behavior_change", "stopShooting", "killanimscript", "stop_deciding_how_to_shoot" );
	self StopContinuousFire();
}

StopContinuousFireOnNoteTrack( animName )
{	
	self waittillmatch( animName, "fire_continuous_end" );
	self notify( "stop_continuous_fire" );
}


FireUntilOutOfAmmo( fireAnim, stopOnAnimationEnd, maxshots )
{
	animName = "fireAnim_" + getUniqueFlagNameIndex();

	//prof_begin("FireUntilOutOfAmmo");

	// reset our accuracy as we aim
	maps\_gameskill::resetMissTime();

	// first, wait until we're aimed right
	while ( !aimedAtShootEntOrPos() )
		wait .05;

	//prof_begin("FireUntilOutOfAmmo");		
	self showFireHideAimIdle();

	rate = 1.0;
	if ( isdefined( self.shootRateOverride ) )
		rate = self.shootRateOverride;
	else if ( self.shootStyle == "full" )
		rate = animscripts\weaponList::autoShootAnimRate() * randomfloatrange( 0.5, 1.0 );
	else if ( self.shootStyle == "burst" )
		rate = animscripts\weaponList::burstShootAnimRate();
	else if ( usingSidearm() )
		rate = 3.0;
	else if ( usingShotgun() )
		rate = shotgunFireRate();

	self setFlaggedAnimKnobRestart( animName, fireAnim, 1, .2, rate );

	// Update the sight accuracy against the player.  Should be called before the volley starts.
	self updatePlayerSightAccuracy();

	//prof_end("FireUntilOutOfAmmo");

	FireUntilOutOfAmmoInternal( animName, fireAnim, stopOnAnimationEnd, maxshots );

	self hideFireShowAimIdle();
}

FireUntilOutOfAmmoInternal( animName, fireAnim, stopOnAnimationEnd, maxshots )
{
	self endon( "enemy" );// stop shooting if our enemy changes, because we have to reset our accuracy and stuff
	// stop shooting if the player becomes invulnerable, so we will call resetAccuracyAndPause again
	if ( isPlayer( self.enemy ) && ( self.shootStyle == "full" || self.shootStyle == "semi" ) )
		level endon( "player_becoming_invulnerable" );

	if ( stopOnAnimationEnd )
	{
		self thread NotifyOnAnimEnd( animName, "fireAnimEnd" );
		self endon( "fireAnimEnd" );
	}

	if ( !isdefined( maxshots ) )
		maxshots = -1;

	numshots = 0;

	hasFireNotetrack = animHasNoteTrack( fireAnim, "fire" );

	usingRocketLauncher = ( weaponClass( self.weapon ) == "rocketlauncher" );

	while ( numshots < maxshots && maxshots > 0 ) // note: maxshots == -1 if no limit
	{
//		prof_begin("FireUntilOutOfAmmoInternal");

		if ( hasFireNotetrack )
			self waittillmatch( animName, "fire" );

		if ( !self.bulletsInClip )
		{
			if ( !cheatAmmoIfNecessary() )
				break;
		}

		if ( !aimedAtShootEntOrPos() )
			break;
		
		self shootAtShootEntOrPos();

		assertex( self.bulletsInClip >= 0, self.bulletsInClip );
		if ( isPlayer( self.enemy ) && self.enemy ent_flag( "player_is_invulnerable" ) )
		{
			if ( randomint( 3 ) == 0 )
				self.bulletsInClip -- ;
		}
		else
		{
			self.bulletsInClip -- ;
		}

		if ( usingRocketLauncher )
		{
			self.a.rockets -- ;
			if ( self.weapon == "rpg" )
			{
				self hidepart( "tag_rocket" );
				self.a.rocketVisible = false;
			}
		}
		
		numshots++;

		self thread shotgunPumpSound( animName );

		if ( self.fastBurst && numshots == maxshots )
			break;

//		prof_end("FireUntilOutOfAmmoInternal");

 		if ( !hasFireNotetrack || (maxShots == 1 && self.shootStyle == "single") )
			self waittillmatch( animName, "end" );
	}

	if ( stopOnAnimationEnd )
		self notify( "fireAnimEnd" );// stops NotifyOnAnimEnd()
}

streamgun_check_cone_for_allies()
{
	// Check


	//prof_begin( "aimedAtShootEntOrPos" );
	if ( !isdefined( self.shootPos ) )
	{
		assert( !isdefined( self.shootEnt ) );

		//prof_end( "aimedAtShootEntOrPos" );
		return true;
	}

	weaponAngles = self getMuzzleAngle();
	shootFromPos = animscripts\shared::getShootFromPos();
	
	anglesToShootPos = vectorToAngles( self.shootPos - shootFromPos );

	absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToShootPos[ 1 ] );
	if ( absyawdiff > anim.aimYawDiffFarTolerance )
	{
		if ( distanceSquared( self getEye(), self.shootPos ) > anim.aimYawDiffCloseDistSQ || absyawdiff > anim.aimYawDiffCloseTolerance )
		{
			//prof_end( "aimedAtShootEntOrPos" );
			return false;
		}
	}

	//prof_end( "aimedAtShootEntOrPos" );
	return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToShootPos[ 0 ] ) <= anim.aimPitchDiffTolerance;
}

aimed_outside_of_yaw_tolerance()
{
	//prof_begin( "aimedAtShootEntOrPos" );
	if ( !isdefined( self.shootPos ) )
	{
		assert( !isdefined( self.shootEnt ) );

		//prof_end( "aimedAtShootEntOrPos" );
		return true;
	}

	weaponAngles = self getMuzzleAngle();
	shootFromPos = animscripts\shared::getShootFromPos();
	
	anglesToShootPos = vectorToAngles( self.shootPos - shootFromPos );

	absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToShootPos[ 1 ] );
	if ( absyawdiff > anim.aimYawDiffFarTolerance )
	{
		if ( distanceSquared( self getEye(), self.shootPos ) > anim.aimYawDiffCloseDistSQ || absyawdiff > anim.aimYawDiffCloseTolerance )
		{
			//prof_end( "aimedAtShootEntOrPos" );
			return false;
		}
	}

	//prof_end( "aimedAtShootEntOrPos" );
	return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToShootPos[ 0 ] ) <= anim.aimPitchDiffTolerance;
}

aimedAtShootEntOrPos()
{
	//prof_begin( "aimedAtShootEntOrPos" );
	if ( !isdefined( self.shootPos ) )
	{
		assert( !isdefined( self.shootEnt ) );

		//prof_end( "aimedAtShootEntOrPos" );
		return true;
	}

	weaponAngles = self getMuzzleAngle();
	shootFromPos = animscripts\shared::getShootFromPos();
	
	anglesToShootPos = vectorToAngles( self.shootPos - shootFromPos );

	absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToShootPos[ 1 ] );
	if ( absyawdiff > anim.aimYawDiffFarTolerance )
	{
		if ( distanceSquared( self getEye(), self.shootPos ) > anim.aimYawDiffCloseDistSQ || absyawdiff > anim.aimYawDiffCloseTolerance )
		{
			//prof_end( "aimedAtShootEntOrPos" );
			return false;
		}
	}

	//prof_end( "aimedAtShootEntOrPos" );
	return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToShootPos[ 0 ] ) <= anim.aimPitchDiffTolerance;
}

NotifyOnAnimEnd( animNotify, endNotify )
{
	self endon( "killanimscript" );
	self endon( endNotify );
	self waittillmatch( animNotify, "end" );
	self notify( endNotify );
}

shootAtShootEntOrPos()
{
	//prof_begin("shootAtShootEntOrPos");

	if ( isdefined( self.shootEnt ) )
	{
		if ( isDefined( self.enemy ) && self.shootEnt == self.enemy )
			self shootEnemyWrapper();

		// it's possible that shootEnt isn't our enemy, which was probably caused by our enemy changing but shootEnt not being updated yet.
		// we don't want to shoot directly at shootEnt because if our accuracy is 0 we shouldn't hit it perfectly.
		// In retrospect, the existance of self.shootEnt was a bad idea and self.enemy should probably have just been used.
		//else
		//	self shootPosWrapper( self.shootEnt getShootAtPos() );
	}
	else
	{
		// if self.shootPos isn't defined, "shoot_behavior_change" should
		// have been notified and we shouldn't be firing anymore
		assert( isdefined( self.shootPos ) );

		self shootPosWrapper( self.shootPos );
	}

	//prof_end("shootAtShootEntOrPos");
}

showRocket()
{
	if ( self.weapon != "rpg" )
		return;

	self.a.rocketVisible = true;
	self showpart("tag_rocket");
	self notify( "showing_rocket" );
}

showRocketWhenReloadIsDone()
{
	if ( self.weapon != "rpg" )
		return;

	self endon( "death" );
	self endon( "showing_rocket" );
	self waittill( "killanimscript" );

	self showRocket();
}

decrementBulletsInClip()
{
	// we allow this to happen even when bulletsinclip is zero,
	// because sometimes we want to shoot even if we're out of ammo,
	// like when we've already started a blind fire animation.
	if ( self.bulletsInClip )
		self.bulletsInClip -- ;
}

shotgunPumpSound( animName )
{
	if ( !weapon_pump_action_shotgun() )
		return;

	self endon( "killanimscript" );

	self notify( "shotgun_pump_sound_end" );
	self endon( "shotgun_pump_sound_end" );

	self thread stopShotgunPumpAfterTime( 2.0 );

	self waittillmatch( animName, "rechamber" );

	self playSound( "ai_shotgun_pump" );

	self notify( "shotgun_pump_sound_end" );
}

stopShotgunPumpAfterTime( timer )
{
	self endon( "killanimscript" );
	self endon( "shotgun_pump_sound_end" );
	wait timer;
	self notify( "shotgun_pump_sound_end" );
}

// Rechambers the weapon if appropriate
Rechamber( isExposed )
{
	// obsolete...
}

// Returns true if character has less than thresholdFraction of his total bullets in his clip.  Thus, a value 
// of 1 would always reload, 0 would only reload on an empty clip.
NeedToReload( thresholdFraction )
{
	if ( self.weapon == "none" )
		return false;
		
	if ( isdefined( self.noreload ) || self.weaponInfo[ self.weapon ].recharges )
	{
		if ( self.bulletsinclip < weaponClipSize( self.weapon ) * 0.5 )
			self.bulletsinclip = int( weaponClipSize( self.weapon ) * 0.5 );
		if ( self.bulletsinclip <= 0 )
			self.bulletsinclip = 0;
		return false;
	}
	
	if ( self.bulletsInClip <= weaponClipSize( self.weapon ) * thresholdFraction )
	{
		if ( thresholdFraction == 0 )
		{
			if ( cheatAmmoIfNecessary() )
				return false;
		}

		return true;
	}
	return false;
}

// Put the gun back in the AI's hand if he cuts off his weapon throw down animation
putGunBackInHandOnKillAnimScript()
{
	self endon( "weapon_switch_done" );
	self endon( "death" );

	self waittill( "killanimscript" );
	animscripts\shared::placeWeaponOn( self.primaryweapon, "right" );
}

Reload( thresholdFraction, optionalAnimation )
{
	self endon( "killanimscript" );

	if ( !NeedToReload( thresholdFraction ) )
		return false;

	//prof_begin( "Reload" );

	self animscripts\battleChatter_ai::evaluateReloadEvent();
	self animscripts\battleChatter::playBattleChatter();

	if ( isDefined( optionalAnimation ) )
	{
		self setFlaggedAnimKnobAll( "reloadanim", optionalAnimation, %body, 1, .1, 1 );
		animscripts\shared::DoNoteTracks( "reloadanim" );
		self animscripts\weaponList::RefillClip();	// This should be in the animation as a notetrack in theory.
		self.a.needsToRechamber = 0;
	}
	else
	{
		if ( self.a.pose == "prone" )
		{
			self setFlaggedAnimKnobAll( "reloadanim", %prone_reload, %body, 1, .1, 1 );
			self UpdateProne( %prone_legs_up, %prone_legs_down, 1, 0.1, 1 );
		}
		else
		{
			println( "Bad anim_pose in combat::Reload" );
			//prof_end( "Reload" );
			wait 2;
			return;
		}
		animscripts\shared::DoNoteTracks( "reloadanim" );
		animscripts\weaponList::RefillClip();	// This should be in the animation as a notetrack in most instances.
		self.a.needsToRechamber = 0;
	}

	//prof_end( "Reload" );
	return true;
}

getGrenadeThrowOffset( throwAnim )
{
	//prof_begin( "getGrenadeThrowOffset" );
	offset = ( 0, 0, 64 );

	if ( isdefined( throwAnim ) )
	{
		foreach( index, grenadeThrowAnim in anim.grenadeThrowAnims )
		{
			if ( throwAnim == grenadeThrowAnim )
			{
				offset = anim.grenadeThrowOffsets[ index ];
				break;
			}
		}
	}

	if ( offset[ 2 ] == 64 )
	{
		if ( isdefined( throwAnim ) )
			println( "^1Warning: undefined grenade throw animation used; hand offset unknown" );
		else
			println( "^1Warning: grenade throw animation ", throwAnim, " has no recorded hand offset" );
	}

	//prof_end( "getGrenadeThrowOffset" );
	return offset;
}

// this function is called from maps\_utility::ThrowGrenadeAtPlayerASAP
ThrowGrenadeAtPlayerASAP_combat_utility()
{
	assert( self isBadGuy() );
	
	for ( i = 0; i < level._players.size; i++ )
	{
		if ( level._players[ i ].numGrenadesInProgressTowardsPlayer == 0 )
		{
			level._players[ i ].grenadeTimers[ "fraggrenade" ] = 0;
			level._players[ i ].grenadeTimers[ "flash_grenade" ] = 0;
		}
	}
	anim.throwGrenadeAtPlayerASAP = true;

	/#
	enemies = getaiarray( "bad_guys" );
	if ( enemies.size == 0 )
		return;
	numwithgrenades = 0;
	for ( i = 0; i < enemies.size; i++ )
	{
		if ( enemies[ i ].grenadeammo > 0 )
			return;
	}
	println( "^1Warning: called ThrowGrenadeAtPlayerASAP, but no enemies have any grenadeammo!" );
	#/
}

setActiveGrenadeTimer( throwingAt )
{
	self.activeGrenadeTimer = spawnstruct();
	if ( isPlayer( throwingAt ) )
	{
		self.activeGrenadeTimer.isPlayerTimer = true;
		self.activeGrenadeTimer.player = throwingAt;
		self.activeGrenadeTimer.timerName = self.grenadeWeapon;
		assertex( isdefined( throwingAt.grenadeTimers[ self.activeGrenadeTimer.timerName ] ), "No grenade timer for " + self.activeGrenadeTimer.timerName );
	}
	else
	{
		self.activeGrenadeTimer.isPlayerTimer = false;
		self.activeGrenadeTimer.timerName = "AI_" + self.grenadeWeapon;
		assertex( isdefined( anim.grenadeTimers[ self.activeGrenadeTimer.timerName ] ), "No grenade timer for " + self.activeGrenadeTimer.timerName );
	}
}

usingPlayerGrenadeTimer()
{
	assert( isDefined( self.activeGrenadeTimer ) );
	return self.activeGrenadeTimer.isPlayerTimer;
}

setGrenadeTimer( grenadeTimer, newValue )
{
	if ( grenadeTimer.isPlayerTimer )
	{
		player = grenadeTimer.player;
		oldValue = player.grenadeTimers[ grenadeTimer.timerName ];
		player.grenadeTimers[ grenadeTimer.timerName ] = max( newValue, oldValue );
	}
	else
	{
		oldValue = anim.grenadeTimers[ grenadeTimer.timerName ];
		anim.grenadeTimers[ grenadeTimer.timerName ] = max( newValue, oldValue );
	}
}

getDesiredGrenadeTimerValue()
{
	nextGrenadeTimeToUse = undefined;
	if ( self usingPlayerGrenadeTimer() )
	{
		player = self.activeGrenadeTimer.player;
		nextGrenadeTimeToUse = gettime() + player.gs.playerGrenadeBaseTime + randomint( player.gs.playerGrenadeRangeTime );
	}
	else
	{
		nextGrenadeTimeToUse = gettime() + 30000 + randomint( 30000 );
	}
	return nextGrenadeTimeToUse;
}

getGrenadeTimerTime( grenadeTimer )
{
	if ( grenadeTimer.isPlayerTimer )
	{
		return grenadeTimer.player.grenadeTimers[ grenadeTimer.timerName ];
	}
	else
	{
		return anim.grenadeTimers[ grenadeTimer.timerName ];
	}
}

considerChangingTarget( throwingAt )
{
	//prof_begin( "considerChangingTarget" );

	if ( !isPlayer( throwingAt ) && self isBadGuy() )
	{
		if ( gettime() < getGrenadeTimerTime( self.activeGrenadeTimer ) )
		{
			if ( level._player.ignoreme )
			{
				//prof_end( "considerChangingTarget" );
				return throwingAt;
			}

			// check if player threatbias is set to be ignored by self
			myGroup = self getthreatbiasgroup();
			playerGroup = level._player getthreatbiasgroup();

			if ( myGroup != "" && playerGroup != "" && getThreatBias( playerGroup, myGroup ) < - 10000 )
			{
				//prof_end( "considerChangingTarget" );
				return throwingAt;
			}


			// can't throw at an AI right now anyway.
			// check if the player is an acceptable target (be careful not to be aware of him when we wouldn't know about him)
			if ( self canSee( level._player ) || ( isAI( throwingAt ) && throwingAt canSee( level._player ) ) )
			{
				if ( isdefined( self.covernode ) )
				{
					angles = VectorToAngles( level._player.origin - self.origin );
					yawDiff =  AngleClamp180( self.covernode.angles[ 1 ] - angles[ 1 ] );
				}
				else
				{
					yawDiff = self GetYawToSpot( level._player.origin );
				}

				if ( abs( yawDiff ) < 60 )
				{
					throwingAt = level._player;
					self setActiveGrenadeTimer( throwingAt );
				}
			}
		}
	}

	//prof_end( "considerChangingTarget" );
	return throwingAt;
}

// a "double" grenade is when 2 grenades land at the player's feet at once.
// we do this sometimes on harder difficulty modes.
mayThrowDoubleGrenade( throwingAt )
{
	assert( self.activeGrenadeTimer.isPlayerTimer );
	assert( self.activeGrenadeTimer.timerName == "fraggrenade" );
	assert( isPlayer( throwingAt ) );

	if ( player_died_recently() )
		return false;

	if ( !throwingAt.gs.double_grenades_allowed )
		return false;

	time = gettime();

	// if it hasn't been long enough since the last double grenade, don't do it
	if ( time < throwingAt.grenadeTimers[ "double_grenade" ] )
		return false;

	// if no one's started throwing a grenade recently, we can't do it
	if ( time > throwingAt.lastFragGrenadeToPlayerStart + 3000 )
		return false;
		
	// stagger double grenades by 0.5 sec
	if ( time < throwingAt.lastFragGrenadeToPlayerStart + 500 )
		return false;	

	return throwingAt.numGrenadesInProgressTowardsPlayer < 2;
}

myGrenadeCoolDownElapsed()
{
	// this should be as fast as possible; put slow checks in grenadeCoolDownElapsed
	return( gettime() >= self.a.nextGrenadeTryTime );
}

grenadeCoolDownElapsed( throwingAt )
{
	if ( player_died_recently() )
		return false;

	if ( self.script_forcegrenade == 1 )
		return true;

	// If we are at a node with script_forcegrenade defined, there is no grenade cooldown
	if ( isdefined( self.node ) && isdefined( self.node.script_forcegrenade ) && self.node.script_forcegrenade == 1 )
		return true;

	if ( !myGrenadeCoolDownElapsed() )
		return false;

	if ( gettime() >= getGrenadeTimerTime( self.activeGrenadeTimer ) )
		return true;

	if ( self.activeGrenadeTimer.isPlayerTimer && self.activeGrenadeTimer.timerName == "fraggrenade" )
		return mayThrowDoubleGrenade( throwingAt );

	return false;
}

 /#
getGrenadeTimerDebugName( grenadeTimer )
{
	if ( grenadeTimer.isPlayerTimer )
	{
		for ( i = 0; i < level._players.size; i++ )
		{
			if ( level._players[ i ] == grenadeTimer.player )
				break;
		}
		return "Player " + ( i + 1 ) + " " + grenadeTimer.timerName;
	}
	else
	{
		return "AI " + grenadeTimer.timerName;
	}
}

printGrenadeTimers()
{
	level notify( "stop_printing_grenade_timers" );
	level endon( "stop_printing_grenade_timers" );

	x = 40;
	y = 40;

	level._grenadeTimerHudElem = [];

	level._grenadeDebugTimers = [];
	keys = getArrayKeys( anim.grenadeTimers );
	for ( i = 0; i < keys.size; i++ )
	{
		timer = spawnstruct();
		timer.isPlayerTimer = false;
		timer.timerName = keys[ i ];
		level._grenadeDebugTimers[ level._grenadeDebugTimers.size ] = timer;
	}
	for ( i = 0; i < level._players.size; i++ )
	{
		player = level._players[ i ];
		keys = getArrayKeys( player.grenadeTimers );
		for ( j = 0; j < keys.size; j++ )
		{
			timer = spawnstruct();
			timer.isPlayerTimer = true;
			timer.player = player;
			timer.timerName = keys[ j ];
			level._grenadeDebugTimers[ level._grenadeDebugTimers.size ] = timer;
		}
	}

	for ( i = 0; i < level._grenadeDebugTimers.size; i++ )
	{
		textelem = newHudElem();
		textelem.x = x;
		textelem.y = y;
		textelem.alignX = "left";
		textelem.alignY = "top";
		textelem.horzAlign = "fullscreen";
		textelem.vertAlign = "fullscreen";
		textelem setText( getGrenadeTimerDebugName( level._grenadeDebugTimers[ i ] ) );

		bar = newHudElem();
		bar.x = x + 110;
		bar.y = y + 2;
		bar.alignX = "left";
		bar.alignY = "top";
		bar.horzAlign = "fullscreen";
		bar.vertAlign = "fullscreen";
		bar setshader( "black", 1, 8 );

		textelem.bar = bar;

		y += 10;

		level._grenadeDebugTimers[ i ].textelem = textelem;
	}

	while ( 1 )
	{
		wait .05;

		for ( i = 0; i < level._grenadeDebugTimers.size; i++ )
		{
			timeleft = ( getGrenadeTimerTime( level._grenadeDebugTimers[ i ] ) - gettime() ) / 1000;

			width = max( timeleft * 4, 1 );
			width = int( width );

			bar = level._grenadeDebugTimers[ i ].textelem.bar;
			bar setShader( "black", width, 8 );
		}
	}
}

destroyGrenadeTimers()
{
	if ( !isdefined( level._grenadeDebugTimers ) )
		return;
	for ( i = 0; i < level._grenadeDebugTimers.size; i++ )
	{
		level._grenadeDebugTimers[ i ].textelem.bar destroy();
		level._grenadeDebugTimers[ i ].textelem destroy();
	}
	level._grenadeDebugTimers = undefined;
}

grenadeTimerDebug()
{
	setDvarIfUninitialized( "scr_grenade_debug", "0" );

	while ( 1 )
	{
		while ( 1 )
		{
			if ( getdebugdvar( "scr_grenade_debug" ) == "1" )
				break;
			wait .5;
		}
		thread printGrenadeTimers();
		while ( 1 )
		{
			if ( getdebugdvar( "scr_grenade_debug" ) != "1" )
				break;
			wait .5;
		}
		level notify( "stop_printing_grenade_timers" );
		destroyGrenadeTimers();
	}
}

grenadeDebug( state, duration, showMissReason )
{
	if ( getdebugdvar( "scr_grenade_debug" ) != "1" )
		return;

	self notify( "grenade_debug" );
	self endon( "grenade_debug" );
	self endon( "killanimscript" );
	self endon( "death" );
	endtime = gettime() + 1000 * duration;

	while ( gettime() < endtime )
	{
		if ( !isDefined( self ) )
		{
			return;
		}
		print3d( self getShootAtPos() + ( 0, 0, 10 ), state );
		if ( isdefined( showMissReason ) && isdefined( self.grenadeMissReason ) )
			print3d( self getShootAtPos() + ( 0, 0, 0 ), "Failed: " + self.grenadeMissReason );
		else if ( isdefined( self.activeGrenadeTimer ) )
			print3d( self getShootAtPos() + ( 0, 0, 0 ), "Timer: " + getGrenadeTimerDebugName( self.activeGrenadeTimer ) );
		wait .05;
	}
}

setGrenadeMissReason( reason )
{
	if ( getdebugdvar( "scr_grenade_debug" ) != "1" )
		return;
	self.grenadeMissReason = reason;
}
#/

TryGrenadePosProc( throwingAt, destination, optionalAnimation, armOffset )
{
	// Dont throw a grenade right near you or your buddies
	if ( !( self isGrenadePosSafe( throwingAt, destination ) ) )
		return false;
	else if ( distanceSquared( self.origin, destination ) < 200 * 200 )
		return false;

	//prof_begin( "TryGrenadePosProc" );	

	trace = physicsTrace( destination + ( 0, 0, 1 ), destination + ( 0, 0, -500 ) );
	if ( trace == destination + ( 0, 0, -500 ) )
		return false;
	trace += ( 0, 0, .1 );// ensure just above ground

	//prof_end( "TryGrenadePosProc" );	

	return TryGrenadeThrow( throwingAt, trace, optionalAnimation, armOffset );
}

TryGrenade( throwingAt, optionalAnimation )
{
	if ( self.weapon == "mg42" || self.grenadeammo <= 0 )
		return false;

	self setActiveGrenadeTimer( throwingAt );

	throwingAt = considerChangingTarget( throwingAt );

	if ( !grenadeCoolDownElapsed( throwingAt ) )
		return false;

	// tagJW<NOTE>: Do not throw grenades at enemy vehicles
	if ( !isAI( throwingAt ) && !isPlayer( throwingAt ) )
		return false;

	 /#
	self thread grenadeDebug( "Tried grenade throw", 4, true );
	#/

	armOffset = getGrenadeThrowOffset( optionalAnimation );

	if ( isdefined( self.enemy ) && throwingAt == self.enemy )
	{
		if ( !checkGrenadeThrowDist() )
		{
			/# self setGrenadeMissReason( "Too close or too far" ); #/
			return false;
		}

		if ( isPlayer( self.enemy ) && self.enemy isPlayerDown() )
		{
			/# self setGrenadeMissReason( "Enemy is downed player" ); #/
			return false;
		}

		if ( self canSeeEnemyFromExposed() )
		{
			if ( !( self isGrenadePosSafe( throwingAt, throwingAt.origin ) ) )
			{
				/# self setGrenadeMissReason( "Teammates near target" ); #/
				return false;
			}
			return TryGrenadeThrow( throwingAt, undefined, optionalAnimation, armOffset );
		}
		else if ( self canSuppressEnemyFromExposed() )
		{
			return TryGrenadePosProc( throwingAt, self getEnemySightPos(), optionalAnimation, armOffset );
		}
		else
		{
			// hopefully we can get through a grenade hint or something
			if ( !( self isGrenadePosSafe( throwingAt, throwingAt.origin ) ) )
			{
				/# self setGrenadeMissReason( "Teammates near target" ); #/
				return false;
			}
			return TryGrenadeThrow( throwingAt, undefined, optionalAnimation, armOffset );
		}

		/# self setGrenadeMissReason( "Don't know where to throw" ); #/
		return false;// didn't know where to throw!
	}
	else
	{
		return TryGrenadePosProc( throwingAt, throwingAt.origin, optionalAnimation, armOffset );
	}
}

TryGrenadeThrow( throwingAt, destination, optionalAnimation, armOffset, fastThrow, withBounce, throwInThread )
{
	// no AI grenade throws in the first 10 seconds, bad during black screen
	if ( gettime() < 10000 && !isdefined( level._ignoreGrenadeSafeTime ) )
	{
		 /# self setGrenadeMissReason( "First 10 seconds of game" ); #/
		return false;
	}

	if ( !isdefined( withBounce ) )
		withBounce = true;

	//prof_begin( "TryGrenadeThrow" );

	if ( isDefined( optionalAnimation ) )
	{
		throw_anim = optionalAnimation;
		// Assume armOffset and gunHand are defined whenever optionalAnimation is.
		gunHand = self.a.gunHand;	// Actually we don't want gunhand in this case.  We rely on notetracks.
	}
	else
	{
		switch( self.a.special )
		{
		case "cover_crouch":
		case "none":
			if ( self.a.pose == "stand" )
			{
				armOffset = ( 0, 0, 80 );
				throw_anim = %stand_grenade_throw;
			}
			else// if ( self.a.pose == "crouch" )
			{
				armOffset = ( 0, 0, 65 );
				throw_anim = %crouch_grenade_throw;
			}
			gunHand = "left";
			break;
		default:// Do nothing - we don't have an appropriate throw animation.
			throw_anim = undefined;
			gunHand = undefined;
			break;
		}
	}

	// If we don't have an animation, we can't throw the grenade.
	if ( !isDefined( throw_anim ) )
	{
		//prof_end( "TryGrenadeThrow" );
		return( false );
	}

	if ( isdefined( destination ) )// Now try to throw it.
	{
		if ( !isdefined( fastThrow ) )
			throwvel = self checkGrenadeThrowPos( armOffset, destination, withBounce, "min energy", "min time", "max time" );
		else
			throwvel = self checkGrenadeThrowPos( armOffset, destination, withBounce, "min time", "min energy" );
	}
	else
	{
		randomRange = self.randomGrenadeRange;
		// scale down random range as target gets closer to avoid crazy sideways throws
		dist = distance( throwingAt.origin, self.origin );
		if ( dist < 800 )
		{
			if ( dist < 256 )
				randomRange = 0;
			else
				randomRange *= (dist - 256) / (800 - 256);
		}
		
		assert( self.enemy == throwingAt );
		if ( !isdefined( fastThrow ) )
			throwvel = self checkGrenadeThrow( armOffset, randomRange, "min energy", "min time", "max time" );
		else
			throwvel = self checkGrenadeThrow( armOffset, randomRange, "min time", "min energy" );
	}

	// the grenade checks are slow. don't do it too often.
	self.a.nextGrenadeTryTime = gettime() + randomintrange( 1000, 2000 );

	if ( isdefined( throwvel ) )
	{
		if ( !isdefined( self.oldGrenAwareness ) )
			self.oldGrenAwareness = self.grenadeawareness;
		self.grenadeawareness = 0;// so we dont respond to nearby grenades while throwing one

		 /#
		if ( getdebugdvar( "anim_debug" ) == "1" )
			thread animscripts\utility::debugPos( destination, "O" );
		#/

		// remember the time we want to delay any future grenade throws to, to avoid throwing too many.
		// however, for now, only set the timer far enough in the future that it will expire when we throw the grenade.
		// that way, if the throw fails (maybe due to killanimscript), we'll try again soon.
		nextGrenadeTimeToUse = self getDesiredGrenadeTimerValue();
		setGrenadeTimer( self.activeGrenadeTimer, min( gettime() + 3000, nextGrenadeTimeToUse ) );

		secondGrenadeOfDouble = false;
		if ( self usingPlayerGrenadeTimer() )
		{
			assert( throwingAt == self.activeGrenadeTimer.player );
			throwingAt.numGrenadesInProgressTowardsPlayer++ ;
			self thread reduceGIPTPOnKillanimscript( throwingAt );
			if ( throwingAt.numGrenadesInProgressTowardsPlayer > 1 )
				secondGrenadeOfDouble = true;

			if ( self.activeGrenadeTimer.timerName == "fraggrenade" )
			{
				if ( throwingAt.numGrenadesInProgressTowardsPlayer <= 1 )
					throwingAt.lastFragGrenadeToPlayerStart = gettime();
			}
		}

		 /#
		if ( getdvar( "grenade_spam" ) == "on" )
			nextGrenadeTimeToUse = 0;
		#/

		//prof_end( "TryGrenadeThrow" );
		if ( isdefined( throwInThread ) )
			thread DoGrenadeThrow( throw_anim, throwVel, nextGrenadeTimeToUse, secondGrenadeOfDouble, throwingAt );
		else
			DoGrenadeThrow( throw_anim, throwVel, nextGrenadeTimeToUse, secondGrenadeOfDouble, throwingAt );

        return true;
	}
	else
	{
		 /# self setGrenadeMissReason( "Couldn't find trajectory" ); #/
		 /#
		if ( getdebugdvar( "debug_grenademiss" ) == "on" && isdefined( destination ) )
			thread grenadeLine( armoffset, destination );
		#/
	}

	//prof_end( "TryGrenadeThrow" );
	return false;
}

reduceGIPTPOnKillanimscript( throwingAt )
{
	self endon( "dont_reduce_giptp_on_killanimscript" );
	self waittill( "killanimscript" );
	throwingAt.numGrenadesInProgressTowardsPlayer -- ;
}

DoGrenadeThrow( throw_anim, throwVel, nextGrenadeTimeToUse, secondGrenadeOfDouble, throwingAt )
{
	self endon( "killanimscript" );
	 /#
	self thread grenadeDebug( "Starting throw", 3 );
	#/

	//prof_begin( "DoGrenadeThrow" );
	
	if ( self.script == "combat" || self.script == "move" )
		self orientmode( "face direction", throwVel );

	self animscripts\battleChatter_ai::evaluateAttackEvent( self.grenadeWeapon );
	self notify( "stop_aiming_at_enemy" );
	self SetFlaggedAnimKnobAllRestart( "throwanim", throw_anim, %body, fasterAnimSpeed(), 0.1, 1 );

	self thread animscripts\shared::DoNoteTracksForever( "throwanim", "killanimscript" );
	
	//prof_begin( "DoGrenadeThrow" );	

	model = getGrenadeModel();

	attachside = "none";
	for ( ;; )
	{
		self waittill( "throwanim", notetrack );
		//prof_begin( "DoGrenadeThrow" );	
		if ( notetrack == "grenade_left" || notetrack == "grenade_right" )
		{
			attachside = attachGrenadeModel( model, "TAG_INHAND" );
			self.isHoldingGrenade = true;
		}
		if ( notetrack == "grenade_throw" || notetrack == "grenade throw" )
			break;
		assert( notetrack != "end" );// we shouldn't hit "end" until after we've hit "grenade_throw"!
		if ( notetrack == "end" )// failsafe
		{
			self.activeGrenadeTimer.player.numGrenadesInProgressTowardsPlayer -- ;
			self notify( "dont_reduce_giptp_on_killanimscript" );
			//prof_end( "DoGrenadeThrow" );
			return false;
		}
	}

	 /#
	if ( getdebugdvar( "debug_grenadehand" ) == "on" )
	{
		tags = [];
		numTags = self getAttachSize();
		emptySlot = [];
		for ( i = 0;i < numTags;i++ )
		{
			name = self getAttachModelName( i );
			if ( issubstr( name, "weapon" ) )
			{
				tagName = self getAttachTagname( i );
				emptySlot[ tagname ] = 0;
				tags[ tags.size ] = tagName;
			}
		}

		for ( i = 0;i < tags.size;i++ )
		{
			emptySlot[ tags[ i ] ]++ ;
			if ( emptySlot[ tags[ i ] ] < 2 )
				continue;
			iprintlnbold( "Grenade throw needs fixing (check console)" );
			println( "Grenade throw animation ", throw_anim, " has multiple weapons attached to ", tags[ i ] );
			break;
		}
	}
	#/

	 /#
	self thread grenadeDebug( "Threw", 5 );
	#/

	self notify( "dont_reduce_giptp_on_killanimscript" );

	if ( self usingPlayerGrenadeTimer() )
	{
		// give the grenade some time to get to the player.
		// if it gets there, we'll reset the timer so we don't throw any more in a while.
		self thread watchGrenadeTowardsPlayer( self.activeGrenadeTimer.player, nextGrenadeTimeToUse );
	}

	self throwGrenade( throwingAt );

	if ( !self usingPlayerGrenadeTimer() )
	{
		setGrenadeTimer( self.activeGrenadeTimer, nextGrenadeTimeToUse );
	}

	if ( secondGrenadeOfDouble )
	{
		assert( self.activeGrenadeTimer.isPlayerTimer );
		player = self.activeGrenadeTimer.player;
		assert( isPlayer( player ) );
		if ( player.numGrenadesInProgressTowardsPlayer > 1 || gettime() - player.lastGrenadeLandedNearPlayerTime < 2000 )
		{
			// two grenades in progress toward player. give them time to arrive.
			player.grenadeTimers[ "double_grenade" ] = gettime() + min( 5000, player.gs.playerDoubleGrenadeTime );
		}
	}

	self notify( "stop grenade check" );

//		assert (attachSide != "none");
	if ( attachSide != "none" )
		self detach( model, attachside );
	else
	{
		print( "No grenade hand set: " );
		println( throw_anim );
		println( "animation in console does not specify grenade hand" );
	}
	self.isHoldingGrenade = undefined;

	self.grenadeawareness = self.oldGrenAwareness;
	self.oldGrenAwareness = undefined;

	//prof_end( "DoGrenadeThrow" );

	self waittillmatch( "throwanim", "end" );
	self notify( "done_grenade_throw" );
	self notify( "weapon_switch_done" );
	// modern

	// TODO: why is this here? why are we assuming that the calling function wants these particular animnodes turned on?
	self setanim( %exposed_modern, 1, .2 );
	self setanim( %exposed_aiming, 1 );
	self clearanim( throw_anim, .2 );
}

watchGrenadeTowardsPlayer( player, nextGrenadeTimeToUse )
{
	player endon( "death" );

	watchGrenadeTowardsPlayerInternal( nextGrenadeTimeToUse );
	player.numGrenadesInProgressTowardsPlayer -- ;
}

watchGrenadeTowardsPlayerInternal( nextGrenadeTimeToUse )
{
	// give the grenade at least 5 seconds to land
	activeGrenadeTimer = self.activeGrenadeTimer;
	timeoutObj = spawnstruct();
	timeoutObj thread watchGrenadeTowardsPlayerTimeout( 5 );
	timeoutObj endon( "watchGrenadeTowardsPlayerTimeout" );

	type = self.grenadeWeapon;

	grenade = self getGrenadeIThrew();
	if ( !isdefined( grenade ) )
	{
		// the throw failed. maybe we died. =(
		return;
	}

	setGrenadeTimer( activeGrenadeTimer, min( gettime() + 5000, nextGrenadeTimeToUse ) );

	 /#
	grenade thread grenadeDebug( "Incoming", 5 );
	#/

	goodRadiusSqrd = 250 * 250;
	giveUpRadiusSqrd = 400 * 400;
	if ( type == "flash_grenade" )
	{
		goodRadiusSqrd = 900 * 900;
		giveUpRadiusSqrd = 1300 * 1300;
	}

	playersToCheck = level._players;

	// wait for grenade to settle
	prevorigin = grenade.origin;
	while ( 1 )
	{
		wait .1;

		if ( !isdefined( grenade ) )
			break;

		if ( distanceSquared( grenade.origin, prevorigin ) < 400 ) // sqr(20)
		{
			/#
			grenade thread grenadeDebug( "Landed", 5 );
			#/
			// grenade is stationary. check if it's near any players
			newPlayersToCheck = [];
			for ( i = 0; i < playersToCheck.size; i++ )
			{
				player = playersToCheck[ i ];
				distSqrd = distanceSquared( grenade.origin, player.origin );
				if ( distSqrd < goodRadiusSqrd )
				{
					 /#
					grenade thread grenadeDebug( "Landed near player", 5 );
					#/

					player grenadeLandedNearPlayer( activeGrenadeTimer, nextGrenadeTimeToUse );
				}
				else if ( distSqrd < giveUpRadiusSqrd )
				{
					newPlayersToCheck[ newPlayersToCheck.size ] = player;
				}
			}
			playersToCheck = newPlayersToCheck;
			if ( playersToCheck.size == 0 )
				break;
		}
		prevorigin = grenade.origin;
	}
}

grenadeLandedNearPlayer( activeGrenadeTimer, nextGrenadeTimeToUse )
{
	player = self;

	// the grenade landed near the player! =D
	anim.throwGrenadeAtPlayerASAP = undefined;

	if ( gettime() - player.lastGrenadeLandedNearPlayerTime < 3000 )
	{
		// double grenade happened
		player.grenadeTimers[ "double_grenade" ] = gettime() + player.gs.playerDoubleGrenadeTime;
	}

	player.lastGrenadeLandedNearPlayerTime = gettime();

	oldValue = player.grenadeTimers[ activeGrenadeTimer.timerName ];
	player.grenadeTimers[ activeGrenadeTimer.timerName ] = max( nextGrenadeTimeToUse, oldValue );
}

getGrenadeIThrew()
{
	self endon( "killanimscript" );
	self waittill( "grenade_fire", grenade );
	return grenade;
}

watchGrenadeTowardsPlayerTimeout( timerlength )
{
	wait timerlength;
	self notify( "watchGrenadeTowardsPlayerTimeout" );
}


attachGrenadeModel( model, tag )
{
	self attach( model, tag );
	thread detachGrenadeOnScriptChange( model, tag );
	return tag;
}


detachGrenadeOnScriptChange( model, tag )
{
	//self endon ("death"); // don't end on death or it will hover when we die!
	self endon( "stop grenade check" );
	self waittill( "killanimscript" );

	if ( !isdefined( self ) )// we may be dead but still defined. if we're not defined, we were probably deleted.
		return;

	if ( isdefined( self.oldGrenAwareness ) )
	{
		self.grenadeawareness = self.oldGrenAwareness;
		self.oldGrenAwareness = undefined;
	}

	self detach( model, tag );
}

offsetToOrigin( start )
{
	forward = anglestoforward( self.angles );
	right = anglestoright( self.angles );
	up = anglestoup( self.angles );
	forward = vector_multiply( forward, start[ 0 ] );
	right = vector_multiply( right, start[ 1 ] );
	up = vector_multiply( up, start[ 2 ] );
	return( forward + right + up );
}

grenadeLine( start, end )
{
	level notify( "armoffset" );
	level endon( "armoffset" );

	start = self.origin + offsetToOrigin( start );
	for ( ;; )
	{
		line( start, end, ( 1, 0, 1 ) );
		print3d( start, start, ( 0.2, 0.5, 1.0 ), 1, 1 );	// origin, text, RGB, alpha, scale
		print3d( end, end, ( 0.2, 0.5, 1.0 ), 1, 1 );	// origin, text, RGB, alpha, scale
		wait( 0.05 );
	}
}

getGrenadeDropVelocity()
{
	yaw = randomFloat( 360 );
	pitch = randomFloatRange( 30, 75 );

	amntz = sin( pitch );
	cospitch = cos( pitch );

	amntx = cos( yaw ) * cospitch;
	amnty = sin( yaw ) * cospitch;

	speed = randomFloatRange( 100, 200 );

	velocity = ( amntx, amnty, amntz ) * speed;
	return velocity;
}

dropGrenade()
{
	grenadeOrigin = self GetTagOrigin( "tag_inhand" );
	velocity = getGrenadeDropVelocity();
	self MagicGrenadeManual( grenadeOrigin, velocity, 3 );
}

lookForBetterCover()
{
	// don't do cover searches if we don't have an enemy.
	if ( !isdefined( self.enemy ) )
		return false;

	if ( self.fixedNode || self.doingAmbush )
		return false;

	//prof_begin( "lookForBetterCover" );

	node = self getBestCoverNodeIfAvailable();

	if ( isdefined( node ) )
	{
		//prof_end( "lookForBetterCover" );
		return useCoverNodeIfPossible( node );
	}

	//prof_end( "lookForBetterCover" );
	return false;
}

getBestCoverNodeIfAvailable()
{
	//prof_begin( "getBestCoverNodeIfAvailable" );
	node = self FindBestCoverNode();

	if ( !isdefined( node ) )
	{
		//prof_end( "getBestCoverNodeIfAvailable" );
		return undefined;
	}

	currentNode = self GetClaimedNode();
	if ( isdefined( currentNode ) && node == currentNode )
	{
		//prof_end( "getBestCoverNodeIfAvailable" );
		return undefined;
	}

	// work around FindBestCoverNode() resetting my .node in rare cases involving overlapping nodes
	// This prevents us from thinking we've found a new node somewhere when in reality it's the one we're already at, so we won't abort our script.
	if ( isdefined( self.coverNode ) && node == self.coverNode )
	{
		//prof_end( "getBestCoverNodeIfAvailable" );
		return undefined;
	}

	//prof_end( "getBestCoverNodeIfAvailable" );
	return node;
}

useCoverNodeIfPossible( node )
{
	oldKeepNodeInGoal = self.keepClaimedNodeIfValid;
	oldKeepNode = self.keepClaimedNode;
	self.keepClaimedNodeIfValid = false;
	self.keepClaimedNode = false;

	if ( self UseCoverNode( node ) )
	{
		return true;
	}
	else
	{
		 /#self thread DebugFailedCoverUsage( node );#/
	}

	self.keepClaimedNodeIfValid = oldKeepNodeInGoal;
	self.keepClaimedNode = oldKeepNode;

	return false;
}

 /#
DebugFailedCoverUsage( node )
{
	if ( getdvar( "scr_debugfailedcover" ) == "" )
		setdvar( "scr_debugfailedcover", "0" );
	if ( getdebugdvarint( "scr_debugfailedcover" ) == 1 )
	{
		self endon( "death" );
		for ( i = 0; i < 20; i++ )
		{
			line( self.origin, node.origin );
			print3d( node.origin, "failed" );
			wait .05;
		}
	}
}
#/

// this function seems okish,
// but the idea behind FindReacquireNode() is that you call it once,
// and then call GetReacquireNode() many times until it returns undefined.
// if we're just taking the first node (the best), we might as well just be using
// FindBestCoverNode().
/*
tryReacquireNode()
{
	self FindReacquireNode();
	node = self GetReacquireNode();
	if (!isdefined(node))
		return false;
	return (self UseReacquireNode(node));
}
*/

shouldHelpAdvancingTeammate()
{
	// if teammate advanced recently
	if ( level._advanceToEnemyGroup[ self.team ] > 0 && level._advanceToEnemyGroup[ self.team ] < level._advanceToEnemyGroupMax )
	{
		if ( gettime() - level._lastAdvanceToEnemyTime[ self.team ] > 4000 )
			return false;

		leadAttacker = level._lastAdvanceToEnemyAttacker[ self.team ];
		nearLeadAttacker = isdefined( leadAttacker ) && distanceSquared( self.origin, leadAttacker.origin ) < 256 * 256;
			
		if ( ( nearLeadAttacker || distanceSquared( self.origin, level._lastAdvanceToEnemySrc[ self.team ] ) < 256 * 256 ) &&
			 ( !isdefined( self.enemy ) || distanceSquared( self.enemy.origin, level._lastAdvanceToEnemyDest[ self.team ] ) < 512 * 512 ) )
		{
			return true;
		}
	}
	
	return false;
}
		
checkAdvanceOnEnemyConditions()
{
	if ( !isdefined( level._lastAdvanceToEnemyTime[ self.team ] ) )
		return false;
			
	if ( shouldHelpAdvancingTeammate() )
		return true;

	if ( gettime() - level._lastAdvanceToEnemyTime[ self.team ] < level._advanceToEnemyInterval )
		return false;
		

	if ( !isSentient( self.enemy ) )
		return false;

	if ( level._advanceToEnemyGroup[ self.team ] )
		level._advanceToEnemyGroup[ self.team ] = 0;
	
	if ( getAICount( self.team ) < getAICount( self.enemy.team ) )
		return false;
		
	return true;
}

tryRunningToEnemy( ignoreSuppression )
{
	if ( !isdefined( self.enemy ) )
		return false;

	if ( self.fixedNode )
		return false;

	if ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" )
		return false;

	if ( !self isingoal( self.enemy.origin ) )
		return false;

	if ( self isLongRangeAI() )
		return false;

	if ( !checkAdvanceOnEnemyConditions() )
		return false;

	self FindReacquireDirectPath( ignoreSuppression );

	// TrimPathToAttack is supposed to be called multiple times, until it returns false.
	// it trims the path a little more each time, until trimming it more would make the enemy invisible from the end of the path.
	// we're skipping this step and just running until we get within close range of the enemy.
	// maybe later we can periodically check while moving if the enemy is visible, and if so, enter exposed.
	//self TrimPathToAttack();

	if ( self ReacquireMove() )
	{
		self.keepClaimedNodeIfValid = false;
		self.keepClaimedNode = false;

		self.a.magicReloadWhenReachEnemy = true;
		
		if ( level._advanceToEnemyGroup[ self.team ] == 0 )
		{
			level._lastAdvanceToEnemyTime[ self.team ] = gettime();
			level._lastAdvanceToEnemyAttacker[ self.team ] = self;
		}
		
		level._lastAdvanceToEnemySrc[ self.team ] = self.origin;
		level._lastAdvanceToEnemyDest[ self.team ] = self.enemy.origin;
		
		level._advanceToEnemyGroup[ self.team ]++;
		return true;
	}

	return false;
}

delayedBadplace( org )
{
	self endon( "death" );
	wait( 0.5 );
	 /#
		if ( getdebugdvar( "debug_displace" ) == "on" )
			thread badplacer( 5, org, 16 );
	#/

	string = "" + anim.badPlaceInt;
	badplace_cylinder( string, 5, org, 16, 64, self.team );
	anim.badPlaces[ anim.badPlaces.size ] = string;
	if ( anim.badPlaces.size >= 10 )// too many badplaces, delete the oldest one and then remove it from the array
	{
		newArray = [];
		for ( i = 1;i < anim.badPlaces.size;i++ )
			newArray[ newArray.size ] = anim.badPlaces[ i ];
		badplace_delete( anim.badPlaces[ 0 ] );
		anim.badPlaces = newArray;
	}
	anim.badPlaceInt++ ;
	if ( anim.badPlaceInt > 10 )
		anim.badPlaceInt -= 20;
}

valueIsWithin( value, min, max )
{
	if ( value > min && value < max )
		return true;
	return false;
}

getGunYawToShootEntOrPos()
{
	if ( !isdefined( self.shootPos ) )
	{
		assert( !isdefined( self.shootEnt ) );
		return 0;
	}

	yaw = self getMuzzleAngle()[ 1 ] - GetYaw( self.shootPos );
	yaw = AngleClamp180( yaw );
	return yaw;
}

getGunPitchToShootEntOrPos()
{
	if ( !isdefined( self.shootPos ) )
	{
		assert( !isdefined( self.shootEnt ) );
		return 0;
	}

	pitch = self getMuzzleAngle()[ 0 ] - VectorToAngles( self.shootPos - self getMuzzlePos() )[ 0 ];
	pitch = AngleClamp180( pitch );
	return pitch;
}

getPitchToEnemy()
{
	if ( !isdefined( self.enemy ) )
		return 0;

	vectorToEnemy = self.enemy getshootatpos() - self getshootatpos();
	vectorToEnemy = vectornormalize( vectortoenemy );
	pitchDelta = 360 - vectortoangles( vectorToEnemy )[ 0 ];

	return AngleClamp180( pitchDelta );
}

getPitchToSpot( spot )
{
	if ( !isdefined( spot ) )
		return 0;

	vectorToEnemy = spot - self getshootatpos();
	vectorToEnemy = vectornormalize( vectortoenemy );
	pitchDelta = 360 - vectortoangles( vectorToEnemy )[ 0 ];

	return AngleClamp180( pitchDelta );
}

watchReloading()
{
	// this only works on the player.
	self.isreloading = false;
	self.lastReloadStartTime = -1;
	
	while ( 1 )
	{
		self waittill( "reload_start" );
		self.isreloading = true;
		self.lastReloadStartTime = GetTime();

		self waittillreloadfinished();
		self.isreloading = false;
	}
}

waittillReloadFinished()
{
	self thread timedNotify( 4, "reloadtimeout" );
	self endon( "reloadtimeout" );
	self endon( "weapon_taken" );
	
	while ( 1 )
	{
		self waittill( "reload" );

		weap = self getCurrentWeapon();
		if ( weap == "none" )
			break;

		if ( self getCurrentWeaponClipAmmo() >= weaponClipSize( weap ) )
			break;
	}
	self notify( "reloadtimeout" );
}

timedNotify( time, msg )
{
	self endon( msg );
	wait time;
	self notify( msg );
}

maxFlashThrowDistSq = 768 * 768;
minGrenadeThrowDistSq = 200 * 200;
maxGrenadeThrowDistSq = 1250 * 1250;

checkGrenadeThrowDist()
{
	diff = self.enemy.origin - self.origin;
	distSq = lengthSquared( ( diff[ 0 ], diff[ 1 ], 0 ) );

	// Flashbangs are threated separately
	if ( self.grenadeWeapon == "flash_grenade" )
		return (distSq < maxFlashThrowDistSq);

	// All other grenades have a min/max range
	return (distSq >= minGrenadeThrowDistSq) && (distSq <= maxGrenadeThrowDistSq);
}

monitorFlash()
{
	self endon( "death" );
	self endon( "stop_monitoring_flash" );

	while ( 1 )
	{
		// "flashbang" is code notifying that the AI can be flash banged
		// "doFlashBanged" is sent below if the AI should do flash banged behavior
		self waittill( "flashbang", origin, amount_distance, amount_angle, attacker, attackerteam );

		if ( isDefined( self.flashBangImmunity ) && self.flashBangImmunity  )
			continue;

		if ( isdefined( self.script_immunetoflash ) && self.script_immunetoflash != 0 )
			continue;
			
		if ( isdefined( self.onSnowMobile ) )
			continue;

		if ( isdefined( self.team ) && isdefined( attackerteam ) && self.team == attackerteam )
		{
			// AI get a break when their own team flashbangs them.
			amount_distance = 3 * ( amount_distance - .75 );
			if ( amount_distance < 0 )
				continue;

			if ( isdefined( self.teamFlashbangImmunity ) )
				continue;
		}

		// at 200 or less of the full range of 1000 units, get the full effect
		minamountdist = 0.2;
		if ( amount_distance > 1 - minamountdist )
			amount_distance = 1.0;
		else
			amount_distance = amount_distance / ( 1 - minamountdist );

		duration = 4.5 * amount_distance;

		if ( duration < 0.25 )
			continue;

		self.flashingTeam = attackerteam;
		self flashBangStart( duration );
		self notify( "doFlashBanged", origin, attacker );
	}
}

isShotgunAI()
{
	return isShotgun( self.primaryweapon );
}

isSniper()
{
	return isSniperRifle( self.primaryweapon );
}

isLongRangeAI()
{
	return isSniper() || usingRocketLauncher();
}

fasterAnimSpeed()
{
	return 1.5;
}

randomfasterAnimSpeed()
{
	return randomfloatrange( 1, 1.2 );
}

getRandomCoverMode( modes )
{
	if ( modes.size == 0 )
		return undefined;
	if ( modes.size == 1 )
		return modes[0];
		
	// 20% chance of attempting to repeat same corner mode
	if ( isdefined( self.a.prevAttack ) && randomint( 100 ) > 20 )
	{
		foreach ( i, mode in modes )
		{
			if ( mode == self.a.prevAttack )
			{
				if ( i < modes.size - 1 )
					modes[ i ] = modes[ modes.size - 1 ];

				modes[ modes.size - 1 ] = undefined;
				break;
			}
		}
	}
	
	return modes[ randomint( modes.size ) ];
}


player_sees_my_scope()
{
	// player sees the scope glint if the dot is within a certain range
	start = self geteye();
	foreach ( player in level._players )
	{
		if ( !self cansee( player ) )
			continue;
			
		end = player GetEye();
	
		angles = VectorToAngles( start - end );
		forward = AnglesToForward( angles );
		player_angles = player GetPlayerAngles();
		player_forward = AnglesToForward( player_angles );
	
		dot = VectorDot( forward, player_forward );
		if ( dot < 0.805 )
			continue;
			
		if ( cointoss() && dot >= 0.996 )
			continue;
		
		return true;
	}
	return false;
}

have_i_landed()
{
	while ( !level._player IsOnGround() )
	{
		wait 0.05;
	}

	// Landed!
}