#include maps\mp\_utility;
#include common_scripts\utility;
//#include _vehicleLogic.gsc;

init()
{
	//tank support cut
	return;
	//tank support cut
	/*
	PrecacheVehicle( "bmp_mp" );
	PrecacheVehicle( "m1a1_mp" );
	PrecacheVehicle( "bradley_mp" );

	precacheModel("vehicle_bmp");
	precacheModel("vehicle_bradley");

	precacheModel("sentry_gun");
	precacheModel("vehicle_m1a1_abrams_d_static");
	precacheTurret( "abrams_minigun_mp" );
	
	/#
	setDevDvar( "tankDir", "" );
	setDevDvar( "tankForceTrigger", 0 );
	if ( getDvar( "tankDebug" ) == "" )
		setDevDvar( "tankDebug", 0 );
	#/
	
	level.killstreakFuncs["tank"] = ::useTank;
	level.tankFire = loadfx( "explosions/large_vehicle_explosion" );
	level.tankCover = loadfx( "props/american_smoke_grenade_mp" );

	level.otherDir["forward"] = "reverse";
	level.otherDir["reverse"] = "forward";

	tankSpawners = Vehicle_GetSpawnerArray();
	
	if ( !tankSpawners.size )
		return;
	
	if (!isDefined( getVehicleNode( "startnode", "targetname" ) ) )
	{
		assertEx ( !isDefined( getVehicleNode( "startnode", "targetname" ) ), "Vehicle spawn is setup but tank path is not setup in this level bug your friendly neighborhood LD."  );
		return false;
	}

	level.tankSpawner["allies"] = tankSpawners[0];
	level.tankSpawner["axis"] = tankSpawners[0];
	level.pathCount = 0; 
	
	foreach ( spawner in tankSpawners )
	{
		if ( isSubStr( spawner.model, "bradley" ) )
			level.tankSpawner["allies"] = spawner;
		
		if ( isSubStr( spawner.model, "bmp" ) )
			level.tankSpawner["axis"] = spawner;
	}
	
	level setupPaths();
	
	*/
}


spawnArmor( owner, vehicletype, model )
{
	armor = self Vehicle_DoSpawn( "tank", owner );
	//armor setModel( model );
	
	armor.health = 3000;
	armor.targeting_delay = 1;
	armor.team = owner.team;
	armor.pers["team"] = armor.team;
	armor.owner = owner;
	armor setCanDamage( true );
	armor.standardSpeed = 12;

	armor thread deleteOnZ();
	armor addToTankList();
	armor.damageCallback = ::Callback_VehicleDamage;
	
	return armor;
}

deleteOnZ()
{
	self endon ( "death" );
	
	originalZ = self.origin[2];
	
	for ( ;; )
	{
		if ( originalZ - self.origin[2] > 2048 )
		{
			self.health = 0;
			self notify( "death" );
			return;
		}
		wait ( 1.0 );
	}
}


useTank( lifeId )
{
	return ( self tryUseTank( ) );
}

tryUseTank( )
{
	if ( isDefined( level.tankInUse ) && level.tankInUse )
	{
		self iPrintLnBold( "Armor support unavailable." );
		return false;
	}
		
	if (!isDefined( getVehicleNode( "startnode", "targetname" ) ) )
	{
		self iPrintLnBold( "Tank is currently not supported in this level, bug your friendly neighborhood LD." );
		return false;
	}

	if ( !Vehicle_GetSpawnerArray().size )
		return false;
	
	if ( self.team == "allies" )
		tank = level.tankSpawner["allies"] spawnArmor( self, "vehicle_bradley" );	
	else
		tank = level.tankSpawner["axis"] spawnArmor( self, "vehicle_bmp" );	
	
	//level.tank = tank;
	tank startTank();
	return true;
}


startTank( tankType )
{
	startNode = getVehicleNode( "startnode", "targetname" );
	waitNode = getVehicleNode( "waitnode", "targetname" );
	self.nodes = GetVehicleNodeArray( "info_vehicle_node", "classname" );
	
	level.tankInUse = true;
	self thread tankUpdate( startNode, waitNode );
	//self thread tankUpdateReverse( startNode, waitNode );
	
	self thread tankDamageMonitor();
	level.tank = self;


	if ( level.teamBased )
	{
		objIDAllies = maps\mp\gametypes\_gameobjects::getNextObjID();
		objective_add( objIDAllies, "invisible", (0,0,0) );
		objective_team( objIDAllies, "allies" );
		level.tank.objID["allies"] = objIDAllies;
	
		objIDAxis = maps\mp\gametypes\_gameobjects::getNextObjID();
		objective_add( objIDAxis, "invisible", (0,0,0) );
		objective_team( objIDAxis, "axis" );
		level.tank.objID["axis"] = objIDAxis;
		
		team = self.team;
		level.tank.team = team;
		level.tank.pers[ "team" ] = team;
	}
	
	mgTurret = spawnTurret( "misc_turret", self.origin, "abrams_minigun_mp" );
    mgTurret linkTo( self, "tag_engine_left", (0,0,-20), (0,0,0) );
    mgTurret setModel( "sentry_minigun" );
    mgTurret.angles = self.angles; 
    mgTurret.owner = self.owner;
    mgTurret makeTurretInoperable();
    self.mgTurret = mgTurret; 
    self.mgTurret SetDefaultDropPitch( 0 );
	
	oldangles = self.angles;
	self.angles = (0,0,0);
	tankTurretPoint = self getTagOrigin( "tag_flash" );
	self.angles = oldangles;
	offset = tankTurretPoint - self.origin;
	
	self thread waitForChangeTeams();
	self thread waitForDisco();
	
	self.timeLastFired = getTime();
	
	neutralTargetEnt = spawn("script_origin", self getTagOrigin("tag_flash") );
	neutralTargetEnt linkTo( self, "tag_origin", offset, (0,0,0) );
	neutralTargetEnt hide();
	self.neutralTarget = neutralTargetEnt;
	
	self thread tankGetTargets();
	self thread destroyTank();
	self thread tankGetMiniTargets();
	self thread checkDanger();
	self thread watchForThreat(); //reacts to players about to fire with rockets
	
	/#
	self thread forceDirection();
	#/
	
}

waitForChangeTeams()
{
	self endon ( "death" );
	self.owner endon ( "disconnect" );
	
	self.owner waittill ( "joined_team" );
	self.health = 0;
	self notify( "death" );
}

waitForDisco()
{
	self endon ( "death" );
	
	self.owner waittill ( "disconnect" );
	self.health = 0;
	self notify( "death" );
}

/#
forceDirection()
{
	for ( ;; )
	{
		if ( getDvar( "tankDir" ) != "" )
		{
			forceDir = getDvar( "tankDir" );
			if ( self.veh_pathdir != forceDir )
			{
				if ( forceDir == "forward" )
					self stopToForward();
				else
					self stopToReverse();
			}
		}
		
		wait ( 0.05 );
	}
}
#/

//=================================================================
//
//					Movement/Update Functions
//
//=================================================================

setDirection( direction )
{
	if ( self.veh_pathdir != direction )
	{
		if ( direction == "forward" )
			self stopToForward();
		else
			self stopToReverse();
	}
}

setEngagementSpeed()
{
	self endon( "death" );

	self notify ( "path_abandoned" );

	while ( isDefined( self.changingDirection ) )
		wait ( 0.05 );

	newSpeed = 2;
	self vehicle_SetSpeed( newSpeed, 10, 10 );
	self.speedType = "engage";
}

setMiniEngagementSpeed()
{
	self endon( "death" );

	self notify ( "path_abandoned" );

	while ( isDefined( self.changingDirection ) )
		wait ( 0.05 );

	newSpeed = 2;
	self vehicle_SetSpeed( newSpeed, 10, 10 );
	self.speedType = "engage";
}

setStandardSpeed()
{
	self endon( "death" );

	while ( isDefined( self.changingDirection ) )
		wait ( 0.05 );

	self vehicle_SetSpeed( self.standardSpeed, 10, 10 );
	self.speedType = "standard";
}

setEvadeSpeed()
{
	self endon( "death" );

	while ( isDefined( self.changingDirection ) )
		wait ( 0.05 );

	self vehicle_setSpeed( 15, 15, 15 );
	self.speedType = "evade";
	wait(1.5);
	self vehicle_setSpeed( self.standardSpeed, 10, 10);	
}

setDangerSpeed()
{
	self endon( "death" );

	while ( isDefined( self.changingDirection ) )
		wait ( 0.05 );

	self vehicle_SetSpeed( 5, 5, 5 );
	self.speedType = "danger";
}

stopToReverse()
{
	debugPrintLn2( "tank changing direction at " + getTime() );
	self vehicle_setSpeed( 0, 5, 6 );
	
	self.changingDirection = true;
	while ( self.veh_speed > 0 )
		wait ( 0.05 );

	wait( 0.25 );
	self.changingDirection = undefined;
	debugPrintLn2( "tank done changing direction" );

	self.veh_transmission = "reverse";
	self.veh_pathDir = "reverse";
	self vehicle_setSpeed( self.standardSpeed, 5, 6 );
}

stopToForward()
{
	debugPrintLn2( "tank changing direction at " + getTime() );
	self vehicle_setSpeed( 0, 5, 6 );

	self.changingDirection = true;
	while ( self.veh_speed > 0 )
		wait ( 0.05 );

	wait( 0.25 );
	self.changingDirection = undefined;
	debugPrintLn2( "tank done changing direction" );

	self.veh_transmission = "forward";
	self.veh_pathDir = "forward";
	self vehicle_setSpeed( self.standardSpeed, 5, 6 );
}

checkDanger()
{
	self endon( "death" );
	
	targets = [];
	players = level.players;
	self.numEnemiesClose = 0;
	
	for( ;; )
	{
		foreach (potentialTarget in players)
		{
			if ( !isDefined( potentialTarget ) )
				continue;
			
			if ( potentialTarget.team == self.team )
			{
				wait( .05 );
				continue;
			}
			
			dist = Distance2d( potentialTarget.origin, self.origin );
			
			if ( dist < 2048 )
			{
				self.numEnemiesClose++;	
			}
			wait( .05 );
		}
		
		if ( isDefined( self.speedType ) && ( self.speedType == "evade" || self.speedType == "engage" ) )
		{
			self.numEnemiesClose = 0;
			continue;
		}
		
		if ( self.numEnemiesClose > 1 )
			self thread setDangerSpeed();
		else
			self thread setStandardSpeed();
		
		self.numEnemiesClose = 0;
		wait( .05 );
	}
}
			
tankUpdate( startNode, waitNode )
{
	self endon( "tankDestroyed" );
	self endon( "death" );
	
	if ( !isDefined( level.graphNodes ) )
	{
		self startPath( startNode );
		return;
	}
	
	self attachPath( startNode );
	self startPath( startNode );
	startNode notify ( "trigger", self, true );

	wait ( 0.05 );

	for ( ;; )
	{
		/#
		while ( getDvar( "tankDir" ) != "" )
			wait ( 0.05 );
		#/

		while ( isDefined( self.changingDirection ) )
			wait ( 0.05 );

		endNode = self getNodeNearEnemies();

		if ( isDefined( endNode ) )
			self.endNode = endNode;
		else
			self.endNode = undefined;

		wait ( 0.65 );
	}
}


Callback_VehicleDamage( inflictor, attacker, damage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName )
{
	if ( ( attacker == self || attacker == self.mgTurret || ( isDefined( attacker.pers ) && attacker.pers["team"] == self.team ) ) && ( attacker != self.owner || meansOfDeath == "MOD_MELEE" ) )
		return;
	
	tankDamage = modifyDamage( meansOfDeath, damage, attacker );

	self Vehicle_FinishDamage( inflictor, attacker, tankDamage, dFlags, meansOfDeath, weapon, point, dir, hitLoc, timeOffset, modelIndex, partName );
}


// accumulate damage and react
tankDamageMonitor()
{
	self endon( "death" );
	self.damageTaken = 0;
	speed = self vehicle_GetSpeed();
	maxHealth = self.health;
	stage1 = false;
	stage2 = false;
	stage3 = false;

	for( ;; )
	{
		self waittill( "damage", amount, attacker, direction_vec, point, damageType );
		
		if ( isDefined( attacker.classname ) && attacker.classname == "script_vehicle"  )
		{
			if ( isDefined( self.bestTarget ) && self.bestTarget != attacker )
			{
				self.forcedTarget = attacker;
				println( "Abandoning Target due to vehicle attacker" );	
				self thread explicitAbandonTarget();
			}			
		}
		else
		{
			if ( isPlayer( attacker ) )
			{
				attacker maps\mp\gametypes\_damagefeedback::updateDamageFeedback( "hitHelicopter" );
	
				if ( attacker _hasPerk( "specialty_armorpiercing" ) )
				{
					damageAdd = amount*level.armorPiercingMod;
					self.health -= int(damageAdd);
				}			
			}
		}
		
		//stages will be used to effect effeciency of the tank 
		//accuracy, speed, smoke emitters etc....
		if ( self.health <= 0 )
		{
			self notify( "death" );	
			print("sent death notify via script");
			return;
		}
		else if (self.health < (maxHealth/4) && stage3 == false )
		{
			//newSpeed = 4;
			//self vehicle_SetSpeed( newSpeed, 10, 10 );
			//self.standardSpeed = newSpeed;
			stage3 = true;
			
			
		}
		else if (self.health < (maxHealth/2) && stage2 == false )
		{
			//newSpeed = 6;
			//self vehicle_SetSpeed( newSpeed, 10, 10 );
			//self.standardSpeed = newSpeed;
			stage2 = true;
			
			
		}
		else if (self.health < (maxHealth/1.5) && stage1 == false )
		{
			//newSpeed = 10;
			//self vehicle_SetSpeed( newSpeed, 10, 10 );
			//self.standardSpeed = newSpeed;
			stage1 = true;
		}
		
		if ( amount > 1000 )
		{
			self handleThreat( attacker );
		}
	}
}

handleThreat( attacker )
{
	self endon( "death" );
	
	rand = randomInt(100);
	
	if ( isDefined( self.bestTarget) && self.bestTarget != attacker && rand > 30 )
	{	
		targ = [];
		targ[0] = self.bestTarget;
		explicitAbandonTarget( true, self.bestTarget );
		self thread acquireTarget( targ );
	}
	else if ( !isDefined( self.bestTarget ) && rand > 30 )
	{
		targ = [];
		targ[0] = attacker;
		self thread acquireTarget( targ );	
	}
	else if ( rand < 30 )
	{
		// all we know here is that it didnt hit the 70%
		playFX( level.tankCover, self.origin);
		self thread setEvadeSpeed();
	}
	else
	{
		self fireWeapon();	
		self playSound( "bmp_fire" );
	}
}

handlePossibleThreat( attacker )
{
	self endon( "death" );
	
	position = relativeAngle( attacker );
	distance = distance( self.origin, attacker.origin );
	
	if ( RandomInt( 4 ) < 3 )
		return;
	
	if( position == "front"  && distance < 768 ) //attempts to crush player
	{
		self thread setEvadeSpeed();
	}
	else if ( position == "rear_side" || ( position == "rear" && distance >= 768 ) )
	{
		playFX( level.tankCover, self.origin);
		self thread setEvadeSpeed();	
	}
	else if( position == "rear" && distance < 768 ) //attempts to crush player
	{	
		
		self stopToReverse();
		self setEvadeSpeed();
		wait( 4 );
		self stopToForward();

	}
	else if( position == "front_side" || position == "front" )
	{
		playFX( level.tankCover, self.origin);
		self stopToReverse();
		self setEvadeSpeed();
		wait( 8 );
		self stopToForward();
	}
}

relativeAngle( ent1 )
{
	self endon( "death" );
	ent1 endon( "death" );
	ent1 endon( "disconnect" );
	
	tankForwardVector = anglesToForward( self.angles );
	tankToEnt = ent1.origin - self.origin;
	tankForwardVector *= (1,1,0);
	tankToEnt *= (1,1,0 );
	
	tankToEnt = VectorNormalize( tankToEnt );
	TankForwardVector = VectorNormalize( tankForwardVector );
	
	targetCosine = VectorDot( tankToEnt, tankForwardVector );
	
	if ( targetCosine > 0 )
	{
		if ( targetCosine > .9 )
			return "front";
		else
			return "front_side";
	}
	else
	{
		if ( targetCosine < -.9 )
			return "rear";
		else
			return "rear_side";
	}	
		
	ent1 iPrintLnBold( targetCosine );
}

watchForThreat()
{
	self endon( "death" );
	
	for ( ;; )
	{
		targets = [];
		players = level.players;
		
		foreach (player in players)
		{
			if ( !isDefined( player ) )
			{
				wait( .05 );
				continue;
			}
			
			if ( !isTarget( player ) )
			{
				wait ( .05 );
				continue;
			}
			
			currentWeapon = player GetCurrentWeapon();

			if ( isSubStr( currentWeapon, "at4" ) || isSubStr( currentWeapon, "stinger" ) || isSubStr( currentWeapon, "javelin" ) )
			{
				self thread handlePossibleThreat( player );
				wait( 8 );
			}
			
			wait( .15 );
		}
	}
}

//=================================================================
//
//					Accessory Functions
//
//=================================================================

// checks if owner is valid, returns false if not valid
checkOwner()
{
	if ( !isdefined( self.owner ) || !isdefined( self.owner.pers["team"] ) || self.owner.pers["team"] != self.team )
	{
		self notify ( "abandoned" );
		return false;	
	}
	return true;
}

drawLine( start, end, timeSlice, color )
{
	drawTime = int(timeSlice * 20);
	for( time = 0; time < drawTime; time++ )
	{
		line( start, end, color,false, 1 );
		wait ( 0.05 );
	}
}

modifyDamage( damageType, amount, attacker )
{
	if ( damageType == "MOD_RIFLE_BULLET" )	
		return ( amount );
	else if ( damageType == "MOD_PISTOL_BULLET" )
		return ( amount );
	else if ( damageType == "MOD_IMPACT" )
		return ( amount );
	else if (damageType == "MOD_MELEE" )
		return ( 0 );
	else if (damageType == "MOD_EXPLOSIVE_BULLET" )
		return ( amount );
	else if (damageType == "MOD_GRENADE" )
		return ( amount * 5 );
	else if (damageType == "MOD_GRENADE_SPLASH" )
		return ( amount * 5 );
	else 
		return amount * 10;
}

destroyTank()
{
	self waittill ( "death" );
	
	if ( level.teamBased )
	{
		team = level.tank.team;
		objective_state( level.tank.objID[team], "invisible" );		
		objective_state( level.tank.objID[level.otherTeam[team]], "invisible" );
	}
	
	/* get the current team
	if ( isDefined( level.tankSpawner["axis"] ) )
		destroyedModel = ;
	else
		destroyedModel = ;
	*/
	
	// award attacker
	self notify( "tankDestroyed" );
	self Vehicle_SetSpeed( 0,10,10 );
	level.tankInUse = false;
	playFX( level.spawnFire, self.origin);
	
	playFX( level.tankFire, self.origin);
	
	self removeFromTankList();
	
 	destroyedTank = Spawn( "script_model", self.origin );
 	// set model to current destroyed model.
    destroyedTank setModel( "vehicle_m1a1_abrams_d_static" );
    destroyedTank.angles = self.angles; 
	self.mgTurret delete();
	self delete();
	
	wait(4);
	destroyedTank delete();
}

//=================================================================
//
//					Main Weapon Targeting Functions
//
//=================================================================

onHitPitchClamp()
{	
	self notify( "onTargOrTimeOut" );
	
	self endon( "onTargOrTimeOut" );
	self endon( "turret_on_target" );
	
	self waittill( "turret_pitch_clamped" );
	println( "Abandoning Target due to turret not being able to reach target" );	
	self thread explicitAbandonTarget( false, self.bestTarget );
}

fireOnTarget()
{
	self endon( "abandonedTarget" );
	self endon( "killedTarget" );
	self endon( "death" );
	self endon( "targetRemoved" );
	self endon( "lostLOS" );
	
	
	for ( ;; )
	{
		self onHitPitchClamp();
		
		if ( !isDefined( self.bestTarget ) )
			continue;
		
		flashOrigin = self GetTagOrigin( "tag_flash" );	
		trace = BulletTrace( self.origin, flashOrigin, false, self );
		if ( trace["position"] != flashOrigin )
		{
			println( "Abandoning Target due to turret not being able to reach target without clipping" );	
			self thread explicitAbandonTarget( false, self.bestTarget );
		}
		
		trace = BulletTrace( flashOrigin, self.bestTarget.origin, true, self );
		distance = Distance(self.origin, trace["position"] );
		realDistance = Distance( self.bestTarget.origin, self.origin );
		
		//hitting somthing not even close
		if ( distance < 384 || distance + 256 < realDistance ) 
		{
			wait( .5 );
			
			if ( distance > 384 )
			{
				self waitForTurretReady();
				self FireWeapon();
				self playSound( "bmp_fire" );
				self.timeLastFired = getTime();
			}
			println( "Abandoning due to not hitting intended space" );
			
			// Adjust forward or backward to hit target...
			// check angle of target
			position = relativeAngle( self.bestTarget );
			
			//if ( position == "rear_side" )
				// backup
			//if ( position == "front_side" )
			
			
			self thread explicitAbandonTarget( false, self.bestTarget );
			return;
		}
			
		self waitForTurretReady();
		
		self FireWeapon();
		self playSound( "bmp_fire" );
		self.timeLastFired = getTime();
	}
}

waitForTurretReady()
{
	self endon( "abandonedTarget" );
	self endon( "killedTarget" );
	self endon( "death" );
	self endon( "targetRemoved" );
	self endon( "lostLOS" );
	
	timeWaited = getTime() - self.timeLastFired;
		
	if ( timeWaited < 1499 )
			wait( 1.5 - timeWaited/1000 );
}

tankGetTargets( badTarget )
{
	self endon( "death" );
	self endon( "leaving" );
	targets = [];
	
	prof_begin( "tankTargets" );
	
	for ( ;; )
	{
		targets = [];
		players = level.players;
		
		if ( isDefined( self.forcedTarget ) )
		{
			targets = [];
			targets[0] = self.ForcedTarget;
			self acquireTarget( targets );
			self.forcedTarget = undefined;
		}
		
		if ( isDefined( level.harrier ) && level.harrier.team != self.team && isAlive( level.harrier ) )
		{
			if( isVehicleTarget( level.tank ) )
				targets[targets.size] = level.tank;
		}
		
		if ( isDefined( level.chopper ) && level.chopper.team != self.team  && isAlive( level.chopper ) )
		{	
			if( isVehicleTarget( level.chopper ) )
				targets[targets.size] = level.chopper;
		}
		
		foreach ( potentialTarget in players )
		{
			if (!isDefined( potentialTarget ) )
			{
				wait(.05);
				continue;
			}
			
			if ( isDefined( badTarget ) && potentialTarget == badTarget ) 
				continue;
			
			if ( isTarget( potentialTarget ) )
			{
				if( isDefined( potentialTarget ) )
					targets[targets.size] = potentialTarget;
			}
			else
				continue;
		}
		if ( targets.size > 0 )
		{
			self acquireTarget( targets );
		}
		else
			wait( 1 );
	}
	prof_end( "tankTargets" );
}

acquireTarget( targets )
{
	self endon( "death" );
	
	if ( targets.size == 1 )
		self.bestTarget = targets[0];
	else
		self.bestTarget = self getBestTarget( targets );
	
	self thread setEngagementSpeed(); // slows tank down to fire on target
	
	// checks to abandon target
	//self thread lostTarget(); // sets lost LOS and time of lost target
	//self thread abandonTarget(); // if target is lost for 3+ seconds drops target and gets new one
	self thread watchTargetDeath( targets ); //abandons target when target killed
	
	
	self SetTurretTargetEnt( self.bestTarget );	// sets turret to target entity
	self fireOnTarget(); // fires on current target.
	self thread setNoTarget();
}

setNoTarget()
{
	self endon( "death" );
	
	self setStandardSpeed();
	self removeTarget();
	self setTurretTargetEnt( self.neutralTarget );
}

getBestTarget( targets )
{
	self endon( "death" );
	mainGunPointOrigin = self getTagOrigin( "tag_flash" );
	tankOrigin = self.origin;
	
	bestYaw = undefined;
	bestTarget = undefined;
	targetHasRocket = false;
	
	foreach ( targ in targets )
	{
		angle = abs ( vectorToAngles ( ( targ.origin - self.origin ) )[1] );
		cannonAngle = abs( self getTagAngles( "tag_flash" )[1] );
		angle = abs ( angle - cannonAngle );			
		
		//vehicle priorities
		if ( isDefined( level.chopper ) && targ == level.chopper )
			return targ;
		
		if ( isDefined( level.harrier ) && targ == level.harrier )
			return targ;
		
		// in this calculation having a rocket removes 40d of rotation cost from best target calculation
		// to prioritize targeting dangerous targets.
		weaponsArray = targ GetWeaponsListItems();
		foreach ( weapon in weaponsArray )
		{
			if ( isSubStr( weapon, "at4" ) || isSubStr( weapon, "jav" ) || isSubStr( weapon, "c4" ) )
				angle -= 40;
		}
				
		if ( !isDefined( bestYaw ) )
		{				
			bestYaw = angle;
			bestTarget = targ;
		} 
		else if ( bestYaw > angle )
		{
			bestYaw = angle;
			bestTarget = targ;			
		}
	}
	
	return ( bestTarget );
}

watchTargetDeath( targets )
{
	self endon( "abandonedTarget" );
	self endon( "lostLOS" );
	self endon( "death" );
	self endon( "targetRemoved" );
		
	bestTarg = self.bestTarget;
	bestTarg endon ( "disconnect" );
	
	bestTarg waittill( "death" );
	
	self notify( "killedTarget" );
	self removeTarget();
	self setStandardSpeed();
	self thread setNoTarget();
}

explicitAbandonTarget( noNewTarget, targ )
{
	self endon( "death" );
	
	self notify( "abandonedTarget" );
	println("ABANDON TARGET EXPLICIT");
	self setStandardSpeed();
	self thread Setnotarget();
	self removeTarget();
	
	if ( isDefined(targ) )
	{
		self.badTarget = targ;
		badTargetReset();
	}	
	
	if ( isDefined(noNewTarget) && noNewTarget )
		return;
	
	return;
}

badTargetReset()
{
	self endon("death");
	
	wait (1.5);
	self.badTarget = undefined;		
}

removeTarget()
{
	self notify( "targetRemoved" );
	
	self.bestTarget = undefined;
	self.lastLostTime = undefined;	
}

isVehicleTarget( potentialTarget )
{
	if ( distance2D( potentialTarget.origin, self.origin ) > 4096 )
		return false;
	
	if ( distance( potentialTarget.origin , self.origin ) < 512 )
		return false;
		
	return turretSightTrace( potentialTarget, false );
}

isTarget( potentialTarget )
{
	self endon( "death" );
	
	dist = distanceSquared( potentialTarget.origin, self.origin );
	
	if ( !level.teamBased && isDefined( self.owner ) && potentialTarget == self.owner )
		return false;
	
	if ( !isalive( potentialTarget ) || potentialTarget.sessionstate != "playing" )
		return false;
		
	if ( dist > 4096*4096 )
		return false;
	
	if ( dist < 512*512 )
		return false;
	
	if ( !isdefined( potentialTarget.pers["team"] ) )
		return false;
		
	if ( potentialTarget == self.owner )
		return false;
	
	if ( level.teamBased && potentialTarget.pers["team"] == self.team )
		return false;
	
	if ( potentialTarget.pers["team"] == "spectator" )
		return false;
	
	if ( isdefined( potentialTarget.spawntime ) && ( gettime() - potentialTarget.spawntime )/1000 <= 5 )
		return false;

	if ( potentialTarget _hasPerk( "specialty_coldblooded" ) )
		return false;
	
	return self Vehicle_CanTurretTargetPoint( potentialTarget.origin, 1, self );
	
	//return self turretSightTrace( potentialTarget, false );
}

turretSightTrace( targ, debug )
{
	turretCanSeeTarget = targ sightConeTrace( self getTagOrigin( "tag_turret" ), self );
	
	if ( turretCanSeeTarget < .7 )
	{
		return false;	
	}
	
	if ( isDefined(debug) && debug )
		self thread drawLine( targ.origin, self getTagOrigin( "tag_turret" ), 10, (1,0,0) );
	
	return true;	
}

//=================================================================
//
//					Secondary Weapon Targeting Functions
//
//=================================================================

isMiniTarget( potentialTarget )
{
	self endon( "death" );
	
	if ( !isalive( potentialTarget ) || potentialTarget.sessionstate != "playing" )
		return false;
	
	if ( !isdefined( potentialTarget.pers["team"] ) )
		return false;
	
	if ( potentialTarget == self.owner )
		return false;
	
	if ( distanceSquared( potentialTarget.origin , self.origin ) > 1024*1024 )
		return false;
	
	if ( level.teamBased && potentialTarget.pers["team"] == self.team )
		return false;
	
	if ( potentialTarget.pers["team"] == "spectator" )
		return false;
	
	if ( isdefined( potentialTarget.spawntime ) && ( gettime() - potentialTarget.spawntime )/1000 <= 5 )
		return false;

	
	if ( isDefined( self ) )
	{
		minTurretEye = self.mgTurret.origin + ( 0, 0, 64 );
		minTurretCanSeeTarget = potentialTarget sightConeTrace( minTurretEye, self );
	
		if ( minTurretCanSeeTarget < 1 )
			return false;	
	}
	
	return true;
}

tankGetMiniTargets()
{
	self endon( "death" );
	self endon( "leaving" );
	miniTargets = [];
 	println( "Geting Mini Targets" );
	
	for ( ;; )
	{
		miniTargets = [];
		players = level.players;
		
		for (i = 0; i <= players.size; i++)
		{
			if ( isMiniTarget( players[i] ) )
			{
				if( isdefined( players[i] ) )
					miniTargets[miniTargets.size] = players[i];
			}
			else
				continue;
			
			wait( .05 );
		}
		if ( miniTargets.size > 0 )
		{
			self acquireMiniTarget( miniTargets );
			return;	
		}	
		else
			wait( .5 );
	}
}

getBestMiniTarget( targets )
{
	self endon( "death" );
	tankOrigin = self.origin;
	
	closest = undefined;
	bestTarget = undefined;
	
	foreach ( targ in targets )
	{		
		curDist = Distance( self.origin, targ.origin );
		
		// in this calculation having a rocket javelin or c4 increases mini turret priority
		// to prioritize targeting dangerous targets.
		curWeaon = targ GetCurrentWeapon();
		if ( isSubStr( curWeaon, "at4" ) || isSubStr( curWeaon, "jav" ) || isSubStr( curWeaon, "c4" ) || isSubStr( curWeaon, "smart" ) || isSubStr( curWeaon, "grenade" ) )
				curDist -= 200;
		
		if ( !isDefined( closest ) )
		{
			closest = curDist;
			bestTarget = targ;
		} 
		else if ( closest > curDist )
		{
			closest = curDist;
			bestTarget = targ;
		}	
	}
	return ( bestTarget );
}

acquireMiniTarget( targets )
{
	self endon( "death" );
	
	if ( targets.size == 1 )
		self.bestMiniTarget = targets[0];
	else
		self.bestMiniTarget = self getBestMiniTarget( targets );

	if ( distance2D( self.origin, self.bestMiniTarget.origin) > 768 ) 
		self thread setMiniEngagementSpeed();
	
	self notify( "acquiringMiniTarget" );
	self.mgTurret SetTargetEntity( self.bestMiniTarget, ( 0,0,64 ) );	// sets turret to target entity
	wait( .15 );
	self thread fireMiniOnTarget(); // fires on current target.
	self thread watchMiniTargetDeath( targets ); //abandons target when target killed	
	self thread watchMiniTargetDistance( targets );
	self thread watchMiniTargetThreat( self.bestMiniTarget );
}

fireMiniOnTarget()
{
	self endon( "death" );
	self endon( "abandonedMiniTarget" );
	self endon( "killedMiniTarget" );
	noTargTime = undefined;
	miniAcquiredTime = getTime();
	
	if ( !isDefined( self.bestMiniTarget ) )
	{
		println("No Targ to fire on");
		return;
	}
	
	println("firing on best target");
	
	while( 1 )
	{
		if ( !isDefined ( self.mgTurret getTurretTarget( true ) ) )
		{
			if ( !isDefined( noTargTime ) )
				noTargTime = getTime();
			
			curTime = getTime();
			
			if ( noTargTime - curTime > 1 )
			{	
				noTargTime = undefined;
				self thread explicitAbandonMiniTarget();
				return;
			}
			
			//println("Waiting because the turret doesnt have a target" );
			
			wait ( .5 );
			continue;
		}
	
		if ( getTime() > miniAcquiredTime + 1000 && !isDefined( self.bestTarget ) )
		{
			if ( distance2D(self.origin, self.bestMiniTarget.origin ) > 768 )
			{
				targets[0] = self.bestMiniTarget;
				self acquireTarget( targets );
			}
		}
		
		numShots = randomIntRange( 10, 16 );
		for ( i = 0; i < numShots; i++ )
		{
			self.mgTurret ShootTurret();
			wait ( .1 );
		}
		wait ( randomFloatRange( 0.5, 3.0 ) );
	}
}

watchMiniTargetDeath( targets )
{
	self endon( "abandonedMiniTarget" );
	self endon( "death" );
	if ( ! isDefined( self.bestMiniTarget ) )
		return;
	
	self.bestMiniTarget waittill( "death" );
	
	self notify( "killedMiniTarget" );
	println( "Killed Mini Target" );
	
	self.bestMiniTarget = undefined;
	self.mgTurret ClearTargetEntity();
	self tankGetMiniTargets();
}

watchMiniTargetDistance( targets )
{
	self endon( "abandonedMiniTarget" );
	self endon( "death" );
	
	for ( ;; )
	{
		if (! isDefined( self.bestMiniTarget ) )
			return;
	
		trace = BulletTrace( self.mgTurret.origin, self.bestMiniTarget.origin, false, self );
		traceDistance = Distance(self.origin, trace["position"] );
		
		if ( traceDistance > 1024 )
		{
			println( "MINI TARGET DIST TOO FAR!!!" );
			self thread explicitAbandonMiniTarget();
			return;	
		}
		println( traceDistance );
		wait ( 2 );
	}	
}

watchMiniTargetThreat( curTarget )
{
	self endon( "abandonedMiniTarget" );
	self endon( "death" );
	self endon( "killedMiniTarget" );
	
	for ( ;; )
	{
		miniTargets = [];
		players = level.players;
		
		for (i = 0; i <= players.size; i++)
		{
			if ( isMiniTarget( players[i] ) )
			{
				if( !isdefined( players[i] ) )
					continue;
				
				if( !isdefined(curTarget) )
					return;
				
				traceOldTarg = Distance(self.origin, CurTarget.origin );
				traceNewTarg = Distance(self.origin, players[i].origin );
				
				if ( traceNewTarg < traceOldTarg )
				{
					self thread explicitAbandonMiniTarget();
					return;	
				}
			}
			
			wait( .05 );
		}
		
		wait( .25 );		
	}	
}

explicitAbandonMiniTarget( noNewTarget )
{

	self notify( "abandonedMiniTarget" );
	
	println( "ABANDONED MINI TARGET" );
	
	self.bestMiniTarget = undefined;
	self.mgTurret ClearTargetEntity();
	
	if ( isDefined(noNewTarget) && noNewTarget )
		return;
	
	self thread tankGetMiniTargets();
	return;
}


addToTankList()
{
	level.tanks[self getEntityNumber()] = self;	
}

removeFromTankList()
{
	level.tanks[self getEntityNumber()] = undefined;
}	


/*************************************************************************
*
*	PATHFINDING AND PATH NODE FUNCTIONS
*
***************************************************************************/

getNodeNearEnemies()
{
	validEnemies = [];
	
	foreach ( player in level.players )
	{
		if ( player.team == "spectator" )
			continue;
			
		if ( player.team == self.team )
			continue;
			
		if ( !isAlive( player ) )
			continue;
			
		player.dist = 0;
		validEnemies[validEnemies.size] = player;		
	}
	
	if ( !validEnemies.size )
		return undefined;

	for ( i = 0; i < validEnemies.size; i++ )
	{
		for ( j = i + 1; j < validEnemies.size; j++ )
		{
			dist = distanceSquared( validEnemies[i].origin, validEnemies[j].origin );
			
			validEnemies[i].dist += dist;
			validEnemies[j].dist += dist;
		}
	}
	
	bestPlayer = validEnemies[0];
	foreach ( player in validEnemies )
	{
		if ( player.dist < bestPlayer.dist )
			bestPlayer = player;
	}

	bestOrigin = bestPlayer.origin;
	
	sortedNodes = sortByDistance( level.graphNodes, bestOrigin );
	
	//thread drawLine( bestOrigin, sortedNodes[0].origin, 10.0, (1,0,1) );
	
	return ( sortedNodes[0] );
}


setupPaths()
{
	tankNodes = [];
	startNodes = [];
	endNodes = [];
	aStarGraphNodes = [];
	
	// setup the start node
	tankNode = GetVehicleNode( "startnode", "targetname" );
	tankNodes[tankNodes.size] = tankNode;
	startNodes[startNodes.size] = tankNode;
	
	while ( isDefined( tankNode.target ) )
	{
		lastNode = tankNode;
		tankNode = GetVehicleNode( tankNode.target, "targetname" );
		tankNode.prev = lastNode;
		
		// case for connected path
		if ( tankNode == tankNodes[0] )	
			break;

		tankNodes[tankNodes.size] = tankNode;
		
		// case for disconnected path
		if ( !isDefined( tankNode.target ) )
			return;
	}

	tankNodes[0].branchNodes = [];
	tankNodes[0] thread handleBranchNode( "forward" );
	aStarGraphNodes[aStarGraphNodes.size] = tankNodes[0];

	// find the start and end nodes of the branches
	branchNodes = GetVehicleNodeArray( "branchnode", "targetname" );
	foreach ( branchNode in branchNodes )
	{
		tankNode = branchNode;
		tankNodes[tankNodes.size] = tankNode;
		startNodes[startNodes.size] = tankNode;

		while ( isDefined( tankNode.target ) )
		{
			lastNode = tankNode;
			tankNode = GetVehicleNode( tankNode.target, "targetname" );
			tankNodes[tankNodes.size] = tankNode;
			tankNode.prev = lastNode;

			if ( !isDefined( tankNode.target ) )
				endNodes[endNodes.size] = tankNode;
		}
	}

	// detect and initialize the branch nodes.  These will be used for the aStar node graph
	foreach ( tankNode in tankNodes )
	{		
		isBranchNode = false;
		foreach ( startNode in startNodes )
		{
			if ( startNode == tankNode )
				continue;
			
			if ( startNode.target == tankNode.targetname )
				continue;
				
			if ( isDefined( tankNode.target ) && tankNode.target == startNode.targetname )
				continue;
			
			if ( distance2d( tankNode.origin, startNode.origin ) > 80 )
				continue;

			startNode thread handleCapNode( tankNode, "reverse" );
			startNode.prev = tankNode;

			if ( !isDefined( tankNode.branchNodes ) )
				tankNode.branchNodes = [];

			tankNode.branchNodes[tankNode.branchNodes.size] = startNode;
			
			isBranchNode = true;
		}
		
		if ( isBranchNode )
			tankNode thread handleBranchNode( "forward" );
		
		isJoinNode = false;
		foreach ( endNode in endNodes)
		{
			if ( endNode == tankNode )
				continue;
			
			if ( !isDefined( tankNode.target ) )
				continue;
			
			if ( tankNode.target == endNode.targetname )
				continue;	

			if ( isDefined( endNode.target ) && endNode.target == tankNode.targetname )
				continue;	
			
			if ( distance2d( tankNode.origin, endNode.origin ) > 80 )
				continue;

			endNode thread handleCapNode( tankNode, "forward" );
			endNode.next = getVehicleNode( tankNode.targetname, "targetname" );
			//endNode.target = tankNode.targetname; // READ-ONLY field...
			endNode.length = distance( endNode.origin, tankNode.origin );

			if ( !isDefined( tankNode.branchNodes ) )
				tankNode.branchNodes = [];

			tankNode.branchNodes[tankNode.branchNodes.size] = endNode;
			
			isJoinNode = true;
		}
		
		if ( isJoinNode )
		{
			assert( !isBranchNode );
			tankNode thread handleBranchNode( "reverse" );
		}
		
		if ( isJoinNode || isBranchNode )
			aStarGraphNodes[aStarGraphNodes.size] = tankNode;
	}

	if ( aStarGraphNodes.size < 3 )
	{
		level notify ( "end_tankPathHandling" );
		return;
	}

	// subdivide the path a bit...
	segmentNodes = [];
	foreach( tankNode in tankNodes )
	{
		if ( !isDefined( tankNode.branchNodes ) )
			continue;
			
		segmentNodes[segmentNodes.size] = tankNode;
	}

	foreach ( segmentNode in segmentNodes )
	{
		tankNode = segmentNode;
		pathLength = 0;
		
		while ( isDefined( tankNode.target ) )
		{
			prevNode = tankNode;
			tankNode = GetVehicleNode( tankNode.target, "targetname" );
			pathLength += distance( tankNode.origin, prevNode.origin );
			
			if ( tankNode == segmentNode )	
				break;
				
			if ( isDefined( tankNode.branchNodes ) )
				break;
		}

		if ( pathLength > 1000 )
		{
			tankNode = segmentNode;
			curLength = 0;

			while ( isDefined( tankNode.target ) )
			{
				prevNode = tankNode;
				tankNode = GetVehicleNode( tankNode.target, "targetname" );
				
				curLength += distance( tankNode.origin, prevNode.origin );
				if ( curLength < pathLength / 2 )
					continue;

				tankNode.branchNodes = []; // necessary?
				tankNode thread handleBranchNode( "forward" );
				aStarGraphNodes[aStarGraphNodes.size] = tankNode;
				break;
			}
		}
	}

	level.graphNodes = initNodeGraph( aStarGraphNodes );
	
	foreach ( tankNode in tankNodes )
	{
		if ( !isDefined( tankNode.graphId ) )
			tankNode thread nodeTracker();
	}
}



getRandomBranchNode( direction )
{
	branchNodes = [];
	foreach ( graphId, linkNode in self.links )
	{
		// pick a branch in the direction we're already heading
		if ( self.linkDirs[graphId] != direction )
			continue;
			
		branchNodes[branchNodes.size] = linkNode;
	}
	
	return ( branchNodes[randomInt( branchNodes.size )] );
}


getNextNodeForEndNode( endNode, direction )
{
	graphNode = level.graphNodes[self.graphId];
	
	continuePath = generatePath( graphNode, endNode, undefined, direction );
	continueG = continuePath[0].g;
	
	changePath = generatePath( graphNode, endNode, undefined, level.otherDir[direction] );
	changeG = changePath[0].g;

	// temporarily force the tank to only go forward
	if ( !getDvarInt( "tankDebug" ) )
		changeG = 9999999;
	
	if ( continueG <= changeG )
		return ( continuePath[1] );
}


handleBranchNode( direction )
{
	level endon ( "end_tankPathHandling" );
	for ( ;; )
	{
		self waittill( "trigger", tank, wasForced );

		graphNode = level.graphNodes[self.graphId];

		tank.node = self;
		
		nextGraphNode = undefined;
		if ( isDefined( tank.endNode ) && tank.endNode != graphNode )
		{
			nextGraphNode = getNextNodeForEndNode( tank.endNode, tank.veh_pathdir );
			
			if ( !isDefined( nextGraphNode ) )
				tank thread setDirection( level.otherDir[tank.veh_pathdir] );
		}
		
		if ( !isDefined( nextGraphNode ) || nextGraphNode == graphNode )
		{
			nextGraphNode = graphNode getRandomBranchNode( tank.veh_pathdir );
		}

		goalNode = graphNode.linkStartNodes[nextGraphNode.graphId];

		if ( tank.veh_pathdir == "forward" )
			nextLinkNode = self getNextNode();
		else
			nextLinkNode = self getPrevNode();

		// if we're already on this path, just keep going
		if ( nextLinkNode != goalNode )
			tank startPath( goalNode );
	}
}


handleCapNode( joinNode, direction )
{
	for ( ;; )
	{
		self waittill( "trigger", tank );
		
		if ( tank.veh_pathdir != direction )
			continue;
		
		debugPrintLn2( "tank starting path at join node: " + joinNode.graphId );
			
		tank startPath( joinNode );
	}
}


nodeTracker()
{
	self.forwardGraphId = getForwardGraphNode().graphId;
	self.reverseGraphId = getReverseGraphNode().graphId;
	
	for ( ;; )
	{
		self waittill ( "trigger", tank, wasForced );
		
		tank.node = self;
		
		/#
		if ( getDvarInt( "tankForceTrigger" ) )
		{
			if ( tank.veh_pathdir == "forward" )
				tank thread forceTrigger( self, self getNextNode(), tank );
			else
				tank thread forceTrigger( self, self getPrevNode(), tank );
			}
		#/

		tank.forwardGraphId = self.forwardGraphId;
		tank.reverseGraphId = self.reverseGraphId;
		
		if ( !isDefined( self.target ) || self.targetname == "branchnode" )
			nodeType = "TRANS";
		else
			nodeType = "NODE";
		
		if ( isDefined( wasForced ) )
			debugPrint3D( self.origin, nodeType, (1,0.5,0), 1, 2, 100 );
		else
			debugPrint3D( self.origin, nodeType, (0,1,0), 1, 2, 100 );
	}
}


forceTrigger( prevNode, nextNode, tank )
{
	nextNode endon ( "trigger" );
	prevNode endon ( "trigger" );
	tank endon ( "death" );
	
	minDist = distanceSquared( tank.origin, nextNode.origin );
	tankDir = tank.veh_pathdir;

	debugPrint3D( prevNode.origin+(0,0,30), "LAST", (0,0,1), 0.5, 1, 100 );
	debugPrint3D( nextNode.origin+(0,0,60), "NEXT", (0,1,0), 0.5, 1, 100 );

	timeOutNextFrame = false;
	for ( ;; )
	{
		wait ( 0.05 );
		
		// tank changed direction
		if ( tankDir != tank.veh_pathdir )
		{
			debugPrintLn2( "tank missed node: reversing direction" );
			tank thread forceTrigger( nextNode, prevNode, tank );
			return;
		}
		
		if ( timeOutNextFrame )
		{
			debugPrintLn2( "... sending notify." );
			nextNode notify ( "trigger", tank, true );
			return;
		}
		
		curDist = distanceSquared( tank.origin, nextNode.origin );
		
		if ( curDist > minDist )
		{
			timeOutNextFrame = true;
			debugPrintLn2( "tank missed node: forcing notify in one frame..." );
		}
			
		minDist = curDist;		
	}
}


getForwardGraphNode()
{
	assert( !isDefined( self.graphId ) );
	
	checkNode = self;	
	while ( !isDefined( checkNode.graphId ) )
		checkNode = checkNode getNextNode();
		
	return checkNode;
}


getReverseGraphNode()
{
	assert( !isDefined( self.graphId ) );
	
	checkNode = self;	
	while ( !isDefined( checkNode.graphId ) )
		checkNode = checkNode getPrevNode();
		
	return checkNode;
}


getNextNode()
{
	if ( isDefined( self.target ) )
		return ( GetVehicleNode( self.target, "targetname" ) );
	else
		return ( self.next );
}


getPrevNode()
{
	return self.prev;
}



// Builds the aStar node graph
initNodeGraph( astarBaseNodes )
{
	graphNodes = [];
	foreach ( pathNode in aStarBaseNodes )
	{
		graphNode = spawnStruct();
		graphNode.linkInfos = [];
		graphNode.links = [];
		graphNode.linkLengths = [];
		graphNode.linkDirs = [];
		graphNode.linkStartNodes = [];
		graphNode.node = pathNode;
		graphNode.origin = pathNode.origin;
		graphNode.graphId = graphNodes.size;
		pathNode.graphId = graphNodes.size;

		debugPrint3D( graphNode.origin + (0,0,80), graphNode.graphId, (1,1,1), 0.65, 2, 100000 );

		graphNodes[graphNodes.size] = graphNode;		
	}

	foreach ( pathNode in aStarBaseNodes )
	{
		graphId = pathNode.graphId;

		checkNode = GetVehicleNode( pathNode.target, "targetname" );
		linkLength = distance( pathNode.origin, checkNode.origin );
		linkStartNode = checkNode;
		
		while ( !isDefined( checkNode.graphId ) )
		{
			linkLength += distance( checkNode.origin, checkNode.prev.origin );
			
			if ( isDefined( checkNode.target ) )
				checkNode = GetVehicleNode( checkNode.target, "targetname" );
			else
				checkNode = checkNode.next;
		}

		assert( checkNode != pathNode );
		graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "forward", linkStartNode );

		checkNode = pathNode.prev;
		linkLength = distance( pathNode.origin, checkNode.origin );
		linkStartNode = checkNode;
		
		while ( !isDefined( checkNode.graphId ) )
		{
			linkLength += distance( checkNode.origin, checkNode.prev.origin );
			checkNode = checkNode.prev;
		}

		assert( checkNode != pathNode );		
		graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "reverse", linkStartNode );
		
		foreach ( branchNode in pathNode.branchNodes )
		{
			checkNode = branchNode;
			linkLength = distance( pathNode.origin, checkNode.origin );
			linkStartNode = checkNode;
			
			if ( checkNode.targetname == "branchnode" )
			{
				while ( !isDefined( checkNode.graphId ) )
				{
					if ( isDefined( checkNode.target ) )
						nextNode = GetVehicleNode( checkNode.target, "targetname" );
					else
						nextNode = checkNode.next;
						
					linkLength += distance( checkNode.origin, nextNode.origin );
					checkNode = nextNode;
				}
				
				assert( checkNode != pathNode );		
				graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "forward", linkStartNode );
			}	
			else
			{
				while ( !isDefined( checkNode.graphId ) )
				{
					linkLength += distance( checkNode.origin, checkNode.prev.origin );
					checkNode = checkNode.prev;
				}
		
				assert( checkNode != pathNode );		
				graphNodes[graphId] addLinkNode( graphNodes[checkNode.graphId], linkLength, "reverse", linkStartNode );
			}
		}
	}

	return graphNodes;
}


addLinkNode( graphNode, linkLength, linkDir, linkStartNode )
{
	assert( self.graphId != graphNode.graphId );
	assert( !isDefined( self.links[graphNode.graphId] ) );
	
	self.links[graphNode.graphId] = graphNode;
	self.linkLengths[graphNode.graphId] = linkLength;
	self.linkDirs[graphNode.graphId] = linkDir;
	self.linkStartNodes[graphNode.graphId] = linkStartNode;

	linkInfo = spawnStruct();
	linkInfo.toGraphNode = graphNode;
	linkInfo.toGraphId = graphNode.graphId;
	linkInfo.length = linkLength;
	linkInfo.direction = linkDir;
	linkInfo.startNode = linkStartNode;
	
	self.linkInfos[graphNode.graphId] = linkInfo;
}


// call function as generatePath(startNode, destNode), otherwise paths will be reversed
generatePath( destNode, startNode, blockedNodes, direction )
{
	level.openList = [];
	level.closedList = [];
	foundPath = false;
	pathNodes = [];
	
	if ( !isDefined( blockedNodes ) )
		blockedNodes = [];
	
	startNode.g = 0;
	startNode.h = getHValue( startNode, destNode );
	startNode.f = startNode.g + startNode.h;
	
	addToClosedList( startNode );

	curNode = startNode;
	for ( ;; )
	{
		foreach ( linkId, checkNode in curNode.links )
		{
			if ( is_in_array( blockedNodes, checkNode ) )
				continue;
				
			if ( is_in_array( level.closedList, checkNode ) )
				continue;
				
			if ( isDefined( direction ) && checkNode.linkDirs[curNode.graphId] != direction )
				continue;
				
			if ( !is_in_array( level.openList, checkNode ) )
			{
				addToOpenList( checkNode );
				
				checkNode.parentNode = curNode;
				checkNode.g = getGValue( checkNode, curNode );
				checkNode.h = getHValue( checkNode, destNode );
				checkNode.f = checkNode.g + checkNode.h;
				
				if ( checkNode == destNode )
					foundPath = true;	
			}
			else
			{
				if ( checkNode.g < getGValue( curNode, checkNode ) )
					continue;
					
				checkNode.parentNode = curNode;
				checkNode.g = getGValue( checkNode, curNode );
				checkNode.f = checkNode.g + checkNode.h;
			}
		}
		
		if ( foundPath )
			break;
			
		addToClosedList( curNode );
		
		bestNode = level.openList[0];

		foreach ( testNode in level.openList )
		{
			if ( testNode.f > bestNode.f )
				continue;
				
			bestNode = testNode;
		}
		
		assert( isDefined( bestNode ) ); // the tank should always have a path

		addToClosedList( bestNode );
		curNode = bestNode;
	}

	assert( isDefined( destNode.parentNode ) );
	
	curNode = destNode;
	while (curNode != startNode)
	{
		pathNodes[pathNodes.size] = curNode;
		curNode = curNode.parentNode;
	}
	pathNodes[pathNodes.size] = curNode;
	
	return pathNodes;	
}


addToOpenList( node )
{
	node.openListID = level.openList.size;
	level.openList[level.openList.size] = node;
	node.closedListID = undefined;
}


addToClosedList( node )
{
	if (isdefined (node.closedListID))
		return;
	
	node.closedListID = level.closedList.size;
	level.closedList[level.closedList.size] = node;
	
	if (!is_in_array (level.openList, node))
		return;
		
	level.openList[node.openListID] = level.openList[level.openList.size - 1];
	level.openList[node.openListID].openListID = node.openListID;
	level.openList[level.openList.size - 1] = undefined;
	node.openListID = undefined;
}


getHValue (node1, node2)
{
	return (distance (node1.node.origin, node2.node.origin));
}


getGValue(node1, node2)
{
	return ( node1.parentNode.g + node1.linkLengths[node2.graphId] );
}


is_in_array( aeCollection, eFindee )
{
	for ( i = 0; i < aeCollection.size; i++ )
	{
		if ( aeCollection[ i ] == eFindee )
			return( true );
	}

	return( false );
}


drawPath( pathNodes )
{
	for ( i = 1; i < pathNodes.size; i++ )
	{
		startNode = pathNodes[i-1];
		endNode = pathNodes[i];
		
		if ( startNode.linkDirs[endNode.graphId] == "reverse" )
			level thread drawLink( startNode.node.origin, endNode.node.origin, (1,0,0) );
		else
			level thread drawLink( startNode.node.origin, endNode.node.origin, (0,1,0) );
			
		vehNode = startNode.linkStartNodes[endNode.graphId];
		level thread drawLink( startNode.node.origin + (0,0,4), vehNode.origin + (0,0,4), (0,0,1) );
		
		if ( startNode.linkDirs[endNode.graphId] == "reverse" )
		{
			while ( !isDefined( vehNode.graphId ) )
			{
				lastVehNode = vehNode;
				vehNode = vehNode.prev;
				level thread drawLink( lastVehNode.origin + (0,0,4), vehNode.origin + (0,0,4), (0,1,1) );
			}
		}
		else
		{
			while ( !isDefined( vehNode.graphId ) )
			{
				lastVehNode = vehNode;
				
				if ( isDefined( vehNode.target ) )
					vehNode = GetVehicleNode( vehNode.target, "targetname" );				
				else
					vehNode = vehNode.next;

				level thread drawLink( lastVehNode.origin + (0,0,4), vehNode.origin + (0,0,4), (0,1,1) );
			}			
		}
	}
}


drawGraph( pathNodes )
{
	/*
	level.pathZOffset = 0;
	foreach ( node in pathNodes )
	{
		println( node.links.size );
		foreach ( linkId, graphNode in node.links )
		{
			if ( node.linkDirs[linkId] == "reverse" )
				level thread drawLink( node.node.origin, graphNode.node.origin, (0,1,0) );
			else
				level thread drawLink( node.node.origin, graphNode.node.origin, (1,0,0) );

			//if ( node.linkDirs[linkId] == "reverse" )
			//	continue;
				
			//level thread drawLink( pathNodes[graphId].node.origin, pathNodes[node.graphId].node.origin, (randomFloat( 2 ), randomFloat( 2 ), randomFloat( 2 )) );		
		}
	}
	*/
}


drawLink( start, end, color )
{
	level endon ( "endpath" );
	for ( ;; )
	{
		line(start, end, color, true);
		wait 0.05;
	}
}

debugPrintLn2( printString )
{
	/#
	if ( getDvarInt( "tankDebug" ) )
		printLn( printString );
	#/
}

debugPrint( printString )
{
	/#
	if ( getDvarInt( "tankDebug" ) )
		print( printString );
	#/
}


debugPrint3D( origin, printString, color, alpha, scale, duration )
{
	/#
	if ( getDvarInt( "tankDebug" ) )
	{
		print3d( origin, printString, color, alpha, scale, duration );
		println( "3D: " + printString );
	}
	#/
}


drawTankGraphIds()
{
	/#
	if ( getDvarInt( "tankDebug" ) )
	{
		self notify ( "drawTankGraphIds" );
		self endon ( "drawTankGraphIds" );
		
		for ( ;; )
		{
			print3d( self.origin + (0,0,128), "FW: " + self.forwardGraphId + " RV: " + self.reverseGraphId, (0,1,0), 1, 3, 1 );
			wait ( 0.05 );
		}
	}
	#/
}

