Back to Index Page generated: Dec 20, 2024, 7:22:09 AM

Expansion Damage Report MFD

Content

Warnings

  1. Information URL mismatch between OXP Manifest and Expansion Manager string length at character position 0

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Displays a list of damaged equipment in an MFD. Displays a list of damaged equipment in an MFD.
Identifier oolite.oxp.phkb.DamageReportMFD oolite.oxp.phkb.DamageReportMFD
Title Damage Report MFD Damage Report MFD
Category HUDs HUDs
Author phkb phkb
Version 2.4 2.4
Tags mfd, equipment mfd, equipment
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL n/a
Download URL https://wiki.alioth.net/img_auth.php/3/3d/DamageReportMFD.oxz n/a
License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
File Size n/a
Upload date 1610873279

Documentation

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

readme.txt

Damage Report MFD
By Nick Rogers

Overview - MFD
==============
This is a simple MFD that will automatically pop up in the first free MFD slot when equipment items are damaged. It will list items in cost order descending, so the more expensive items are listed at the top. The first line will display a count of the number of damaged items, and then the top 9 damaged items will be listed underneath.

The MFD has a couple of modes, selectable via a primable equipment item. Press "B" to change the mode. Available modes are:

Visible when damaged    - The damage report will always be visible when items are damaged.
Autohide after 60 sec   - The damage report will auto-hide itself after displaying damaged items for 60 seconds.
Manual                  - The damage report will only be displayed when the user makes it visible using the MFD selection keys.

The Damage Report MFD costs 250 Cr and is available from any system of tech level 4 or above.

The selected mode is stored in the save game file.

Damage Report Interface
-----------------------
When this OXP is installed, and your ship has damaged equipment, a new interface screen will become available on the F4 screen, called "Damage Report". Opening this screen will list each damaged item, the estimated cost of repairs, and the minimum tech level required in order to repair it.

If you select an item in the list and press enter, a long range chart will appear, mapping the route to the closest destination of a suitable tech level where repairs can be conducted. Below this will be details of that equipment item, including the estimated repair cost and tech level required for purchase.  

If the closest planet for repairs is not the current planet, you will be able to automatically set your destination to that planet with the option "Set course for (planet name)". 

If you have bought the MFD equipment, you will also have the ability to set the mode here. A menu option will be available, displaying the current type of MFD (Active or Passive), as well as the current mode. Selecting this menu item will cycle through all the available options.

MFD Active: The mode can be changed during flight using primable equipment.
MFD Passive: The mode can't be changed during flight, only via the interface screen.

License
=======
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/

Image from http://simpleicon.com/wrench.html.

Version History
===============
2.4
- Fixed discrepancy between route shown on map and the number of jumps shown in text.
- Integration with planned OXP's.

2.3.4
- Bug fixes.

2.3.3
- When setting a course to a target system, F7 screen will now display the new destination.
- When viewing course to nearest repair system, map with switch to a short range chart if the distance to it is less than 7.4 LY.

2.3.2
- Added total estimated costs to the F4 interface page if more than 1 item is damaged.
- Code refactoring.

2.3.1
- Added number of jumps to map screen.

2.3.0
- Included Ship Configuration armour repair items in damaged list.
- Included IronHide armour repair item in damaged list.

2.2.8
- Better integration with the ANA.

2.2.7
- Fixed issue with HUD not becoming visible again when launching while viewing the damage report screen.

2.2.6
- Fixed issue where player destination was getting reset when a chart screen is exited by pressing a function key, rather than via the "Exit" command.

2.2.5
- Fixed issue where displaying course info when there is no system in the current chart with the required TL would result in a blank page, rather than the chart.
- Added check for above condition, so now the player is told there is no system with the required TL in the current chart.
- Fixed issue where, if the system with the required TL was more than 99 ly away, it was being assumed there was no system available.

2.2.4
- Updated check for "Allow Big GUI".
- Bug fixes.

2.2.3
- Fixed small bug when attempting to update the MFD after the player ship is destroyed.
- Updated screenID's to enable BGS background sounds.
- Renamed background overlay image to prevent possibility of future duplication.
- Toned down overlay image.
- Trimmed unnecessary items from equipment.plist.
- Code refactoring.

2.2.2
- Fixed small issue with manifest file.

2.2.1
- Updated distance calculations to use route distance, rather than point-to-point distance.
- Fixed missing ";" in Javascript file.

2.2.0
- Added overlay background image to interface screen.

2.1.1
- Added routine to use 1.83/4 code to check for big GUI HUD's.

2.1.0
- MFD can now be removed.
- MFD now has two types: active (where the mode can be changed during flight) and passive (where the mode can only be changed via the interface screen)
- Added mission screen exception for Xenon Redux UI.
- Code refactoring.

2.0.1
- Added direct support for the MFD configuration system in the HUD Selector OXP.
- Improved the ability of the MFD to autohide itself when there are no damaged items.

2.0.0
- Added "Damage Report" interface screen.

1.0.0
- Initial release

Equipment

Name Visible Cost [deci-credits] Tech-Level
Damage Report MFD yes 2500 4+
Damage Report MFD yes 2500 4+
Remove Damage Report MFD no 500 4+
Remove Damage Report MFD no 500 4+

Ships

This expansion declares no ships. This may be related to warnings.

Models

This expansion declares no models. This may be related to warnings.

Scripts

Path
Scripts/damagereport_conditions.js
"use strict";
this.name        = "DamageReportMFD_Conditions";
this.author      = "phkb";
this.copyright   = "2016 phkb";
this.description = "Condition script for the Damage Report MFD (Passive Mode)";
this.licence     = "CC BY-NC-SA 4.0";

//-------------------------------------------------------------------------------------------------------------
this.allowAwardEquipment = function(equipment, ship, context) {
	var p = player.ship;
	if (equipment == "EQ_DAMAGE_REPORT_MFD_PASSIVE") {
		if (context != "scripted") return false;
	}
	if (equipment == "EQ_DAMAGE_REPORT_MFD") {
		if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") == "EQUIPMENT_OK" || p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") == "EQUIPMENT_DAMAGED") return false;
	}
	return true
}
Scripts/damagereport_equip.js
"use strict";
this.name        = "DamageReportMFD_Equipment";
this.author      = "phkb";
this.copyright   = "2016 phkb";
this.description = "Allows the Damage Report MFD mode to be changed.";
this.licence     = "CC BY-NC-SA 4.0";

this.mode = function() {

	var w = worldScripts.DamageReportMFD;
	var p = player;

	w._mode += 1;
	if (w._mode == 3) w._mode = 0;

	switch (w._mode) {
		case 0:
			p.consoleMessage("Mode now 'Visible when damaged'");
			break;
		case 1:
			p.consoleMessage("Mode now 'Autohide after " + w._timeout + " sec'");
			break;
		case 2:
			p.consoleMessage("Mode now 'Manual'");
			break;
	}
	w.$updateMFD();

}
Scripts/damagereport_mfd.js
"use strict";
this.name        = "DamageReportMFD";
this.author      = "phkb";
this.copyright   = "2016 phkb";
this.description = "Displays damaged equipment in an MFD";
this.licence     = "CC BY-NC-SA 4.0";

this._mfdID = -1;
this._mode = 0;							// can be 0 = Visible when damaged, 1 = Autohide 60, 2 = manual
this._timeout = 60;						// number of seconds mode 1 will take to autohide

this._maxpage = 0;						// total number of pages available for display
this._curpage = 0;						// the current page being displayed
this._msRows = 18;						// rows to display on the mission screen
this._displayMode = 0;					// controls the type of display (0 = master list, 1 = detail item)
this._selectedKey = ""; 				// equipment key of the currently selected item on the damage report interface screen.
this._routeMode = "SHORTEST";			// default mode for long range chart

//-------------------------------------------------------------------------------------------------------------
this.startUp = function() {
	this._suspendedDestination = null;
	this._hudHidden = false;
	this._damageReportOpen = false;

	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.startUpComplete = function() {
	if (missionVariables.DamageReportMFD_Mode) {
		// get the stored mode value
		this._mode = missionVariables.DamageReportMFD_Mode;
	}
	if (player.ship.docked) {
		this.$initInterface(player.ship.dockedStation);
	}
	// add a mission screen exception to Xenon UI
	if (worldScripts.XenonUI) {
		var wx = worldScripts.XenonUI;
		wx.$addMissionScreenException("oolite-damagereport-detail-map");
	}
	// add a mission screen exception to Xenon Redux UI
	if (worldScripts.XenonReduxUI) {
		var wxr = worldScripts.XenonReduxUI;
		wxr.$addMissionScreenException("oolite-damagereport-detail-map");
	}

	// add interface to HUD selector
	var h = worldScripts.hudselector;
	if (h) h.$HUDSelectorAddMFD(this.name, this.name);
}

//=============================================================================================================
// event interfaces

//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function() {
	// save the mode value
	missionVariables.DamageReportMFD_Mode = this._mode;
}

//-------------------------------------------------------------------------------------------------------------
this.shipWillLaunchFromStation = function(station) {
	this.$updateMFD();
	if (this._mode == 0 && this.$hasDamagedEquipment() == false) {
		if (this._autoHide && this._autoHide.isRunning) this._autoHide.stop();
		this._autoHide = new Timer(this, this.$autoHideMFD, 1, 0);
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipDockedWithStation = function(station) {
	this.$initInterface(station);
}

//-------------------------------------------------------------------------------------------------------------
this.equipmentDamaged = function(equipment) {
	this.$updateMFD();
}

//-------------------------------------------------------------------------------------------------------------
this.equipmentRepaired = function(equipment) {
	// theoretically this shouldn't happen during flight, as repairs are normally conducted at a station.
	// but if something like "repair bots oxp" is inatlled, then it could potentially happen.
	// so we'll update the mfd here as well
	this.$updateMFD();
	if (player.ship.docked) this.$initInterface(player.ship.dockedStation);
}

//-------------------------------------------------------------------------------------------------------------
this.playerBoughtEquipment = function(equipment) {
	var p = player.ship;
	if (equipment == "EQ_DAMAGE_REPORT_MFD_REMOVAL") {
		p.removeEquipment("EQ_DAMAGE_REPORT_MFD");
		p.removeEquipment("EQ_DAMAGE_REPORT_MFD_REMOVAL");
		delete missionVariables.DamageReportMFD_Mode;
	}
	if (equipment == "EQ_DAMAGE_REPORT_MFD_PASSIVE_REMOVAL") {
		p.removeEquipment("EQ_DAMAGE_REPORT_MFD_PASSIVE");
		p.removeEquipment("EQ_DAMAGE_REPORT_MFD_PASSIVE_REMOVAL");
		delete missionVariables.DamageReportMFD_Mode;
	}
	if (equipment == "EQ_DAMAGE_REPORT_MFD") {
		this.$initInterface(p.dockedStation);
	}
}

//-------------------------------------------------------------------------------------------------------------
this.guiScreenChanged = function(to, from) {
	if (from == "GUI_SCREEN_MISSION" && this._damageReportOpen) {
		this._damageReportOpen = false;
		if (this.$isBigGuiActive() == false) player.ship.hudHidden = this._hudHidden;
		if (this._suspendedDestination) player.ship.targetSystem = this._suspendedDestination;
		this._suspendedDestination = null;
	}
}

//=============================================================================================================
// MFD code

//-------------------------------------------------------------------------------------------------------------
// updates the text of the damage report MFD
this.$updateMFD = function() {
	function comparePrice(a,b) {
		if (a.price < b.price) return 1;
		if (a.price > b.price) return -1;
		return 0;
	}

	var output = "";
	var p = player.ship;

	// only report if the player is in space
	if (p.isInSpace == false) return;
	// if we haven't bought the mfd, or it's damaged itself, don't display anything
	if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD") != "EQUIPMENT_OK" && p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") != "EQUIPMENT_OK") return;

	var eq = p.equipment;
	var list = [];

	// look for any visible, damaged equipment
	for (var i = 0; i < eq.length; i++) {
		var q = eq[i];
		if (q.isVisible && p.equipmentStatus(q.equipmentKey) == "EQUIPMENT_DAMAGED") {
			list.push({key:q.equipmentKey, price:q.price, name:q.name});
		}
	}
	var bl = worldScripts.BreakableLasers_Main;
	if (bl) {
		if (p.forwardWeapon.equipmentKey == bl._damagedEq) {
			var q = EquipmentInfo.infoForKey(bl._holdDamage["FORWARD"].primary);
			list.push({key:q.equipmentKey, price:q.price, name:q.name});
		}
		if (p.aftWeapon.equipmentKey == bl._damagedEq) {
			var q = EquipmentInfo.infoForKey(bl._holdDamage["AFT"].primary);
			list.push({key:q.equipmentKey, price:q.price, name:q.name});
		}
		if (p.portWeapon.equipmentKey == bl._damagedEq) {
			var q = EquipmentInfo.infoForKey(bl._holdDamage["PORT"].primary);
			list.push({key:q.equipmentKey, price:q.price, name:q.name});
		}
		if (p.starboardWeapon.equipmentKey == bl._damagedEq) {
			var q = EquipmentInfo.infoForKey(bl._holdDamage["STARBOARD"].primary);
			list.push({key:q.equipmentKey, price:q.price, name:q.name});
		}
	}

	if (list.length == 0) {
		// no damaged equipment. yay!
		output = "Damage Report:\n\nAll systems operational";
	} else {
		// some damaged equipment. bummer.
		list.sort(comparePrice);

		output = "Damage Report: " + list.length + (list.length == 1 ? " item" : " items");

		// once we hit 9 items, we can't display any more so break out of the loop
		var limit = 9;
		if (list.length < 9) limit = list.length
		for (var i = 0; i < limit; i++) {
			output += "\n" + list[i].name;
		}
	}

	// set the text in the MFD
	p.setMultiFunctionText(this.name, output, false);

	// if the hud is hidden don't try an update - it will get confusing as to which mfd slot is open or not.
	if (p.hudHidden == false) {
		// mode 0 (on when damaged), and mode 1 (autohide after 60 secs), see if we need to force the mfd to display
		if (this._mode == 0 || this._mode == 1) {
			// find the slot currently set for this MFD
			this.$findMFDID();

			// if we haven't got a set slot (this._mfdID == -1) or the set slot we had is now unavailable...
			if (this._mfdID == -1 ||
				(p.multiFunctionDisplayList[this._mfdID] && p.multiFunctionDisplayList[this._mfdID] != "" && p.multiFunctionDisplayList[this._mfdID] != this.name)) {
				// find a free slot
				// first, make sure we reset our slot id marker (in the case where the previous one is in use)
				this._mfdID = -1;
				// search for a free slot
				for (var i = 0; i < p.multiFunctionDisplayList.length; i++) {
					if (!p.multiFunctionDisplayList[i] || p.multiFunctionDisplayList[i] == "") {
						this._mfdID = i;
						break;
					}
				}
			}

			// we have a free slot, so force the mfd to display
			if (this._mfdID != -1) {
				if (list.length > 0) {
					p.setMultiFunctionDisplay(this._mfdID, this.name);
				} else {
					// hide the MFD if there are no damaged items
					if (this._mode == 0) this.$autoHideMFD();
				}
				if (this._mode == 1) {
					// set a timer to hide the mfd
					if (this._autoHide && this._autoHide.isRunning) this._autoHide.stop();
					this._autoHide = new Timer(this, this.$autoHideMFD, this._timeout, 0);
				}
			}
		}
		// for mode 2 (Manual) we don't need to do anything. The player will control the visibility of the MFD
	}
}

//-------------------------------------------------------------------------------------------------------------
// records the index of the MFD that currently holds the damage report mfd
this.$findMFDID = function() {
	var p = player.ship;
	for (var i = 0; i < p.multiFunctionDisplayList.length; i++) {
		if (p.multiFunctionDisplayList[i] == this.name) this._mfdID = i;
	}
}

//-------------------------------------------------------------------------------------------------------------
// hides all instances of the damage report MFD
this.$autoHideMFD = function $autoHideMFD() {
	var p = player.ship;
	if (p && p.multiFunctionDisplayList) {
		for (var i = 0; i < p.multiFunctionDisplayList.length; i++) {
			if (p.multiFunctionDisplayList[i] == this.name) {
				p.setMultiFunctionDisplay(i, "");
			}
		}
	}
}

//=============================================================================================================
// Interface screen code

//-------------------------------------------------------------------------------------------------------------
// returns true if there is damaged equipment on the ship, otherwise false
this.$hasDamagedEquipment = function() {
	var p = player.ship;
	var eq = p.equipment;
	var result = false;
	for (var i = 0; i < eq.length; i++) {
		var itm = eq[i];
		if (p.equipmentStatus(itm.equipmentKey) == "EQUIPMENT_DAMAGED") {
			result = true;
			break;
		}
	}
	return result;
}

//-------------------------------------------------------------------------------------------------------------
this.$initInterface = function(station) {
	var p = player.ship;
	if ((p.equipmentStatus("EQ_DAMAGE_REPORT_MFD") == "EQUIPMENT_OK" || p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") == "EQUIPMENT_OK") || this.$hasDamagedEquipment() == true) {
		station.setInterface(this.name,{
			title:"Damage report",
			category:expandDescription("[interfaces-category-logs]"),
			summary:"Lists all damaged equipment and details of potential repair costs and locations",
			callback:this.$initReport.bind(this)
		});
	} else {
		station.setInterface(this.name, null);
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$initReport = function() {
	this._displayMode = 0;
	this._curpage = 0;
	delete this._lastchoice;
	this._routeMode = player.ship.routeMode;
	this.$showDamageReport();
}

//-------------------------------------------------------------------------------------------------------------
// displays either the list of damaged equipment (_displayMode = 0) or the long range chart with route to nearest fix (_displayMode = 1)
this.$showDamageReport = function() {
	function comparePrice(a,b) {
		if (a.price < b.price) return 1;
		if (a.price > b.price) return -1;
		return 0;
	}

	var p = player.ship;

	var curChoices = {};
	var def = "99_EXIT";
	var text = "";

	// turn off the HUD (unless the hud implements the big Gui)
	this._hudHidden = p.hudHidden;
	if (this.$isBigGuiActive() == false) {
		p.hudHidden = true;
	}
	this._msRows = 18;
	if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD") == "EQUIPMENT_OK" || p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") == "EQUIPMENT_OK")
		this._msRows = 17;

	this._damageReportOpen = true;
	var total = 0;

	// lists all damaged equipment
	if (this._displayMode == 0) {
		var eq = p.equipment;
		var list = [];

		// look for any visible, damaged equipment
		for (var i = 0; i < eq.length; i++) {
			var q = eq[i];
			if (q.isVisible && p.equipmentStatus(q.equipmentKey) == "EQUIPMENT_DAMAGED") {
				list.push({key:q.equipmentKey, price:q.price, name:q.name, techLevel:(q.effectiveTechLevel == 0 ? 1 : q.effectiveTechLevel), repairCost:(q.price / 10) * 0.5});
				total += (q.price / 10) * 0.5;
			}
		}

		// look for damaged lasers
		if (worldScripts.BreakableLasers_Main) {
			var bl = worldScripts.BreakableLasers_Main;
			if (p.forwardWeapon.equipmentKey == bl._damagedEq) {
				var q = EquipmentInfo.infoForKey(bl._holdDamage["FORWARD"].primary);
				list.push({key:q.equipmentKey, price:q.price, name:q.name, techLevel:(q.effectiveTechLevel == 0 ? 1 : q.effectiveTechLevel), repairCost:(q.price / 10) * 0.5});
			}
			if (p.aftWeapon.equipmentKey == bl._damagedEq) {
				var q = EquipmentInfo.infoForKey(bl._holdDamage["AFT"].primary);
				list.push({key:q.equipmentKey, price:q.price, name:q.name, techLevel:(q.effectiveTechLevel == 0 ? 1 : q.effectiveTechLevel), repairCost:(q.price / 10) * 0.5});
			}
			if (p.portWeapon.equipmentKey == bl._damagedEq) {
				var q = EquipmentInfo.infoForKey(bl._holdDamage["PORT"].primary);
				list.push({key:q.equipmentKey, price:q.price, name:q.name, techLevel:(q.effectiveTechLevel == 0 ? 1 : q.effectiveTechLevel), repairCost:(q.price / 10) * 0.5});
			}
			if (p.starboardWeapon.equipmentKey == bl._damagedEq) {
				var q = EquipmentInfo.infoForKey(bl._holdDamage["STARBOARD"].primary);
				list.push({key:q.equipmentKey, price:q.price, name:q.name, techLevel:(q.effectiveTechLevel == 0 ? 1 : q.effectiveTechLevel), repairCost:(q.price / 10) * 0.5});
			}
		}

		// look for ShipConfig damaged items
		if (worldScripts.ShipConfiguration_Core) {
			var sc = worldScripts.ShipConfiguration_Core;
			var arm = worldScripts.ShipConfiguration_Armour;
			var k2 = sc.$equipmentItemInUse("armour", player.ship);
			if (k2 !== "") {
				var cur = parseInt(parseInt(((200 - (arm._armourFront + arm._armourAft)) / 200) * 100) / 10);
				if ((arm._armourFront < 100 || arm._armourAft < 100) && cur === 0) cur = 1;
				if (cur > 0) {
					var k1 = k2 + "_REPAIR" + cur
					var q1 = EquipmentInfo.infoForKey(k1);
					list.push({key:k1, price:q1.price, name:q1.name.replace("Repair:","").trim(), techLevel:(k1.effectiveTechLevel === 0 ? 1 : q1.effectiveTechLevel), repairCost:(q1.price / 10) * 0.5});
					total += (q1.price / 10) * 0.5;
				}
			}
		}
		
		// iron hide damage?
		if (p.equipmentStatus("EQ_IRONHIDE") === "EQUIPMENT_OK") {
			if (missionVariables.ironHide_percentage < 100) {
				var q1 = EquipmentInfo.infoForKey("EQ_IRONHIDE_REPAIR");
				list.push({key:q1.equipmentKey, price:q1.price, name:q1.name.replace("Repair:","").trim(), techLevel:(q1.effectiveTechLevel === 0 ? 1 : q1.effectiveTechLevel), repairCost:"Unknown"});
			}
		}
		
		var col1 = 15;
		var col2 = 9;
		var col3 = 8;

		if (list.length == 0) {
			// no damaged equipment. yay!
			text = "All systems operational";
			this._maxpage = 0;
			this._curpage = 0;
		} else {
			// some damaged equipment. bummer.
			list.sort(comparePrice);

			// calculate the max number of pages
			this._maxpage = Math.ceil(list.length / this._msRows);

			text = "Note: Estimated costs are dependant on the station where the repairs are carried out. Actual costs may be higher." + 
				(list.length > 1 ? " (Total estimated costs " + formatCredits(total, false, true) + ")" : "") + "\n\n";

			text += this.$padTextRight("Equipment item", col1);
			text += this.$padTextLeft("Est Repair Cost", col2);
			text += this.$padTextLeft("Tech Level Req", col3);

			var items = 0;
			var start = this._curpage * this._msRows;
			var end = (this._curpage * this._msRows) + this._msRows;
			var itemText = "";
			var repairItem = 0;

			for (var i = 0; i < list.length; i++) {
				items += 1;
				if ((items - 1) >= start && (items - 1) < end) {
					repairItem += 1;
					itemText = this.$padTextRight(list[i].name, col1) +
								this.$padTextLeft((this.$isNumeric(list[i].repairCost) ? list[i].repairCost.toFixed(2) : list[i].repairCost), col2) +
								this.$padTextLeft(list[i].techLevel, col3);
					curChoices["01_" + (repairItem < 10 ? "0" : "") + repairItem + "_REPAIRITEM_" + i + ":" + list[i].key] = {text:itemText, color:"orangeColor", alignment:"LEFT"};
				}
			}
			for (var i = 0; i < (this._msRows + 1) - repairItem; i++) {
				curChoices["02_SPACER_" + i.toString()] = "";
			}

			if (this._curpage < this._maxpage - 1) {
				curChoices["10_GOTONEXT"] = {text:"[option_nextpage]", color:"yellowColor"};
			} else {
				curChoices["10_GOTONEXT"] = {text:"[option_nextpage]", color:"darkGrayColor", unselectable:true};
			}
			if (this._curpage > 0) {
				curChoices["11_GOTOPREV"] = {text:"[option_prevpage]", color:"yellowColor"};
			} else {
				curChoices["11_GOTOPREV"] = {text:"[option_prevpage]", color:"darkGrayColor", unselectable:true};
			}
		}

		if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD") == "EQUIPMENT_OK" || p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") == "EQUIPMENT_OK") {
			var mfdType = "DR_TYPE1";
			if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") == "EQUIPMENT_OK") mfdType = "DR_TYPE2";

			mfdType += "_MODE" + this._mode;

			curChoices["80_" + mfdType] = expandDescription("[" + mfdType + "]");
		}

		curChoices["99_EXIT"] = "Exit Damage Report";

		var opts = {
			screenID: "oolite-damagereport-main-summary",
			title: "Damage Report" + (this._maxpage <= 1 ? "" : " - Page " + (this._curpage + 1) + " of " + this._maxpage),
			allowInterrupt: true,
			exitScreen: "GUI_SCREEN_INTERFACES",
			overlay: {name:"dr-wrench.png", height:546},
			choices: curChoices,
			initialChoicesKey: this._lastchoice?this._lastchoice:def,
			message: text
		};
	}

	// displays the long range chart with route to nearest fix, plus equipment repair details
	if (this._displayMode == 1) {
		var equip = EquipmentInfo.infoForKey(this._selectedKey);
		var closest = this.$findClosestTechLevel(equip.effectiveTechLevel - 1);
		var rt = system.info.routeToSystem(System.infoForSystem(galaxyNumber, closest.id), (this._routeMode === "SHORTEST" ? "OPTIMIZED_BY_JUMPS" : "OPTIMIZED_BY_TIME"));

		text = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; // make sure we don't overlay the chart
		if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY") && rt && rt.route.length > 1) {
			text += this.$padTextRight("", 6.45) + "Jumps: " + (rt.route.length - 1) + "\n";
		} else {
			text += "\n";
		}
		text += this.$padTextRight("Equipment item: ",10) + this.$padTextLeft(equip.name.replace("Repair:","").trim(), 22) + "\n";
		if (equip.price > 0) {
			text += this.$padTextRight("Estimated repair cost: ", 15) + this.$padTextLeft(((equip.price / 10) * 0.5).toFixed(2), 17) + "\n";
		} else {
			text += this.$padTextRight("Estimated repair cost: ", 15) + this.$padTextLeft("Unknown", 17) + "\n";
		}
		text += this.$padTextRight("Tech level required for repair: ", 20) + this.$padTextLeft((equip.effectiveTechLevel == 0 ? 1 : equip.effectiveTechLevel).toString(), 12) + "\n";

		var bg = "LONG_RANGE_CHART";

		if (closest != null) {
			if (system.info.distanceToSystem(System.infoForSystem(galaxyNumber, closest.id)) < 7.4) bg = "SHORT_RANGE_CHART";
			// hold the player's destination
			this._suspendedDestination = p.targetSystem;

			// override it for the display
			p.targetSystem = closest.id;

			text += this.$padTextRight("Nearest planet with suitable tech level: ", 17)
				+ this.$padTextLeft(closest.name + " (TL:" + closest.techLevel + ") "
				+ this._ecos[closest.economy] + this._govs[closest.government]
				+ (closest.id === system.ID ? " *Current*" : ""), 15);

			if (bg === "LONG_RANGE_CHART") {
				if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
					if (this._routeMode == "SHORTEST") {
						bg = "LONG_RANGE_CHART_SHORTEST";
						curChoices["90_SHORTEST"] = {text:"Show shortest route", color:"darkGrayColor", unselectable:true};
						curChoices["91_QUICKEST"] = "Show quickest route";
					} else {
						bg = "LONG_RANGE_CHART_QUICKEST";
						curChoices["90_SHORTEST"] = "Show shortest route";
						curChoices["91_QUICKEST"] = {text:"Show quickest route", color:"darkGrayColor", unselectable:true};
					}
				}
			}
			if (closest.id != system.ID && p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
				curChoices["96_SETCOURSE:" + closest.id] = "Set course for " + closest.name;
			}
		} else {
			text += "\nALERT: No system of required tech level located!\n";
			if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
				bg = "LONG_RANGE_CHART_SHORTEST";
			}
			// hold the player's destination
			this._suspendedDestination = p.targetSystem;
			p.targetSystem = system.ID;
		}

		curChoices["98_CLOSE"] = "Close";
		def = "98_CLOSE";

		var opts = {
			screenID: "oolite-damagereport-detail-map",
			title: "Damage Report Detail",
			backgroundSpecial: bg,
			allowInterrupt: true,
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

	mission.runScreen(opts, this.$screenHandler, this);
}

//-------------------------------------------------------------------------------------------------------------
// handles UI responses on the damage report screen
this.$screenHandler = function(choice) {

	delete this._lastchoice;
	var newChoice = "";

	if (this._suspendedDestination) player.ship.targetSystem = this._suspendedDestination;
	this._suspendedDestination = null;

	if (!choice) {
		return; // launched while reading?
	} else if (choice.indexOf("REPAIRITEM") >= 0) {
		// get the selected item
		// use the index attached to the end of the choice
		this._selectedKey = choice.substring(choice.indexOf(":") + 1);
		this._displayMode = 1;
		this._savedChoice = choice;
	} else if (choice == "11_GOTOPREV") {
		this._curpage -= 1;
		if (this._curpage == 0) {
			newChoice = "10_GOTONEXT";
		}
	} else if (choice == "10_GOTONEXT") {
		this._curpage += 1;
		if (this._curpage == this._maxpage - 1) {
			newChoice = "11_GOTOPREV";
		}
	} else if (choice.indexOf("80_") >= 0) {
		var mfdType = choice.substring(6, 11);
		var mfdMode = parseInt(choice.substring(16));
		mfdMode += 1;
		if (mfdMode == 3) {
			mfdMode = 0;
			if (mfdType == "TYPE1") {
				mfdType = "TYPE2";
			} else {
				mfdType = "TYPE1";
			}
		}
		this.$switchMFDType(mfdType);
		this._mode = mfdMode;
		newChoice = "80_DR_" + mfdType + "_MODE" + mfdMode;
	} else if (choice.indexOf("96_SETCOURSE") >= 0) {
		var id = parseInt(choice.substring(choice.indexOf(":") + 1));
		player.ship.targetSystem = id;
		player.ship.infoSystem = player.ship.targetSystem;
		// force the Xenon HUD to update it's destination
		if (worldScripts.XenonHUD) {
			var w = worldScripts.XenonHUD;
			w.guiScreenChanged("", "GUI_SCREEN_SHORT_RANGE_CHART");
		}
		this._displayMode = 0;
	} else if (choice == "90_SHORTEST") {
		this._routeMode = "SHORTEST";
	} else if (choice == "91_QUICKEST") {
		this._routeMode = "QUICKEST";
	} else if (choice == "98_CLOSE") {
		this._displayMode = 0;
		this._selectedKey = "";
		newChoice = this._savedChoice;
		delete this._savedChoice;
	}

	this._lastchoice = choice;
	if (newChoice != "") {
		this._lastchoice = newChoice;
	}

	if (choice != "99_EXIT") {
		this.$showDamageReport();
	}

}

//-------------------------------------------------------------------------------------------------------------
// switch between the different types of MFD: TYPE1 = normal, with primable equipment, or TYPE2 = passive, can't be set during flight.
this.$switchMFDType = function(mfdType) {
	var p = player.ship;
	switch (mfdType) {
		case "TYPE2":
			if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD") != "EQUIPMENT_OK") return;
			if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") == "EQUIPMENT_OK") return;
			p.removeEquipment("EQ_DAMAGE_REPORT_MFD");
			p.awardEquipment("EQ_DAMAGE_REPORT_MFD_PASSIVE");
			break;
		case "TYPE1":
			if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD_PASSIVE") != "EQUIPMENT_OK") return;
			if (p.equipmentStatus("EQ_DAMAGE_REPORT_MFD") == "EQUIPMENT_OK") return;
			p.removeEquipment("EQ_DAMAGE_REPORT_MFD_PASSIVE");
			p.awardEquipment("EQ_DAMAGE_REPORT_MFD");
			break;
	}
}

//-------------------------------------------------------------------------------------------------------------
// appends space to currentText to the specified length in 'em'
this.$padTextRight = function(currentText, desiredLength, leftSwitch) {
	if (currentText == null) currentText = "";
	var hairSpace = String.fromCharCode(31);
	var ellip = "…";
	var currentLength = defaultFont.measureString(currentText.toString().replace(/%%/g, "%"));
	var hairSpaceLength = defaultFont.measureString(hairSpace);
	// calculate number needed to fill remaining length
	var padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
	if (padsNeeded < 1)	{
		// text is too long for column, so start pulling characters off
		var tmp = currentText;
		do {
			tmp = tmp.substring(0, tmp.length - 2) + ellip;
			if (tmp == ellip) break;
		} while (defaultFont.measureString(tmp.replace(/%%/g, "%")) > desiredLength);
		currentLength = defaultFont.measureString(tmp);
		padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
		currentText = tmp;
	}
	// quick way of generating a repeated string of that number
	if (!leftSwitch || leftSwitch == false) {
		return currentText + new Array(padsNeeded).join(hairSpace);
	} else {
		return new Array(padsNeeded).join(hairSpace) + currentText;
	}
}

//-------------------------------------------------------------------------------------------------------------
// appends space to currentText to the specified length in 'em'
this.$padTextLeft = function(currentText, desiredLength) {
	return this.$padTextRight(currentText, desiredLength, true);
}

//-------------------------------------------------------------------------------------------------------------
// returns an object containing the id, name, tl, government and distance of the closest system whose TechLevel is greater than or equal to the passed value
this.$findClosestTechLevel = function(requiredTL) {
	var planets = SystemInfo.filteredSystems(this, function(other) {
		return (other.techlevel >= requiredTL);
	});

	var dist = 200;
	var selected = -1;
	var checkDist = 0;

	for (var i = 0; i < planets.length; i++) {
		var chkrt = system.info.routeToSystem(planets[i]);
		if (chkrt) {
			checkDist = chkrt.distance;
		} else {
			checkDist = system.info.distanceToSystem(planets[i]);
		}
		if (checkDist < dist) {
			dist = checkDist;
			selected = planets[i].systemID;
		}
	}

	if (selected != -1) {
		var info = System.infoForSystem(galaxyNumber, selected);
		var rt = system.info.routeToSystem(info);
		if (rt) {
			var dist = rt.distance;
		} else {
			var dist = system.info.distanceToSystem(info);
		}
		return {id:selected, techLevel:(info.techlevel + 1), name:info.name, economy:info.economy, government:info.government, distance:dist};
	} else {
		return null;
	}

}

//-------------------------------------------------------------------------------------------------------------
// returns true if a HUD with allowBigGUI is enabled, otherwise false
this.$isBigGuiActive = function() {
	if (oolite.compareVersion("1.83") <= 0) {
		return player.ship.hudAllowsBigGui;
	} else {
		var bigGuiHUD = ["XenonHUD.plist", "coluber_hud_ch01-dock.plist"]; 	// until there is a property we can check, I'll be listing HUD's that have the allow_big_gui property set here
		if (bigGuiHUD.indexOf(player.ship.hud) >= 0) {
			return true;
		} else {
			return false;
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
// returns true if passed value is numeric, otherwise false
this.$isNumeric = function(n) {
	return !isNaN(parseFloat(n)) && isFinite(n);
}