#include maps\_utility;
#include animscripts\combat_utility;
#include animscripts\utility;
#include animscripts\shared;
#include common_scripts\utility;

/*
This file contains the overall behavior for all "whack-a-mole" cover nodes.

Callbacks which must be defined:

 All callbacks should return true or false depending on whether they succeeded in doing something.
 If functionality for a callback isn't available, just don't define it.

mainLoopStart()
	optional
reload()
	plays a reload animation in a hidden position
leaveCoverAndShoot()
	does the main attacking; steps out or stands up and fires, goes back to hiding.
	should obey orders from decideWhatAndHowToShoot in shoot_behavior.gsc.
look( maxtime )
	looks for up to maxtime, stopping and returning if enemy becomes visible or if suppressed
fastlook()
	looks quickly
idle()
	idles until the "end_idle" notify.
flinch()
	flinches briefly (1-2 seconds), doesn't need to return true or false.
grenade( throwAt )
	steps out and throws a grenade at the given player / ai
grenadehidden( throwAt )
	throws a grenade at the given player / ai without leaving cover
blindfire()
	blindfires from cover

example:
behaviorCallbacks = spawnstruct();
behaviorCallbacks.reload = ::reload;
...
animscripts\cover_behavior::main( behaviorCallbacks );

*/

#using_animtree( "generic_human" );


MELEE_GRACE_PERIOD_REQUIRED_TIME	= 3000;
MELEE_GRACE_PERIOD_GIVEN_TIME		= 5000;

main( behaviorCallbacks )
{
	self.couldntSeeEnemyPos = self.origin;// ( set couldntSeeEnemyPos to a place the enemy can't be while we're in corner behavior )

	behaviorStartTime = gettime();
	coverTimers = spawnstruct();
	coverTimers.nextAllowedLookTime = behaviorStartTime - 1;
	coverTimers.nextAllowedSuppressTime = behaviorStartTime - 1;

	// we won't look for better cover purely out of boredom until this time
	resetLookForBetterCoverTime();
	resetRespondToDeathTime();

	self.seekOutEnemyTime = gettime();

	self.a.lastEncounterTime = behaviorStartTime;
	self.a.idlingAtCover = false;
	self.a.movement = "stop";
	
	// if we break out of cover mode after this time, we will get a grace period during which we can melee charge the player
	self.meleeCoverChargeMinTime = behaviorStartTime + MELEE_GRACE_PERIOD_REQUIRED_TIME;

	 /#
	if ( getdvar( "scr_coveridle" ) == "1" )
		self.coverNode.script_onlyidle = true;
	#/

	self thread watchSuppression();

	desynched = ( gettime() > 2500 );

	correctAngles = getCorrectCoverAngles();

	for ( ;; )
	{
		if ( shouldHelpAdvancingTeammate() )
		{
			if ( tryRunningToEnemy( true ) )
			{
				wait 0.05;
				continue;
			}
		}
			
		if ( isdefined( behaviorCallbacks.mainLoopStart ) )
		{
			startTime = gettime();
			self thread endIdleAtFrameEnd();

			[[ behaviorCallbacks.mainLoopStart ]]();

			if ( gettime() == startTime )
				self notify( "dont_end_idle" );
		}
		
		if ( isdefined( behaviorCallbacks.moveToNearByCover ) )
		{
			if ( [[ behaviorCallbacks.moveToNearByCover ]]() )
				continue;
		}

		// tagJW<NOTE>: We do not need or want to teleport the actor when locked to an object
		if ( !self isLinked() )
		{
			self safeTeleport( self.covernode.origin, correctAngles );
		}

		if ( !desynched )
		{
			idle( behaviorCallbacks, 0.05 + randomfloat( 1.5 ) );
			desynched = true;
			continue;
		}

		if ( doNonAttackCoverBehavior( behaviorCallbacks ) )
			continue;

		if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level._player ) )
		{
			if ( tryThrowingGrenade( behaviorCallbacks, level._player ) )
				continue;
		}
		
		if ( respondToDeadTeammate() )
			return;

		// determine visibility and suppressability of enemy.
		visibleEnemy = false;
		suppressableEnemy = false;
		if ( isalive( self.enemy ) )
		{
			visibleEnemy = isEnemyVisibleFromExposed();
			suppressableEnemy = canSuppressEnemyFromExposed();
		}

		// decide what to do.
		if ( visibleEnemy )
		{
			if ( self.a.getBoredOfThisNodeTime < gettime() )
			{
				if ( lookForBetterCover() )
					return;
			}
			
			attackVisibleEnemy( behaviorCallbacks );
		}
		else
		{
			if ( isdefined( self.aggressiveMode ) || enemyIsHiding() )
			{
				if ( advanceOnHidingEnemy() )
					return;
			}

			if ( suppressableEnemy )
			{
				attackSuppressableEnemy( behaviorCallbacks, coverTimers );
			}
			else
			{
				if ( attackNothingToDo( behaviorCallbacks, coverTimers ) )
					return;
			}
		}
	}
}

end_script( coverMode )
{
	self.turnToMatchNode = undefined;
	self.a.prevAttack = undefined;

	if ( isDefined( self.meleeCoverChargeMinTime ) && (self.meleeCoverChargeMinTime <= getTime()) )
	{
		// give the AI a chance to charge the player if he forced him out of cover
		self.meleeCoverChargeGraceEndTime = getTime() + MELEE_GRACE_PERIOD_GIVEN_TIME;
		self.meleeCoverChargeMinTime = undefined;
	}
}


getCorrectCoverAngles()
{
	correctAngles = ( self.coverNode.angles[ 0 ], getNodeForwardYaw( self.coverNode ), self.coverNode.angles[ 2 ] );
	return correctAngles;
}


RESPOND_TO_DEATH_RETRY_INTERVAL = 30 * 1000;

respondToDeadTeammate()
{
	if ( self atDangerousNode() && self.a.respondToDeathTime < gettime() )
	{
		if ( lookForBetterCover() )
			return true;
		
		self.a.respondToDeathTime = gettime() + RESPOND_TO_DEATH_RETRY_INTERVAL;
	}

	return false;
}


doNonAttackCoverBehavior( behaviorCallbacks )
{
	 /#
	if ( isDefined( self.coverNode.script_onlyidle ) )
	{
		assert( self.coverNode.script_onlyidle );// true or undefined
		idle( behaviorCallbacks );
		return true;
	}
	#/

	// if we're suppressed, we do other things.
	if ( suppressedBehavior( behaviorCallbacks ) )
	{
		if ( isEnemyVisibleFromExposed() )
			resetSeekOutEnemyTime();
		self.a.lastEncounterTime = gettime();
		return true;
	}

	// reload if we need to; everything in this loop involves shooting.
	if ( coverReload( behaviorCallbacks, 0 ) )
		return true;

	return false;
}

attackVisibleEnemy( behaviorCallbacks )
{
	if ( distanceSquared( self.origin, self.enemy.origin ) > 750 * 750 )
	{
		if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
			return;
	}

	if ( leaveCoverAndShoot( behaviorCallbacks, "normal" ) )
	{
		resetSeekOutEnemyTime();
		self.a.lastEncounterTime = gettime();
	}
	else
	{
		idle( behaviorCallbacks );
	}
}

attackSuppressableEnemy( behaviorCallbacks, coverTimers )
{
	if ( self.doingAmbush )
	{
		if ( leaveCoverAndShoot( behaviorCallbacks, "ambush" ) )
			return;
	}
	else if ( self.provideCoveringFire || gettime() >= coverTimers.nextAllowedSuppressTime )
	{
		preferredActivity = "suppress";
		if ( !self.provideCoveringFire && ( gettime() - self.lastSuppressionTime ) > 5000 && randomint( 3 ) < 2 )
			preferredActivity = "ambush";
		else if ( !self animscripts\shoot_behavior::shouldSuppress() )
			preferredActivity = "ambush";

		if ( leaveCoverAndShoot( behaviorCallbacks, preferredActivity ) )
		{
			coverTimers.nextAllowedSuppressTime = gettime() + randomintrange( 3000, 20000 );
			// if they're there, we've seen them
			if ( isEnemyVisibleFromExposed() )
				self.a.lastEncounterTime = gettime();
			return;
		}
	}
	
	if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
		return;
	
	idle( behaviorCallbacks );
}


attackNothingToDo( behaviorCallbacks, coverTimers )
{
	if ( coverReload( behaviorCallbacks, 0.1 ) )
		return false;

	if ( isdefined( self.enemy ) )
	{
		if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
			return false;
	}

	if ( !self.doingAmbush && gettime() >= coverTimers.nextAllowedLookTime )
	{
		if ( lookForEnemy( behaviorCallbacks ) )
		{
			coverTimers.nextAllowedLookTime = gettime() + randomintrange( 4000, 15000 );

			// if they're there, we've seen them
			return false;
		}
	}

	// we're *really* bored right now
	if ( gettime() > self.a.getBoredOfThisNodeTime )
	{
		if ( cantFindAnythingToDo() )
			return true;
	}

	if ( self.doingAmbush || ( gettime() >= coverTimers.nextAllowedSuppressTime && isdefined( self.enemy ) ) )
	{
		// be ready to ambush them if they happen to show up
		if ( leaveCoverAndShoot( behaviorCallbacks, "ambush" ) )
		{
			if ( isEnemyVisibleFromExposed() )
				resetSeekOutEnemyTime();
			self.a.lastEncounterTime = gettime();
			coverTimers.nextAllowedSuppressTime = gettime() + randomintrange( 6000, 20000 );
			return false;
		}
	}

	idle( behaviorCallbacks );
	return false;
}


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

	// if we couldn't see our enemy last time we stepped out, and they haven't moved, assume we still can't see them.
	if ( distanceSquared( self.enemy.origin, self.couldntSeeEnemyPos ) < 16 * 16 )
		return false;
	else
		return canSeeEnemyFromExposed();
}

suppressedBehavior( behaviorCallbacks )
{
	if ( !isSuppressedWrapper() )
		return false;

	nextAllowedBlindfireTime = gettime();

	justlooked = true;

	//prof_begin( "suppressedBehavior" );

	while ( isSuppressedWrapper() )
	{
		justlooked = false;

		// tagJW<NOTE>: We do not need or want to teleport the actor when locked to an object
		if ( !self isLinked() )
		{
			self safeTeleport( self.coverNode.origin );
		}
		
		tryMovingNodes = true;
		
		// guys that favor blindfire should try to blindfire instead of move a lot more
		if ( isdefined( self.favor_blindfire ) )
			tryMovingNodes = coinToss();
		
		if ( tryMovingNodes && !isdefined( self.locked_combat ) )
		{
			if ( tryToGetOutOfDangerousSituation( behaviorCallbacks ) )
			{
				self notify( "killanimscript" );
				//prof_end( "suppressedBehavior" );
				return true;
			}
		}
		
		// if we're only at a concealment node, and it's not providing cover, we shouldn't try to use the cover to keep us safe!
		if ( self.a.atConcealmentNode && self canSeeEnemy() )
		{
			//prof_end( "suppressedBehavior" );
			return false;
		}
		
		if ( isEnemyVisibleFromExposed() || canSuppressEnemyFromExposed() )
		{
			if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level._player ) )
			{
				if ( tryThrowingGrenade( behaviorCallbacks, level._player ) )
					continue;
			}
			
			if ( coverReload( behaviorCallbacks, 0 ) )
				continue;
			
			if ( self.team != "allies" && gettime() >= nextAllowedBlindfireTime )
			{
				if ( blindfire( behaviorCallbacks ) )
				{
					nextAllowedBlindfireTime = gettime();
					if ( !isdefined( self.favor_blindfire ) )
						nextAllowedBlindfireTime += randomintrange( 3000, 12000 );
					
					continue;
				}
			}
			
			if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
			{
				justlooked = true;
				continue;
			}
		}
		
		if ( coverReload( behaviorCallbacks, 0.1 ) )
			continue;
		
		//prof_end( "suppressedBehavior" );
		idle( behaviorCallbacks );
	}

	if ( !justlooked && randomint( 2 ) == 0 )
		lookfast( behaviorCallbacks );

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

// returns array of integers 0 through n-1, in random order
getPermutation( n )
{
	permutation = [];
	assert( n > 0 );
	if ( n == 1 )
	{
		permutation[ 0 ] = 0;
	}
	else if ( n == 2 )
	{
		permutation[ 0 ] = randomint( 2 );
		permutation[ 1 ] = 1 - permutation[ 0 ];
	}
	else
	{
		for ( i = 0; i < n; i++ )
			permutation[ i ] = i;
		for ( i = 0; i < n; i++ )
		{
			switchIndex = i + randomint( n - i );
			temp = permutation[ switchIndex ];
			permutation[ SwitchIndex ] = permutation[ i ];
			permutation[ i ] = temp;
		}
	}
	return permutation;
}

callOptionalBehaviorCallback( callback, arg, arg2, arg3 )
{
	if ( !isdefined( callback ) )
		return false;

	//prof_begin( "callOptionalBehaviorCallback" );
	self thread endIdleAtFrameEnd();

	starttime = gettime();

	val = undefined;
	if ( isdefined( arg3 ) )
		val = [[ callback ]]( arg, arg2, arg3 );
	else if ( isdefined( arg2 ) )
		val = [[ callback ]]( arg, arg2 );
	else if ( isdefined( arg ) )
		val = [[ callback ]]( arg );
	else
		val = [[ callback ]]();

	 /#
	// if this assert fails, a behaviorCallback callback didn't return true or false.
	assert( isdefined( val ) && ( val == true || val == false ) );

	// behaviorCallbacks must return true if and only if they let time pass.
	// (it is also important that they only let time pass if they did what they were supposed to do,
	//  but that's not so easy to enforce.)
	if ( val )
		assert( gettime() != starttime );
	else
		assert( gettime() == starttime );
	#/

	if ( !val )
		self notify( "dont_end_idle" );

	//prof_end( "callOptionalBehaviorCallback" );

	return val;
}

watchSuppression()
{
	self endon( "killanimscript" );

	// self.lastSuppressionTime is the last time a bullet whizzed by.
	// self.suppressionStart is the last time we were thinking it was safe when a bullet whizzed by.

	self.lastSuppressionTime = gettime() - 100000;
	self.suppressionStart = self.lastSuppressionTime;

	while ( 1 )
	{
		self waittill( "suppression" );

		time = gettime();
		if ( self.lastSuppressionTime < time - 700 )
			self.suppressionStart = time;
		self.lastSuppressionTime = time;
	}
}

coverReload( behaviorCallbacks, threshold )
{
	if ( self.bulletsInClip > weaponClipSize( self.weapon ) * threshold )
		return false;

	self.isreloading = true;

	result = callOptionalBehaviorCallback( behaviorCallbacks.reload );

	self.isreloading = false;

	return result;
}

// initialGoal can be either "normal", "suppress", or "ambush".
leaveCoverAndShoot( behaviorCallbacks, initialGoal )
{
	self thread animscripts\shoot_behavior::decideWhatAndHowToShoot( initialGoal );

	if ( !self.fixedNode && !self.doingAmbush && !isdefined( self.locked_combat ) )
		self thread breakOutOfShootingIfWantToMoveUp();

	val = callOptionalBehaviorCallback( behaviorCallbacks.leaveCoverAndShoot );

	self notify( "stop_deciding_how_to_shoot" );

	return val;
}

lookForEnemy( behaviorCallbacks )
{
	if ( self.a.atConcealmentNode && self canSeeEnemy() )
		return false;

	if ( self.a.lastEncounterTime + 6000 > gettime() )
	{
		return lookfast( behaviorCallbacks );
	}
	else
	{
		// look slow if possible
		result = callOptionalBehaviorCallback( behaviorCallbacks.look, 2 + randomfloat( 2 ) );
		if ( result )
			return true;
		return callOptionalBehaviorCallback( behaviorCallbacks.fastlook );
	}
}

lookfast( behaviorCallbacks )
{
	// look fast if possible
	result = callOptionalBehaviorCallback( behaviorCallbacks.fastlook );
	if ( result )
		return true;
	return callOptionalBehaviorCallback( behaviorCallbacks.look, 0 );
}

idle( behaviorCallbacks, howLong )
{
	self.flinching = false;

	if ( isdefined( behaviorCallbacks.flinch ) )
	{
		// flinch if we just started getting shot at very recently
		if ( !self.a.idlingAtCover && gettime() - self.suppressionStart < 600 )
		{
			if ( [[ behaviorCallbacks.flinch ]]() )
				return true;
		}
		else
		{
			// if bullets aren't already whizzing by, idle for now but flinch if we get incoming fire
			self thread flinchWhenSuppressed( behaviorCallbacks );
		}
	}

	if ( !self.a.idlingAtCover )
	{
		assert( isdefined( behaviorCallbacks.idle ) );// idle must be available!
		self thread idleThread( behaviorCallbacks.idle );// this thread doesn't stop until "end_idle", which must be notified before we start anything else! use endIdleAtFrameEnd() to do this.
		self.a.idlingAtCover = true;
	}

	if ( isdefined( howLong ) )
		self idleWait( howLong );
	else
		self idleWaitABit();

	if ( self.flinching )
		self waittill( "flinch_done" );

	self notify( "stop_waiting_to_flinch" );
}

idleWait( howLong )
{
	self endon( "end_idle" );
	wait howLong;
}

idleWaitAbit()
{
	self endon( "end_idle" );
	wait 0.3 + randomfloat( 0.1 );
	self waittill( "do_slow_things" );
}

idleThread( idlecallback )
{
	self endon( "killanimscript" );
	self [[ idlecallback ]]();
}

flinchWhenSuppressed( behaviorCallbacks )
{
	self endon( "killanimscript" );
	self endon( "stop_waiting_to_flinch" );

	lastSuppressionTime = self.lastSuppressionTime;

	while ( 1 )
	{
		self waittill( "suppression" );

		time = gettime();

		if ( lastSuppressionTime < time - 2000 )
			break;

		lastSuppressionTime = time;
	}

	self.flinching = true;

	self thread endIdleAtFrameEnd();

	assert( isdefined( behaviorCallbacks.flinch ) );
	val = [[ behaviorCallbacks.flinch ]]();

	if ( !val )
		self notify( "dont_end_idle" );

	self.flinching = false;
	self notify( "flinch_done" );
}

endIdleAtFrameEnd()
{
	self endon( "killanimscript" );
	self endon( "dont_end_idle" );
	waittillframeend;
	
	if ( !isdefined( self ) )
		return;

	self notify( "end_idle" );
	self.a.idlingAtCover = false;
}

tryThrowingGrenade( behaviorCallbacks, throwAt )
{
	assert( isdefined( throwAt ) );

	// don't throw backwards
	forward = anglesToForward( self.angles );
	dir = vectorNormalize( throwAt.origin - self.origin );
	if ( vectorDot( forward, dir ) < 0 )
		return false;

	if ( self.doingAmbush && !recentlySawEnemy() )
		return false;

	if ( self isPartiallySuppressedWrapper() )
	{
		return callOptionalBehaviorCallback( behaviorCallbacks.grenadehidden, throwAt );
	}
	else
	{
		return callOptionalBehaviorCallback( behaviorCallbacks.grenade, throwAt );
	}
}

blindfire( behaviorCallbacks )
{
	if ( !canBlindFire() )
		return false;

	return callOptionalBehaviorCallback( behaviorCallbacks.blindfire );
}

// Need this?
breakOutOfShootingIfWantToMoveUp()
{
	self endon( "killanimscript" );
	self endon( "stop_deciding_how_to_shoot" );

	while ( 1 )
	{
		if ( self.fixedNode || self.doingAmbush )
			return;

		wait 0.5 + randomfloat( 0.75 );

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

		if ( enemyIsHiding() )
		{
			if ( advanceOnHidingEnemy() )
				return;
		}

		if ( !self recentlySawEnemy() && !self canSuppressEnemy() )
		{
			if ( gettime() > self.a.getBoredOfThisNodeTime )
			{
				if ( cantFindAnythingToDo() )
					return;
			}
		}
	}
}

enemyIsHiding()
{
	// if this function is called, we already know that our enemy is not visible from exposed.
	// check to see if they're doing anything hiding-like.

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

	if ( self.enemy isFlashed() )
		return true;

	if ( isplayer( self.enemy ) )
	{
		if ( isdefined( self.enemy.health ) && self.enemy.health < self.enemy.maxhealth )
			return true;
	}
	else
	{
		if ( isAI( self.enemy ) && self.enemy isSuppressedWrapper() )
			return true;
	}

	if ( isdefined( self.enemy.isreloading ) && self.enemy.isreloading )
		return true;

	return false;
}

resetRespondToDeathTime()
{
	self.a.respondToDeathTime = 0;
}


resetLookForBetterCoverTime()
{
	currentTime = gettime();
	
	// treat group of shuffle nodes as one node, don't increase getBoredOfThisNodeTime by too much
	if ( isdefined( self.didShuffleMove ) && currentTime > self.a.getBoredOfThisNodeTime )
	{
		self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 2000, 5000 );
	}
	else if ( isdefined( self.enemy ) )
	{
		dist = distance2D( self.origin, self.enemy.origin );
		if ( dist < self.engageMinDist )
			self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 5000, 10000 );
		else if ( dist > self.engageMaxDist && dist < self.goalradius )
			self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 2000, 5000 );
		else
			self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 10000, 15000 );
	}
	else
	{
		self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 5000, 15000 );
	}
}

resetSeekOutEnemyTime()
{
	// we'll be willing to actually run right up to our enemy in order to find them if we haven't seen them by this time.
	// however, we'll try to find better cover before seeking them out
	if ( isdefined( self.aggressiveMode ) )
		self.seekOutEnemyTime = gettime() + randomintrange( 500, 1000 );
	else
		self.seekOutEnemyTime = gettime() + randomintrange( 3000, 5000 );
}

// these next functions are "look for better cover" functions.
// they don't always need to cause the actor to leave the node immediately,
// but if they keep being called over and over they need to become more and more likely to do so,
// as this indicates that new cover is strongly needed.
cantFindAnythingToDo()
{
	return advanceOnHidingEnemy();
}

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

	if ( isdefined( self.aggressiveMode ) && gettime() >= self.seekOutEnemyTime )
	{
		return tryRunningToEnemy( false );
	}
	
	foundBetterCover = false;
	if ( !isdefined( self.enemy ) || !self.enemy isFlashed() )
		foundBetterCover = lookForBetterCover();

	if ( !foundBetterCover && isdefined( self.enemy ) && !self canSeeEnemyFromExposed() )
	{
		if ( gettime() >= self.seekOutEnemyTime )
		{
			return tryRunningToEnemy( false );
		}
	}

	// maybe at this point we could look for someone who's suppressing our enemy,
	// and if someone is, we can say "cover me!" and have them say "i got you covered" or something.

	return foundBetterCover;
}

tryToGetOutOfDangerousSituation( behaviorCallbacks )
{
	if ( isdefined( behaviorCallbacks.moveToNearByCover ) )
	{
		if ( [[ behaviorCallbacks.moveToNearByCover ]]() )
			return true;
	}

	return lookForBetterCover();
}

// TEMP move these into animsets
set_standing_turns()
{
	self.a.array[ "turn_left_45" ] = %exposed_tracking_turn45L;
	self.a.array[ "turn_left_90" ] = %exposed_tracking_turn90L;
	self.a.array[ "turn_left_135" ] = %exposed_tracking_turn135L;
	self.a.array[ "turn_left_180" ] = %exposed_tracking_turn180L;
	self.a.array[ "turn_right_45" ] = %exposed_tracking_turn45R;
	self.a.array[ "turn_right_90" ] = %exposed_tracking_turn90R;
	self.a.array[ "turn_right_135" ] = %exposed_tracking_turn135R;
	self.a.array[ "turn_right_180" ] = %exposed_tracking_turn180R;
}

set_crouching_turns()
{
	self.a.array[ "turn_left_45" ] = %exposed_crouch_turn_90_left;
	self.a.array[ "turn_left_90" ] = %exposed_crouch_turn_90_left;
	self.a.array[ "turn_left_135" ] = %exposed_crouch_turn_180_left;
	self.a.array[ "turn_left_180" ] = %exposed_crouch_turn_180_left;
	self.a.array[ "turn_right_45" ] = %exposed_crouch_turn_90_right;
	self.a.array[ "turn_right_90" ] = %exposed_crouch_turn_90_right;
	self.a.array[ "turn_right_135" ] = %exposed_crouch_turn_180_right;
	self.a.array[ "turn_right_180" ] = %exposed_crouch_turn_180_right;
}


turnToMatchNodeDirection( nodeAngleOffset )
{
	if ( isdefined( self.node ) )
	{
		node = self.node;

		absRelYaw = abs( AngleClamp180( self.angles[1] - ( node.angles[1] + nodeAngleOffset ) ) );

		if ( self.a.pose == "stand" && node getHighestNodeStance() != "stand" )
		{
			if ( absRelYaw > 45 && absRelYaw < 90 )
				self orientmode( "face angle", self.angles[1] );
			else
				self orientmode( "face current" );
			
			rate = 1.5;
			noteTime = getNotetrackTimes( %exposed_stand_2_crouch, "anim_pose = \"crouch\"" )[0];
			noteTime = min( 1, noteTime * 1.1 );
			time = noteTime * getAnimLength( %exposed_stand_2_crouch ) / rate;
			
			self setflaggedanimknoballrestart( "crouchanim", %exposed_stand_2_crouch, %body, 1, .2, rate );
			self animscripts\shared::DoNoteTracksForTime( time, "crouchanim" );
			self clearanim( %body, 0.2 );
		}
		
		self orientmode( "face angle", self.angles[1] );

		relYaw = AngleClamp180( self.angles[1] - ( node.angles[1] + nodeAngleOffset ) );

		if ( abs( relYaw ) > 45 )
		{
			if ( self.a.pose == "stand" )
				set_standing_turns();
			else
				set_crouching_turns();
			
			self.turnThreshold = 45;
			self.turnToMatchNode = true;
			animscripts\combat::TurnToFaceRelativeYaw( relYaw );
			self.turnToMatchNode = undefined;
		}
	}
}

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

	if ( isdefined( self.didShuffleMove ) )
	{
		self.didShuffleMove = undefined;
		return false;
	}

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

	if ( randomint( 3 ) == 0 )
		return false;

	if ( self.fixedNode || self.doingAmbush || self.keepClaimedNode || self.keepClaimedNodeIfValid )
		return false;
		
	if ( distanceSquared( self.origin, self.node.origin ) > 16 * 16 )
		return false;

	node = self findshufflecovernode();

	if ( isdefined( node ) && ( node != self.node ) && self useCoverNode( node ) )
	{
		self.shuffleMove = true;
		self.shuffleNode = node;
		self.didShuffleMove = true;

		// give code a chance use new cover node
		wait 0.5;
		return true;
	}

	return false;
}

