Back to Index Page generated: Jun 25, 2025, 7:13:19 AM

Expansion Ship Comparison

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Displays side-by-side comparison of ships, helping players making decisions about new ship purchases. Displays side-by-side comparison of ships, helping players making decisions about new ship purchases.
Identifier oolite.oxp.phkb.ShipComparison oolite.oxp.phkb.ShipComparison
Title Ship Comparison Ship Comparison
Category Ambience Ambience
Author phkb phkb
Version 2.7 2.7
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL https://wiki.alioth.net/index.php/Ship_Comparison n/a
Download URL https://wiki.alioth.net/img_auth.php/5/58/ShipComparison.oxz n/a
License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
File Size n/a
Upload date 1749450298

Documentation

Also read http://wiki.alioth.net/index.php/Ship%20Comparison

readme.txt

Ship Comparison
By Nick Rogers

Overview
========
This OXP aims to help players who are in the market for a new ship by providing a visual, side-by-side comparison of ship specifications.

A new interface screen "Ship Comparisons" allows the details of up to three ships to be displayed. Details included are:
- Price
- Max speed
- Injector speed
- Thrust/pitch/yaw
- Weapon positions
- Missile pylons
- Cargo capacity (plus expansion size, if available)
- Energy banks
- Energy recharge rate
- Hyperspace capable
- Mass
- Equipment space (plus amount of cargo that can be converted to equipment space) - Only if the "Ship Configuration" OXP is installed. Note: values are without an extended cargo bay installed, if application to that ship.

A menu option, "View standard equipment availability", shows a list of all the standard equipment items, and whether each one is (a) fitted as standard, (b) is available for optional installation, or (c) not available.
The standard equipment items shown are:
- Forward weapon type
- ECM
- Fuel scoops
- Passenger berths
- Energy unit
- Naval energy unit
- Docking computer
- Shield booster
- Military shield enhancement
- Galactic hyperdrive
- Scanner targeting enhancement
- Multi-targeting system

Overriding Data
===============
If a particular ship needs to have it's own entry, even if it would normally be listed under another ship type, you can override the list entry for that data key.
Put this into the startUpComplete routine of your OXP:

	var sc = worldScripts.ShipComparison;
	if (sc) {
		// add an override for a particular ship data key, giving it a new name
		sc._override["noshaders_z_groovy_cobra1_baker_player"] = "Cobra Mark I Special";
		
		// optionally, if you need to override the extra cargo capacity or the weapon facings, use the following
		sc._extraData["Cobra Mark I Special"] = [5, 15];
		// data element 1 is the additional cargo available for this ship type (5 in the example)
		// data element 2 is the weapon facings on this ship type (1 = forward only, 3 = forward and aft, 15 = all) (15 = All in the example)
	}
	
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/

Balance image from http://simpleicon.com/balance.html

Version History
===============
2.7
- Moved all text into descriptions.plist for easier localisation.
- HUDs that aren't Big GUI enabled weren't being hidden on the equipment comparison screen.
- More bug fixes with ship mass display.
- Removed misleading "N/A" on equipment items not mentioned in standard or optional equipment.
- Added Aft Laser to equipment comparison.
- Added Turrets to ship general stats comparison.
- Forward and Aft laser info on equipment comparison now includes "N/A" for ships that do not have those weapon facings.

2.6
- Bug fix for ship mass calculation without Ship Configuration.

2.5
- Copied "Mass" calculations from Ship Configuration so it can be display without Ship Configuration.

2.4
- Added shortcut key to F3F3 Shipyard screen to jump directly into the comparison tool. Also added text to Contextual Help.
- Added option to set one of the comparison ships to your current ship for faster comparisons.
- Added option to limit the list of ships to only those available in the current shipyard.

2.3
- Fixed issue where the equipment space array was continually growing with duplicate entries.

2.2
- Further improvements to handling of situation where _oo_shipyard property is not defined for a particular player ship.

2.1
- Better handling of situations where the _oo_shipyard property is not defined for a particular player ship.

2.0
- Added ship price to the list of information shown.
- Put pitch, roll and yaw onto 1 line.
- Added new menu option, "View standard equipment availability", which shows which standard equipment items are fitted as standard, available for optional installation, or not available at all.
- Removed requirement to store a value in missionVariables.
- Added option to clear ship list to Library interface.

1.9
- Worked out how to get weapon facings and large cargo bay values without needing to create ship objects to test. Should work with all ships, but I have left the override array in place in case exceptions arise in the future.

1.8
- Added Cobra Mark IV to list of exceptions.

1.7
- Changed "==" comparisons to "===" for performance improvements.
- Added ship mass to display (only if "Ship Configuration" OXP v0.3.1 is installed)
- Moved the F4 interface to "Ship Systems", putting it near "Ship Registrations".

1.6
- Fixed Javascript error when attempting to check for script_info objects on ships that don't have any script_info defined.

1.5
- Added ability to hide ships via script info.

1.4
- Added Equipment space from "Ship Configuration" OXP v0.1.0 or later. Only available if that OXP is installed.
- Additional ship data overrides.

1.3
- Added a "+" symbol to extra cargo value to make it clearer it is in addition to the base cargo capacity.
- Additional ship data overrides.
- Added dictionary and code examples to allow OXP's to override ship data for a particular data key.

1.2
- Added cargo expansion size to list of items.
- Fixed issue where missing "weapon_facings" value was not defaulting to the correct value. If any ships has been missed or show up incorrectly just let me know.
- Added a "change scroll direction" function so if you go past the ship you want to can turn around and go back, rather than having to scroll all the way around the list.

1.1
- Fixed issue where some ship definitions were incorrectly showing with "0" missiles.
- Fixed issue where some ship definitions were showing a blank in the cargo capacity field.
- Added code to hide specifications of some ships (classified data and such).

1.0
- Initial release.

Equipment

This expansion declares no equipment.

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
Scripts/shipcomparison.js
"use strict";
this.name = "ShipComparison";
this.author = "phkb";
this.copyright = "2017 phkb";
this.description = "Routines for selecting ships and displaying details of them side-by-side.";
this.license = "CC BY-NC-SA 4.0";

this._lastChoice = "";
this._shipList = [];
this._selectedList = [];
this._direction = 1;
this._itemColor = "yellowColor";
this._menuColor = "orangeColor";
this._exitColor = "yellowColor";
this._disabledColor = "darkGrayColor";
this._hideListBasic = expandDescription("[shipcomp_hidden_ships]").split("|");
this._hideListAll = ["Constrictor"];
this._shipConfig = false;
this._resetData = false;
this._clearShips = false;
this._trueValues = ["yes", "1", 1, "true", true];
this._exitScreen = "GUI_SCREEN_INTERFACES";
this._limitList = false;
this._switch = false;

this._shipMassExceptions = {
	"Cobra Mark III": 214737.6875,
};

// configuration settings for use in Lib_Config
this._compConfig = {
	Name: this.name,
	Alias: expandDescription("[shipcomp_config_alias]"),
	Display: expandDescription("[shipcomp_config_display]"),
	Alive: "_compConfig",
	Notify: "$onChange",
	Bool: {
		B0: { Name: "_resetData", Def: false, Desc: expandDescription("[shipcomp_reset_data]") },
		B1: { Name: "_clearShips", Def: false, Desc: expandDescription("[shipcomp_clear_selections]") },
		Info: expandDescription("[shipcomp_config_bool_info]")
	}
};

// set with this._override[data key] = "New ship name";
this._override = {};
// this is a holding array for equipment space data for ships, if ShipConfig is installed
this._equipSpace = [];
this._mass = {};

// element 1: extra cargo
// element 2: weapon facings
this._extraData = {
};

//-------------------------------------------------------------------------------------------------------------
this.$onChange = function () {
	if (this._resetData === true) {
		this._equipSpace = [];
		this._resetData = false;
		player.consoleMessage(expandDescription("[shipcomp_reset]"));
	}
	if (this._clearShips === true) {
		this._selectedList = [];
		this._clearShips = false;
		player.consoleMessage(expandDescription("[shipcomp_clear]"));
	}
}

//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
	// register our settings, if Lib_Config is present
	if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this._compConfig);

	if (worldScripts.ShipConfiguration_Core && worldScripts.ShipConfiguration_Core.$getEquipmentSpaceFromShipKey) this._shipConfig = true
	// set up the interface screen, if required
	this.$initInterface(player.ship.dockedStation);
	// load any equipment space calculations that have been done previously
	if (missionVariables.ShipComparison_EquipSpace) {
		this._equipSpace = JSON.parse(missionVariables.ShipComparison_EquipSpace);
		this.$cleanUpEquipSpace();
	}
	if (missionVariables.ShipComparison_Reset) delete missionVariables.ShipComparison_Reset;

	if (oolite.compareVersion("1.91") <= 0) {
		setExtraGuiScreenKeys(this.name, {
			guiScreen: "GUI_SCREEN_SHIPYARD",
			registerKeys: { "sckey1": [{ key: "c", mod1: true }] },
			callback: this.$directOpen.bind(this)
		});

		if (worldScripts.ContextualHelp) {
			worldScripts.ContextualHelp.$addHelpTextToGuiScreen(this.name, "GUI_SCREEN_SHIPYARD", expandDescription("[shipcomp_help_keys]"));
		}

	}
}

//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function () {
	if (this._equipSpace.length > 0) {
		// if we've gone to the trouble of calculating equipment space, we'll retrieve the results so that we keep the speed of lookup values
		missionVariables.ShipComparison_EquipSpace = JSON.stringify(this._equipSpace);
	} else {
		delete missionVariables.ShipComparison_EquipSpace;
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipDockedWithStation = function (station) {
	// set up interface screen
	this.$initInterface(station);
}

//-------------------------------------------------------------------------------------------------------------
this.missionScreenEnded = function () {
	if (player.ship.hudHidden == true) player.ship.hudHidden = false;
}

//-------------------------------------------------------------------------------------------------------------
// initialise the interface screen in the station
this.$initInterface = function (station) {
	station.setInterface(this.name, {
		title: expandDescription("[shipcomp_interface_title]"),
		category: expandDescription("[interfaces-category-ship-systems]"),
		summary: expandDescription("[shipcomp_interface_summary]"),
		callback: this.$openComparison.bind(this)
	});
}

//-------------------------------------------------------------------------------------------------------------
this.$openComparison = function () {
	function compare(a, b) {
		return a.name > b.name;
	}

	this._exitScreen = "GUI_SCREEN_INTERFACES";
	this._lastChoice = "";
	this.$buildShipList();
	this._shipList.sort(compare);
	if (this._switch == true) {
		this._switch = false;
		this.$showComparisonEquipment();
		return;
	}
	this.$showComparison();
}

//-------------------------------------------------------------------------------------------------------------
this.$directOpen = function () {
	function compare(a, b) {
		return a.name > b.name;
	}

	this._exitScreen = "GUI_SCREEN_SHIPYARD";
	this._lastChoice = "";
	this.$buildShipList();
	this._shipList.sort(compare);
	if (this._switch == true) {
		this._switch = false;
		this.$showComparisonEquipment();
		return;
	}
	this.$showComparison();
}

//-------------------------------------------------------------------------------------------------------------
this.$showComparison = function () {

	var text = "";
	var colShip = 10;
	var colDesc = 10;
	var curChoices = {};
	var sc = null;
	var keyList = [];
	var shipArray = [];

	if (this.$isBigGuiActive() === false) player.ship.hudHidden = true;

	for (var i = 0; i < this._selectedList.length; i++) {
		if (this._selectedList[i] != "") {
			var shipDef = Ship.shipDataForKey(this._selectedList[i]);
			shipArray.push(shipDef);
			keyList.push(this._selectedList[i]);
		}
	}
	colShip = Math.floor(22 / shipArray.length);

	var textList = expandDescription("[shipcomp_properties]").split("|");
	var outList = [];
	for (var i = 0; i < textList.length; i++) outList.push("");

	// select ships to be in comparison (up to 4)
	text = expandDescription("[shipcomp_select]") + ":\n";

	if (this._shipConfig) {
		textList.push(expandDescription("[shipcomp_item_equipspace]"));
		outList.push("");
		sc = worldScripts.ShipConfiguration_Core;
	} else {
		text += "\n";
	}
	var na = expandDescription("[shipcomp_na]")

	for (var ol = 0; ol < textList.length; ol++) {
		outList[ol] = this.$padTextRight(textList[ol] + (textList[ol] != "" ? ":" : ""), colDesc);
		for (var i = 0; i < shipArray.length; i++) {
			var shipDef = shipArray[i];
			var shipName = shipDef.name;
			if (this._override[this._selectedList[i]]) shipName = this._override[this._selectedList[i]];

			switch (textList[ol]) {
				case expandDescription("[shipcomp_item_shiptype]"):
					outList[ol] += this.$padTextRight(shipName, colShip);
					break;
				case expandDescription("[shipcomp_item_price]"):
					if (shipDef._oo_shipyard) {
						outList[ol] += this.$padTextRight(formatCredits(parseInt(shipDef._oo_shipyard.price), false, true), colShip);
					} else {
						log(this.name, expandDescription("[shipcomp_shipyard_error]", { name: shipName, key: keyList[i] }));
					}
					break;
				case expandDescription("[shipcomp_item_speed]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						outList[ol] += this.$padTextRight((parseFloat(shipDef["max_flight_speed"]) / 1000).toFixed(3) + " " + expandDescription("[shipcomp_ls]"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_thrust]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						outList[ol] += this.$padTextRight((parseFloat(shipDef["thrust"]) / 1000).toFixed(3) + " " + expandDescription("[shipcomp_ls]"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_injectorspeed]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var inj = 7;
						if (0 >= oolite.compareVersion("1.81") && shipDef["injector_speed_factor"] != undefined) inj = parseFloat(shipDef["injector_speed_factor"]);
						outList[ol] += this.$padTextRight(((parseFloat(shipDef["max_flight_speed"]) * inj) / 1000).toFixed(3) + " " + expandDescription("[shipcomp_ls]"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_pitchrollyaw]"):
					if (this._hideListAll.indexOf(shipName) >= 0 || this._hideListBasic.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var yaw = parseFloat(shipDef["max_flight_pitch"]);
						if (shipDef["max_flight_yaw"] != undefined) yaw = parseFloat(shipDef["max_flight_yaw"]);
						outList[ol] += this.$padTextRight(parseFloat(shipDef["max_flight_pitch"]).toFixed(1)
							+ "/" + parseFloat(shipDef["max_flight_roll"]).toFixed(1)
							+ "/" + yaw.toFixed(1), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_weaponfacings]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var facings = shipDef["weapon_facings"];
						if (shipDef._oo_shipyard && shipDef._oo_shipyard.weapon_facings != undefined) facings = shipDef._oo_shipyard.weapon_facings;
						if (this._extraData[shipName] != null) facings = this._extraData[shipName][1].toString();
						var wpn = expandDescription("[shipcomp_forward]");
						if (facings === "15") {
							wpn = expandDescription("[shipcomp_all]");
						} else {
							if (" 3 7 11 ".indexOf(" " + facings + " ") >= 0) wpn += ", " + expandDescription("[shipcomp_aft]");
							if (" 5 7 13 ".indexOf(" " + facings + " ") >= 0) wpn += ", " + expandDescription("[shipcomp_port]");
							if (" 9 11 13 ".indexOf(" " + facings + " ") >= 0) wpn += ", " + expandDescription("[shipcomp_starboard]");
						}
						if (facings === "0") wpn = expandDescription("[shipcomp_none]");
						if (facings === na) wpn = na;
						outList[ol] += this.$padTextRight(wpn, colShip);
					}
					break;
				case expandDescription("[shipcomp_item_turrets]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var turrets = 0;
						var sub = shipDef["subentities"];
						if (sub && sub.length > 0) {
							for (var j = 0; j < sub.length; j++) {
								if (sub[j].type == "ball_turret") turrets += 1;
							}
						}
						if (turrets > 0) {
							outList[ol] += this.$padTextRight(turrets, colShip);
						} else {
							outList[ol] += this.$padTextRight("", colShip);
						}
					}
					break;
				case expandDescription("[shipcomp_item_maxmissiles]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var miss = 0;
						if (shipDef["max_missiles"] != undefined) {
							miss = parseInt(shipDef["max_missiles"]);
						} else {
							miss = parseInt(shipDef["missiles"]);
						}
						outList[ol] += this.$padTextRight(miss, colShip);
					}
					break;
				case expandDescription("[shipcomp_item_maxcargo]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var cargo = 0;
						if (shipDef["max_cargo"] != undefined) cargo = parseInt(shipDef["max_cargo"]);

						var exp = "";
						if (shipDef["extra_cargo"] != undefined) {
							exp = shipDef["extra_cargo"];
						}
						if (exp === "") {
							if (shipDef._oo_shipyard) {
								if ((shipDef._oo_shipyard.standard_equipment.extras && shipDef._oo_shipyard.standard_equipment.extras.indexOf("EQ_CARGO_BAY") >= 0) ||
									(shipDef._oo_shipyard.optional_equipment && shipDef._oo_shipyard.optional_equipment.indexOf("EQ_CARGO_BAY") >= 0)) exp = 15;
							}
						}
						if (this._extraData[shipName] != null) exp = this._extraData[shipName][0].toString();

						if (exp === undefined || exp === "0") exp = "";
						outList[ol] += this.$padTextRight(cargo.toString() + (exp != "" ? " (+" + exp + ")" : ""), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_maxenergy]"):
					if (this._hideListAll.indexOf(shipName) >= 0 || this._hideListBasic.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var en = 200;
						if (shipDef["max_energy"] != undefined) en = parseInt(shipDef["max_energy"]);
						if (Math.floor(en / 64) === 0) en = 64;
						outList[ol] += this.$padTextRight(Math.floor(en / 64), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_rechargerate]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var rech = 1;
						if (shipDef["energy_recharge_rate"] != undefined) rech = parseFloat(shipDef["energy_recharge_rate"]);
						outList[ol] += this.$padTextRight(rech.toFixed(1), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_hyperspace]"):
					var hyper = expandDescription("[shipcomp_yes]");
					if (shipDef["hyperspace_motor"] != undefined && (shipDef["hyperspace_motor"] === "no" || shipDef["hyperspace_motor"] === "false" || shipDef["hyperspace_motor"] === "0")) hyper = expandDescription("[shipcomp_no]");
					outList[ol] += this.$padTextRight(hyper, colShip);
					break;
				case expandDescription("[shipcomp_item_equipspace]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						var found = false;
						// check to see if we have already analysed this ship type
						for (var j = 0; j < this._equipSpace.length; j++) {
							if (this._equipSpace[j].shipType == shipName) {
								outList[ol] += this.$padTextRight(this._equipSpace[j].equipSpace + (this._equipSpace[j].cargoSpace != na && this._equipSpace[j].cargoSpace != 0 ? " (+" + this._equipSpace[j].cargoSpace + ")" : ""), colShip);
								found = true;
								break;
							}
						}
						// if we haven't we will need to execute a query against ShipConfig
						if (found === false) {
							var es = sc.$getEquipmentSpaceFromShipKey(this._selectedList[i]);
							outList[ol] += this.$padTextRight(es.equipSpace + (es.cargoSpace != na && es.cargoSpace != 0 ? " (+" + es.cargoSpace + ")" : ""), colShip);
							// store the result so we don't have to run this process again (but only if there was a definite value, not N/A)
							if (es.equipSpace != "N/A") this._equipSpace.push({ shipType: shipName, equipSpace: es.equipSpace, cargoSpace: es.cargoSpace, shipMass: (es.hasOwnProperty("mass") ? es.mass : na) });
						}
					}
					break;
				case expandDescription("[shipcomp_item_mass]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight(na, colShip);
					} else {
						// check to see if we have already analysed this ship type
						if (!sc) {
							// if ship config is not installed
							var mass = this._mass[shipName];
							if (!mass) {
								mass = this.$getMassFromShipKey(this._selectedList[i]);
								// store the result so we don't have to do this again
								if (mass && mass > 0) this._mass[shipName] = mass;
							}
							if (!mass || mass == 0) {
								outList[ol] += this.$padTextRight(na, colShip);
							} else {
								outList[ol] += this.$padTextRight(mass.toFixed(0), colShip);
							}
						} else {
							var idx = -1
							var found = false;
							// if ship config is installed
							for (var j = 0; j < this._equipSpace.length; j++) {
								if (this._equipSpace[j].shipType === shipName) {
									idx = j;
									if (this._equipSpace[j].hasOwnProperty("shipMass") && this._equipSpace[j].shipMass != "N/A") {
										outList[ol] += this.$padTextRight(this._equipSpace[j].shipMass.toFixed(0), colShip);
										found = true;
									}
									break;
								}
							}
							// if we haven't we will need to execute a query against ShipConfig
							if (found === false) {
								var es = sc.$getEquipmentSpaceFromShipKey(this._selectedList[i]);
								if (es.hasOwnProperty("mass") && es.mass != "N/A") {
									outList[ol] += this.$padTextRight(es.mass.toFixed(0), colShip);
								} else {
									outList[ol] += this.$padTextRight(na, colShip);
								}
								// store the result so we don't have to run this process again (but only if there was a definite value, not N/A)
								if (es.mass != "N/A" || idx === -1) {
									this._equipSpace.push({ shipType: shipName, equipSpace: es.equipSpace, cargoSpace: es.cargoSpace, shipMass: (es.hasOwnProperty("mass") ? es.mass : na) });
								} else {
									this._equipSpace[idx].shipMass = es.mass;
								}
							}
						}
					}
					break;
			}
		}
	}
	
	for (var i = 0; i < outList.length; i++) {
		text += outList[i] + "\n";
	}

	if (this._selectedList.length === 0) {
		if (this._direction === 1) {
			curChoices["01_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(1), ship: this._shipList[0].name }), color: this._menuColor };
			curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(2), ship: this._shipList[0].name }), unselectable: true, color: this._disabledColor };
			curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(3), ship: this._shipList[0].name }), unselectable: true, color: this._disabledColor };
		} else {
			curChoices["01_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(1), ship: this._shipList[this._shipList.length - 1].name }), color: this._menuColor };
			curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(2), ship: this._shipList[this._shipList.length - 1].name }), unselectable: true, color: this._disabledColor };
			curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(3), ship: this._shipList[this._shipList.length - 1].name }), unselectable: true, color: this._disabledColor };
		}
	} else {
		for (var i = 0; i < this._selectedList.length; i++) {
			for (var j = 0; j < this._shipList.length; j++) {
				if (this._shipList[j].key === this._selectedList[i]) {
					if (this._direction === 1) {
						if (j < this._shipList.length - 1) {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(i + 1), ship: this._shipList[j + 1].name }), color: this._menuColor };
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(i + 1), ship: this._shipList[0].name }), color: this._menuColor };
						}
					} else {
						if (j > 0) {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(i + 1), ship: this._shipList[j - 1].name }), color: this._menuColor };
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(i + 1), ship: this._shipList[this._shipList.length - 1].name }), color: this._menuColor };
						}

					}
				}
			}
		}
		if (this._selectedList.length === 1) {
			if (this._direction === 1) {
				curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(2), ship: this._shipList[0].name }), color: this._menuColor };
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(3), ship: this._shipList[0].name }), unselectable: true, color: this._disabledColor };
			} else {
				curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(2), ship: this._shipList[this._shipList.length - 1].name }), color: this._menuColor };
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(3), ship: this._shipList[this._shipList.length - 1].name }), unselectable: true, color: this._disabledColor };
			}
		}
		if (this._selectedList.length === 2) {
			if (this._direction === 1) {
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(3), ship: this._shipList[0].name }), color: this._menuColor };
			} else {
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", { number: this.$numberWord(3), ship: this._shipList[this._shipList.length - 1].name }), color: this._menuColor };
			}
		}
	}
	curChoices["04_CURRENT"] = { text: "[shipcomp_first_current]", color: this._menuColor };
	if (this._limitList == false) {
		curChoices["04A_LIMIT"] = { text: "[shipcomp_shipyard_only]", color: this._menuColor };
	} else {
		curChoices["04B_LIMIT"] = { text: "[shipcomp_all_ships]", color: this._menuColor };
	}
	curChoices["05_DIRECTION"] = { text: (this._direction === 1 ? "[shipcomp_direction_ascending]" : "[shipcomp_direction_descending]"), color: this._menuColor };
	curChoices["06_EQUIPMENT"] = { text: "[shipcomp_view_equipment]", color: this._itemColor };
	curChoices["99_EXIT"] = { text: "[shipcomp_return]", color: this._itemColor };
	var def = "99_EXIT";

	var opts = {
		screenID: "oolite-ship-compare-map",
		title: expandDescription("[shipcomp_config_alias]"),
		allowInterrupt: true,
		exitScreen: this._exitScreen,
		overlay: { name: "compare-balance.png", height: 546 },
		choices: curChoices,
		initialChoicesKey: (this._lastChoice ? this._lastChoice : def),
		message: text
	};

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

//-------------------------------------------------------------------------------------------------------------
this.$showComparisonEquipment = function () {

	var text = "";
	var colShip = 10;
	var colDesc = 10;
	var curChoices = {};
	var sc = null;
	var keyList = [];
	var shipArray = [];

	if (this.$isBigGuiActive() === false) player.ship.hudHidden = true;

	for (var i = 0; i < this._selectedList.length; i++) {
		if (this._selectedList[i] != "") {
			var shipDef = Ship.shipDataForKey(this._selectedList[i]);
			shipArray.push(shipDef);
			keyList.push(this._selectedList[i]);
		}
	}
	colShip = Math.floor(22 / shipArray.length);

	var textList = expandDescription("[shipcomp_equipment]").split("|");
	var outList = [];
	for (var i = 0; i < textList.length; i++) outList.push("");

	// select ships to be in comparison (up to 4)
	text = expandDescription("[shipcomp_select]") + ":\n";

	for (var ol = 0; ol < textList.length; ol++) {
		outList[ol] = this.$padTextRight(textList[ol] + (textList[ol] != "" ? ":" : ""), colDesc);
		for (var i = 0; i < shipArray.length; i++) {
			var shipDef = shipArray[i];
			var shipName = shipDef.name;
			if (this._override[this._selectedList[i]]) shipName = this._override[this._selectedList[i]];

			switch (textList[ol]) {
				case expandDescription("[shipcomp_item_shiptype]"):
					outList[ol] += this.$padTextRight(shipName, colShip);
					break;
				case expandDescription("[shipcomp_item_tl]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						if (shipDef._oo_shipyard) {
							outList[ol] += this.$padTextRight(shipDef._oo_shipyard.techlevel, colShip);
						} else {
							log(this.name, expandDescription("[shipcomp_shipyard_error", { name: shipName, key: keyList[i] }));
						}
					}
					break;
				case expandDescription("[shipcomp_item_forwardweapon]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$forwardWeaponType(shipDef), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_aftweapon]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$aftWeaponType(shipDef), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_ecm]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_ECM"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_fuelscoops]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_FUEL_SCOOPS"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_passengers]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_PASSENGER_BERTH"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_energyunit]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_ENERGY_UNIT"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_navalunit]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_NAVAL_ENERGY_UNIT"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_dockingcomp]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_DOCK_COMP"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_booster]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_SHIELD_BOOSTER"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_milbooster]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_NAVAL_SHIELD_BOOSTER"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_galdrive]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_GAL_DRIVE"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_targetenh]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_SCANNER_SHOW_MISSILE_TARGET"), colShip);
					}
					break;
				case expandDescription("[shipcomp_item_multitarget]"):
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$shipDefHasOption(shipDef, "EQ_MULTI_TARGET"), colShip);
					}
					break;
			}
		}
	}

	for (var i = 0; i < outList.length; i++) {
		text += outList[i] + "\n";
	}

	if (this._selectedList.length === 0) {
		if (this._direction === 1) {
			curChoices["01_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(1), ship:this._shipList[0].name}), color: this._menuColor };
			curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(2), ship:this._shipList[0].name}), unselectable: true, color: this._disabledColor };
			curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(3), ship:this._shipList[0].name}), unselectable: true, color: this._disabledColor };
		} else {
			curChoices["01_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(1), ship:this._shipList[this._shipList.length - 1].name}), color: this._menuColor };
			curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(2), ship:this._shipList[this._shipList.length - 1].name}), unselectable: true, color: this._disabledColor };
			curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(3), ship:this._shipList[this._shipList.length - 1].name}), unselectable: true, color: this._disabledColor };
		}
	} else {
		for (var i = 0; i < this._selectedList.length; i++) {
			for (var j = 0; j < this._shipList.length; j++) {
				if (this._shipList[j].key === this._selectedList[i]) {
					if (this._direction === 1) {
						if (j < this._shipList.length - 1) {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(i + 1), ship:this._shipList[j + 1].name}), color: this._menuColor };
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(i + 1), ship: this._shipList[0].name}), color: this._menuColor };
						}
					} else {
						if (j > 0) {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(i + 1), ship:this._shipList[j - 1].name}), color: this._menuColor };
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(i + 1), ship:this._shipList[this._shipList.length - 1].name }), color: this._menuColor };
						}

					}
				}
			}
		}
		if (this._selectedList.length === 1) {
			if (this._direction === 1) {
				curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(2), ship:this._shipList[0].name }), color: this._menuColor };
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(3), ship:this._shipList[0].name }), unselectable: true, color: this._disabledColor };
			} else {
				curChoices["02_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(2), ship:this._shipList[this._shipList.length - 1].name }), color: this._menuColor };
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(3), ship:this._shipList[this._shipList.length - 1].name }), unselectable: true, color: this._disabledColor };
			}
		}
		if (this._selectedList.length === 2) {
			if (this._direction === 1) {
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(3), ship:this._shipList[0].name }), color: this._menuColor };
			} else {
				curChoices["03_CHANGE"] = { text: expandDescription("[shipcomp_change_ship]", {number:this.$numberWord(3), ship:this._shipList[this._shipList.length - 1].name }), color: this._menuColor };
			}
		}
	}
	curChoices["04_CURRENT"] = { text: "[shipcomp_first_current]", color: this._menuColor };
	if (this._limitList == false) {
		curChoices["04A_LIMIT"] = { text: "[shipcomp_shipyard_only]", color: this._menuColor };
	} else {
		curChoices["04B_LIMIT"] = { text: "[shipcomp_all_ships]", color: this._menuColor };
	}
	curChoices["05_DIRECTION"] = { text: (this._direction === 1 ? "[shipcomp_direction_ascending]" : "[shipcomp_direction_descending]"), color: this._menuColor };
	curChoices["06_SHIPSTATS"] = { text: "[shipcomp_general_stats]", color: this._itemColor };
	curChoices["99_EXIT"] = { text: "[shipcomp_return]", color: this._itemColor };
	var def = "99_EXIT";

	var opts = {
		screenID: "oolite-ship-compare-map",
		title: expandDescription("[shipcomp_config_alias]"),
		allowInterrupt: true,
		exitScreen: this._exitScreen,
		overlay: { name: "compare-balance.png", height: 546 },
		choices: curChoices,
		initialChoicesKey: (this._lastChoice ? this._lastChoice : def),
		message: text
	};

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

//-------------------------------------------------------------------------------------------------------------
this.$screenHandler = function (choice) {
	if (!choice) return;
	this._lastChoice = choice;
	switch (choice) {
		case "01_CHANGE":
			this.$getNewItem(0);
			break;
		case "02_CHANGE":
			this.$getNewItem(1);
			break;
		case "03_CHANGE":
			this.$getNewItem(2);
			break;
		case "04_CURRENT":
			var idx = this.$findCurrentShip();
			if (idx >= 0) {
				this._selectedList[0] = this._shipList[idx].key;
			}
			break;
		case "04A_LIMIT":
			this._limitList = true;
			this._selectedList.length = 0;
			if (this._exitScreen == "GUI_SCREEN_INTERFACES") {
				this.$openComparison();
			} else {
				this.$directOpen();
			}
			return;
		case "04B_LIMIT":
			this._limitList = false;
			this._selectedList.length = 0;
			if (this._exitScreen == "GUI_SCREEN_INTERFACES") {
				this.$openComparison();
			} else {
				this.$directOpen();
			}
			return;
		case "06_EQUIPMENT":
			this._lastChoice = "06_SHIPSTATS";
			this.$showComparisonEquipment();
			return;
		case "05_DIRECTION":
			if (this._direction === 1) {
				this._direction = -1;
			} else {
				this._direction = 1;
			}
			break;
	}
	if (choice != "99_EXIT") this.$showComparison();
}

//-------------------------------------------------------------------------------------------------------------
this.$screenHandlerEq = function (choice) {
	if (!choice) return;

	this._lastChoice = choice;
	switch (choice) {
		case "01_CHANGE":
			this.$getNewItem(0);
			break;
		case "02_CHANGE":
			this.$getNewItem(1);
			break;
		case "03_CHANGE":
			this.$getNewItem(2);
			break;
		case "04_CURRENT":
			var idx = this.$findCurrentShip();
			if (idx >= 0) {
				this._selectedList[0] = this._shipList[idx].key;
			}
			break;
		case "04A_LIMIT":
			this._limitList = true;
			this._selectedList.length = 0;
			this._switch = true;
			if (this._exitScreen == "GUI_SCREEN_INTERFACES") {
				this.$openComparison();
			} else {
				this.$directOpen();
			}
			return;
		case "04B_LIMIT":
			this._limitList = false;
			this._selectedList.length = 0;
			this._switch = true;
			if (this._exitScreen == "GUI_SCREEN_INTERFACES") {
				this.$openComparison();
			} else {
				this.$directOpen();
			}
			return;
		case "06_SHIPSTATS":
			this._lastChoice = "06_EQUIPMENT";
			this.$showComparison();
			return;
		case "05_DIRECTION":
			if (this._direction === 1) {
				this._direction = -1;
			} else {
				this._direction = 1;
			}
			break;
	}
	if (choice != "99_EXIT") this.$showComparisonEquipment();
}

//-------------------------------------------------------------------------------------------------------------
this.$getNewItem = function (idx) {
	var cur = "";
	if (this._selectedList.length >= (idx + 1)) {
		cur = this._selectedList[idx];
	}
	if (cur === "") {
		this._selectedList.push(this._shipList[0].key);
	} else {
		for (var i = 0; i < this._shipList.length; i++) {
			if (this._shipList[i].key === cur) {
				if (this._direction === 1) {
					if (i === this._shipList.length - 1) {
						this._selectedList[idx] = this._shipList[0].key;
					} else {
						this._selectedList[idx] = this._shipList[i + 1].key;
					}
				} else {
					if (i === 0) {
						this._selectedList[idx] = this._shipList[this._shipList.length - 1].key;
					} else {
						this._selectedList[idx] = this._shipList[i - 1].key;
					}
				}
				break;
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$findCurrentShip = function () {
	var p = player.ship;
	for (var i = 0; i < this._shipList.length; i++) {
		if (this._shipList[i].name == p.shipClassName) return i;
	}
	return -1;
}

//-------------------------------------------------------------------------------------------------------------
this.$buildShipList = function () {
	var truevalues = this._trueValues;
	var keys = Ship.keysForRole("player");
	this._shipList.length = 0;

	if (this._limitList) {
		var stn = player.ship.dockedStation;
		// get all ships in the shipyard
		var numShips = stn.shipyard.length;
		for (var i = 0; i < numShips; i++) {
			var shp = stn.shipyard[i];
			this.$addItemToArray(shp.ship.name, shp.shipdata_key);
		}
		// make sure the player ship is added, though
		this.$addItemToArray(player.ship.shipClassName, player.ship.dataKey);
	} else {
		for (var i = 0; i < keys.length; i++) {
			var shp = Ship.shipDataForKey(keys[i]);
			// only add records that have a corresponding shipyard entry
			if (shp._oo_shipyard) {
				if (!shp.script_info || !shp.script_info["sc_ignore"] || truevalues.indexOf(shp.script_info["sc_ignore"]) === -1)
					this.$addItemToArray(shp.name, keys[i]);
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$addItemToArray = function (shipName, shipKey) {
	var ovr = this._override[shipKey];
	if (ovr) shipName = ovr;
	for (var i = 0; i < this._shipList.length; i++) {
		if (this._shipList[i].name === shipName) return;
	}
	this._shipList.push({ name: shipName, key: shipKey });
}

//-------------------------------------------------------------------------------------------------------------
this.$numberWord = function (nbr) {
	switch (nbr) {
		case 1: return expandDescription("[shipcomp_first]");
		case 2: return expandDescription("[shipcomp_second]");
		case 3: return expandDescription("[shipcomp_third]");
		case 4: return expandDescription("[shipcomp_fourth]"); // in case we ever add a fourth column!
	}
}

//-------------------------------------------------------------------------------------------------------------
// appends space to currentText to the specified length in 'em'
this.$padTextRight = function $padTextRight(currentText, desiredLength, leftSwitch) {
	if (currentText == null) currentText = "";
	currentText = currentText.toString();
	var space = " ";
	var hairSpace = String.fromCharCode(31);
	var ellip = "…";
	var currentLength = defaultFont.measureString(currentText.replace(/%%/g, "%"));
	var hairSpaceLength = defaultFont.measureString(hairSpace);
	var spaceLength = defaultFont.measureString(space);
	// calculate number needed to fill remaining length
	var spacesNeeded = parseInt(Math.floor((desiredLength - currentLength) / spaceLength));
	if (spacesNeeded < 0) {
		spacesNeeded = 0;
	} else {
		if (!leftSwitch || leftSwitch === false) {
			currentText += new Array(spacesNeeded).join(space);
		} else {
			currentText = new Array(spacesNeeded).join(space) + currentText;
		}
		currentLength = defaultFont.measureString(currentText.replace(/%%/g, "%"));
	}
	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.replace(/%%/g, "%"));
		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);
}

//-------------------------------------------------------------------------------------------------------------
this.$shipDefHasOption = function (def, eqKey) {
	var found = "";
	if (!def._oo_shipyard) return expandDescription("[shipcomp_data_error]");
	if (def._oo_shipyard.standard_equipment.extras && def._oo_shipyard.standard_equipment.extras.indexOf(eqKey) >= 0) found = expandDescription("[shipcomp_standard]");
	if (found === "" && def._oo_shipyard.optional_equipment && def._oo_shipyard.optional_equipment.indexOf(eqKey) >= 0) found = expandDescription("[shipcomp_optional]");
	return found;
}

//-------------------------------------------------------------------------------------------------------------
this.$forwardWeaponType = function (def) {
	var found = expandDescription("[shipcomp_none]");
	var facings = parseInt(def["weapon_facings"]);
	if (facings == 0) return expandDescription("[shipcomp_na]");
	if (!def._oo_shipyard) return expandDescription("[shipcomp_data_error]");
	if (def._oo_shipyard.standard_equipment.forward_weapon_type && def._oo_shipyard.standard_equipment.forward_weapon_type != "") {
		var eq = EquipmentInfo.infoForKey(def._oo_shipyard.standard_equipment.forward_weapon_type);
		if (eq) {
			found = eq.name;
		} else {
			found = expandDescription("[shipcomp_unknown]");
		}
	}
	return found;
}

//-------------------------------------------------------------------------------------------------------------
this.$aftWeaponType = function (def) {
	var found = expandDescription("[shipcomp_none]");
	var facings = parseInt(def["weapon_facings"]);
	if (facings == 0 || facings == 1) return expandDescription("[shipcomp_na]");
	if (!def._oo_shipyard) return expandDescription("[shipcomp_data_error]");
	if (def._oo_shipyard.standard_equipment.aft_weapon_type && def._oo_shipyard.standard_equipment.aft_weapon_type != "") {
		var eq = EquipmentInfo.infoForKey(def._oo_shipyard.standard_equipment.forward_weapon_type);
		if (eq) {
			found = eq.name;
		} else {
			found = expandDescription("[shipcomp_unknown]");
		}
	}
	return found;
}

//-------------------------------------------------------------------------------------------------------------
this.$cleanUpEquipSpace = function $cleanUpEquipSpace() {
	var newData = [];
	for (var i = 0; i < this._equipSpace.length; i++) {
		var found = false;
		for (var j = 0; j < newData.length; j++) {
			if (newData[j].shipType == this._equipSpace[i].shipType) {
				found = true;
				break;
			}
		}
		if (found === false) {
			newData.push(JSON.parse(JSON.stringify(this._equipSpace[i])));
		}
	}
	this._equipSpace.length = 0;
	this._equipSpace = newData;
}

//-------------------------------------------------------------------------------------------------------------
// external routine to return the mass for a ship key
// we'll create a temporary ship object and use that to get the details
this.$getMassFromShipKey = function $getMassFromShipKey(shipKey) {
	var result = 0;
	// find a spot well away from any possible objects
	if (system.sun) {
		var temppos = system.sun.position.cross(system.mainPlanet.position).direction().multiply(4E9).subtract(system.mainPlanet.position);
	} else {
		var temppos = Vector3D(0, 0, 0);
	}
	// add a ship with the particular key
	var tempship = system.addShips("[" + shipKey + "]", 1, temppos, 0);
	if (tempship != null) {
		// force this ship to be "dead" in space - we don't want it to do anything disruptive!
		tempship[0].switchAI("oolite-nullAI.js");
		result = this.$getShipMass(tempship[0]);
		// clean up our temp ship
		tempship[0].remove(true);
	} else {
		// this could happen if the ship is not allowed to be spawned in this system due to constraints in the shipdata.plist file for the ship
		result = 0;
	}
	return result;
}

//-------------------------------------------------------------------------------------------------------------
// returns the ships mass, either from the exception list, or from the ship itself
this.$getShipMass = function $getShipMass(ship) {
	var mass = this._shipMassExceptions[ship.shipClassName];
	if (!mass) mass = ship.mass;
	return mass;
}

//-------------------------------------------------------------------------------------------------------------
// returns true if a HUD with allowBigGUI is enabled, otherwise false
this.$isBigGuiActive = function $isBigGuiActive() {
	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;
		}
	}
}