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

character.script

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

#define CHAR_TALK_TRIGGERED			0
#define CHAR_TALK_PRIMARY			1
#define CHAR_TALK_SECONDARY			2
#define CHAR_WALKTURN				65
#define CHAR_MIN_TURN				10
#define CHAR_TURN_THRESHOLD			45
#define CHAR_GETOUTOFWAY_DIST		124
#define CHAR_ALLOW_WALK_DIST		180
#define CHAR_ALLOW_WALK_DIST_NPC	80
#define CHAR_WAIT_BLOCKED			6
#define CHAR_PUSH_DELAY				0.35

#define CHAR_BLEND_COWER_TO_IDLE	6
#define CHAR_BLEND_TURN_TO_IDLE		0

object character : ai {
	entity		head;
	float		talkMode;
	float		talk_secondary_index;
	string		customAnim;
	float		customBlendOut;
	string		walkAnim;
	boolean		allow_turn;
	boolean		run;
	boolean		can_talk;
	boolean		no_cower;
	boolean		no_cower_saved;
	boolean		ignore_push;
	boolean		skipOutOfPath;
	character	listening_to_character;
	character	next_listener;
	float		lastPushTime;
	float		pushTime;

	entity		current_path;
	entity		next_path;
	
	boolean		follow_nodes;
	boolean		dont_push_others;

	float		playHeadAnim( string animname, float blend_frames );
	void		endHeadAnim( float blend_out );
	
	void		executePathCommand( entity pathnode );
	void		finishPathCommand();
	void		cancelPathCommand();

	void		checkBlocked();
	boolean		getOutOfWay();

	//
	// States
	//
	void		state_Begin();
	void		state_TalkTrigger();
	void		state_Idle();
	void		state_Killed();

	//
	// actions
	//
	void		idle_stand();
	void		idle_followPathEntities( entity pathnode );
	void		idle_talk();

	void		init();
	void		destroy();
	void		faceTowardsEntity( entity target, float duration );
	
	void		playCustomCycle( string animname, float blendTime );
	void		playCustomAnim( string animname, float blendIn, float blendOut );
	boolean		inCustomAnim();
	void		endCustomAnim();
	void		waitForCustomAnim( string animname, float blendIn, float blendOut );

	void		state_Spawner();
	void		state_Cower();
	void		check_cower();
	void		target_talk();
	void		say_triggered();
	void		say_primary();
	void		say_secondary();

	void		skip_conversation();

	void		disable_turning();
	void		enable_turning();

	// path following
	void 		path_corner();
	void 		path_anim();
	void 		path_cycleanim();
	void 		path_turn();
	void 		path_wait();
	void 		path_waitfortrigger();
	void 		path_hide();
	void 		path_show();
	void		path_conversation_listen();
	void		path_conversation();
	void		path_headanim();
	void 		path_talk();
	void 		path_talk_triggered();
	void 		path_talk_primary();
	void 		path_talk_secondary();
	void		path_lookat();
	void 		state_FollowAlternatePath();
	void 		follow_alternate_path1();
	void 		follow_alternate_path2();
	void 		follow_alternate_path3();

	// head anim states
	void		Anim_Disable();
	void		Head_Idle();
	void		Head_Dead();
	void		Head_TalkHeadOnly();

	// legs anim states
	void		Legs_Death();
	void		Legs_Idle();
	void		Legs_Walk();
	void		Legs_Walk_Special();
	void		Legs_Run();
	void		Legs_TurnLeft();
	void		Legs_TurnRight();
	void		Legs_Cower();
	void		Legs_CustomAnim();
	void		Legs_CustomCycle();
};

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

	Head animation control

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

void character::Anim_Disable() {
}

void character::Head_Idle() {
	idleAnim( ANIMCHANNEL_HEAD, "stand" );
}

void character::Head_Dead() {
	playAnim( ANIMCHANNEL_HEAD, "dead" );
}

void character::Head_TalkHeadOnly() {
	playAnim( ANIMCHANNEL_HEAD, customAnim );
	while( !animDone( ANIMCHANNEL_HEAD, customBlendOut ) ) {
		waitFrame();
	}
	finishAction( "talkAnim" );
	animState( ANIMCHANNEL_HEAD, "Head_Idle", customBlendOut );
}

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

	Legs animation control

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

void character::Legs_Death() {
	finishAction( "dead" );
}

void character::Legs_Idle() {
	float delta;

	if ( allow_turn && !AI_FORWARD ) {
		delta = getTurnDelta();
		if ( delta > CHAR_MIN_TURN ) {
			Legs_TurnLeft();
		}
		if ( delta < -CHAR_MIN_TURN ) {
			Legs_TurnRight();
		}
	}
	
	allowMovement( false );
	idleAnim( ANIMCHANNEL_LEGS, "stand" );

	eachFrame {
		if ( AI_FORWARD ) {
			if ( walkAnim != "" ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Walk_Special", 8 );
			} else if ( run ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Run", 8 );
			} else {
				animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
			}
		}
		
		if ( allow_turn ) {
			if ( getTurnDelta() > CHAR_MIN_TURN ) {
				animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", 4 );
			}
			if ( getTurnDelta() < -CHAR_MIN_TURN ) {
				animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", 4 );
			}
		}
	}
}

void character::Legs_Walk() {
	float delta;

	allowMovement( true );
	playCycle( ANIMCHANNEL_LEGS, "walk" );

	while( AI_FORWARD && ( walkAnim == "" ) ) {
		if ( run ) {
			delta = getTurnDelta();
			if ( ( delta < CHAR_WALKTURN ) && ( delta > -CHAR_WALKTURN ) ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Run", 8 );
			}
		}
		waitFrame();
	}
	
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
}

void character::Legs_Walk_Special() {
	allowMovement( true );
	playCycle( ANIMCHANNEL_LEGS, walkAnim );
	
	while( AI_FORWARD && ( walkAnim != "" ) )	{
		waitFrame();
	}
	
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 8 );
}

void character::Legs_Run() {
	float delta;

	allowMovement( true );
	playCycle( ANIMCHANNEL_LEGS, "run" );
	
	while( AI_FORWARD && run && ( walkAnim == "" ) ) {
		delta = getTurnDelta();
		if ( ( delta > CHAR_WALKTURN ) || ( delta < -CHAR_WALKTURN ) ) {
			animState( ANIMCHANNEL_LEGS, "Legs_Walk", 8 );
		}

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

void character::Legs_TurnLeft() {
	float delta;

	allowMovement( true );
	animTurn( 180 );
	playAnim( ANIMCHANNEL_LEGS, "turn_left" );
	while( !animDone( ANIMCHANNEL_LEGS, CHAR_BLEND_TURN_TO_IDLE ) ) {
		waitFrame();
	}
	animTurn( 0 );
	
	if ( allow_turn && !AI_FORWARD ) {
		delta = getTurnDelta();
		if ( delta > CHAR_MIN_TURN ) {
			animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", CHAR_BLEND_TURN_TO_IDLE );
		}
		if ( delta < -CHAR_MIN_TURN ) {
			animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", CHAR_BLEND_TURN_TO_IDLE );
		}
	}

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

void character::Legs_TurnRight() {
	float delta;

	allowMovement( true );
	animTurn( 180 );
	playAnim( ANIMCHANNEL_LEGS, "turn_right" );
	while( !animDone( ANIMCHANNEL_LEGS, CHAR_BLEND_TURN_TO_IDLE ) ) {
		waitFrame();
	}
	animTurn( 0 );

	if ( allow_turn && !AI_FORWARD ) {
		delta = getTurnDelta();
		if ( delta > CHAR_MIN_TURN ) {
			animState( ANIMCHANNEL_LEGS, "Legs_TurnLeft", CHAR_BLEND_TURN_TO_IDLE );
		}
		if ( delta < -CHAR_MIN_TURN ) {
			animState( ANIMCHANNEL_LEGS, "Legs_TurnRight", CHAR_BLEND_TURN_TO_IDLE );
		}
	}

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

void character::Legs_Cower() {
	setTalkTarget( $null_entity );
	startSound( "snd_cower", SND_CHANNEL_VOICE, false );
	playAnim( ANIMCHANNEL_LEGS, "cower" );
	
	while( !animDone( ANIMCHANNEL_LEGS, CHAR_BLEND_COWER_TO_IDLE ) ) {
		lookAt( $player1, 0.1 );
		waitFrame();
	}
	
	setTalkTarget( $null_entity );
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", CHAR_BLEND_COWER_TO_IDLE );
}

void character::Legs_CustomCycle() {
	allowMovement( true );
	playCycle( ANIMCHANNEL_LEGS, customAnim );
	while( 1 ) {
		waitFrame();
	}
}

void character::Legs_CustomAnim() {
	allowMovement( true );
	playAnim( ANIMCHANNEL_LEGS, customAnim );
	
	while( !animDone( ANIMCHANNEL_LEGS, customBlendOut ) ) {
		waitFrame();
	}
	
	finishAction( "customAnim" );
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", customBlendOut );
}

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

	AI

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

/*
=====================
character::init
=====================
*/
void character::init() {
	float mod;

	mod = getIntKey( "head_look" );
	setBoneMod( mod );

	lastPushTime = 0;
	pushTime = 0;

	dont_push_others = getIntKey( "dont_push_others" );

	talkMode = CHAR_TALK_PRIMARY;
	talk_secondary_index = 1;
	follow_nodes = true;
	walkAnim = "";
	run = false;

	can_talk = getIntKey( "talks" );
	no_cower = getIntKey( "no_cower" );
	ignore_push = getIntKey( "ignore_push" );
	no_cower_saved = no_cower;
	if ( getFloatKey( "turn_rate" ) != 0 ) {
		allow_turn = true;
	} else {
		ignore_push = true;
	}

	self.setKey( "conversationNext", "" );
	
	if ( getIntKey( "spawner" ) ) {
		setNextState( "state_Spawner" );
	} else {
		setNextState( "state_Begin" );
	}
}

/*
=====================
character::destroy
=====================
*/
void character::destroy() {
	character conversation_char;

	// disconnect from anyone we're listening to
	if ( listening_to_character ) {
		conversation_char = listening_to_character;
        while( conversation_char ) {
			if ( self == conversation_char.next_listener ) {
				// point the previous one in the list to the our next listener
				conversation_char.next_listener = next_listener;
				break;
			}

			conversation_char = conversation_char.next_listener;
		}
	}
}

/*
=====================
character::state_Spawner
=====================
*/
void character::state_Spawner() {
	entity	ent;
	float	triggerCount;
	float	maxSpawn;
	float	i;
	string	name;

	maxSpawn = getIntKey( "spawner" );
	name = getName();

	hide();

	triggerCount = 0;
	AI_ACTIVATED = false;
	while( 1 ) {
		if ( AI_ACTIVATED ) {
			triggerCount++;
			AI_ACTIVATED = false;
		}

		if ( triggerCount ) {
			if ( canBecomeSolid() ) {
				for( i = 0; i < maxSpawn; i++ ) {
					ent = sys.getEntity( name + "_" + i );
					if ( !ent ) {
						break;
					}
				}

				if ( i < maxSpawn ) {
					triggerCount--;
					sys.copySpawnArgs( self );
					sys.setSpawnArg( "spawner", "0" );
					sys.setSpawnArg( "name", name + "_" + i );
					if ( getKey( "spawn_target" ) != "" ) {
						sys.setSpawnArg( "target", getKey( "spawn_target" ) );
					}
					ent = sys.spawn( getKey( "classname" ) );
					sys.trigger( ent );
				}
			}
		}
		waitFrame();
	}
}

/*
=====================
character::state_Cower
=====================
*/
void character::state_Cower() {
	float waittime;

	stopSound( SND_CHANNEL_VOICE, false );
	if ( head ) {
		head.stopSound( SND_CHANNEL_VOICE, false );
	}

	endHeadAnim( 6 );
	animState( ANIMCHANNEL_LEGS, "Legs_Cower", 6 );
	while( inAnimState( ANIMCHANNEL_LEGS, "Legs_Cower" ) ) {
		waitFrame();
	}

	setTalkTarget( $null_entity );
	setState( "state_Idle" );
}

/*
=====================
character::check_cower
=====================
*/
void character::check_cower() {
	if ( !no_cower ) {
		if ( heardSound( false ) ) {
			endHeadAnim( 6 );
			cancelPathCommand();
			setState( "state_Cower" );
		}
	}
}

/*
=====================
character::target_talk

forces character to talk to the player
=====================
*/
void character::target_talk() {
	cancelPathCommand();
	setTalkTarget( $player1 );
	idle_talk();
	setState( "state_Idle" );
}

/*
=====================
character::say_triggered

forces character to say his triggered talk anim to the player
=====================
*/
void character::say_triggered() {
	cancelPathCommand();
	talkMode = CHAR_TALK_TRIGGERED;
	setTalkTarget( $player1 );
	setState( "state_Idle" );
}

/*
=====================
character::say_primary

forces character to say his primary talk anim to the player
=====================
*/
void character::say_primary() {
	cancelPathCommand();
	talkMode = CHAR_TALK_PRIMARY;
	setTalkTarget( $player1 );
	setState( "state_Idle" );
}

/*
=====================
character::say_secondary

forces character to say his secondary talk anim to the player
=====================
*/
void character::say_secondary() {
	cancelPathCommand();
	talkMode = CHAR_TALK_SECONDARY;
	setTalkTarget( $player1 );
	setState( "state_Idle" );
}

/*
=====================
character::faceTowardsEntity
=====================
*/
void character::faceTowardsEntity( entity target, float duration ) {
	vector delta;
	vector ang1;
	vector ang2;
	vector ang3;

	delta = target.getOrigin();
	delta = delta - getOrigin();
	ang1 = sys.VecToAngles( delta );
	ang2 = getAngles();
	ang3 = anglemod180( ang1 - ang2 );
	if ( ang3_y < -CHAR_TURN_THRESHOLD ) {
		turnTo( ang1_y );
	} else if ( ang3_y > CHAR_TURN_THRESHOLD ) {
		turnTo( ang1_y );
	}
	
	lookAt( target, duration );
}

/*
=====================
character::playCustomCycle
=====================
*/
void character::playCustomCycle( string animname, float blendTime ) {
	customAnim = animname;
	animState( ANIMCHANNEL_LEGS, "Legs_CustomCycle", blendTime );
}

/*
=====================
character::playCustomAnim
=====================
*/
void character::playCustomAnim( string animname, float blendIn, float blendOut ) {
	customBlendOut = blendOut;
	customAnim = animname;
	animState( ANIMCHANNEL_LEGS, "Legs_CustomAnim", blendIn );
}

/*
=====================
character::inCustomAnim
=====================
*/
boolean character::inCustomAnim() {
	return inAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim" );
}

/*
=====================
character::endCustomAnim
=====================
*/
void character::endCustomAnim() {
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", customBlendOut );
}

/*
=====================
character::waitForCustomAnim
=====================
*/
void character::waitForCustomAnim( string animname, float blendIn, float blendOut ) {
	check_cower();
	customBlendOut = blendOut;
	customAnim = animname;
	animState( ANIMCHANNEL_LEGS, "Legs_CustomAnim", blendIn );
	while( inAnimState( ANIMCHANNEL_LEGS, "Legs_CustomAnim" ) ) {
		check_cower();
		waitFrame();
	}
}

/*
=====================
character::endHeadAnim
=====================
*/
void character::endHeadAnim( float blend_out ) {
	if ( getHead() ) {
		stopAnim( ANIMCHANNEL_HEAD, blend_out );
	}
	setBlendFrames( ANIMCHANNEL_LEGS, blend_out );
	idleAnim( ANIMCHANNEL_LEGS, "stand" );

	animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
	if ( getHead() ) {
		animState( ANIMCHANNEL_HEAD, "Head_Idle", blend_out );
	}
}

/*
=====================
character::playHeadAnim
=====================
*/
float character::playHeadAnim( string animname, float blend_frames ) {
	string anim;
	float headlength;
	float bodylength;

	animState( ANIMCHANNEL_LEGS, "Anim_Disable", blend_frames );
	setBlendFrames( ANIMCHANNEL_LEGS, blend_frames );
	idleAnim( ANIMCHANNEL_LEGS, "stand" );
	waitFrame();

	anim = chooseAnim( ANIMCHANNEL_HEAD, animname );
	if ( anim != "" ) {
		setBlendFrames( ANIMCHANNEL_HEAD, blend_frames );
		playAnim( ANIMCHANNEL_HEAD, anim );
		headlength = animLength( ANIMCHANNEL_HEAD, anim );

		anim = chooseAnim( ANIMCHANNEL_LEGS, animname );
		if ( anim == "" ) {
			animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_frames );
		} else {
			bodylength = animLength( ANIMCHANNEL_LEGS, anim );
			if ( bodylength < headlength ) {
				playCycle( ANIMCHANNEL_LEGS, anim );
			} else {
				playAnim( ANIMCHANNEL_LEGS, anim );
			}
		}

		return ANIMCHANNEL_HEAD;
	} else {
		// play on legs
		if ( blend_frames >= 0 ) {
			setBlendFrames( ANIMCHANNEL_LEGS, blend_frames );
		}
		anim = chooseAnim( ANIMCHANNEL_LEGS, animname );
		if ( anim == "" ) {
			playAnim( ANIMCHANNEL_LEGS, "stand" );
		} else {
			playAnim( ANIMCHANNEL_LEGS, animname );
		}
		return ANIMCHANNEL_LEGS;
	}
}

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

	States

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

/*
=====================
character::state_Begin
=====================
*/
void character::state_Begin() {
	float	doHide;
	float	teleportType;
	string	triggerAnim;
	float	movetype;
	
	head = getHead();

	talkMode = CHAR_TALK_PRIMARY;
	follow_nodes = true;
	walkAnim = "";
	run = false;

	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 );
	if ( getHead() ) {
		animState( ANIMCHANNEL_HEAD, "Head_Idle", 0 );
	}

	doHide = getIntKey( "hide" );
	teleportType = getIntKey( "teleport" );
	triggerAnim = getKey( "trigger_anim" );
	if ( triggerAnim != "" ) {
		//
		// hide until triggered and then play a special animation
		//
		checkAnim( ANIMCHANNEL_LEGS, triggerAnim );
		hide();
		waitUntil( AI_ACTIVATED );
		clearEnemy();
		waitUntil( canBecomeSolid() );
		show();
		waitForCustomAnim( triggerAnim, 0, 4 );
	} else if ( teleportType > 0 ) {
		//
		// teleport in when triggered
		//
		hide();
		AI_ACTIVATED = false;	
		waitUntil( AI_ACTIVATED );
		clearEnemy();
		waitUntil( canBecomeSolid() );
		becomeSolid();
		movetype = getMoveType();
		setMoveType( MOVETYPE_STATIC );
		if ( teleportType == 1 ) {
			startFx( "fx/teleporter1.fx" );
			wait( 1.6 );
		} else if ( teleportType == 2 ) {
			startFx( "fx/teleporter2.fx" );
			wait( 2.6 );
		} else if ( teleportType == 3 ) {
			startFx( "fx/teleporter3.fx" );
			wait( 3.6 );
		} else {
			startFx( "fx/teleporter.fx" );
			wait( 0.6 );
		}
		show();
		playCustomAnim( "teleport", 0, 4 );
		waitAction( "customAnim" );
		setMoveType( movetype );
	} else if ( doHide ) {
		//
		// hide until triggered
		//
		hide();
		AI_ACTIVATED = false;	
		waitUntil( AI_ACTIVATED );
		if ( doHide == 1 ) {
			AI_ACTIVATED = false;
			clearEnemy();
		}
		waitUntil( canBecomeSolid() );
		show();
	}

	if ( getIntKey( "trigger" )  ) {
		AI_ACTIVATED = false;
		waitUntil( AI_ACTIVATED );
	}

	float waittime;
	waittime = getFloatKey( "wait" );
	if ( waittime > 0 ) {
		sys.wait( waittime );
	}
	
	if ( getIntKey( "talktrigger" )  ) {
		setState( "state_TalkTrigger" );
	} else {
		setState( "state_Idle" );
	}
}

/*
=====================
character::state_TalkTrigger
=====================
*/
void character::state_TalkTrigger() {
	string animname;
	entity activator;

	animname = getKey( "anim" );
	if ( animname != "" ) {
		playCustomCycle( animname, 0 );
	}

	while( !AI_ACTIVATED ) {
		check_cower();
		waitFrame();
	}

	animState( ANIMCHANNEL_LEGS, "Legs_Idle", 12 );
	
	setTalkTarget( $player1 );
	talkMode = CHAR_TALK_TRIGGERED;
	
	setState( "state_Idle" );
}
	
/*
=====================
character::state_Idle
=====================
*/
void character::state_Idle() {
	entity node;
	
	if ( can_talk ) {
		setTalkState( TALK_OK );
	}
	no_cower = no_cower_saved;

	while( 1 ) {
		if ( AI_TALK ) {
			idle_talk();
			continue;
		}
		
		if ( follow_nodes ) {
			if ( !current_path ) {
				current_path = randomPath();
			}
			if ( current_path ) {
				idle_followPathEntities( current_path );
                if ( getIntKey( "follow_once" ) ) {
					follow_nodes = false;
				}
				continue;
			}
		}

        idle_stand();
	}
}

/*
=====================
character::state_Killed
=====================
*/
void character::state_Killed() {
	stopMove();
	animState( ANIMCHANNEL_LEGS, "Legs_Death", 0 );
	if ( getHead() ) {
		animState( ANIMCHANNEL_HEAD, "Head_Dead", 0 );
	}
	stopThinking();
}

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

	Idle

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

/*
=====================
character::idle_stand
=====================
*/
void character::idle_stand() {
	stopMove();
	while( !AI_TALK ) {
		check_cower();
		if ( AI_PUSHED ) {
			getOutOfWay();
		}

		waitFrame();
	}
}

/*
=====================
character::idle_talk
=====================
*/
void character::idle_talk() {
	float	ang;
	float	talktime;
	float	endtime;
	float	talkradius;
	float	blendin;
	float	blendout;
	entity	target;
	vector	originalAngles;
	float	currentTime;
	boolean	no_turn;
	entity  talkTo;
	float	channel;

	talkTo = getTalkTarget();
	
	no_turn = getIntKey( "talk_no_turn" );

	originalAngles = getAngles();
	stopMove();

	if ( !no_turn && allow_turn ) {
		faceTowardsEntity( talkTo, 0.1 );
		sys.wait( 0.5 );
		// wait for turn anim to finish
		while( !inAnimState( ANIMCHANNEL_LEGS, "Legs_Idle" ) ) {
			if ( talkMode != CHAR_TALK_TRIGGERED ) {
				check_cower();
			}
			lookAt( talkTo, 0.1 );
			waitFrame();
		}
	}	

	allowMovement( true );

	talkradius = getFloatKey( "talkradius" );
	if ( talkMode == CHAR_TALK_TRIGGERED ) {
		// triggered response
		blendin = self.getIntKey( "talk0_blendin" );
		blendout = self.getIntKey( "talk0_blendout" );
		channel = playHeadAnim( "talk_trigger", blendin );
		talkMode = CHAR_TALK_PRIMARY;
	} else if ( talkMode == CHAR_TALK_PRIMARY ) {
		// primary response
		blendin = self.getIntKey( "talk1_blendin" );
		blendout = self.getIntKey( "talk1_blendout" );
		channel = playHeadAnim( "talk_primary", blendin );
		talkMode = CHAR_TALK_SECONDARY;
	} else if ( talkMode == CHAR_TALK_SECONDARY ) {
		// secondary response
		blendin = self.getIntKey( "talk2_blendin" );
		blendout = self.getIntKey( "talk2_blendout" );
		channel = playHeadAnim( "talk_secondary" + talk_secondary_index, blendin );
		talk_secondary_index = talk_secondary_index + 1;
		if ( !hasAnim( ANIMCHANNEL_HEAD, "talk_secondary" + talk_secondary_index ) && !hasAnim( ANIMCHANNEL_LEGS, "talk_secondary" + talk_secondary_index ) ) {
			talk_secondary_index = 1;
		}
	}

	while( !animDone( channel, blendout ) ) {
		if ( distanceTo( talkTo ) > talkradius ) {
			// stop talking
			stopSound( SND_CHANNEL_VOICE, false );
			if ( head ) {
				head.stopSound( SND_CHANNEL_VOICE, false );
			}
			break;
		}

		check_cower();
		if ( no_turn || !allow_turn ) {
			lookAt( talkTo, 0.1 );
		} else {
			faceTowardsEntity( talkTo, 0.1 );
		}
		waitFrame();
	}
	endHeadAnim( blendout );
	setTalkTarget( $null_entity );
	
	talktime = getFloatKey( "talktime" );
	currentTime = sys.getTime();
	endtime = currentTime + talktime;
	while( ( currentTime < endtime ) && ( distanceTo( talkTo ) <= talkradius ) ) {
		check_cower();
		if ( no_turn || !allow_turn ) {
			lookAt( talkTo, 0.1 );
		} else {
			faceTowardsEntity( talkTo, 0.1 );
		}
		if ( AI_TALK ) {
			allowMovement( false );
			return;
		}
		if ( AI_PUSHED ) {
			getOutOfWay();
		}
		waitFrame();
		currentTime = sys.getTime();
	}

	lookAt( $null_entity, 0 );
	turnTo( originalAngles_y );
	allowMovement( false );
}

/*
=====================
character::idle_followPathEntities
=====================
*/
void character::idle_followPathEntities( entity pathnode ) {
	current_path = pathnode;
	while( current_path ) {
		check_cower();
		executePathCommand( current_path );
		if ( AI_TALK ) {
			setState( "state_Idle" );
		}
	}
}

/*
=====================
character::executePathCommand
=====================
*/
void character::executePathCommand( entity pathnode ) {
	string nodeaction;

	current_path = pathnode;
	next_path = current_path.randomPath();
	nodeaction = current_path.getKey( "classname" );
	if ( !hasFunction( nodeaction ) ) {
		sys.warning( "'" + getName() + "' encountered an unsupported path entity '" + nodeaction + "' on entity '" + current_path.getName() + "'\n" );
		return;
	}

	//sys.print( getName() + " : " + current_path.getName() + " : " + nodeaction + "\n" );

	skipOutOfPath = current_path.getIntKey( "skip" );

	if ( current_path.getIntKey( "no_cower" ) ) {
		no_cower = true;
	}
	if ( current_path.getIntKey( "no_talk" ) && can_talk ) {
		setTalkState( TALK_BUSY );
	}

	callFunction( nodeaction );

	if ( AI_TALK ) {
		cancelPathCommand();
		setState( "state_Idle" );
	}

	finishPathCommand();
}

/*
=====================
character::finishPathCommand
=====================
*/
void character::finishPathCommand() {
	string triggername;
	entity triggerent;

	// 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 = next_path;

	skipOutOfPath = false;
	if ( can_talk ) {
		setTalkState( TALK_OK );
	}
	no_cower = no_cower_saved;
}

/*
=====================
character::cancelPathCommand
=====================
*/
void character::cancelPathCommand() {
	if ( skipOutOfPath ) {
		finishPathCommand();
	}

	skipOutOfPath = false;
	if ( can_talk ) {
		setTalkState( TALK_OK );
	}
	no_cower = no_cower_saved;
}

/*
=====================
character::getOutOfWay
=====================
*/
boolean character::getOutOfWay() {
	vector org;
	vector oldorigin;
	vector pos;
	vector ang;
	vector dir;

	if ( ignore_push ) {
		return false;
	}

	// check if we were pushed in the last frame
	if ( ( sys.getTime() - lastPushTime ) > GAME_FRAMETIME ) {
		// start the push timer
		lastPushTime = sys.getTime();
		pushTime = lastPushTime + CHAR_PUSH_DELAY;
		return false;
	}

	// update the last push time so next frame we know we were pushed this frame
	lastPushTime = sys.getTime() + GAME_FRAMETIME;

	// check if we've been pushed long enough
	if ( sys.getTime() < pushTime ) {
		return false;
	}

	// choose a point inside the aas
	oldorigin = getOrigin();
	org = pushPointIntoAAS( oldorigin );

	// determine if there's a direction we can walk
	setOrigin( org );
	for( ang_y = 0; ang_y < 360; ang_y += 45 ) {
		dir = sys.angToForward( ang );
		pos = pushPointIntoAAS( org + dir * CHAR_GETOUTOFWAY_DIST );
		if ( testMoveToPosition( pos ) ) {
			break;
		}
	}
	setOrigin( oldorigin );

	if ( ang_y >= 360 ) {
		// no direction that we can move in, so don't do anything
		return false;
	}

	// make sure we're inside the aas
	moveToPosition( org );
	while( !AI_MOVE_DONE ) {
		check_cower();
		if ( AI_TALK ) {
			setState( "state_Idle" );
		}
		waitFrame();
	}

	turnToPos( pos );

	// slide him the rest of the way to make sure we're fully in the aas
	if ( org != getOrigin() ) {
		slideTo( org, 0.2 );
		while( !AI_MOVE_DONE ) {
			check_cower();
			if ( AI_TALK ) {
				setState( "state_Idle" );
			}
			waitFrame();
		}
	}

	// move out of the way
	moveToPosition( pos );
	while( !AI_MOVE_DONE ) {
		check_cower();
		if ( AI_TALK ) {
			setState( "state_Idle" );
		}
		waitFrame();
	}
	
	// turn around so we face the way we came
	turnTo( getCurrentYaw() + 180 );

	stopMove();

	return true;
}

/*
=====================
character::checkBlocked
=====================
*/
void character::checkBlocked() {
	entity obstacle;
	vector playerMoveDir;
	character npc;
	float allow_walk_dist;
	float end_time;
	boolean waitUntilPathClear;
	float channel;
	vector obstacle_pos;
	vector delta;

	if ( dont_push_others ) {
		if ( moveStatus() >= MOVE_STATUS_BLOCKED_BY_OBJECT ) {
			// just wait for the path to be clear
			obstacle = getObstacle();

			if ( moveStatus() == MOVE_STATUS_BLOCKED_BY_OBJECT ) {
				kickObstacles( getObstacle(), 60 );
				return;
			}

			obstacle_pos = obstacle.getOrigin();
			saveMove();
			stopMove();
			while( 1 ) {
				delta = obstacle_pos - obstacle.getOrigin();
				delta_z = 0;
				if ( sys.vecLength( delta ) > 4 ) {
					restoreMove();
					if ( moveStatus() < MOVE_STATUS_BLOCKED_BY_OBJECT ) {
						break;
					}
					if ( getObstacle() != obstacle ) {
						break;
					}
					stopMove();
				}
				check_cower();
				if ( AI_PUSHED ) {
					getOutOfWay();
				}
				if ( AI_TALK ) {
					setState( "state_Idle" );
				}
				waitFrame();
			}
		}
		return;
	}

	waitUntilPathClear = false;
	while( moveStatus() >= MOVE_STATUS_BLOCKED_BY_OBJECT ) {
		obstacle = getObstacle();
		npc = obstacle;
		if ( npc ) {
			if ( npc.ignore_push ) {
				// can't push this guy
				break;
			}
		} else if ( moveStatus() == MOVE_STATUS_BLOCKED_BY_OBJECT ) {
			kickObstacles( getObstacle(), 60 );
			break;
		}

		obstacle_pos = obstacle.getOrigin();

		saveMove();
		stopMove();

		channel = playHeadAnim( "talk_excuseme", 8 );

		// prevent an infinite loop if we don't have an excuse me anim
		waitFrame();

		while( !animDone( channel, 8 ) ) {
			check_cower();
			faceTowardsEntity( obstacle, 0.1 );
			waitFrame();
		}
		endHeadAnim( 8 );

		if ( npc ) {
			npc.AI_PUSHED = true;
			npc.lastPushTime = sys.getTime();
			npc.pushTime = sys.getTime();
			allow_walk_dist = CHAR_ALLOW_WALK_DIST_NPC;
		} else {
			allow_walk_dist = CHAR_ALLOW_WALK_DIST;
		}

		end_time = sys.getTime() + CHAR_WAIT_BLOCKED;
		while( ( sys.getTime() < end_time ) || AI_PUSHED || waitUntilPathClear ) {
			check_cower();
			faceTowardsEntity( obstacle, 0.1 );

			delta = obstacle_pos - obstacle.getOrigin();
			delta_z = 0;
			
			if ( sys.vecLength( delta ) < 4 ) {
				if ( sys.getTime() > end_time ) {
					break;
				}
				if ( AI_PUSHED ) {
					if ( getOutOfWay() ) {
						waitUntilPathClear = true;
						end_time = sys.getTime() + CHAR_WAIT_BLOCKED;
					}
				}
			} else {
				restoreMove();
				if ( !AI_OBSTACLE_IN_PATH ) {
					if ( !waitUntilPathClear || ( sys.getTime() > end_time ) ) {
						break;
					}
				} else if ( getObstacle() != obstacle ) {
					break;
				}

				if ( distanceTo( obstacle ) > allow_walk_dist ) {
					break;
				}
				if ( moveStatus() >= MOVE_STATUS_BLOCKED_BY_OBJECT ) {
					if ( AI_PUSHED ) {
						if ( getOutOfWay() ) {
							waitUntilPathClear = true;
							end_time = sys.getTime() + CHAR_WAIT_BLOCKED;
						}
					}
				}
				stopMove();
			}

			if ( AI_TALK ) {
				setState( "state_Idle" );
			}

			waitFrame();
		}
		restoreMove();
	}
}

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

	path functions

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

/*
=====================
character::path_corner
=====================
*/
void character::path_corner() {
	run = current_path.getIntKey( "run" );
	walkAnim = current_path.getKey( "anim" );
	moveToEntity( current_path );
	waitFrame();
	while( !AI_MOVE_DONE && !AI_TALK ) {
		check_cower();
		checkBlocked();
		moveToEntity( current_path );
		waitFrame();
	}
	walkAnim = "";
	run = false;

	if ( AI_TALK ) {
		cancelPathCommand();
		setState( "state_Idle" );
	}
	if ( AI_DEST_UNREACHABLE ) {
		// Can't reach
		sys.warning( "entity '" + getName() + "' couldn't reach path_corner '" + current_path.getName() + "'" );
		waitFrame();
	}
}

/*
=====================
character::path_anim
=====================
*/
void character::path_anim() {
	string	animname;
	float	ang;
	float	blend_in;
	float	blend_out;
	float	channel;

	animname = current_path.getKey( "anim" );
	blend_in = current_path.getIntKey( "blend_in" );
	blend_out = current_path.getIntKey( "blend_out" );

	if ( current_path.getKey( "angle" ) != "" ) {
		ang = current_path.getFloatKey( "angle" );
		turnTo( ang );
		while( !facingIdeal() ) {
			check_cower();
			waitFrame();
		}
	}

	if ( current_path.getIntKey( "head_anim" ) ) {
		channel = playHeadAnim( animname, blend_in );
		while( !animDone( channel, blend_out ) ) {
			check_cower();
			if ( AI_TALK ) {
				endHeadAnim( blend_out );
				sys.wait( blend_out / 24 );
				cancelPathCommand();
				setState( "state_Idle" );
			}
			waitFrame();
		}
		endHeadAnim( blend_out );
	} else {
		playCustomAnim( animname, blend_in, blend_out );
		while( inCustomAnim() ) {
			check_cower();
			if ( AI_TALK ) {
				endCustomAnim();
				sys.wait( blend_out / 24 );
				cancelPathCommand();
				setState( "state_Idle" );
			}
			waitFrame();
		}
	}
}

/*
=====================
character::path_cycleanim
=====================
*/
void character::path_cycleanim() {
	string	animname;
	float	ang;
	float	blend_in;
	float	blend_out;
	float	waittime;

	animname = current_path.getKey( "anim" );
	blend_in = current_path.getIntKey( "blend_in" );
	blend_out = current_path.getIntKey( "blend_out" );

	waittime = current_path.getFloatKey( "wait" );

	AI_ACTIVATED = false;

	if ( current_path.getKey( "angle" ) != "" ) {
		ang = current_path.getFloatKey( "angle" );
		turnTo( ang );
		while( !facingIdeal() ) {
			if ( !waittime && AI_ACTIVATED ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
				return;
			}
			check_cower();
			waitFrame();
		}
	}

	playCustomCycle( animname, blend_in );

	if ( waittime ) {
		waittime += sys.getTime();
		while( sys.getTime() < waittime ) {
			check_cower();
			if ( AI_TALK ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
				sys.wait( blend_out / 24 );
				cancelPathCommand();
				setState( "state_Idle" );
			}
			waitFrame();
		}
	} else {
		while( !AI_ACTIVATED ) {
			check_cower();
			if ( AI_TALK ) {
				animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
				sys.wait( blend_out / 24 );
				cancelPathCommand();
				setState( "state_Idle" );
				return;
			}
			waitFrame();
		}
	}
	animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
}

/*
=====================
character::path_turn
=====================
*/
void character::path_turn() {
	vector ang;

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

	// wait 1 frame so that Legs_Idle can start turning.
	waitFrame();

	while( !facingIdeal() ) {
		check_cower();
		waitFrame();
	}
}

/*
=====================
character::path_wait
=====================
*/
void character::path_wait() {
	float waittime;

	waittime = current_path.getFloatKey( "wait" );
	waittime += sys.getTime();
	while( sys.getTime() < waittime ) {
		check_cower();
		waitFrame();
	}
}

/*
=====================
character::path_waitfortrigger
=====================
*/
void character::path_waitfortrigger() {
	AI_ACTIVATED = false;
	while( !AI_ACTIVATED ) {
		check_cower();
		if ( AI_TALK ) {
			cancelPathCommand();
			setState( "state_Idle" );
		}
		waitFrame();
	}
}
			
/*
=====================
character::path_hide
=====================
*/
void character::path_hide() {
	hide();
}

/*
=====================
character::path_show
=====================
*/
void character::path_show() {
	waitUntil( canBecomeSolid() );
	show();
}

/*
=====================
character::skip_conversation
=====================
*/
void character::skip_conversation() {
	string classname;

	classname = current_path.getKey( "classname" );
	while( sys.strLeft( classname, 17 ) == "path_conversation" ) {
		current_path = next_path;
		next_path = next_path.randomPath();
		if ( !next_path ) {
			break;
		}
		classname = current_path.getKey( "classname" );
	}
	stopSound( SND_CHANNEL_VOICE, false );
	if ( head ) {
		head.stopSound( SND_CHANNEL_VOICE, false );
	}
}

/*
=====================
character::break_out_of_conversation
=====================
*/
void character::break_out_of_conversation() {
	character listener;

	// tell our listeners to skip out
	while( next_listener ) {
		listener = next_listener;
		next_listener = listener.next_listener;
		listener.skip_conversation();
		listener.next_listener = $null_entity;
		listener.listening_to_character  = $null_entity;
	}

	skip_conversation();
}

/*
=====================
character::path_conversation_listen
=====================
*/
void character::path_conversation_listen() {
	string	focus_character;
	string	anim;
	boolean no_look;
	float	blend_in;
	float	blend_out;
	
	stopMove();

	focus_character = current_path.getKey( "focus" );
	listening_to_character = sys.getEntity( focus_character );

	anim = current_path.getKey( "anim" );
	if ( anim != "" ) {
		blend_in = current_path.getIntKey( "blend_in" );
		blend_out = current_path.getIntKey( "blend_out" );
		playCustomCycle( anim, blend_in );
	}

	no_look = current_path.getIntKey( "no_look" );

	// link ourselves into the focus character's listeners
	next_listener = listening_to_character.next_listener;
	listening_to_character.next_listener = self;

	while( listening_to_character ) {
		if ( AI_TALK ) {
			listening_to_character.break_out_of_conversation();
			cancelPathCommand();
			setState( "state_Idle" );
		}
		check_cower();
		if ( !no_look ) {
			lookAt( listening_to_character, 0.1 );
		}
		waitFrame();
	}

	if ( anim != "" ) {
		animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
	}
}

/*
=====================
character::path_conversation
=====================
*/
void character::path_conversation() {
	string		dialog;
	string		anim;
	string		focus_character;
	float		waitTime;
	entity		listener;
	character	char_listener;
	float		blend_in;
	float		blend_out;
	float		channel;

	stopMove();

	anim = current_path.getKey( "anim" );
	focus_character = current_path.getKey( "focus" );
	waitTime = current_path.getFloatKey( "wait" );
	listener = sys.getEntity( focus_character );
	blend_in = current_path.getIntKey( "blend_in" );
	blend_out = current_path.getIntKey( "blend_out" );

	// say the conversation line
	dialog = current_path.getKey( "snd_dialog" );
	if ( dialog != "" ) {
		startSoundShader( dialog, SND_CHANNEL_VOICE );
	}

	setBlendFrames( ANIMCHANNEL_LEGS, blend_in );
	if ( getHead() ) {
		animState( ANIMCHANNEL_HEAD, "Anim_Disable", blend_in );
	}

	channel = playHeadAnim( anim, blend_in );
	while( !animDone( channel, blend_out ) ) {
		if ( AI_TALK ) {
			break;
		}
		check_cower();
		lookAt( listener, 0.1 );
		waitFrame();
	}

	endHeadAnim( blend_out );

	if ( AI_TALK ) {
		break_out_of_conversation();
		cancelPathCommand();
		setState( "state_Idle" );
	}

	if ( waitTime ) {
		// wait for a bit
		waitTime += sys.getTime();
		while( waitTime > sys.getTime() ) {
			if ( AI_TALK ) {
				break_out_of_conversation();
				cancelPathCommand();
				setState( "state_Idle" );
			}
			check_cower();
			lookAt( listener, 0.1 );
			waitFrame();
		}
	}

	// tell our listeners we're done
	while( next_listener ) {
		char_listener = next_listener;
		next_listener = char_listener.next_listener;
		char_listener.next_listener = $null_entity;
		char_listener.listening_to_character  = $null_entity;
	}
}

/*
=====================
character::path_headanim
=====================
*/
void character::path_headanim() {
	float blendIn;
	
	blendIn = current_path.getIntKey( "blend_in" );
	customBlendOut = current_path.getIntKey( "blend_out" );
	customAnim = current_path.getKey( "anim" );
	animState( ANIMCHANNEL_HEAD, "Head_TalkHeadOnly", blendIn );
}

/*
=====================
character::path_waitforheadanim
=====================
*/
void character::path_waitforheadanim() {
	string	focus_entity;
	entity	focus;
	
	stopMove();

	focus_entity = current_path.getKey( "focus" );
	focus = sys.getEntity( focus_entity );

	while( inAnimState( ANIMCHANNEL_HEAD, "Head_TalkHeadOnly" ) ) {
		if ( AI_TALK ) {
			cancelPathCommand();
			setState( "state_Idle" );
		}
		check_cower();
		lookAt( focus, 0.1 );
		waitFrame();
	}
}

/*
=====================
character::path_talk

forces character to talk to the player
=====================
*/
void character::path_talk() {
	setTalkTarget( $player1 );
	idle_talk();
}

/*
=====================
character::path_talk_triggered

forces character to say his triggered talk anim to the player
=====================
*/
void character::path_talk_triggered() {
	talkMode = CHAR_TALK_TRIGGERED;
	setTalkTarget( $player1 );
	idle_talk();
}

/*
=====================
character::path_talk_primary

forces character to say his primary talk anim to the player
=====================
*/
void character::path_talk_primary() {
	talkMode = CHAR_TALK_PRIMARY;
	setTalkTarget( $player1 );
	idle_talk();
}

/*
=====================
character::path_talk_secondary

forces character to say his secondary talk anim to the player
=====================
*/
void character::path_talk_secondary() {
	talkMode = CHAR_TALK_SECONDARY;
	setTalkTarget( $player1 );
	idle_talk();
}

/*
=====================
character::path_lookat
=====================
*/
void character::path_lookat() {
	string		dialog;
	string		anim;
	string		focus_entity;
	float		waitTime;
	entity		focus;
	float		blend_in;
	float		blend_out;
	
	stopMove();

	anim = current_path.getKey( "anim" );
	blend_in = current_path.getIntKey( "blend_in" );
	blend_out = current_path.getIntKey( "blend_out" );
	focus_entity = current_path.getKey( "focus" );
	focus = sys.getEntity( focus_entity );
	waitTime = current_path.getFloatKey( "wait" );

	if ( anim != "" ) {
		playCustomCycle( anim, blend_in );
	}

	if ( !waitTime ) {
		AI_ACTIVATED = false;
		while( !AI_ACTIVATED ) {
			if ( AI_TALK ) {
				cancelPathCommand();
				if ( anim != "" ) {
					animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
				}
				setState( "state_Idle" );
			}
			check_cower();
			lookAt( focus, 0.1 );
			waitFrame();
		}
	} else {
		waitTime += sys.getTime();
		AI_ACTIVATED = false;
		while( waitTime > sys.getTime() ) {
			if ( AI_ACTIVATED ) {
				break;
			}
			if ( AI_TALK ) {
				if ( anim != "" ) {
					animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
				}
				cancelPathCommand();
				setState( "state_Idle" );
			}
			check_cower();
			lookAt( focus, 0.1 );
			waitFrame();
		}
	}

	if ( anim != "" ) {
		animState( ANIMCHANNEL_LEGS, "Legs_Idle", blend_out );
	}
}

/*
=====================
character::state_FollowAlternatePath
=====================
*/
void character::state_FollowAlternatePath() {
	endHeadAnim( 8 );

	idle_followPathEntities( current_path );
	setState( "state_Idle" );
}

/*
=====================
character::follow_alternate_path1
=====================
*/
void character::follow_alternate_path1() {
	current_path = getEntityKey( "alt_path1" );
	setState( "state_FollowAlternatePath" );
}

/*
=====================
character::follow_alternate_path2
=====================
*/
void character::follow_alternate_path2() {
	current_path = getEntityKey( "alt_path2" );
	setState( "state_FollowAlternatePath" );
}

/*
=====================
character::follow_alternate_path3
=====================
*/
void character::follow_alternate_path3() {
	current_path = getEntityKey( "alt_path3" );
	setState( "state_FollowAlternatePath" );
}

/*
=====================
character::disable_turning
=====================
*/
void character::disable_turning() {
	allow_turn = false;
}

/*
=====================
character::enable_turning
=====================
*/
void character::enable_turning() {
	allow_turn = true;
}
