| Config/script.js | "use strict";
this.name = "navi_mfd";
this.author = "spara";
this.copyright = "2015 Spara";
this.description = "Show navigational info in an MFD";
this.licence = "CC BY-NC-SA 4.0";
//distance unit to show in mfd.
this.$distUnits = ["OU", "km", "m", "CZ"];
//basis in meters for one distUnit.
//for kiloometers, set to 1000
//for OUs, set to 905520, which is roughly the distance of planet and sun in Lave in an unmodified game.
this.$ostronomicalUnits = [905520, 1000, 1, 2.08641];
this.$rounding = [6, 3, 0, 0];
this.$unitSetting = 0;
this._cancelled = false;
this.libSettings = {
	Name: this.name, Display: "Settings", Alias: "Navigation MFD", Alive: "libSettings",
	SInt: {
		S0: { Name: "$unitSetting", Def: 0, Min: 0, Max: 3, Desc: "Display units" },
		Info: "0 = OU, 1 = km, 2 = m, 3 = CZ (Cavezzi)"
	},
};
this.startUp = function () {
	this.$interval = 1; //Refresh frequency. (in secs)
	if (missionVariables.NavigationMFD_Units) {
		this.$unitSetting = parseInt(missionVariables.NavigationMFD_Units);
	}
	//timer for display update
	this.$distanceTimer = new Timer(this, this.$updateDistance, 0, this.$interval);
	this.$distanceTimer.stop();
	//storage variable
	this.$targetName = "unknown";
	//symbols
	this.$govs = new Array();
	for (var i = 0; i < 8; i++) {
		this.$govs.push(String.fromCharCode(i));
	}
	this.$ecos = new Array();
	for (i = 0; i < 8; i++) {
		this.$ecos.push(String.fromCharCode(23 - i));
	}
	this.$nova = String.fromCharCode(215) + " " + String.fromCharCode(215) + " ";
	//for route planner to work in interstellar space too	
	this.$savedSystem = system.ID;
	//bgs voice countdown timer differs from core countdown
	//hyper countdown is synced to bgs or ctz if present,
	//otherwise to core countdown
	if (worldScripts["BGS-M"] || worldScripts["countdown_to_zero"]) this.$BGS = true;
	//docking clearance status for stations
	this.$clearanceStation = null;
}
this.startUpComplete = function () {
	// register our settings, if Lib_Config is present
	if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this.libSettings);
}
this.playerWillSaveGame = function () {
	missionVariables.NavigationMFD_Units = this.$unitSetting;
}
//start display timer when launching and entering system
this.shipWillLaunchFromStation = this.shipExitedWitchspace = function () {
	this._cancelled = 0;
	//for route planner to work in interstellar space too
	if (!system.isInterstellarSpace) this.$savedSystem = system.ID;
	this.$setHyperMessage();
	this.$distanceTimer.start();
}
//switch to countdown synced timer
this.playerStartedJumpCountdown = function (type, seconds) {
	if (this._cancelled == true) {
		this._cancelled = false;
		return;
	}
	if (type === "standard") {
		this.$distanceTimer.stop();
		this.$countdown = seconds;
		this.$hyperTimer = new Timer(this, this._hyperDriveCountdown, 0, 1);
	}
}
//switch back to basic timer
this.playerCancelledJumpCountdown = this.playerJumpFailed = function () {
	if (!this.$countdown) this._cancelled = true;
	if (this.$hyperTimer) {
		this.$hyperTimer.stop();
		delete this.$hyperTimer;
		delete this.$countdown;
	}
	this.$distanceTimer.start();
}
//update display once and stop countdown timer
this.shipWillEnterWitchspace = function () {
	//triggers "hyperjump in progress" message
	if (this.$hyperTimer) {
		this.$countdown = -1;
		this.$updateDistance();
		this.$hyperTimer.stop();
		delete this.$hyperTimer;
		delete this.$countdown;
	}
}
//update hyperspace display data when switching from map
this.guiScreenChanged = function (to, from) {
	if (from === "GUI_SCREEN_SHORT_RANGE_CHART" || from === "GUI_SCREEN_LONG_RANGE_CHART") {
		this.$setHyperMessage();
		this.$updateDistance();
	}
}
//update compass target data and start timer on launch
this.shipLaunchedFromStation = function () {
	this.compassTargetChanged(player.ship.compassTarget, player.ship.compassMode);
	this.$distanceTimer.start();
}
//stop timers when docking
this.shipWillDockWithStation = function () {
	//yes, it's possible to dock while hyperspace countdown is running
	if (this.$hyperTimer) {
		this.$hyperTimer.stop();
		delete this.$hyperTimer;
		delete this.$countdown;
		this.$setHyperMessage();
		this.$updateDistance();
	}
	this.$distanceTimer.stop();
}
//hyper countdown timer function
this._hyperDriveCountdown = function _hyperDriveCountdown() {
	this.$updateDistance();
	this.$countdown--;
}
//update mfd
this.$updateDistance = function $updateDistance() {
	if (player.ship.equipmentStatus("EQ_NAVIGATION_MFD") === "EQUIPMENT_OK") {
		player.ship.setMultiFunctionText("navi_mfd", this.$message());
	}
	else player.ship.setMultiFunctionText("navi_mfd", null);
}
//compose output
this.$message = function () {
	//hyperspace data is updated only when it changes
	var message = this.$hyperMessage;
	message += "Hyperdrive Status:\n" + this.$status();
	var target = player.ship.compassTarget;
	//valid target case
	if (target) {
		if (target.isStation) {
			var stationStatus = this._stationStatus(target);
		}
		else var stationStatus = "";
		message += this._paddedHyperInfo("Space Compass:") + stationStatus + "\n" + this.$truncate("  " + this.$targetName, 14) + "\n";
		if (this.$targetName != "Jump Marker") {
			var distInM = player.ship.position.distanceTo(target) - target.collisionRadius;
			if (distInM < 0) distInM = 0;
			var dist = distInM / this.$ostronomicalUnits[this.$unitSetting];
			message += "Distance: " + dist.toFixed(this.$rounding[this.$unitSetting]) + " " + this.$distUnits[this.$unitSetting] + "\n";
			message += "Estimated Travel Time: ";
			var vPtoT = target.position.subtract(player.ship.position); //player-target vector
			var dotVH = player.ship.velocity.dot(vPtoT); //dot product between velocity and heading
			if (dotVH > 0.1) {//check heading
				var vVelPtoT = vPtoT.multiply(dotVH / (vPtoT.dot(vPtoT))); //velocity vector projection on player-target vector    
				var timeToTarget = distInM / vVelPtoT.magnitude();
				var hours = Math.floor(timeToTarget / 3600);
				var mins = Math.floor((timeToTarget - 3600 * hours) / 60);
				var secs = Math.floor(timeToTarget - hours * 3600 - mins * 60);
				if (hours < 10)
					hours = "0" + hours;
				if (mins < 10)
					mins = "0" + mins;
				if (secs < 10)
					secs = "0" + secs;
				message += hours + ":" + mins + ":" + secs;
			}
			else message = message + "**:**:**";
		} else {
			message += "Distance: -\nEstimated Travel Time: **:**:**";
		}
	}
	//null case
	else {
		message += "Space Compass Target:\n  -\n";
		message += "Distance: -\n";
		message += "Estimated Travel Time: **:**:**";
	}
	return message;
}
//remember the station which clearance was asked from
this.playerRequestedDockingClearance = function () {
	this.$clearanceStation = player.ship.target;
}
//the docking status of stations
this._stationStatus = function (whom) {
	if (whom.requiresDockingClearance) {
		if (whom === this.$clearanceStation) {
			switch (player.dockingClearanceStatus) {
				case "DOCKING_CLEARANCE_STATUS_REQUESTED":
					return "Dock: Hold";
					break;
				case "DOCKING_CLEARANCE_STATUS_GRANTED":
					return "Dock: Granted";
					break;
				case "DOCKING_CLEARANCE_STATUS_TIMING_OUT":
					return "Dock: Expiring";
					break;
			}
		}
		return "Dock: Request";
	}
	return "Dock: Free";
}
//compose hyperspace output
this.$setHyperMessage = function () {
	// nextSystem new in for Oolite 1.86
	if (player.ship.hasOwnProperty("nextSystem") === true) {
		var hyperSystemObj = System.infoForSystem(galaxyNumber, player.ship.nextSystem);
	} else {
		var hyperSystemObj = System.infoForSystem(galaxyNumber, player.ship.targetSystem);
		//first condition for compatibility with older versions of Oolite
		if (player.ship.routeMode && player.ship.targetSystem !== system.ID) {
			var mode = player.ship.routeMode;
			if (mode === "OPTIMIZED_BY_JUMPS" || mode === "OPTIMIZED_BY_TIME") {
				// cope with the situation of being in interstellar space and then selecting your originating system as the target
				if (player.ship.targetSystem === this.$savedSystem && system.isInterstellarSpace === true) {
					hyperSystemObj = System.infoForSystem(galaxyNumber, this.$savedSystem);
				} else {
					var routeToNextSyst = System.infoForSystem(galaxyNumber, this.$savedSystem).routeToSystem(hyperSystemObj, mode);
					if (routeToNextSyst && routeToNextSyst.route.length >= 1 && routeToNextSyst.route[1]) {
						hyperSystemObj = System.infoForSystem(galaxyNumber, routeToNextSyst.route[1]);
					}
					//if no route can be plotted, set hyperSystemObj to current system to nullify target from printout
					else hyperSystemObj = system.info;
				}
			}
		}
	}
	var hyperSystem = hyperSystemObj.name;
	//in interstellar space the distance calculation is done manually.
	if (system.isInterstellarSpace) {
		var s = player.ship.galaxyCoordinatesInLY;
		var t = hyperSystemObj.coordinates;
		//this is officially weird
		this.$hyperDistance = 0.4 * Math.floor(2.5 * Math.sqrt(Math.pow(s[0] - t[0], 2) + Math.pow(s[1] - t[1], 2)));
	}
	else {
		this.$hyperDistance = hyperSystemObj.distanceToSystem(system.info);
	}
	//default to no target set
	var hyperTime = "**:**:**\n";
	var hyperDistance = "-";
	if (!hyperSystemObj.sun_gone_nova) {
		var planetDistance = ((hyperSystemObj.planet_distance - hyperSystemObj.radius * 10) / this.$ostronomicalUnits[this.$unitSetting]).toFixed(this.$rounding[this.$unitSetting]);// + " " + this.$distUnits[this.$unitSetting];
	}
	else var planetDistance = "-";
	if (hyperSystemObj.systemID != -1) {
		var W2P = Vector3D([0, 0, hyperSystemObj.planet_distance]);
		if (hyperSystemObj.sun_distance_modifier) {
			var P2SDist = hyperSystemObj.sun_distance_modifier * hyperSystemObj.radius * 10;
		}
		else if (hyperSystemObj.sun_distance_multiplier) {
			var P2SDist = hyperSystemObj.sun_distance * hyperSystemObj.sun_distance_multiplier;
		}
		else var P2SDist = hyperSystemObj.sun_distance;
		var P2S = Vector3D(hyperSystemObj.sun_vector.split(" ")).multiply(-1 * P2SDist);
		var sunDistance = ((W2P.add(P2S).magnitude() - hyperSystemObj.sun_radius) / this.$ostronomicalUnits[this.$unitSetting]).toFixed(this.$rounding[this.$unitSetting]) + " (" + this.$distUnits[this.$unitSetting] + ")";
	}
	//target system set
	if (hyperSystem !== system.name) {
		if (this.$hyperDistance < 0.1) var hyperDistance = "0.1";
		else var hyperDistance = this.$hyperDistance.toFixed(1);
		hyperDistance += " ly";
		//calculate time for valid cases only
		if (player.ship.hasHyperspaceMotor && this.$hyperDistance <= 7) {
			var hyperTime = this.$hyperDistance * this.$hyperDistance;//in hours
			var hours = Math.floor(hyperTime);
			var mins = Math.floor((hyperTime - hours) * 60);
			var secs = Math.floor((hyperTime - hours - mins / 60) * 3600);
			if (hours < 10) hours = "0" + hours;
			if (mins < 10) mins = "0" + mins;
			if (secs < 10) secs = "0" + secs;
			hyperTime = hours + ":" + mins + ":" + secs + "\n";
		}
	}
	if (system.isInterstellarSpace) {
		this.$hyperMessage = "Present System: Interstellar space\n";
	}
	else {
		if (!system.sun.hasGoneNova) {
			this.$hyperMessage = this._hyperSystemLine("Present System:", system.name + " (TL" + (system.techLevel + 1) + ")", this.$ecos[system.economy] + this.$govs[system.government]) + "\n";
		}
		else {
			this.$hyperMessage = this._hyperSystemLine("Present System:", system.name + " (TL0)", this.$nova) + "\n";
		}
	}
	if (hyperSystem !== system.name) {
		if (!hyperSystemObj.sun_gone_nova) {
			this.$hyperMessage += this._hyperSystemLine("Target System:", hyperSystem + " (TL" + (hyperSystemObj.techlevel + 1) + ")", this.$ecos[hyperSystemObj.economy] + this.$govs[hyperSystemObj.government]) + "\n";
		}
		else {
			this.$hyperMessage += this._hyperSystemLine("Target System:", hyperSystem + " (TL0)", this.$nova) + "\n";
		}
	}
	else this.$hyperMessage += "Target System: " + hyperSystem + "\n";
	//this.$hyperMessage = this.$hyperMessage + "Estimated Travel Time: " + hyperTime;
	this.$hyperMessage += this._paddedHyperInfo("  Distance: " + hyperDistance);
	this.$hyperMessage += "Time: " + hyperTime;
	//this.$hyperMessage += "  Main Lane: " + planetDistance + "\n";
	this.$hyperMessage += this._paddedHyperInfo("  Planet: " + (isNaN(planetDistance) ? "-" : planetDistance));
	this.$hyperMessage += "Sun: " + sunDistance + "\n";
}
//helper to format system lines
this._hyperSystemLine = function (preText, systemName, systemSymbol) {
	var line = preText + " " + systemName;
	return line + this._pads(line + systemSymbol, 14.4) + systemSymbol;
}
//helper to format 1st hyper system data line
this._paddedHyperInfo = function (text) {
	return text + this._pads(text, 7.1);
}
//padding function
this._pads = function (text, width) {
	var hairSpace = String.fromCharCode(31);
	var hairSpaceLength = defaultFont.measureString(hairSpace);
	var currentLength = global.defaultFont.measureString(text);
	var padsNeeded = Math.floor((width - currentLength) / hairSpaceLength);
	if (padsNeeded >= 1) {
		return new Array(padsNeeded).join(hairSpace);
	}
	else return "";
}
//update compass target name. there's some cim still here.
this.compassTargetChanged = function (whom, mode) {
	this.$targetName = "Initializing...";
	if (!whom) return;//null case
	if (whom.displayName) this.$targetName = whom.displayName;
	else if (whom.name) this.$targetName = whom.name;
	else if (whom.beaconLabel) this.$targetName = whom.beaconLabel;
	else if (whom.beaconCode) this.$targetName = whom.beaconCode;
	else this.$targetName = "Unidentified";
	if (mode === "COMPASS_MODE_BASIC") {
		if (whom.isPlanet) this.$targetName = "Planet";
		else this.$targetName = "Main Station";
	}
	else if (mode === "COMPASS_MODE_BEACONS") {
		// Escape capsule locator OXP
		if (this.$targetName === "Metal fragment" && whom.beaconCode.substr(0, 1) === "E")
			this.$targetName = "Escape Capsule";
		else if (this.$targetName === "Navigation Buoy") {
			if (whom.beaconCode.substr(0, 1) === "g")
				this.$targetName = "GRS Buoy Factory Nav Buoy";
			else if (whom.beaconCode.substr(0, 1) === "N")
				this.$targetName = "Main Station Nav Buoy";
		}
		// Tracker OXP
		else if (this.$targetName === "Tracker") {
			if (whom.target.displayName)
				this.$targetName = whom.target.displayName;
			else this.$targetName = whom.target.name;
			this.$targetName += " (Tracked)";
		}
	}
	else if (mode === "COMPASS_MODE_PLANET")
		this.$targetName = "Planet";
	else if (mode === "COMPASS_MODE_SUN") {
		//distant suns oxp
		if (system.info.sun_name)
			this.$targetName = system.info.sun_name + " (Sun)";
		else this.$targetName = "Sun";
	}
	else if (mode === "COMPASS_MODE_STATION")
		this.$targetName = "Main Station";
	this.$updateDistance();
}
//hyperdrive status. mass locks, fuel etc. there's some cim here too.
this.$status = function () {
	if (this.$countdown && this.$countdown === -1) {
		return "  Hyperjump in progress\n";
	}
	if (!player.ship.hasHyperspaceMotor) return "  Not installed\n";
	if (player.ship.targetSystem === system.ID) {
		return "  Present system targeted\n";
	}
	if (this.$hyperDistance > 7) return "  Target out of range\n";
	if (player.ship.fuel < this.$hyperDistance) {
		return "  Not enough fuel\n";
	}
	var position = player.ship.position;
	function isBlocker(entity) {
		if (!entity.isShip) return false;
		if (entity.mass / position.squaredDistanceTo(entity) >= 10) {
			return true;
		}
		return false;
	}
	var blockers = system.filteredEntities(this, isBlocker, player.ship, 25600);
	if (blockers.length > 0)
		return this.$truncate("  Too close to " + blockers[0].name, 14) + "\n";
	if (this.$countdown) {
		//sync to BGS countdown
		if (this.$BGS) {
			return "  Countdown in progress " + (this.$countdown - 1) + "\n";
		}
		//sync to core countdown
		else {
			return "  Countdown in progress " + this.$countdown + "\n";
		}
	}
	return "  Ready\n";
}
//squeezing looks ugly, so I'll truncate instead.
this.$truncate = function (line, width) {
	var lastChar = "";
	//remove characters from the end of the line.
	while (global.defaultFont.measureString(line) > width) {
		lastChar = line.charAt(line.length - 1);
		line = line.slice(0, -1);
	}
	//no chopping was done. return
	if (lastChar === "") return line;
	//if the last char is a space or last removed char was space, return
	if (line.charAt(line.length - 1) === " " || lastChar === " ") {
		return line;
	}
	//if the last char is a char and the second last is a space, remove the char and return
	if (line.charAt(line.length - 2) === " ") {
		return line.slice(0, -1);
	}
	//it seems that we have a chopped word in our hands. looks nicer to end it with a dot
	line += ".";
	while (global.defaultFont.measureString(line) > width) {
		line = line.slice(0, -2);
		line += ".";
	}
	return line;
}
 |