#using scripts\codescripts\struct;

#using scripts\shared\flag_shared;
#using scripts\shared\math_shared;
#using scripts\shared\array_shared;
#using scripts\shared\sound_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\statemachine_shared;
#using scripts\shared\vehicle_shared;
#using scripts\shared\vehicle_death_shared;
#using scripts\shared\turret_shared;
#using scripts\shared\ai\systems\ai_interface;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
       
                                                                                                            	  	  	              	   	                                                                                                                                       	                                                                                                                                                            	                                                                                                                                                                






#namespace vehicle_ai;

function autoexec __init__sytem__() {     system::register("vehicle_ai",&__init__,undefined,undefined);    }

// _vehicle_ai.gsc - all things related to vehicles ai

#using_animtree( "generic" );

function __init__()
{
}

function RegisterSharedInterfaceAttributes( archetype )
{
	/*
	 * Name: force_high_speed
	 * Summary: Always uses max speed instead of default speed when moving
	 * Example: ai::SetAiAttribute( vehicle, "sprint", true );"
	 */
	ai::RegisterMatchedInterface(
		archetype,
		"force_high_speed",
		false,
		array( true, false ) );
}

//*****************************************************************************
//
// 	Vehicle AI Commands
// 
//	- These are used in conjunction with the vehicle ai system
//	- If the vehicle is not setup to use the ai system they might do nothing
//	- Please see James Snider or Ruoyao Ma if you have questions
//
//*****************************************************************************

function InitThreatBias()
{
	aiarray = GetAIArray();

	foreach( ai in aiarray )
	{
		if ( ai === self )
		{
			continue;
		}

		if ( self.ignoreFireFly === true && ai.firefly === true )
		{
			self SetPersonalIgnore( ai );
		}

		if ( self.ignoreDecoy === true && ai.isDecoy === true )
		{
			self SetPersonalIgnore( ai );
		}
	}
}

function EntityIsArchetype( entity, archetype )
{
	if ( !isdefined( entity ) )
	{
		return false;
	}
	if ( isplayer( entity ) && entity.usingvehicle && isdefined( entity.viewlockedentity ) && entity.viewlockedentity.archetype === archetype )
	{
		return true;
	}
	if ( isvehicle( entity ) && entity.archetype === archetype )
	{
		return true;
	}
	return false;
}

// self == AI vehicle
function GetEnemyTarget()
{
	if( isdefined( self.enemy ) && self VehCanSee( self.enemy ) )
	{
		return self.enemy;
	}
	else if( isdefined( self.enemylastseenpos ) )
	{
		return self.enemylastseenpos;
	}

	return undefined;
}

function GetTargetPos( target, geteye )
{
	pos = undefined;

	if ( isdefined( target ) )
	{
		if ( IsVec( target ) )
		{
			pos = target;
		}
		else if ( ( isdefined( geteye ) && geteye ) && IsSentient( target ) )
		{
			pos = target GetEye();
		}
		else if ( IsEntity( target ) )
		{
			pos = target.origin;
		}
		else if ( isdefined( target.origin ) && IsVec( target.origin ) )
		{
			pos = target.origin;
		}
	}

	return pos;
}

function GetTargetEyeOffset( target )
{
	offset = (0,0,0);

	if ( isdefined( target ) && IsSentient( target ) )
	{
		offset = target GetEye() - target.origin;
	}

	return offset;
}

/@
"Name: fire_for_time( <totalFireTime>, [turretIdx], [target] )"
"Summary: fire a weapon for a set of period"
"CallOn: a vehicle"
"MandatoryArg: <totalFireTime> : how long should the weapon fire"
"OptionalArg: [turretIdx] : which weapon to use. use number to get turret on vehicle. if undefined the vehicle will try to use it's main weapon."
"OptionalArg: [target] : what target it is shooting at. this will affect hit chance."
"Example: vehicle vehicle_ai::fire_for_time( RandomFloatRange( 0.3, 0.6 ) ); "
@/
function fire_for_time( totalFireTime, turretIdx, target, intervalScale = 1.0 )
{
	self endon( "death" );
	self endon( "change_state" );
	self notify( "fire_stop" );
	self endon( "fire_stop" );

	if ( !isdefined( turretIdx ) )
	{
		turretIdx = 0;
	}

	weapon = self SeatGetWeapon( turretIdx );

	assert( isdefined( weapon ) && weapon.name!="none" && weapon.fireTime>0 );

	firetime = weapon.firetime * intervalScale;

	fireCount = int( floor( totalFireTime / fireTime ) ) + 1;

	__fire_for_rounds_internal( fireCount, fireTime, turretIdx, target );
}

/@
"Name: fire_for_rounds( <fireCount>, [turretIdx], [target] )"
"Summary: fire a weapon for a set of period"
"CallOn: a vehicle"
"MandatoryArg: <fireCount> : how many rounds should the weapon fire"
"OptionalArg: [turretIdx] : which weapon to use. use number to get turret on vehicle. if undefined the vehicle will try to use it's main weapon."
"OptionalArg: [target] : what target it is shooting at. this will affect hit chance."
"Example: vehicle vehicle_ai::fire_for_rounds( 5 ); "
@/
function fire_for_rounds( fireCount, turretIdx, target )
{
	self endon( "death" );
	self endon( "fire_stop" );
	self endon( "change_state" );

	if ( !isdefined( turretIdx ) )
	{
		turretIdx = 0;
	}

	weapon = self SeatGetWeapon( turretIdx );

	assert( isdefined( weapon ) && weapon.name!="none" && weapon.fireTime>0 );

	__fire_for_rounds_internal( fireCount, weapon.fireTime, turretIdx, target );
}

function __fire_for_rounds_internal( fireCount, fireInterval, turretIdx, target )
{
	self endon( "death" );
	self endon( "fire_stop" );
	self endon( "change_state" );

	if( isdefined( target ) && IsSentient( target ) )
	{
		target endon( "death" );
	}
	
	assert( isdefined( turretIdx ) );

	aiFireChance = 1;

	// fire less, unless shooting player or a bigdog or mentioned specifically from level script
	if ( ( isdefined( target ) && !IsPlayer( target ) && isAI( target ) ) || isdefined( self.fire_half_blanks )  )
	{
		// 1 in 2 bullets will be real
		aiFireChance = 2;
	}

	counter = 0;
	while ( counter < fireCount )
	{
		if ( self.avoid_shooting_owner === true && self owner_in_line_of_fire() )
		{
			wait fireInterval;
			continue;
		}

		if ( isdefined( target ) && !isVec( target ) && isdefined( target.attackerAccuracy ) && target.attackerAccuracy == 0 )
		{
			self FireTurret( turretIdx, true );
		}
		else if ( aiFireChance > 1 )
		{
			self FireTurret( turretIdx, counter % aiFireChance );
		}
		else
		{
			self FireTurret( turretIdx );
		}

		counter++;
		wait fireInterval;
	}
}

function owner_in_line_of_fire()
{
	if ( !isdefined( self.owner ) )
		return false;

	dist_squared_to_owner = DistanceSquared( self.owner.origin, self.origin );
	line_of_fire_dot = ( ( dist_squared_to_owner < 96 * 96 ) ? 0.8660 : 0.9848 ); // cos( 30 degrees ) : cos ( 10 degrees );
	gun_angles = self GetTagAngles( (isdefined(self.avoid_shooting_owner_ref_tag)?self.avoid_shooting_owner_ref_tag:"tag_flash") );
	gun_forward = AnglesToForward( gun_angles );
	dot = VectorDot( gun_forward, VectorNormalize( self.owner.origin - self.origin ) ); // rough approximation, good enough
	return ( dot > line_of_fire_dot );
}

function SetTurretTarget( target, turretIdx = 0, offset = (0,0,0) )
{
	if ( isEntity( target ) )
	{
		if ( turretIdx == 0 )
		{
			self SetTurretTargetEnt( target, offset );
		}
		else
		{
			self SetGunnerTargetEnt( target, offset, turretIdx - 1 );
		}
	}
	else if ( isVec( target ) )
	{
		origin = target + offset;

		if ( turretIdx == 0 )
		{
			self SetTurretTargetVec( target );
		}
		else
		{
			self SetGunnerTargetVec( target, turretIdx - 1 );
		}
	}
	else
	{
		AssertMsg( "Turret target must be an entity or a vector." );
	}
}

function FireTurret( turretIdx, isFake )
{
	self FireWeapon( turretIdx, undefined, undefined, self );
}

function Javelin_LoseTargetAtRightTime( target )
{
	self endon( "death" );
	
	self waittill( "weapon_fired", proj );
	
	if( !isdefined( proj ) )
	{
		return;
	}
	
	proj endon( "death" );
	
	wait 2;
	
	while( isdefined( target ) )
	{
		if( proj GetVelocity()[2] < -150 && DistanceSquared( proj.origin, target.origin ) < ( (1200) * (1200) ) )
		{
			proj Missile_SetTarget( undefined );
			break;
		}
		wait 0.1;
	}
}

function waittill_pathing_done( maxtime = 15 )
{
	self endon( "change_state" );

	self util::waittill_any_ex( maxtime, "near_goal", "force_goal", "reached_end_node", "goal", "pathfind_failed", "change_state" );
}

function waittill_pathresult( maxtime = 0.5 )
{
	self endon( "change_state" );

	result = self util::waittill_any_timeout( maxtime, "pathfind_failed", "pathfind_succeeded", "change_state" );
	succeeded = ( result === "pathfind_succeeded" );
	return succeeded;
}

function waittill_asm_terminated()
{
	self endon( "death" );
	self notify( "end_asm_terminated_thread" );
	self endon( "end_asm_terminated_thread" );
	self waittill( "asm_terminated" );
	self notify( "asm_complete", "__terminated__" );
}

function waittill_asm_timeout( timeout )
{
	self endon( "death" );
	self notify( "end_asm_timeout_thread" );
	self endon( "end_asm_timeout_thread" );
	wait timeout;
	self notify( "asm_complete", "__timeout__" );
}

function waittill_asm_complete( substate_to_wait, timeout = 10 )
{
	self endon( "death" );

	self thread waittill_asm_terminated();
	self thread waittill_asm_timeout( timeout );

	substate = undefined;
	while ( !isdefined( substate ) || ( substate != substate_to_wait && substate != "__terminated__" && substate != "__timeout__" ) )
	{
		self waittill( "asm_complete", substate );
	}
	self notify( "end_asm_terminated_thread" );
	self notify( "end_asm_timeout_thread" );
}

// self == vehicle
function throw_off_balance( damageType, hitPoint, hitDirection, hitLocationInfo )
{
	if ( damageType == "MOD_EXPLOSIVE" || damageType == "MOD_GRENADE_SPLASH" || damageType == "MOD_PROJECTILE_SPLASH" )
	{
		self SetVehVelocity( self.velocity + VectorNormalize( hitDirection ) * 300 );
		ang_vel = self GetAngularVelocity();
		ang_vel += ( RandomFloatRange( -300, 300 ), RandomFloatRange( -300, 300 ), RandomFloatRange( -300, 300 ) );
		self SetAngularVelocity( ang_vel );
	}
	else
	{
		ang_vel = self GetAngularVelocity();
		yaw_vel = RandomFloatRange( -320, 320 );
		yaw_vel += math::sign( yaw_vel ) * 150;

		ang_vel += ( RandomFloatRange( -150, 150 ), yaw_vel, RandomFloatRange( -150, 150 ) );
		self SetAngularVelocity( ang_vel );
	}
}

function predicted_collision()
{
	self endon( "crash_done" );
	self endon( "death" );

	while ( 1 )
	{
		self waittill( "veh_predictedcollision", velocity, normal );
		if ( normal[2] >= 0.6 )
		{
			self notify( "veh_collision", velocity, normal );
		}
	}
}

// self == vehicle
function collision_fx( normal )
{
	tilted = ( normal[2] < 0.6 );
	fx_origin = self.origin - normal * ( tilted ? 28 : 10 );

	self PlaySound( "veh_wasp_wall_imp" );
	//PlayFX( level._effect[ "drone_nudge" ], fx_origin, normal );
}

// self == vehicle
function nudge_collision()
{
	self endon( "crash_done" );
	self endon( "power_off_done" );
	self endon( "death" );
	self notify( "end_nudge_collision" );
	self endon( "end_nudge_collision" );

	if ( self.notsolid === true )
	{
		return;
	}

	while ( 1 )
	{
		self waittill( "veh_collision", velocity, normal );
		ang_vel = self GetAngularVelocity() * 0.5;
		self SetAngularVelocity( ang_vel );
		
		empedOrOff = ( self get_current_state() === "emped" || self get_current_state() === "off" );
		
		// bounce off walls
		if ( IsAlive( self ) && ( normal[2] < 0.6 || !empedOrOff ) ) // stable or active
		{
			self SetVehVelocity( self.velocity + normal * 90 );
			self collision_fx( normal );
		}
		else if ( empedOrOff )
		{
			if ( isdefined( self.bounced ) )
			{
				self playsound( "veh_wasp_wall_imp" );
				self SetVehVelocity( ( 0, 0, 0 ) );
				self SetAngularVelocity( ( 0, 0, 0 ) );

				pitch = self.angles[0];
				pitch = math::sign( pitch ) * math::clamp( abs( pitch ), 10, 15 );
				self.angles = ( pitch, self.angles[1], self.angles[2] );

				self.bounced = undefined;
				self notify( "landed" );
				return;
			}
			else
			{
				self.bounced = true;
				self SetVehVelocity( self.velocity + normal * 30 );
				self collision_fx( normal );
			}
		}
		else // tilted
		{
			impact_vel = abs( VectorDot( velocity, normal ) );
			
			if(  normal[2] < 0.6 && impact_vel < 100 )
			{
				self SetVehVelocity( self.velocity + normal * 90 );
				self collision_fx( normal );
			}
			else
			{
				self playsound( "veh_wasp_ground_death" );
				self thread vehicle_death::death_fire_loop_audio();
				self notify( "crash_done" );
			}
		}
	}
}

//self is vehicle
function level_out_for_landing()
{
	self endon( "death" );
	self endon( "change_state" );
	self endon( "landed" );
	
	while( 1 )
	{
		velocity = self.velocity;	// setting the angles clears the velocity so we save it off and set it back
		self.angles = ( self.angles[0] * 0.85, self.angles[1], self.angles[2] * 0.85 );
		ang_vel = self GetAngularVelocity() * 0.85;
		self SetAngularVelocity( ang_vel );
		self SetVehVelocity( velocity + (0,0,-60) );
		{wait(.05);};
	}
}


//self is vehicle
function immolate( attacker )
{
	self endon( "death" );

	self thread burning_thread( attacker, attacker );
}

function burning_thread( attacker, inflictor )
{
	self endon( "death" );
	self notify( "end_immolating_thread" );
	self endon( "end_immolating_thread" );

	damagePerSecond = self.settings.burn_damagepersecond;
	if ( !isdefined( damagePerSecond ) || damagePerSecond <= 0 )
	{
		return;
	}

	secondsPerOneDamage = 1.0 / Float( damagePerSecond );

	if ( !isdefined( self.abnormal_status ) )
	{
		self.abnormal_status = spawnStruct();
	}

	if ( self.abnormal_status.burning !== true )
	{
		self vehicle::toggle_burn_fx( true );
	}

	self.abnormal_status.burning = true;
	self.abnormal_status.attacker = attacker;
	self.abnormal_status.inflictor = inflictor;

	lastingTime = self.settings.burn_lastingtime;
	if ( !isdefined( lastingTime ) )
	{
		lastingTime = 999999;
	}

	startTime = GetTime();
	interval = max( secondsPerOneDamage, 0.5 );
	damage = 0.0;
	
	while ( TimeSince( startTime ) < lastingTime )
	{
		previousTime = GetTime();
		wait interval;
		damage += TimeSince( previousTime ) * damagePerSecond;
		damageInt = Int( damage );
		self DoDamage( damageInt, self.origin, attacker, self, "none", "MOD_BURNED" );
		damage -= damageInt;
	}

	self.abnormal_status.burning = false;
	self vehicle::toggle_burn_fx( false );
}

//self is vehicle
function iff_notifyMeInNSec(time,note)
{
	self endon("death");
	wait time;
	self notify(note);
}
function iff_override( owner,time = 60 )
{
	self endon( "death" );
	
	//save off the old team
	self._iffoverride_oldTeam = self.team;
	
	self iff_override_team_switch_behavior( owner.team );
	if(isDefined(self.iff_override_cb))
		self [[self.iff_override_cb]](true);
	
	if(isDefined(self.settings) && !( isdefined( self.settings.iffshouldrevertteam ) && self.settings.iffshouldrevertteam ) )
	{
		//do not revert team
		return;
	}
	timeout = (isDefined(self.settings)?self.settings.ifftimetillrevert:time);
	assert(timeout>10);
	self thread iff_notifyMeInNSec(timeout-10,"iff_override_revert_warn");//5 defined in _cybercom.gsh; 
	msg = self util::waittill_any_timeout( timeout,"iff_override_reverted", "death" );
	if (msg == "timeout" )
	{
		self notify ("iff_override_reverted");
	}

	self playsound ("gdt_iff_deactivate");
	
	self iff_override_team_switch_behavior( self._iffoverride_oldTeam );
	if(isDefined(self.iff_override_cb))
		self [[self.iff_override_cb]](false);
	
}

function iff_override_team_switch_behavior( team )
{
	self endon( "death" );
	
	old_ignoreme = self.ignoreme;
	self.ignoreme = true;

	self start_scripted();
	self vehicle::lights_off();
	wait 0.1;

	wait( 1 );
	
	self SetTeam( team );

	self blink_lights_for_time( 1 );
	
	self stop_scripted();

	wait 1;
	self.ignoreme = old_ignoreme;
}

function blink_lights_for_time( time )
{
	self endon( "death" );

	startTime = GetTime();
	
	self vehicle::lights_off();
	wait 0.1;
	
	while ( GetTime() < startTime + time * 1000 )
	{
		self vehicle::lights_off();
		wait 0.2;
		self vehicle::lights_on();
		wait 0.2;
	}

	self vehicle::lights_on();
}

function TurnOff()
{
	self notify( "shut_off" );
}

function TurnOn()
{
	self notify( "start_up" );
}

function TurnOffAllLightsAndLaser()
{
	self LaserOff();
	self vehicle::lights_off();
	self vehicle::toggle_lights_group( 1, false );
	self vehicle::toggle_lights_group( 2, false );
	self vehicle::toggle_lights_group( 3, false );
	self vehicle::toggle_lights_group( 4, false );
	self vehicle::toggle_burn_fx( false );
	self vehicle::toggle_emp_fx( false );
}

function TurnOffAllAmbientAnims()
{
	self vehicle::toggle_ambient_anim_group( 1, false );
	self vehicle::toggle_ambient_anim_group( 2, false );
	self vehicle::toggle_ambient_anim_group( 3, false );
}

function ClearAllLookingAndTargeting()
{
	self ClearTargetEntity();
	self ClearGunnerTarget(0);
	self ClearGunnerTarget(1);
	self ClearGunnerTarget(2);
	self ClearGunnerTarget(3);
	self ClearLookAtEnt();
}

function ClearAllMovement( zeroOutSpeed = false )
{
	if( !IsAirborne( self ) )
	{
		self CancelAIMove();
	}
	self ClearVehGoalPos();
	self PathVariableOffsetClear();
	self PathFixedOffsetClear();

	if ( zeroOutSpeed === true )
	{
		self notify( "landed" );
		self SetVehVelocity( ( 0, 0, 0 ) );
		self SetPhysAcceleration( ( 0, 0, 0 ) );
		self SetAngularVelocity( ( 0, 0, 0 ) );
	}
}

function shared_callback_damage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, psOffsetTime, damageFromUnderneath, modelIndex, partName, vSurfaceNormal )
{
	if ( should_emp( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) )
	{
		minEmpDownTime = 0.8 * self.settings.empdowntime;
		maxEmpDownTime = 1.2 * self.settings.empdowntime;
		self notify ( "emped", RandomFloatRange( minEmpDownTime, maxEmpDownTime ), eAttacker, eInflictor );
	}

	if ( should_burn( self, weapon, sMeansOfDeath, eInflictor, eAttacker ) )
	{
		self thread burning_thread( eAttacker, eInflictor );
	}

	if ( !isdefined( self.damageLevel ) )
	{
		self.damageLevel = 0;
		self.newDamageLevel = self.damageLevel;
	}

	newDamageLevel = vehicle::should_update_damage_fx_level( self.health, iDamage, self.healthdefault );
	if ( newDamageLevel > self.damageLevel )
	{
		self.newDamageLevel = newDamageLevel;
	}

	if ( self.newDamageLevel > self.damageLevel )
	{
		self.damageLevel = self.newDamageLevel;
		if ( self.pain_when_damagelevel_change === true )
		{
			self notify( "pain" );
		}
		vehicle::set_damage_fx_level( self.damageLevel );
	}

	return iDamage;
}

function should_emp( vehicle, weapon, meansOfDeath, eInflictor, eAttacker )
{
	if( !isdefined( vehicle ) || meansOfDeath === "MOD_IMPACT" || vehicle.disableElectroDamage === true  )
	{
		return false;
	}

	if ( !( ( isdefined( weapon ) && weapon.isEmp ) || meansOfDeath === "MOD_ELECTROCUTED" ) )
	{
		return false;
	}

	causer = ( isdefined( eAttacker ) ? eAttacker : eInflictor );
	if ( !isdefined( causer ) )
	{
		return true;
	}
         
	if ( IsAI( causer ) && IsVehicle( causer ) ) // don't make electro contagious between AI vehicles
	{
		return false;
	}

	if ( level.teamBased )
	{		
		return ( vehicle.team != causer.team );
	}
	else
	{
		if ( isdefined( vehicle.owner ) )
		{
			return ( vehicle.owner != causer );
		}	
		
		return ( vehicle != causer );
	}
}

function should_burn( vehicle, weapon, meansOfDeath, eInflictor, eAttacker )
{
	if ( level.disableVehicleBurnDamage === true || vehicle.disableBurnDamage === true )
	{
		return false;
	}
	
	if( !isdefined( vehicle ) )
	{
		return false;
	}

	if ( meansOfDeath !== "MOD_BURNED" )
	{
		return false;
	}

	if ( vehicle === eInflictor ) // this is how we do continuesly burning damage 
	{
		return false;
	}

	causer = ( isdefined( eAttacker ) ? eAttacker : eInflictor );
	if ( !isdefined( causer ) )
	{
		return true;
	}

	if ( IsAI( causer ) && IsVehicle( causer ) ) // don't make burning contagious between AI vehicles
	{
		return false;
	}
         
	if ( level.teamBased )
	{		
		return ( vehicle.team != causer.team );
	}
	else
	{
		if ( isdefined( vehicle.owner ) )
		{
			return ( vehicle.owner != causer );
		}	
		
		return ( vehicle != causer );
	}
}

//*****************************************************************************
//
// 	Vehicle AI states
// 
//	- These are the default states if the vehicle type don't change the callback
//	- To change a specific callback:
//	    self get_state_callbacks("combat").update_func = custom_update_func;
//	- update_func will be called on a thread, generally have a loop inside and endon( "change_state" )
//		* update_func can have wait, or set state in it
//	- enter_func and exit_func are guaranteed to be called on state transition, so it's good for init and clean up for a state
//		* enter_func and exit_func should not have wait, or set state in it
//
//*****************************************************************************

// self == vehicle
function StartInitialState( defaultState = "combat" )
{
	// we expect script_startstate to be only changed from Radiant, not in script, so we don't wait here

	params = SpawnStruct();
	params.isInitialState = true;

	// Set the first state
	if ( isdefined( self.script_startstate ) )
	{
		self set_state( self.script_startstate, params );
	}
	else
	{
		// Set the first state
		self set_state( defaultState, params );
	}	
}

// self == vehicle
function start_scripted( disable_death_state, no_clear_movement )
{
	params = spawnStruct();
	params.no_clear_movement = no_clear_movement;
	self set_state( "scripted", params );
	self._no_death_state = disable_death_state;
}

function stop_scripted( statename )
{
	if ( isAlive( self ) && is_instate( "scripted" ) )
	{
		if ( isdefined( statename ) )
		{
			self set_state( statename );
		}
		else 
		{
			self set_state( "combat" );
		}
	}
}

// self == vehicle
function set_role( rolename )
{
	self.current_role = rolename;
}

// self == vehicle
function set_state( name, params )
{
	self.state_machines[ self.current_role ] thread statemachine::set_state( name, params );
}

function evaluate_connections( eval_func, params )
{
	self.state_machines[ self.current_role ] statemachine::evaluate_connections( eval_func, params );
}

// self == vehicle
function get_state_callbacks( statename )
{
	rolename = "default";
	if ( isdefined( self.current_role ) )
	{
		rolename = self.current_role;
	}
	
	if ( IsDefined( self.state_machines[ rolename ] ) )
	{
		return self.state_machines[ rolename ].states[ statename ];
	}

	return undefined;
}

// self == vehicle
function get_state_callbacks_for_role( rolename, statename )
{
	if ( !isdefined( rolename ) )
	{
		rolename = "default";
	}

	if ( IsDefined( self.state_machines[ rolename ] ) )
	{
		return self.state_machines[ rolename ].states[ statename ];
	}

	return undefined;
}

// self == vehicle
function get_current_state()
{
	if ( IsDefined( self.current_role ) && IsDefined( self.state_machines[ self.current_role ].current_state ) )
	{
		return self.state_machines[ self.current_role ].current_state.name;
	}

	return undefined;
}

// self == vehicle
function get_previous_state()
{
	if ( IsDefined( self.current_role ) && IsDefined( self.state_machines[ self.current_role ].previous_state ) )
	{
		return self.state_machines[ self.current_role ].previous_state.name;
	}

	return undefined;
}

// self == vehicle
function get_next_state()
{
	if ( IsDefined( self.current_role ) && IsDefined( self.state_machines[ self.current_role ].next_state ) )
	{
		return self.state_machines[ self.current_role ].next_state.name;
	}

	return undefined;
}


// self == vehicle
function is_instate( statename )
{
	if ( IsDefined( self.current_role ) && IsDefined( self.state_machines[ self.current_role ].current_state ) )
	{
		return self.state_machines[ self.current_role ].current_state.name === statename;
	}

	return false;
}

// self == vehicle
function add_state( name, enter_func, update_func, exit_func )
{
	if ( IsDefined( self.current_role ) )
	{
		statemachine = self.state_machines[ self.current_role ];

		if ( IsDefined( statemachine ) )
		{
			state = statemachine statemachine::add_state( name, enter_func, update_func, exit_func );
			return state;
		}
	}

	return undefined;
}

// self == vehicle
function add_interrupt_connection( from_state_name, to_state_name, on_notify, checkfunc )
{
	self.state_machines[ self.current_role ] statemachine::add_interrupt_connection( from_state_name, to_state_name, on_notify, checkfunc );
}

// self == vehicle
function add_utility_connection( from_state_name, to_state_name, checkfunc, defaultScore )
{
	self.state_machines[ self.current_role ] statemachine::add_utility_connection( from_state_name, to_state_name, checkfunc, defaultScore );
}

// self == vehicle
function init_state_machine_for_role( rolename )
{
	if ( !isdefined( rolename ) )
	{
		rolename = "default";
	}

	statemachine = statemachine::create( rolename, self );
	statemachine.isRole = true;

	if ( !IsDefined( self.current_role ) )
	{
		set_role( rolename );
	}
	
	// special states
	statemachine statemachine::add_state( "suspend", // empty state used to "suspend" the state machine, not to be confused with "off" state
						  undefined,
						  undefined,
						  undefined );

	statemachine statemachine::add_state( "death",
						  &defaultstate_death_enter,
						  &defaultstate_death_update,
						  undefined );
	
	statemachine statemachine::add_state( "scripted",
						  &defaultstate_scripted_enter,
						  undefined, // no update (scripter taking manual control or player controlling)
						  &defaultstate_scripted_exit );

	// general states
	statemachine statemachine::add_state( "combat",
						  &defaultstate_combat_enter,
						  undefined,
						  &defaultstate_combat_exit );

	statemachine statemachine::add_state( "emped",
						  &defaultstate_emped_enter,
						  &defaultstate_emped_update,
						  &defaultstate_emped_exit,
						  &defaultstate_emped_reenter );
	
	statemachine statemachine::add_state( "surge",
						  &defaultstate_surge_enter,
						  &defaultstate_surge_update,
						  &defaultstate_surge_exit );

	statemachine statemachine::add_state( "off",
						  &defaultstate_off_enter,
						  undefined,
						  &defaultstate_off_exit );

	statemachine statemachine::add_state( "driving",
						  &defaultstate_driving_enter,
						  undefined,
						  &defaultstate_driving_exit );

	statemachine statemachine::add_state( "pain",
						  &defaultstate_pain_enter,
						  undefined,
						  &defaultstate_pain_exit );


	statemachine statemachine::add_interrupt_connection( "off",		"combat",	"start_up" );
	statemachine statemachine::add_interrupt_connection( "driving",	"combat",	"exit_vehicle" );
	statemachine statemachine::add_utility_connection( "emped",	"combat" );
	statemachine statemachine::add_utility_connection( "pain",	"combat" );

	statemachine statemachine::add_interrupt_connection( "combat",	"emped",	"emped" );
	statemachine statemachine::add_interrupt_connection( "pain",	"emped",	"emped" );
	statemachine statemachine::add_interrupt_connection( "emped",	"emped",	"emped" );// emped vehicle can be emped again, it will delay the recover

	statemachine statemachine::add_interrupt_connection( "combat",	"surge",	"surge" );
	statemachine statemachine::add_interrupt_connection( "off",		"surge",	"surge" );
	statemachine statemachine::add_interrupt_connection( "pain",	"surge",	"surge" );
	statemachine statemachine::add_interrupt_connection( "emped",	"surge",	"surge" );
	
	statemachine statemachine::add_interrupt_connection( "combat",	"off",		"shut_off" );
	statemachine statemachine::add_interrupt_connection( "emped",	"off",		"shut_off" );
	statemachine statemachine::add_interrupt_connection( "pain",	"off",		"shut_off" );
	// no "driving" to "off". the player will need to be kicked out first.

	statemachine statemachine::add_interrupt_connection( "combat",	"driving",	"enter_vehicle" );
	statemachine statemachine::add_interrupt_connection( "emped",	"driving",	"enter_vehicle" );
	statemachine statemachine::add_interrupt_connection( "off",		"driving",	"enter_vehicle" );
	statemachine statemachine::add_interrupt_connection( "pain",	"driving",	"enter_vehicle" );

	statemachine statemachine::add_interrupt_connection( "combat",	"pain",		"pain" );
	statemachine statemachine::add_interrupt_connection( "emped",	"pain",		"pain" );
	statemachine statemachine::add_interrupt_connection( "off",		"pain",		"pain" );
	statemachine statemachine::add_interrupt_connection( "driving",	"pain",		"pain" );

	// no connection to "death". "death" state is handled in this callback as a special case
	self.overrideVehicleKilled = &Callback_VehicleKilled;
	self.overrideVehicleDeathPostGame = &Callback_VehicleKilled;
	
	// no connection to "scripted". start_scripted and stop_scripted are used to control the transition in and out of "scripted" state.

	statemachine thread statemachine::set_state("suspend");

	self thread on_death_cleanup();
	
	return statemachine;
}

//if levels or gamemodes want to add custom vehicle states, they can register them to this list

function register_custom_add_state_callback( func )
{
	if( !IsDefined( level.level_specific_add_state_callbacks ) )
	{
		level.level_specific_add_state_callbacks = [];
	}
	
	level.level_specific_add_state_callbacks[level.level_specific_add_state_callbacks.size] = func;
}

function call_custom_add_state_callbacks()
{
	if( IsDefined( level.level_specific_add_state_callbacks ) )
    {
    	for( i = 0; i < level.level_specific_add_state_callbacks.size; i++ )
    	{
    		self [[ level.level_specific_add_state_callbacks[i] ]]();
    	}
    }
}

// ----------------------------------------------
// State: death
// ----------------------------------------------
// self == vehicle
function Callback_VehicleKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime )
{
	if ( ( isdefined( self._no_death_state ) && self._no_death_state ) )
	{
		return;
	}

	death_info = SpawnStruct();
	death_info.inflictor = eInflictor;
	death_info.attacker = eAttacker;
	death_info.damage = iDamage;
	death_info.meansOfDeath = sMeansOfDeath;
	death_info.weapon = weapon;
	death_info.dir = vDir;
	death_info.hitLoc = sHitLoc;
	death_info.timeOffset = psOffsetTime;

	self set_state( "death", death_info );
}

function on_death_cleanup()
{
	state_machines = self.state_machines;
	
	self waittill("free_vehicle");
	
	//Clear State Machine or we will have a script leak caused by cross reference
	foreach(stateMachine in state_machines)
	{
		stateMachine statemachine::clear();
	}
}

function defaultstate_death_enter( params )
{
	self vehicle::toggle_tread_fx( false );
	self vehicle::toggle_exhaust_fx( false );
	self vehicle::toggle_sounds( false );
	self DisableAimAssist();

	TurnOffAllLightsAndLaser();
	TurnOffAllAmbientAnims();
	ClearAllLookingAndTargeting();
	ClearAllMovement();
	self CancelAIMove();

	//self SetBrake( 1 );
	self.takedamage = 0;
	self vehicle_death::death_cleanup_level_variables();
}

function burning_death_fx()
{
	if ( isdefined( self.settings.burn_death_fx_1 ) && isdefined( self.settings.burn_death_tag_1 ) )
	{
		PlayFxOnTag( self.settings.burn_death_fx_1, self, self.settings.burn_death_tag_1 );
	}
	
	if ( isdefined( self.settings.burn_death_sound_1 ) )
	{
		self PlaySound( self.settings.burn_death_sound_1 );
	}
}

function emp_death_fx()
{
	if ( isdefined( self.settings.emp_death_fx_1 ) && isdefined( self.settings.emp_death_tag_1 ) )
	{
		PlayFxOnTag( self.settings.emp_death_fx_1, self, self.settings.emp_death_tag_1 );
	}
	
	if ( isdefined( self.settings.emp_death_sound_1 ) )
	{
		self PlaySound( self.settings.emp_death_sound_1 );
	}
}

function death_radius_damage_special( radiusScale, meansOfDamage )
{
	self endon( "death" );

	if ( !isdefined( self ) || self.abandoned === true || self.damage_on_death === false || self.radiusdamageradius <= 0 )
	{
		return;
	}

	position = self.origin + ( 0,0,15 );
	radius = self.radiusdamageradius * radiusScale;
	damageMax = self.radiusdamagemax;
	damageMin = self.radiusdamagemin;

	{wait(.05);};

	if ( isdefined( self ) )
	{
		self RadiusDamage( position, radius, damageMax, damageMin, undefined, meansOfDamage );
	}
}

function burning_death( params )
{
	self endon( "death" );
	self burning_death_fx();
	self.skipFriendlyFireCheck = true; // burning explosion damage friendlies
	self thread death_radius_damage_special( 2, "MOD_BURNED" );
	self vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay );
	self vehicle::do_death_dynents( 3 );
	self vehicle_death::DeleteWhenSafe( 10 );
}

function emped_death( params )
{
	self endon( "death" );
	self emp_death_fx();
	self.skipFriendlyFireCheck = true; // emp explosion damage friendlies
	self thread death_radius_damage_special( 2, "MOD_ELECTROCUTED");
	self vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay );
	self vehicle::do_death_dynents( 2 );
	self vehicle_death::DeleteWhenSafe();
}

function gibbed_death( params )
{
	self endon( "death" );
	self vehicle_death::death_fx();
	self thread vehicle_death::death_radius_damage();
	self vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay );
	self vehicle::do_death_dynents();
	self vehicle_death::DeleteWhenSafe();
}

function default_death( params )
{
	self endon( "death" );
	self vehicle_death::death_fx();
	self thread vehicle_death::death_radius_damage();
	self vehicle_death::set_death_model( self.deathmodel, self.modelswapdelay );

	if( isdefined( level.disable_thermal ) )
	{
		[[level.disable_thermal]]();
	}

	waittime = (isdefined(self.waittime_before_delete)?self.waittime_before_delete:0);

	owner = self GetVehicleOwner();
	if ( isDefined( owner ) && self isRemoteControl() )
	{
		// make sure player see some destruction
		waittime = max( waittime, 4 );
	}
	
	util::waitForTime( waittime );
	vehicle_death::FreeWhenSafe();
}

function get_death_type( params )
{
	if ( self.delete_on_death === true )
	{
		death_type = "default";
	}
	else
	{
		death_type = self.death_type;
	}

	if ( !isdefined( death_type ) )
	{
		death_type = params.death_type;
	}

	// burning
	if( !isdefined( death_type ) && isdefined( self.abnormal_status ) && self.abnormal_status.burning === true )
	{
		death_type = "burning";
	}

	// emped
	if ( !isdefined( death_type ) && ( isdefined( self.abnormal_status ) && self.abnormal_status.emped === true ) ||
		( isdefined( params.weapon ) && params.weapon.isEmp ) )
	{
		death_type = "emped";
	}

	return death_type;
}

function defaultstate_death_update( params )
{
	self endon( "death" );

	if( IsDefined( level.vehicle_destructer_cb ) )
	{
    	[[level.vehicle_destructer_cb]]( self );
	}

	if ( self.delete_on_death === true )
	{
		default_death( params );
		vehicle_death::DeleteWhenSafe( 0.25 );
	}
	else
	{
		death_type = (isdefined(get_death_type( params ))?get_death_type( params ):"default");

		switch( death_type )
		{
		case "burning": burning_death( params ); break;
		case "emped": emped_death( params ); break;
		case "gibbed": gibbed_death( params ); break;
		default: default_death( params ); break;
		}
	}
}

// ----------------------------------------------

// ----------------------------------------------
// State: scripted
// ----------------------------------------------
function defaultstate_scripted_enter( params )
{
	if ( params.no_clear_movement !== true )
	{
		ClearAllLookingAndTargeting();
		ClearAllMovement();
		if ( HasASM( self ) )
		{
			self ASMRequestSubstate( "locomotion@movement" );
		}
		self ResumeSpeed();
	}
}

// no defaultstate_scripted_update() function

function defaultstate_scripted_exit( params )
{
	if ( params.no_clear_movement !== true )
	{
		ClearAllLookingAndTargeting();
		ClearAllMovement();
	}
}

// ----------------------------------------------
// State: combat
// ----------------------------------------------
function defaultstate_combat_enter( params )
{
}

function defaultstate_combat_exit( params )
{
}

// ----------------------------------------------
// State: emped
// ----------------------------------------------
function defaultstate_emped_enter( params )
{
	self vehicle::toggle_tread_fx( false );
	self vehicle::toggle_exhaust_fx( false );
	self vehicle::toggle_sounds( false );

	params.laserOn = IsLaserOn( self );

	self LaserOff();
	self vehicle::lights_off();
	ClearAllLookingAndTargeting();
	ClearAllMovement();

	if( IsAirborne( self ) )
	{
		self SetRotorSpeed( 0 );
	}

	if ( !isdefined( self.abnormal_status ) )
	{
		self.abnormal_status = spawnStruct();
	}

	self.abnormal_status.emped = true;
	self.abnormal_status.attacker = params.notify_param[1];
	self.abnormal_status.inflictor = params.notify_param[2];

	self vehicle::toggle_emp_fx( true );
}

function emp_startup_fx()
{
	if ( isdefined( self.settings.emp_startup_fx_1 ) && isdefined( self.settings.emp_startup_tag_1 ) )
	{
		PlayFxOnTag( self.settings.emp_startup_fx_1, self, self.settings.emp_startup_tag_1 );
	}
}

function defaultstate_emped_update( params )
{
	self endon ("death");
	self endon ("change_state");
	
	time = params.notify_param[0];
	assert( isdefined( time ) );
	Cooldown( "emped_timer", time );

	while( !IsCooldownReady( "emped_timer" ) )
	{
		timeLeft = max( GetCooldownLeft( "emped_timer" ), 0.5 );
		wait timeLeft;
	}

	self.abnormal_status.emped = false;
	self vehicle::toggle_emp_fx( false );
	self emp_startup_fx();
	wait 1;

	self evaluate_connections();
}

function defaultstate_emped_exit( params )
{
	self vehicle::toggle_tread_fx( true );
	self vehicle::toggle_exhaust_fx( true );
	self vehicle::toggle_sounds( true );

	if ( params.laserOn === true )
	{
		self LaserOn();
	}
	self vehicle::lights_on();
	if( IsAirborne( self ) )
	{
		self SetPhysAcceleration( ( 0, 0, 0 ) );
		self thread nudge_collision();
		self SetRotorSpeed( 1 );
	}
}

function defaultstate_emped_reenter( params )
{
	return true;
}

// ----------------------------------------------
// State: surge
// ----------------------------------------------

function defaultstate_surge_enter( params )
{
}

function defaultstate_surge_exit( params )
{
}

function defaultstate_surge_update( params )
{
	self endon( "change_state" );
	self endon( "death" );

	if ( !isdefined( self.abnormal_status ) )
	{
		self.abnormal_status = spawnStruct();
	}

	self.abnormal_status.emped = true;
	//self.abnormal_status.attacker = params.notify_param[1];
	//self.abnormal_status.inflictor = params.notify_param[2];

	pathfailcount = 0;
	//blink the lights to make the vehicle look crazy
	self thread flash_team_switching_lights();
	
	targets = GetAITeamArray( "axis", "team3" );
	ArrayRemoveValue( targets, self );
	closest = ArrayGetClosest( self.origin, targets );
	
	self SetSpeed( self.settings.surgespeedmultiplier * self.settings.defaultMoveSpeed );
	
	startTime = GetTime();
	self thread swap_team_after_time( params.notify_param[0] );
	while( GetTime() - startTime < self.settings.surgetimetolive * 1000  )
	{
		if ( !IsDefined( closest ) )
		{
			self detonate( params.notify_param[0] );
		}
		else
		{
			foundpath = false;
			targetPos = closest.origin + ( 0, 0, 32 );

			if ( IsDefined( targetPos ) )
			{
				queryResult = PositionQuery_Source_Navigation( targetPos, 0, 64, 35, 5, self );

				foreach ( point in queryResult.data )
				{
					self.current_pathto_pos = point.origin;

					foundpath = self SetVehGoalPos( self.current_pathto_pos, false, true );
					if ( foundpath )
					{
						self thread path_update_interrupt( closest, params.notify_param[0] );
						
						pathfailcount = 0;

						self vehicle_ai::waittill_pathing_done( self.settings.surgetimetolive );
						
						try_detonate( closest, params.notify_param[0] );

						break;
					}
					waittillframeend;
				}
			}

			if ( !foundpath )
			{
				pathfailcount++;
				if ( pathfailcount > 10 )
				{
					self detonate( params.notify_param[0] );
				}
			}
			wait 0.2;
		}
	}
	if( IsAlive( self ) )
	{
		self detonate( params.notify_param[0] );
	}
}

function path_update_interrupt( closest, attacker )
{
	self endon( "death" );
	self endon( "change_state" );
	self endon( "near_goal" );
	self endon( "reached_end_node" );

	wait .1; 	// sometimes endons may get fired off so wait a bit for the goal to get updated
	
	while( !self try_detonate( closest, attacker ) )
	{
		if( isdefined( self.current_pathto_pos ) )
		{
			if( distance2dSquared( self.current_pathto_pos, self.goalpos ) > ( (self.goalradius) * (self.goalradius) ) )
			{
				wait 0.5;
				
				self notify( "near_goal" );
			}
		}
		wait 0.1;
	}
}

function swap_team_after_time( attacker )
{
	self endon( "death" );
	self endon( "change_state" );
	
	wait( 0.25 * self.settings.surgetimetolive );
	self SetTeam( attacker.team );
}

function try_detonate( closest, attacker )
{
	if ( IsDefined( closest ) && IsAlive( closest ) )
	{
		if( distanceSquared( closest.origin, self.origin ) < ( (80) * (80) ) )
		{
			self detonate( attacker );
			return true;
		}
	}
	
	return false;
}

function detonate( attacker )
{
	self SetTeam( attacker.team );
	self RadiusDamage( self.origin + ( 0, 0, 5 ), self.settings.surgedamageradius, 1500, 1000, attacker, "MOD_EXPLOSIVE" );
	if( IsAlive( self ) )
	{
		self kill();
	}
}

function flash_team_switching_lights()
{
	self endon( "death" );
	self endon( "change_state" );
	
	while( 1 )
	{
		self vehicle::lights_off();
		wait( 0.1 );
		self vehicle::lights_on( "allies" );
		wait( 0.1 );
		self vehicle::lights_off();
		wait( 0.1 );
		self vehicle::lights_on( "axis" );
		wait( 0.1 );
	}
}

// ----------------------------------------------
// State: off
// ----------------------------------------------
function defaultstate_off_enter( params )
{
	self vehicle::toggle_tread_fx( false );
	self vehicle::toggle_exhaust_fx( false );
	self vehicle::toggle_sounds( false );
	self DisableAimAssist();

	params.laserOn = IsLaserOn( self );

	TurnOffAllLightsAndLaser();
	TurnOffAllAmbientAnims();
	ClearAllLookingAndTargeting();
	ClearAllMovement();
	
	if( isdefined( level.disable_thermal ) )
	{
		[[level.disable_thermal]]();
	}
	
	if( IsAirborne( self ) )
	{
		if ( params.isInitialState !== true && params.no_falling !== true )
		{
			self SetPhysAcceleration( ( 0, 0, -300 ) );
			self thread level_out_for_landing();
		}
		self SetRotorSpeed( 0 );
	}
}

function defaultstate_off_exit( params )
{
	self vehicle::toggle_tread_fx( true );
	self vehicle::toggle_exhaust_fx( true );
	self vehicle::toggle_sounds( true );
	self EnableAimAssist();
	if( IsAirborne( self ) )
	{
		self SetPhysAcceleration( ( 0, 0, 0 ) );
		self thread nudge_collision();
		self SetRotorSpeed( 1 );
	}
	if ( params.laserOn === true )
	{
		self LaserOn();
	}
	
	if( isdefined( level.enable_thermal ) )
	{
		if( self get_next_state() !== "death" )
		{
			[[level.enable_thermal]]();
		}
	}
	
	self vehicle::lights_on();
}

// ----------------------------------------------
// State: driving
// ----------------------------------------------
function defaultstate_driving_enter( params )
{
	params.driver = self GetSeatOccupant( 0 );
	assert ( isdefined(params.driver) );

	self DisableAimAssist();
	
	if ( level.playersDrivingVehiclesBecomeInvulnerable )
	{
		params.driver EnableInvulnerability();
		params.driver.ignoreme = true;
	}

	self.turretRotScale = 1;
	self.team = params.driver.team;
	if ( HasASM( self ) )
	{
		self ASMRequestSubstate( "locomotion@movement" );
	}
	
	self SetHeliHeightCap( true );

	ClearAllLookingAndTargeting();
	ClearAllMovement();
	self CancelAIMove();
	
	if( isdefined( params.driver ) && !isdefined( self.customDamageMonitor ) )
	{
		self thread vehicle::monitor_damage_as_occupant( params.driver );
	}
}

function defaultstate_driving_exit( params )
{
	self EnableAimAssist();
	if( isdefined( params.driver ) )
	{
		params.driver DisableInvulnerability();
		params.driver.ignoreme = false;
	}
	self.turretRotScale = 1;

	self SetHeliHeightCap( false );
	
	ClearAllLookingAndTargeting();
	ClearAllMovement();
	
	if( isdefined( params.driver ) )
	{
		params.driver vehicle::stop_monitor_damage_as_occupant();
	}
}

// ----------------------------------------------
// State: pain
// ----------------------------------------------
function defaultstate_pain_enter( params )
{
	ClearAllLookingAndTargeting();
	ClearAllMovement();
}

function defaultstate_pain_exit( params )
{
	ClearAllLookingAndTargeting();
	ClearAllMovement();
}

// ----------------------------------------------
// Vehicle AI position finding



function CanSeeEnemyFromPosition( position, enemy, sight_check_height )
{
	sightCheckOrigin = position + (0,0,sight_check_height);
	return sighttracepassed( sightCheckOrigin, enemy.origin + (0,0,30), false, self );
}

function FindNewPosition( sight_check_height )
{
	if( self.goalforced )
	{
		goalpos = GetClosestPointOnNavMesh( self.goalpos, self.radius * 2, self.radius );
		///#sphere(self.goalpos,15,(0,1,0),1,true,10,100000 ) ;#/
		return goalpos;
	}
	
	point_spacing = 90;

	PixBeginEvent( "vehicle_ai_shared::FindNewPosition" );
	queryResult = PositionQuery_Source_Navigation( self.origin, 0, 2000, 300/*half height*/, point_spacing, self, point_spacing * 2 );
	PixEndEvent();

	// filter
	PositionQuery_filter_Random( queryResult, 0, 50 );
	PositionQuery_Filter_DistanceToGoal( queryResult, self );
	vehicle_ai::PositionQuery_Filter_OutOfGoalAnchor( queryResult, 50 );
	
	origin = self.goalpos;
		
	best_point = undefined;
	best_score = -999999;
	
	if ( isdefined( self.enemy ) )
	{
		PositionQuery_Filter_Sight( queryResult, self.enemy.origin, self GetEye() - self.origin, self, 0, self.enemy );
		self vehicle_ai::PositionQuery_Filter_EngagementDist( queryResult, self.enemy, self.settings.engagementDistMin, self.settings.engagementDistMax );
		
		if( turret::has_turret( 1 ) )
		{
			side_turret_enemy = turret::get_target( 1 );
			if( isdefined( side_turret_enemy ) && side_turret_enemy != self.enemy )
			{
				PositionQuery_Filter_Sight( queryResult, side_turret_enemy.origin, (0,0,sight_check_height), self, 20, self, "sight2" );
			}
		}
		
		if( turret::has_turret( 2 ) )
		{
			side_turret_enemy = turret::get_target( 2 );
			if( isdefined( side_turret_enemy ) && side_turret_enemy != self.enemy )
			{
				PositionQuery_Filter_Sight( queryResult, side_turret_enemy.origin, (0,0,sight_check_height), self, 20, self, "sight3" );
			}
		}
		
		foreach ( point in queryResult.data )
		{	
			/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "engagementDist" ] = -point.distAwayFromEngagementArea;    #/    point.score += -point.distAwayFromEngagementArea;;
			
			if( distance2dSquared( self.origin, point.origin ) < 170 * 170 )
			{
				/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "tooCloseToSelf" ] = -170;    #/    point.score += -170;;
			}
			
			if( isdefined( point.sight ) && point.sight )
			{
				/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "sight" ] = 250;    #/    point.score += 250;;
			}
			if( isdefined( point.sight2 ) && point.sight2 )
			{
				/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "sight2" ] = 150;    #/    point.score += 150;;
			}
			if( isdefined( point.sight3 ) && point.sight3 )
			{
				/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "sight3" ] = 150;    #/    point.score += 150;;
			}
			
			if ( point.score > best_score )
			{
				best_score = point.score;
				best_point = point;
			}
		}
	}
	else
	{
		foreach ( point in queryResult.data )
		{
			if( distance2dSquared( self.origin, point.origin ) < 170 * 170 )
			{
				/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "tooCloseToSelf" ] = -100;    #/    point.score += -100;;
			}
			
			if( point.score > best_score )
			{
				best_score = point.score;
				best_point = point;
			}		
		}
	}
		
	self vehicle_ai::PositionQuery_DebugScores( queryResult );
	
	if( isdefined( best_point ) )
	{
		/#
			//line( best_point, best_point + (0,0,100), (.3,1,.3), 1.0, true, 100 );
		#/
		origin = best_point.origin;
	}
	
	return origin + (0,0,10);
}

// ----------------------------------------------
// time handling
// ----------------------------------------------

// in seconds
// usage: attackStart = GetTime(); if( TimeSince( attackStart ) > 5 ) ...
function TimeSince( startTimeInMilliseconds )
{
	return ( GetTime() - startTimeInMilliseconds ) * 0.001;
}

function CooldownInit()
{
	if( !isdefined( self._cooldown ) )
	{
		self._cooldown = [];
	}
}

function Cooldown( name, time_seconds )
{
	CooldownInit();

	self._cooldown[ name ] = GetTime() + time_seconds * 1000;
}

function GetCooldownTimeRaw( name )
{
	CooldownInit();

	if ( !isdefined( self._cooldown[ name ] ) )
	{
		self._cooldown[ name ] = GetTime() - 1;
	}
	return self._cooldown[ name ];
}

function GetCooldownLeft( name )
{
	CooldownInit();

	return ( GetCooldownTimeRaw( name ) - GetTime() ) * 0.001;
}

function IsCooldownReady( name, timeForward_seconds )
{
	CooldownInit();

	if ( !isdefined( timeForward_seconds ) )
	{
		timeForward_seconds = 0;
	}

	cooldownReadyTime = self._cooldown[ name ];
	return !isdefined( cooldownReadyTime ) || GetTime() + timeForward_seconds * 1000 > cooldownReadyTime;
}

function ClearCooldown( name )
{
	CooldownInit();

	self._cooldown[ name ] = GetTime() - 1;
}

function AddCooldownTime( name, time_seconds )
{
	CooldownInit();

	self._cooldown[ name ] = GetCooldownTimeRaw( name ) + time_seconds * 1000;
}

function ClearAllCooldowns()
{
	if( isdefined( self._cooldown ) )
	{
		foreach ( str_name, cooldown in self._cooldown )
		{
			self._cooldown[ str_name ] = GetTime() - 1;
		}
	}
}

// ----------------------------------------------
// debug helpers for position query
// ----------------------------------------------

function PositionQuery_DebugScores( queryResult )
{
	if ( !( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) )
	{
		return;
	}
	
	foreach( point in queryResult.data )
	{
		point DebugScore( self );
	}
}

// self == pointStruct
function DebugScore( entity )
{
	/#
	if ( !isdefined( self._scoreDebug ) )
	{
		return;
	}
	
	if ( !( isdefined( GetDvarInt("hkai_debugPositionQuery") ) && GetDvarInt("hkai_debugPositionQuery") ) )
	{
		return;
	}

	step = 10;
	count = 1;
	
	color = (1,0,0);
	if ( self.score >= 0 )
	{
		color = (0,1,0);
	}

	RecordStar( self.origin, color );
	record3DText( "" + self.score + ":", self.origin - (0,0,step * count), color );
	foreach( name, score in self._scoreDebug )
	{
		count++;
		record3DText( name + " " + score, self.origin - (0,0,step * count), color );
	}
	#/
}


function _less_than_val( left, right )
{
	if ( !isdefined( left ) )
	{
		return false;
	}
	else if ( !isdefined( right ) )
	{
		return true;
	}

	return left < right;
}

function _cmp_val( left, right, descending )
{
	if ( descending )
	{
		return _less_than_val( right, left );
	}
	else
	{
		return _less_than_val( left, right );
	}
}

function _sort_by_score( left, right, descending )
{
	return _cmp_val( left.score, right.score, descending );
}

function PositionQuery_Filter_Random( queryResult, min, max )
{
	foreach( point in queryResult.data )
	{
		score = RandomFloatRange( min, max );
		/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "random" ] = score;    #/    point.score += score;;
	}
}

function PositionQuery_PostProcess_SortScore( queryResult, descending = true )
{
	sorted = array::merge_sort( queryResult.data, &_sort_by_score, descending );

	queryResult.data = sorted;
}

function PositionQuery_Filter_OutOfGoalAnchor( queryResult, tolerance = 1 )
{
	foreach( point in queryResult.data )
	{
		if ( point.distToGoal > tolerance )
		{
			score = -10000 - point.distToGoal * 10;
			/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "outOfGoalAnchor" ] = score;    #/    point.score += score;;
		}
	}
}

function PositionQuery_Filter_EngagementDist( queryResult, enemy, engagementDistanceMin, engagementDistanceMax )
{
	if( !isdefined( enemy ) )
		return;

	engagementDistance = ( engagementDistanceMin + engagementDistanceMax ) * 0.5;
	half_engagement_width = Abs( engagementDistanceMax - engagementDistance );
	
	enemy_origin = ( enemy.origin[0], enemy.origin[1], 0 );

	vec_enemy_to_self = VectorNormalize( ( self.origin[0], self.origin[1], 0 ) - enemy_origin );
		
	foreach( point in queryResult.data )
	{
		point.distAwayFromEngagementArea = 0;	// <-------  result value initialization
	
		vec_enemy_to_point = ( point.origin[0], point.origin[1], 0 ) - enemy_origin;
		

		dist_in_front_of_enemy = VectorDot( vec_enemy_to_point, vec_enemy_to_self );
		
		if( abs(dist_in_front_of_enemy) < engagementDistanceMin )
		{
			dist_in_front_of_enemy = -engagementDistanceMin;
		}
		
		dist_away_from_sweet_line = Abs( dist_in_front_of_enemy - engagementDistance );

		if( dist_away_from_sweet_line > half_engagement_width )
		{
			point.distAwayFromEngagementArea = dist_away_from_sweet_line - half_engagement_width;
		}
		
		too_far_dist = engagementDistanceMax * 1.1;
		too_far_dist_sq = ( (too_far_dist) * (too_far_dist) );
		
		dist_from_enemy_sq = distance2dSquared( point.origin, enemy_origin );
		
		if( dist_from_enemy_sq > too_far_dist_sq )
		{
			ratioSq = dist_from_enemy_sq / too_far_dist_sq;
			dist = ratioSq * too_far_dist;
			dist_outside = dist - too_far_dist;
			
			if( dist_outside > point.distAwayFromEngagementArea )
			{
				point.distAwayFromEngagementArea = dist_outside;
			}
		}
	}
}

function PositionQuery_Filter_DistAwayFromTarget( queryResult, targetArray, distance, tooClosePenalty )
{
	if ( !isdefined( targetArray ) || !isArray( targetArray ) )
	{
		return;
	}

	foreach( point in queryResult.data )
	{
		tooClose = false;
		foreach( target in targetArray )
		{
			origin = undefined;
			if ( IsVec( target ) )
			{
				origin = target;
			}
			else if ( IsSentient( target ) && IsAlive( target ) )
			{
				origin = target.origin;
			}
			else if ( IsEntity( target ) )
			{
				origin = target.origin;
			}

			if ( isdefined( origin ) && distance2dSquared( point.origin, origin ) < ( (distance) * (distance) ) )
			{
				tooClose = true;
				break;
			}
		}

		if ( tooClose )
		{
			/#    if ( !isdefined( point._scoreDebug ) )    {     point._scoreDebug = [];    }    point._scoreDebug[ "TooCloseToOthers" ] = tooClosePenalty;    #/    point.score += tooClosePenalty;;
		}
	}
}

function DistancePointToEngagementHeight( origin, enemy, engagementHeightMin, engagementHeightMax )
{
	if( !isdefined( enemy ) )
		return undefined;

	result = 0;

	engagementHeight = 0.5 * ( self.settings.engagementHeightMin + self.settings.engagementHeightMax );
	half_height = Abs( engagementHeightMax - engagementHeight );

	targetHeight = enemy.origin[2] + engagementHeight;
	distFromEngagementHeight = Abs( origin[2] - targetHeight );

	if ( distFromEngagementHeight > half_height )
	{
		result = distFromEngagementHeight - half_height;
	}

	return result;
}

function PositionQuery_Filter_EngagementHeight( queryResult, enemy, engagementHeightMin, engagementHeightMax )
{
	if( !isdefined( enemy ) )
		return;

	engagementHeight = 0.5 * ( engagementHeightMin + engagementHeightMax );
	half_height = Abs( engagementHeightMax - engagementHeight );

	foreach( point in queryResult.data )
	{
		point.distEngagementHeight = 0;	// <-------  result value initialization

		targetHeight = enemy.origin[2] + engagementHeight;
		distFromEngagementHeight = Abs( point.origin[2] - targetHeight );

		if ( distFromEngagementHeight > half_height )
		{
			point.distEngagementHeight = distFromEngagementHeight - half_height;
		}
	}
}

function PositionQuery_PostProcess_RemoveOutOfGoalRadius( queryResult, tolerance = 1 )
{
	for( i = 0; i < queryResult.data.size; i++ )
	{
		point = queryResult.data[i];
		if ( point.distToGoal > tolerance )
		{
			ArrayRemoveIndex( queryResult.data, i );
			i--;
		}
	}
}

function UpdatePersonalThreatBias_AttackerLockedOnToMe( threat_bias, bias_duration, get_perfect_info, update_last_seen ) // self == sentient
{
	UpdatePersonalThreatBias_ViaClientFlags( self.locked_on, threat_bias, bias_duration, get_perfect_info, update_last_seen );
}

function UpdatePersonalThreatBias_AttackerLockingOnToMe( threat_bias, bias_duration, get_perfect_info, update_last_seen ) // self == sentient
{
	UpdatePersonalThreatBias_ViaClientFlags( self.locking_on, threat_bias, bias_duration, get_perfect_info, update_last_seen );
}

function UpdatePersonalThreatBias_ViaClientFlags( client_flags, threat_bias, bias_duration, get_perfect_info = true, update_last_seen = true ) // self == sentient
{
	assert( isdefined( client_flags ) );
	
	remaining_flags_to_process = client_flags;
	for ( i = 0; remaining_flags_to_process && i < level.players.size; i++ )
	{
		attacker = level.players[ i ];
		if ( isdefined( attacker ) )
		{
			client_flag = ( 1 << attacker getEntityNumber() );
			if ( client_flag & remaining_flags_to_process )
			{
				self SetPersonalThreatBias( attacker, Int( threat_bias ), bias_duration );
				
				if ( get_perfect_info )
					self GetPerfectInfo( attacker, update_last_seen );
				
				remaining_flags_to_process &= ~client_flag;
			}
		}
	}
}

/#
function UpdatePersonalThreatBias_Bots( threat_bias, bias_duration ) // self == sentient
{
	foreach( player in level.players )
	{
		if (player util::is_bot())
		{
			self SetPersonalThreatBias( player, Int( threat_bias ), bias_duration );
		}
	}
}
#/

//switch target when someone starts to hijack 
function target_hijackers()
{
	self endon( "death" );
	
	while( 1 )
	{
		self waittill( "ccom_lock_being_targeted", hijackingPlayer );

		self GetPerfectInfo( hijackingPlayer, true );

		if( isPlayer( hijackingPlayer ) )
		{
			self SetPersonalThreatBias( hijackingPlayer, 1500, 4.0 );
		}
	}
}

