Back to Index Page generated: Jun 13, 2026, 7:54:54 PM

Expansion Navigation MFD

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Display navigational information in an MFD. Information includes data from hyperdrive system and space compass. Display navigational information in an MFD. Information includes data from hyperdrive system and space compass.
Identifier oolite.oxp.spara.navigation_mfd oolite.oxp.spara.navigation_mfd
Title Navigation MFD Navigation MFD
Category HUDs HUDs
Author Mika Spara Mika Spara
Version 1.9.2 1.9.2
Tags mfd mfd
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Dependent Expansions
  • oolite.oxp.Norby.Ambience_Collection:1.3
  • Information URL https://wiki.alioth.net/index.php/Navigation_MFD n/a
    Download URL https://wiki.alioth.net/img_auth.php/9/9a/Navigation_MFD_1.9.2.oxz n/a
    License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
    File Size n/a
    Upload date 1700567193

    Relationships Diagram

    Documentation

    Also read http://wiki.alioth.net/index.php/Navigation%20MFD

    navigation_mfd_readme_&_license.txt

    Navi MFD OXP ver 1.9.1 (18.1.2018)
    
    Author: spara (Mika Spåra)
    
    _Overview_
    
    Display navigational information in an MFD. Information includes data from hyperdrive system and space compass. Costs 650 credits.
    
    _Requirements_
    
    * Oolite 1.82
    
    _Installing_
    
    Install the OXP by copying navigation_mdf.oxz to your AddOns-folder.
    
    _Credits_
    
    ------
    
    This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 4.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
    

    Equipment

    Name Visible Cost [deci-credits] Tech-Level
    Navigation MFD yes 6500 7+

    Ships

    This expansion declares no ships.

    Models

    This expansion declares no models.

    Scripts

    Path
    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;
    }