/***********************************************************************

ai_character_sentry.script

***********************************************************************/

#define SENTRY_WAIT_GETCLOSER		3
#define	SENTRY_LEAD_TIMEOUT			1
#define SENTRY_FIRERATE				0.1
#define SENTRY_MIN_SHOTS			8
#define SENTRY_MIN_LOOK_DELAY		4
#define	SENTRY_MAX_LOOK_DELAY		8
#define	SENTRY_LOOK_TIME			1.5
#define SENTRY_LEAD_DIST			140
#define SENTRY_LOST_DIST			280
#define SENTRY_WALKTURN				65
#define SENTRY_WALKTURN2			30
#define SENTRY_ATTACK_MAX_LENGTH	4
#define SENTRY_ATTACK_MIN_LENGTH	1.2
#define SENTRY_WAIT_MAX_LENGTH		1
#define SENTRY_WAIT_MIN_LENGTH		0.3
#define SENTRY_MIN_TURN				10

#define RETREATING					-1
#define	NOT_MOVING					0
#define	APPROACHING					1

#define RETREATING_DEST				-1
#define	APPROACHING_DEST			1

#define PLAYER_LOST					0
#define PLAYER_BEHIND_ME			1
#define PLAYER_OK					2
#define PLAYER_AHEAD_OF_ME			3

object char_sentry : character {
	boolean		lead_player;
	float		hanging;
	boolean		awake;
	boolean		fire;
	float		playerMoveState;
	entity		light;
	boolean		light_on;
	float		nextPositionSearch;
	entity		ignore_enemy;
	float		ignore_enemy_time;
	vector		lastValidPlayerPosition;
	float		playerPositionThread;
	
	//
	// States
	//
	void		state_WaitForTrigger();
	void		state_Idle();
	void		state_Killed();
	void		state_FollowPath();
	void		state_Wait();
	void		state_GetCloser();
	void		state_CantReachPlayer();
	void		state_Lead();
	void		state_FindPlayer();
	void		state_Combat();
	void		combat_chase();
	boolean		find_attack_position();

	// attack checks
	float		check_attacks();
	void		do_attack( float attack_flags );
	void		combat_range();

	void		state_Begin();
	void		init();
	void		destroy();

	void		spawn_flashlight();
	void		flashlight_off();
	void		flashlight_on();
	void		ignore( entity ignore_ent );
	boolean		checkForEnemy( float use_fov );
	
	float		checkDestinationDistance();
	void		updatePlayerPositionThread();

	// path commands
	void		path_sentry_light_on();
	void		path_sentry_light_off();
	void		path_sentry_shutdown();
	void		path_sentry_unlock_door();
	void		path_corner();
	void		path_sentry_lead_player();
	void		path_sentry_ignore_player();
		
	// torso anim states
	void		Torso_Death();
	void		Torso_Idle();
	void		Torso_Pain();
	void		Torso_RangeAttack();
	
	// legs anim states
	void		Legs_Fold();
	void		Legs_Folded();
	void		Legs_Unfold();
	void		Legs_Hanging();
	void		Legs_Death();
	void		Legs_Idle();
	void		Legs_Walk();
	void		Legs_TurnLeft();
	void		Legs_TurnRight();
};

/***********************************************************************

	Torso animation control

***********************************************************************/

void char_sentry::Torso_Death() {
	finishAction( "dead" );

	// never exit
	waitUntil( 0 );
}

void char_sentry::Torso_Idle() {
	idleAnim( ANIMCHANNEL_TORSO, "stand" );
	
	eachFrame {
		if ( AI_PAIN ) {
			Torso_Pain();
			idleAnim( ANIMCHANNEL_TORSO, "stand" );
		}
		if ( fire ) {
			animState( ANIMCHANNEL_TORSO, "Torso_RangeAttack", 4 );
		}
	}
}

void char_sentry::Torso_Pain() {
	string animname;

	animname = getPainAnim();
	setBlendFrames( ANIMCHANNEL_TORSO, 2 );
	playAnim( ANIMCHANNEL_TORSO, animname );

	while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) {
		waitFrame();
	}
	
	// no pain for 2 seconds
	preventPain( 2 );
	
	finishAction( "pain" );
	setBlendFrames( ANIMCHANNEL_TORSO, 4 );
}

void char_sentry::Torso_RangeAttack() {
	float endtime;
	float firetime;
	float numshots;
	
	setAnimPrefix( "" );
		
	playAnim( ANIMCHANNEL_TORSO, "range_attack" );
	while( !animDone( ANIMCHANNEL_TORSO, 0 ) ) {
		if ( AI_PAIN ) {
			Torso_Pain();
		}
		waitFrame();
	}
	
	numshots = 0;
	while( fire || ( numshots < SENTRY_MIN_SHOTS ) ) {
		endtime = RandomDelay( SENTRY_ATTACK_MIN_LENGTH, SENTRY_ATTACK_MAX_LENGTH );
		setBlendFrames( ANIMCHANNEL_TORSO, 2 );
		playCycle( ANIMCHANNEL_TORSO, "range_attack_loop" );
		firetime = sys.getTime();
		while( ( fire || ( numshots < SENTRY_MIN_SHOTS ) ) && ( sys.getTime() < endtime ) ) {
			if ( sys.getTime() >= firetime ) {
				startSound( "snd_fire", SND_CHANNEL_WEAPON, false );
				attackMissile( "Barrel" );
				numshots++;
				firetime = sys.getTime() + SENTRY_FIRERATE;
			}
			if ( AI_PAIN ) {
				Torso_Pain();
				playCycle( ANIMCHANNEL_TORSO, "range_attack_loop" );
			}
			waitFrame();
		}

		if ( !fire && ( numshots >= SENTRY_MIN_SHOTS ) ) {
			break;
		}

		setBlendFrames( ANIMCHANNEL_TORSO, 2 );
		playCycle( ANIMCHANNEL_TORSO, "range_attack_aim" );
		endtime = RandomDelay( SENTRY_WAIT_MIN_LENGTH, SENTRY_WAIT_MAX_LENGTH );
		while( ( fire || ( numshots < SENTRY_MIN_SHOTS ) ) && ( sys.getTime() < endtime ) ) {
			if ( AI_PAIN ) {
				Torso_Pain();
			}
			waitFrame();
		}
	}
	
	playAnim( ANIMCHANNEL_TORSO, "range_attack_end" );
	while( !animDone( ANIMCHANNEL_TORSO, 4 ) ) {
		if ( AI_PAIN ) {
			Torso_Pain();
		}
		waitFrame();
	}
	
	finishAction( "range_attack" );
	animState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 );
}

/***********************************************************************

	Legs animation control

***********************************************************************/

void char_sentry::Legs_Death() {
	while( AI_DEAD ) {
		waitFrame();
	}
	
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 );
}

void char_sentry::Legs_Idle() {
	float delta;
	
	if ( !AI_FORWARD && !facingIdeal() ) {
		if ( getTurnDelta() > SENTRY_MIN_TURN ) {
			animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 );
		}

		if ( getTurnDelta() < -SENTRY_MIN_TURN ) {
			animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 );
		}
	}
	
	idleAnim( ANIMCHANNEL_LEGS, "stand" );

	eachFrame {
		if ( !awake ) {
			animState( ANIMCHANNEL_LEGS, "Legs_Fold", 4 );
		}
		if ( AI_FORWARD ) {
			delta = getTurnDelta();
			if ( ( delta <= SENTRY_WALKTURN ) && ( delta >= -SENTRY_WALKTURN ) ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
			}
		}
		if ( !facingIdeal() ) {
			if ( getTurnDelta() > SENTRY_MIN_TURN ) {
				animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 );
			}
			if ( getTurnDelta() < -SENTRY_MIN_TURN ) {
				animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 );
			}
		}
	}
}

void char_sentry::Legs_Walk() {
	float delta;
	
	playCycle( ANIMCHANNEL_LEGS, "walk" );
	
	while( AI_FORWARD )	{
		delta = getTurnDelta();
		if ( ( delta > SENTRY_WALKTURN ) || ( delta < -SENTRY_WALKTURN ) ) {
			break;
		}

		waitFrame();
	}
	
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 12 );
}

void char_sentry::Legs_Fold() {
	startSound( "snd_shutdown", SND_CHANNEL_VOICE, false );
	playAnim( ANIMCHANNEL_LEGS, "fold" );	
	while( !animDone( ANIMCHANNEL_LEGS, 0 ) ) {
		waitFrame();
	}
	flashlight_off();
	finishAction( "fold" );
	animState( ANIMCHANNEL_LEGS, "Legs_Folded", 0 );
}

void char_sentry::Legs_Folded() {
	playCycle( ANIMCHANNEL_LEGS, "folded" );	
	while( !awake ) {
		waitFrame();
	}
	
	animState( ANIMCHANNEL_LEGS, "Legs_Unfold", 4 );
}

void char_sentry::Legs_Unfold() {
	playAnim( ANIMCHANNEL_LEGS, "unfold" );	
	while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
		waitFrame();
	}
	
	finishAction( "unfold" );
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
}

void char_sentry::Legs_Hanging() {
	playCycle( ANIMCHANNEL_LEGS, "releaseidle" );	
	while( hanging ) {
		waitFrame();
	}
	
	playAnim( ANIMCHANNEL_LEGS, "load" );	
	while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
		waitFrame();
	}
	
	finishAction( "unload" );
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
}

void char_sentry::Legs_TurnLeft() {
	float turnAmount;
	float delta;
	
	turnAmount = getTurnDelta();
	if ( turnAmount > 110 ) {
		// do it in two turns
		turnAmount *= 0.5;
	}

	playAnim( ANIMCHANNEL_LEGS, "turn_left" );
	while( !animDone( ANIMCHANNEL_LEGS, 0 ) ) {
		if ( AI_FORWARD ) {
			delta = getTurnDelta();
			if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
			}
		}
		waitFrame();
	}
	
	playAnim( ANIMCHANNEL_LEGS, "turn_right" );
	while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
		if ( AI_FORWARD ) {
			delta = getTurnDelta();
			if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
			}
		}
		waitFrame();
	}
	
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
}

void char_sentry::Legs_TurnRight() {
	float turnAmount;
	float delta;
	
	turnAmount = getTurnDelta();
	if ( turnAmount < -110 ) {
		// do it in two turns
		turnAmount *= 0.5;
	}

	playAnim( ANIMCHANNEL_LEGS, "turn_right" );
	while( !animDone( ANIMCHANNEL_LEGS, 0 ) ) {
		if ( AI_FORWARD ) {
			delta = getTurnDelta();
			if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Walk", 12 );
			}
		}
		waitFrame();
	}
	
	playAnim( ANIMCHANNEL_LEGS, "turn_left" );
	while( !animDone( ANIMCHANNEL_LEGS, 8 ) ) {
		if ( AI_FORWARD ) {
			delta = getTurnDelta();
			if ( ( delta <= SENTRY_WALKTURN2 ) && ( delta >= -SENTRY_WALKTURN2 ) ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
			}
		}
		waitFrame();
	}

	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
}

/***********************************************************************

	AI

***********************************************************************/

/*
=====================
char_sentry::state_Begin
=====================
*/
void char_sentry::state_Begin() {
	fire = false;
	setBoneMod( true );

	light_on = false;
	if ( getIntKey( "flashlight" ) ) {
		spawn_flashlight();
	}

	hanging = getIntKey( "hanging" );
	awake = getIntKey( "unfolded" );

	waitFrame();
	if ( hanging ) {
		animState( ANIMCHANNEL_LEGS, "Legs_Hanging", 0 );
	} else if ( !awake ) {
		animState( ANIMCHANNEL_LEGS, "Legs_Folded", 0 );
	} else {
		animState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
	}
	animState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 );
	setState( "state_WaitForTrigger" );
}

/*
=====================
char_sentry::init
=====================
*/
void char_sentry::init() {
	lead_player = getIntKey( "lead_player" );
	can_talk = false;
	no_cower_saved = true;
	no_cower = true;
}

/*
=====================
char_sentry::destroy
=====================
*/
void char_sentry::destroy() {
	light.remove();
	if ( playerPositionThread ) {
		sys.terminate( playerPositionThread );
		playerPositionThread = 0;
	}
}

/*
=====================
char_sentry::spawn_flashlight
=====================
*/
void char_sentry::spawn_flashlight() {
	string texture;
	float distance;

	distance = getFloatKey( "flashlight_distance" );
	if ( !distance ) {
		distance = 640;
	}
	
	// use ik_pose to bind light
	playAnim( ANIMCHANNEL_TORSO, "ik_pose" );
	if ( getIntKey( "flashlight" ) == 2 ) {
		// spot light
		sys.setSpawnArg( "light_target", "1 0 0" );
		sys.setSpawnArg( "light_up", "0 0 .5" );
		sys.setSpawnArg( "light_right", "0 -.5 0" );	
		sys.setSpawnArg( "light_end", distance + " 0 0" );

		texture = getKey( "mtr_flashlight" );
		sys.setSpawnArg( "texture", texture );

		sys.setSpawnArg( "name", getName() + "_light" );	
		light = sys.spawn( "light" );
		light.setAngles( getAngles() );
		light.bindToJoint( self, "light", true );
		light.setOrigin( '0 0 0' );
	} else {
		// radial light
		sys.setSpawnArg( "name", getName() + "_light" );	
		light = sys.spawn( "light" );
		light.setRadius( 256 );	
		light.setAngles( getAngles() + '0 90 0' );
		light.bindToJoint( self, "light", true );
		texture = getKey( "mtr_flashlight" );
		light.setShader( texture );
		light.setOrigin( '0 0 0' );
	}
	stopAnim( ANIMCHANNEL_TORSO, 0 );

	flashlight_off();
}

/*
=====================
char_sentry::flashlight_off
=====================
*/
void char_sentry::flashlight_off() {
	string skin;

	if ( light ) {
		light.Off();
		skin = getKey( "skin_flashlight_off" );
		setSkin( skin );
		light_on = false;
	}
}
	
/*
=====================
char_sentry::flashlight_on
=====================
*/
void char_sentry::flashlight_on() {
	string skin;

	if ( light ) {
		light.On();
		skin = getKey( "skin_flashlight_on" );
		setSkin( skin );
		light_on = true;
	}
}
	
/*
=====================
char_sentry::ignore
=====================
*/
void char_sentry::ignore( entity ignore_ent ) {
	ignore_enemy = ignore_ent;
	ignore_enemy_time = sys.getTime() + 2;
}

/*
=====================
char_sentry::checkForEnemy
=====================
*/
boolean	char_sentry::checkForEnemy( float use_fov ) {
	entity enemy;
	entity ent;

	enemy = $null_entity;
	if ( lead_player && $player1.hasEnemies() ) {
		enemy = closestReachableEnemyOfEntity( $player1 );
		if ( ( ignore_enemy_time > sys.getTime() ) && ( ignore_enemy == enemy ) ) {
			enemy = $null_entity;
		}
		if ( !enemy ) {
			enemy = $player1.closestEnemyToPoint( getOrigin() );
			if ( ( ignore_enemy_time > sys.getTime() ) && ( ignore_enemy == enemy ) ) {
				enemy = $null_entity;
			}
		}
	}
	
	if ( !enemy ) {
		enemy = findEnemyAI( false );
		if ( ( ignore_enemy_time > sys.getTime() ) && ( ignore_enemy == enemy ) ) {
			enemy = $null_entity;
		}
	}

	if ( enemy ) {
		startSound( "snd_sight_enemy", SND_CHANNEL_VOICE, false );
		setEnemy( enemy );
        return true;
	} else {
		clearEnemy();
	}

	return false;
}

/*
=====================
char_sentry::checkDestinationDistance()
=====================
*/
float char_sentry::checkDestinationDistance() {
	float	playerDist;
	float	currentDist;
	float	leadDistance;
	float	relativeMoveDir;
	vector	delta;

	// determine if the player is moving toward us
	delta = getOrigin() - lastValidPlayerPosition;
	relativeMoveDir = delta * $player1.getLinearVelocity();
	if ( relativeMoveDir > 1 ) {
		// Player is approaching
		playerMoveState = APPROACHING;
	} else if ( relativeMoveDir < -1 ) {
		// Player is moving away from us
		playerMoveState = RETREATING;
	} else {
		// Player is standing still
		playerMoveState = NOT_MOVING;
	}

	if ( !current_path ) {
		return PLAYER_OK;
	}

	playerDist = distanceToPoint( lastValidPlayerPosition );
	if ( playerDist < SENTRY_LEAD_DIST ) {
		return PLAYER_OK;
	}

	if ( !canSee( $player1 ) ) {
		currentDist = travelDistanceToEntity( current_path );
		leadDistance = travelDistanceBetweenPoints( lastValidPlayerPosition, current_path.getOrigin() );
		if ( currentDist > leadDistance ) {
			return PLAYER_AHEAD_OF_ME;
		} else {
			return PLAYER_LOST;
		}
	}

	if ( playerDist > SENTRY_LOST_DIST ) {
		currentDist = travelDistanceToEntity( current_path );
		leadDistance = travelDistanceBetweenPoints( lastValidPlayerPosition, current_path.getOrigin() );
		if ( currentDist > leadDistance ) {
			return PLAYER_AHEAD_OF_ME;
		} else {
			return PLAYER_BEHIND_ME;
		}
	}

	return PLAYER_OK;
}

/*
=====================
char_sentry::updatePlayerPositionThread()
=====================
*/
void char_sentry::updatePlayerPositionThread() {
	vector pos;

	while( 1 ) {
		if ( canReachEntity( $player1 ) ) {
			lastValidPlayerPosition = getReachableEntityPosition( $player1 );
		}
		//sys.debugBounds( '1 0 0', lastValidPlayerPosition + $player1.getMins(), lastValidPlayerPosition + $player1.getMaxs(), 0 );
		waitFrame();
	}
}

/***********************************************************************

	Path commands

***********************************************************************/

/*
=====================
char_sentry::path_sentry_light_on
=====================
*/
void char_sentry::path_sentry_light_on() {
	flashlight_on();
}

/*
=====================
char_sentry::path_sentry_light_off
=====================
*/
void char_sentry::path_sentry_light_off() {
	flashlight_off();
}

/*
=====================
char_sentry::path_sentry_shutdown
=====================
*/
void char_sentry::path_sentry_shutdown() {
	vector ang;
	string triggername;
	entity triggerent;

	stopMove();

	ang = current_path.getAngles();
	turnTo( ang_y );
	waitUntil( facingIdeal() );

	awake = false;
	AI_ACTIVATED = false;

	if ( playerPositionThread ) {
		sys.terminate( playerPositionThread );
		playerPositionThread = 0;
	}
	waitAction( "fold" );
	
	// trigger any entities the path had targeted
	triggername = current_path.getKey( "trigger" );
	if ( triggername != "" ) {
		triggerent = sys.getEntity( triggername );
		if ( triggerent ) {
			triggerent.activate( self );
		}
	}
	
	while( !AI_ACTIVATED ) {
		waitFrame();
	}

	if ( getIntKey( "flashlight_on" ) ) {
		flashlight_on();
	}
	awake = true;
	waitAction( "unfold" );
}

/*
=====================
char_sentry::path_sentry_unlock_door
=====================
*/
void char_sentry::path_sentry_unlock_door() {
	vector ang;
	string triggername;
	entity triggerent;

	if ( current_path.getKey( "trigger" ) == "" ) {
		// already unlocked the door
		return;
	}

	ang = current_path.getAngles();
	turnTo( ang_y );
	waitUntil( facingIdeal() );

	startSound( "snd_open_door", SND_CHANNEL_VOICE, false );
	playCustomAnim( "unlock_door", 4, 4 );
	waitAction( "customAnim" );

	// trigger any entities the path had targeted
	triggername = current_path.getKey( "trigger" );
	if ( triggername != "" ) {
		triggerent = sys.getEntity( triggername );
		if ( triggerent ) {
			triggerent.activate( self );
		}
	}

	current_path.setKey( "trigger", "" );
}

/*
=====================
char_sentry::path_corner
=====================
*/
void char_sentry::path_corner() {
	if ( !lead_player ) {
		setState( "state_FollowPath" );
	}

	setNeverDormant( true );
	if ( !canSee( $player1 ) ) {
		setState( "state_FindPlayer" );
	}

	if ( checkDestinationDistance() == PLAYER_BEHIND_ME ) {
		setState( "state_Wait" );
	} else {
		setState( "state_Lead" );
	}
}

/*
=====================
char_sentry::path_sentry_lead_player
=====================
*/
void char_sentry::path_sentry_lead_player() {
	lead_player = true;
	if ( !playerPositionThread ) {
		playerPositionThread = thread updatePlayerPositionThread();
	}
}

/*
=====================
char_sentry::path_sentry_ignore_player
=====================
*/
void char_sentry::path_sentry_ignore_player() {
	lead_player = false;
	if ( playerPositionThread ) {
		sys.terminate( playerPositionThread );
		playerPositionThread = 0;
	}
}

/***********************************************************************

	States

***********************************************************************/

/*
=====================
char_sentry::state_Killed
=====================
*/
void char_sentry::state_Killed() {
	stopMove();

	if ( playerPositionThread ) {
		sys.terminate( playerPositionThread );
		playerPositionThread = 0;
	}

	flashlight_off();

	animState( ANIMCHANNEL_TORSO, "Torso_Death", 0 );
	animState( ANIMCHANNEL_LEGS, "Legs_Death", 0 );

	waitAction( "dead" );

	float burnDelay = getFloatKey( "burnaway" );
	if ( burnDelay != 0 ) {
		preBurn();
		sys.wait( burnDelay );
		burn();
		startSound( "snd_burn", SND_CHANNEL_BODY, false );
		sys.wait( 3 );
		remove();
	}

	stopThinking();
}

/*
=====================
char_sentry::state_WaitForTrigger
=====================
*/
void char_sentry::state_WaitForTrigger() {
	if ( hanging != 2 ) {
		AI_ACTIVATED = false;
		while( !AI_ACTIVATED ) {
			waitFrame();
		}
	}

	if ( getIntKey( "flashlight_on" ) ) {
		flashlight_on();
	}
	
	if ( hanging ) {
		hanging = false;
		awake = true;
		waitAction( "unload" );
	} else if ( !awake ) {
		awake = true;
		waitAction( "unfold" );
	}

	// hanging guys turn off damage untill they're out.  make sure we get damage turned back on.
	allowDamage();	
	lookAt( $player1, 3 );

	current_path = randomPath();
	//next_path = current_path.randomPath();
	lookAt( $player1, 3 );

	setState( "state_Idle" );
}

/*
=====================
char_sentry::state_Idle
=====================
*/
void char_sentry::state_Idle() {
	stopMove();

	if ( lead_player && !playerPositionThread ) {
		playerPositionThread = thread updatePlayerPositionThread();
	}

	if ( current_path ) {
		executePathCommand( current_path );
	}

	while( 1 ) {
		if ( lead_player ) {
			if ( !canSee( $player1 ) ) {
				setState( "state_FindPlayer" );
			}
		}

		if ( checkForEnemy( true ) ) {
			setState( "state_Combat" );
		}
		if ( AI_PUSHED ) {
			getOutOfWay();
		}
		if ( current_path ) {
			executePathCommand( current_path );
		}
		waitFrame();
	}
}

/*
=====================
char_sentry::state_FollowPath
=====================
*/
void char_sentry::state_FollowPath() {
	if ( !current_path ) {
		setState( "state_Idle" );
	}

	moveToEntity( current_path );

	while( 1 ) {
		if ( checkForEnemy( true ) ) {
			setState( "state_Combat" );
		}

		checkBlocked();

		if ( AI_MOVE_DONE ) {
			if ( AI_DEST_UNREACHABLE ) {
				waitFrame();
				setState( "state_FindPlayer" );
			}
			setNeverDormant( getFloatKey( "neverdormant" ) );
			finishPathCommand();
			setState( "state_Idle" );
		}
		
		waitFrame();
	}
}

/*
=====================
char_sentry::state_Wait
=====================
*/
void char_sentry::state_Wait() {
	float getCloserTime;

	float result = checkDestinationDistance();
	if ( result == PLAYER_LOST ) {
		setState( "state_FindPlayer" );
	} else if ( result == PLAYER_AHEAD_OF_ME ) {
		setState( "state_Lead" );
	} else if ( ( result == PLAYER_OK ) && ( playerMoveState == APPROACHING ) ) {
		setState( "state_Lead" );
	}

	stopMove();
	startSound( "snd_waiting_for_player", SND_CHANNEL_VOICE, false );

	getCloserTime = sys.getTime() + SENTRY_WAIT_GETCLOSER;
	while( 1 ) {
		if ( checkForEnemy( true ) ) {
			setState( "state_Combat" );
		}

		lookAt( $player1, 0.1 );

		if ( AI_PUSHED ) {
			getOutOfWay();
		}

		result = checkDestinationDistance();
		if ( result == PLAYER_LOST ) {
			setState( "state_FindPlayer" );
		} else if ( result == PLAYER_AHEAD_OF_ME ) {
			setState( "state_Lead" );
		} else if ( ( result == PLAYER_OK ) && ( playerMoveState == APPROACHING ) ) {
			setState( "state_Lead" );
		} else  if ( ( sys.getTime() > getCloserTime ) && ( result == PLAYER_BEHIND_ME ) ) {
			setState( "state_GetCloser" );
		}
		waitFrame();
	}
}

/*
=====================
char_sentry::state_GetCloser
=====================
*/
void char_sentry::state_GetCloser() {
	while( 1 ) {
		moveToPosition( lastValidPlayerPosition );
		if ( AI_MOVE_DONE ) {
			if ( !canReachEntity( $player1 ) ) {
				setState( "state_CantReachPlayer" );
			}
		}

		if ( checkForEnemy( true ) ) {
			setState( "state_Combat" );
		}

		checkBlocked();

		float result = checkDestinationDistance();
		
		if ( result == PLAYER_LOST ) {
			setState( "state_FindPlayer" );
		}
        if ( result == PLAYER_OK ) {
			setState( "state_Wait" );
		}
		waitFrame();
	}
}

/*
=====================
char_sentry::state_CantReachPlayer
=====================
*/
void char_sentry::state_CantReachPlayer() {
	startSound( "snd_cant_reach_player", SND_CHANNEL_VOICE, false );
	stopMove();
	while( 1 ) {
		if ( canReachEntity( $player1 ) ) {
			setState( "state_Wait" );
		}
		if ( checkForEnemy( true ) ) {
			setState( "state_Combat" );
		}
		waitFrame();
	}
}

/*
=====================
char_sentry::state_Lead
=====================
*/
void char_sentry::state_Lead() {
	float	nextLookTime;
	float	leadTimeOut;

	if ( !current_path ) {
		setState( "state_Idle" );
	}

	nextLookTime = RandomDelay( SENTRY_MIN_LOOK_DELAY, SENTRY_MAX_LOOK_DELAY );
	moveToEntity( current_path );

	leadTimeOut = sys.getTime() + SENTRY_LEAD_TIMEOUT;

	while( 1 ) {
		if ( checkForEnemy( true ) ) {
			setState( "state_Combat" );
		}

		if ( !light_on && ( sys.getTime() > nextLookTime ) ) {
			lookAt( $player1, SENTRY_LOOK_TIME );
			nextLookTime = RandomDelay( SENTRY_MIN_LOOK_DELAY, SENTRY_MAX_LOOK_DELAY );
		}

		checkBlocked();
		
		float result = checkDestinationDistance();
		if ( ( result != PLAYER_BEHIND_ME ) && ( result != PLAYER_LOST ) ) {
			leadTimeOut = sys.getTime() + SENTRY_LEAD_TIMEOUT;
		} else if ( sys.getTime() > leadTimeOut ) {
			setState( "state_Wait" );
		} else if ( !light_on ) {
			// look back at player to see what he's up to
			lookAt( $player1, 0.1 );
			nextLookTime += 0.1;
		}

		if ( AI_MOVE_DONE ) {
			if ( AI_DEST_UNREACHABLE ) {
				waitFrame();
				setState( "state_FindPlayer" );
			}
			setNeverDormant( getFloatKey( "neverdormant" ) );
			finishPathCommand();
			setState( "state_Idle" );
		}
		
		waitFrame();
	}
}

/*
=====================
char_sentry::state_FindPlayer
=====================
*/
void char_sentry::state_FindPlayer() {
	while( 1 ) {
		moveToPosition( lastValidPlayerPosition );
		if ( AI_MOVE_DONE || AI_DEST_UNREACHABLE ) {
			if ( !canReachEntity( $player1 ) ) {
				setState( "state_CantReachPlayer" );
			}
		}

		if ( checkForEnemy( true ) ) {
			waitFrame();
			setState( "state_Combat" );
		}

		checkBlocked();
		
		float result = checkDestinationDistance();
		if ( canSee( $player1 ) && ( ( result == PLAYER_BEHIND_ME ) || ( result == PLAYER_OK ) ) ) {
			setState( "state_Wait" );
		} else if ( result == PLAYER_AHEAD_OF_ME ) {
			setState( "state_Lead" );
		}
		waitFrame();
	}
}

/*
=====================
char_sentry::state_Combat
=====================
*/
void char_sentry::state_Combat() {
	float attack_flags;

	eachFrame {
		faceEnemy();
		lookAtEnemy( 1 );
		
		if ( AI_ENEMY_DEAD || !getEnemy() ) {
			startSound( "snd_target_lost", SND_CHANNEL_VOICE, false );
			AI_ENEMY_DEAD = false;
			clearEnemy();
			setState( "state_Idle" );
		}
		
		attack_flags = check_attacks();
		if ( attack_flags ) {
			do_attack( attack_flags );
			continue;
		}
		
		if ( canReachEnemy() ) {
			combat_chase();
		} else if ( !find_attack_position() ) {
			ignore( getEnemy() );
			checkForEnemy( false );
		}
		
		waitFrame();
	}
}

/*
=====================
char_sentry::combat_chase
=====================
*/
void char_sentry::combat_chase() {
	float attack_flags;
	
	moveToEnemy();
	while( !AI_DEST_UNREACHABLE ) {
		if ( AI_ENEMY_DEAD ) {
			startSound( "snd_target_lost", SND_CHANNEL_VOICE, false );
			AI_ENEMY_DEAD = false;
			clearEnemy();
			setState( "state_Idle" );
		}

		if ( AI_MOVE_DONE ) {
			if ( !enemyPositionValid() ) {
				clearEnemy();
				setState( "state_Idle" );
			}
			moveToEnemy();
		}
		
		lookAtEnemy( 1 );

		attack_flags = check_attacks();
		if ( attack_flags ) {
			do_attack( attack_flags );
			return;
		}
		
		waitFrame();
	}
	stopMove();
}

/*
=====================
char_sentry::find_attack_position
=====================
*/
boolean char_sentry::find_attack_position() {
	float attack_flags;

	if ( sys.getTime() < nextPositionSearch ) {
		return false;
	}
	nextPositionSearch = sys.getTime() + 1;
	locateEnemy();
	moveToAttackPosition( getEnemy(), "range_attack_loop" );
	if ( AI_DEST_UNREACHABLE ) {
		return false;
	}

	while( !AI_MOVE_DONE ) {
		if ( AI_ENEMY_DEAD ) {
			startSound( "snd_target_lost", SND_CHANNEL_VOICE, false );
			AI_ENEMY_DEAD = false;
			clearEnemy();
			setState( "state_Idle" );
		}

		lookAtEnemy( 1 );

		attack_flags = check_attacks();
		if ( attack_flags ) {
			do_attack( attack_flags );
			return true;
		}
		
		waitFrame();
	}
	stopMove();

	return true;
}


/*
=====================
char_sentry::do_attack
=====================
*/
void char_sentry::do_attack( float attack_flags ) {
	if ( attack_flags & ATTACK_MISSILE ) {
		combat_range();
	}
}

/*
=====================
char_sentry::check_attacks
=====================
*/
float char_sentry::check_attacks() {
	float attack_flags = 0;
	
	if ( canHitEnemyFromAnim( "range_attack_loop" ) ) {
		attack_flags |= ATTACK_MISSILE;
	}
	
	return attack_flags;
}

/*
=====================
char_sentry::combat_range
=====================
*/
void char_sentry::combat_range() {
	faceEnemy();
	fire = true;
	while( canHitEnemyFromAnim( "range_attack_loop" ) ) {
		waitFrame();
	}
	fire = false;
	waitUntil( !inAnimState( ANIMCHANNEL_TORSO, "Torso_RangeAttack" ) );
}
