#include animscripts\SetPoseMovement;
#include animscripts\combat_utility;
#include common_scripts\utility;
#using_animtree( "generic_human" );

// Every script calls initAnimTree to ensure a clean, fresh, known animtree state.  
// clearanim should never be called directly, and this should never occur other than
// at the start of an animscript
// This function now also does any initialization for the scripts that needs to happen 
// at the beginning of every main script.
initAnimTree( animscript )
{
    self clearAnim( %body, 0.3 );
	self setAnim( %body, 1, 0 );	// The %body node should always have weight 1.

	if ( animscript != "pain" && animscript != "death" )
		self.a.special = "none";

	self.missedSightChecks = 0;

	self.a.aimweight = 1.0;
	self.a.aimweight_start = 1.0;
	self.a.aimweight_end = 1.0;
	self.a.aimweight_transframes = 0;
	self.a.aimweight_t = 0;

	IsInCombat();

	assertEX( isDefined( animscript ), "Animscript not specified in initAnimTree" );
}

// UpdateAnimPose does housekeeping at the start of every script's main function.  It does stuff like making prone 
// calculations are only being done if the character is actually prone.
UpdateAnimPose()
{
	assertEX( self.a.movement == "stop" || self.a.movement == "walk" || self.a.movement == "run", "UpdateAnimPose " + self.a.pose + " " + self.a.movement );

	if ( isDefined( self.desired_anim_pose ) && self.desired_anim_pose != self.a.pose )
	{
		if ( self.a.pose == "prone" )
			self ExitProneWrapper( 0.5 );

		if ( self.desired_anim_pose == "prone" )
		{
			self SetProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up );
			self EnterProneWrapper( 0.5 );
			self setAnimKnobAll( %prone_aim_5, %body, 1, 0.1, 1 );
		}
	}

	self.desired_anim_pose = undefined;
}


initialize( animscript )
{
	if ( isdefined( self.longDeathStarting ) )
	{
		if ( animscript != "pain" && animscript != "death" )
		{
			// we probably just came out of an animcustom.
			// just die, it's not safe to do anything else
			self kill( self.origin );
		}
		if ( animscript != "pain" )
		{
			self.longDeathStarting = undefined;
			self notify( "kill_long_death" );
		}
	}
	if ( isdefined( self.a.mayOnlyDie ) && animscript != "death" )
	{
		// we probably just came out of an animcustom.
		// just die, it's not safe to do anything else
		self kill( self.origin );
	}

	// scripts can define this to allow cleanup before moving on
	if ( isDefined( self.a.postScriptFunc ) )
	{
		scriptFunc = self.a.postScriptFunc;
		self.a.postScriptFunc = undefined;

		[[ scriptFunc ]]( animscript );
	}

	// TODO: proper handing when animations exist
	if ( animscript != "combat" && animscript != "pain" && animscript != "death" && usingSidearm() )
	{
		self animscripts\combat::switchToLastWeapon( %pistol_stand_switch, true );
		//self animscripts\shared::placeWeaponOn( self.primaryweapon, "right" );
	}

	if ( animscript != "combat" && animscript != "move" && animscript != "pain" )
		self.a.magicReloadWhenReachEnemy = undefined;

	if ( animscript != "death" )
		self.a.nodeath = false;

	if ( isDefined( self.isHoldingGrenade ) && ( animscript == "pain" || animscript == "death" || animscript == "flashed" ) )
	{
		self dropGrenade();
	}
	self.isHoldingGrenade = undefined;

	 /#
	//thread checkGrenadeInHand( animscript );
	#/

	self animscripts\squadmanager::aiUpdateAnimState( animscript );

	self.coverNode = undefined;
	self.suppressed = false;
	self.isReloading = false;
	self.changingCoverPos = false;
	self.a.aimIdleThread = undefined;
	self.a.scriptStartTime = gettime();

	self.a.atConcealmentNode = false;
	if ( isdefined( self.node ) && ( self.node.type == "Conceal Prone" || self.node.type == "Conceal Crouch" || self.node.type == "Conceal Stand" ) )
		self.a.atConcealmentNode = true;

	initAnimTree( animscript );

	UpdateAnimPose();
}

/#
checkGrenadeInHand( animscript )
{
	// ensure no grenade left in hand
	self endon( "killanimscript" );

	// pain and death animscripts don't execute script between notifying killanimscript and starting the next animscript,
	// so the grenade cleanup thread might still be waiting to run.
	if ( animscript == "pain" || animscript == "death" )
	{
		wait .05;
		waittillframeend;
	}

	attachSize = self getattachsize();
	for ( i = 0; i < attachSize; i++ )
	{
		model = toLower( self getAttachModelName( i ) );
		assertex( model != "weapon_m67_grenade", "AI has a grenade in hand after animscript finished. Please call over an animscripter! " + self.origin );
		assertex( model != "weapon_m84_flashbang_grenade", "AI has a grenade in hand after animscript finished. Please call over an animscripter! " + self.origin );
		assertex( model != "weapon_us_smoke_grenade", "AI has a grenade in hand after animscript finished. Please call over an animscripter! " + self.origin );
	}
}
#/

getPreferredWeapon()
{
	if ( isdefined( self.wantshotgun ) && self.wantshotgun )
	{
		if ( isShotgun( self.primaryweapon ) )
			return self.primaryweapon;
		else if ( isShotgun( self.secondaryweapon ) )
			return self.secondaryweapon;
	}
	return self.primaryweapon;
}

badplacer( time, org, radius )
{
	for ( i = 0;i < time * 20;i++ )
	{
		for ( p = 0;p < 10;p++ )
		{
			angles = ( 0, randomint( 360 ), 0 );
			forward = anglestoforward( angles );
			scale = vector_multiply( forward, radius );
			line( org, org + scale, ( 1, 0.3, 0.3 ) );
		}
		wait( 0.05 );
	}
}


printDisplaceInfo()
{
	self endon( "death" );
	self notify( "displaceprint" );
	self endon( "displaceprint" );
	for ( ;; )
	{
		print3d( self.origin + ( 0, 0, 60 ), "displacer", ( 0, 0.4, 0.7 ), 0.85, 0.5 );
		wait( 0.05 );
	}
}


// Returns whether or not the character should be acting like he's under fire or expecting an enemy to appear 
// any second.
IsInCombat()
{
	if ( self.alertLevelInt > 1 )
		return true;
		
	if ( isdefined( self.enemy ) )
	{
		self.a.combatEndTime = gettime() + anim.combatMemoryTimeConst + randomint( anim.combatMemoryTimeRand );
		return true;
	}
	return( self.a.combatEndTime > gettime() );
}


/#
DebugIsInCombat()
{
	return( self.a.combatEndTime > gettime() );
}
#/


GetEnemyEyePos()
{
    if ( isdefined( self.enemy ) )
	{
		self.a.lastEnemyPos = self.enemy getShootAtPos();
		self.a.lastEnemyTime = gettime();
		return self.a.lastEnemyPos;
	}
	else if (
			( isDefined( self.a.lastEnemyTime ) ) &&
			( isDefined( self.a.lastEnemyPos ) ) &&
			( self.a.lastEnemyTime + 3000 < gettime() )
			 )
	{
		return self.a.lastEnemyPos;
	}
	else
	{
		// Return a point in front of you.  Note that the distance to this point is significant, because 
		// this function is used to determine an appropriate attack stance. 16 feet( 196 units ) seems good...
		targetPos = self getShootAtPos();
		targetPos = targetPos + ( 196 * self.lookforward[ 0 ], 196 * self.lookforward[ 1 ], 196 * self.lookforward[ 2 ] );
		return targetPos;
	}
}


GetNodeForwardYaw( node )
{
	if ( !isdefined( self.heat ) )
	{
		if ( node.type == "Cover Left" )
			return node.angles[1] + 90;
		else if ( node.type == "Cover Right" )
			return node.angles[1] - 90;
	}
		
	return node.angles[1];
}

GetNodeYawToOrigin( pos )
{
	if ( isdefined( self.node ) )
		yaw = self.node.angles[ 1 ] - GetYaw( pos );
	else
		yaw = self.angles[ 1 ] - GetYaw( pos );

	yaw = AngleClamp180( yaw );
	return yaw;
}

GetNodeYawToEnemy()
{
	pos = undefined;
	if ( isdefined( self.enemy ) )
	{
		pos = self.enemy.origin;
	}
	else
	{
		if ( isdefined( self.node ) )
			forward = anglestoforward( self.node.angles );
		else
			forward = anglestoforward( self.angles );
		forward = vector_multiply( forward, 150 );
		pos = self.origin + forward;
	}

	if ( isdefined( self.node ) )
		yaw = self.node.angles[ 1 ] - GetYaw( pos );
	else
		yaw = self.angles[ 1 ] - GetYaw( pos );
	yaw = AngleClamp180( yaw );
	return yaw;
}

GetYawToSpot( spot )
{
	yaw = self.angles[ 1 ] - GetYaw( spot );
	yaw = AngleClamp180( yaw );
	return yaw;
}
// warning! returns (my yaw - yaw to enemy) instead of (yaw to enemy - my yaw)
GetYawToEnemy()
{
	pos = undefined;
	if ( isdefined( self.enemy ) )
	{
		pos = self.enemy.origin;
	}
	else
	{
		forward = anglestoforward( self.angles );
		forward = vector_multiply( forward, 150 );
		pos = self.origin + forward;
	}

	yaw = self.angles[ 1 ] - GetYaw( pos );
	yaw = AngleClamp180( yaw );
	return yaw;
}

GetYaw( org )
{
	return VectorToYaw( org - self.origin );
}

GetYaw2d( org )
{
	angles = VectorToAngles( ( org[ 0 ], org[ 1 ], 0 ) - ( self.origin[ 0 ], self.origin[ 1 ], 0 ) );
	return angles[ 1 ];
}

// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
AbsYawToEnemy()
{
	assert( isdefined( self.enemy ) );
	yaw = self.angles[ 1 ] - GetYaw( self.enemy.origin );
	yaw = AngleClamp180( yaw );
	if ( yaw < 0 )
		yaw = -1 * yaw;
	return yaw;
}

// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
AbsYawToEnemy2d()
{
	assert( isdefined( self.enemy ) );
	yaw = self.angles[ 1 ] - GetYaw2d( self.enemy.origin );
	yaw = AngleClamp180( yaw );
	if ( yaw < 0 )
		yaw = -1 * yaw;
	return yaw;
}

// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
AbsYawToOrigin( org )
{
	yaw = self.angles[ 1 ] - GetYaw( org );
	yaw = AngleClamp180( yaw );
	if ( yaw < 0 )
		yaw = -1 * yaw;
	return yaw;
}

AbsYawToAngles( angles )
{
	yaw = self.angles[ 1 ] - angles;
	yaw = AngleClamp180( yaw );
	if ( yaw < 0 )
		yaw = -1 * yaw;
	return yaw;
}

GetYawFromOrigin( org, start )
{
	angles = VectorToAngles( org - start );
	return angles[ 1 ];
}

GetYawToTag( tag, org )
{
	yaw = self gettagangles( tag )[ 1 ] - GetYawFromOrigin( org, self gettagorigin( tag ) );
	yaw = AngleClamp180( yaw );
	return yaw;
}

GetYawToOrigin( org )
{
	yaw = self.angles[ 1 ] - GetYaw( org );
	yaw = AngleClamp180( yaw );
	return yaw;
}

GetEyeYawToOrigin( org )
{
	yaw = self gettagangles( "TAG_EYE" )[ 1 ] - GetYaw( org );
	yaw = AngleClamp180( yaw );
	return yaw;
}

isStanceAllowedWrapper( stance )
{
	if ( isdefined( self.coverNode ) )
		return self.coverNode doesNodeAllowStance( stance );
	return self isStanceAllowed( stance );
}


choosePose( preferredPose )
{
	if ( !isDefined( preferredPose ) )
	{
		preferredPose = self.a.pose;
	}

	// Find out if we should be standing, crouched or prone
	switch( preferredPose )
	{
	case "stand":
		if ( self isStanceAllowedWrapper( "stand" ) )
		{
			resultPose = "stand";
		}
		else if ( self isStanceAllowedWrapper( "crouch" ) )
		{
			resultPose = "crouch";
		}
		else if ( self isStanceAllowedWrapper( "prone" ) )
		{
			resultPose = "prone";
		}
		else
		{
			println( "No stance allowed!  Remaining standing." );
			resultPose = "stand";
		}
		break;

	case "crouch":
		if ( self isStanceAllowedWrapper( "crouch" ) )
		{
			resultPose = "crouch";
		}
		else if ( self isStanceAllowedWrapper( "stand" ) )
		{
			resultPose = "stand";
		}
		else if ( self isStanceAllowedWrapper( "prone" ) )
		{
			resultPose = "prone";
		}
		else
		{
			println( "No stance allowed!  Remaining crouched." );
			resultPose = "crouch";
		}
		break;

	case "prone":
		if ( self isStanceAllowedWrapper( "prone" ) )
		{
			resultPose = "prone";
		}
		else if ( self isStanceAllowedWrapper( "crouch" ) )
		{
			resultPose = "crouch";
		}
		else if ( self isStanceAllowedWrapper( "stand" ) )
		{
			resultPose = "stand";
		}
		else
		{
			println( "No stance allowed!  Remaining prone." );
			resultPose = "prone";
		}
		break;

	default:
		println( "utility::choosePose, called in " + self.script + " script: Unhandled anim_pose " + self.a.pose + " - using stand." );
		resultPose = "stand";
		break;
	}
	return resultPose;
}

GetClaimedNode()
{
	myNode = self.node;
	if ( isdefined( myNode ) && ( self nearNode( myNode ) || ( isdefined( self.coverNode ) && myNode == self.coverNode ) ) )
		return myNode;
	return undefined;
}

GetNodeType()
{
	myNode = GetClaimedNode();
	if ( isDefined( myNode ) )
		return myNode.type;
	return "none";
}

GetNodeDirection()
{
	myNode = GetClaimedNode();
	if ( isdefined( myNode ) )
	{
		// thread [[ anim.println ]]( "GetNodeDirection found node, returned: " + myNode.angles[ 1 ] );#/ 
		return myNode.angles[ 1 ];
	}
	// thread [[ anim.println ]]( "GetNodeDirection didn't find node, returned: " + self.desiredAngle );#/ 
	return self.desiredAngle;
}

GetNodeForward()
{
	myNode = GetClaimedNode();
	if ( isdefined( myNode ) )
		return AnglesToForward( myNode.angles );
	return AnglesToForward( self.angles );
}

GetNodeOrigin()
{
	myNode = GetClaimedNode();
	if ( isdefined( myNode ) )
		return myNode.origin;
	return self.origin;
}

/#
isDebugOn()
{
	return( ( getdebugdvarint( "animDebug" ) == 1 ) || ( isDefined( anim.debugEnt ) && anim.debugEnt == self ) );
}

drawDebugLineInternal( fromPoint, toPoint, color, durationFrames )
{
	// println( "Drawing line, color " + color[ 0 ] + ", " + color[ 1 ] + ", " + color[ 2 ] );
	// player = getentarray( "player", "classname" )[0];
	// println( "Point1 : " + fromPoint + ", Point2: " + toPoint + ", player: " + player.origin );
	for ( i = 0;i < durationFrames;i++ )
	{
		line( fromPoint, toPoint, color );
		wait( 0.05 );
	}
}

drawDebugLine( fromPoint, toPoint, color, durationFrames )
{
	if ( isDebugOn() )
		thread drawDebugLineInternal( fromPoint, toPoint, color, durationFrames );
}

debugLine( fromPoint, toPoint, color, durationFrames )
{
	for ( i = 0;i < durationFrames * 20;i++ )
	{
		line( fromPoint, toPoint, color );
		wait( 0.05 );
	}
}

drawDebugCross( atPoint, radius, color, durationFrames )
{
	atPoint_high = 		atPoint + ( 		0, 			0, 		   radius	 );
	atPoint_low = 		atPoint + ( 		0, 			0, 		 - 1 * radius	 );
	atPoint_left = 		atPoint + ( 		0, 		   radius, 		0		 );
	atPoint_right = 		atPoint + ( 		0, 		 - 1 * radius, 		0		 );
	atPoint_forward = 	atPoint + ( radius, 		0, 			0		 );
	atPoint_back = 		atPoint + ( -1 * radius, 		0, 			0		 );
	thread debugLine( atPoint_high, 	atPoint_low, 	color, durationFrames );
	thread debugLine( atPoint_left, 	atPoint_right, 	color, durationFrames );
	thread debugLine( atPoint_forward, 	atPoint_back, 	color, durationFrames );
}

drawDebugCrossOld( atPoint, radius, color, durationFrames )
{
	if ( thread isDebugOn() )
	{
		atPoint_high = 		atPoint + ( 		0, 			0, 		   radius	 );
		atPoint_low = 		atPoint + ( 		0, 			0, 		 - 1 * radius	 );
		atPoint_left = 		atPoint + ( 		0, 		   radius, 		0		 );
		atPoint_right = 		atPoint + ( 		0, 		 - 1 * radius, 		0		 );
		atPoint_forward = 	atPoint + ( radius, 		0, 			0		 );
		atPoint_back = 		atPoint + ( -1 * radius, 		0, 			0		 );
		thread animscripts\utility::drawDebugLine( atPoint_high, 	atPoint_low, 	color, durationFrames );
		thread animscripts\utility::drawDebugLine( atPoint_left, 	atPoint_right, 	color, durationFrames );
		thread animscripts\utility::drawDebugLine( atPoint_forward, 	atPoint_back, 	color, durationFrames );
	}
}

UpdateDebugInfoInternal()
{
	if ( isDefined( anim.debugEnt ) && ( anim.debugEnt == self ) )
		doInfo = true;
	else
		doInfo = getdebugdvarInt( "animscriptinfo" );

	if ( doInfo )
	{
		thread drawDebugInfoThread();
	}
	else
	{
		self notify( "EndDebugInfo" );
	}
}

UpdateDebugInfo()
{
	self endon( "death" );
    for ( ;; )
    {
		thread UpdateDebugInfoInternal();
		wait 1;
    }
}


drawDebugInfoThread()
{
	self endon( "EndDebugInfo" );
	self endon( "death" );

	for ( ;; )
	{
		self thread drawDebugInfo();
		wait 0.05;
	}
}

drawDebugInfo()
{
	// What do we want to print?
	line[ 0 ]  = self getEntityNumber() + " " + self.script;
	line[ 1 ]  = self.a.pose + " " + self.a.movement;
	if ( self thread DebugIsInCombat() )
		line[ 2 ]  = "in combat for " + ( self.a.combatEndTime - gettime() ) + " ms.";
	else
		line[ 2 ]  = "not in combat";
	line[ 3 ]  = self.a.lastDebugPrint1;

	belowFeet = self.origin + ( 0, 0, -8 );
	// aboveHead = self getShootAtPos() + ( 0, 0, 8 );
	offset = ( 0, 0, -10 );
	for ( i = 0 ; i < line.size ; i++ )
	{
		if ( isDefined( line[ i ] ) )
		{
			textPos = ( belowFeet[ 0 ] + ( offset[ 0 ] * i ), belowFeet[ 1 ] + ( offset[ 1 ] * i ), belowFeet[ 2 ] + ( offset[ 2 ] * i ) );
			print3d( textPos, line[ i ], ( .2, .2, 1 ), 1, 0.75 );	// origin, text, RGB, alpha, scale
		}
	}
}
#/

safemod( a, b )
{
	 /* 
	Here are some modulus results from in - game:
	10 % 3 = 1
	10 % - 3 = 1
	 - 10 % 3 = -1
	 - 10 % - 3 = -1
	however, we never want a negative result.
	 */ 
	result = int( a ) % b;
	result += b;
	return result % b;
}

AbsAngleClamp180( angle )
{
	return abs( AngleClamp180( angle ) );
}

// Returns an array of 4 weights( 2 of which are guaranteed to be 0 ), which should be applied to forward, 
// right, back and left animations to get the angle specified.
//              front
//        / -- -- | -- -- \
//       /       180       \
//      / \       |       / \
//     / - 135    |     135  \
//     |     \    |     /    |
// left| - 90 -- -- + -- -- 90 - |right
//     |     /    |     \    |
//     \  - 45    |     45   / 
//      \ /       |       \ / 
//       \        0        / 
//        \ -- -- | -- -- / 
//               back

QuadrantAnimWeights( yaw )
{
	forwardWeight = cos( yaw );
	leftWeight    = sin( yaw );

	result[ "front" ]	 = 0;
	result[ "right" ]	 = 0;
	result[ "back" ]	 = 0;
	result[ "left" ]	 = 0;

	if ( isdefined( self.alwaysRunForward ) )
	{
		assert( self.alwaysRunForward );// always set alwaysRunForward to either true or undefined.

		result[ "front" ] = 1;
		return result;
	}

	if ( forwardWeight > 0 )
	{
		if ( leftWeight > forwardWeight )
			result[ "left" ] = 1;
		else if ( leftWeight < -1 * forwardWeight )
			result[ "right" ] = 1;
		else
			result[ "front" ] = 1;
	}
	else
	{
		// if moving backwards, don't blend.
		// it looks horrible because the feet cycle in the opposite direction.
		// either way, feet slide, but this looks better.
		backWeight = -1 * forwardWeight;
		if ( leftWeight > backWeight )
			result[ "left" ] = 1;
		else if ( leftWeight < forwardWeight )
			result[ "right" ] = 1;
		else
			result[ "back" ] = 1;
	}


	return result;
}

getQuadrant( angle )
{
	angle = AngleClamp( angle );

	if ( angle < 45 || angle > 315 )
	{
		quadrant = "front";
	}
	else if ( angle < 135 )
	{
		quadrant = "left";
	}
	else if ( angle < 225 )
	{
		quadrant = "back";
	}
	else
	{
		quadrant = "right";
	}
	return quadrant;
}


// Checks to see if the input is equal to any of up to ten other inputs.
IsInSet( input, set )
{
	for ( i = set.size - 1; i >= 0; i -- )
	{
		if ( input == set[ i ] )
			return true;
	}
	return false;
}

playAnim( animation )
{
	if ( isDefined( animation ) )
	{
		// self thread drawString( animation );	// Doesn't work for animations, only strings.
		println( "NOW PLAYING: ", animation );
		self setFlaggedAnimKnobAllRestart( "playAnim", animation, %root, 1, .1, 1 );
		timeToWait = getanimlength( animation );
		timeToWait = ( 3 * timeToWait ) + 1;	// So looping animations play through 3 times.
		self thread NotifyAfterTime( "time is up", "time is up", timeToWait );
		self waittill( "time is up" );
		self notify( "enddrawstring" );
	}
}

NotifyAfterTime( notifyString, killmestring, time )
{
	self endon( "death" );
	self endon( killmestring );
	wait time;
	self notify( notifyString );
}

// Utility function, made for MilestoneAnims(), which displays a string until killed.
drawString( stringtodraw )
{
	self endon( "killanimscript" );
	self endon( "enddrawstring" );
	for ( ;; )
	{
		wait .05;
		print3d( ( self GetDebugEye() ) + ( 0, 0, 8 ), stringtodraw, ( 1, 1, 1 ), 1, 0.2 );
	}
}

drawStringTime( msg, org, color, timer )
{
	maxtime = timer * 20;
	for ( i = 0;i < maxtime;i++ )
	{
		print3d( org, msg, color, 1, 1 );
		wait .05;
	}
}

showLastEnemySightPos( string )
{
	self notify( "got known enemy2" );
	self endon( "got known enemy2" );
	self endon( "death" );

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

	if ( self.enemy.team == "allies" )
		color = ( 0.4, 0.7, 1 );
	else
		color = ( 1, 0.7, 0.4 );

	while ( 1 )
	{
		wait( 0.05 );
		if ( !isdefined( self.lastEnemySightPos ) )
			continue;

		print3d( self.lastEnemySightPos, string, color, 1, 2.15 );	// origin, text, RGB, alpha, scale
	}
}

 /#
printDebugTextProc( string, org, printTime, color )
{
	level notify( "stop debug print " + org );
	level endon( "stop debug print " + org );

	if ( !isdefined( color ) )
		color = ( 0.3, 0.9, 0.6 );

	timer = printTime * 20;
	for ( i = 0;i < timer;i += 1 )
	{
		wait( 0.05 );
		print3d( org, string, color, 1, 1 );	// origin, text, RGB, alpha, scale
	}
}

printDebugText( string, org, printTime, color )
{
	if ( getdebugdvar( "anim_debug" ) != "" )
		level thread printDebugTextProc( string, org, printTime, color );
}
#/

hasEnemySightPos()
{
	if ( isdefined( self.node ) )
		return( canSeeEnemyFromExposed() || canSuppressEnemyFromExposed() );
	else
		return( canSeeEnemy() || canSuppressEnemy() );
}

getEnemySightPos()
{
	return self.goodShootPos;
}


util_ignoreCurrentSightPos()
{
	if ( !hasEnemySightPos() )
		return;

	self.ignoreSightPos = getEnemySightPos();
	self.ignoreOrigin = self.origin;
}


util_evaluateKnownEnemyLocation()
{
	if ( !hasEnemySightPos() )
		return false;

	myGunPos = self getMuzzlePos();
	myEyeOffset = ( self getShootAtPos() - myGunPos );

	if ( ( isdefined( self.ignoreSightPos ) ) && ( isdefined( self.ignoreOrigin ) ) )
	{
		// Ignore the current last sight pos if you've previously invalidated it from this position
		if ( distance( self.origin, self.ignoreOrigin ) < 25 )
			return false;
	}

	self.ignoreSightPos = undefined;

	canSee = self canshoot( getEnemySightPos(), myEyeOffset );
	if ( !canSee )
	{
		self.ignoreSightPos = getEnemySightPos();
		return false;
	}

	return true;

}


debugTimeout()
{
	wait( 5 );
	self notify( "timeout" );
}

debugPosInternal( org, string, size )
{
	self endon( "death" );
	self notify( "stop debug " + org );
	self endon( "stop debug " + org );
	ent = spawnstruct();
	ent thread debugTimeout();
	ent endon( "timeout" );
	if ( self.enemy.team == "allies" )
		color = ( 0.4, 0.7, 1 );
	else
		color = ( 1, 0.7, 0.4 );

	while ( 1 )
	{
		wait( 0.05 );
		print3d( org, string, color, 1, size );	// origin, text, RGB, alpha, scale
	}
}

debugPos( org, string )
{
	thread debugPosInternal( org, string, 2.15 );
}

debugPosSize( org, string, size )
{
	thread debugPosInternal( org, string, size );
}

debugBurstPrint( numShots, maxShots )
{
	burstSize = numShots / maxShots;
	burstSizeStr = undefined;

	if ( numShots == self.bulletsInClip )
		burstSizeStr = "all rounds";
	else
	if ( burstSize < 0.25 )
		burstSizeStr = "small burst";
	else
	if ( burstSize < 0.5 )
		burstSizeStr = "med burst";
	else
		burstSizeStr = "long burst";

	thread animscripts\utility::debugPosSize( self.origin + ( 0, 0, 42 ), burstSizeStr, 1.5 );
	thread animscripts\utility::debugPos( self.origin + ( 0, 0, 60 ), "Suppressing" );
}


printShootProc()
{
	self endon( "death" );
	self notify( "stop shoot " + self.export );
	self endon( "stop shoot " + self.export );

	printTime = 0.25;
	timer = printTime * 20;
	for ( i = 0;i < timer;i += 1 )
	{
		wait( 0.05 );
		print3d( self.origin + ( 0, 0, 70 ), "Shoot", ( 1, 0, 0 ), 1, 1 );	// origin, text, RGB, alpha, scale
	}
}

printShoot()
{
	 /#
	if ( getdebugdvar( "anim_debug" ) == "3" )
		self thread printShootProc();
	#/
}

showDebugProc( fromPoint, toPoint, color, printTime )
{
	self endon( "death" );
// 	self notify( "stop debugline " + self.export );
// 	self endon( "stop debugline " + self.export );

	timer = printTime * 20;
	for ( i = 0;i < timer;i += 1 )
	{
		wait( 0.05 );
		line( fromPoint, toPoint, color );
	}
}

showDebugLine( fromPoint, toPoint, color, printTime )
{
	self thread showDebugProc( fromPoint, toPoint + ( 0, 0, -5 ), color, printTime );
}

shootEnemyWrapper()
{
	[[ anim.shootEnemyWrapper_func ]]();
}

shootEnemyWrapper_normal()
{
	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 notify( "shooting" );
	self shoot();
}

shootEnemyWrapper_shootNotify()
{
	level notify( "an_enemy_shot", self );
	shootEnemyWrapper_normal();
}

shootPosWrapper( shootPos )
{
	endpos = bulletSpread( self getMuzzlePos(), shootPos, 4 );

	self.a.lastShootTime = gettime();

	self notify( "shooting" );
	self shoot( 1, endpos );
}


throwGun()
{
	org = spawn( "script_model", ( 0, 0, 0 ) );
	org setmodel( "temp" );
	org.origin = self getTagOrigin( "tag_weapon_right" ) + ( 50, 50, 0 );
	org.angles = self getTagAngles( "tag_weapon_right" );
	right = anglestoright( org.angles );
	right = vector_multiply( right, 15 );
	forward = anglestoforward( org.angles );
	forward = vector_multiply( forward, 15 );
	org moveGravity( ( 0, 50, 150 ), 100 );

	weaponClass = "weapon_" + self.weapon;
	weapon = spawn( weaponClass, org.origin );
	weapon.angles = self getTagAngles( "tag_weapon_right" );
	weapon linkto( org );

// 	org rotateVelocity( ( 100, 0, 0 ), 12 );
	lastOrigin = org.origin;
	while ( ( isdefined( weapon ) ) && ( isdefined( weapon.origin ) ) )
	{
		start = lastOrigin;
		end = org.origin;
		angles = vectortoangles( end - start );
		forward = anglestoforward( angles );
		forward = vector_multiply( forward, 4 );
		trace = bulletTrace( end, end + forward, true, weapon );
		if ( isalive( trace[ "entity" ] ) && trace[ "entity" ] == self )
		{
			wait( 0.05 );
			continue;
		}

		if ( trace[ "fraction" ] < 1.0 )
			break;

		lastOrigin = org.origin;
		wait( 0.05 );
	}
	 /* 
	if( isdefined( trace[ "entity" ] ) )
	{
		if( isSentient( trace[ "entity" ] ) )
			trace[ "entity" ] DoDamage( 300, weapon.origin );
	}
	 */ 
	if ( ( isdefined( weapon ) ) && ( isdefined( weapon.origin ) ) )
		weapon unlink();
	org delete();
}

setEnv( env )
{
	anim.idleAnimTransition [ "stand" ][ "in" ] = %casual_stand_idle_trans_in;
	// anim.idleAnimTransition [ "stand" ][ "out" ] = %casual_stand_idle_trans_out;

	anim.idleAnimArray		[ "stand" ][ 0 ][ 0 ] = %casual_stand_idle;
	anim.idleAnimArray		[ "stand" ][ 0 ][ 1 ] = %casual_stand_idle_twitch;
	anim.idleAnimArray		[ "stand" ][ 0 ][ 2 ] = %casual_stand_idle_twitchB;
	anim.idleAnimWeights	[ "stand" ][ 0 ][ 0 ] = 2;
	anim.idleAnimWeights	[ "stand" ][ 0 ][ 1 ] = 1;
	anim.idleAnimWeights	[ "stand" ][ 0 ][ 2 ] = 1;

	anim.idleAnimArray		[ "stand" ][ 1 ][ 0 ] = %casual_stand_v2_idle;
	anim.idleAnimArray		[ "stand" ][ 1 ][ 1 ] = %casual_stand_v2_twitch_radio;
	anim.idleAnimArray		[ "stand" ][ 1 ][ 2 ] = %casual_stand_v2_twitch_shift;
	anim.idleAnimArray		[ "stand" ][ 1 ][ 3 ] = %casual_stand_v2_twitch_talk;
	anim.idleAnimWeights	[ "stand" ][ 1 ][ 0 ] = 10;
	anim.idleAnimWeights	[ "stand" ][ 1 ][ 1 ] = 4;
	anim.idleAnimWeights	[ "stand" ][ 1 ][ 2 ] = 7;
	anim.idleAnimWeights	[ "stand" ][ 1 ][ 3 ] = 4;

	anim.idleAnimArray		[ "stand_cqb" ][ 0 ][ 0 ] = %cqb_stand_idle;
	anim.idleAnimArray		[ "stand_cqb" ][ 0 ][ 1 ] = %cqb_stand_twitch;
	anim.idleAnimWeights	[ "stand_cqb" ][ 0 ][ 0 ] = 2;
	anim.idleAnimWeights	[ "stand_cqb" ][ 0 ][ 1 ] = 1;

	anim.idleAnimTransition [ "crouch" ][ "in" ] = %casual_crouch_idle_in;
	// anim.idleAnimTransition [ "crouch" ][ "out" ] = %casual_crouch_idle_out;

	anim.idleAnimArray		[ "crouch" ][ 0 ][ 0 ] = %casual_crouch_idle;
	//anim.idleAnimArray	[ "crouch" ][ 0 ][ 1 ] = %casual_crouch_twitch;	// %casual_crouch_point
	anim.idleAnimWeights	[ "crouch" ][ 0 ][ 0 ] = 6;
	//anim.idleAnimWeights	[ "crouch" ][ 0 ][ 1 ] = 3;
}


/*
=============
///ScriptDocBegin
"Name: PersonalColdBreath()"
"Summary: Makes cold breath effect play on an AI"
"Module: AI"
"CallOn: An AI"
"Example: level.price thread PersonalColdBreath()"
"SPMP: singleplayer"
///ScriptDocEnd
=============
*/
PersonalColdBreath()
{
	tag = "TAG_EYE";
	self endon( "death" );
	self notify( "stop personal effect" );
	self endon( "stop personal effect" );
	while ( isdefined( self ) )
	{
		wait( 0.05 );
		if( !isdefined( self ) )
			break;
		
		//only play cold breath when stopped
		if ( ( isdefined( self.a.movement ) ) && ( self.a.movement == "stop" ) )
		{
			//don't play cold breath if indoors
			if ( ( isdefined( self.isindoor ) ) && ( self.isindoor  == 1 ) )
				continue;
			
			playfxOnTag( level._effect[ "cold_breath" ], self, tag );
			wait( 2.5 + randomfloat( 3 ) );
		}
		else
			wait( 0.5 );
	}
}


/*
=============
///ScriptDocBegin
"Name: PersonalColdBreathStop()"
"Summary: Stops cold breath effect playing on an AI"
"Module: AI"
"CallOn: An AI"
"Example: level.price thread PersonalColdBreathStop()"
"SPMP: singleplayer"
///ScriptDocEnd
=============
*/
PersonalColdBreathStop()
{
	self notify( "stop personal effect" );
}

PersonalColdBreathSpawner()
{
	self endon( "death" );
	self notify( "stop personal effect" );
	self endon( "stop personal effect" );
	for ( ;; )
	{
		self waittill( "spawned", spawn );
		if ( maps\_utility::spawn_failed( spawn ) )
			continue;
		spawn thread PersonalColdBreath();
	}
}

isSuppressedWrapper()
{
	if ( isdefined( self.forceSuppression ) )
		return self.forceSuppression;
	
	if ( self.suppressionMeter <= self.suppressionThreshold )
		return false;
	return self issuppressed();	// takes into account .ignoreSuppression
}

// if not suppressed, sometimes we still want to look cautious, like leaning out of a corner instead of stepping out.
// this determines whether we should do that or not.
isPartiallySuppressedWrapper()
{
	if ( self.suppressionMeter <= self.suppressionThreshold * 0.25 )
		return false;
	return( self issuppressed() );	// takes into account .ignoreSuppression
}

getNodeOffset( node )
{
	if ( isdefined( node.offset ) )
		return node.offset;

	// ( right offset, forward offset, vertical offset )
	// you can get an actor's current eye offset by setting scr_eyeoffset to his entnum.
	// this should be redone whenever animations change significantly.
	cover_left_crouch_offset = 	( -26, .4, 36 );
	cover_left_stand_offset = 	( -32, 7, 63 );
	cover_right_crouch_offset = ( 43.5, 11, 36 );
	cover_right_stand_offset = 	( 36, 8.3, 63 );
	cover_crouch_offset = 		( 3.5, -12.5, 45 );// maybe we could account for the fact that in cover crouch he can stand if he needs to?
	cover_stand_offset = 		( -3.7, -22, 63 );

	cornernode = false;
	nodeOffset = ( 0, 0, 0 );

	right = anglestoright( node.angles );
	forward = anglestoforward( node.angles );

	switch( node.type )
	{
		case "Cover Left":
			if ( node getHighestNodeStance() == "crouch" )
				nodeOffset = calculateNodeOffset( right, forward, cover_left_crouch_offset );
			else
				nodeOffset = calculateNodeOffset( right, forward, cover_left_stand_offset );
		break;

		case "Cover Right":
			if ( node getHighestNodeStance() == "crouch" )
				nodeOffset = calculateNodeOffset( right, forward, cover_right_crouch_offset );
			else
				nodeOffset = calculateNodeOffset( right, forward, cover_right_stand_offset );
		break;

		case "Cover Stand":
		case "Conceal Stand":
		case "Turret":
			nodeOffset = calculateNodeOffset( right, forward, cover_stand_offset );
		break;

		case "Cover Crouch":
		case "Cover Crouch Window":
		case "Conceal Crouch":
			nodeOffset = calculateNodeOffset( right, forward, cover_crouch_offset );
		break;
	}

	node.offset = nodeOffset;
	return node.offset;
}

calculateNodeOffset( right, forward, baseoffset )
{
	return vector_multiply( right, baseoffset[ 0 ] ) + vector_multiply( forward, baseoffset[ 1 ] ) + ( 0, 0, baseoffset[ 2 ] );
}

recentlySawEnemy()
{
	return( isdefined( self.enemy ) && self seeRecently( self.enemy, 5 ) );
}

canSeeEnemy( cacheDuration )
{
	if ( !isdefined( self.enemy ) )
		return false;

	if ( (isDefined( cacheDuration ) && self canSee( self.enemy, cacheDuration )) || self canSee( self.enemy ) )
	{
		if ( !checkPitchVisibility( self geteye(), self.enemy getshootatpos() ) )
			return false;

		self.goodShootPos = GetEnemyEyePos();

		dontGiveUpOnSuppressionYet();

		return true;
	}

	return false;
}

canSeeEnemyFromExposed()
{
	//prof_begin( "canSeeEnemyFromExposed" );

	if ( !isdefined( self.enemy ) )
	{
		self.goodShootPos = undefined;
		//prof_end( "canSeeEnemyFromExposed" );
		return false;
	}

	enemyEye = GetEnemyEyePos();
	if ( !isDefined( self.node ) )
	{
		result = self canSee( self.enemy );
	}
	else
	{
		result = canSeePointFromExposedAtNode( enemyEye, self.node );
	}

	if ( result )
	{
		self.goodShootPos = enemyEye;

		dontGiveUpOnSuppressionYet();
	}
	else
	{
		 /#
		if ( self getentnum() == getdebugdvarint( "anim_dotshow" ) )
			thread persistentDebugLine( self.node.origin + getNodeOffset( self.node ), enemyEye );
		#/
	}

	//prof_end( "canSeeEnemyFromExposed" );
	return result;
}

canSeePointFromExposedAtNode( point, node )
{
	if ( node.type == "Cover Left" || node.type == "Cover Right" )
	{
		if ( !self animscripts\corner::canSeePointFromExposedAtCorner( point, node ) )
			return false;
	}

	nodeOffset = getNodeOffset( node );
	lookFromPoint = node.origin + nodeOffset;

	if ( !checkPitchVisibility( lookFromPoint, point, node ) )
		return false;

	if ( !sightTracePassed( lookFromPoint, point, false, undefined ) )
	{
		if ( node.type == "Cover Crouch" || node.type == "Conceal Crouch" )
		{
			// also consider the ability to stand at crouch nodes
			lookFromPoint = ( 0, 0, 64 ) + node.origin;
			return sightTracePassed( lookFromPoint, point, false, undefined );
		}

		return false;
	}

	return true;
}

// check vertical angle is within our aiming abilities
checkPitchVisibility( fromPoint, toPoint, atNode )
{
	// Compute Aiming Limits + Tolerance
	minPitch = self.downAimLimit - anim.aimPitchDiffTolerance;
	maxPitch = self.upAimLimit + anim.aimPitchDiffTolerance;
	
	pitch = AngleClamp180( vectorToAngles( toPoint - fromPoint )[ 0 ] );
		
	if( pitch > maxPitch )
		return false;
	
	if ( pitch < minPitch )
	{
		if ( isdefined( atNode ) && atNode.type != "Cover Crouch" && atNode.type != "Conceal Crouch" )
			return false;
		if ( pitch < (anim.coverCrouchLeanPitch + minPitch) )
			return false;
	}
	
	return true;
}

dontGiveUpOnSuppressionYet()
{
	// we'll reset the giveUpOnSuppression timer the next time we want to suppress
	self.a.shouldResetGiveUpOnSuppressionTimer = true;
}

updateGiveUpOnSuppressionTimer()
{
	if ( !isdefined( self.a.shouldResetGiveUpOnSuppressionTimer ) )
		self.a.shouldResetGiveUpOnSuppressionTimer = true;

	if ( self.a.shouldResetGiveUpOnSuppressionTimer )
	{
		// after this time, we will decide that our enemy might not be where we thought they were
		// this will cause us to look for better cover
		self.a.giveUpOnSuppressionTime = gettime() + randomintrange( 15000, 30000 );

		self.a.shouldResetGiveUpOnSuppressionTimer = false;
	}
}

showLines( start, end, end2 )
{
	for ( ;; )
	{
		line( start, end, ( 1, 0, 0 ), 1 );
		wait( 0.05 );
		line( start, end2, ( 0, 0, 1 ), 1 );
		wait( 0.05 );
	}
}

aiSuppressAI()
{
	if ( !self canAttackEnemyNode() )
		return false;

	shootPos = undefined;
	if ( isdefined( self.enemy.node ) )
	{
		nodeOffset = getNodeOffset( self.enemy.node );
		shootPos = self.enemy.node.origin + nodeOffset;
	}
	else
		shootPos = self.enemy getShootAtPos();

	// canAttackEnemyNode sometimes returns true even though we can't see the point, because
	// our eye pos is not right at our node's offset
	if ( !self canShoot( shootPos ) )
		return false;
	if ( self.script == "combat" )
	{
		// make sure we can also see the tip of our gun
		if ( !sighttracepassed( self geteye(), self getMuzzlePos(), false, undefined ) )
			return false;
	}

	self.goodShootPos = shootPos;
	return true;
}

canSuppressEnemyFromExposed()
{
	// FromExposed includes checking from the offset of the node the AI is at

	if ( !hasSuppressableEnemy() )
	{
		self.goodShootPos = undefined;
		return false;
	}

	if ( !isplayer( self.enemy ) )
		return aiSuppressAI();

	if ( isdefined( self.node ) )
	{
		if ( self.node.type == "Cover Left" || self.node.type == "Cover Right" )
		{
			// Don't try to shoot at stuff behind the node
			if ( !self animscripts\corner::canSeePointFromExposedAtCorner( self GetEnemyEyePos(), self.node ) )
				return false;
		}

		nodeOffset = getNodeOffset( self.node );
		startOffset = self.node.origin + nodeOffset;
	}
	else
		startOffset = self getMuzzlePos();

	if ( !checkPitchVisibility( startOffset, self.lastEnemySightPos ) )
		return false;

	return findGoodSuppressSpot( startOffset );
}

canSuppressEnemy()
{
	if ( !hasSuppressableEnemy() )
	{
		self.goodShootPos = undefined;
		return false;
	}

	if ( !isplayer( self.enemy ) )
		return aiSuppressAI();

	startOffset = self getMuzzlePos();

	if ( !checkPitchVisibility( startOffset, self.lastEnemySightPos ) )
		return false;

	return findGoodSuppressSpot( startOffset );
}

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

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

	updateGiveUpOnSuppressionTimer();
	if ( gettime() > self.a.giveUpOnSuppressionTime )
		return false;

	if ( !needRecalculateSuppressSpot() )
		return isdefined( self.goodShootPos );

	return true;
}

canSeeAndShootPoint( point )
{
	if ( !sightTracePassed( self getShootAtPos(), point, false, undefined ) )
		return false;

	if ( self.a.weaponPos[ "right" ] == "none" )
		return false;

	gunpoint = self getMuzzlePos();

	return sightTracePassed( gunpoint, point, false, undefined );
}

needRecalculateSuppressSpot()
{
	if ( isdefined( self.goodShootPos ) && !self canSeeAndShootPoint( self.goodShootPos ) )
		return true;

	// we need to recalculate the suppress spot
	// if we've moved or if we saw our enemy in a different place than when we
	// last calculated it
	return(
		!isdefined( self.lastEnemySightPosOld ) ||
		self.lastEnemySightPosOld != self.lastEnemySightPos ||
		distanceSquared( self.lastEnemySightPosSelfOrigin, self.origin ) > 1024// 1024 = 32 * 32
	 );
}

findGoodSuppressSpot( startOffset )
{
	if ( !needRecalculateSuppressSpot() )
		return isdefined( self.goodShootPos );

	if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) > squared( self.enemy.maxVisibleDist ) )
	{
		self.goodShootPos = undefined;
		return false;	
	}

	// make sure we can see from our eye to our gun; if we can't then we really shouldn't be trying to suppress at all!
	if ( !sightTracePassed( self getShootAtPos(), startOffset, false, undefined ) )
	{
		self.goodShootPos = undefined;
		return false;
	}


	self.lastEnemySightPosSelfOrigin = self.origin;
	self.lastEnemySightPosOld = self.lastEnemySightPos;


	currentEnemyPos = GetEnemyEyePos();

	trace = bullettrace( self.lastEnemySightPos, currentEnemyPos, false, undefined );
	startTracesAt = trace[ "position" ];

	percievedMovementVector = self.lastEnemySightPos - startTracesAt;
	lookVector = vectorNormalize( self.lastEnemySightPos - startOffset );
	percievedMovementVector = percievedMovementVector - vector_multiply( lookVector, vectorDot( percievedMovementVector, lookVector ) );
	// percievedMovementVector is what self.lastEnemySightPos - startTracesAt looks like from our position( that is, projected perpendicular to the direction we're looking ).

	idealTraceInterval = 20.0;
	numTraces = int( length( percievedMovementVector ) / idealTraceInterval + 0.5 );// one trace every 20 units, ideally
	if ( numTraces < 1 )
		numTraces = 1;
	if ( numTraces > 20 )
		numTraces = 20;// cap it
	vectorDif = self.lastEnemySightPos - startTracesAt;
	vectorDif = ( vectorDif[ 0 ] / numTraces, vectorDif[ 1 ] / numTraces, vectorDif[ 2 ] / numTraces );
	numTraces++ ;// to get both start and end points for traces

	traceTo = startTracesAt;
	 /#
	if ( getdebugdvarint( "debug_dotshow" ) == self getentnum() )
	{
		thread print3dtime( 15, self.lastEnemySightPos, "lastpos", ( 1, .2, .2 ), 1, 0.75 );	// origin, text, RGB, alpha, scale
		thread print3dtime( 15, startTracesAt, "currentpos", ( 1, .2, .2 ), 1, 0.75 );	// origin, text, RGB, alpha, scale
	}
	#/

	self.goodShootPos = undefined;

	goodTraces = 0;
	neededGoodTraces = 2;// we stop at 3 good traces away from the cover where they disappeared, should be about 40 units
	for ( i = 0; i < numTraces + neededGoodTraces; i++ )
	{
		tracePassed = sightTracePassed( startOffset, traceTo, false, undefined );
		thisTraceTo = traceTo;

		 /#
		if ( getdebugdvarint( "debug_dotshow" ) == self getentnum() )
		{
			if ( tracePassed )
				color = ( .2, .2, 1 );
			else
				color = ( .2, .2, .2 );
			// showDebugLine( startOffset, traceTo, color, 0.75 );
			thread print3dtime( 15, traceTo, ".", color, 1, 0.75 );	// origin, text, RGB, alpha, scale
		}
		#/

		// after we've hit self.lastEnemySightPos, look only perpendicular to our line of sight
		if ( i == numTraces - 1 )
		{
			vectorDif = vectorDif - vector_multiply( lookVector, vectorDot( vectorDif, lookVector ) );
		}

		traceTo += vectorDif;// for next time

		if ( tracePassed )
		{
			goodTraces++ ;

			self.goodShootPos = thisTraceTo;

			// if first trace succeeded, we take it, because it probably means they're crouched under cover and we can shoot over it
			if ( i > 0 && goodTraces < neededGoodTraces && i < numTraces + neededGoodTraces - 1 )
				continue;

			return true;
		}
		else
		{
			goodTraces = 0;
		}
	}

	return isdefined( self.goodShootPos );
}

// Returns an animation from an array of animations with a corrosponding array of weights.
anim_array( animArray, animWeights )
{
	total_anims = animArray.size;
	idleanim = randomint( total_anims );
	assert( total_anims );
	assert( animArray.size == animWeights.size );
	if ( total_anims == 1 )
		return animArray[ 0 ];

	weights = 0;
	total_weight = 0;

	for ( i = 0;i < total_anims;i++ )
		total_weight += animWeights[ i ];

	anim_play = randomfloat( total_weight );
	current_weight	 = 0;

	for ( i = 0;i < total_anims;i++ )
	{
		current_weight += animWeights[ i ];
		if ( anim_play >= current_weight )
			continue;

		idleanim = i;
		break;
	}

	return animArray[ idleanim ];
}

print3dtime( timer, org, msg, color, alpha, scale )
{
	newtime = timer / 0.05;
	for ( i = 0;i < newtime;i++ )
	{
		print3d( org, msg, color, alpha, scale );
		wait( 0.05 );
	}
}

print3drise( org, msg, color, alpha, scale )
{
	newtime = 5 / 0.05;
	up = 0;
	org = org + randomvector( 30 );

	for ( i = 0;i < newtime;i++ )
	{
		up += 0.5;
		print3d( org + ( 0, 0, up ), msg, color, alpha, scale );
		wait( 0.05 );
	}
}

crossproduct( vec1, vec2 )
{
	return( vec1[ 0 ] * vec2[ 1 ] - vec1[ 1 ] * vec2[ 0 ] > 0 );
}

getGrenadeModel()
{
	return getWeaponModel( self.grenadeweapon );
}

sawEnemyMove( timer )
{
	if ( !isdefined( timer ) )
		timer = 500;
	return( gettime() - self.personalSightTime < timer );
}

canThrowGrenade()
{
	if ( !self.grenadeAmmo )
		return false;

	if ( self.script_forceGrenade )
		return true;

	return( isplayer( self.enemy ) );
}

usingBoltActionWeapon()
{
	return( weaponIsBoltAction( self.weapon ) );
}

random_weight( array )
{
	idleanim = randomint( array.size );
	if ( array.size > 1 )
	{
		anim_weight = 0;
		for ( i = 0;i < array.size;i++ )
			anim_weight += array[ i ];

		anim_play = randomfloat( anim_weight );

		anim_weight = 0;
		for ( i = 0;i < array.size;i++ )
		{
			anim_weight += array[ i ];
			if ( anim_play < anim_weight )
			{
				idleanim = i;
				break;
			}
		}
	}

	return idleanim;
}

setFootstepEffect( name, fx )
{
	assertEx( isdefined( name ), "Need to define the footstep surface type." );
	assertEx( isdefined( fx ), "Need to define the footstep effect." );
	if ( !isdefined( anim.optionalStepEffects ) )
		anim.optionalStepEffects = [];
	anim.optionalStepEffects[ anim.optionalStepEffects.size ] = name;
	level._effect[ "step_" + name ] = fx;
}

setFootstepEffectSmall( name, fx )
{
	assertEx( isdefined( name ), "Need to define the footstep surface type." );
	assertEx( isdefined( fx ), "Need to define the mud footstep effect." );
	if ( !isdefined( anim.optionalStepEffectsSmall ) )
		anim.optionalStepEffectsSmall = [];
	anim.optionalStepEffectsSmall[ anim.optionalStepEffectsSmall.size ] = name;
	level._effect[ "step_small_" + name ] = fx;
}

setNotetrackEffect( notetrack, tag, surfacename, fx, sound_prefix, sound_suffix )
{
	assert( isdefined( notetrack ) );
	assert( isdefined( tag ) );
	assert( isdefined( fx ) );
	assertEx( isstring( notetrack ), "Notetrack name must be a string" );
	
	if ( !isdefined( surfacename ) )
		surfacename = "all";
	
	if ( !isdefined( level._notetrackFX ) )
		level._notetrackFX = [];
	
	level._notetrackFX[ notetrack ][ surfacename ] = spawnStruct();
	level._notetrackFX[ notetrack ][ surfacename ].tag = tag;
	level._notetrackFX[ notetrack ][ surfacename ].fx = fx;
	if ( isdefined( sound_prefix ) )
		level._notetrackFX[ notetrack ][ surfacename ].sound_prefix = sound_prefix;
	if ( isdefined( sound_suffix ) )
		level._notetrackFX[ notetrack ][ surfacename ].sound_suffix = sound_suffix;
}

persistentDebugLine( start, end )
{
	self endon( "death" );
	level notify( "newdebugline" );
	level endon( "newdebugline" );

	for ( ;; )
	{
		line( start, end, ( 0.3, 1, 0 ), 1 );
		wait( 0.05 );
	}
}


EnterProneWrapper( timer )
{
	thread enterProneWrapperProc( timer );
}

enterProneWrapperProc( timer )
{
	self endon( "death" );
	self notify( "anim_prone_change" );
	self endon( "anim_prone_change" );
	// wrapper so we can put a breakpoint on it
	self EnterProne( timer, isDefined( self.a.onback ) );
	self waittill( "killanimscript" );

	// in case we dont actually make it into prone by the time another script comes in
	if ( self.a.pose != "prone" && !isdefined( self.a.onback ) )
		self.a.pose = "prone";
}

ExitProneWrapper( timer )
{
	thread ExitProneWrapperProc( timer );
}

ExitProneWrapperProc( timer )
{
	self endon( "death" );
	self notify( "anim_prone_change" );
	self endon( "anim_prone_change" );
	// wrapper so we can put a breakpoint on it
	self ExitProne( timer );
	self waittill( "killanimscript" );

	// in case we dont actually leave prone, change it out of prone
	if ( self.a.pose == "prone" )
		self.a.pose = "crouch";
}

canBlindfire()
{
	if ( self.a.atConcealmentNode )
		return false;

	if ( !animscripts\weaponList::usingAutomaticWeapon() )
		return false;

	if ( weaponClass( self.weapon ) == "mg" )
		return false;

	if ( isdefined( self.disable_blindfire ) && self.disable_blindfire == true )
		return false;

	return true;
}

canHitSuppressSpot()
{
	if ( !hasEnemySightPos() )
		return false;
	myGunPos = self getMuzzlePos();
	return( sightTracePassed( myGunPos, getEnemySightPos(), false, undefined ) );
}


moveAnim( animname ) /* string */ 
{
	assert( isdefined( self.a.moveAnimSet ) );
	return self.a.moveAnimSet[ animname ];
}

randomAnimOfTwo( anim1, anim2 )
{
	if ( randomint( 2 ) )
		return anim1;
	else
		return anim2;
}

animArray( animname ) /* string */ 
{
	// println( "playing anim: ", animname );

	assert( isdefined( self.a.array ) );
	/#
	if ( !isdefined( self.a.array[ animname ] ) )
	{
		dumpAnimArray();
		assertex( isdefined( self.a.array[ animname ] ), "self.a.array[ \"" + animname + "\" ] is undefined" );
	}
	#/

	return self.a.array[ animname ];
}

animArrayAnyExist( animname )
{
	assert( isdefined( self.a.array ) );
	 /#
	if ( !isdefined( self.a.array[ animname ] ) )
	{
		dumpAnimArray();
		assertex( isdefined( self.a.array[ animname ] ), "self.a.array[ \"" + animname + "\" ] is undefined" );
	}
	#/

	return self.a.array[ animname ].size > 0;
}

animArrayPickRandom( animname )
{
	assert( isdefined( self.a.array ) );
	 /#
	if ( !isdefined( self.a.array[ animname ] ) )
	{
		dumpAnimArray();
		assertex( isdefined( self.a.array[ animname ] ), "self.a.array[ \"" + animname + "\" ] is undefined" );
	}
	#/
	assert( self.a.array[ animname ].size > 0 );

	index = randomint( self.a.array[ animname ].size );

	return self.a.array[ animname ][ index ];
}

 /#
dumpAnimArray()
{
	println( "self.a.array:" );
	keys = getArrayKeys( self.a.array );
	for ( i = 0; i < keys.size; i++ )
	{
		if ( isarray( self.a.array[ keys[ i ] ] ) )
			println( " array[ \"" + keys[ i ] + "\" ] = {array of size " + self.a.array[ keys[ i ] ].size + "}" );
		else
			println( " array[ \"" + keys[ i ] + "\" ] = ", self.a.array[ keys[ i ] ] );
	}
}
#/

array( a, b, c, d, e, f, g, h, i, j, k, l, m, n )
{
	array = [];
	if ( isdefined( a ) ) array[ 0 ] = a; else return array;
	if ( isdefined( b ) ) array[ 1 ] = b; else return array;
	if ( isdefined( c ) ) array[ 2 ] = c; else return array;
	if ( isdefined( d ) ) array[ 3 ] = d; else return array;
	if ( isdefined( e ) ) array[ 4 ] = e; else return array;
	if ( isdefined( f ) ) array[ 5 ] = f; else return array;
	if ( isdefined( g ) ) array[ 6 ] = g; else return array;
	if ( isdefined( h ) ) array[ 7 ] = h; else return array;
	if ( isdefined( i ) ) array[ 8 ] = i; else return array;
	if ( isdefined( j ) ) array[ 9 ] = j; else return array;
	if ( isdefined( k ) ) array[ 10 ] = k; else return array;
	if ( isdefined( l ) ) array[ 11 ] = l; else return array;
	if ( isdefined( m ) ) array[ 12 ] = m; else return array;
	if ( isdefined( n ) ) array[ 13 ] = n;
	return array;
}


getAIPrimaryWeapon()
{
	return self.primaryweapon;
}

getAISecondaryWeapon()
{
	return self.secondaryweapon;
}

getAISidearmWeapon()
{
	return self.sidearm;
}

getAICurrentWeapon()
{
	return self.weapon;
}

usingPrimary()
{
	return( self.weapon == self.primaryweapon && self.weapon != "none" );
}

usingSecondary()
{
	return( self.weapon == self.secondaryweapon && self.weapon != "none" );
}

usingSidearm()
{
	return( self.weapon == self.sidearm && self.weapon != "none" );
}

getAICurrentWeaponSlot()
{
	if ( self.weapon == self.primaryweapon )
		return "primary";
	else if ( self.weapon == self.secondaryweapon )
		return "secondary";
	else if ( self.weapon == self.sidearm )
		return "sidearm";
	else
		assertMsg( "self.weapon does not match any known slot" );
}

AIHasWeapon( weapon )
{
	if ( isDefined( self.weaponInfo[ weapon ] ) )
		return true;

	return false;
}

getAnimEndPos( theanim )
{
	moveDelta = getMoveDelta( theanim, 0, 1 );
	return self localToWorldCoords( moveDelta );
}


damageLocationIsAny( a, b, c, d, e, f, g, h, i, j, k, ovr )
{
	 /* possibile self.damageLocation's:
		"torso_upper"
		"torso_lower"
		"helmet"
		"head"
		"neck"
		"left_arm_upper"
		"left_arm_lower"
		"left_hand"
		"right_arm_upper"
		"right_arm_lower"
		"right_hand"
		"gun"
		"none"
		"left_leg_upper"
		"left_leg_lower"
		"left_foot"
		"right_leg_upper"
		"right_leg_lower"
		"right_foot"
	 */ 

	if ( !isdefined( a ) ) return false; if ( self.damageLocation == a ) return true;
	if ( !isdefined( b ) ) return false; if ( self.damageLocation == b ) return true;
	if ( !isdefined( c ) ) return false; if ( self.damageLocation == c ) return true;
	if ( !isdefined( d ) ) return false; if ( self.damageLocation == d ) return true;
	if ( !isdefined( e ) ) return false; if ( self.damageLocation == e ) return true;
	if ( !isdefined( f ) ) return false; if ( self.damageLocation == f ) return true;
	if ( !isdefined( g ) ) return false; if ( self.damageLocation == g ) return true;
	if ( !isdefined( h ) ) return false; if ( self.damageLocation == h ) return true;
	if ( !isdefined( i ) ) return false; if ( self.damageLocation == i ) return true;
	if ( !isdefined( j ) ) return false; if ( self.damageLocation == j ) return true;
	if ( !isdefined( k ) ) return false; if ( self.damageLocation == k ) return true;
	assert( !isdefined( ovr ) );
	return false;
}


usingPistol()
{
	return weaponClass( self.weapon ) == "pistol";
}

usingRocketLauncher()
{
	return weaponClass( self.weapon ) == "rocketlauncher";
}

usingMG()
{
	return weaponClass( self.weapon ) == "mg";
}

usingShotGun()
{
	return weaponclass( self.weapon ) == "spread";
}

usingRifleLikeWeapon()
{
	class = weaponClass( self.weapon );
	
	switch( class )
	{
	case "mg":
	case "smg":
	case "sniper":
	case "rifle":
	case "spread":
		return true;
	}
	
	return false;
}

ragdollDeath( moveAnim )
{
	self endon( "killanimscript" );

	lastOrg = self.origin;
	moveVec = ( 0, 0, 0 );
	for ( ;; )
	{
		wait( 0.05 );
		force = distance( self.origin, lastOrg );
		lastOrg = self.origin;

		if ( self.health == 1 )
		{
			self.a.nodeath = true;
			self startRagdoll();
			self clearAnim( moveAnim, 0.1 );
			wait( 0.05 );
			physicsExplosionSphere( lastOrg, 600, 0, force * 0.1 );
			self notify( "killanimscript" );
			return;
		}

	}
}

shouldCQB()
{
	return isdefined( self.cqbwalking ) && !isdefined( self.grenade );
}

isCQBWalking()
{
	return isdefined( self.cqbwalking );
}

isCQBWalkingOrFacingEnemy()
{
	return !self.faceMotion || isdefined( self.cqbwalking );
}

randomizeIdleSet()
{
	self.a.idleSet = randomint( 2 );
}

isShotgun( weapon )
{
	return weaponclass( weapon ) == "spread";
}

isSniperRifle( weapon )
{
	return weaponclass( weapon ) == "sniper";
}

weapon_pump_action_shotgun()
{
	return self.weapon != "none" && weaponIsBoltAction( self.weapon ) && weaponclass( self.weapon ) == "spread";
}


// meant to be used with any integer seed, for a small integer maximum (ideally one that divides anim.randomIntTableSize)
getRandomIntFromSeed( intSeed, intMax )
{
	assert( intMax > 0 );

	index = intSeed % anim.randomIntTableSize;
	return anim.randomIntTable[ index ] % intMax;
}


getCurrentWeaponSlotName()
{
	assert( isDefined( self ) );
	
	if ( self usingSecondary() )
		return "secondary";
		
	if ( self usingSidearm() )
		return "sidearm";

	// primary and unknowns/none return this slot by default
	return "primary";
}