Back to Index Page generated: May 8, 2024, 6:16:03 AM

Expansion Ship Comparison

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 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.3 2.3
Tags
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/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 1610873302

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
- 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.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. This may be related to warnings.

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/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.licence     = "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 = ["GalCop Viper", "GalCop Viper Interceptor"];
this._hideListAll = ["Constrictor"];
this._shipConfig = false;
this._resetData = false;
this._clearShips = false;
this._trueValues = ["yes", "1", 1, "true", true];

// configuration settings for use in Lib_Config
this._compConfig = {Name:this.name, Alias:"Ship Comparison", Display:"Config", Alive:"_compConfig", Notify:"$onChange",
	Bool:{
		B0:{Name:"_resetData", Def:false, Desc:"Reset stored values"},
		B1:{Name:"_clearShips", Def:false, Desc:"Clears ship selections"},
		Info:"0 - Resets all stored equipment space and ship mass values.\n1 - Clears all the currently selected ships"}
};

// 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 = [];

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

//-------------------------------------------------------------------------------------------------------------
this.$onChange = function() {
	if (this._resetData === true) {
		this._equipSpace = [];
		this._resetData = false;
		player.consoleMessage("Stored ship values cleared");
	}
	if (this._clearShips === true) {
		this._selectedList = [];
		this._clearShips = false;
		player.consoleMessage("Selected ships cleared");
	}
}

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

//-------------------------------------------------------------------------------------------------------------
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);
}

//-------------------------------------------------------------------------------------------------------------
// initialise the interface screen in the station
this.$initInterface = function(station) {
	station.setInterface(this.name,{
		title:"Ship comparisons",
		category:"Ship Systems",
		summary:"Displays side-by-side details of selected ships.",
		callback:this.$openComparison.bind(this)
	});
}

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

	this._lastChoice = "";
	this.$buildShipList();
	this._shipList.sort(compare);
	this.$showComparison();
}

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

	var text = "";
	var colShip = 10;
	var colDesc = 10;
	var curChoices = {};
	var sc = null;
	var keyList = [];
	var shipArray = [];
	
	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 = ["Ship type", "", "Price", "Max speed", "Max thrust", "Injector speed", "Pitch/Roll/Yaw", 
		"Weapon positions", "Missile pylons", "Cargo capacity", "Energy banks", "Energy recharge rate", "Hyperspace capable"];
	var outList = ["", "", "", "", "", "", "", "", "", "", "", "", "", ""];

		// select ships to be in comparison (up to 4)
	text = "Select up to 3 ships to compare:\n";

	if (this._shipConfig) {
		textList.push("Equipment space");
		textList.push("Mass");
		outList.push("");
		outList.push("");
		sc = worldScripts.ShipConfiguration_Core;
		// if this HUD doesn't have big gui enabled, take out the blank line normally under the ship type so there's room for the extra fields
		if (player.ship.hudAllowsBigGui === false) {
			textList.splice(1, 1);
			outList.splice(1, 1);
		}
	} else {
		text += "\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 "Ship type":
					outList[ol] += this.$padTextRight(shipName, colShip);
					break;
				case "Price":
					if (shipDef._oo_shipyard) {
						outList[ol] += this.$padTextRight(formatCredits(parseInt(shipDef._oo_shipyard.price), false, true), colShip);
					} else {
						log(this.name, "!!ERROR: _oo_shipyard property not set for ship " + shipName + ", key " + keyList[i]);
					}
					break;
				case "Max speed":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", colShip);
					} else {
						outList[ol] += this.$padTextRight((parseFloat(shipDef["max_flight_speed"]) / 1000).toFixed(3) + " LS", colShip);
					}
					break;
				case "Max thrust":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", colShip);
					} else {
						outList[ol] += this.$padTextRight((parseFloat(shipDef["thrust"]) / 1000).toFixed(3) + " LS", colShip);
					}
					break;
				case "Injector speed":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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) + " LS", colShip);
					}
					break;
				case "Pitch/Roll/Yaw":
					if (this._hideListAll.indexOf(shipName) >= 0 || this._hideListBasic.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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 "Weapon positions":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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 = "Forward";
						if (facings === "15") {
							wpn = "All";
						} else {
							if (" 3 7 11 ".indexOf(" " + facings + " ") >= 0) wpn += ", Aft";
							if (" 5 7 13 ".indexOf(" " + facings + " ") >= 0) wpn += ", Port";
							if (" 9 11 13 ".indexOf(" " + facings + " ") >= 0) wpn += ", Starboard";
						}
						if (facings === "0") wpn = "None";
						if (facings === "N/A") wpn = "N/A";
						outList[ol] += this.$padTextRight(wpn, colShip);
					}
					break;
				case "Missile pylons":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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 "Cargo capacity":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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 "Energy banks":
					if (this._hideListAll.indexOf(shipName) >= 0 || this._hideListBasic.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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 "Energy recharge rate":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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 "Hyperspace capable":
					var hyper = "Yes";
					if (shipDef["hyperspace_motor"] != undefined && (shipDef["hyperspace_motor"] === "no" || shipDef["hyperspace_motor"] === "false" || shipDef["hyperspace_motor"] === "0")) hyper = "No";
					outList[ol] += this.$padTextRight(hyper, colShip);
					break;
				case "Equipment space":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", 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 != "N/A" && 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]);
							var ms = 0;
							outList[ol] += this.$padTextRight(es.equipSpace + (es.cargoSpace != "N/A" && 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 : "N/A")});
						}
					}
					break;
				case "Mass":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("N/A", colShip);
					} else {
						var found = false;
						var idx = -1;
						// 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) {
								idx = j;
								if (this._equipSpace[j].hasOwnProperty("mass") && this._equipSpace[j].mass != "N/A") {
									outList[ol] += this.$padTextRight(this._equipSpace[j].mass.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("N/A", 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" || idx === -1) {
								this._equipSpace.push({shipType:shipName, equipSpace:es.equipSpace, cargoSpace:es.cargoSpace, shipMass:(es.hasOwnProperty("mass") ? es.mass : "N/A")});
							} else {
								this._equipSpace[idx].mass = 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:"Change first ship to '" + this._shipList[0].name + "'", color:this._menuColor};
			curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[0].name + "'", unselectable:true, color:this._disabledColor};
			curChoices["03_CHANGE"] = {text:"Change third ship to '" + this._shipList[0].name + "'", unselectable:true, color:this._disabledColor};
		} else {
			curChoices["01_CHANGE"] = {text:"Change first ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
			curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[this._shipList.length - 1].name + "'", unselectable:true, color:this._disabledColor};
			curChoices["03_CHANGE"] = {text:"Change third ship to '" + 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:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[j + 1].name + "'", color:this._menuColor};
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = {text:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[0].name + "'", color:this._menuColor};
						}
					} else {
						if (j > 0) {
							curChoices["0" + (i + 1) + "_CHANGE"] = {text:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[j - 1].name + "'", color:this._menuColor};
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = {text:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
						}

					}
				}
			}
		}
		if (this._selectedList.length === 1) {
			if (this._direction === 1) {
				curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[0].name + "'", color:this._menuColor};
				curChoices["03_CHANGE"] = {text:"Change third ship to '" + this._shipList[0].name + "'", unselectable:true, color:this._disabledColor};
			} else {
				curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
				curChoices["03_CHANGE"] = {text:"Change third ship to '" + 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:"Change third ship to '" + this._shipList[0].name + "'", color:this._menuColor};
			} else {
				curChoices["03_CHANGE"] = {text:"Change third ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
			}
		}
	}
	curChoices["05_DIRECTION"] = {text:(this._direction === 1 ? "Change scroll direction to ascending" : "Change scroll direction to descending"), color:this._menuColor};
	curChoices["06_EQUIPMENT"] = {text:"View standard equipment availability", color:this._itemColor};
	curChoices["99_EXIT"] = {text:"Return", color:this._itemColor};
	var def = "99_EXIT";

	var opts = {
		screenID: "oolite-ship-compare-map",
		title: "Ship Comparison",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		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 = [];

	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 = ["Ship type", "", "Techlevel", "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"];
	var outList = ["", "", "", "", "", "", "", "", "", "", "", "", "", ""];

		// select ships to be in comparison (up to 4)
	text = "Select up to 3 ships to compare:\n";

	if (this._shipConfig) {
		// if this HUD doesn't have big gui enabled, take out the blank line normally under the ship type so there's room for the extra fields
		if (player.ship.hudAllowsBigGui === false) {
			textList.splice(1, 1);
			outList.splice(1, 1);
		}
	} else {
		text += "\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 "Ship type":
					outList[ol] += this.$padTextRight(shipName, colShip);
					break;
				case "Techlevel":
					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, "!!ERROR: _oo_shipyard property not set for ship " + shipName + ", key " + keyList[i]);
						}
					}
					break;
				case "Forward weapon type":
					if (this._hideListAll.indexOf(shipName) >= 0) {
						outList[ol] += this.$padTextRight("?", colShip);
					} else {
						outList[ol] += this.$padTextRight(this.$forwardWeaponType(shipDef), colShip);
					}
					break;
				case "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 "Fuel scoops":
					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 "Passenger berths":
					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 "Energy unit":
					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 "Naval energy unit":
					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 "Docking computer":
					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 "Shield 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 "Military shield enhancement":
					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 "Galactic hyperdrive":
					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 "Scanner targeting enhancement":
					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 "Multi-targeting system":
					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:"Change first ship to '" + this._shipList[0].name + "'", color:this._menuColor};
			curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[0].name + "'", unselectable:true, color:this._disabledColor};
			curChoices["03_CHANGE"] = {text:"Change third ship to '" + this._shipList[0].name + "'", unselectable:true, color:this._disabledColor};
		} else {
			curChoices["01_CHANGE"] = {text:"Change first ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
			curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[this._shipList.length - 1].name + "'", unselectable:true, color:this._disabledColor};
			curChoices["03_CHANGE"] = {text:"Change third ship to '" + 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:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[j + 1].name + "'", color:this._menuColor};
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = {text:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[0].name + "'", color:this._menuColor};
						}
					} else {
						if (j > 0) {
							curChoices["0" + (i + 1) + "_CHANGE"] = {text:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[j - 1].name + "'", color:this._menuColor};
						} else {
							curChoices["0" + (i + 1) + "_CHANGE"] = {text:"Change " + this.$numberWord(i + 1) + " ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
						}

					}
				}
			}
		}
		if (this._selectedList.length === 1) {
			if (this._direction === 1) {
				curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[0].name + "'", color:this._menuColor};
				curChoices["03_CHANGE"] = {text:"Change third ship to '" + this._shipList[0].name + "'", unselectable:true, color:this._disabledColor};
			} else {
				curChoices["02_CHANGE"] = {text:"Change second ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
				curChoices["03_CHANGE"] = {text:"Change third ship to '" + 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:"Change third ship to '" + this._shipList[0].name + "'", color:this._menuColor};
			} else {
				curChoices["03_CHANGE"] = {text:"Change third ship to '" + this._shipList[this._shipList.length - 1].name + "'", color:this._menuColor};
			}
		}
	}
	curChoices["05_DIRECTION"] = {text:(this._direction === 1 ? "Change scroll direction to ascending" : "Change scroll direction to descending"), color:this._menuColor};
	curChoices["06_SHIPSTATS"] = {text:"View general ship statistics", color:this._itemColor};
	curChoices["99_EXIT"] = {text:"Return", color:this._itemColor};
	var def = "99_EXIT";

	var opts = {
		screenID: "oolite-ship-compare-map",
		title: "Ship Comparison",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		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 "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 "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.$buildShipList = function() {
	var truevalues = this._trueValues;
	var keys = Ship.keysForRole("player");
	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 "First";
		case 2: return "Second";
		case 3: return "Third";
		case 4: return "Fourth"; // in case we ever add a fourth column!
	}
}

//-------------------------------------------------------------------------------------------------------------
// 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);
	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) > 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);
}

//-------------------------------------------------------------------------------------------------------------
this.$shipDefHasOption = function(def, eqKey) {
	var found = "N/A";
	if (!def._oo_shipyard) return "Data error";
	if (def._oo_shipyard.standard_equipment.extras && def._oo_shipyard.standard_equipment.extras.indexOf(eqKey) >= 0) found = "Standard";
	if (found === "N/A" && def._oo_shipyard.optional_equipment && def._oo_shipyard.optional_equipment.indexOf(eqKey) >= 0) found = "Optional";
	return found;
}

//-------------------------------------------------------------------------------------------------------------
this.$forwardWeaponType = function(def) {
	var found = "None";
	if (!def._oo_shipyard) return "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 = "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;
}