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

Expansion Equipment Storage

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Allows ship equipment to be stored at main stations. Allows ship equipment to be stored at main stations.
Identifier oolite.oxp.phkb.EquipmentStorage oolite.oxp.phkb.EquipmentStorage
Title Equipment Storage Equipment Storage
Category Mechanics Mechanics
Author phkb phkb
Version 1.8 1.8
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL https://wiki.alioth.net/index.php/Equipment_Storage_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/d/db/EquipmentStorage_1.8.oxz n/a
License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
File Size n/a
Upload date 1697812298

Documentation

Also read http://wiki.alioth.net/index.php/Equipment%20Storage

readme.txt

Equipment Storage
By Nick Rogers

Overview
========
Sometimes, when you purchase a new ship, you want to include equipment you have already purchased. In the core game there is no way around this. If you sell your ship, you have to repurchase everything. 

The Equipment Storage OXP provides an alternative. Every main station now has a self-storage facility into which your excess equipment can be transferred. An F4 interface screen, "Storage facility management", will be available that will show you all the equipment you have stored at this station or any other station, and allow you to transfer most installed equipment items into storage. Equipment stored in the current station can be either sold or reinstalled. Equipment stored in other stations can be sold or transferred to the current station, after paying a transfer fee, but depending on the length of the trip and the number of dangerous systems the transfer ship must take to reach the current system, you may end up losing the equipment.

Most cargo can also be stored in the facilities, and transferred between them. After selecting "Storage facility management", you will have an option to move cargo from your hold into the facility.

The cost of storage for most equipment items 1cr per equipment item per day. Some equipment items are categorised as software or having a negligible space or weight, and the cost for these items is 0.1cr per item per day.

Cargo storage costs are calculated at 1cr per ton per day.

Note: Storage facilities are only available at the main station in each system. If you dock at any other station the F4 interface screen will instead read "Storage facility information" and will provide details of what equipment and cargo you have in storage at the main station.

Note: Slaves, firearms and narcotics cannot be stored in any facility, by GalCop law. 

Ship Configuration Integration
==============================
When the Ship Configuration OXP is installed, the interface through which equipment items are transferred to storage is moved into the Ship Configuration equipment item on the F3 Equip Ship screen. The F4 interface screen will still be available for viewing stored equipment, and for reinstallation and transfer.

Note: Because Ship Configuration implements an equipment space/weight factor, the costs of storage could increase for some equipment items. The rate is 1cr per ton per day. See the Ship Configuration documentation for more information on the space/weight factor of individual items.

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

Storage icon made by Freepik (http://www.freepik.com) from http://www.flaticon.com is licensed by Creative Commons BY 3.0 (http://creativecommons.org/licenses/by/3.0/)

Version History
===============
1.8
- Made compatible with New Cargoes 2.0.

1.7
- Added extra parameter in call to Hermitage OXP.
- Fixed some potential referencing errors on mission screens.

1.6
- Added missing entry in descriptions.plist.

1.5
- Added Trail Detector item to override list, allowing it to be stored.
- Added Mining IFF Scanner Upgrade item to override list, allowing it to be stored.
- Added Rescue Stations equipment items to override list, allowing them to be stored.
- Fixed issue with Ship Configuration items (Fuel Scoops, Heat Shields and Fuel Injectors) that were not being assessed correctly in determining when an item was in use.
- Bug fixes.

1.4
- Added IronHide armour to the exclusion list.

1.3
- Added ShipVersion's placeholder equipment items to the list of items that can't be stored.
- Code refactoring.
- Bug fixes.

1.2
- Added overlay to transfer cargo screen.
- Cleaned up some instances where the core commodity name (eg "gem_stones") was being displayed, instead of the display name (eg "gem-stones").
- Added interfaces to another OXP in development.

1.1
- Corrected OXP name in manifest file.

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/equipstorage.js
"use strict";
this.name = "EquipmentStorage";
this.author = "phkb";
this.copyright = "2019 phkb";
this.description = "Handles the process of storing equipment items for reuse later";
this.licence = "CC BY-NC-SA 4.0";

// TODO:	Allow storage of missiles

this._noStore = ["EQ_BREAKABLE_TORUSDRIVE", "EQ_BREAKABLE_HUD_IFF_SCANNER", "EQ_IRONHIDE"];
this._allowedIgnored = ["EQ_SCANNER_SHOW_MISSILE_TARGET", "EQ_MULTI_TARGET", "EQ_ADVANCED_NAVIGATIONAL_ARRAY", "EQ_INTEGRATED_TARGETING_SYSTEM",
	"EQ_TARGET_MEMORY", "EQ_SMUGGLING_PHASE_ADJUSTMENT", "EQ_SDC_HACK_CHIP", "EQ_IG_INSURANCE", "EQ_IG_LEGAL_HACK", "EQ_LMSS_ACTUATOR",
	"EQ_ESCORTCONTRACTS", "EQ_TEA_MAKER", "EQ_TEAPOT", "EQ_COMMSLOGMFD", "EQ_COMMSLOGMFD_PASSIVE", "EQ_BROADCASTCOMMSMFD", "EQ_DAMAGE_REPORT_MFD",
	"EQ_DAMAGE_REPORT_MFD_PASSIVE", "EQ_COMBATMFD", "EQ_NAVIGATION_MFD", "EQ_APRIL_INVENTORY", "EQ_APRIL_TRANSFER", "EQ_APRIL_UNLOAD",
	"EQ_AMS_1", "EQ_AMS_2", "EQ_AMS_3", "EQ_AMS_4", "EQ_GLARE_FILTER", "EQ_AMS_5", "EQ_AMS_6", "EQ_AUTO_EJECT", "EQ_BOUNTY_INFORMER", "EQ_CARGOSPOTTER",
	"EQ_ELITE_RANK", "EQ_RESERVE_TANK_EMPTY", "EQ_AUX_TANK_EMPTY", "EQ_WEAPON_GATLING_LASER_COOLER", "EQ_HUDSELECTOR", "EQ_MARKET_INQUIRER_MFD",
	"EQ_TROPHY_MFD", "EQ_UPS_IDCLEANER", "EQ_MULTIPASS", "EQ_MULTIPASSED_ID", "EQ_HARDWIRE_ORE_PROCESSOR", "EQ_TARGETSELECTOR",
	"EQ_TARGETSELECTOR_MODECHANGER", "EQ_SHIPS_CAT_1", "EQ_SHIPS_CAT_2", "EQ_SHIPS_CAT_3", "EQ_SHIPS_CAT_4", "EQ_SHIPS_CAT_5", "EQ_SHIPS_CAT_6",
	"EQ_SHIPS_CAT_7", "EQ_SHIPS_CAT_8", "EQ_SHIPS_CAT_9", "EQ_CTE_TRADERNET", "EQ_CAMERA_DRONES", "EQ_WPH_ASC_UPGRADE", "EQ_POLICE_SCANNER_UPGRADE",
	"EQ_Q-CHARGER_MESS", "EQ_REPAIRBOTS_RECHARGE", "EQ_SAFETYCATCH", "EQ_SAFETY_MINE", "EQ_SCANNER_ALERTING_ENHANCEMENT", "EQ_HYPER_RADIO",
	"EQ_HYPER_RADIO_ADD", "EQ_HYPER_RADIO_PIRATE", "EQ_ILS", "EQ_WEAPON_LASER_COOLER", "EQ_MANIFEST_MFD", "EQ_SC_MANUAL_CONFIGURATOR_BASIC",
	"EQ_SC_MANUAL_CONFIGURATOR_STANDARD", "EQ_SC_MANUAL_CONFIGURATOR_ADVANCED", "EQ_TARGET_AUTOLOCK", "EQ_ENEMY_TARGETER_UPGRADE",
	"EQ_TELESCOPEEXT", "EQ_TORUSTOSUN", "EQ_TRACKER", "EQ_TRACKERCAM_LINK", "EQ_TWS_GALACTICSATNAV", "EQ_WELCOME_MAT", "EQ_GAL_DRIVE_MOD",
	"EQ_SALON_CONTROL", "EQ_SNIPER_SCOPE", "EQ_SYSTEM_MARKET_WATCH", "EQ_AUTOECM", "EQ_GALAXY_INFO", "EQ_LONG_RANGE_SCANNER_JUMP",
	"EQ_RETICLE_TARGET_SENSITIVE", "EQ_MANIF_SCANNERX", "EQ_MC_DESCRAMBLER", "EQ_MURPH_THARGOID_DRIVE_TOFIT", "EQ_ENEMY_TARGETER_UPGRADE",
	"EQ_SNIPERLOCK", "EQ_MFD_FAST_CONFIG", "EQ_GCM_COMMS_RELAY_SWITCH", "EQ_GCM_CARGO_STOPPER", "EQ_GCM_EJECTION_DAMPER", "EQ_GCM_UNPROCESSED_SCAN_DATA",
	"EQ_GCM_SOFTWARE", "EQ_GCM_STOLEN_SCHEMATICS", "EQ_GCM_STOLEN_CODES", "EQ_GCM_STOLEN_DOCUMENTS", "EQ_GCM_STOLEN_WEAPONDESIGNS",
	"EQ_RRS_FLIGHTFORM", "EQ_RRS_ESCORT_TARGETING_SYSTEM", "EQ_RRS_ESCORT_TARGETING_SYSTEM_WAYSTATION", 
	"EQ_NS_MINING_SCANNER_UPGRADE", "EQ_NS_SCANNER_SCAVENGER_HACK", "EQ_TRAIL_DETECTOR"
];

this._storage = []; // main array holding storage information
this._sc_core = null; // link to shipconfig core functions
this._compNames = ["KennOords Storage", "BulkStor", "RentaSpace", "Storage King", "HOowards Storage World",
	"%H Self Storage", "Galactic Storage", "StoreIt Self Storage", "Systemwide Storage", "Safe-n-Sound",
	"Premier Storage Solutions", "TopShelf Storage", "HandiStor", "SpaceWays Self Storage", "B&B Stor-More", "OO-Haul Storage"
];
this._salesRep = ""; // holds a string containing the name of the local sales rep
this._page = 0; // controls which page of information will be displayed
this._lastChargeDate = 0; // date player was last charged for storage
this._display = 0; // controls which display will be shown on the interface screen
this._displayType = 0; // 0 = current location only, 1 = all locations
this._selectedItem = ""; // currently selected item
this._lastChoice = ["", "", "", "", "", "", "", "", ""]; // players last choice on each of the displays
this._lastChoiceStore = "";
this._lastSource = -1; // used to establish the start point for destination system calculations
this._commodities = []; // stores a list of installed commodity keys
this._storedPrices = []; // stores a copy of any sample prices so the player always gets the same answer
this._establishmentFee = 100;
this._disabled = false;
this._menuColor = "orangeColor"; // color of general menu items
this._itemColor = "yellowColor"; // color of special menu items (eg exit menu items)
this._damagedColor = "redColor"; // color of damaged equipment items
this._installedColor = "greenColor"; // color of installed equipment items
this._riskColor = "0.8 0.8 0.8 1"; // color of risky transit items
this._refundPct = 0.85; // value between 0 and 1 that indicates how much refund you get on equipment sold. 0 = no refund, 1 = refund full original price
this._NCInstalled = false;

//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
	if (worldScripts.ShipConfiguration_Core) this._sc_core = worldScripts.ShipConfiguration_Core;

	if (this._disabled) {
		delete this.playerWillSaveGame;
		delete this.shipWillDockWithStation;
		delete this.playerEnteredNewGalaxy;
		delete this.dayChanged;
		delete this.playerBoughtEquipment;
		delete this.playerBoughtNewShip;
		delete this.reportScreenEnded;
		delete this.missionScreenEnded;
		delete this.startUpComplete;
		return;
	}

	// add a mission screen exception to Xenon UI
	if (worldScripts.XenonUI) {
		var wx = worldScripts.XenonUI;
		wx.$addMissionScreenException("oolite-storage-shortrangechart-map");
		wx.$addMissionScreenException("oolite-storage-longrangechart-map");
	}
	// add a mission screen exception to Xenon Redux UI
	if (worldScripts.XenonReduxUI) {
		var wxr = worldScripts.XenonReduxUI;
		wxr.$addMissionScreenException("oolite-storage-shortrangechart-map");
		wxr.$addMissionScreenException("oolite-storage-longrangechart-map");
	}
	if (worldScripts.DisplayCurrentCourse) {
		var dcc = worldScripts.DisplayCurrentCourse;
		dcc._screenIDList.push("oolite-storage-shortrangechart-map");
		dcc._screenIDList.push("oolite-storage-longrangechart-map");
	}

	if (worldScripts.CargoTypeExtension) this._NCInstalled = true;

	// pull in the values from ShipConfig vars, if loading for the first time after the split
	if (missionVariables.ShipConfig_EquipStorage) {
		this._storage = JSON.parse(missionVariables.ShipConfig_EquipStorage);
		this.$addExtraField();
		delete missionVariables.ShipConfig_EquipStorage;
	}
	if (missionVariables.ShipConfig_LastChargeDate) {
		this._lastChargeDate = missionVariables.ShipConfig_LastChargeDate;
		delete missionVariables.ShipConfig_LastChargeDate;
	}
	// otherwise, we'll get our own versions
	if (missionVariables.EquipmentStorage_Data) {
		this._storage = JSON.parse(missionVariables.EquipmentStorage_Data);
		delete missionVariables.EquipmentStorage_Data;
	}
	if (missionVariables.EquipmentStorage_LastChargeDate) {
		this._lastChargeDate = missionVariables.EquipmentStorage_LastChargeDate;
	}
	this.$initInterface(player.ship.dockedStation);
	this._lastSource = system.ID;

	var keys = Object.keys(system.mainStation.market);
	for (var i = 0; i < keys.length; i++) {
		this._commodities.push(keys[i]);
	}
	this.$compileSamplePrices();

	// if shipconfig is present, point our nostore list at shipconfig's nosell list
	if (this._sc_core) this._noStore = this._noStore.concat(this._sc_core._noSell);
	// include shipversions placeholder equipment items
	if (worldScripts.shipversion) {
		for (var i = 75; i <= 100; i++) {
			this._noStore.push("EQ_SERVICE_LEVEL_" + i.toString());
		}
		for (var i = 10; i <= 100; i++) {
			this._noStore.push("EQ_SHIP_VERSION_" + i.toString());
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function () {
	if (this._storage.length > 0) {
		missionVariables.EquipmentStorage_Data = JSON.stringify(this._storage);
	} else {
		delete missionVariables.EquipmentStorage_Data;
	}
	missionVariables.EquipmentStorage_LastChargeDate = this._lastChargeDate;
}

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

//-------------------------------------------------------------------------------------------------------------
this.shipWillLaunchFromStation = function (station) {
	this._lastSource = system.ID;
}

//-------------------------------------------------------------------------------------------------------------
this.shipExitedWitchspace = function () {
	// update the lastSource ID
	if (system.ID != -1) this._lastSource = system.ID;
	this.$compileSamplePrices();
}

//-------------------------------------------------------------------------------------------------------------
this.playerEnteredNewGalaxy = function (galaxyNumber) {
	// any in transit items will be transported to their destinations - a galjump freebee
	for (var i = 0; i < this._storage.length; i++) {
		if (this._storage[i].inTransit === true) {
			this._storage[i].system = this._storage[i].transitDestination;
			this._storage[i].systemName = this._storage[i].transitDestinationName;
			this._storage[i].inTransit = false;
			this._storage[i].transitSystem = -1;
			this._storage[i].transitDestination = -1;
			this._storage[i].transitETA = 0;
			this._storage[i].transitStart = 0;
		}
	}
	this._lastSource = system.ID;
}

//-------------------------------------------------------------------------------------------------------------
this.dayChanged = function () {
	// charge the player for storage
	if ((clock.seconds - this._lastChargeDate) > 86400) {
		this._lastChargeDate = clock.seconds;
		var data = this.$storageSystemList();
		var abort = false;
		var compList = "";
		var dropList = {};
		var totalRefund = 0;
		var totalCost = 0;

		var keys = Object.keys(data);
		for (var i = 0; i < keys.length; i++) {
			var gal = parseInt(keys[i].split("_")[0]);
			var sys = parseInt(keys[i].split("_")[1]);
			var cost = this.$calculateSystemCost(gal, sys);
			var count = this.$storageSystemCount(gal, sys);
			if (player.credits > cost) {
				player.credits -= cost;
				totalCost += cost;
				compList += (compList === "" ? "" : "\n") + "- " + formatCredits(cost, false, true) + " for storage of " + count + (count === 1 ? " item" : " items") + " in the " + data[keys[i]] + " system" +
					(galaxyNumber != gal ? " (G" + (gal + 1) + ")" : "");

			} else {
				// refund half the cost of the items in storage, then remove them
				abort = true;
				var refund = 0;
				for (var j = this._storage.length - 1; j >= 0; j--) {
					var item = this._storage[j];
					if (item.galaxy === gal && item.system === sys) {
						if (item.equipmentKey != "") {
							var info = EquipmentInfo.infoForKey(item.equipmentKey);
							if (info.effectiveTechLevel > 14) {
								refund += 1000;
							} else {
								refund += (info.price / 10) * 0.5;
							}
						}
						if (item.commodity != "") {
							refund += ((system.mainStation.market[item.commodity].price / 10) * item.weight) * 0.5;
						}
					}
					this._storage.splice(j, 1);
				}
				totalRefund += refund
				dropList[keys[i]] = {
					galaxy: gal,
					system: sys,
					systemName: data[keys[i]],
					refund: refund,
					count: count
				};
			}
		}
		// send a refund email, if required
		if (abort === true && totalRefund > 0) {
			player.credits += totalRefund;
			// give the player an email, if installed
			var ga = worldScripts.GalCopAdminServices;
			if (ga) {
				var email = worldScripts.EmailSystem;
				var keys = Object.keys(dropList);
				for (var i = 0; i < keys.length; i++) {
					var item = dropList[keys[i]];

					var msg = expandDescription("[storeequip-cancelled]", {
						systemname: item.systemName,
						galaxy: (parseInt(item.galaxy) === galaxyNumber ? "" : " (G" + item.galaxy + ")"),
						count: item.count,
						refund: formatCredits(item.refund, true, true)
					});

					if (item.galaxy != galaxyNumber) {
						email.$createEmail({
							sender: expandDescription(this._compNames[item.system % 16]),
							subject: "Storage Cancelled",
							date: global.clock.seconds,
							message: msg,
							expiryDays: ga._defaultExpiryDays
						});
					} else {
						email.$createEmail({
							sender: expandDescription(this._compNames[item.system % 16]),
							subject: "Storage Cancelled",
							date: global.clock.seconds,
							sentFrom: item.system,
							message: msg,
							expiryDays: ga._defaultExpiryDays
						});
					}
				}
			}
		}
		// send a confirmation email, if required
		if (compList.length > 0 && totalCost > 0) {
			// give the player an email, if installed
			var ga = worldScripts.GalCopAdminServices;
			if (ga) {
				var email = worldScripts.EmailSystem;

				var msg = expandDescription("[storeequip-dailycost]", {
					cost: formatCredits(totalCost, true, true),
					costdetails: compList
				});

				email.$createEmail({
					sender: "Storage Facility",
					subject: "Storage payment",
					date: global.clock.seconds,
					message: msg,
					expiryDays: ga._defaultExpiryDays
				});
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.playerBoughtEquipment = function (equipment) {
	this.$checkForArrivals();
}

//-------------------------------------------------------------------------------------------------------------
this.playerBoughtNewShip = function (ship) {
	this.$checkForArrivals();
}

//-------------------------------------------------------------------------------------------------------------
this.reportScreenEnded = this.missionScreenEnded = function () {
	this.$checkForArrivals();
}

//-------------------------------------------------------------------------------------------------------------
this.$addExtraField = function() {
	if (this._storage.length == 0) return;
	for (var i = 0; i < this._storage.length; i++) {
		if (!this._storage[i].hasOwnProperty("extra")) this._storage[i].extra = "";
	}
}

//-------------------------------------------------------------------------------------------------------------
// mission screen for displaying list of equipment that can be stored
this.$storeEquipment = function $storeEquipment() {
	function compare(a, b) {
		return b.space - a.space;
	}

	var p = player.ship;
	var stn = p.dockedStation;
	var used = 0;
	var maxspace = 0;
	if (this._sc_core) {
		used = this._sc_core.$calcEquipmentSpace(p);
		maxspace = Math.ceil(this._sc_core.$equipmentSpaceTotal(p) + this._sc_core.$cargoSpaceAvailable(p));
	}
	var interrupt = true;
	var spc = String.fromCharCode(31) + String.fromCharCode(31) + String.fromCharCode(31);

	var text = "";
	var curChoices = {};
	var itemcount = 0;

	var pagesize = 15;
	if (!this._sc_core) pagesize += 5;

	this._hudHidden = p.hudHidden;
	if (this.$isBigGuiActive() === false) p.hudHidden = true;
	if (this._sc_core) this._sc_core._configScreen = true;
	var enabled = true;
	if (this.$storageSystemCount(galaxyNumber, system.ID) === 0 && player.credits < this._establishmentFee) enabled = false;

	// display the list of options
	if (used > maxspace) {
		// make sure we don't lock the player in during flight
		if (p.alertCondition === 0) {
			interrupt = false; // set to false to force the player to sell equipment before continuing
		}
		if (this._sc_core) {
			curChoices["98_SWITCH"] = {
				text: "[sc-change-config]",
				color: this._itemColor
			};
			pagesize -= 1;
		}
		curChoices["99_EXIT"] = {
			text: "[storage-return]",
			color: this._disabledColor,
			unselectable: true
		};
	} else {
		curChoices["99_EXIT"] = {
			text: "[storage-return]",
			color: this._itemColor
		};
		if (this._sc_core) this._sc_core._initialSetup = false;
	}

	text += this.$pageHeader();

	if (this._sc_core) {
		text += this.$padTextRight(expandDescription("[storage-equipment-title]"), 16) +
			this.$padTextLeft((this._sc_core ? "Equip" : " "), 4) +
			this.$padTextLeft(" ", 4) +
			this.$padTextLeft("Storage", 8) + "\n";
	} else {
		var addText = " ";
		if (stn.isMainStation === true && this.$storageSystemCount(galaxyNumber, system.ID) === 0) {
			addText += expandDescription("[storage-deposit-required]", {
				fee: formatCredits(this._establishmentFee, false, true)
			});
		}
		text += this.$padTextRight(addText, 24) +
			this.$padTextLeft("Storage", 8) + "\n";
	}

	text += this.$padTextRight("Equipment Item", 16) +
		this.$padTextLeft((this._sc_core ? "space" : " "), 4) +
		this.$padTextLeft((this._sc_core ? "Weight" : " "), 4) +
		this.$padTextLeft("Cost", 8) + "\n\n";

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

	for (var i = 0; i < eq.length; i++) {
		var item = eq[i];
		// exclusions
		// don't include "no store" items
		if (this._noStore.indexOf(item.equipmentKey) >= 0) continue;
		if (this._sc_core) {
			// boosters can't be included either
			if (this._sc_core._boosters.indexOf(item.equipmentKey) >= 0) continue;
			// nor can armour
			if (this._sc_core._armour.indexOf(item.equipmentKey) >= 0) continue;
			// nor can extensions
			if (this._sc_core._extensions.indexOf(item.equipmentKey) >= 0) continue;
		}
		// we can't store smuggling compartments, because we can't reliably reinstall them later
		// storage facilities only exist at main stations, but smuggling compartments can only be installed at non-main stations
		if (item.equipmentKey.indexOf("EQ_SMUGGLING_COMPARTMENT") >= 0) continue;
		// don't include any items in the ignore equipment list that aren't specially allowed
		if (this._sc_core && this._sc_core._ignoreEquip.indexOf(item.equipmentKey) >= 0 && this._allowedIgnored.indexOf(item.equipmentKey) === -1) continue;

		var space = 0;
		var weight = 0;
		var zeroSpace = 0;

		var lookup = null;
		if (this._sc_core) lookup = this._sc_core._equipmentMatrix[item.equipmentKey];
		if (lookup) {
			space = parseFloat(lookup[0]);
			weight = parseFloat(lookup[1]);
			if (space === 0 && weight === 0) zeroSpace = 1;
		} else {
			if (item.requiredCargoSpace === 0) {
				if (this._allowedIgnored.indexOf(item.equipmentKey) >= 0) {
					zeroSpace = 1;
				} else {
					space = 1;
					weight = 1;
				}
			} else {
				weight = item.requiredCargoSpace;
				space = weight;
			}
		}

		var colr = this._menuColor;
		if (p.equipmentStatus(item.equipmentKey) === "EQUIPMENT_DAMAGED") colr = this._damagedColor;
		// special case for armour
		if (this._sc_core && this._sc_core._armour.indexOf(item.equipmentKey) >= 0) {
			if (p.script._armourFront < 100 || p.script._armourAft < 100) colr = this._damagedColor;
		}
		if (enabled === false) colr = this._disabledColor;
		if (item.isVisible && item.requiresEmptyPylon === false && this.$equipmentIsRequired(item.equipmentKey) === false && (!this._sc_core || (space > 0 || weight > 0 || zeroSpace === 1))) {
			// any items in the negotiate list will show as "???" in the cost column 
			// if the player chooses to sell this item he will have to negotiate a price
			var loop = 1;
			// special case for passenger berths - only 1 item is in the equipment list, but you could have multiple installed
			if (item.equipmentKey === "EQ_PASSENGER_BERTH") loop = p.passengerCapacity;
			for (var j = 1; j <= loop; j++) {
				list.push({
					eq: item.equipmentKey,
					space: space,
					weight: weight,
					cost: this.$calculateStorageCost(space, weight),
					color: colr,
					name: (item.equipmentKey === "EQ_PASSENGER_BERTH" ? "Passenger Berth" : item.name),
					section: ""
				});
			}
		}
	}

	// laser weapons
	var lmss = worldScripts.LMSS_Core;
	var section = ["Forward", "Aft", "Port", "Starboard", "Forward - Secondary", "Aft - Secondary", "Port - Secondary", "Starboard - Secondary"];
	for (var i = 0; i < 7; i++) {
		var key = "";
		var damage = false;
		switch (i) {
			case 0:
				key = p.forwardWeapon.equipmentKey;
				if (key == "EQ_WEAPON_DAMAGED_LASER") {
					key = worldScripts.BreakableLasers_Main._holdDamage["FORWARD"].primary;
					damage = true;
				}
				break;
			case 1:
				key = p.aftWeapon.equipmentKey;
				if (key == "EQ_WEAPON_DAMAGED_LASER") {
					key = worldScripts.BreakableLasers_Main._holdDamage["AFT"].primary;
					damage = true;
				}
				break;
			case 2:
				key = p.portWeapon.equipmentKey;
				if (key == "EQ_WEAPON_DAMAGED_LASER") {
					key = worldScripts.BreakableLasers_Main._holdDamage["PORT"].primary;
					damage = true;
				}
				break;
			case 3:
				key = p.starboardWeapon.equipmentKey;
				if (key == "EQ_WEAPON_DAMAGED_LASER") {
					key = worldScripts.BreakableLasers_Main._holdDamage["STARBOARD"].primary;
					damage = true;
				}
				break;
			case 4:
				if (lmss) {
					key = lmss._forwardAltKey;
					if (key == "EQ_WEAPON_DAMAGED_LASER") {
						key = worldScripts.BreakableLasers_Main._holdDamage["FORWARD"].secondary;
						damage = true;
					}
				}
				break;
			case 5:
				if (lmss) {
					key = lmss._aftAltKey;
					if (key == "EQ_WEAPON_DAMAGED_LASER") {
						key = worldScripts.BreakableLasers_Main._holdDamage["AFT"].secondary;
						damage = true;
					}
				}
				break;
			case 6:
				if (lmss) {
					key = lmss._portAltKey;
					if (key == "EQ_WEAPON_DAMAGED_LASER") {
						key = worldScripts.BreakableLasers_Main._holdDamage["PORT"].secondary;
						damage = true;
					}
				}
				break;
			case 7:
				if (lmss) {
					key = lmss._starboardAltKey;
					if (key == "EQ_WEAPON_DAMAGED_LASER") {
						key = worldScripts.BreakableLasers_Main._holdDamage["STARBOARD"].secondary;
						damage = true;
					}
				}
				break;
		}
		if (key !== "" && key !== "EQ_WEAPON_NONE") {
			var item = EquipmentInfo.infoForKey(key);
			var space = 1; // for storage purposes, it's always 1t
			var weight = 0;
			var zeroSpace = 0;

			if (this._sc_core) {
				var lookup = this._sc_core._equipmentMatrix[item.equipmentKey];
				if (lookup) {
					space = parseFloat(lookup[0]);
					if (space === 0) space = 1;
					weight = parseFloat(lookup[1]);
				} else {
					weight = 1;
				}
			} else {
				weight = 1;
			}

			colr = this._menuColor;
			if (damage) colr = this._damagedColor;

			list.push({
				eq: item.equipmentKey,
				space: space,
				weight: weight,
				cost: this.$calculateStorageCost(space, weight),
				color: colr,
				name: item.name + " (" + section[i] + ")",
				section: i
			});
		}
	}

	if (list.length > 0) {
		list.sort(compare);

		var maxpage = Math.ceil(list.length / pagesize);
		if (maxpage < (this._page + 1)) this._page -= 1;
		var end = ((this._page * pagesize) + pagesize);
		if (end > list.length) end = list.length;
		// note: then station equipmentPriceFactor has been removed to prevent exploitation of equipment sales at different stations (galcop => rock hermit)
		for (var i = (this._page * pagesize); i < end; i++) {
			curChoices["01_EQUIP-" + (i < 10 ? "0" : "") + i + "~" + list[i].eq + (list[i].section !== "" ? "|" + list[i].section : "")] = {
				text: this.$padTextRight(list[i].name, 16) +
					this.$padTextLeft((this._sc_core ? list[i].space.toFixed(1) + spc + "t" : " "), 4) +
					this.$padTextLeft((this._sc_core ? list[i].weight.toFixed(1) + spc + "t" : " "), 4) +
					(list[i].cost === "???" ? this.$padTextLeft("???", 8) :
						this.$padTextLeft(formatCredits((list[i].cost), true, true) + "/d", 8)), //* stn.equipmentPriceFactor
				alignment: "LEFT",
				unselectable: !enabled,
				color: list[i].color
			};
			itemcount += 1;
		}
		for (var i = 0; i < ((pagesize + 1) - itemcount); i++) {
			curChoices["90_SPACER_" + i] = "";
		}
	} else {
		text += expandDescription("[storage-none-to-store]");
	}

	if (maxpage > 1 && this._page < (maxpage - 1)) {
		curChoices["95_NEXT"] = {
			text: "[storage-nextpage]",
			color: this._itemColor
		};
	} else {
		curChoices["95_NEXT"] = {
			text: "[storage-nextpage]",
			color: this._disabledColor,
			unselectable: true
		};
	}
	if (this._page > 0) {
		curChoices["96_PREV"] = {
			text: "[storage-prevpage]",
			color: this._itemColor
		};
	} else {
		curChoices["96_PREV"] = {
			text: "[storage-prevpage]",
			color: this._disabledColor,
			unselectable: true
		};
	}

	var def = "99_EXIT";
	if (this._lastChoiceStore != "") def = this._lastChoiceStore;

	var opts = {
		screenID: "oolite-storage-equipment-map",
		title: "Store Equipment",
		allowInterrupt: interrupt,
		exitScreen: (this._sc_core ? this._sc_core._sellEquipExitScreen : "GUI_SCREEN_INTERFACES"),
		choices: curChoices,
		initialChoicesKey: def,
		message: text
	};

	if (this._sc_core) this._sc_core.$addShipModelToScreen(opts);
	mission.runScreen(opts, this.$storeEquipmentScreenHandler, this);
	if (this._sc_core) this._sc_core.$finaliseShipModel();
}

//-------------------------------------------------------------------------------------------------------------
// handles menu selection on store equipment screen
this.$storeEquipmentScreenHandler = function $storeEquipmentScreenHandler(choice) {

	if (!choice) return;

	var p = player.ship;
	var stn = p.dockedStation;

	this._lastChoiceStore = choice;

	if (choice.indexOf("01_EQUIP") >= 0) {
		var item = choice.substring(choice.indexOf("~") + 1);
		var section = ""
		// check for a laser position
		if (item.indexOf("|") >= 0) {
			section = item.split("|")[1];
			item = item.split("|")[0];
		}
		var info = EquipmentInfo.infoForKey(item);
		var space = 0;
		var weight = 0;
		if (section === "") {
			var dam = p.equipmentStatus(item);

			var lookup = null;
			if (this._sc_core) lookup = this._sc_core._equipmentMatrix[info.equipmentKey];
			if (lookup) {
				space = parseFloat(lookup[0]);
				weight = parseFloat(lookup[1]);
				if (space === 0 && info.requiredCargoSpace > 0) space = info.requiredCargoSpace;
			} else {
				if (info.requiredCargoSpace === 0) {
					if (this._allowedIgnored.indexOf(info.equipmentKey) === -1) {
						space = 1;
						weight = 1;
					}
				} else {
					weight = info.requiredCargoSpace;
					space = weight;
				}
			}

			// special case for passenger berths
			if (item === "EQ_PASSENGER_BERTH") {
				var creds = player.credits;
				p.awardEquipment("EQ_PASSENGER_BERTH_REMOVAL");
				p.credits = creds;
			} else {
				p.removeEquipment(item);
			}

			if (item.indexOf("EQ_LMSS") >= 0) {
				// if we've removed all the positions, then remove the activator as well
				if (p.equipmentStatus("EQ_LMSS_FRONT") != "EQUIPMENT_OK" && p.equipmentStatus("EQ_LMSS_AFT") != "EQUIPMENT_OK" &&
					p.equipmentStatus("EQ_LMSS_PORT") != "EQUIPMENT_OK" && p.equipmentStatus("EQ_LMSS_STARBOARD") != "EQUIPMENT_OK") {
					p.removeEquipment("EQ_LMSS_ACTIVATOR");
				}
				worldScripts.LMSS_Core.$resetInterface();
			}

			if (this._sc_core) this._sc_core._current = {};
			var idx = this.$countItemAtStation(item);
			// for some reason passenger berths report as unavailable
			if (item === "EQ_PASSENGER_BERTH" && dam === "EQUIPMENT_UNAVAILABLE") dam = "EQUIPMENT_OK";
		} else {
			// weapons are removed from specific locations
			var lookup = null;
			if (this._sc_core) lookup = this._sc_core._equipmentMatrix[item];
			if (lookup) {
				space = parseFloat(lookup[0]);
				if (space === 0) space = 1;
				weight = parseFloat(lookup[1]);
			} else {
				space = 1;
				weight = 1;
			}

			var dam = "EQUIPMENT_OK"; // lasers can't be damaged
			var lmss = worldScripts.LMSS_Core;
			var bl = worldScripts.BreakableLasers_Main;
			switch (section) {
				case "0":
					if (p.forwardWeapon.equipmentKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["FORWARD"].primary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					p.forwardWeapon = "EQ_WEAPON_NONE";
					break;
				case "1":
					if (p.aftWeapon.equipmentKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["AFT"].primary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					p.aftWeapon = "EQ_WEAPON_NONE";
					break;
				case "2":
					if (p.portWeapon.equipmentKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["PORT"].primary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					p.portWeapon = "EQ_WEAPON_NONE";
					break;
				case "3":
					if (p.starboardWeapon.equipmentKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["STARBOARD"].primary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					p.starboardWeapon = "EQ_WEAPON_NONE"
					break;
				case "4":
					if (lmss._forwardAltKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["FORWARD"].secondary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					lmss._forwardAltKey = "EQ_WEAPON_NONE";
					p.removeEquipment("EQ_LMSS_FRONT_WEAPON");
					break;
				case "5":
					if (lmss._aftAltKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["AFT"].secondary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					lmss._aftAltKey = "EQ_WEAPON_NONE";
					p.removeEquipment("EQ_LMSS_AFT_WEAPON");
					break;
				case "6":
					if (lmss._portAltKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["PORT"].primary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					lmss._portAltKey = "EQ_WEAPON_NONE";
					p.removeEquipment("EQ_LMSS_PORT_WEAPON");
					break;
				case "7":
					if (lmss._starboardAltKey == "EQ_WEAPON_DAMAGED_LASER") {
						bl._holdDamage["STARBOARD"].primary = "";
						dam = "EQUIPMENT_DAMAGED";
					}
					lmss._starboardAltKey = "EQ_WEAPON_NONE";
					p.removeEquipment("EQ_LMSS_STARBOARD_WEAPON");
					break;
			}
			if (lmss) lmss.$updateManifest();
			if (bl) bl.$updateManifest();

			var idx = this.$countItemAtStation(item);
			// once stored, they will become just standard lasers, not allocated to any specific position on the ship
			// positions will be confirmed when lasers are re-installed 
		}

		// charge player deposit
		if (this.$storageSystemCount(galaxyNumber, system.ID) === 0) {
			player.credits -= this._establishmentFee;
			player.consoleMessage(formatCredits(this._establishmentFee, false, true) + " deducted to open storage facility account", 5);
		}

		this._storage.push({
			galaxy: galaxyNumber,
			system: system.ID,
			systemName: system.name,
			equipmentKey: item,
			commodity: "",
			space: space,
			weight: weight,
			status: dam,
			index: idx,
			inTransit: false,
			transitETA: 0,
			transitDestination: -1,
			transitSystem: -1,
			transitStart: 0
		});

		// give the player an email, if installed
		var ga = worldScripts.GalCopAdminServices;
		if (ga) {
			var email = worldScripts.EmailSystem;
			var storeName = expandDescription(this._compNames[system.ID % 16]);

			if (this.$storageSystemCount(galaxyNumber, system.ID) === 0) {
				// email T&C's
			}

			// were we charged something, or did we get a refund?
			var cost = "This item will be stored here at a rate of " + formatCredits(this.$calculateStorageCost(space, weight), true, true) + " per day.";

			var msg = expandDescription("[storeequip-email]", {
				storagename: storeName,
				equipname: info.name,
				equipcost: cost,
				sender: this.$salesRep()
			});

			email.$createEmail({
				sender: storeName,
				subject: "Storing equipment",
				date: global.clock.adjustedSeconds,
				message: msg,
				expiryDays: ga._defaultExpiryDays
			});
		}
		if (this._sc_core) this._sc_core.$addEquipmentSpaceItem(p);
	}

	if (choice === "95_NEXT") this._page += 1;
	if (choice === "96_PREV") this._page -= 1;
	if (choice === "98_SWITCH") {
		this._sc_core._source = "";
		this._sc_core.$shipConfig();
		return;
	}

	if (this._sc_core && this._sc_core._source === "REMOVEITEM") return;

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

	if (choice === "99_EXIT") {
		if (this._sc_core) {
			if (this._sc_core._source === "CONFIGSCREEN") {
				this._sc_core._source = "";
				this._sc_core.$shipConfig();
			}
			return;
		}
		this.$viewStoredItems();
	}
}

//-------------------------------------------------------------------------------------------------------------
// initialise the equipment specification list
this.$initInterface = function $initInterface(station) {
	// no access in interstellar space
	if (system.ID === -1) {
		station.setInterface(this.name, null);
		return;
	}
	if (this.$stationHasStorageFacilities(station) === true) {
		station.setInterface(this.name, {
			title: "Storage facility management",
			category: "Station Interfaces",
			summary: "Lists any equipment or cargo you have stored at this station or in other systems.",
			callback: this.$openStorageListing.bind(this)
		});
	} else {
		station.setInterface(this.name, {
			title: "Storage facility information",
			category: "Station Interfaces",
			summary: "Lists any equipment or cargo you have stored at the main station or in other systems.",
			callback: this.$openStorageListing.bind(this)
		});
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$openStorageListing = function $openStorageListing() {
	this._page = 0;
	this._display = 0;
	this._displayType = 0;
	this.$viewStoredItems();
}

//-------------------------------------------------------------------------------------------------------------
// views mission screen showing currently stored equipment
this.$viewStoredItems = function $viewStoredItems() {
	function compare(a, b) {
		return (b.sort > a.sort ? -1 : 1);
	}

	var p = player.ship;
	var stn = p.dockedStation;

	var text = "";
	var curChoices = {};
	var itemcount = 0;
	var list = [];

	var pagesize = 20;
	this._hudHidden = p.hudHidden;
	if (this.$isBigGuiActive() === false) p.hudHidden = true;
	if (this._sc_core) this._sc_core._configScreen = true;

	// main list
	if (this._display === 0) {
		pagesize = 19;
		text += expandDescription(this._compNames[system.ID % 16]) + " Facility Management" + (p.dockedStation.isMainStation === true ? "" : " (at Main Station)") + "\n\n";

		text += this.$padTextRight(expandDescription("Item"), 16) +
			this.$padTextRight("Location", 10) +
			this.$padTextLeft("Cost", 6) + "\n";

		for (var i = 0; i < this._storage.length; i++) {
			if (this._displayType === 1 || (this._storage[i].galaxy === galaxyNumber && this._storage[i].system === system.ID) || this._storage[i].inTransit === true) {
				if (this._storage[i].equipmentKey != "") {
					var equip = EquipmentInfo.infoForKey(this._storage[i].equipmentKey);
					if (equip) { // only display equipment items that are currently installed (in case an OXP is removed)
						var colr = this._menuColor;
						if (this._storage[i].galaxy === galaxyNumber && this._storage[i].system === system.ID) colr = this._installedColor;
						if (this._storage[i].status === "EQUIPMENT_DAMAGED") colr = this._damagedColor;
						if (this._storage[i].galaxy != galaxyNumber) colr = "blueColor";
						if (this._storage[i].inTransit === true) colr = this._riskColor;

						var info = null;
						var dist = 0;
						if (this._storage[i].galaxy === galaxyNumber && this._storage[i].system != system.ID && this._storage[i].system >= 0) {
							info = System.infoForSystem(galaxyNumber, this._storage[i].system);
							var route = system.info.routeToSystem(info);
							if (route && route.route.length > 0) dist = route.distance;
						}

						list.push({
							equipmentName: "Equipment: " + (equip.equipmentKey === "EQ_PASSENGER_BERTH" ? "Passenger Berth" : equip.name),
							location: (this._storage[i].inTransit === false ? this._storage[i].systemName + " " + (info != null ? "(dist: " + dist.toFixed(1) + "ly)" : (this._storage[i].galaxy != galaxyNumber ? "(G" + (this._storage[i].galaxy + 1) + ")" : "")) : "In transit"),
							cost: formatCredits(this.$calculateStorageCost(this._storage[i].space, this._storage[i].weight), true, true) + " / day",
							equipmentKey: this._storage[i].equipmentKey,
							commodity: "",
							galaxy: this._storage[i].galaxy,
							system: this._storage[i].system,
							color: colr,
							sort: this._storage[i].galaxy + "_" + this._storage[i].systemName + "_" + equip.name,
							id: this._storage[i].galaxy + "|" + this._storage[i].system + "|" + this._storage[i].equipmentKey + "|" + this._storage[i].index
						});
					}
				}
				if (this._storage[i].commodity != "" && this._commodities.indexOf(this._storage[i].commodity) >= 0) {
					var colr = this._menuColor;
					if (this._storage[i].galaxy === galaxyNumber && this._storage[i].system === system.ID) colr = this._installedColor;
					if (this._storage[i].galaxy != galaxyNumber) colr = "blueColor";
					if (this._storage[i].inTransit === true) colr = this._riskColor;

					var info = null;
					var dist = 0;
					if (this._storage[i].galaxy === galaxyNumber && this._storage[i].system != system.ID && this._storage[i].system >= 0) {
						info = System.infoForSystem(galaxyNumber, this._storage[i].system);
						var route = system.info.routeToSystem(info);
						if (route && route.route.length > 0) dist = route.distance;
					}

					list.push({
						equipmentName: "Cargo: " + this._storage[i].weight + this.$getCommodityUnit(this._storage[i].commodity) + " × " + displayNameForCommodity(this._storage[i].commodity) + (this._storage[i].extra != "" ? "*" : ""),
						location: (this._storage[i].inTransit === false ? this._storage[i].systemName + " " + (info != null ? "(dist: " + dist.toFixed(1) + "ly)" : (this._storage[i].galaxy != galaxyNumber ? "(G" + (this._storage[i].galaxy + 1) + ")" : "")) : "In transit"),
						cost: formatCredits(this.$calculateStorageCost(this._storage[i].space, this._storage[i].weight, this.$getCommodityUnit(this._storage[i].commodity)), true, true) + " / day",
						equipmentKey: "",
						commodity: this._storage[i].commodity,
						galaxy: this._storage[i].galaxy,
						system: this._storage[i].system,
						color: colr,
						sort: this._storage[i].galaxy + "_" + this._storage[i].systemName + "_zz" + displayNameForCommodity(this._storage[i].commodity), // ensure cargo items come after equipment
						id: this._storage[i].galaxy + "|" + this._storage[i].system + "|" + this._storage[i].commodity + "|" + this._storage[i].index
					});

				}
			}
		}

		if (list.length > 0) {
			list.sort(compare);

			var maxpage = Math.ceil(list.length / pagesize);
			if (maxpage < (this._page + 1)) this._page -= 1;
			var end = ((this._page * pagesize) + pagesize);
			if (end > list.length) end = list.length;
			// note: the station equipmentPriceFactor has been removed to prevent exploitation of equipment sales at different stations (galcop => rock hermit)
			for (var i = (this._page * pagesize); i < end; i++) {
				curChoices["01_EQUIP-" + (i < 10 ? "0" : "") + i + "~" + list[i].id] = {
					text: this.$padTextRight(list[i].equipmentName, 16) +
						this.$padTextRight(list[i].location, 10) +
						this.$padTextLeft(list[i].cost, 6),
					alignment: "LEFT",
					color: list[i].color
				};
				itemcount += 1;
			}
			for (var i = 0; i < (pagesize - itemcount) + (p.dockedStation.isMainStation === false ? 1 : 0); i++) {
				curChoices["90_SPACER_" + i] = "";
			}
		} else {
			text += expandDescription("[storage-equip-none]");
		}

		if (p.dockedStation.isMainStation === true) {
			if (this.$storageSystemCount(galaxyNumber, system.ID) === 0 && player.credits < this._establishmentFee) {
				if (!this._sc_core) {
					curChoices["96_Z_STOREEQUIP"] = {
						text: expandDescription("[storage-store-equipment]") + " (insufficient credits to open account)",
						color: this._disabledColor,
						unselectable: true
					};
				}
				curChoices["98_MOVECARGO"] = {
					text: expandDescription("[storage-move-cargo]") + " (insufficient credits to open account)",
					color: this._disabledColor,
					unselectable: true
				};
			} else {
				if (!this._sc_core) {
					curChoices["96_Z_STOREEQUIP"] = {
						text: expandDescription("[storage-store-equipment]"),
						color: this._itemColor,
					};
				}
				curChoices["98_MOVECARGO"] = {
					text: "[storage-move-cargo]",
					color: this._itemColor
				};
			}
		}

		if (maxpage > 1 && this._page < (maxpage - 1)) {
			curChoices["95_NEXT"] = {
				text: "[storage-nextpage]",
				color: this._itemColor
			};
		} else {
			curChoices["95_NEXT"] = {
				text: "[storage-nextpage]",
				color: this._disabledColor,
				unselectable: true
			};
		}
		if (this._page > 0) {
			curChoices["96_PREV"] = {
				text: "[storage-prevpage]",
				color: this._itemColor
			};
		} else {
			curChoices["96_PREV"] = {
				text: "[storage-prevpage]",
				color: this._disabledColor,
				unselectable: true
			};
		}

		if (this._displayType === 1) {
			curChoices["97_SHOWLOCAL"] = {
				text: "[storage-showlocal]",
				color: this._itemColor
			};
		} else {
			curChoices["97_SHOWALL"] = {
				text: "[storage-showall]",
				color: this._itemColor
			};
		}

		curChoices["99_EXIT"] = {
			text: "Exit",
			color: this._itemColor
		};

		var def = "99_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];

		var opts = {
			screenID: "oolite-storage-main-map",
			title: "Storage - " + (this._displayType === 0 ? "Local" : "All Systems"),
			allowInterrupt: true,
			overlay: {
				name: "equipstorage.png",
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

	// equipment details
	if (this._display === 1) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		if (item.equipmentKey != "") {
			var equip = EquipmentInfo.infoForKey(item.equipmentKey);
		}

		var info = null;
		var dist = 0;
		var jumps = 0;
		var danger = 0;
		var illegal = 0;
		var time = 0;
		if (item.galaxy === galaxyNumber && item.system != system.ID && item.system >= 0) {
			this.$revealMap();
			info = System.infoForSystem(galaxyNumber, item.system);
			var route = system.info.routeToSystem(info);
			jumps = route.route.length;
			dist = route.distance;
			danger = this.$checkRouteDangerLevel(route);
			if (item.commodity != "") illegal = this.$checkRouteIllegalLevel(route, item.commodity);
			time = route.time;
			this.$restoreMap();
		}

		if (item.equipmentKey != "") {
			text = this.$padTextRight("Equipment item:", 10) + (item.equipmentKey === "EQ_PASSENGER_BERTH" ? "Passenger Berth" : equip.name) + "\n";
			text += this.$padTextRight("Equipment status:", 10) + (item.status === "EQUIPMENT_OK" ? "OK" : "Damaged") + "\n";
			if (this._sc_core) {
				if (parseFloat(item.space) === 0 && parseFloat(item.weight) === 0) {
					text += this.$padTextRight("Space/weight:", 10) + "Negligible\n";
				} else {
					text += this.$padTextRight("Space/weight:", 10) + (parseFloat(item.space) === 0 ? item.weight : item.space) + "t/" + item.weight + "t\n";
				}
			}
			text += this.$padTextRight("Storage cost:", 10) + formatCredits(this.$calculateStorageCost(item.space, item.weight), true, true) + " per day\n";
		}
		if (item.commodity != "") {
			text = this.$padTextRight("Cargo type:", 10) + displayNameForCommodity(item.commodity) + "\n";
			if (this._sc_core) {
				var spcwt = this.$cargoSpaceRequired(this.$getCommodityUnit(item.commodity), item.weight);
				text += this.$padTextRight("Space/weight:", 10) + spcwt + "t/" + item.weight + this.$getCommodityUnit(item.commodity) + "\n";
			}
			text += this.$padTextRight("Storage cost:", 10) + formatCredits(this.$calculateStorageCost(item.space, item.weight, this.$getCommodityUnit(item.commodity)), true, true) + " per day\n";
			if (item.extra != "") {
				text += this.$padTextRight("Note:", 10)  + item.extra.split(";").length.toString() + this.$getCommodityUnit(item.commodity) + " is from specialty market.\n";
			}
		}
		if (item.system != -1) {
			text += this.$padTextRight("Location:", 10) +
				item.systemName + " " + (info != null ? "(dist: " + dist.toFixed(1) + "ly)" : (item.galaxy != galaxyNumber ? "(G" + (item.galaxy + 1) + ")" : "")) + "\n";
		} else {
			text += this.$padTextRight("Location:", 10) + "In transit\n";
			var eta = this.$getTimeRemaining(item.transitETA);
			text += this.$padTextRight("ETA:", 10) + System.systemNameForID(item.transitDestination) + (eta === "Overdue" ? " - delivery overdue" : " in " + eta) + "\n";
		}
		var tfrcost = 0;
		if (dist > 0) {
			tfrcost = this.$calculateTransferCost(dist, jumps, danger, illegal);
			text += this.$padTextRight("Transfer cost:", 10) + formatCredits(tfrcost, true, true) + "\n";
			text += this.$padTextRight("Transfer time:", 10) + time.toFixed(1) + " hrs\n";
			text += this.$padTextRight("Transfer risk:", 10) + expandDescription("[transfer-danger-" + danger + "]") + "\n";
		}

		if (dist > 7.5) {
			curChoices["25_SHOWMAP_LONG"] = {
				text: "[storage-showmap]",
				color: this._itemColor
			};
		} else if (dist > 0 && dist <= 7.5) {
			curChoices["25_SHOWMAP_SHORT"] = {
				text: "[storage-showmap]",
				color: this._itemColor
			};
		}
		if (dist > 0 && player.credits >= tfrcost) {
			if (item.equipmentKey != "") {
				curChoices["26_TRANSFER"] = {
					text: "[storage-transfer]",
					color: this._itemColor
				};
			} else if (item.commodity != "") {
				curChoices["26_TRANSFER"] = {
					text: "[storage-transfer-cargo]",
					color: this._itemColor
				};
			}
		}
		if (item.inTransit === true) {
			curChoices["35_WAIT"] = {
				text: "[storage-wait]",
				color: this._itemColor
			};
		}
		// can only install items if we're in the same system and at the main station
		if (item.galaxy === galaxyNumber && item.system === system.ID && p.dockedStation.isMainStation === true) {
			if (item.equipmentKey != "") {
				// can we install this item?
				var inst = true;
				var reason = "";
				var curr = p.equipmentStatus(item.equipmentKey);

				if (p.canAwardEquipment(item.equipmentKey) === false) {
					inst = false;
					reason = " (required equipment item not installed or item not permitted)";
				}
				// special case for shield boosters
				//if (item.equipmentKey === "EQ_NAVAL_SHIELD_BOOSTER" && p.equipmentStatus("EQ_SHIELD_BOOSTER") != "EQUIPMENT_OK") {inst = false; reason = " (required equipment item not installed)";}

				// if we can't carry multiple versions of it, and we already have one, then no
				if (curr === "EQUIPMENT_OK" && equip.canCarryMultiple === false) {
					inst = false;
					reason = " (already installed)";
				}
				// reset for laser items
				if (item.equipmentKey.indexOf("EQ_WEAPON") >= 0) {
					inst = true;
					reason = "";
				}
				// if the current item of the same time is damaged, and so's the one in storage, then nodeName
				// (otherwise it can be used to fix the broken one)
				if (curr === "EQUIPMENT_DAMAGED" && item.status === "EQUIPMENT_DAMAGED") {
					inst = false;
					reason = " (both items damaged)";
				}
				// do we have room for it, or will it put the ship into overload?
				var eq = EquipmentInfo.infoForKey(item.equipmentKey);
				if (eq.requiredCargoSpace > 0 && p.cargoSpaceAvailable < eq.requiredCargoSpace) {
					inst = false;
					reason = " (insufficient cargo space)";
				}
				var lookup = null;
				if (this._sc_core) lookup = this._sc_core._equipmentMatrix[item.equipmentKey];
				if (lookup) {
					var equipSpace = this._sc_core.$availableEquipmentSpace(p);
					// subtract the space the existing item is taking up
					if (curr === "EQUIPMENT_OK" || curr === "EQUIPMENT_DAMAGED") equipSpace += lookup[0];
					if (lookup[0] > equipSpace) {
						inst = false;
						reason = " (insufficient equipment space)";
					}
				}

				if (inst === true) {
					curChoices["30_INSTALL"] = {
						text: "[storage-install]",
						color: this._itemColor
					};
				} else {
					curChoices["30_INSTALL"] = {
						text: expandDescription("[storage-install]" + reason),
						color: this._disabledColor,
						unselectable: true
					};
				}
			}
			if (item.commodity != "") {
				// can we transfer any of this cargo into our hold
				var inst = true;
				var reason = "";
				if (p.cargoSpaceAvailable === 0) {
					inst = false;
					reason = " (insufficient cargo space)";
				}
				if (inst === true) {
					curChoices["31_LOAD"] = {
						text: "[storage-load-cargo]",
						color: this._itemColor
					};
				} else {
					curChoices["31_LOAD"] = {
						text: expandDescription("[storage-load-cargo]" + reason),
						color: this._disabledColor,
						unselectable: true
					};
				}
			}
		}

		// work out item cost
		if (item.equipmentKey != "") {
			if (item.inTransit === false && equip.techLevel <= 14 && item.galaxy === galaxyNumber) {
				var sellcost = equip.price / 10 * (item.status === "EQUIPMENT_DAMAGED" ? 0.5 : (item.equipmentKey.indexOf("EQ_WEAPON") >= 0 ? 1 : this._refundPct)); // * factor;
				curChoices["70_SELL"] = {
					text: expandDescription("[storage-sell]", {
						cost: formatCredits(sellcost, true, true)
					}),
					color: this._itemColor
				};
			}
		}
		if (item.commodity != "") {
			// can only sell stored cargo when you're in the same system
			if (item.inTransit === false && item.galaxy === galaxyNumber) {
				var pcsfee = 1;
				var maxqty = item.weight;
				var sellcost = 0;
				var price = 0;
				if (item.system === system.ID) {
					if ((item.weight + system.mainStation.market[item.commodity].quantity) > 127) {
						maxqty = 127 - system.mainStation.market[item.commodity].quantity;
					}
					// 10% price penalty for selling remotely
					if (p.dockedStation.isMainStation === false) pcsfee = 0.9;
					price = (system.mainStation.market[item.commodity].price / 10) * pcsfee;

				} else {
					if (item.weight + Math.floor(system.scrambledPseudoRandomNumber(77) * 127) > 127) {
						maxqty = 127 - Math.floor(system.scrambledPseudoRandomNumber(77) * 127);
					}
					// price will be 20% less than the sample price
					pcsfee = 0.8;
					price = this.$getSamplePrice(item.system, item.commodity) * pcsfee;
				}
				// only show the sell option if we can sell something
				if (maxqty > 0) {
					sellcost = price * maxqty;
					curChoices["71_SELL"] = {
						text: expandDescription("[storage-sell]", {
							cost: maxqty + this.$getCommodityUnit(item.commodity) + (item.extra != "" ? "*" : "") + " @ " + price.toFixed(1) + 
							", total " + formatCredits(sellcost, true, true) + 
							(pcsfee < 1 ? " incl " + ((1 - pcsfee) * 100).toFixed(0) + "% process fee" : "")
						}),
						color: this._itemColor
					};
				} else {
					curChoices["71_SELL"] = {
						text: "[storage-nosell]",
						color: this._disabledColor,
						unselectable: true
					};
				}
			}
		}
		// give player the option of dumping stuff in other galaxies (you're unable to do business transactions from different sectors)
		if (item.galaxy != galaxyNumber) {
			curChoices["80_DUMP"] = {
				text: "[storage-dump]",
				color: this._itemColor
			};
		}
		curChoices["98_EXIT"] = {
			text: "[storage-return]",
			color: this._itemColor
		};

		var def = "98_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];

		var opts = {
			screenID: "oolite-storage-item-map",
			title: "Storage - " + (item.inTransit === true ? "In Transit" : item.systemName),
			allowInterrupt: true,
			overlay: {
				name: "equipstorage.png",
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

	// show short range map to location
	if (this._display === 2) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];

		// hold the player's destination
		this._suspendedDestination = p.targetSystem;
		// override it for the display
		p.targetSystem = item.system;

		var bg = "SHORT_RANGE_CHART";
		curChoices["20_RETURN"] = {
			text: "[storage-return]",
			color: this._itemColor
		};
		if (item.system != system.ID && item.system >= 0 && this.$playerTargetSystem() != item.system && p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
			curChoices["96_SETCOURSE~" + item.system] = {
				text: "Set course for " + item.systemName,
				color: this._itemColor
			};
		}

		curChoices["98_EXIT"] = {
			text: "[storage-return]",
			color: this._itemColor
		};

		def = "20_RETURN";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];

		var opts = {
			screenID: "oolite-storage-shortrangechart-map",
			title: "Storage - Chart",
			backgroundSpecial: bg,
			allowInterrupt: true,
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

	// show long range map to location
	if (this._display === 3) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];

		// hold the player's destination
		this._suspendedDestination = p.targetSystem;
		// override it for the display
		p.targetSystem = item.system;

		var bg = "LONG_RANGE_CHART";
		if (p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) bg = "LONG_RANGE_CHART_SHORTEST";

		curChoices["20_RETURN"] = {
			text: "[storage-return]",
			color: this._itemColor
		};
		if (item.system != system.ID && item.system >= 0 && this.$playerTargetSystem() != item.system && p.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
			curChoices["96_SETCOURSE~" + item.system] = {
				text: "Set course for " + item.systemName,
				color: this._itemColor
			};
		}

		curChoices["98_EXIT"] = {
			text: "[storage-return]",
			color: this._itemColor
		};

		def = "20_RETURN";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];

		var opts = {
			screenID: "oolite-storage-longrangechart-map",
			title: "Storage - Chart",
			backgroundSpecial: bg,
			allowInterrupt: true,
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

	// request transfer
	if (this._display === 4) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		if (item.equipmentKey != "") {
			var equip = EquipmentInfo.infoForKey(item.equipmentKey);
		}

		var info = null;
		var dist = 0;
		var jumps = 0;
		var danger = 0;
		var illegal = 0;
		var time = 0;
		if (item.galaxy === galaxyNumber && item.system != system.ID && item.system >= 0) {
			this.$revealMap();
			info = System.infoForSystem(galaxyNumber, item.system);
			var route = system.info.routeToSystem(info);
			jumps = route.route.length;
			dist = route.distance;
			danger = this.$checkRouteDangerLevel(route);
			if (item.commodity != "") illegal = this.$checkRouteIllegalLevel(route, item.commodity);
			time = route.time;
			this.$restoreMap();
		}

		if (item.equipmentKey != "") {
			text = this.$padTextRight("Equipment item:", 10) + (item.equipmentKey === "EQ_PASSENGER_BERTH" ? "Passenger Berth" : equip.name) + "\n";
			text += this.$padTextRight("Equipment status:", 10) + (item.status === "EQUIPMENT_OK" ? "OK" : "Damaged") + "\n";
			text += this.$padTextRight("Space/weight:", 10) + (item.space === 0 ? item.weight : item.space) + "t/" + item.weight + "t\n";
			text += this.$padTextRight("Storage cost:", 10) + formatCredits(this.$calculateStorageCost(item.space, item.weight), true, true) + " per day\n";
		}
		if (item.commodity != "") {
			text = this.$padTextRight("Cargo type:", 10) + displayNameForCommodity(item.commodity) + "\n";
			var spcwt = this.$cargoSpaceRequired(this.$getCommodityUnit(item.commodity), item.weight);
			text += this.$padTextRight("Space/weight:", 10) + spcwt + "t/" + item.weight + this.$getCommodityUnit(item.commodity) + "\n";
			text += this.$padTextRight("Storage cost:", 10) + formatCredits(this.$calculateStorageCost(item.space, item.weight, this.$getCommodityUnit(item.commodity)), true, true) + " per day\n";
		}
		if (item.system != -1) {
			text += this.$padTextRight("Location:", 10) +
				item.systemName + " " + (info != null ? "(dist: " + dist.toFixed(1) + "ly)" : (item.galaxy != galaxyNumber ? "(G" + (item.galaxy + 1) + ")" : "")) + "\n";
		} else {
			text += this.$padTextRight("Location:", 10) + "In transit\n";
			var eta = this.$getTimeRemaining(item.transitETA);
			text += this.$padTextRight("ETA:", 10) + System.systemNameForID(item.transitDestination) + (eta === "Overdue" ? " - delivery overdue" : " in " + eta) + "\n";
		}
		if (dist > 0) {
			var tfrcost = this.$calculateTransferCost(dist, jumps, danger, illegal);
			text += this.$padTextRight("Transfer cost:", 10) + formatCredits(tfrcost, true, true) + "\n";
			text += this.$padTextRight("Transfer time:", 10) + time.toFixed(1) + " hrs\n";
			text += this.$padTextRight("Transfer risk:", 10) + expandDescription("[transfer-danger-" + danger + "]") + "\n";
		}

		if (item.commodity != "" && illegal > 0) {
			text += "\n\nNote: The transfer cost includes premiums for bribing officials.";
		}
		text += "\n\nDo you want to wait here for this item to arrive?";

		curChoices["40_YES"] = {
			text: "Yes",
			color: this._itemColor
		};
		curChoices["41_NO"] = {
			text: "No",
			color: this._itemColor
		};
		curChoices["42_CANCEL"] = {
			text: "Cancel transfer",
			color: this._itemColor
		};
		def = "42_CANCEL";

		var opts = {
			screenID: "oolite-storage-item-map",
			title: "Storage - " + item.systemName,
			allowInterrupt: true,
			overlay: {
				name: "equipstorage.png",
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

	// transfer cargo to storage - select commodity
	if (this._display === 5) {
		var cargo = p.manifest.list;
		var itemcount = 1;
		var sdm = worldScripts.Smugglers_DockMaster;
		var gcm = worldScripts.GalCopBB_Missions;

		var notAllowed = ["slaves", "firearms", "narcotics"];

		text = "Select the cargo you want to transfer into storage:\n";
		if (this.$storageSystemCount(galaxyNumber, system.ID) === 0) {
			text += "(Note: " + formatCredits(this._establishmentFee, false, true) + " will be charged to establish account)\n";
			itemcount += 1;
		}
		for (var i = 0; i < cargo.length; i++) {
			// make sure we don't store relabelled cargo
			var q = cargo[i].quantity;
			if (sdm && sdm._cargoLabelled.length > 0) {
				for (var j = 0; j < sdm._cargoLabelled.length; j++) {
					if (sdm._cargoLabelled[j].newCommodity === cargo[i].commodity) q -= sdm._cargoLabelled[j].quantity;
				}
			}
			// make sure we're not storing something required for a mission that would stop that mission from working.
			if (gcm) {
				var restr = gcm.$cargoRestricted(cargo[i].commodity);
				q -= restr;
			}
			if (q > 0) {
				var unselect = false;
				if (notAllowed.indexOf(cargo[i].commodity) >= 0) unselect = true
				curChoices["50_" + (i < 10 ? "0" : "") + i + "_CARGO~" + cargo[i].commodity] = {
					text: cargo[i].displayName + " (" + q + cargo[i].unit + ")" + (this.$calcCTEcargo(cargo[i].commodity) > 0 ? "*" : "") +
						(unselect === true ? " - Not permitted" : ""),
					color: (unselect === false ? this._menuColor : this._disabledColor),
					unselectable: unselect
				};
				itemcount += 1;
			}
		}

		for (var i = 0; i < (25 - itemcount); i++) {
			curChoices["97_SPACER_" + i] = "";
		}
		curChoices["98_EXIT"] = {
			text: "[storage-return]",
			color: this._itemColor
		};

		var def = "98_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];

		var opts = {
			screenID: "oolite-storage-cargo-map",
			title: "Storage - Cargo",
			allowInterrupt: true,
			overlay: {
				name: "equipstorage.png",
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};

	}

	// how much of the cargo should be transferred
	if (this._display === 6) {
		// either from storage (tfrType = 1) or ships hold (tfrType = 0)
		this.$getTransferAmount();
		return;
	}

	// confirm dumping
	if (this._display === 7) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		var itmText = "";
		if (item.equipmentKey != "") {
			var equip = EquipmentInfo.infoForKey(item.equipmentKey);
			itmText = " - " + (item.equipmentKey === "EQ_PASSENGER_BERTH" ? "Passenger Berth" : equip.name);
		}
		if (item.commodity != "") {
			itmText = " - " + item.weight + this.$getCommodityUnit(item.commodity) + " × " + displayNameForCommodity(item.commodity);
		}

		text = expandDescription("[storage-dump-confirm]", {
			item: itmText
		});

		curChoices["81_YES"] = {
			text: "Yes",
			color: this._itemColor
		};
		curChoices["82_NO"] = {
			text: "No",
			color: this._itemColor
		};

		var def = "82_NO";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];

		var opts = {
			screenID: "oolite-storage-cargo-map",
			title: "Storage - Dump Item",
			allowInterrupt: true,
			overlay: {
				name: "equipstorage.png",
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

	// install laser
	if (this._display === 8) {
		var itemcount = 3;
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		if (item.equipmentKey != "") {
			var equip = EquipmentInfo.infoForKey(item.equipmentKey);
		}
		text = "Install: " + equip.name + "\n\nSelect the position into which you want to install this weapon:\n";

		var lmss = worldScripts.LMSS_Core;
		var section = ["Forward", "Aft", "Port", "Starboard", "Forward - Secondary", "Aft - Secondary", "Port - Secondary", "Starboard - Secondary"];

		for (var i = 0; i < 7; i++) {
			var curr = "X";
			switch (i) {
				case 0:
					if (" 1 3 5 7 9 11 13 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						curr = p.forwardWeapon.equipmentKey;
					break;
				case 1:
					if (" 2 3 6 7 10 11 14 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						curr = p.aftWeapon.equipmentKey;
					break;
				case 2:
					if (" 4 5 6 7 12 13 14 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						curr = p.portWeapon.equipmentKey;
					break;
				case 3:
					if (" 8 9 10 11 12 13 14 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						curr = p.starboardWeapon.equipmentKey;
					break;
				case 4:
					if (" 1 3 5 7 9 11 13 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						if (lmss && p.equipmentStatus("EQ_LMSS_FRONT") === "EQUIPMENT_OK") curr = lmss._forwardAltKey;
					break;
				case 5:
					if (" 2 3 6 7 10 11 14 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						if (lmss && p.equipmentStatus("EQ_LMSS_AFT") === "EQUIPMENT_OK") curr = lmss._aftAltKey;
					break;
				case 6:
					if (" 4 5 6 7 12 13 14 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						if (lmss && p.equipmentStatus("EQ_LMSS_PORT") === "EQUIPMENT_OK") curr = lmss._portAltKey;
					break;
				case 7:
					if (" 8 9 10 11 12 13 14 15 ".indexOf(" " + p.weaponFacings.toString() + " ") >= 0)
						if (lmss && p.equipmentStatus("EQ_LMSS_STARBOARD") === "EQUIPMENT_OK") curr = lmss._starboardAltKey;
					break;
			}
			if (curr !== "X") {
				itemcount += 1;
				if (curr === "" || curr === "EQ_WEAPON_NONE") {
					curChoices[(60 + i) + "_WEAPON~" + item.equipmentKey] = {
						text: this.$padTextRight(section[i], 31),
						color: this._menuColor,
						unselectable: false
					};
				} else {
					curChoices[(60 + i) + "_WEAPON~" + item.equipmentKey] = {
						text: this.$padTextRight(section[i], 10) + this.$padTextLeft("Current: " + EquipmentInfo.infoForKey(curr).name, 21),
						color: this._disabledColor,
						unselectable: true
					};
				}
			}
		}
		for (var i = 0; i < (25 - itemcount); i++) {
			curChoices["97_SPACER_" + i] = "";
		}
		curChoices["98_EXIT"] = {
			text: "[storage-return]",
			color: this._itemColor
		};

		var def = "98_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];

		var opts = {
			screenID: "oolite-storage-item-map",
			title: "Storage - Weapon Installation",
			allowInterrupt: true,
			overlay: {
				name: "equipstorage.png",
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}

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

//-------------------------------------------------------------------------------------------------------------
this.$itemStorageScreenHandler = function $itemStorageScreenHandler(choice) {

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

	if (choice == null) return;

	var p = player.ship;
	var stn = p.dockedStation;
	this._lastChoice[this._display] = choice;

	if (choice.indexOf("01_EQUIP") >= 0) {
		this._selectedItem = choice.substring(choice.indexOf("~") + 1);
		this._display = 1;
	}
	if (choice === "20_RETURN") this._display = 1;
	if (choice === "25_SHOWMAP_SHORT") this._display = 2;
	if (choice === "25_SHOWMAP_LONG") this._display = 3;

	if (choice === "26_TRANSFER") this._display = 4;
	if (choice === "35_WAIT") {
		// switch over to the wait option
		choice = "40_YES";
	}
	if (choice === "30_INSTALL") {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		// for laser weapons, we need to check what position it will be loaded into
		if (item.equipmentKey.indexOf("EQ_WEAPON") >= 0) {
			this._display = 8;
		} else {
			var equip = EquipmentInfo.infoForKey(item.equipmentKey);

			this._display = 0;

			// we should only get here if it's possible to install the equipment, so we shouldn't need to do any extra checks
			if (p.equipmentStatus(item.equipmentKey) === "EQUIPMENT_DAMAGED") {
				if (equip.techLevel <= 14) {
					// ask the player if they want to keep the damaged item?
					var curChoices = {};
					var sellcost = equip.price / 10 * 0.5 * this._refundPct;
					curChoices["01_SELL"] = "Sell item (" + formatCredits(sellcost, true, true) + ")";
					curChoices["02_STORE"] = "Store item";
					var opts = {
						screenID: "oolite-storage-swapout-map",
						title: "Storage - " + item.systemName,
						allowInterrupt: false,
						overlay: {
							name: "equipstorage.png",
							height: 546
						},
						exitScreen: "GUI_SCREEN_INTERFACES",
						choices: curChoices,
						message: expandDescription("[storage-sellorstore]"),
					};
					mission.runScreen(opts, this.$sellOrStoreDamaged, this);
					return;
				} else {
					// no choice but to store the item, so swap it out
					p.setEquipmentStatus(item.equipmentKey, "EQUIPMENT_OK");
					item.status = "EQUIPMENT_DAMAGED";
					player.consoleMessage("Damaged item replaced with working item, damaged item stored.");
				}
			} else {
				if (p.awardEquipment(item.equipmentKey) === true) {
					if (item.status === "EQUIPMENT_DAMAGED") p.setEquipmentStatus(item.equipmentKey, "EQUIPMENT_DAMAGED");
					player.consoleMessage("Item installed from storage.");
					if (item.equipmentKey.indexOf("EQ_LMSS") >= 0) {
						worldScripts.LMSS_Core.playerBoughtEquipment(item.equipmentKey);
					}
					this._storage.splice(itemIndex, 1);
				} else {
					player.consoleMessage("!ALERT! Unable to install equipment.");
					this._display = 1;
				}
			}
		}
	}
	if (choice === "31_LOAD") {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		this._tfrCargo = item.commodity;
		this._maxCargo = item.weight;
		this._tfrExtra = item.extra.split(";").length.toString();

		// how much of this can the player ship handle?
		var orig = p.manifest[this._tfrCargo];
		p.manifest[this._tfrCargo] += this._maxCargo;
		// check that the hold actually took all the amount
		if (p.manifest[this._tfrCargo] != (orig + this._maxCargo)) {
			// if not, adjust the amount of transfer
			this._maxCargo = p.manifest[this._tfrCargo] - orig;
		}
		p.manifest[this._tfrCargo] = orig;

		this._tfrType = 1; // transfer to cargo hold
		this._display = 6; // ask player how much cargo to transfer
	}
	if (choice === "42_CANCEL") this._display = 1;
	if (choice === "40_YES") {
		// we're waiting, so more the equipment directly into local storage and add the required time
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		var equip = EquipmentInfo.infoForKey(item.equipmentKey);

		this.$revealMap();
		var danger = 0;
		var time = 0;
		var info = System.infoForSystem(galaxyNumber, (item.inTransit === true ? item.transitSystem : item.system));
		var route = system.info.routeToSystem(info);
		var jumps = route.route.length;
		var dist = route.distance;
		var danger = this.$checkRouteDangerLevel(route);
		var illegal = (item.commodity != "" ? this.$checkRouteIllegalLevel(route, item.commodity) : 0);
		var time = route.time * 3600 + (Math.floor(Math.random() * 4) * 3600);

		var tfrcost = this.$calculateTransferCost(dist, jumps, danger, illegal);
		player.credits -= parseInt(tfrcost * 10) / 10;
		player.consoleMessage("Transfer request initiated. Your account has been debited " + formatCredits(tfrcost, true, true));

		var destroyed = false;
		// work out if the item arrives safely
		for (var i = 0; i < route.route.length; i++) {
			var sys = System.infoForSystem(galaxyNumber, route.route[i]);
			if (Math.random() > ((sys.government + 1) / 9) && Math.random() > ((sys.government + 1) / 9) && Math.random() > ((sys.government + 1) / 9)) {
				destroyed = true;
				break;
			}
		}
		if (destroyed === false && item.commodity != "") {
			// chance of item being confiscated
			var chance = 0;
			for (var i = 0; i < illegal; i++) {
				chance += Math.random();
			}
			var caught = (chance < (illegal * 0.2) ? false : true);
		}
		this.$restoreMap();
		// add the transit time to the current time
		clock.addSeconds(time);
		this._display = 0;

		var info = null;
		var route = null;

		if (destroyed === true) {
			// bad luck, pal
			var sellcost = 0;
			var extra = "";
			if (equip) {
				if (equip.techLevel <= 14) {
					sellcost = (equip.price / 10 * (item.status === "EQUIPMENT_DAMAGED" ? 0.5 : 1) * 0.1);
					extra = ", and we hope this insurance payment (of approximately 10% of the equipment value) will go some way towards alleviating the pain";
				}
			} else if (item.commodity != "") {
				// 10% of cargo value
				sellcost = Math.floor(((system.mainStation.manifest[item.commodity].price / 10) * item.weight) * 0.1);
				extra = ", and we hope this insurance payment (of approximately 10% of the cargo value) will go some towards alleviating the pain";
			}
			var storeName = expandDescription(this._compNames[item.system % 16]);
			mission.runScreen({
				screenID: "oolite-storage-destroyed-map",
				title: "Storage - Transfer",
				overlay: {
					name: "equipstorage.png",
					height: 546
				},
				allowInterrupt: false,
				message: (equip ? expandDescription("[storage-transfer-destroyed]", {
					extra: extra,
					storagename: storeName
				}) : expandDescription("[storage-transfer-destroyed-cargo]", {
					extra: extra,
					storagename: storeName
				})),
				exitScreen: "GUI_SCREEN_INTERFACES"
			}, this.$transferFailed, this);

			// give player a consolation refund of 10% of item cost
			if (sellcost > 0) {
				player.credits += sellcost;
				player.consoleMessage("You have been refunded " + formatCredits(sellcost, true, true));
			}

			// remove the item from the storage array
			this._storage.splice(itemIndex, 1);
			return;

		} else if (caught === true) {
			var storeName = expandDescription(this._compNames[item.system % 16]);
			mission.runScreen({
				screenID: "oolite-storage-caught-map",
				title: "Storage - Transfer",
				overlay: {
					name: "equipstorage.png",
					height: 546
				},
				allowInterrupt: false,
				message: expandDescription("[storage-transfer-caught]", {
					storagename: storeName
				}),
				exitScreen: "GUI_SCREEN_INTERFACES"
			}, this.$transferFailed, this);

			this._storage.splice(itemIndex, 1);
			return;
		} else {
			// otherwise just move the item to be local
			item.system = system.ID;
			item.systemName = system.name;
		}
	}
	if (choice === "41_NO") {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];

		var info = System.infoForSystem(galaxyNumber, item.system);
		var route = system.info.routeToSystem(info);
		var jumps = route.route.length;
		var dist = route.distance;
		var danger = this.$checkRouteDangerLevel(route);
		var illegal = (item.commodity != "" ? this.$checkRouteIllegalLevel(route, item.commodity) : 0);

		var tfrcost = this.$calculateTransferCost(dist, jumps, danger, illegal);
		player.credits -= parseInt(tfrcost * 10) / 10;
		player.consoleMessage("Transfer request initiated. Your account has been debited " + formatCredits(tfrcost, true, true) + ".");

		item.inTransit = true;
		item.transitDestination = system.ID;
		item.transitDestinationName = system.name;
		item.transitSystem = item.system;
		item.transitStart = clock.adjustedSeconds;
		item.system = -1;
		item.transitETA = clock.adjustedSeconds + (route.time * 3600) + 7200;

		this._display = 0;
	}
	if (choice.indexOf("50_") >= 0) {
		this._tfrCargo = choice.substring(choice.indexOf("~") + 1);
		this._maxCargo = p.manifest[this._tfrCargo];
		this._tfrType = 0; // transfer to storage
		this._tfrExtra = this.$calcCTEcargo(this._tfrCargo).toString();
		this._display = 6;
	}
	if (choice.match(/6\d\_WEAPON/g)) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];
		var equip = choice.substring(choice.indexOf("~") + 1);
		var section = choice.substring(1, 2);
		// note: because of the way I interfaced LMSS with redspear's "NewLasers" OXP, you will need to have the LMSS OXP in order to install most New Lasers
		var lmss = worldScripts.LMSS_Core;
		if (lmss) lmss._switching = true;
		var bl = worldScripts.BrakableLasers_Main;
		// we will only allow installation into an empty position, so there is no need to do a remove/re-store
		switch (section) {
			case "0":
				if (item.status == "EQUIPMENT_DAMAGED") {
					p.forwardWeapon = "EQ_WEAPON_DAMAGED_LASER";
					bl._holdDamage["FORWARD"].primary = equip;
					bl.$updateManifest();
				} else {
					p.forwardWeapon = equip;
				}
				break;
			case "1":
				if (item.status == "EQUIPMENT_DAMAGED") {
					p.forwardWeapon = "EQ_WEAPON_DAMAGED_LASER";
					bl._holdDamage["AFT"].primary = equip;
					bl.$updateManifest();
				} else {
					p.aftWeapon = equip;
				}
				break;
			case "2":
				if (item.status == "EQUIPMENT_DAMAGED") {
					p.forwardWeapon = "EQ_WEAPON_DAMAGED_LASER";
					bl._holdDamage["PORT"].primary = equip;
					bl.$updateManifest();
				} else {
					p.portWeapon = equip;
				}
				break;
			case "3":
				if (item.status == "EQUIPMENT_DAMAGED") {
					p.forwardWeapon = "EQ_WEAPON_DAMAGED_LASER";
					bl._holdDamage["STARBOARD"].primary = equip;
					bl.$updateManifest();
				} else {
					p.starboardWeapon = equip;
				}
				break;
			case "4":
				if (lmss) {
					if (item.status == "EQUIPMENT_DAMAGED") {
						lmss._forwardAltKey = "EQ_WEAPON_DAMAGED_LASER";
						bl._holdDamage["FORWARD"].secondary = equip;
						bl.$updateManifest();
					} else {
						lmss._forwardAltKey = equip;
					}
					p.awardEquipment("EQ_LMSS_FRONT_WEAPON");
				}
				break;
			case "5":
				if (lmss) {
					if (item.status == "EQUIPMENT_DAMAGED") {
						lmss._aftAltKey = "EQ_WEAPON_DAMAGED_LASER";
						bl._holdDamage["AFT"].secondary = equip;
						bl.$updateManifest();
					} else {
						lmss._aftAltKey = equip;
					}
					p.awardEquipment("EQ_LMSS_AFT_WEAPON");
				}
				break;
			case "6":
				if (lmss) {
					if (item.status == "EQUIPMENT_DAMAGED") {
						lmss._portAltKey = "EQ_WEAPON_DAMAGED_LASER";
						bl._holdDamage["PORT"].secondary = equip;
						bl.$updateManifest();
					} else {
						lmss._portAltKey = equip;
					}
					p.awardEquipment("EQ_LMSS_PORT_WEAPON");
				}
				break;
			case "7":
				if (lmss) {
					if (item.status == "EQUIPMENT_DAMAGED") {
						lmss._starboardAltKey = "EQ_WEAPON_DAMAGED_LASER";
						bl._holdDamage["STARBOARD"].secondary = equip;
						bl.$updateManifest();
					} else {
						lmss._starboardAltKey = equip;
					}
					p.awardEquipment("EQ_LMSS_STARBOARD_WEAPON");
				}
				break;
		}
		if (lmss) {
			lmss._switching = false;
			lmss.$updateManifest();
		}
		this._storage.splice(itemIndex, 1);
		this._display = 0;
	}
	if (choice === "70_SELL") {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		this.$sellEquipment(itemIndex);
		this._display = 0;
	}
	if (choice === "71_SELL") {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		// ask how much to sell
		this._tfrCargo = this._storage[itemIndex].commodity;
		this._maxCargo = this._storage[itemIndex].weight;
		this._tfrExtra = this._storage[itemIndex].extra.split(";").length.toString();
		if (this._storage[itemIndex].system != system.ID) {
			if (this._maxCargo + (system.scrambledPseudoRandomNumber(77) * 127) > 127) {
				this._maxCargo = 127 - (system.scrambledPseudoRandomNumber(77) * 127);
			}
		} else {
			if ((this._maxCargo + system.mainStation.market[this._tfrCargo].quantity) > 127) {
				this._maxCargo = 127 - system.mainStation.market[this._tfrCargo].quantity;
			}
		}
		this._tfrType = 2;
		this._display = 6;
	}
	if (choice === "80_DUMP") this._display = 7;
	if (choice === "81_YES") {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		this._storage.splice(itemIndex, 1);
		player.consoleMessage("Item was successfully dumped.");
		this._display = 0;
	}
	if (choice === "82_NO") this._display = 1;
	if (choice.indexOf("96_SETCOURSE") >= 0) {
		var dest = parseInt(choice.substring(choice.indexOf("~") + 1));
		if (dest >= 0 && dest <= 255) {
			player.ship.targetSystem = dest;
			player.ship.infoSystem = player.ship.targetSystem;
			player.consoleMessage("Course set for " + System.systemNameForID(dest));
		}
	}
	if (choice === "96_Z_STOREEQUIP") {
		this.$storeEquipment();
		return;
	}
	if (choice === "95_NEXT") this._page += 1;
	if (choice === "96_PREV") this._page -= 1;
	if (choice === "97_SHOWLOCAL") {
		this._displayType = 0;
		this._lastChoice[this._display] = "97_SHOWALL";
	}
	if (choice === "97_SHOWALL") {
		this._displayType = 1;
		this._lastChoice[this._display] = "97_SHOWLOCAL";
	}
	if (choice === "98_MOVECARGO") this._display = 5;
	if (choice === "98_EXIT") this._display = 0;

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

//-------------------------------------------------------------------------------------------------------------
// prompts the user for the transfer amount
this.$getTransferAmount = function $getTransferAmount(type) {

	var text = "";

	// don't show a screen when only 1 unit is possible
	if (this._maxCargo === 1) {
		switch (this._tfrType) {
			case 0:
				this.$getCargoTransferIn(1);
				return;
			case 1:
				this.$getCargoTransferOut(1);
				return;
			case 2:
				this.$getCargoToSell(1);
				return;
		}
	}

	switch (this._tfrType) {
		case 0:
			text = expandDescription("[enter-quantity-storage]", {
				commodity: displayNameForCommodity(this._tfrCargo),
				quantity: this._maxCargo,
				cte_note: (this._tfrExtra != "" ? "\n\nNote: " + this._tfrExtra + " units of this cargo is from the specialty market." : "")
			});
			break;
		case 1:
			text = expandDescription("[enter-quantity-hold]", {
				commodity: displayNameForCommodity(this._tfrCargo),
				quantity: this._maxCargo,
				cte_note: (this._tfrExtra != "" ? "\n\nNote: " + this._tfrExtra + " units of this cargo is from the specialty market." : "")
			});
			break;
		case 2:
			text = expandDescription("[enter-quantity-sell]", {
				commodity: displayNameForCommodity(this._tfrCargo),
				quantity: this._maxCargo,
				cte_note: (this._tfrExtra != "" ? "\n\nNote: " + this._tfrExtra + " units of this cargo is from the specialty market." : "")
			});
			break;
	}

	var opts = {
		screenID: "oolite-storage-transfer-map",
		title: "Storage - " + displayNameForCommodity(this._tfrCargo),
		allowInterrupt: false,
		overlay: {
			name: "equipstorage.png",
			height: 546
		},
		exitScreen: "GUI_SCREEN_INTERFACES",
		message: text,
		textEntry: true
	};

	switch (this._tfrType) {
		case 0:
			mission.runScreen(opts, this.$getCargoTransferIn, true);
			break;
		case 1:
			mission.runScreen(opts, this.$getCargoTransferOut, true);
			break;
		case 2:
			mission.runScreen(opts, this.$getCargoToSell, true);
			break;
	}
}

//-------------------------------------------------------------------------------------------------------------
// parses the input from the getTransferAmount screen - for transferring cargo to storage
this.$getCargoTransferIn = function $getCargoTransferIn(param) {
	if (!param) {
		this._display = 0;
		this.$viewStoredItems();
		return;
	}
	if (parseInt(param) >= 1 && parseInt(param) <= this._maxCargo) {
		var amount = parseInt(param);

		var cte = worldScripts.CargoTypeExtension;
		var extra = "";
		if (cte) {
			extra = this.$extractCargoFromCTE(this._tfrCargo, amount, this._maxCargo);
		}

		var idx = this.$countItemAtStation(this._tfrCargo);
		if (idx === 0) {
			// charge establishment fee (if required)
			if (this.$storageSystemCount(galaxyNumber, system.ID) === 0) {
				player.credits -= this._establishmentFee;
				player.consoleMessage(formatCredits(this._establishmentFee, false, true) + " deducted to open storage facility account", 5);
			}
			var spcwt = this.$cargoSpaceRequired(this.$getCommodityUnit(this._tfrCargo), amount);

			this._storage.push({
				galaxy: galaxyNumber,
				system: system.ID,
				systemName: system.name,
				equipmentKey: "",
				commodity: this._tfrCargo,
				space: spcwt,
				weight: amount,
				status: "",
				index: 0,
				inTransit: false,
				transitETA: 0,
				transitDestination: -1,
				transitSystem: -1,
				transitStart: 0,
				extra: extra
			});
		} else {
			idx = this.$getStorageIndex(galaxyNumber + "|" + system.ID + "|" + this._tfrCargo + "|0");
			this._storage[idx].weight += amount;
			var spcwt = this.$cargoSpaceRequired(this.$getCommodityUnit(this._tfrCargo), this._storage[idx].weight);
			this._storage[idx].space = spcwt;
			this._storage[idx].extra += (this._storage[idx].extra == "" ? "" : ";") + extra;
		}

		// take the cargo out of the hold
		var p = player.ship;
		p.manifest[this._tfrCargo] -= amount;
	}
	this._display = 0;
	this.$viewStoredItems();
}

//-------------------------------------------------------------------------------------------------------------
this.$calcCTEcargo = function (generic) {
	var cte = worldScripts.CargoTypeExtension;
    if (!cte) return 0;
	// if there isn't enough generic cargo of a particular type
	// throw out special cargo entries of that type until there is
	var totalspecial = 0;
	for (var j = 0; j < cte.specialCargoCarried[generic].length; j++) {
		if (cte.specialCargoRegister[cte.specialCargoCarried[generic][j].type]) {
			totalspecial += cte.specialCargoCarried[generic][j].quantity;
		}
	}
	return totalspecial;
}

//-------------------------------------------------------------------------------------------------------------
// parses the input from the getTransferAmount screen - for transferring cargo to hold
this.$getCargoTransferOut = function $getCargoTransferOut(param) {
	if (!param) {
		this._display = 1;
		this.$viewStoredItems();
		return;
	}
	if (parseInt(param) >= 1 && parseInt(param) <= this._maxCargo) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);

		var p = player.ship;
		var amount = parseInt(param);
		var starting = p.manifest[this._tfrCargo];
		// put the cargo into the main hold
		p.manifest[this._tfrCargo] += amount;
		// check that the hold actually took all the amount
		if (p.manifest[this._tfrCargo] != (starting + amount)) {
			// if not, adjust the amount of transfer
			amount = p.manifest[this._tfrCargo] - starting;
		}
		// interface with hermitage cargo
		//if (worldScripts.Hermitage_Cargo) {
		//	worldScripts.Hermitage_Cargo.$addCargoToData(this._tfrCargo, amount, p.dockedStation.equivalentTechLevel, system.info.economy);
		//}

		var count = 0;
		var ncmanifest = this.$checkForAndRemoveCTECargo(this._tfrCargo, amount);
		if (ncmanifest && this._NCInstalled) {
			var cte = worldScripts.CargoTypeExtension;
			cte.mergePlayerManifest(ncmanifest);
		}
		this._storage[itemIndex].weight -= amount;

		if (this._storage[itemIndex].weight === 0) {
			this._storage.splice(itemIndex, 1);
			this._display = 0;
			this.$viewStoredItems();
			return;
		}
		this._storage[itemIndex].space = this.$cargoSpaceRequired(this.$getCommodityUnit(this._tfrCargo), this._storage[itemIndex].weight);
	}
	this._display = 1;
	this.$viewStoredItems();
}

//-------------------------------------------------------------------------------------------------------------
// parses the input from the getTransferAmount screen - for selling cargo 
this.$getCargoToSell = function $getCargoToSell(param) {
	if (!param) {
		this._display = 1;
		this.$viewStoredItems();
		return;
	}
	if (parseInt(param) >= 1 && parseInt(param) <= this._maxCargo) {
		var itemIndex = this.$getStorageIndex(this._selectedItem);
		var item = this._storage[itemIndex];

		var amount = parseInt(param);
		var price = 0;
		// give player credits
		if (item.system === system.ID) {
			// 10% price penalty for selling remotely in the same system
			price = (system.mainStation.market[item.commodity].price / 10) * (player.ship.dockedStation.isMainStation ? 1 : 0.9);
			// add cargo to market
			var current = system.mainStation.market[item.commodity].quantity;
			// make sure we don't go over capacity
			var diff = 0;
			if (current + amount > system.mainStation.market[item.commodity].capacity) {
				diff = (current + amount) - system.mainStation.market[item.commodity].capacity;
				amount -= diff;
			}
			system.mainStation.setMarketQuantity(item.commodity, current + amount);
		} else {
			// use the sample price as the means to calc the sale price at a distant system
			// includes a 20% price penalty for selling remotely
			price = this.$getSamplePrice(item.system, item.commodity) * 0.8;
		}

		player.credits += price * amount;
		player.consoleMessage("You have been credited " + formatCredits(price * amount, true, true)) + ".";
		this.$playSound("sell");
		// remove qty from storage
		this._storage[itemIndex].weight -= amount;
		// if we have nothing left, remove the item from the array
		if (this._storage[itemIndex].weight === 0) {
			this._storage.splice(itemIndex, 1);
			this._display = 0;
			this.$viewStoredItems();
			return;
		}
		this._storage[itemIndex].space = this.$cargoSpaceRequired(this.$getCommodityUnit(this._tfrCargo), this._storage[itemIndex].weight);
	}
	this._display = 1;
	this.$viewStoredItems();
}

//-------------------------------------------------------------------------------------------------------------
// extracts an certain amount of a particular commodity from new cargoes
this.$extractCargoFromCTE = function(commodity, amount, max) {
	var cte = worldScripts.CargoTypeExtension;
	var special = this.$calcCTEcargo(commodity);
	var normal = max - special;
	if (amount - normal > 0) {
		// we're transferring some special cargo
		var storage = [];
		var ncmanifest = cte.suspendPlayerManifest(commodity);
		var nc = ncmanifest.split("|");
		var lst = nc[1].split("/");
		var counter = 0;
		
		for (var i = 0; i < lst.length; i++) {
			if (lst[i] != "") {
				var itms = lst[i].split(";");
				for (var j = 0; j < itms.length; j++) {
					if (itms[j] != "") {
						// reduce the original
						var sub = itms[j].split("=");
						var removed = 0;
						while (sub[1] > 0) {
							sub[1]--;
							counter++;
							removed++;
							if ((amount - normal) - counter == 0) break;
						}
						itms[j] = sub.join("=");
						
						//storage.push(sub[0] + "=" + removed + "=" + sub[2]);
						// make a copy of what we're taking out
						// we'll store them a 1 unit items to make the return process easier
						for (var k = 0; k < removed; k++) {
							var st = "";
							for (var m = 0; m < sub.length; m++) {
								st += (st == "" ? "" : "=") + (m == 1 ? 1 : sub[m]);
							}
							storage.push(st);
						}
					}
				}
				lst[i] = itms.join(";");
			}
			if ((amount - normal) - counter == 0) break;
		}

		nc[1] = lst.join("/");
		ncmanifest = nc.join("|");
		// tell NC about what we've done
		cte.mergePlayerManifest(ncmanifest);
		// pull our storage data together
		extra = storage.join(";");
	}
	return extra;
}

//-------------------------------------------------------------------------------------------------------------
this.$checkForAndRemoveCTECargo = function(commodity, quantity) {
	var extra = "";
	for (var i = 0; i < this._sc_Cargo.length; i++) {
		if (this._storage[i].commodity == commodity) {		
			if (this._storage[i].extra != "") {
				var dta = [];
				dta = this._storage[i].extra.split(";");
				var normal = this._storage[i].weight - dta.length;
				if (quantity - normal > 0) {
					var diff = quantity - normal;
					// compile a list of the cargo items to re-merge with nc
					while (diff > 0) {
						extra += (extra == "" ? "" : ";") + dta.shift();
						diff--;
					}
					this._storage[i].extra = dta.join(";");
				}
			}
		}
	}
	// because we store everything as 1 unit items, we need to compile it back into totals so NC has good data
	var hold = this.$compileCTEValues(extra);
	var ncmanifest = "2|" + this.$buildMergeManifest(commodity, hold);
	return ncmanifest;
}

//-------------------------------------------------------------------------------------------------------------
// compile multiple 1 unit items down to one entry with a 1+ unit count
this.$compileCTEValues = function(extra) {
	var items = extra.split(";");
	var list = {};
	for (var i = 0; i < items.length; i++) {
		if (!list[items[i]]) {
			list[items[i]] = 1;
		} else {
			list[items[i]] = list[items[i]] + 1;
		}
	}
	var keys = Object.keys(list);
	var final = "";
	for (var i = 0; i < keys.length; i++) {
		var subs = keys[i].split("=");
		final = (final == "" ? "" : ";") + subs[0] + "=" + list[keys[i]] + "=" + subs[2];
	}
	return final;
}

//-------------------------------------------------------------------------------------------------------------
// put the manifest string together
// blank entries in all but the item we are processing the commodity for
this.$buildMergeManifest = function(commodity, extra) {
	var cte = worldScripts.CargoTypeExtension;
	var goodsList = cte.cargoTypes;
	var serial = "";
	for (var i = 0; i < goodsList.length; i++) {
		if (goodsList[i] == commodity) {
			serial += extra;
		}
		if (i + 1 < goodsList.length) {
			serial += "/";
		}
	}
	return serial;
}

//-------------------------------------------------------------------------------------------------------------
// used to sell a particular item out of the storage array
this.$sellEquipment = function $sellEquipment(itemIndex) {
	var item = this._storage[itemIndex];
	var info = EquipmentInfo.infoForKey(item.equipmentKey);
	var stn = player.ship.dockedStation;

	var sellcost = info.price / 10 * (item.status === "EQUIPMENT_DAMAGED" ? 0.5 : 1) * (item.equipmentKey.indexOf("EQ_WEAPON") >= 0 ? 1 : this._refundPct);
	player.credits += sellcost;

	// give the player an email, if installed
	var ga = worldScripts.GalCopAdminServices;
	if (ga) {
		var email = worldScripts.EmailSystem;
		var cost = "In total you were refunded " + formatCredits(sellcost, true, true) + ".";

		var msg = expandDescription("[storage-sell-email]", {
			storename: expandDescription(this._compNames[item.system % 16]),
			systemname: item.systemName,
			equipname: (item.equipmentKey === "EQ_PASSENGER_BERTH" ? "Passenger Berth" : info.name),
			equipcost: cost
		});

		email.$createEmail({
			sender: expandDescription(this._compNames[item.system % 16]),
			subject: "Removing equipment",
			date: global.clock.adjustedSeconds,
			sentFrom: item.system,
			message: msg,
			expiryDays: ga._defaultExpiryDays
		});
	}

	player.consoleMessage("You have been refunded " + formatCredits(sellcost, true, true));

	this._storage.splice(itemIndex, 1);
}

//-------------------------------------------------------------------------------------------------------------
this.$transferFailed = function $transferFailed(choice) {
	this.$viewStoredItems();
}

//-------------------------------------------------------------------------------------------------------------
this.$sellOrStoreDamaged = function $sellOrStoreDamaged(choice) {

	if (choice == null) return;

	var p = player.ship;
	var itemIndex = this.$getStorageIndex(this._selectedItem);
	var item = this._storage[itemIndex];

	if (choice === "01_SELL") {
		// this is a swap out replacement to a damaged item, and sell the damaged one
		p.setEquipmentStatus(item.equipmentKey, "EQUIPMENT_OK");
		item.status = "EQUIPMENT_DAMAGED";
		player.consoleMessage("Damaged item replaced with working item, damaged item sold.");
		this.$sellEquipment(itemIndex);
	}
	if (choice === "02_STORE") {
		// just change the status of each equipment item
		p.setEquipmentStatus(item.equipmentKey, "EQUIPMENT_OK");
		item.status = "EQUIPMENT_DAMAGED";
		player.consoleMessage("Damaged item replaced with working item, damaged item stored.");
	}

	this.$viewStoredItems();
	return;
}

//-------------------------------------------------------------------------------------------------------------
// calculates the storage cost for a single item of equipment, based on its size/weight profile
this.$calculateStorageCost = function $calculateStorageCost(space, weight, commodityType) {
	var calc = 0;
	if (commodityType != null) {
		space = this.$cargoSpaceRequired(commodityType, weight);
		switch (commodityType) {
			case "kg":
				weight /= 1000;
				break;
			case "g":
				weight /= 1000000;
				break;
		}
	}
	calc = space + (weight > space ? weight - space : 0);
	if (calc <= 0) calc = 0.1;
	return calc;
}

//-------------------------------------------------------------------------------------------------------------
this.$salesRep = function $salesRep() {
	if (this._salesRep === "") {
		this._salesRep = randomName() + " " + randomName();
	}
	return this._salesRep;
}

//-------------------------------------------------------------------------------------------------------------
// calculates the total daily cost of all equipment currently stored throughout the galaxy
this.$calculateTotalDailyCost = function $calculateTotalDailyCost() {
	var cost = 0;
	for (var i = 0; i < this._storage.length; i++) {
		var item = this._storage[i];
		var add = true;
		if (item.equipmentKey != "") {
			var equip = EquipmentInfo.infoForKey(item.equipmentKey);
			if (!equip) add = false;
		}
		if (add === true) cost += this.$calculateStorageCost(item.space, item.weight);
	}
	return cost;
}

//-------------------------------------------------------------------------------------------------------------
// returns the current daily cost for the gal/sys ID combination
this.$calculateSystemCost = function $calculateSystemCost(galaxyNum, systemID) {
	var cost = 0;
	for (var i = 0; i < this._storage.length; i++) {
		var item = this._storage[i];
		if (item.system === systemID && item.galaxy === galaxyNum) {
			var add = true;
			if (item.equipmentKey != "") {
				var equip = EquipmentInfo.infoForKey(item.equipmentKey);
				if (!equip) add = false;
			}
			if (add === true) cost += this.$calculateStorageCost(item.space, item.weight);
		}
	}
	return cost;
}

//-------------------------------------------------------------------------------------------------------------
// returns a dictionary of gal/sys id's with the corresponding name of the system where the player currently has equipment stored
this.$storageSystemList = function $storageSystemList() {
	var data = {};
	for (var i = 0; i < this._storage.length; i++) {
		var item = this._storage[i];
		var add = true;
		if (item.equipmentKey != "") {
			var equip = EquipmentInfo.infoForKey(item.equipmentKey);
			if (!equip) add = false;
		}
		if (add === true) data[item.galaxy + "_" + item.system] = item.systemName;
	}
	return data;
}

//-------------------------------------------------------------------------------------------------------------
// counts the number of equipment items stored at the selected gal/sys 
this.$storageSystemCount = function $storageSystemCount(galaxyNum, systemID) {
	var count = 0;
	for (var i = 0; i < this._storage.length; i++) {
		var item = this._storage[i];
		if (item.system === systemID && item.galaxy === galaxyNum) {
			if (item.equipmentKey != "") {
				var equip = EquipmentInfo.infoForKey(item.equipmentKey);
				if (equip) count += 1;
			}
			if (item.commodity != "") {
				count += item.weight;
			}
		}
	}
	return count;
}

//-------------------------------------------------------------------------------------------------------------
// returns true if the station has equipment storage facilities, otherwise false
this.$stationHasStorageFacilities = function $stationHasStorageFacilities(station) {
	if (station.isMainStation) return true;
	return false;
}

//-------------------------------------------------------------------------------------------------------------
// returns the count of the specific equipment key stored at a station (in case the player decides to store multiples of the same item)
this.$countItemAtStation = function $countItemAtStation(itemKey) {
	var count = 0;
	for (var i = 0; i < this._storage.length; i++) {
		if (this._storage[i].galaxy === galaxyNumber && this._storage[i].system === system.ID) {
			if (this._storage[i].equipmentKey === itemKey) {
				var equip = EquipmentInfo.infoForKey(this._storage[i].equipmentKey);
				if (equip) count += 1;
			}
			if (this._storage[i].commodity === itemKey) {
				count += this._storage[i].weight;
			}
		}
	}
	return count;
}

//-------------------------------------------------------------------------------------------------------------
this.$getStorageIndex = function $getStorageIndex(datakey) {
	var data = datakey.split("|");
	var gal = parseInt(data[0]);
	var sys = parseInt(data[1]);
	var eq = data[2];
	var idx = parseInt(data[3]);

	for (var i = 0; i < this._storage.length; i++) {
		if (this._storage[i].galaxy === gal && this._storage[i].system === sys && (this._storage[i].equipmentKey === eq || this._storage[i].commodity === eq) && this._storage[i].index === idx) return i;
	}
	return -1;
}

//-------------------------------------------------------------------------------------------------------------
// returns a weighted average danger level for a particular route
this.$checkRouteDangerLevel = function $checkRouteDangerLevel(route) {
	var danger = 0;
	var count = 0;

	for (var i = 0; i < route.route.length; i++) {
		var sys = System.infoForSystem(galaxyNumber, route.route[i]);
		danger += (7 - sys.government);
		count += 1;
		if (sys.government === 0) {
			danger += (7 - sys.government);
			count += 1;
		}
		if (sys.government <= 1) {
			danger += (7 - sys.government);
			count += 1;
		}
		if (sys.government <= 2) {
			danger += (7 - sys.government);
			count += 1;
		}
		if (sys.government <= 3) {
			danger += (7 - sys.government);
			count += 1;
		}
		if (sys.government <= 4) {
			danger += (7 - sys.government);
			count += 1;
		}
		if (sys.government <= 5) {
			danger += (7 - sys.government);
			count += 1;
		}
	}
	danger = parseInt(danger / count);
	return danger;
}

//-------------------------------------------------------------------------------------------------------------
// returns a count of the number of systems where this good is illegal
this.$checkRouteIllegalLevel = function $checkRouteIllegalLevel(rt, commodity) {
	var si = worldScripts.Smugglers_Illegal;
	if (!si) return 0;
	var illegal = 0;
	for (var i = 0; i < rt.route.length; i++) {
		if (si.$isGoodIllegal(rt.route[i], commodity) === true) illegal += 1;
	}
	return illegal;
}

//-------------------------------------------------------------------------------------------------------------
// returns the player's target system (1.80) or the next jump to their target system (1.82)
this.$playerTargetSystem = function $playerTargetSystem() {
	if (player.ship.hasOwnProperty("nextSystem")) return player.ship.nextSystem;

	var target = player.ship.targetSystem;

	if (oolite.compareVersion("1.81") < 0 && player.ship.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY") === true) {
		// in 1.81 or greater, the target system could be more than 7 ly away. It becomes, essentially, the final destination.
		// there could be multiple interim stop points between the current system and the target system.
		// the only way to get this info is to recreate a route using the same logic as entered on the ANA, and pick item 1
		// from the list. That should be the next destination in the list.
		if (system.ID === -1) {
			var myRoute = System.infoForSystem(galaxyNumber, this._lastSource).routeToSystem(System.infoForSystem(galaxyNumber, target), player.ship.routeMode);
		} else {
			var myRoute = System.infoForSystem(galaxyNumber, global.system.ID).routeToSystem(System.infoForSystem(galaxyNumber, target), player.ship.routeMode);
		}
		if (myRoute && myRoute.route.length >= 1) {
			target = myRoute.route[1];
		}
	}

	return target;
}

//-------------------------------------------------------------------------------------------------------------
// returns a string containing the hours, minutes (and possibly seconds) remaining until the expiry time is reached
this.$getTimeRemaining = function $getTimeRemaining(expiry, hoursOnly) {
	var diff = expiry - clock.adjustedSeconds;
	var result = "";
	if (diff > 0) {
		var hours = Math.floor(diff / 3600);
		var mins = Math.floor((diff - (hours * 3600)) / 60);
		var secs = Math.floor(diff - (hours * 3600) - (mins * 60));
		// special case - reduce 1 hour down to mins
		if (hours === 1 && mins < 40) {
			hours = 0;
			mins += 60;
		}
		// special case - reduce 1 min down to secs
		if (hours === 0 && mins === 1 && secs < 40) {
			mins = 0;
			secs += 60;
		}
		if (hoursOnly && hoursOnly === true && mins > 30 && hours > 1) hours += 1;
		if (hours > 0) result += hours + " hours";
		if ((hoursOnly == null || hoursOnly === false) || (hours === 0 && mins > 0)) {
			if (mins > 0) result += (result === "" ? "" : " ") + mins + " mins";
		}
		if ((hoursOnly == null || hoursOnly === false) || (hours === 0 && mins === 0 && secs > 0)) {
			if (hours === 0 && secs > 0) result += (result === "" ? "" : " ") + secs + " secs";
		}
	} else {
		result = "Overdue";
	}
	return result;
}

//-------------------------------------------------------------------------------------------------------------
// check for any in transit arrivals
this.$checkForArrivals = function $checkForArrivals() {
	var si = worldScripts.Smugglers_Illegal;
	var mapOn = false;

	// any in transit arrivals?
	for (var i = this._storage.length - 1; i >= 0; i--) {
		if (this._storage[i].inTransit === true) {
			var item = this._storage[i];
			var equip = null;
			var cargo = "";

			if (item.equipmentKey != "") equip = EquipmentInfo.infoForKey(item.equipmentKey);
			if (item.commodity != "" && this._commodities.indexOf(item.commodity) >= 0) cargo = item.commodity;

			// only move valid equipment items
			if (equip || cargo != "") {
				if (mapOn === false) {
					this.$revealMap();
					mapOn = true;
				}
				// our starting system
				var info1 = System.infoForSystem(galaxyNumber, item.transitSystem);
				// our ultimate destination
				var info2 = System.infoForSystem(galaxyNumber, item.transitDestination);
				// route between the two
				var route1 = info1.routeToSystem(info2);

				// now work out the first step of this complete route
				var info3 = System.infoForSystem(galaxyNumber, route1.route[1]);
				// get the route between info1 (start) and this new point
				var route2 = info1.routeToSystem(info3);

				if (item.transitStart + (route2.time * 3600) < clock.adjustedSeconds) {
					if (Math.random() > ((info3.government + 1) / 9) && Math.random() > ((info3.government + 1) / 9) && Math.random() > ((info3.government + 1) / 9)) {
						var sellcost = 0;
						var extra = "";
						var text = "";
						extra = " Insurance pays you " + formatCredits(sellcost, true, true) + ".";
						if (equip && equip.techLevel <= 14) {
							sellcost = (equip.price / 10 * (item.status === "EQUIPMENT_DAMAGED" ? 0.5 : 1) * 0.1);
							text = "Storage Transfer Shipping reports that the ship carrying your " + (item.equipmentKey === "EQ_PASSENGER_BERTH" ? "passenger berth" : equip.name.toLowerCase()) +
								" has been destroyed by pirates while on route to " + info2.name + "." + extra;
						}
						if (cargo != "") {
							sellcost = ((system.mainStation.market[cargo].price / 10) * 0.1) * item.weight;
							text = "Storage Transfer Shipping reports that the ship carrying your cargo of " + item.weight + this.$getCommodityUnit(item.commodity) + " × " + displayNameForCommodity(item.commodity) +
								" has been destroyed by pirates while on route to " + info2.name + "." + extra;
						}
						// tell the player via an arrival report messages
						player.addMessageToArrivalReport(text);

						this._storage.splice(i, 1);
					} else {
						// move the ship along the plot
						item.transitSystem = info3.systemID;
						// add some time
						item.transitStart += (route2.time * 3600) + 3600;
						// check for illegal goods
						if (si && cargo != "") {
							// player may have paid a premium to allow for the shipping commander to bribe official's, but that doesn't mean it's going to work!
							if (si.$isGoodIllegal(item.transitSystem, item.commodity) === true) {
								// attempt a bribe on player's behalf - change player amount. if they can't afford it, remove cargo
								// 2 tries
								var chance = worldScripts.Smugglers_DockMaster._bribeChance[system.ID];
								var result = false;
								for (var tries = 1; tries <= 2; tries++) {
									var bribe_attempt = Math.random();
									if (bribe_attempt >= chance) {
										result = true;
										break;
									}
								}
								if (result === false) {
									// tell the player via an arrival report messages
									var text = "Storage Transfer Shipping reports that your cargo of " + item.weight + this.$getCommodityUnit(item.commodity) + " × " + displayNameForCommodity(item.commodity) +
										" has been discovered and confiscated by GalCop Customs officials in " + info3.name + "." + extra;
									player.addMessageToArrivalReport(text);
									this._storage.splice(i, 1);
									continue;
								}
							}
						}

						if (item.transitSystem === item.transitDestination) {
							// arrived! reset transit data
							item.system = item.transitDestination;
							item.systemName = info2.name;
							item.inTransit = false;
							item.transitSystem = -1;
							item.transitDestination = -1;
							item.transitETA = 0;
							item.transitStart = 0;
						}
					}
				}
			}
		}
	}
	if (mapOn === true) {
		this.$restoreMap();
	}
}

//-------------------------------------------------------------------------------------------------------------
// returns the cost of transferring an item to local storage, based on the total distance, number of jumps, the danger level and the number of illegal systems
this.$calculateTransferCost = function $calculateTransferCost(dist, jumps, danger, illegal) {
	// cargo only: we add a premium for bribes if there are any systems that consider the cargo illegal
	// for equipment, illegal should always be zero
	return (((dist / jumps) * 3) * (danger + 1)) * 10 + (illegal * (system.scrambledPseudoRandomNumber(illegal) * 500 + 500));
}

//-------------------------------------------------------------------------------------------------------------
// gets the commodity type for a particular commodity (t, kg or g)
this.$getCommodityUnit = function $getCommodityUnit(generic) {
	if (system.isInterstellarSpace) {
		var m = player.ship.manifest;
		if (m[generic] > 0) {
			for (var i = 0; i < m.list.length; i++) {
				if (m.list[i].commodity == generic) return m.list[i].unit;
			}
		} else {
			return "t";
		}
	}
	var types = ["t", "kg", "g"];
	return types[system.mainStation.market[generic].quantity_unit];
}

//-------------------------------------------------------------------------------------------------------------
// returns the minimum amount of space required for a particular cargo type (kg, g or t) and weight
this.$cargoSpaceRequired = function $cargoSpaceRequired(cargoType, weight) {
	var spcwt = 0;
	switch (cargoType) {
		case "kg":
			spcwt = parseInt(weight / 1000);
			break;
		case "g":
			spcwt = parseInt(weight / 1000000);
			break;
		default:
			spcwt = weight;
			break;
	}
	if (spcwt === 0) spcwt = 1;
	return spcwt;
}

//-------------------------------------------------------------------------------------------------------------
// compiles a list of sample prices for all the cargo/system combinations we have in storage
this.$compileSamplePrices = function $compileSamplePrices() {
	this._storedPrices.length = 0;
	var sc = worldScripts.Smugglers_Contracts;
	if (system.ID === -1) return;
	for (var i = 0; i < this._storage.length; i++) {
		if (this._storage[i].commodity != "" && this._storage[i].galaxy === galaxyNumber && this._storage[i].system != system.ID) {
			var sys = System.infoForSystem(galaxyNumber, this._storage[i].system);
			var prc = 0;
			if (sc) {
				prc = sc.$priceForCommodity(this._storage[i].commodity, sys);
			} else {
				prc = (sys.samplePrice(this._storage[i].commodity) / 10);
			}
			this._storedPrices.push({
				system: this._storage[i].system,
				commodity: this._storage[i].commodity,
				price: prc
			});
		}
	}
	sc = null
}

//-------------------------------------------------------------------------------------------------------------
// returns the price for a particular system/commoodity combo
this.$getSamplePrice = function $getSamplePrice(sysID, commodity) {
	for (var i = 0; i < this._storedPrices.length; i++) {
		if (this._storedPrices[i].system === sysID && this._storedPrices[i].commodity === commodity) return this._storedPrices[i].price;
	}
	// if we get here, the specific system/commodity pair wan't matched, so add a new one to the array and return the new price
	var sc = worldScripts.Smugglers_Contracts;
	var sys = System.infoForSystem(galaxyNumber, sysID);
	var prc = 0;
	if (sc) {
		prc = sc.$priceForCommodity(commodity, sys);
	} else {
		prc = (sys.samplePrice(commodity) / 10);
	}
	// add it to the array so we don't have to keep looking it up
	this._storedPrices.push({
		system: sysID,
		commodity: commodity,
		price: prc
	});
	return prc;
}

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

//-------------------------------------------------------------------------------------------------------------
// appends space to currentText to the specified length in 'em'
this.$padTextRight = function $padTextRight(currentText, desiredLength, leftSwitch) {
	if (currentText == null) currentText = "";
	var hairSpace = String.fromCharCode(31);
	var ellip = "…";
	var currentLength = defaultFont.measureString(currentText.replace(/%%/g, "%"));
	var hairSpaceLength = defaultFont.measureString(hairSpace);
	// calculate number needed to fill remaining length
	var padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
	if (padsNeeded < 1) {
		// text is too long for column, so start pulling characters off
		var tmp = currentText;
		do {
			tmp = tmp.substring(0, tmp.length - 2) + ellip;
			if (tmp === ellip) break;
		} while (defaultFont.measureString(tmp.replace(/%%/g, "%")) > desiredLength);
		currentLength = defaultFont.measureString(tmp.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 $padTextLeft(currentText, desiredLength) {
	return this.$padTextRight(currentText, desiredLength, true);
}

//-------------------------------------------------------------------------------------------------------------
this.$pageHeader = function $pageHeader() {
	if (this._sc_core) {
		return this._sc_core.$pageHeader("store");
	} else {
		return "";
	}
}

//-------------------------------------------------------------------------------------------------------------
// player the buy/sell sound effects
this.$playSound = function $playSound(soundtype) {
	var mySound = new SoundSource;
	switch (soundtype) {
		case "buy":
			mySound.sound = "[buy-commodity]";
			break;
		case "sell":
			mySound.sound = "[sell-commodity]";
			break;
	}
	mySound.loop = false;
	mySound.play();
}

//-------------------------------------------------------------------------------------------------------------
// routine to completely reveal the system map (if parts have been concealed) to ensure route calculations work
this.$revealMap = function $revealMap() {
	this._holdConcealment = [];
	this._unconcealmentActive = true;
	//if (this._debug) log(this.name, "system map uncovered");
	for (var i = 0; i < 256; i++) {
		var sys = System.infoForSystem(galaxyNumber, i);
		var c = 0;
		if (sys.concealment) c = sys.concealment;
		this._holdConcealment.push(c);
		if (c != 0) sys.setProperty(2, "concealment", 0);
	}
}

//-------------------------------------------------------------------------------------------------------------
// routine to restore system map (if parts had been concealed) after route calculations have completed
this.$restoreMap = function $restoreMap() {
	if (this._holdConcealment.length === 0) return;
	for (var i = 0; i < this._holdConcealment.length; i++) {
		var sys = System.infoForSystem(galaxyNumber, i);
		if (this._holdConcealment[i] != 0) sys.setProperty(2, "concealment", this._holdConcealment[i]);
	}
	//if (this._debug) log(this.name, "system map restored");
	this._unconcealmentActive = false;
}

//-------------------------------------------------------------------------------------------------------------
// returns true if the passed equipment key is required by another piece of installed equipment, otherwise false
this.$equipmentIsRequired = function $equipmentIsRequired(equipmentKey) {

	// todo: look at requiresAnyEquipment as well
	var p = player.ship;
	var eq = p.equipment;
	var result = false;

	// special case for passengers: make sure we always have enough berths for the number of passengers currently on board.
	if (equipmentKey === "EQ_PASSENGER_BERTH") {
		if (p.passengerCount > 0 && p.passengerCapacity <= p.passengerCount) {
			result = true;
		}
	} else {
		for (var i = 0; i < eq.length; i++) {
			// special case that isn't listed in equipment config
			//if (equipmentKey === "EQ_SHIELD_BOOSTER" && eq[i].equipmentKey === "EQ_NAVAL_SHIELD_BOOSTER") {
			//	result = true;
			//	break;
			// }
			var info = EquipmentInfo.infoForKey(eq[i].equipmentKey);
			if (info.requiresEquipment) {
				if (info.requiresEquipment.indexOf(equipmentKey) >= 0 || 
					// some special cases for ShipConfig items
					(this._sc_core && 
						((equipmentKey.indexOf("EQ_FUEL_SCOOPS") >= 0 && info.requiresEquipment.indexOf("EQ_FUEL_SCOOPS") >= 0) ||
						(equipmentKey.indexOf("EQ_FUEL_INJECTION") >= 0 && info.requiresEquipment.indexOf("EQ_FUEL_INJECTION") >= 0) || 
						(equipmentKey.indexOf("EQ_HEAT_SHIELD") >= 0 && info.requiresEquipment.indexOf("EQ_HEAT_SHIELD") >= 0))
					)) {
					result = true;
					break;
				}
			}
		}
	}
	return result;
}