| Scripts/Deep_Horizon_Adv_Nav_Comp_World.js | "use strict";
this.name = "Deep_Horizon_Adv_Nav_Comp";
this.authors = "Cmd. Cheyd (Blake Deakins) and PhantorGorth (Paul Cooper)";
this.contributors = "Svengali, Nick Rogers";
this.description = "Adds a prime-able piece of equipment that improves interstellar navigation in the form of fuel savings.";
this.copyright = "� Creative Commons Attribution-Noncommercial-ShareAlike 3.0 Unported license (Modified)";
/*
	***************************************************************************************************************
	**  A significant portion of the code contained herein was based on code originally written and contributed by
	**	Svengali.  Without his contribution, this OXP would not have been possible.  Thanks Olli!
	***************************************************************************************************************
*/
//*************************************************************************************************************
//************************************         Variable Declaration        ************************************
//*************************************************************************************************************
this.$jumpDistance = 0;
this.usedANC = false;
this.$lastSource = -1;
//*************************************************************************************************************
//************************************           Event Functions           ************************************
//*************************************************************************************************************
this.startUp = function () {
	// get instance
	this.myFCB = new CheydsReorient();
	this.systemDone = false;
	this.loopHere = false;
}
this.shipWillLaunchFromStation = function () {
	this._systemSetup();
	if (this.systemDone) return;
	this.loopHere = false;
	this.entryVectorCorrect = false;
	this.$lastSource = system.ID;
}
this.shipWillEnterWitchspace = function (cause) {
	this.systemDone = false;
	this.myFCB.fcbRemove(1);
	if (cause == "wormhole") {
		this.playerCancelledJumpCountdown();
		return;
	} else if (cause == "standard jump" && this.usedANC) {
		// Calculate distance about to be jumped.  Use this after the jump for fuel waste calculations
		var playerTarget = this._playerTargetSystem();
		this.$jumpDistance = Number(System.infoForSystem(galaxyNumber, system.ID).distanceToSystem(System.infoForSystem(galaxyNumber, playerTarget)).toFixed(1));
		this.entryVector = player.ship.heading.angleTo(this.$jumpMarker.position.subtract(player.ship.position).direction());
	}
}
this.shipExitedWitchspace = function () {
	if (system.ID != -1) this.$lastSource = system.ID;
	// If the ANC was used, calculate distance travelled and reward fuel savings.
	this._systemSetup();
	this.loopHere = false;
	if (!this.usedANC) return;
	this.usedANC = false;
	if ((this.$jumpDistance > 1.0) && this.entryVectorCorrect) {
		var fuelSaved = Number((this.$jumpDistance * 0.1).toFixed(1));
		player.ship.fuel += fuelSaved;
		player.consoleMessage("Fuel savings due to optimized routing: " + fuelSaved, 4);
		this.entryVectorCorrect = false;
	} else if (this.$jumpDistance < 1.0) {
		player.consoleMessage("Jump distance was too short to achieve appreciable fuel savings.", 4);
	} else {
		player.consoleMessage("Entry vector across witchspace event horizon meniscus too far off calculated.  Fuel savings negated.", 4);
	}
}
this.shipWillExitWitchspace = function () {
	if (this.entryVector < 0.010) this.entryVectorCorrect = true;
	this._stopNavFrameCallback();
	if (this.$navFrameVE) this.$navFrameVE.remove();
	if (this.$navStarVE) this.$navStarVE.remove();
	if (this.navigationTimer) {
		this.navigationTimer.stop();
		delete this.navigationTimer;
	}
}
this.shipWillDockWithStation = this.shipDied = function () {
	// cleaning up to avoid trouble
	this.usedANC = false;
	this._stopNavFrameCallback();
	delete this.navFrameCallbackID;
	if (this.$navFrameVE) this.$navFrameVE.remove();
	if (this.$navStarVE) this.$navStarVE.remove();
	if (this.myFCB) this.myFCB.fcbRemove(1);
	if (this.navigationTimer) {
		this.navigationTimer.stop();
		delete this.navigationTimer;
	}
	this.loopHere = false;
}
this.playerStartedJumpCountdown = function (type, seconds) {
	if (!this.usedANC) {
		if (type === "standard") {
			if (worldScripts["Deep_Horizon_Emer_Jump_Initiator"]) {
				if (worldScripts["Deep_Horizon_Emer_Jump_Initiator"].$usedEWI) return;
				else {
					this._spawnDHINavVEs();
					this._reorientPlayer();
				}
			} else {
				this._spawnDHINavVEs();
				this._reorientPlayer();
			}
		}
	} else {
		if (this.navigationTimer && this.loopHere) {
			this._cancelANC(false);
			this.loopHere = false;
		}
		if (type == "standard" && seconds == player.ship.hyperspaceSpinTime * 2) this.loopHere = true;
		else if (type == "standard" && seconds != player.ship.hyperspaceSpinTime * 2) {
			this._cancelANC(false);
			this._reorientPlayer();
		}
	}
}
this.playerCancelledJumpCountdown = this.playerStartedAutoPilot = function () {
	//If the ANC is running, kill it.
	if (this.usedANC) this._cancelANC(true);
	else {
		this.usedANC = false;
		this._stopNavFrameCallback();
		if (this.$navFrameVE) this.$navFrameVE.remove();
		if (this.$navStarVE) this.$navStarVE.remove();
		if (this.myFCB) this.myFCB.fcbRemove(1);
		if (this.navigationTimer) {
			this.navigationTimer.stop();
			delete this.navigationTimer;
		}
		this.loopHere = false;
	}
}
this.playerJumpFailed = function (reason) {
	//If the ANC is running, kill it.
	this.usedANC = false;
	this._stopNavFrameCallback();
	delete this.navFrameCallbackID;
	if (this.$navFrameVE) this.$navFrameVE.remove();
	if (this.$navStarVE) this.$navStarVE.remove();
	if (this.myFCB) this.myFCB.fcbRemove(1);
	if (this.navigationTimer) {
		this.navigationTimer.stop();
		delete this.navigationTimer;
	}
	this.loopHere = false;
}
//*************************************************************************************************************
//************************************            OXP Functions            ************************************
//*************************************************************************************************************
//-------------------------------------------------------------------------------------------------------------
// returns the player's target system (1.80) or the next jump to their target system (1.82)
this._playerTargetSystem = function () {
	if (player.ship.hasOwnProperty("nextSystem")) return player.ship.nextSystem;
	var p = player.ship;
	var target = p.targetSystem;
	var last = system.ID;
	if (system.ID == -1) {
		last = this.$lastSource;
	}
	if (oolite.compareVersion("1.81") < 0) {
		// in 1.81 or greater, the target system could be more than 7 ly away. It becomes, essentially, the final destination.
		// there could be multiple interim stop points between the current system and the target system.
		// the only way to get this info is to recreate a route using the same logic as entered on the ANA, and pick item 1
		// from the list. That should be the next destination in the list.
		var myRoute = System.infoForSystem(global.galaxyNumber, last).routeToSystem(System.infoForSystem(global.galaxyNumber, target), p.routeMode);
		if (myRoute) {
			target = myRoute.route[1];
		}
	}
	return target;
}
this._ancActivated = function () {
	var playerTarget = this._playerTargetSystem();
	if (!playerTarget || playerTarget == system.ID) {
		// Local system is selected.  Halt.
		player.consoleMessage("Local system selected.  Navigation calculations unnecessary.  Shutting down.", 4);
		return;
	}
	if (System.infoForSystem(galaxyNumber, system.ID).distanceToSystem(System.infoForSystem(galaxyNumber, playerTarget)) > player.ship.fuel) {
		// Insufficient Fuel.  Halt.
		player.consoleMessage("Insufficient fuel to reach destination system.  Navigation calculations unnecessary.  Shutting down.", 4);
		return;
	}
	if (!this.navigationTimer) {
		// Proceed with ANC-style long-count jump.
		this._spawnDHINavVEs();
		//Pause to simulate calculations...
		var delay = Math.floor((system.scrambledPseudoRandomNumber(24784 + playerTarget) * 100) / 33);
		this.navigationTimer = new Timer(this, this._reorientPlayer, 3 + delay);
	} else {
		// Cancel the ANC.
		this._cancelANC(false);
	}
	return;
}
this._initiateJump = function () {
	var self = worldScripts["Deep_Horizon_Adv_Nav_Comp"];
	if (player.ship.status === "STATUS_IN_FLIGHT") {
		self.usedANC = true;
		player.ship.beginHyperspaceCountdown(player.ship.hyperspaceSpinTime * 2);
	} else self.usedANC = false;
}
this._spawnDHINavVEs = function () {
	// Select farpointMarker that matches the destination system.
	var playerTarget = this._playerTargetSystem();
	for (var i = this.$farpointMarkers.length; i > 0; i--) {
		if (playerTarget == this.$farpointMarkers[i - 1].systemID) {
			this.$jumpMarker = this.$farpointMarkers[i - 1]
		}
	}
	player.consoleMessage("Locking onto " + System.systemNameForID(playerTarget) + " system witchpoint nav beacon.", 4);
	// Spawn Visual Effect and store reference to it
	this.$navFrameVE = system.addVisualEffect("deephorizon_navframe", this._vectoredPositionToTarget(this.$jumpMarker.position, player.ship.collisionRadius + 5000));
	this.$navStarVE = system.addVisualEffect("deephorizon_navstar", this._vectoredPositionToTarget(this.$jumpMarker.position, player.ship.collisionRadius + 50000));
	// Orient the VE toward the player ship
	this.$navFrameVE.scale(0.66);
	this.$navStarVE.scale(3.30 - this.$jumpMarker.distanceToSystem * .27);
	var tex = this.$jumpMarker.systemID % 4;
	switch (tex) {
		case 1:
			this.$navStarVE.setMaterials({ "dhi_navstar.png": { "textures": ["dhi_navstar2.png"], "fragment_shader": "dhi_anc_jumpstar.fragment", "emission_map": "dhi_navstar2.png", "uniforms": { "uColorMap": { "type": "texture", "value": "0" }, "uSpecIntensity": "shaderFloat1", "uSpecColor": "shaderVector1" }, "vertex_shader": "dhi_anc_jumpstar.vertex" } });
			break;
		case 2:
			this.$navStarVE.setMaterials({ "dhi_navstar.png": { "textures": ["dhi_navstar3.png"], "fragment_shader": "dhi_anc_jumpstar.fragment", "emission_map": "dhi_navstar3.png", "uniforms": { "uColorMap": { "type": "texture", "value": "0" }, "uSpecIntensity": "shaderFloat1", "uSpecColor": "shaderVector1" }, "vertex_shader": "dhi_anc_jumpstar.vertex" } });
			break;
		case 3:
			this.$navStarVE.setMaterials({ "dhi_navstar.png": { "textures": ["dhi_navstar4.png"], "fragment_shader": "dhi_anc_jumpstar.fragment", "emission_map": "dhi_navstar4.png", "uniforms": { "uColorMap": { "type": "texture", "value": "0" }, "uSpecIntensity": "shaderFloat1", "uSpecColor": "shaderVector1" }, "vertex_shader": "dhi_anc_jumpstar.vertex" } });
			break;
	}
	this._reorientVEToPlayer(this.$navFrameVE);
	this._reorientVEToPlayer(this.$navStarVE);
	this.navFrameCallbackID = addFrameCallback(this._positionNavVisualEffects.bind(this));
}
this._positionNavVisualEffects = function () {
	this._reorientVEToPlayer(this.$navFrameVE);
	this._reorientVEToPlayer(this.$navStarVE);
	this.$navFrameVE.position = this._vectoredPositionToTarget(this.$jumpMarker.position, player.ship.collisionRadius + 5000);
	this.$navStarVE.position = this._vectoredPositionToTarget(this.$jumpMarker.position, player.ship.collisionRadius + 10000);
	this.$navStarVE.shaderVector1 = this.$jumpMarker.uSpecColor;
	this.$navStarVE.shaderFloat1 = this.$jumpMarker.uSpecIntensity;
	return;
}
this._stopNavFrameCallback = function () {
	if (!this.navFrameCallbackID) return;
	removeFrameCallback(this.navFrameCallbackID);
	delete this.navFrameCallbackID;
	return;
};
this._reorientPlayer = function _reorientPlayer() {
	//Calculation of the roll and Pitch accelerations and decelerations based on variations from a Cobra Mk3.
	var rollAcc = (0.2) * (player.ship.maxThrust / 30) / (player.ship.mass / 185580.015625) / ((player.ship.boundingBox.x + player.ship.boundingBox.y) / 160);
	if (rollAcc > 1) rollAcc = 1;
	else if (rollAcc < 0.28) rollAcc = 0.28;
	var pitchAcc = rollAcc * (160 / 95) * ((player.ship.boundingBox.x + player.ship.boundingBox.y) / 160) / ((player.ship.boundingBox.y + player.ship.boundingBox.z) / 95);
	if (pitchAcc > 1) pitchAcc = 1;
	else if (pitchAcc < 0.255) pitchAcc = 0.255;
	var rollDec = rollAcc / 2;
	var pitchDec = pitchAcc / 2;
	//Turn the player to the appropriate buoy for the selected system.ID
	this.myFCB.fcbFace(player.ship, [[1, this.$jumpMarker], [0, this.$jumpMarker]], 0, [rollAcc, pitchAcc], [rollDec, pitchDec], this._initiateJump);
	player.consoleMessage(System.systemNameForID(this._playerTargetSystem()) + " system beacon acquired.  Reorienting ship for vectored system departure\n", 4);
	return;
}
this._reorientVEToPlayer = function (navVE) {
	var orient = this._lookAtRotate(player.ship.position.subtract(navVE.position), player.ship.orientation.vectorUp());
	if (orient === null) orient = this._lookAtRotate(player.ship.position.subtract(navVE.position), player.ship.orientation.vectorForward().multiply(-1));
	navVE.orientation = orient;
}
this._orthoNormalise = function (a, b) {
	//Returns a normalised vector that is in the plane of "ab" and is at 90� to "a" in the same half plane as "b".
	var a2 = a.direction();
	var b2 = b.direction();
	var c = a.cross(b).cross(a);
	return c.direction();
}
this._lookAtRotateEuler = function (theta, phi, psi) {
	//Returns a Quaternion that matches a rotational transformation that is described by the Euler angles.
	//Uses Y axis at the basis axis for theta and the Z axis is used to measure phi from.
	var q = new Quaternion(1, 0, 0, 0);
	var theta2 = (Math.PI / 2) - theta;
	q = q.rotateZ(psi);
	q = q.rotateX(theta2);
	q = q.rotateY(phi);
	return q;
}
this._lookAtRotate = function (forward, up) {
	//returns an orientation quaternion given a Forward vector and an Up vector (the Forward and Up do not need to be
	//orthonormal but they can not be parallel or anti-parallel.)
	if (forward.direction().cross(up.direction()).magnitude() < 0.01) return null; // Return null if Forward and Up are parallel or anti-parallel or nearly so.
	var f = forward.direction();
	var u = this._orthoNormalise(f, up);
	var v = new Vector3D(0, 1, 0); //Uses Y axis at the basis axis for theta and the Z axis is used to measure phi from for finding the Euler angles.
	var u2 = this._orthoNormalise(f, v);
	var sign = -u2.cross(u).dot(f);
	var sign = sign && sign / Math.abs(sign);
	var psi = sign * u2.angleTo(u);
	var z = new Vector3D(0, 0, 1);
	var h = new Vector3D(f.x, 0, f.z);
	sign = -z.cross(h).y;
	sign = sign && sign / Math.abs(sign);
	var phi = sign * z.angleTo(h);
	sign = f.y;
	sign = sign && sign / Math.abs(sign);
	var theta = (Math.PI / 2) - (sign * h.angleTo(f));
	return this._lookAtRotateEuler(theta, phi, psi);
}
this._vectoredPositionToTarget = function (targetPosVector, distance) {
	var v = targetPosVector.subtract(player.ship.position).direction();
	v = v.multiply(distance);
	v = v.add(player.ship.position);
	return v;
}
this._cancelANC = function (silent) {
	if (!silent) player.consoleMessage("Advanced Navigation calculations cancelled.", 4);
	this._stopNavFrameCallback();
	if (this.$navFrameVE) this.$navFrameVE.remove();
	if (this.$navStarVE) this.$navStarVE.remove();
	if (this.navigationTimer) {
		this.navigationTimer.stop();
		delete this.navigationTimer;
	}
	player.ship.cancelHyperspaceCountdown();
	if (this.myFCB) this.myFCB.fcbRemove(1);
	this.loopHere = false;
	this.usedANC = false;
	return;
}
this._ancDamaged = function () {
	if ((equipment == "EQ_ADV_NAV_COMP" || equipment == "EQ_ADVANCED_NAVIGATIONAL_ARRAY") && this.navigationTimer) {
		this._cancelANC(false);
	}
	return;
}
this._systemSetup = function () {
	this.$farpointMarkers = new Array();
	// Determine how many farpoint markers are needed
	this.$localSystems = System.infoForSystem(galaxyNumber, system.ID).systemsInRange(7);
	this.$spawnCount = this.$localSystems.length;
	// For each in-range system spawn a farpoint marker
	this.$farpointMarkers = system.addShips("DHI_farpoint_marker", this.$spawnCount, player.ship.position, 5000000);
	//Assign a matching system.ID as an additional value to each buoy
	for (var i = this.$farpointMarkers.length; i > 0; i--) {
		var fpm = this.$farpointMarkers[i - 1];
		fpm.systemID = this.$localSystems[i - 1].systemID;
		fpm.galCoordinates = this.$localSystems[i - 1].coordinates;
		fpm.name = this.$localSystems[i - 1].name;
		fpm.distanceToSystem = System.infoForSystem(galaxyNumber, system.ID).distanceToSystem(this.$localSystems[i - 1]);
		fpm.uSpecColor = this._selectColor(this.$localSystems[i - 1].systemID, i);
		fpm.uSpecIntensity = 0.5;
		fpm.position = this.positionFarpointMarker(fpm, 50000000);
	}
	this.systemDone = true;
	return;
}
//-------------------------------------------------------------------------------------------------------------
this.$anc_shipLaunchedEscapePod = function $anc_shipLaunchedEscapePod(pod, passengers) {
	pod.remove(true);
}
this._selectColor = function (sysID, i) {
	var color = System.infoForSystem(galaxyNumber, sysID).sun_color;
	if (typeof (color) === 'undefined') {
		var c = Math.floor(system.scrambledPseudoRandomNumber(785331 - i) * 7);
		switch (c) {
			case 0: color = "magentaColor"; break;
			case 1: color = "redColor"; break;
			case 2: color = "orangeColor"; break;
			case 3: color = "yellowColor"; break;
			case 4: color = "whiteColor"; break;
			case 5: color = "cyanColor"; break;
			default: color = "blueColor"; break;
		}
	}
	switch (color) {
		case "magentaColor": var colorVec = [1, 0, 1]; break;
		case "redColor": var colorVec = [1, 0, 0]; break;
		case "orangeColor": var colorVec = [1, 0.5, 0]; break;
		case "yellowColor": var colorVec = [1, 1, 0]; break;
		case "whiteColor": var colorVec = [1, 1, 1]; break;
		case "cyanColor": var colorVec = [0, 1, 1]; break;
		case "blueColor": var colorVec = [0, 0, 1]; break;
		default: colorVec = [1, 1, 1];
	}
	return colorVec;
}
this.positionFarpointMarker = function positionFarpointMarker(marker, distance) {
	if (marker.galCoordinates == null) return null;
	var t = marker.galCoordinates;
	var s = System.infoForSystem(galaxyNumber, system.ID).coordinates;
	// special case: if target system has same coords as current system, force them to be different
	if (t.distanceTo(s) == 0) t.z += 1;
	var tV = t.subtract(s);
	var u = system.scrambledPseudoRandomNumber(34567346);
	var v = system.scrambledPseudoRandomNumber(431976567);
	var w = system.scrambledPseudoRandomNumber(9834674);
	var theta = Math.acos(2 * u - 1);
	var phi = 2 * Math.PI * v;
	var psi = 2 * Math.PI * w;
	var v1 = new Vector3D(Math.sin(theta) * Math.sin(phi), Math.sin(theta) * Math.cos(phi), Math.cos(theta));
	var v2 = new Vector3D(0, 0, 1); //straight up
	var v3 = v2.cross(v1);
	v3 = v3.direction(); // normalize (should be normalized anyway)
	var q1 = new Quaternion(1, 0, 0, 0);
	q1 = q1.rotate(v3, theta).normalize();
	var v4 = tV.rotateBy(q1).direction(); // rotate target vector tV so that the coordinate space has pole moved from straight up to v1 (theta, phi) (spherical coords)
	var q2 = new Quaternion(1, 0, 0, 0);
	q2 = q2.rotate(v1, psi).normalize();
	v4 = v4.rotateBy(q2).direction(); // rotate about the new pole v1 by psi
	return v4.multiply(distance).add(player.ship.position);
}
this._anc_sum = function (arr) {
	var total = 0;
	for (var a = 0; a < arr.length; a++) { total += arr[a]; };
	return total
}
this._anc_time_taken = function (angle, max_ang_vel, dampa, dampb) {
	var ret_val = [0, 0, 0];
	ret_val[0] = (angle / max_ang_vel) + ((dampa + dampb) * 0.5);
	ret_val[1] = dampa;
	ret_val[2] = dampb;
	if (ret_val[0] < (dampa + dampb)) {
		var m = Math.sqrt((2 * angle) / (max_ang_vel * (dampa + dampb)));
		ret_val[0] = m * (dampa + dampb);
		ret_val[1] *= m;
		ret_val[2] *= m;
	}
	return ret_val;
}
//*************************************************************************************************************
//************************************            Lib Functions            ************************************
//*************************************************************************************************************
this.CheydsReorient = function CheydsReorient() { this._root = null; }
CheydsReorient.prototype = {
	constructor: CheydsReorient,
	cheyds_fcb: 0,
	cheyds_fcbs: [],
	cheyds_doAutoRemove: true,
	cheyds_autoRemove: function (mode) {
		if (mode) {
			if (!this.cheyds_autoRemoveTimer) this.cheyds_autoRemoveTimer = new Timer(this, this.fcbRemove, 0, 3);
			else if (!this.cheyds_autoRemoveTimer.isRunning) this.cheyds_autoRemoveTimer.start();
		} else if (this.cheyds_autoRemoveTimer) this.cheyds_autoRemoveTimer.stop();
	},
	// Face target
	//	ent			Entity. Required. To be reoriented.
	//	tar			Entity. Required. Target entity.
	//	recall		Boolean. Recall settings. If set ignores dampb and starts over when duration over.
	//	angAccA		Number. Angular acceleration array
	//	angDecA		Number. Angular deceleration array 
	fcbFace: function (ent, tarVA, recall, angAccA, angDecA, call_back) {
		if (!ent || !ent.isValid) return;
		var croStep = 0, ccl_sum = 0, ccl_ent = ent, ccl_tarVA = tarVA, ccl_tar = tarVA[0], ccl_recall = recall, ccl_angAccA = angAccA, ccl_angDecA = angDecA, ccl_angAcc, ccl_angDec, ccl_dampa, ccl_dampb;
		var ccl_last, initialAngle, aimAngle, alpha, reset = 0, max_ang_vel = 0, targetVector;
		var cheyds_fcbs_ref, absSecs, totalSecs = 0;
		var finalCallbackCalled = false, ccl_callback = call_back;
		var lastOrientation, lastUpAngle;
		this.cheyds_fcb = addFrameCallback(function (delta) {
			if (!delta || !ccl_ent || !ccl_ent.isValid) return;
			if (ccl_sum == 0) {
				lastOrientation = ccl_ent.orientation;
				lastUpAngle = ccl_ent.orientation.vectorUp().angleTo(worldScripts["Deep_Horizon_Adv_Nav_Comp"]._orthoNormalise(ccl_ent.heading, ccl_tar[1].position.subtract(ccl_ent.position)));
				if ((!ccl_recall) && (ccl_tarVA.length > 0) && (croStep < ccl_tarVA.length)) {
					ccl_tar = ccl_tarVA[croStep];
				}
				if (ccl_tar[0] == 0) {
					max_ang_vel = ccl_ent.maxPitch;
					targetVector = ccl_tar[1].position.subtract(ccl_ent.position).direction();
					if (reset == 0) initialAngle = ccl_ent.heading.angleTo(targetVector);
				}
				else {
					max_ang_vel = ccl_ent.maxRoll;
					targetVector = worldScripts["Deep_Horizon_Adv_Nav_Comp"]._orthoNormalise(ccl_ent.heading, ccl_tar[1].position.subtract(ccl_ent.position));
					if (reset == 0) initialAngle = ccl_ent.orientation.vectorUp().angleTo(targetVector);
				}
				if (ccl_angAccA.length > 0) if (croStep < ccl_angAccA.length) { ccl_angAcc = ccl_angAccA[croStep]; } else { ccl_angAcc = ccl_angAccA[ccl_angAccA.length - 1]; }
				if (ccl_angDecA.length > 0) if (croStep < ccl_angDecA.length) { ccl_angDec = ccl_angDecA[croStep]; } else { ccl_angDec = ccl_angDecA[ccl_angDecA.length - 1]; }
				ccl_dampa = max_ang_vel / ccl_angAcc;
				ccl_dampb = max_ang_vel / ccl_angDec;
				let ret_val = worldScripts["Deep_Horizon_Adv_Nav_Comp"]._anc_time_taken(initialAngle, max_ang_vel, ccl_dampa, ccl_dampb);
				ccl_last = ret_val[0];
				log("Reorient Function", "ccl_last = " + ccl_last + ".");
				ccl_dampa = ret_val[1];
				ccl_dampb = ret_val[2];
				totalSecs += ccl_last;
				if (croStep == (ccl_tarVA.length - 1)) cheyds_fcbs_ref[1] = absSecs + totalSecs;
				alpha = ccl_last / (ccl_last - (ccl_dampa + ccl_dampb) / 2);
			}
			ccl_sum += delta;
			if (ccl_sum >= ccl_last) {
				if (ccl_recall) ccl_sum = 0;
				else if (croStep < (ccl_tarVA.length - 1)) {
					croStep += 1;
					ccl_sum = 0;
					reset = 0;
					return;
				}
				else if ((croStep == (ccl_tarVA.length - 1)) && (!finalCallbackCalled)) {
					finalCallbackCalled = true;
					ccl_callback();
				}
				else return;
			}
			if (ccl_tar[0] == 0) {
				let currentForward = ccl_ent.heading;
				let currentForwardAngle = currentForward.angleTo(targetVector);
				let currentUpAngle = ccl_ent.orientation.vectorUp().angleTo(targetVector);
				if (ccl_sum < ccl_dampa) aimAngle = (1 - (alpha * (ccl_last / ccl_dampa) * (ccl_sum / ccl_last) * (ccl_sum / ccl_last) / 2)) * initialAngle;
				else if (ccl_sum < (ccl_last - ccl_dampb)) aimAngle = (1 - ((alpha * ccl_dampa / ccl_last / 2) + alpha * (ccl_sum - ccl_dampa) / ccl_last)) * initialAngle;
				else if (ccl_sum < ccl_last) aimAngle = (alpha * (ccl_last / ccl_dampb) * (ccl_last - ccl_sum) / ccl_last * (ccl_last - ccl_sum) / ccl_last / 2) * initialAngle;
				else aimAngle = 0;
				let angle = currentForwardAngle - aimAngle;
				let cross = lastOrientation.vectorForward().cross(targetVector).direction();
				ccl_ent.orientation = worldScripts["Deep_Horizon_Adv_Nav_Comp"]._lookAtRotate(ccl_ent.orientation.rotate(cross, -angle).vectorForward(), lastOrientation.vectorUp());
				lastOrientation = ccl_ent.orientation;
				lastUpAngle = currentUpAngle;
			}
			else {
				let currentForward = ccl_ent.heading;
				let currentForwardAngle = currentForward.angleTo(targetVector);
				let currentUpAngle = ccl_ent.orientation.vectorUp().angleTo(targetVector);
				if (ccl_sum < ccl_dampa) aimAngle = (1 - (alpha * (ccl_last / ccl_dampa) * (ccl_sum / ccl_last) * (ccl_sum / ccl_last) / 2)) * initialAngle;
				else if (ccl_sum < (ccl_last - ccl_dampb)) aimAngle = (1 - ((alpha * ccl_dampa / ccl_last / 2) + alpha * (ccl_sum - ccl_dampa) / ccl_last)) * initialAngle;
				else if (ccl_sum < ccl_last) aimAngle = (alpha * (ccl_last / ccl_dampb) * (ccl_last - ccl_sum) / ccl_last * (ccl_last - ccl_sum) / ccl_last / 2) * initialAngle;
				else aimAngle = 0;
				let angle = currentUpAngle - aimAngle;
				let cross = lastOrientation.vectorUp().cross(targetVector).direction();
				ccl_ent.orientation = worldScripts["Deep_Horizon_Adv_Nav_Comp"]._lookAtRotate(lastOrientation.vectorForward(), ccl_ent.orientation.rotate(cross, -angle).vectorUp());
				lastOrientation = ccl_ent.orientation;
				lastUpAngle = currentUpAngle;
			}
			reset = 1;
			return;
		});
		absSecs = clock.absoluteSeconds;
		if (ccl_recall) this.cheyds_fcbs.push([this.cheyds_fcb, absSecs + 0xFFFFFF]);
		else {
			this.cheyds_fcbs.push([this.cheyds_fcb, absSecs + 0xFFFFFF]);
			cheyds_fcbs_ref = this.cheyds_fcbs[this.cheyds_fcbs.length - 1];
			if (this.cheyds_doAutoRemove) this.cheyds_autoRemove(1);
		}
		return;
	},
	// Check fcbs and log them. 
	fcbCheckAll: function () {
		if (!this.cheyds_fcbs.length) return (false);
		for (let i = 0; i < this.cheyds_fcbs.length; i++) {
			if (isValidFrameCallback(this.cheyds_fcbs[i][0])) log("fcbCheck", "valid i:" + i + " fcb:" + this.cheyds_fcbs[i][0] + " typeof:" + typeof (this.cheyds_fcbs[i][0]) + " lasts:" + (clock.absoluteSeconds - this.cheyds_fcbs[i][1]));
			else log("fcbCheck", "invalid i:" + i);
		}
		return (true);
	},
	// Clean fcbs.
	//	all		Boolean. Clean all fcbs. 
	fcbRemove: function fcbRemove(all) {
		if (!this.cheyds_fcbs.length) return (false);
		var checked = 0;
		for (let i = 0; i < this.cheyds_fcbs.length; i++) {
			if (isValidFrameCallback(this.cheyds_fcbs[i][0])) {
				if (all) removeFrameCallback(this.cheyds_fcbs[i][0]);
				else if (this.cheyds_doAutoRemove && clock.absoluteSeconds > this.cheyds_fcbs[i][1]) {
					removeFrameCallback(this.cheyds_fcbs[i][0]);
					checked++;
				}
			} else checked++;
		}
		if (all || this.cheyds_fcbs.length === checked) {
			this.cheyds_fcb = 0;
			this.cheyds_fcbs = [];
			this.cheyds_autoRemove();
		}
		return (true);
	}
};
 |