#include maps\_utility;
#include common_scripts\utility;
#include maps\_anim;

hiding_door_spawner()
{
	// place a hiding_door_guy prefab and then place a spawner next to it with script_noteworthy "hiding_door_spawner". 
	// Spawn the guy however you like (trigger or script)
	// Target the spawner to a trigger, this trigger will make the guy open the door.
	// Alternatively put a script_flag_wait on the spawner. The guy will wait for the flag to be set before opening the door.
	// If you put neither, a trigger_radius will be spawned, using the radius of the spawner if a radius is set
	
	door_orgs = getentarray( "hiding_door_guy_org", "targetname" );
	assertex( door_orgs.size, "Hiding door guy with export " + self.export + " couldn't find a hiding_door_org!" );
	
	door_org = getclosest( self.origin, door_orgs );
	assertex( distance( door_org.origin, self.origin ) < 256, "Hiding door guy with export " + self.export + " was not placed within 256 units of a hiding_door_org" );
	
	door_org.targetname = undefined;// so future searches won't grab this one
	
	//get door models (and script_brushmodel doors, if applicable)
	door_models = getentarray( door_org.target, "targetname" );
	door_model = undefined;
	brushmodel_door = undefined;
	
	badplaceBrush = undefined;
	if( IsDefined( door_org.script_linkto ) )
	{
		badplaceBrush = door_org get_linked_ent();
	}
	
	//if there is only one ent, it must be the script_model door
	if ( door_models.size == 1 )
	{
		door_model = door_models[ 0 ];
	}
	
	//if targeting more than one ent, the LD wants to substitute a custom script_brushmodel door
	else
	{
		foreach( ent in door_models )
		{
			if ( ent.code_classname == "script_brushmodel" )
			{
				brushmodel_door = ent;
			}
			else if ( ent.code_classname == "script_model" )
			{
				door_model = ent;
			}
		}
		assertex( isdefined( brushmodel_door ), "Hiding door org at " + door_org.origin + " targets multiple entities, but not a script_brushmodel door" );
		assertex( isdefined( door_model ), "Hiding door org at " + door_org.origin + " targets multiple entities, but not a script_model door" );
	}
	
	door_clip = getent( door_model.target, "targetname" );
	assert( isdefined( door_model.target ) );
	
	pushPlayerClip = undefined;
	if ( isdefined( door_clip.target ) )
		pushPlayerClip = getent( door_clip.target, "targetname" );
	if ( isdefined( pushPlayerClip ) )
	{
		door_org thread hiding_door_guy_pushplayer( pushPlayerClip );
		
		if( !IsDefined( level._hiding_door_pushplayer_clips ) )
		{
			level._hiding_door_pushplayer_clips = [];
		}
		level._hiding_door_pushplayer_clips[ level._hiding_door_pushplayer_clips.size ] = pushPlayerClip;
	}
	
	door_model delete();// we spawn our own door, the one in the prefab is just to aid placement
	
	door = spawn_anim_model( "hiding_door" );
	door_org thread anim_first_frame_solo( door, "fire_3" );
	if ( isdefined( brushmodel_door ) )
	{
		brushmodel_door linkTo( door, "door_hinge_jnt" );
		door hide();
	}
	
	if ( isdefined( door_clip ) )
	{
		door_clip linkto( door, "door_hinge_jnt" );
		door_clip disconnectPaths();
	}
	
	trigger = undefined;
	if ( isdefined( self.target ) )
	{
		trigger = getent( self.target, "targetname" );
		if ( !issubstr( trigger.classname, "trigger" ) )
			trigger = undefined;
	}
	
	if ( !isdefined( self.script_flag_wait ) && !isdefined( trigger ) )
	{
		radius = 200;
		if ( isdefined( self.radius ) )
			radius = self.radius;

		// no trigger mechanism specified, so add a radius trigger
		trigger = spawn( "trigger_radius", door_org.origin, 0, radius, 48 );
	}
	
	if ( isdefined( badplaceBrush ) )
		badPlace_Brush( badplaceBrush getentitynumber(), 0, badplaceBrush, "allies" );
	
	self add_spawn_function( ::hiding_door_guy, door_org, trigger, door, door_clip, badplaceBrush );
}

hiding_door_guy( door_org, trigger, door, door_clip, badplaceBrush )
{
	starts_open = hiding_door_starts_open( door_org );

	self.animname = "hiding_door_guy";
	self endon( "death" );
	self endon( "damage" );
	
	self.grenadeammo = 2;

	self set_deathanim( "death_2" );
	self.allowdeath = true;
	self.health = 50000;// buffer health, he "dies" in one hit
	
	// Force enemy to charge out instead of careful
	force_charge = false;
	if( IsDefined( self.script_parameters ) && self.script_parameters == "force_charge" )
	{
		force_charge = true;
	}

	guy_and_door = [];
	guy_and_door[ guy_and_door.size ] = door;
	guy_and_door[ guy_and_door.size ] = self;

	thread hiding_door_guy_cleanup( door_org, self, door, door_clip, badplaceBrush );
	thread hiding_door_death( door, door_org, self, door_clip, badplaceBrush );

	if ( starts_open )
	{
		// wait for trigger before closing the door
		door_org thread anim_loop( guy_and_door, "idle" );
	}
	else
	{
		door_org thread anim_first_frame( guy_and_door, "fire_3" );
	}

	if ( isdefined( trigger	 ) )
	{
		wait 0.05;
		trigger waittill( "trigger" );
	}
	else
	{
		flag_wait( self.script_flag_wait );
	}

	if ( starts_open )
	{
		door_org notify( "stop_loop" );
		door_org anim_single( guy_and_door, "close" );
	}
	
	counter = 0;
	timesFired = 0;
	for ( ;; )
	{
		//-----------------
		// GET ENEMY AND ENEMY DIRECTION
		//-----------------
		
		enemy = level._player;
		if ( isdefined( self.enemy ) )
			enemy = self.enemy;
		assert( isdefined( enemy ) );
		direction = hiding_door_get_enemy_direction( door.angles, self.origin, enemy.origin );
		
		//-----------------
		// ABORT CONDITIONS
		//-----------------
		
		// Abort door behavior if the player comes up behind the AI
		if ( self player_entered_backdoor( direction ) )
		{
			if ( self quit_door_behavior() )
				return;
		}
		
		// Abort door behavior after peeking a couple times if the player can see him from behind
		if ( counter >= 2 )
		{
			if ( self quit_door_behavior( true ) )
				return;
		}
		
		//-----------------
		// DETERMINE SCENE BASED ON ENEMY DIRECTION
		//-----------------
		
		scene = undefined;
		if ( direction == "left" || direction == "front" )
		{
			scene = "fire_3";
		}
		else if ( direction == "right" )
		{
			scene = "fire_1";
			if ( cointoss() )
				scene = "fire_2";
		}
		else
		{
			// player or enemy is behind him so just open and close the door and peek
			door_org anim_single( guy_and_door, "open" );
			door_org anim_single( guy_and_door, "close" );
			counter++;
			continue;
		}
		assert( isdefined( scene ) );
		
		//-----------------
		// CHARGE CONDITION + CHANCE
		//-----------------
		
		if ( self hiding_door_guy_should_charge( direction, enemy, timesFired ) || force_charge )
		{
			scene = "jump";
			if ( coinToss() || force_charge )
			{
				if ( self mayMoveToPoint( animscripts\utility::getAnimEndPos( level._scr_anim[ self.animname ][ "kick" ] ) ) )
					scene = "kick";
			}
			
			// connect paths on the door and handle player clip
			thread hiding_door_death_door_connections( door_clip, badplaceBrush );
			door_org notify( "push_player" );
			
			// stop the thread that waits for him to break out of door behavior to open the door since this anim opens it for us
			self notify( "charge" );
			
			// guy charges out
			self.allowdeath = true;
			self.health = 100;
			self clear_deathanim();
			door_org anim_single( guy_and_door, scene );
			
			// now he goes to exposed combat
			self quit_door_behavior();
			return;
				
		}
		
		//-----------------
		// THROW A GRENADE?
		//-----------------
		
		// randomly do grenade throw if the AI has grenade ammo. More likely if hte AI has more grenades
		if ( self hiding_door_guy_should_throw_grenade( direction, timesFired ) )
		{
			self.grenadeammo--;
			scene = "grenade";
		}
		
		counter = 0;
		timesFired++;
		
		//-----------------
		// DO ANIM
		//-----------------
		
		door_org thread anim_single( guy_and_door, scene );
		
		// delay the settime by a frame or it wont work
		// this is so we can skip the slow creep part of the animation
		delaythread( 0.05, ::anim_set_time, guy_and_door, scene, 0.3 );
		door_org waittill( scene );
		
		//-----------------
		// IDLE FOR A MOMENT
		//-----------------
		
		door_org thread anim_first_frame( guy_and_door, "open" );
		wait( randomfloatrange( 0.2, 1.0 ) );
		door_org notify( "stop_loop" );
	}
}

quit_door_behavior( sightTraceRequired, door_org )
{
	if ( !isdefined( sightTraceRequired ) )
		sightTraceRequired = false;
	
	if ( sightTraceRequired )
	{
		if ( !sightTracePassed( level._player getEye(), self getEye(), false, self ) )
			return false;
	}
	
	self.health = 100;
	self clear_deathanim();
	self.goalradius = 512;
	self setGoalPos( self.origin );
	self notify( "quit_door_behavior" );
	self stopanimscripted();
	self notify( "killanimscript" );
	return true;
}

player_entered_backdoor( direction )
{
	if ( direction != "behind" )
		return false;
	
	d = distance( self.origin, level._player.origin );
	if ( d > 250 )
		return false;
	
	if ( !sightTracePassed( level._player getEye(), self getEye(), false, self ) )
		return false;
	
	return true;
}

hiding_door_guy_should_charge( direction, enemy, timesFired )
{
	TIMES_FIRED_MIN = 3;
	MIN_DIST = 100;
	MAX_DIST = 600;
	
	if ( timesFired < TIMES_FIRED_MIN )
		return false;
	
	if ( enemy != level._player )
		return false;
	
	if ( direction != "front" )
		return false;
	
	d = distance( self.origin, level._player.origin );
	if ( d < MIN_DIST )
		return false;
	if ( d > MAX_DIST )
		return false;
	
	return coinToss();
}

hiding_door_guy_should_throw_grenade( direction, timesFired )
{
	if ( timesFired < 1 )
		return false;
	if ( direction == "behind" )
		return false;
	if ( randomint( 100 ) < 25 * self.grenadeammo )
		return true;
	return false;
}

hiding_door_get_enemy_direction( viewerAngles, viewerOrigin, targetOrigin )
{
	forward = anglesToForward( viewerAngles );
	vFacing = vectorNormalize( forward );
	anglesToFacing = vectorToAngles( vFacing );
	anglesToPoint = vectorToAngles( targetOrigin - viewerOrigin );

	angle = anglesToFacing[ 1 ] - anglesToPoint[ 1 ];
	angle += 360;
	angle = int( angle ) % 360;
	
	direction = undefined;
	
	if ( angle >= 90 && angle <= 270 )
		direction = "behind";
	else if ( angle >= 300 || angle <= 45 )
		direction = "front";
	else if ( angle < 90 )
		direction = "right";
	else if ( angle > 270 )
		direction = "left";
	
	assert( isdefined( direction ) );
	return direction;
}

hiding_door_guy_cleanup( door_org, guy, door, door_clip, badplaceBrush )
{
	guy endon( "charge" );
	
	// if the guy gets deleted before the sequence happens this thread will catch that and clean up any problems that could arise
	guy waittill_either( "death", "quit_door_behavior" );
	
	// stop the looping animations because the guy is removed now
	door_org notify( "stop_loop" );

	thread hiding_door_death_door_connections( door_clip, badplaceBrush );
	door_org notify( "push_player" );
	if ( !isdefined( door.played_death_anim ) )
	{
		door.played_death_anim = true;
		door_org thread anim_single_solo( door, "death_2" );
	}
}

hiding_door_guy_pushplayer( pushPlayerClip )
{
	self waittill( "push_player" );
	pushPlayerClip moveto( self.origin, 1.5 );
	wait 1.5;
	pushPlayerClip delete();
}

hiding_door_guy_grenade_throw( guy )
{
	// called from a notetrack
	startOrigin = guy getTagOrigin( "J_Wrist_RI" );
	strength = ( distance( level._player.origin, guy.origin ) * 2.0 );
	if ( strength < 300 )
		strength = 300;
	if ( strength > 1000 )
		strength = 1000;
	vector = vectorNormalize( level._player.origin - guy.origin );
	velocity = vector_multiply( vector, strength );
	guy magicGrenadeManual( startOrigin, velocity, randomfloatrange( 3.0, 5.0 ) );
}

hiding_door_death( door, door_org, guy, door_clip, badplaceBrush )
{
	guy endon( "charge" );
	guy endon( "quit_door_behavior" );
	
	guy waittill( "damage", dmg, attacker );
	if ( !isalive( guy ) )
		return;
	thread hiding_door_death_door_connections( door_clip, badplaceBrush );
	door_org notify( "push_player" );
	door_org thread anim_single_solo( guy, "death_2" );
	if ( !isdefined( door.played_death_anim ) )
	{
		door.played_death_anim = true;
		door_org thread anim_single_solo( door, "death_2" );
	}
	wait( 0.5 );
	if ( isalive( guy ) )
	{
		if ( IsDefined( attacker ) )
		{
			guy Kill( ( 0, 0, 0 ), attacker );
		}
		else
		{
			//guy.a.nodeath = true;
			guy kill( ( 0, 0, 0 ) );
		}
	}
}

hiding_door_death_door_connections( door_clip, badplaceBrush )
{
	wait 2;
	
	if ( isdefined( door_clip ) )
		door_clip disconnectpaths();
	
	if ( isdefined( badplaceBrush ) )
		badPlace_Delete( badplaceBrush getentitynumber() );
}

hiding_door_starts_open( door_org )
{
	return ( isdefined( door_org.script_noteworthy ) && ( door_org.script_noteworthy == "starts_open" ) );
}