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

#using_animtree( "generic_human" );

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

	[[ self.exception[ "move" ] ]]();

    moveInit();
    getUpIfProne();
    animscripts\utility::initialize( "move" );

    wasInCover = self wasPreviouslyInCover();

	if ( wasInCover && isdefined( self.shuffleMove ) )
	{
		moveCoverToCover();
		moveCoverToCoverFinish();
	}
	else if ( IsDefined( self.battleChatter ) && self.battleChatter )
	{
		self moveStartBattleChatter( wasInCover );
		self animscripts\battlechatter::playBattleChatter();
	}

	self thread stairsCheck();
	self thread pathChangeCheck();
	self thread animDodgeObstacle();

	self animscripts\cover_arrival::startMoveTransition();
	self.doingReacquireStep = undefined;
	self.ignorePathChange = undefined;

	self thread startThreadsToRunWhileMoving();

	self thread animscripts\cover_arrival::setupApproachNode( true );

	self.shoot_while_moving_thread = undefined;
	self.aim_while_moving_thread = undefined;

/#
	assert( !isdefined( self.trackLoopThread ) );
	self.trackLoopThread = undefined;
#/

	self.runNGun = undefined;
	
	MoveMainLoop( true );
}


// called by code on ending this script
end_script()
{
	if ( isdefined( self.oldGrenadeWeapon ) )
	{
		self.grenadeWeapon = self.oldGrenadeWeapon;
		self.oldGrenadeWeapon = undefined;
	}

	self.teamFlashbangImmunity = undefined;
	self.minInDoorTime = undefined;
	self.ignorePathChange = undefined;
	self.shuffleMove = undefined;
	self.shuffleNode = undefined;
	self.runNGun = undefined;
	self.reactingToBullet = undefined;
	self.requestReactToBullet = undefined;

	self.currentDodgeAnim = undefined;
	self.moveLoopOverrideFunc = undefined;	
}


moveInit()
{
	self.reactingToBullet = undefined;
	self.requestReactToBullet = undefined;
	self.update_move_anim_type = undefined;
	self.update_move_front_bias = undefined;
	self.runNGunWeight = 0;
	self.arrivalStartDist = undefined;
}

getUpIfProne()
{
	if ( self.a.pose == "prone" )
	{
		newPose = self animscripts\utility::choosePose( "stand" );

		if ( newPose != "prone" )
		{
			self orientMode( "face current" );
			self animMode( "zonly_physics", false );
			rate = 1;
			if ( isdefined( self.grenade ) )
				rate = 2;
			self animscripts\cover_prone::proneTo( newPose, rate );
			self animMode( "none", false );
			self orientMode( "face default" );
		}
	}
}

wasPreviouslyInCover()
{
	switch( self.prevScript )
	{
		case "cover_crouch":
		case "cover_left":
		case "cover_prone":
		case "cover_right":
		case "cover_stand":
		case "concealment_crouch":
		case "concealment_prone":
		case "concealment_stand":
		case "cover_wide_left":
		case "cover_wide_right":
		case "hide":
		case "turret":
			return true;
	}
	
	return false;
}


moveStartBattleChatter( wasInCover )
{
	if ( self.moveMode == "run" )
	{
		// SRS 10/30/08: removed a bunch of unnecessary logic here
		self animscripts\battleChatter_ai::evaluateMoveEvent( wasInCover );
	}
}

MoveMainLoop( doWalkCheck )
{
	MoveMainLoopInternal( doWalkCheck );
	self notify( "abort_reload" ); // in case a reload was going and MoveMainLoopInternal hit an endon
}

ChangeMoveMode( moveMode )
{
	if ( moveMode != self.prevMoveMode )
	{
		if ( isdefined( self.customMoveAnimSet ) && isdefined( self.customMoveAnimSet[ moveMode ] ) )
		{
			self.a.moveAnimSet = self.customMoveAnimSet[ moveMode ];
		}
		else		
		{
			self.a.moveAnimSet = anim.animsets.move[ moveMode ];
			
			if ( ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" ) && 
				 ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) > squared( 100 ) ) )
			{
				self.sideStepRate = 1;
				animscripts\init_common::set_ambush_sidestep_anims();
			}
			else
			{
				self.sideStepRate = 1.35;
			}
		}
			
		self.prevMoveMode = moveMode;
	}
}

MoveMainLoopInternal( doWalkCheck )
{
	self endon( "killanimscript" );
	self endon( "move_interrupt" );

	prevLoopTime = self getAnimTime( %walk_and_run_loops );
	self.a.runLoopCount = randomint( 10000 );// integer that is incremented each time we complete a run loop

	self.prevMoveMode = "none";
	
	self.moveLoopCleanupFunc = undefined;

	// if initial destination is closer than 64 walk to it.
	for ( ;; )
	{
		loopTime = self getAnimTime( %walk_and_run_loops );
		if ( loopTime < prevLoopTime )
			self.a.runLoopCount++ ;
		prevLoopTime = loopTime;

		ChangeMoveMode( self.moveMode );
		MoveMainLoopProcess( self.moveMode );
		
		if ( isDefined( self.moveLoopCleanupFunc ) )
		{
			self [[self.moveLoopCleanupFunc]]();
			self.moveLoopCleanupFunc = undefined;
		}
		
		self notify( "abort_reload" ); // in case a reload was going and MoveMainLoopProcess hit an endon
	}
}

MoveMainLoopProcess( moveMode )
{
	self endon( "move_loop_restart" );
	
	//prof_begin("MoveMainLoop");
	
	self animscripts\face::SetIdleFaceDelayed( anim.alertface );
	
	if ( isdefined( self.moveLoopOverrideFunc ) )
	{
		self [[ self.moveLoopOverrideFunc ]]();
	}
	else if ( self shouldCQB() )
	{
		self animscripts\cqb::MoveCQB();
	}
	else
	{
		if ( moveMode == "run" )
		{
			self animscripts\run::MoveRun();
		}
		else
		{
			assert( moveMode == "walk" );
			self animscripts\walk::MoveWalk();
		}
	}

	self.requestReactToBullet = undefined;
	//prof_end("MoveMainLoop");
}


MayShootWhileMoving()
{
	if ( self.weapon == "none" )
		return false;

	weapclass = weaponClass( self.weapon );
	if ( !usingRifleLikeWeapon() && !usingMinigun() ) 
		return false;

	if ( self isSniper() )
	{
		if ( !( self isCQBWalking() ) && self.faceMotion )
			return false;
	}

	if ( isdefined( self.dontShootWhileMoving ) )
	{
		assert( self.dontShootWhileMoving );// true or undefined
		return false;
	}

	return true;
}

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

	// it's possible for this to be called by CQB while it's already running from run.gsc,
	// even though run.gsc will kill it on the next frame. We can't let it run twice at once.
	self notify( "doing_shootWhileMoving" );
	self endon( "doing_shootWhileMoving" );

	self.a.array[ "fire" ] = %exposed_shoot_auto_v3;

	if ( isdefined( self.weapon ) && weapon_pump_action_shotgun() )
		self.a.array[ "single" ] = array( %shotgun_stand_fire_1A, %shotgun_stand_fire_1B );
	else
		self.a.array[ "single" ] = array( %exposed_shoot_semi1 );

	self.a.array[ "burst2" ] = %exposed_shoot_burst3;
	self.a.array[ "burst3" ] = %exposed_shoot_burst3;
	self.a.array[ "burst4" ] = %exposed_shoot_burst4;
	self.a.array[ "burst5" ] = %exposed_shoot_burst5;
	self.a.array[ "burst6" ] = %exposed_shoot_burst6;

	self.a.array[ "semi2" ] = %exposed_shoot_semi2;
	self.a.array[ "semi3" ] = %exposed_shoot_semi3;
	self.a.array[ "semi4" ] = %exposed_shoot_semi4;
	self.a.array[ "semi5" ] = %exposed_shoot_semi5;

	self.a.array[ "continuous" ] = array( %nx_tp_stand_exposed_stream_01 );

	while ( 1 )
	{
		if ( !self.bulletsInClip )
		{
			if ( self isCQBWalkingOrFacingEnemy() )
			{
				self.ammoCheatTime = 0;
				cheatAmmoIfNecessary();
			}

			if ( !self.bulletsInClip )
			{
				wait 0.5;
				continue;
			}
		}

		self shootUntilShootBehaviorChange();
		// can't clear %exposed_modern because there are transition animations within it that we might play when going to prone
		self clearAnim( %exposed_aiming, 0.2 );
	}
}


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

	// wait a frame so MoveMainLoop can start. Otherwise one of the following threads could unsuccesfully try to interrupt movement before it starts
	wait 0.05;

	self thread bulletWhizbyCheck_whileMoving();
	self thread meleeAttackCheck_whileMoving();
	self thread animscripts\door::inDoorCqbToggleCheck();
	self thread animscripts\door::doorEnterExitCheck();
}

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

	self.prevStairsState = self.stairsState;

	while ( 1 )
	{
		wait .05;
		if ( self.prevStairsState != self.stairsState )
		{
			// don't interrupt path change animation if getting off stairs to flat ground
			if ( !isdefined( self.ignorePathChange ) || self.stairsState != "none" )
				self notify( "move_loop_restart" );
		}

		self.prevStairsState = self.stairsState;
	}
}


restartMoveLoop( skipMoveTransition )
{
	self endon( "killanimscript" );

	if ( !skipMoveTransition )
		animscripts\cover_arrival::startMoveTransition();

	self.ignorePathChange = undefined;

	self clearanim( %root, 0.1 );
	self OrientMode( "face default" );
	self animMode( "none", false );

	self.requestArrivalNotify = true;
	MoveMainLoop( !skipMoveTransition );
}


pathChangeCheck()
{
	self endon( "killanimscript" );
	self endon( "move_interrupt" );

	self.ignorePathChange = true;	// this will be turned on / off in other threads at appropriate times

	while ( 1 )
	{
		// no other thread should end on "path_changed"
		self waittill( "path_changed", doingReacquire, newDir );

		// no need to check for doingReacquire since faceMotion should be a good check

		assert( !isdefined( self.ignorePathChange ) || self.ignorePathChange );	// should be true or undefined

		if ( isdefined( self.ignorePathChange ) || isdefined( self.noTurnAnims ) )
			continue;

		if ( !self.faceMotion )
			continue;

		if ( self.a.movement != "run" && self.a.movement != "walk" )
			continue;

		if ( self.a.pose != "stand" )
			continue;

		self notify( "stop_move_anim_update" );
		self.update_move_anim_type = undefined;

		angleDiff = AngleClamp180( self.angles[ 1 ] - vectortoyaw( newDir ) );

		turnAnim = pathChange_getTurnAnim( angleDiff );
			
		if ( isdefined( turnAnim ) )
		{
			self.turnAnim = turnAnim;
			self.turnTime = getTime();
			self.moveLoopOverrideFunc = ::pathChange_doTurnAnim;
			
			self notify( "move_loop_restart" );
			self animscripts\run::endFaceEnemyAimTracking();
		}
	}
}

pathChange_getTurnAnim( angleDiff )
{
	if ( isdefined( self.pathTurnAnimOverrideFunc ) )
		return [[ self.pathTurnAnimOverrideFunc ]]( angleDiff );

	turnAnim = undefined;
	secondTurnAnim = undefined;
	
	if ( self shouldCQB() || self.movemode == "walk" )
	{
		if ( isdefined( self.customTurnAnimSet ) && isdefined( self.customTurnAnimSet[ "cqb" ] ) )
		{
			animArray = self.customTurnAnimSet[ "cqb" ];
		}
		else
		{
			animArray = anim.cqbTurnAnims;
		}
	}
	else
	{
		if ( isdefined( self.customTurnAnimSet ) && isdefined( self.customTurnAnimSet[ "run" ] ) )
		{
			animArray = self.customTurnAnimSet[ "run" ];
		}
		else
		{
			animArray = anim.runTurnAnims;
		}
	}

	if ( angleDiff < -30 )
	{
		if ( angleDiff > -60 )			// bias for 45 turns
		{
			turnAnim = animArray[ "L45" ];
			
			// awkward pivot turn after doing animation
			//if ( angleDiff < -45 )
			//	secondTurnAnim = animArray[ "L90" ];
		}
		else if ( angleDiff > -112.5 )
		{
			turnAnim = animArray[ "L90" ];
			if ( angleDiff > -90 )
				secondTurnAnim = animArray[ "L45" ];
			else
				secondTurnAnim = animArray[ "L135" ];
		}
		else if ( angleDiff > -157.5 )
		{
			turnAnim = animArray[ "L135" ];
			if ( angleDiff > -135 )
				secondTurnAnim = animArray[ "L90" ];
			else
				secondTurnAnim = animArray[ "180" ];
		}
		else
		{
			turnAnim = animArray[ "180" ];
			secondTurnAnim = animArray[ "L135" ];
		}
	}
	else if ( angleDiff > 30 )
	{
		if ( angleDiff < 60 )
		{
			turnAnim = animArray[ "R45" ];
			
			// awkward pivot turn after doing animation
			//if ( angleDiff > 45 )
			//	secondTurnAnim = animArray[ "R90" ];
		}
		else if ( angleDiff < 112.5 )
		{
			turnAnim = animArray[ "R90" ];
			if ( angleDiff < 90 )
				secondTurnAnim = animArray[ "R45" ];
			else
				secondTurnAnim = animArray[ "R135" ];
		}
		else if ( angleDiff < 157.5 )
		{
			turnAnim = animArray[ "R135" ];
			if ( angleDiff < 135 )
				secondTurnAnim = animArray[ "R90" ];
			else
				secondTurnAnim = animArray[ "180" ];
		}
		else
		{
			turnAnim = animArray[ "180" ];
			secondTurnAnim = animArray[ "R135" ];
		}
	}
	
	if ( isdefined( turnAnim ) )
	{
		if ( pathChange_canDoTurnAnim( turnAnim ) )
			return turnAnim;
	}
	
	if ( isdefined( secondTurnAnim ) )
	{
		if ( pathChange_canDoTurnAnim( secondTurnAnim ) )
			return secondTurnAnim;
	}
	
	return undefined;
}

pathChange_canDoTurnAnim( turnAnim )
{
	if ( !isdefined( self.pathgoalpos ) )
		return false;

	codeMoveTimes = getNotetrackTimes( turnAnim, "code_move" );
	assert( codeMoveTimes.size == 1 );

	codeMoveTime = codeMoveTimes[ 0 ];
	assert( codeMoveTime <= 1 );

	moveDelta = getMoveDelta( turnAnim, 0, codeMoveTime );
	codeMovePoint = self localToWorldCoords( moveDelta );

	/#
	animscripts\utility::drawDebugLine( self.origin, codeMovePoint, ( 1, 1, 0 ), 20 );
	animscripts\utility::drawDebugLine( self.origin, self.pathgoalpos, ( 0, 1, 0 ), 20 );
	#/

	//if ( distanceSquared( self.origin, codeMovePoint ) > distanceSquared( self.origin, self.pathgoalpos ) )
	if ( isdefined( self.arrivalStartDist ) && ( squared( self.arrivalStartDist ) > distanceSquared( self.pathgoalpos, codeMovePoint ) ) )
		return false;

	moveDelta = getMoveDelta( turnAnim, 0, 1 );
	endPoint = self localToWorldCoords( moveDelta );

	endPoint = codeMovePoint + vectornormalize( endPoint - codeMovePoint ) * 20;

	/# animscripts\utility::drawDebugLine( codeMovePoint, endPoint, ( 1, 1, 0 ), 20 ); #/

	return self mayMoveFromPointToPoint( codeMovePoint, endPoint, true, true );
}

pathChange_doTurnAnim()
{
	self endon( "killanimscript" );
	
	self.moveLoopOverrideFunc = undefined;
	
	turnAnim = self.turnAnim;
	
	if ( gettime() > self.turnTime + 50 )
		return; // too late
	
	self animMode( "zonly_physics", false );
	
	self.moveLoopCleanupFunc = ::pathChange_cleanupTurnAnim;
	
	self.ignorePathChange = true;
	
	// Figure out blend in time
	blendTime = 0.05;
	if ( isdefined( self.pathTurnAnimBlendTime ) )
	{
		blendTime = isdefined( self.pathTurnAnimBlendTime );
	}
	if( self IsCQBWalking() && animHasNotetrack( turnAnim, "cqb_blend_finish" ) )
	{
		blend_finish_time = GetNotetrackTimes( turnAnim, "cqb_blend_finish" );
		blendTime = blend_finish_time[0] * GetAnimLength( turnAnim );
	}
	else if ( !self IsCQBWalking() && animHasNotetrack( turnAnim, "blend_finish" ) )
	{
		blend_finish_time = GetNotetrackTimes( turnAnim, "blend_finish" );
		blendTime = blend_finish_time[0] * GetAnimLength( turnAnim );
	}

	// Figure out and store blend out time (handled in various move scripts)
	if ( animHasNotetrack( turnanim, "blend_out_finish" ) )
	{
		finish_time = GetNoteTrackTimes( turnanim, "finish" );
		blend_finish_time = GetNotetrackTimes( turnanim, "blend_out_finish" );

		/#
		if ( blend_finish_time[0] < finish_time[0] )
		{
			AssertMsg( "blend_finish tag must occur later than finish tag." );
		}
		#/

		assert( blend_finish_time[0] > finish_time[0] );
		if ( self IsCQBWalking() )
		{
			self.run_blend_finish_time = undefined;
			self.cqb_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( turnanim );
		}
		else
		{
			self.cqb_blend_finish_time = undefined;
			self.run_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( turnanim );
		}
	}

	handle_move_transition_notes( turnAnim );
		
	self clearanim( %body, blendTime );
	self setflaggedanimrestart( "turnAnim", turnAnim, 1, blendTime, self.movePlaybackRate );
	self OrientMode( "face current" );

	assert( animHasNotetrack( turnAnim, "code_move" ) );
	self animscripts\shared::DoNoteTracks( "turnAnim" );	// until "code_move"

	self.ignorePathChange = undefined;
	self OrientMode( "face motion" );	// want to face motion, don't do l / r / b anims
	self animmode( "none", false );

	//assert( animHasNotetrack( turnAnim, "finish" ) );
	self animscripts\shared::DoNoteTracks( "turnAnim" );
}

pathChange_doMoveTransition()
{
	self.moveLoopOverrideFunc = undefined;
	if ( gettime() > self.turnTime + 50 )
		return; // too late
	
	self.moveLoopCleanupFunc = ::pathChange_cleanupTurnAnim;
	
	animscripts\cover_arrival::startMoveTransition();
}

pathChange_cleanupTurnAnim()
{
	blend_out_time = 0.1;

	if ( shouldCQB() )
	{
		blend_out_time = self animscripts\cqb::get_cqb_blend_in_time();
	}

	self.ignorePathChange = undefined;
	
	self OrientMode( "face default" );
	self clearanim( %root, blend_out_time );
	self animMode( "none", false );
}

dodgeMoveLoopOverride()
{
	self pushplayer( true );
	self animMode( "zonly_physics", false );
	self clearanim( %body, 0.2 );	
	
	self setflaggedanimrestart( "dodgeAnim", self.currentDodgeAnim, 1, 0.2, 1 );
	self animscripts\shared::DoNoteTracks( "dodgeAnim" );

	self animmode( "none", false );
	self orientMode( "face default" );

	if ( animHasNotetrack( self.currentDodgeAnim, "code_move" ) )
		self animscripts\shared::DoNoteTracks( "dodgeAnim" );	// return on code_move

	self clearanim( %civilian_dodge, 0.2 );

	self pushplayer( false );
	self.currentDodgeAnim = undefined;
	self.moveLoopOverrideFunc = undefined;
	return true;
}


tryDodgeWithAnim( dodgeAnim, dodgeAnimDelta )
{
	rightDir = ( self.lookAheadDir[1], -1 * self.lookAheadDir[0], 0 );

	forward = self.lookAheadDir * dodgeAnimDelta[0];
	right   = rightDir * dodgeAnimDelta[1];
	
	dodgePos = self.origin + forward - right;

	self pushPlayer( true );
	if ( self mayMoveToPoint( dodgePos ) )
	{
		self.currentDodgeAnim = dodgeAnim;
		self.moveLoopOverrideFunc = ::dodgeMoveLoopOverride;
		self notify( "move_loop_restart" );
		
		/# 
		if ( getdvar( "scr_debugdodge" ) == "1" )
			thread debugline( self.origin, dodgePos, ( 0, 1, 0 ), 3 );
		#/
		
		return true;
	}

	/# 
	if ( getdvar( "scr_debugdodge" ) == "1" )
		thread debugline( self.origin, dodgePos, ( 0.5, 0.5, 0 ), 3 );
	#/	
	
	self pushPlayer( false );
	return false;
}

animDodgeObstacle()
{
	if ( !isdefined( self.dodgeLeftAnim ) || !isdefined( self.dodgeRightAnim ) )
		return;

	self endon( "killanimscript" );
	self endon( "move_interrupt" );

	while ( 1 )
	{
		// no other thread should end on "path_changed"
		self waittill( "path_need_dodge", dodgeEnt, dodgeEntPos );
		
		if ( self animscripts\utility::IsInCombat() )
		{
			self.noDodgeMove = false;
			return;
		}
		
		if ( !isSentient( dodgeEnt ) )
			continue;
			
		/# 
		if ( getdvar( "scr_debugdodge" ) == "1" )
		{
			thread debugline( dodgeEnt.origin + (0, 0, 10), dodgeEntPos, ( 1, 1, 0 ), 3 );
			thread debugline( self.origin, dodgeEntPos, ( 1, 0, 0 ), 3 );
		}
		#/
			
		dirToDodgeEnt = vectorNormalize( dodgeEntPos - self.origin );
		
		if ( ( self.lookAheadDir[0] * dirToDodgeEnt[1] ) - ( dirToDodgeEnt[0] * self.lookAheadDir[1] ) > 0 )
		{
			// right first
			if ( !tryDodgeWithAnim( self.dodgeRightAnim, self.dodgeRightAnimOffset ) )
				tryDodgeWithAnim( self.dodgeLeftAnim, self.dodgeLeftAnimOffset );
		}
		else
		{
			// left first
			if ( !tryDodgeWithAnim( self.dodgeLeftAnim, self.dodgeLeftAnimOffset ) )
				tryDodgeWithAnim( self.dodgeRightAnim, self.dodgeRightAnimOffset );
		}
		
		if ( isdefined( self.currentDodgeAnim ) )
			wait getanimlength( self.currentDodgeAnim );
		else
			wait 0.1;
	}
}

setDodgeAnims( leftAnim, rightAnim )
{
	self.noDodgeMove = true;	// don't let code path around obstacle to dodge
	//self pushplayer( true );
	
	self.dodgeLeftAnim = leftAnim;
	self.dodgeRightAnim = rightAnim;
	
	time = 1;
	if ( animHasNoteTrack( leftAnim, "code_move" ) )
		time = getNotetrackTimes( leftAnim, "code_move" )[0];
	
	self.dodgeLeftAnimOffset = getMoveDelta( leftAnim, 0, time );

	time = 1;
	if ( animHasNoteTrack( rightAnim, "code_move" ) )
		time = getNotetrackTimes( rightAnim, "code_move" )[0];

	self.dodgeRightAnimOffset = getMoveDelta( rightAnim, 0, time );
	
	self.interval = 80;	// good value for civilian dodge animations
}

clearDodgeAnims()
{
	self.noDodgeMove = false;
	self.dodgeLeftAnim = undefined;
	self.dodgeRightAnim = undefined;
	self.dodgeLeftAnimOffset = undefined;
	self.dodgeRightAnimOffset = undefined;
}

meleeAttackCheck_whileMoving()
{
	self endon( "killanimscript" );
	
	while ( 1 )
	{
		// Try to melee our enemy if it's another AI
		if ( isDefined( self.enemy ) && ( isAI( self.enemy ) || isdefined( self.meleePlayerWhileMoving ) ) )
		{
			if ( abs( self GetMotionAngle() ) <= 135 ) // only when moving forward or sideways
				animscripts\melee::Melee_TryExecuting();
		}
		
		wait 0.1;
	}
}

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

	if ( isdefined( self.disableBulletWhizbyReaction ) )
		return;

	while ( 1 )
	{
		self waittill( "bulletwhizby", shooter );
		
		if ( self.moveMode != "run" || !self.faceMotion || self.a.pose != "stand" || isdefined( self.reactingToBullet ) )
			continue;
		
		if ( self.stairsState != "none" )
			continue;
		
		if ( !isdefined( self.enemy ) && !self.ignoreAll && isDefined( shooter.team ) && isEnemyTeam( self.team, shooter.team ) )
		{
			self.whizbyEnemy = shooter;
			self animcustom( animscripts\reactions::bulletWhizbyReaction );	// this will end move script
			continue;
		}
		
		if ( self.lookaheadHitsStairs || self.lookaheadDist < 100 )
			continue;
		
		if ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) < 10000 )
		{
			wait 0.2;
			continue;
		}
		
		if( !IsDefined( self.noReactToBullets ) || self.noReactToBullets == false )
		{
			self.requestReactToBullet = gettime();
		}
		
		self notify( "move_loop_restart" );
		self animscripts\run::endFaceEnemyAimTracking();
	}
}


get_shuffle_to_corner_start_anim( shuffleLeft, startNode )
{
	if ( startNode.type == "Cover Left" )
	{
		assert( !shuffleLeft );
		return %CornerCrL_alert_2_shuffle;
	}
	else if ( startNode.type == "Cover Right" )
	{
		assert( shuffleLeft );
		return %CornerCrR_alert_2_shuffle;
	}
	else
	{
		if ( shuffleLeft )
			return %covercrouch_hide_2_shuffleL;
		else
			return %covercrouch_hide_2_shuffleR;
	}
}


setup_shuffle_anim_array( shuffleLeft, startNode, endNode )
{
	anim_array = [];

	assert( isdefined( startNode ) );
	assert( isdefined( endNode ) );
	
	if ( endNode.type == "Cover Left" )
	{
		assert( shuffleLeft );
		anim_array[ "shuffle_start" ]	 = get_shuffle_to_corner_start_anim( shuffleLeft, startNode );
		anim_array[ "shuffle" ]			 = %covercrouch_shuffleL;
		anim_array[ "shuffle_end" ]		 = %CornerCrL_shuffle_2_alert;
	}
	else if ( endNode.type == "Cover Right" )
	{
		assert( !shuffleLeft );
		anim_array[ "shuffle_start" ]	 = get_shuffle_to_corner_start_anim( shuffleLeft, startNode ); 
		anim_array[ "shuffle" ]			 = %covercrouch_shuffleR;
		anim_array[ "shuffle_end" ]		 = %CornerCrR_shuffle_2_alert;
	}
	else if ( endNode.type == "Cover Stand" && startNode.type == endNode.type )
	{
		if ( shuffleLeft )
		{
			anim_array[ "shuffle_start" ]	 = %coverstand_hide_2_shuffleL;
			anim_array[ "shuffle" ]			 = %coverstand_shuffleL;
			anim_array[ "shuffle_end" ]		 = %coverstand_shuffleL_2_hide;
		}
		else
		{
			anim_array[ "shuffle_start" ]	 = %coverstand_hide_2_shuffleR;
			anim_array[ "shuffle" ]			 = %coverstand_shuffleR;
			anim_array[ "shuffle_end" ]		 = %coverstand_shuffleR_2_hide;
		}
	}
	else
	{
		//assert( endNode.type == "Cover Crouch" || endNode.type == "Cover Crouch Window" );
		if ( shuffleLeft )
		{
			anim_array[ "shuffle_start" ]	 = get_shuffle_to_corner_start_anim( shuffleLeft, startNode ); 
			anim_array[ "shuffle" ]			 = %covercrouch_shuffleL;
			
			if ( endNode.type == "Cover Stand" )
				anim_array[ "shuffle_end" ]		 = %coverstand_shuffleL_2_hide;
			else
				anim_array[ "shuffle_end" ]		 = %covercrouch_shuffleL_2_hide;
		}
		else
		{
			anim_array[ "shuffle_start" ]	 = get_shuffle_to_corner_start_anim( shuffleLeft, startNode ); 
			anim_array[ "shuffle" ]			 = %covercrouch_shuffleR;
			
			if ( endNode.type == "Cover Stand" )
				anim_array[ "shuffle_end" ]		 = %coverstand_shuffleR_2_hide;
			else
				anim_array[ "shuffle_end" ]		 = %covercrouch_shuffleR_2_hide;
		}
	}

	self.a.array = anim_array;
}

moveCoverToCover_checkStartPose( startNode, endNode )
{
	if ( self.a.pose == "stand" && ( endNode.type != "Cover Stand" || startNode.type != "Cover Stand" ) )
	{
		self.a.pose = "crouch";
		return false;
	}
	
	return true;
}

moveCoverToCover_checkEndPose( endNode )
{
	if ( self.a.pose == "crouch" && endNode.type == "Cover Stand" )
	{
		self.a.pose = "stand";
		return false;	
	}
	
	return true;
}


serverFPS = 20;
serverSPF = 0.05;

moveCoverToCover()
{
	self endon( "killanimscript" );
	self endon( "goal_changed" );

	shuffleNode = self.shuffleNode;

	self.shuffleMove = undefined;
	self.shuffleNode = undefined;
	self.shuffleMoveInterrupted = true;

	if ( !isdefined( self.prevNode ) )
		return;
	
	if ( !isdefined( self.node ) || !isdefined( shuffleNode ) || self.node != shuffleNode )
		return;
	
	shuffleNodeType = self.prevNode;

	node = self.node;

	moveDir = node.origin - self.origin;
	if ( lengthSquared( moveDir ) < 1 )
		return;

	moveDir = vectornormalize( moveDir );
	forward = anglestoforward( node.angles );
	
	shuffleLeft = ( ( forward[ 0 ] * moveDir[ 1 ] ) - ( forward[ 1 ] * moveDir[ 0 ] ) ) > 0;

	if ( moveDoorSideToSide( shuffleLeft, shuffleNodeType, node ) )
		return;
	
	if ( moveCoverToCover_checkStartPose( shuffleNodeType, node ) )
		blendTime = 0.1;
	else
		blendTime = 0.4;
		
	setup_shuffle_anim_array( shuffleLeft, shuffleNodeType, node );

	self animMode( "zonly_physics", false );

	self clearanim( %body, blendTime );

	startAnim	 = animarray( "shuffle_start" );
	shuffleAnim = animarray( "shuffle" );
	endAnim		 = animarray( "shuffle_end" );

	//assertEx( animhasnotetrack( startAnim, "finish" ), "animation doesn't have finish notetrack " + startAnim );
	if ( animhasnotetrack( startAnim, "finish" ) )
		startEndTime = getNotetrackTimes( startAnim, "finish" )[ 0 ];
	else
		startEndTime = 1;

	startDist   = length( getMoveDelta( startAnim, 0, startEndTime ) );
	shuffleDist	 = length( getMoveDelta( shuffleAnim, 0, 1 ) );
	endDist		 = length( getMoveDelta( endAnim, 0, 1 ) );

	remainingDist = distance( self.origin, node.origin );

	if ( remainingDist > startDist )
	{
		self OrientMode( "face angle", getNodeForwardYaw( shuffleNodeType ) );
		
		self setflaggedanimrestart( "shuffle_start", startAnim, 1, blendTime );
		self animscripts\shared::DoNoteTracks( "shuffle_start" );
		self clearAnim( startAnim, 0.2 );
		remainingDist -= startDist;

		blendTime = 0.2; // reset blend for looping move
	}
	else
	{
		self OrientMode( "face angle", node.angles[1] );
	}

	playEnd = false;
	if ( remainingDist > endDist )
	{
		playEnd = true;
		remainingDist -= endDist;
	}

	loopTime = getAnimLength( shuffleAnim );
	playTime = loopTime * ( remainingDist / shuffleDist ) * 0.9;
	playTime = floor( playTime * serverFPS ) * serverSPF;

	self setflaggedanim( "shuffle", shuffleAnim, 1, blendTime );
	self animscripts\shared::DoNoteTracksForTime( playTime, "shuffle" );

	// account for loopTime not being exact since loop animation delta isn't uniform over time
	for ( i = 0; i < 2; i++ )
	{
		remainingDist = distance( self.origin, node.origin );
		if ( playEnd )
			remainingDist -= endDist;

		if ( remainingDist < 4 )
			break;

		playTime = loopTime * ( remainingDist / shuffleDist ) * 0.9;	// don't overshoot
		playTime = floor( playTime * serverFPS ) * serverSPF;

		if ( playTime < 0.05 )
			break;

		self animscripts\shared::DoNoteTracksForTime( playTime, "shuffle" );
	}

	if ( playEnd )
	{
		if ( moveCoverToCover_checkEndPose( node ) )
			blendTime = 0.2;
		else
			blendTime = 0.4;
			
		self clearAnim( shuffleAnim, blendTime );
		self setflaggedanim( "shuffle_end", endAnim, 1, blendTime );
		self animscripts\shared::DoNoteTracks( "shuffle_end" );
		
		// clear animation in moveCoverToCoverFinish if needed
	}

	self safeTeleport( node.origin );
	self animMode( "normal" );

	self.shuffleMoveInterrupted = undefined;
}


moveCoverToCoverFinish()
{
	if ( isdefined( self.shuffleMoveInterrupted ) )
	{
		self clearanim( %cover_shuffle, 0.2 );
		
		self.shuffleMoveInterrupted = undefined;
		self animmode( "none", false );
		self orientmode( "face default" );
	}
	else
	{
		wait 0.2;	// don't clear animation, wait for cover script to take over
		
		self clearanim( %cover_shuffle, 0.2 );
	}
}

moveDoorSideToSide( shuffleLeft, startNode, endNode )
{
	sideToSideAnim = undefined;
	
	if ( startNode.type == "Cover Right" && endNode.type == "Cover Left" && !shuffleLeft )
		sideToSideAnim = %corner_standR_Door_R2L;
	else if ( startNode.type == "Cover Left" && endNode.type == "Cover Right" && shuffleLeft )
		sideToSideAnim = %corner_standL_Door_L2R;
		
	if ( !isdefined( sideToSideAnim ) )
		return false;

	self animMode( "zonly_physics", false );
	self orientmode( "face current" );

	self setflaggedanimrestart( "sideToSide", sideToSideAnim, 1, 0.2 );
	
	assert( animHasNoteTrack( sideToSideAnim, "slide_start" ) );
	assert( animHasNoteTrack( sideToSideAnim, "slide_end" ) );

	self animscripts\shared::DoNoteTracks( "sideToSide", ::handleSideToSideNotetracks );

	slideStartTime = self getAnimTime( sideToSideAnim );
	slideDir = endNode.origin - startNode.origin;
	slideDir = vectornormalize( ( slideDir[0], slideDir[1], 0 ) );

	animDelta = getMoveDelta( sideToSideAnim, slideStartTime, 1 );
	remainingVec = endNode.origin - self.origin;
	remainingVec = ( remainingVec[0], remainingVec[1], 0 );
	slideDist = vectordot( remainingVec, slideDir ) - abs( animDelta[1] );
	
	if ( slideDist > 2 )
	{
		slideEndTime = getNoteTrackTimes( sideToSideAnim, "slide_end" )[0];
		slideTime = ( slideEndTime - slideStartTime ) * getAnimLength( sideToSideAnim );
		assert( slideTime > 0 );

		slideFrames = int( ceil( slideTime / 0.05 ) );
		slideIncrement = slideDir * slideDist / slideFrames;
		self thread slideForTime( slideIncrement, slideFrames );
	}

	self animscripts\shared::DoNoteTracks( "sideToSide" );

	self safeTeleport( endNode.origin );
	self animMode( "none" );
	self orientmode( "face default" );

	self.shuffleMoveInterrupted = undefined;
	wait 0.2;	
	
	return true;
}

handleSideToSideNotetracks( note )
{
	if ( note == "slide_start" )
		return true;
}

slideForTime( slideIncrement, slideFrames )
{
	self endon( "killanimscript" );
	self endon( "goal_changed" );
	
	while ( slideFrames > 0 )
	{
		self safeTeleport( self.origin + slideIncrement );
		slideFrames--;
		wait 0.05;
	}
}

MoveStandMoveOverride( override_anim, weights )
{
	self endon( "movemode" );
	self clearanim( %combatrun, 0.6 );
	self setanimknoball( %combatrun, %body, 1, 0.5, self.moveplaybackrate );

	if ( isdefined( self.requestReactToBullet ) && gettime() - self.requestReactToBullet < 100 && isdefined( self.run_overrideBulletReact ) && randomFloat( 1 ) < self.a.reactToBulletChance )
	{
		animscripts\run::CustomRunningReactToBullets();
		return;
	}

	if ( isarray( override_anim ) )
	{
		if ( isdefined( self.run_override_weights ) )
			moveAnim = choose_from_weighted_array( override_anim, weights );	
		else
			moveAnim = override_anim[ randomint( override_anim.size ) ];
	}
	else
	{
		moveAnim = override_anim;
	}

	self setflaggedanimknob( "moveanim", moveAnim, 1, 0.2 );
	animscripts\shared::DoNoteTracks( "moveanim" );
}