| Config/script.js | "use strict";
this.name = "Smugglers_BlackMarket";
this.author = "phkb";
this.description = "Looks after the Black Market interface screen.";
this.licence = "CC BY-NC-SA 3.0";
/*
Ideas: 
- Add an equipment selling facility, so players can try to sell equipment for a profit
	- add interface into SDC to "board ship" then "steal equipment" from NPC ships
	- need ships created by SDC to include default equipment from SC
- Add notes to bulletin board about where you might be able to find the phase scanner, then increase chance at that destination
*/
this._disabled = false;
this._debug = false;
this._smugglingAllegiance = ["pirate", "chaotic"];
this._includeRoles = ["rockhermit", "rockhermit-chaotic", "rockhermit-pirate", "random_hits_any_spacebar"]; // make sure these stations have a Black Market
this._excludeRoles = ["slaver_base", "rrs_slaverbase"]; // stations with these roles won't have a Black Market
this._fakePermits = []; // list of systems where fake permits can be bought, plus the rating of each
// value of 0 means no fake permits can be bought in that system
// value of 0.5 to 0.99 reliability of permits bought (0.99 means almost perfect)
this._display = 0;
this._noticeboard = [];
this._selectedIndex = 0;
this._waypoints = [];
this._fakePermitCost = 120;
this._waypointCost = 20;
this._phaseScanCost = 450;
this._blackMarketSalesPerson = "";
this._sellCommodity = "";
this._sellQuantity = 0;
this._sellType = "";
this._offerQuantity = 0;
this._offerPrice = 0.0;
this._menuColor = "orangeColor";
this._itemColor = "yellowColor";
this._disabledColor = "darkGrayColor";
this._blackMarketSales = [];
this._blackMarketContacts = [];
this._blackMarketShutdown = [];
this._blackMarketWelcome = "";
this._sting = false;
this._stingShip = null;
this._stingStation = null;
this._stingShipChance = 0.3;
this._noBribe = false;
this._lastChoice = ["", "", "", "", "", "", "", "", "", "", ""];
this._additionalSaleItems = [];
this._systemFactor = -1;
this._stationIncludeScriptKey = [];
this._stationExcludeScriptKey = [];
this._bribeChance = 0;
this._stash = [];
this._stashLocation = null;
this._pirates = [];
this._curpage = 0;
//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
	if (this._disabled) {
		delete this.playerWillSaveGame;
		delete this.playerEnteredNewGalaxy;
		delete this.shipExitedWitchspace;
		delete this.shipDockedWithStation;
		delete this.shipLaunchedFromStation;
		delete this.startUpComplete;
		return;
	}
	if (worldScripts.Smugglers_Illegal) this._smugglers = true;
	if (missionVariables.Smuggling_Noticeboard) {
		this._noticeboard = JSON.parse(missionVariables.Smuggling_Noticeboard);
	} else {
		this._rebuildTimer = new Timer(this, this.$buildNoticeboard, 2, 0);
	}
	if (this._smugglers) {
		if (missionVariables.Smuggling_FakePermits) {
			this._fakePermits = JSON.parse(missionVariables.Smuggling_FakePermits);
		} else {
			this.$buildFakePermitArray();
		}
	}
	if (missionVariables.Smuggling_Waypoints) this._waypoints = JSON.parse(missionVariables.Smuggling_Waypoints);
	if (missionVariables.Smuggling_BlackMarketSales) {
		this._blackMarketSales = JSON.parse(missionVariables.Smuggling_BlackMarketSales);
		delete missionVariables.Smuggling_BlackMarketSales;
	}
	if (missionVariables.Smuggling_BlackMarketContacts) this._blackMarketContacts = JSON.parse(missionVariables.Smuggling_BlackMarketContacts);
	if (missionVariables.Smuggling_BlackMarketShutdown) this._blackMarketShutdown = JSON.parse(missionVariables.Smuggling_BlackMarketShutdown);
	if (missionVariables.Smuggling_BlackMarketSting) this._sting = missionVariables.Smuggling_BlackMarketSting;
	if (missionVariables.Smuggling_BlackMarketStingShipChance) this._stingShipChance = missionVariables.Smuggling_BlackMarketStingShipChance;
	if (missionVariables.Smuggling_BlackMarketAdditional) this._additionalSaleItems = JSON.parse(missionVariables.Smuggling_BlackMarketAdditional);
	if (missionVariables.Smuggling_BlackMarketSystemFactor) this._systemFactor = missionVariables.Smuggling_BlackMarketSystemFactor;
	if (this._systemFactor === -1) this.$setupSystemFactor();
	if (missionVariables.Smuggling_BlackMarketStashes) this._stash = JSON.parse(missionVariables.Smuggling_BlackMarketStashes);
	for (var i = this._stash.length - 1; i >= 0; i--) {
		if (this._stash[i].systemID == system.ID && (clock.adjustedSeconds - this._stash[i].created) / 86400 < 5) {
			this.$setupCargoStash();
		}
		if ((clock.adjustedSeconds - this._stash[i].created) / 86400 >= 5) {
			this._stash.splice(i, 1);
		}
	}
	// because stations display name may not be populated at this point, create a timer to set up the sting
	if (this._sting) this._delayStingSetup = new Timer(this, this.$setupStingAfterLoad, 2, 0);
	// set up the interface screen, if required
	this.$initMainInterface(player.ship.dockedStation);
	this.$addCoreEquipmentToBlackMarket();
}
//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function () {
	missionVariables.Smuggling_Noticeboard = JSON.stringify(this._noticeboard);
	if (this._smugglers) missionVariables.Smuggling_FakePermits = JSON.stringify(this._fakePermits);
	missionVariables.Smuggling_Waypoints = JSON.stringify(this._waypoints);
	missionVariables.Smuggling_BlackMarketStingShipChance = this._stingShipChance;
	if (this._blackMarketSales.length > 0) missionVariables.Smuggling_BlackMarketSales = JSON.stringify(this._blackMarketSales);
	if (this._blackMarketContacts.length > 0) missionVariables.Smuggling_BlackMarketContacts = JSON.stringify(this._blackMarketContacts);
	if (this._blackMarketShutdown.length > 0) missionVariables.Smuggling_BlackMarketShutdown = JSON.stringify(this._blackMarketShutdown);
	if (this._additionalSaleItems.length > 0) missionVariables.Smuggling_BlackMarketAdditional = JSON.stringify(this._additionalSaleItems);
	missionVariables.Smuggling_BlackMarketSystemFactor = this._systemFactor;
	if (this._sting) {
		missionVariables.Smuggling_BlackMarketSting = this._sting;
		missionVariables.Smuggling_BlackMarketStingStation = this._stingStation.displayName;
		if (this._stingShip) missionVariables.Smuggling_BlackMarketStingShip = true;
	}
	if (this._stash.length > 0) {
		missionVariables.Smuggling_BlackMarketStashes = JSON.stringify(this._stash);
	} else {
		if (missionVariables.Smuggling_BlackMarketStashes) {
			delete missionVariables.Smuggling_BlackMarketStashes;
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
this.playerEnteredNewGalaxy = function (galaxyNumber) {
	if (this._smugglers) this.$buildFakePermitArray();
	this._waypoints = [];
	this._blackMarketShutdown = [];
	this._stash.length = 0;
}
//-------------------------------------------------------------------------------------------------------------
this.shipExitedWitchspace = function () {
	this._bribeChance = Math.random();
	this._additionalSaleItems = [];
	this.$setupSystemFactor();
	// rebuild the notice board a few seconds after entering a new system
	// that should give the other systems a chance to finish
	this._rebuildTimer = new Timer(this, this.$buildNoticeboard, 5, 0);
	this.$addRHWaypoint();
	this._blackMarketSales = [];
	// reset any sting info
	this._sting = false;
	this._stingStation = null;
	this._stingShip = null;
	// find a station with a black market
	var stns = system.stations;
	for (var i = 0; i < stns.length; i++) {
		if (this.$doesStationHaveBlackMarket(stns[i]) === true) {
			var hasVisited = this.$playerHasBeenToBlackMarket(stns[i]);
			// there's a small chance of a black market sting if the player has visited the station, and an even smaller chance if they haven't
			if ((hasVisited === true && Math.random() > 0.9) || (hasVisited === false && Math.random() > 0.98)) {
				// set up the sting
				this.$setupSting(stns[i]);
				// don't set up more than one sting operation, so break here when we're done.
				break;
			}
		}
	}
	// clean up any shutdowns that have expired
	if (this._blackMarketShutdown.length > 0) {
		for (var i = this._blackMarketShutdown.length - 1; i >= 0; i--) {
			if (clock.adjustedSeconds - this._blackMarketShutdown[i].date > 2592000) this._blackMarketShutdown.splice(i, 1);
		}
	}
	// cargo stash
	for (var i = this._stash.length - 1; i >= 0; i--) {
		if (this._stash[i].systemID == system.ID && (clock.adjustedSeconds - this._stash[i].created) / 86400 < 5) {
			this.$setupCargoStash();
		}
		if ((clock.adjustedSeconds - this._stash[i].created) / 86400 >= 5) {
			this._stash.splice(i, 1);
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
this.shipDockedWithStation = function (station) {
	this.$addCoreEquipmentToBlackMarket();
	this.$initMainInterface(station);
	this._blackMarketSalesPerson = "";
	this._blackMarketWelcome = "";
	// remove the sting ship after we dock, if it's still there
	if (this._sting === true && this._stingStation === station && this._stingShip && this._stingShip.isValid) {
		this._stingShip.remove(true);
		this._stingShip = null;
	}
}
//-------------------------------------------------------------------------------------------------------------
this.shipLaunchedFromStation = function (station) {
	this.$addRHWaypoint();
	// remove the sale items when we launch
	this.$removeSaleItem("EQ_CLOAKING_DEVICE");
	var eq = player.ship.equipment;
	for (var i = 0; i < eq.length; i++) {
		if (eq[i].equipmentKey.indexOf("NAVAL") >= 0 || eq[i].equipmentKey.indexOf("MILITARY") >= 0) {
			var item = EquipmentInfo.infoForKey(eq[i].equipmentKey);
			this.$removeSaleItem(eq[i].equipmentKey);
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
this.guiScreenChanged = function (to, from) {
	if (guiScreen === "GUI_SCREEN_SYSTEM_DATA") {
		this.$checkForWaypoints();
	}
}
//-------------------------------------------------------------------------------------------------------------
this.infoSystemChanged = function (to, from) {
	if (guiScreen === "GUI_SCREEN_SYSTEM_DATA") {
		this.$checkForWaypoints();
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$checkForWaypoints = function () {
	if (this._waypoints.length > 0) {
		var sysID = player.ship.targetSystem;
		if (player.ship.hasOwnProperty("infoSystem")) sysID = player.ship.infoSystem;
		if (this._waypoints.indexOf(sysID) >= 0) {
			mission.addMessageText(expandDescription("[blackmarket-waypoint-here]"));
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
/* adds an item to the Black Market additional sale items
 passed object must have:
	key:				(required) text to be sent back to the calling OXP to identify the sale item
	text:				(required) text to be displayed on the sale screen
	cost:				(required) cost of the item to be sold. must be greater than 0
	worldScript:		(required) name of the calling worldScript
	sellCallback:		(required) name of the function to call if the item is sold
*/
this.$addSaleItem = function $addSaleItem(obj) {
	if (obj.hasOwnProperty("key") === false || obj.key == "") {
		throw "Invalid sale item properties: 'key' must be supplied";
	}
	if (obj.hasOwnProperty("text") === false || obj.text == "") {
		throw "Invalid sale item properties: 'text' must be supplied";
	}
	if (obj.hasOwnProperty("cost") === false) {
		throw "Invalid sale item properties: 'cost' must be supplied";
	}
	if (parseInt(obj.cost) === 0) {
		throw "Invalid sale item properties: 'cost' must be greater than 0";
	}
	if (obj.hasOwnProperty("worldScript") === false || obj.worldScript == "") {
		throw "Invalid sale item properties: 'worldScript' must be supplied";
	}
	if (obj.hasOwnProperty("sellCallback") === false || obj.sellCallback == "") {
		throw "Invalid sale item properties: 'sellCallback' must be supplied";
	}
	for (var i = 0; i < this._additionalSaleItems.length; i++) {
		if (this._additionalSaleItems[i].key === obj.key) {
			throw "Invalid sale item properties: 'key' of " + obj.key + " is already in use.";
		}
	}
	this._additionalSaleItems.push(obj);
}
//-------------------------------------------------------------------------------------------------------------
// removes all items from the sale list linked to a particular worldScript
this.$removeWorldScriptItems = function $removeWorldScriptItems(ws) {
	for (var i = this._additionalSaleItems.length - 1; i >= 0; i--) {
		if (this._additionalSaleItems[i].worldScript === ws) {
			this._additionalSaleItems.splice(i, 1);
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
// removes an item from the sale list with the specific key value
this.$removeSaleItem = function $removeSaleItem(key) {
	for (var i = 0; i < this._additionalSaleItems.length; i++) {
		if (this._additionalSaleItems[i].key === key) {
			this._additionalSaleItems.splice(i, 1);
			break;
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
// add a noticeboard item, with a callback for when it is viewed
this.$addNoticeboardItem = function (msg, ws, fn) {
	function compare(a, b) {
		if (a.idx < b.idx) return -1;
		if (a.idx > b.idx) return 1;
		return 0;
	}
	var item = {
		idx: this.$rand(100),
		message: msg,
	};
	if (ws && ws != "" && fn && fn != "") {
		item["worldscript"] = ws;
		item["function"] = fn;
	}
	this._noticeboard.push(item);
	// sort the list, based on the random index, so the items are mixed up.
	this._noticeboard.sort(compare);
}
//-------------------------------------------------------------------------------------------------------------
// initialise the illegal goods F4 screen entries
this.$initMainInterface = function $initMainInterface(station) {
	// only at pirate or chaotic stations, all rock hermits, and selected main stations
	if (this._debug === true || (this.$doesStationHaveBlackMarket(station) === true)) {
		station.setInterface(this.name, {
			title: expandDescription("[blackmarket_interface_title]"),
			category: expandDescription("[blackmarket_interface_category]"),
			summary: expandDescription("[blackmarket_interface_summary]"),
			callback: this.$initialBlackMarket.bind(this)
		});
	} else {
		station.setInterface(this.name, null);
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$isStationIncluded = function $isStationIncluded(station) {
	for (var i = 0; i < this._includeRoles.length; i++) {
		if (station.hasRole(this._includeRoles[i]) === true) return true;
	}
	if (this._stationIncludeScriptKey.length > 0) {
		for (var i = 0; i < this._stationIncludeScriptKey.length; i++) {
			var item = this._stationIncludeScriptKey[i].key;
			var value = this._stationIncludeScriptKey[i].value;
			if (station.script.hasOwnProperty(item) === true && station.script[item] == value) return true;
		}
	}
	return false;
}
//-------------------------------------------------------------------------------------------------------------
this.$isStationExcluded = function $isStationExcluded(station) {
	for (var i = 0; i < this._excludeRoles.length; i++) {
		if (station.hasRole(this._excludeRoles[i]) === true) return true;
	}
	if (this._stationExcludeScriptKey.length > 0) {
		for (var i = 0; i < this._stationExcludeScriptKey.length; i++) {
			var item = this._stationExcludeScriptKey[i].key;
			var value = this._stationExcludeScriptKey[i].value;
			if (station.script.hasOwnProperty(item) === true && station.script[item] == value) return true;
		}
	}
	return false;
}
//-------------------------------------------------------------------------------------------------------------
this.$initialBlackMarket = function $initialBlackMarket() {
	this._display = 0;
	this._selectedIndex = 0;
	this._curpage = 0;
	this.$blackMarketOptions();
}
//-------------------------------------------------------------------------------------------------------------
this.$blackMarketOptions = function $blackMarketOptions() {
	var curChoices = {};
	var text = "";
	var p = player.ship;
	var stn = p.dockedStation;
	var def = "";
	var si = worldScripts.Smugglers_Illegal;
	var se = worldScripts.Smugglers_Equipment;
	var sc = worldScripts.Smugglers_Contracts;
	var pagesize = 20;
	var image = "blackmarket-skull.png";
	if (this.$isBigGuiActive() === true) {
		pagesize = 26;
	}
	if (this._smugglers) {
		sc._smugglingPageOverlay = image;
		sc._smugglingPageOverlayHeight = 546;
	}
	var govs = new Array();
	for (var i = 0; i < 8; i++)
		govs.push(String.fromCharCode(i));
	var spc = String.fromCharCode(31);
	// main menu
	if (this._display === 0) {
		// if we haven't shown the initial welcome, get one now
		if (this._blackMarketWelcome === "") {
			// use one of the standard welcomes, but if it's a sting use the alternate ones. Also, there's a chance of the alternate ones anyway, so it's not a complete give-away
			this._blackMarketWelcome = expandDescription("[blackmarket-welcome" + (this._sting === true || Math.random() > 0.8 ? "-sting" : "") + "]", {
				contactname: this.$getBlackMarketContact(p.dockedStation)
			});
		}
		// add this to the text
		text = this._blackMarketWelcome
		// reset the welcome text for the next time we show the main menu, so that the welcome text is only displayed once.
		this._blackMarketWelcome = expandDescription("[blackmarket_services]");
		var itemcount = 2;
		curChoices["01_NOTICEBOARD"] = {
			text: "[blackmarket-noticeboard]",
			color: this._menuColor
		};
		if (this._waypoints.length > 0) {
			curChoices["01A_CURRENT_WAYPOINTS"] = {
				text: "[blackmarket-list-waypoints]",
				color: this._menuColor
			};
			itemcount += 1;
		}
		if (!worldScripts.ContractsOnBB && this._smugglers == true) {
			// contracts only available at rock hermits
			if ((this._debug === true ||
				(Ship.roleIsInCategory(p.dockedStation.primaryRole, "oolite-rockhermits") && p.dockedStation.hasRole("slaver_base") === false && p.dockedStation.hasRole("rrs_slaverbase") === false)) &&
				sc._contracts.length > 0) {
				//if ((this._debug === true || this._smugglingAllegiance.indexOf(p.dockedStation.allegiance) >= 0) && sc._contracts.length > 0) { // alternate logic for non-galcop stations
				curChoices["02_CONTRACTS"] = {
					text: expandDescription("[blackmarket_smuggling]", { count: sc._contracts.length }),
					color: this._menuColor
				};
				itemcount += 1;
			}
		}
		curChoices["03_PURCHASE"] = {
			text: "[blackmarket-purchasing]",
			color: this._menuColor
		};
		if (this._smuggling) {
			if (se.$playerHasIllegalCargo(system.mainStation) === true || se.$playerHasHiddenIllegalCargo(system.mainStation) === true) {
				var saleitems = 0;
				var viscargo = p.manifest.list;
				for (var i = 0; i < viscargo.length; i++) {
					if (system.mainStation.market[viscargo[i].commodity].legality_import > 0 && this._blackMarketSales.indexOf(viscargo[i].commodity) === -1) saleitems += 1;
				}
				var smuggle = se.$getSmugglingCargo();
				for (var i = 0; i < smuggle.length; i++) {
					if (system.mainStation.market[smuggle[i].commodity].legality_import > 0 && this._blackMarketSales.indexOf(smuggle[i].commodity) === -1) saleitems += 1;
				}
				if (saleitems > 0) {
					curChoices["04_SELL_ILLEGAL"] = {
						text: "[blackmarket-sellcargo-menu]",
						color: this._menuColor
					};
					itemcount += 1;
				}
			}
			if (se.$sellablePhaseScans() > 0) {
				curChoices["07_SELLSCAN"] = {
					text: "[blackmarket-sellphasescan-menu]",
					color: this._menuColor
				};
				itemcount += 1;
			}
		}
		if (this._additionalSaleItems.length > 0) {
			curChoices["08_SELLADDITIONAL"] = {
				text: "Sell additional items",
				color: this._menuColor
			}
			itemcount += 1;
		}
		curChoices["99_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 4) - itemcount); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "99_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_menu]"),
			overlay: {
				name: image,
				height: 546
			},
			allowInterrupt: true,
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// display noticeboard item
	if (this._display === 2) {
		if (this._selectedIndex < this._noticeboard.length - 1) {
			curChoices["20_NEXTITEM"] = {
				text: "[blackmarket-nextitem]",
				color: this._menuColor
			};
			curChoices["30_LASTITEM"] = {
				text: "[blackmarket-lastitem]",
				color: this._menuColor
			};
		} else {
			curChoices["20_NEXTITEM"] = {
				text: "[blackmarket-nextitem]",
				unselectable: true,
				color: this._disabledColor
			};
			curChoices["30_LASTITEM"] = {
				text: "[blackmarket-lastitem]",
				unselectable: true,
				color: this._disabledColor
			};
		}
		if (this._selectedIndex > 0) {
			curChoices["21_PREVITEM"] = {
				text: "[blackmarket-previtem]",
				color: this._menuColor
			};
			curChoices["31_FIRSTITEM"] = {
				text: "[blackmarket-firstitem]",
				color: this._menuColor
			};
		} else {
			curChoices["21_PREVITEM"] = {
				text: "[blackmarket-previtem]",
				unselectable: true,
				color: this._disabledColor
			};
			curChoices["31_FIRSTITEM"] = {
				text: "[blackmarket-firstitem]",
				unselectable: true,
				color: this._disabledColor
			};
		}
		curChoices["98_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < (pagesize - 10); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_noticeboard]", { num: (this._selectedIndex + 1), max: (this._noticeboard.length) }),
			overlay: {
				name: image,
				height: 546
			},
			allowInterrupt: true,
			choices: curChoices,
			initialChoicesKey: def,
			exitScreen: "GUI_SCREEN_INTERFACES",
			message: this._noticeboard[this._selectedIndex].message
		};
		// call the callback, if present
		if (this._noticeboard[this._selectedIndex].hasOwnProperty("worldscript") && this._noticeboard[this._selectedIndex].hasOwnProperty("function")) {
			var ws = this._noticeboard[this._selectedIndex].hasOwnProperty("worldscript");
			var fn = this._noticeboard[this._selectedIndex].hasOwnProperty("function");
			if (worldScripts[ws][fn]) {
				worldScripts[ws][fn]();
			}
		}
	}
	// purchase items
	if (this._display === 3) {
		text = expandDescription("[blackmarket-purchaseheader]");
		var itemcount = 0;
		if (this._smuggling && this._fakePermits[system.ID] > 0) {
			curChoices["04_FAKEPERMITS"] = {
				text: "[blackmarket-buypermits-menu]",
				color: this._menuColor
			};
			itemcount += 1;
		}
		curChoices["05_WAYPOINTS"] = {
			text: "[blackmarket-buywaypoints-menu]",
			color: this._menuColor
		};
		itemcount += 1;
		if (this._smuggling) {
			curChoices["06_PHASESCAN"] = {
				text: "[blackmarket-buyphasescan-menu]",
				color: this._menuColor
			};
			itemcount += 1;
		}
		curChoices["98_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 1) - itemcount); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "98_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_shop]"),
			allowInterrupt: true,
			overlay: {
				name: image,
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// purchase fake permits
	if (this._display === 4) {
		text = expandDescription("[blackmarket-buypermits]", {
			cash: formatCredits(player.credits, true, true)
		});
		text += this.$padTextRight(expandDescription("[blackmarket_item_header]"), 20) + this.$padTextLeft(expandDescription("[blackmarket_cost_header]"), 10);
		// get list of local planets
		var sys = system.info.systemsInRange(15);
		var itemcount = 0;
		// loop through list
		for (var i = 0; i < sys.length; i++) {
			// for any that have illegal goods that can have permits, add the planet name + permit type to the list
			var goods = si.$illegalGoodsList(sys[i].systemID);
			if (goods.length > 0) {
				for (var j = 0; j < goods.length; j++) {
					if (goods[j].permit === 1 && system.scrambledPseudoRandomNumber(j) > 0.6 && si.$playerHasPermit(sys[i].systemID, goods[j].commodity, false) === false) {
						curChoices["60_PERMIT~" + sys[i].systemID + "|" + goods[j].commodity] = {
							text: this.$padTextRight(sys[i].name + " (" + govs[sys[i].government] + spc + "TL" + (sys[i].techlevel + 1) + ") -- " + si.$translateCommodityList(goods[j].commodity), 20) +
								this.$padTextLeft(formatCredits(this._fakePermitCost * stn.equipmentPriceFactor, true, true), 10),
							alignment: "LEFT",
							color: this._menuColor
						};
						itemcount += 1;
					}
				}
			}
		}
		if (itemcount === 0) text += expandDescription("[blackmarket-none-available]");
		curChoices["96_SPACER"] = "";
		itemcount += 1;
		// display the list of options
		curChoices["97_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 4) - itemcount); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "97_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_shop]"),
			overlay: {
				name: image,
				height: 546
			},
			allowInterrupt: true,
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// purchase rock hermit waypoints
	if (this._display === 5) {
		text = expandDescription("[blackmarket-buywaypoints]", {
			cash: formatCredits(player.credits, true, true)
		});
		text += this.$padTextRight(expandDescription("[blackmarket_item_header]"), 20) + this.$padTextLeft(expandDescription("[blackmarket_cost_header]"), 10);
		var sys = system.info.systemsInRange(7);
		var itemcount = 0;
		for (var i = 0; i < sys.length; i++) {
			if (this._waypoints.indexOf(sys[i].systemID) === -1 && system.scrambledPseudoRandomNumber(sys[i].systemID) > 0.7) {
				curChoices["61_WAYPOINT_" + (i < 10 ? "0" : "") + i + "~" + sys[i].systemID] = {
					text: this.$padTextRight(sys[i].name + " (" + govs[sys[i].government] + spc + "TL" + (sys[i].techlevel + 1) + ")", 20) +
						this.$padTextLeft(formatCredits(this._waypointCost * stn.equipmentPriceFactor, true, true), 10),
					alignment: "LEFT",
					color: this._menuColor
				};
				itemcount += 1;
			}
		}
		if (itemcount === 0) text += expandDescription("[blackmarket-none-available]");
		curChoices["96_SPACER"] = "";
		itemcount += 1;
		// display the list of options
		curChoices["97_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 4) - itemcount); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "97_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_shop]"),
			allowInterrupt: true,
			overlay: {
				name: image,
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// purchase phase scan settings
	if (this._display === 6) {
		text = expandDescription("[blackmarket-buyphasescan]", {
			cash: formatCredits(player.credits, true, true)
		});
		text += this.$padTextRight(expandDescription("[blackmarket_item_header]"), 20) + this.$padTextLeft(expandDescription("[blackmarket_cost_header]"), 10);
		// there won't be many of these
		// get a list of all planets with illegal goods
		var sys = system.info.systemsInRange(7);
		var itemcount = 0;
		for (var i = 0; i < sys.length; i++) {
			var goods = si.$illegalGoodsList(sys[i].systemID);
			if (se.$playerHasPhaseScan(sys[i].systemID) === false && system.scrambledPseudoRandomNumber(sys[i].systemID) > 0.5) {
				curChoices["62_PHASESCAN~" + sys[i].systemID] = {
					text: this.$padTextRight(sys[i].name + " (" + govs[sys[i].government] + spc + "TL" + (sys[i].techlevel + 1) + ")", 20) +
						this.$padTextLeft(formatCredits(this._phaseScanCost * stn.equipmentPriceFactor, true, true), 10),
					alignment: "LEFT",
					color: this._menuColor
				};
				itemcount += 1;
			}
		}
		if (itemcount === 0) text += expandDescription("[blackmarket-none-available]");
		curChoices["96_SPACER"] = "";
		itemcount += 1;
		// display the list of options
		curChoices["97_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 4) - itemcount); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "97_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_shop]"),
			allowInterrupt: true,
			overlay: {
				name: image,
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// sell phase scan
	if (this._display === 7) {
		text = expandDescription("[blackmarket-sellphasescan]", {
			cash: formatCredits(player.credits, true, true)
		});
		text += this.$padTextRight(expandDescription("[blackmarket_item_header]"), 20) + this.$padTextLeft(expandDescription("[blackmarket_cost_header]"), 10);
		var list = se.$getSellablePhaseScans();
		for (var i = 0; i < list.length; i++) {
			curChoices["63_PHASESCAN~" + (i < 10 ? "0" : "") + i] = {
				text: this.$padTextRight(list[i].text, 20) +
					this.$padTextLeft(formatCredits(((this._phaseScanCost * stn.equipmentPriceFactor) * 0.8), true, true), 10),
				alignment: "LEFT",
				color: this._menuColor
			};
		}
		curChoices["96_SPACER"] = "";
		// display the list of options
		curChoices["97_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 4) - list.length); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "97_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_sell_phase_scan]"),
			allowInterrupt: true,
			overlay: {
				name: image,
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// sell illegal cargo on black market
	if (this._display === 8) {
		text = expandDescription("[blackmarket-sellcargo]", {
			cash: formatCredits(player.credits, true, true)
		});
		var sdm = worldScripts.Smugglers_DockMaster;
		var heading = false;
		var itemcount = 0;
		var viscargo = p.manifest.list;
		for (var i = 0; i < viscargo.length; i++) {
			if (system.mainStation.market[viscargo[i].commodity].legality_import > 0 && this._blackMarketSales.indexOf(viscargo[i].commodity) === -1) {
				if (heading === false) {
					curChoices["73_HEADING"] = {
						text: "[blackmarket-standard-hold]",
						alignment: "LEFT",
						color: this._itemColor,
						unselectable: true
					};
					heading = true;
					itemcount += 1;
				}
				var q = viscargo[i].quantity;
				// remove any relabelled cargo from sale
				if (sdm && sdm.$getTotalRelabelled(viscargo[i].commodity) > 0) q = q - sdm.$getTotalRelabelled(viscargo[i].commodity);
				if (q > 0) {
					curChoices["73_SELL~" + viscargo[i].commodity] = {
						text: viscargo[i].displayName + " (" + q + viscargo[i].unit + ")",
						alignment: "LEFT",
						color: this._menuColor
					};
					itemcount += 1;
				}
			}
		}
		heading = false;
		if (this._smuggling) {
			var smuggle = se.$getSmugglingCargo();
			for (var i = 0; i < smuggle.length; i++) {
				if (system.mainStation.market[smuggle[i].commodity].legality_import > 0 && this._blackMarketSales.indexOf(smuggle[i].commodity) === -1) {
					if (heading === false) {
						curChoices["74_HEADING"] = {
							text: "[blackmarket-smuggling-compartment]",
							alignment: "LEFT",
							color: this._itemColor,
							unselectable: true
						};
						heading = true;
						itemcount += 1;
					}
					curChoices["74_SELL~" + smuggle[i].commodity] = {
						text: smuggle[i].quantity + " " + smuggle[i].unit + " × " + smuggle[i].displayName,
						alignment: "LEFT",
						color: this._menuColor
					};
					itemcount += 1;
				}
			}
		}
		if (itemcount === 0) {
			text += expandDescription("[blackmarket-sellcargo-none]");
			itemcount += 1;
		}
		curChoices["96_SPACER"] = "";
		// display the list of options
		curChoices["98_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 4) - itemcount); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "98_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_sell_illegals]"),
			allowInterrupt: true,
			overlay: {
				name: image,
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// accept/reject black market sell offer
	if (this._display === 9) {
		// work out if there is a buyer for this commodity
		var itemcount = 1;
		var ci = si._commodityInfo[this._sellCommodity];
		var main_markup = ci[1];
		if (main_markup === 0) main_markup = 1; // default to 1 for commodities we have no data for
		var bm_markup = ci[2];
		if (bm_markup === 0) bm_markup = 1.1; // default to 75% of main for commodities we have no data for.
		if (system.scrambledPseudoRandomNumber(bm_markup) < ((28 - system.info.economy) / 7)) {
			// work out how much of this cargo the market is willing to buy
			this._offerQuantity = parseInt(this._sellQuantity * ((7 - system.info.economy) + 1) / 8);
			// there is still a chance there will be a buyer for all the amount
			if (this._offerQuantity < this._sellQuantity && system.scrambledPseudoRandomNumber(this._sellQuantity) > 0.8) {
				this._offerQuantity = this._sellQuantity;
			}
			// better prices if player's smuggling reputation is higher
			this._offerPrice = (system.info.samplePrice(this._sellCommodity) * (bm_markup +
				((system.scrambledPseudoRandomNumber(main_markup) * (sc.$getSmugglingReputationPrecise() / 7))) -
				(0.5 * (1 - sc.$getSmugglingReputationPrecise() / 7))) / 10);
			text = expandDescription("[blackmarket-buyer]", {
				quantity: this._offerQuantity + " " + se.$getCommodityType(this._sellCommodity),
				commodity: displayNameForCommodity(this._sellCommodity),
				price: formatCredits(this._offerPrice, false, true),
				total: formatCredits(this._offerQuantity * this._offerPrice, false, true)
			});
			curChoices["80_ACCEPTOFFER"] = {
				text: "[blackmarket-accept]",
				color: this._menuColor
			};
			curChoices["81_REJECTOFFER"] = {
				text: "[blackmarket-reject]",
				color: this._menuColor
			};
			def = "80_ACCEPTOFFER";
			itemcount += 2;
		} else {
			// no buyer found
			text = expandDescription("[blackmarket-nobuyer]", {
				commodity: this._sellCommodity
			});
			def = "98_EXIT";
			curChoices["98_EXIT"] = {
				text: "[blackmarket-return]",
				color: this._itemColor
			};
		}
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		for (var i = 0; i < ((pagesize - 6) - itemcount); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_sell_illegals]"),
			allowInterrupt: true,
			overlay: {
				name: image,
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	if (this._display === 10) {
		text = expandDescription("[blackmarket-sellotheritems]", {
			cash: formatCredits(player.credits, true, true)
		});
		text += this.$padTextRight(expandDescription("[blackmarket_item_header]"), 20) + this.$padTextLeft(expandDescription("[blackmarket_cost_header]"), 10);
		var list = this._additionalSaleItems;
		for (var i = 0; i < list.length; i++) {
			curChoices["50_OTHERITEM~" + (i < 10 ? "0" : "") + i] = {
				text: this.$padTextRight(list[i].text, 20) +
					this.$padTextLeft(formatCredits(list[i].cost * this._systemFactor, true, true), 10),
				alignment: "LEFT",
				color: this._menuColor
			};
		}
		curChoices["96_SPACER"] = "";
		// display the list of options
		curChoices["98_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		for (var i = 0; i < ((pagesize - 4) - list.length); i++) {
			curChoices["99_SPACER_" + i] = "";
		}
		def = "98_EXIT";
		if (this._lastChoice[this._display] != "") def = this._lastChoice[this._display];
		var opts = {
			screenID: "oolite-smuggling-blackmarket-map",
			title: expandDescription("[blackmarket_sell_other]"),
			allowInterrupt: true,
			overlay: {
				name: image,
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: def,
			message: text
		};
	}
	// rock hermit waypoints purchased
	if (this._display === 11) {
		var redux = 6;
		var maxpages = Math.ceil(this._waypoints.length / (pagesize - redux));
		if (maxpages === 0) maxpages = 1;
		text = expandDescription("[blackmarket_rh_waypoints]", { page: (this._curpage + 1), max: maxpages });
		if (this._waypoints.length > 0) {
			var pagestart = this._curpage * (pagesize - redux);
			var pageend = this._curpage * (pagesize - redux) + (pagesize - redux);
			if (pageend > this._waypoints.length) pageend = this._waypoints.length;
			for (var i = pagestart; i < pageend; i++) {
				var info = System.infoForSystem(galaxyNumber, this._waypoints[i]);
				var rt = system.info.routeToSystem(info);
				if (rt) {
					var dist = rt.distance;
				} else {
					var dist = system.info.distanceToSystem(info);
				}
				text += expandDescription("[blackmarket_system_info]", { sysname: info.name, gov: govs[info.government], tl: (info.techlevel + 1), dist: dist.toFixed(1) });
			}
		} else {
			text += expandDescription("[blackmarket_none]");
		}
		if (this._curpage === maxpages - 1 || this._waypoints.length === 0) {
			curChoices["10_NEXTPAGE"] = {
				text: "[blackmarket-nextpage]",
				color: this._disabledColor,
				unselectable: true
			};
		} else {
			curChoices["10_NEXTPAGE"] = {
				text: "[blackmarket-nextpage]",
				color: this._menuColor
			};
		}
		if (this._curpage === 0 || this._waypoints.length === 0) {
			curChoices["11_PREVPAGE"] = {
				text: "[blackmarket-prevpage]",
				color: this._disabledColor,
				unselectable: true
			};
		} else {
			curChoices["11_PREVPAGE"] = {
				text: "[blackmarket-prevpage]",
				color: this._menuColor
			};
		}
		curChoices["98_EXIT"] = {
			text: "[blackmarket-return]",
			color: this._itemColor
		};
		def = "98_EXIT";
		var opts = {
			screenID: "oolite-smuggling-waypoints-map",
			title: expandDescription("[blackmarket_rh_waypoints_title]"),
			allowInterrupt: true,
			overlay: {
				name: "blackmarket-map_marker.png",
				height: 546
			},
			exitScreen: "GUI_SCREEN_INTERFACES",
			choices: curChoices,
			initialChoicesKey: this._lastChoice ? this._lastChoice : def,
			message: text
		};
	}
	mission.runScreen(opts, this.$screenHandler, this);
}
//-------------------------------------------------------------------------------------------------------------
this.$screenHandler = function $screenHandler(choice) {
	if (choice == null) {
		this.$blackMarketOptions();
		return;
	}
	this._lastChoice[this._display] = choice;
	var p = player.ship;
	var stn = p.dockedStation;
	var se = worldScripts.Smugglers_Equipment;
	switch (choice) {
		case "01_NOTICEBOARD":
			this._display = 2;
			this._selectedIndex = 0;
			break;
		case "02_CONTRACTS":
			var sc = worldScripts.Smugglers_Contracts;
			sc.$smugglingContractsScreens();
			break;
		case "03_PURCHASE":
			this._display = 3;
			break;
		case "04_SELL_ILLEGAL":
			this._display = 8;
			break;
		case "04_FAKEPERMITS":
			this._display = 4;
			break;
		case "05_WAYPOINTS":
			this._display = 5;
			break;
		case "06_PHASESCAN":
			this._display = 6;
			break;
		case "07_SELLSCAN":
			this._display = 7;
			break;
		case "97_EXIT":
			this._display = 3;
			break;
		case "08_SELLADDITIONAL":
			this._display = 10;
			break;
		case "01A_CURRENT_WAYPOINTS":
			this._display = 11;
			break;
		case "10_NEXTPAGE":
			this._curpage += 1;
			break;
		case "11_PREVPAGE":
			this._curpage -= 1;
			break;
		case "20_NEXTITEM":
			this._selectedIndex += 1;
			break;
		case "21_PREVITEM":
			this._selectedIndex -= 1;
			break;
		case "30_LASTITEM":
			this._selectedIndex = this._noticeboard.length - 1;
			break;
		case "31_FIRSTITEM":
			this._selectedIndex = 0;
			break;
		case "80_ACCEPTOFFER":
			if (this._sting && p.dockedStation === this._stingStation) {
				this.$stingOperation();
				return;
			}
			// pay the player
			player.credits += this._offerQuantity * this._offerPrice;
			// remove the cargo, but because this is a black market sale, it doesn't appear on the stations market. just remove it.
			switch (this._sellType) {
				case 1: // standard hold
					p.manifest[this._sellCommodity] -= this._offerQuantity;
					break;
				case 2: // smuggling compartment
					if (se) se.$removeCargo(this._offerCommodity, this._offerQuantity);
					break;
			}
			this._blackMarketSales.push(this._sellCommodity);
			se.$playSound("sell");
			player.consoleMessage(expandDescription("[blackmarket_credit]", { amount: formatCredits(this._offerQuantity * this._offerPrice, true, true) }));
			this._sellCommodity = "";
			this._sellQuantity = 0;
			this._sellType = 0;
			this._offerQuantity = 0;
			this._offerPrice = 0;
			this._display = 8;
			break;
		case "81_REJECTOFFER":
			this._sellCommodity = "";
			this._sellQuantity = 0;
			this._sellType = 0;
			this._offerQuantity = 0;
			this._offerPrice = 0;
			this._display = 8;
			break;
		case "98_EXIT":
			this._display = 0;
			break;
	}
	if (choice.indexOf("60_") >= 0) {
		if (this._sting && p.dockedStation === this._stingStation) {
			this.$stingOperation();
			return;
		}
		if (player.credits >= (this._fakePermitCost * stn.equipmentPriceFactor)) {
			var data = choice.substring(choice.indexOf("~") + 1);
			var subdata = data.split("|");
			var sysID = parseInt(subdata[0]);
			var si = worldScripts.Smugglers_Illegal;
			si._permits.push({
				systemID: sysID,
				commodity: subdata[1],
				fake: this._fakePermits[system.ID]
			});
			player.credits -= this._fakePermitCost * stn.equipmentPriceFactor;
			this.$sendPurchasingEmail(expandDescription("[blackmarket_email_fake_permit]", { commodity: si.$translateCommodityList(subdata[1]), dest: System.infoForSystem(galaxyNumber, sysID).name }),
				this._fakePermitCost * stn.equipmentPriceFactor);
		} else {
			player.consoleMessage(expandDescription("[blackmarket_no_cash]"));
		}
	}
	if (choice.indexOf("61_") >= 0) {
		if (player.credits >= (this._waypointCost * stn.equipmentPriceFactor)) {
			var sysID = parseInt(choice.substring(choice.indexOf("~") + 1));
			this._waypoints.push(sysID);
			player.credits -= this._waypointCost * stn.equipmentPriceFactor;
			this.$sendPurchasingEmail(expandDescription("[blackmarket_email_rh_waypoint]", { dest: System.infoForSystem(galaxyNumber, sysID).name }),
				this._waypointCost * stn.equipmentPriceFactor);
		} else {
			player.consoleMessage(expandDescription("[blackmarket_no_cash]"));
		}
	}
	if (choice.indexOf("62_") >= 0) {
		if (this._sting && p.dockedStation === this._stingStation) {
			this.$stingOperation();
			return;
		}
		if (player.credits >= this._phaseScanCost * stn.equipmentPriceFactor) {
			var sysID = parseInt(choice.substring(choice.indexOf("~") + 1));
			var phase = se.$getSystemPhase(sysID);
			se.$addPhaseScan(phase, sysID, 2);
			player.credits -= this._phaseScanCost * stn.equipmentPriceFactor;
			this.$sendPurchasingEmail(expandDescription("[blackmarket_email_phase_scan]", { dest: System.infoForSystem(galaxyNumber, sysID).name }),
				this._phaseScanCost * stn.equipmentPriceFactor);
		} else {
			player.consoleMessage(expandDescription("[blackmarket_no_cash]"));
		}
	}
	if (choice.indexOf("63_") >= 0) {
		if (this._sting && p.dockedStation === this._stingStation) {
			this.$stingOperation();
			return;
		}
		var idx = parseInt(choice.substring(choice.indexOf("~") + 1));
		var list = se.$getSellablePhaseScans();
		se.$sellPhaseScan(list[idx].gov, list[idx].tl);
		player.credits += (this._phaseScanCost * stn.equipmentPriceFactor) * 0.8;
		this.$sendSaleEmail(expandDescription("[blackmarket_email_govtype_phase_scan]", { gov: this.$governmentDescription(list[idx].gov), tl: (list[idx].tl + 1) }), (this._phaseScanCost * stn.equipmentPriceFactor) * 0.8);
	}
	if (choice.indexOf("74_SELL") >= 0) { // sell cargo from smuggling compartment
		this._sellCommodity = choice.substring(choice.indexOf("~") + 1);
		this._sellQuantity = se.$getCargoQuantity(this._sellCommodity);
		this._sellType = 2; // smuggling compartment
		this._display = 9;
	}
	if (choice.indexOf("73_SELL") >= 0) { // sell cargo from standard hold
		var sdm = worldScripts.Smugglers_DockMaster;
		this._sellCommodity = choice.substring(choice.indexOf("~") + 1);
		this._sellQuantity = p.manifest[this._sellCommodity];
		// remove any relabelled cargo
		if (sdm && sdm.$getTotalRelabelled(this._sellCommodity) > 0) this._sellQuantity = this._sellQuantity - sdm.$getTotalRelabelled(viscargo[i].commodity);
		this._sellType = 1; // standard hold
		this._display = 9;
	}
	if (choice.indexOf("50_OTHERITEM") >= 0) {
		// todo: make it possible for the player to not find a buyer
		var idx = parseInt(choice.substring(choice.indexOf("~") + 1));
		// tell the calling script the item was sold, passing back the item key and the sell price
		worldScripts[this._additionalSaleItems[idx].worldScript][this._additionalSaleItems[idx].sellCallback](this._additionalSaleItems[idx].key, parseInt((this._additionalSaleItems[idx].cost * this._systemFactor) * 10) / 10);
		// remove the item from the list
		player.credits += parseInt((this._additionalSaleItems[idx].cost * this._systemFactor) * 10) / 10;
		this._additionalSaleItems.splice(idx, 1);
	}
	if (choice != "99_EXIT" && choice != "02_CONTRACTS") {
		this.$blackMarketOptions();
	}
}
//-------------------------------------------------------------------------------------------------------------
// sends an email confirming black market purchase
this.$sendPurchasingEmail = function $sendPurchasingEmail(itemText, cost) {
	var email = worldScripts.EmailSystem;
	if (email) {
		var ga = worldScripts.GalCopAdminServices;
		if (this._blackMarketSalesPerson === "") {
			this._blackMarketSalesPerson = expandDescription("%N [nom]");
		}
		email.$createEmail({
			sender: expandDescription("[blackmarket_email_sender]"),
			subject: expandDescription("[blackmarket_email_subject_buy]"),
			date: clock.seconds,
			message: expandDescription("[blackmarket-purchasing]", {
				item: itemText,
				itemcost: cost.toFixed(2),
				sender: this._blackMarketSalesPerson
			}),
			expiryDays: ga._defaultExpiryDays
		});
	}
}
//-------------------------------------------------------------------------------------------------------------
// sends an email confirming black market sale
this.$sendSaleEmail = function $sendSaleEmail(itemText, cost) {
	var email = worldScripts.EmailSystem;
	if (email) {
		var ga = worldScripts.GalCopAdminServices;
		if (this._blackMarketSalesPerson === "") {
			this._blackMarketSalesPerson = expandDescription("%N [nom]");
		}
		email.$createEmail({
			sender: expandDescription("[blackmarket_email_sender]"),
			subject: expandDescription("[blackmarket_email_subject_sell]"),
			date: clock.seconds,
			message: expandDescription("[blackmarket-sale]", {
				item: itemText,
				itemcost: cost.toFixed(2),
				sender: this._blackMarketSalesPerson
			}),
			expiryDays: ga._defaultExpiryDays
		});
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$buildNoticeboard = function $buildNoticeboard() {
	function compare(a, b) {
		if (a.idx < b.idx) return -1;
		if (a.idx > b.idx) return 1;
		return 0;
	}
	this._noticeboard = [];
	// type 1: dock master bribe mesages
	// type 2: suggested phase settings
	// type 3: where to get good fake permits
	// type 4: random chatter
	// type 5: cargo stash (or not, maybe)
	var sdm = worldScripts.Smugglers_DockMaster;
	var si = worldScripts.Smugglers_Illegal;
	var se = worldScripts.Smugglers_Equipment;
	var finalcount = 0;
	// type 1: bribe messages
	// get all systems within 15ly
	if (this._smugglers) {
		var sys = system.info.systemsInRange(15);
		var list = [];
		// look for any with a low bribe chance (< 500cr)
		for (var i = 0; i < sys.length; i++) {
			if (sys[i].hasOwnProperty("sun_gone_nova") === false || sys[i].sun_gone_nova === false) {
				var chance = sdm._bribeChance[sys[i].systemID];
				if (chance <= 0.22) {
					// decide whether or not to show it (max 2)
					if (list.length < 2 && Math.random() > 0.6) {
						var amount = parseInt(parseInt(((chance + 0.1) * 32) ^ 2) * 100) / 100;
						list.push({
							system: sys[i].name,
							amount: amount
						});
					}
				}
			}
		}
		for (var i = 0; i < list.length; i++) {
			var msg = expandDescription("[noticeboard-dmbribe]", {
				planet: list[i].system,
				amount: list[i].amount
			});
			this._noticeboard.push({
				idx: this.$rand(100),
				message: msg
			});
		}
		// type 3: fake permits
		finalcount = 0 + (Math.random() > 0.5 ? 1 : 0);
		for (var i = 0; i < sys.length; i++) {
			if (this._fakePermits[sys[i].systemID] >= 0.8) {
				if (Math.random() > 0.7) {
					var msg = expandDescription("[noticeboard-fakepermit]", {
						planet: sys[i].name
					});
					this._noticeboard.push({
						idx: this.$rand(100),
						message: msg
					});
					finalcount += 1;
				}
				// break out if we hit the limit
				if (finalcount >= 2) break;
			}
		}
		// type 2: phase settings
		// will never be super accurate, but will be better than nothing.
		// get all systems with 25ly with illegal goods
		sys = system.info.systemsInRange(25);
		list = [];
		var count = 0;
		for (var i = sys.length - 1; i >= 0; i--) {
			var goods = si.$illegalGoodsList(sys[i].systemID);
			var suggest_phase = se.$getSystemPhase(sys[i].systemID) + (this.$rand(50) * (Math.random() > 0.5 ? 1 : -1));
			// make sure we have a positive number
			if (suggest_phase < 0) suggest_phase = se.$getSystemPhase(sys[i].systemID) + (this.$rand(20) + 20);
			// make sure we don't go over the limit
			if (suggest_phase >= 1000) suggest_phase = se.$getSystemPhase(sys[i].systemID) + ((this.$rand(20) + 20) * -1);
			if (goods.length >= 0) {
				list.push({
					system: sys[i].name,
					phase: suggest_phase,
					hasIllegal: true
				});
				count += 1;
			} else {
				list.push({
					system: sys[i].name,
					phase: suggest_phase,
					hasIllegal: false
				});
			}
		}
		// pick 1 or (at most) 2
		finalcount = 0 + (Math.random() > 0.5 ? 1 : 0);
		if (count > 0) {
			// pick the ones with illegal goods
			for (var i = 0; i < list.length; i++) {
				if (list[i].hasIllegal === true && Math.random() > 0.7) {
					var msg = expandDescription("[noticeboard-phasescan]", {
						planet: list[i].system,
						phase: list[i].phase
					});
					this._noticeboard.push({
						idx: this.$rand(100),
						message: msg
					});
					finalcount += 1;
				}
				// break out if we hit the limit
				if (finalcount >= 2) break;
			}
		} else {
			// pick the ones with illegal goods
			for (var i = 0; i < list.length; i++) {
				if (Math.random() > 0.7) {
					var msg = expandDescription("[noticeboard-phasescan]", {
						planet: list[i].system,
						phase: list[i].phase
					});
					this._noticeboard.push({
						idx: this.$rand(100),
						message: msg
					});
					finalcount += 1;
				}
				// break out if we hit the limit
				if (finalcount >= 2) break;
			}
		}
		// type 5: phase updates
		finalcount = 0;
		if (se._phaseUpdates.length > 0) {
			// get a list of planets in 25 LY that have had a setting change.
			for (var i = 0; i < sys.length; i++) {
				for (var j = 0; j < se._phaseUpdates.length; j++) {
					if (sys[i].government === se._phaseUpdates[j].gov && sys[i].techlevel === se._phaseUpdates[j].tl) {
						finalcount += 1;
						var msg = expandDescription("[noticeboard-phaseupdate]", {
							planetname: sys[i].name
						});
						this._noticeboard.push({
							idx: this.$rand(100),
							message: msg
						});
					}
				}
			}
		}
	}
	// type 4: random chatter
	finalcount = 0;
	var randomchatter = this.$rand(3);
	var checking = [];
	missionVariables.randomphone = (this.$rand(899999) + 100000) + "-" + (this.$rand(899) + 100) + "-" + (this.$rand(89999) + 10000);
	for (var i = 1; i <= randomchatter; i++) {
		var msg = expandDescription("[noticeboard-randomchatter]");
		if (checking.indexOf(msg) === -1) {
			this._noticeboard.push({
				idx: this.$rand(100),
				message: msg
			});
			checking.push(msg);
		}
	}
	delete missionVariables.randomphone;
	// type 5: cargo stash
	var stash = this.$rand(3) - 1; // between 0 and 2
	var sys = system.info.systemsInRange(10);
	for (var i = 0; i < stash; i++) {
		var s = sys[Math.floor(Math.random() * sys.length)];
		// make sure we don't add two stashes in the same system
		var found = false;
		for (var i = 0; i < this._stash.length; i++) {
			if (this._stash[i].systemID == s.systemID) found = true;
		}
		if (found == true) continue;
		this._stash.push({ systemID: s.systemID, created: clock.adjustedSeconds });
		missionVariables.stashdist = parseInt(system.info.distanceToSystem(s) * 10) / 10;
		var msg = expandDescription("[noticeboard-cargo-stash]");
		this._noticeboard.push({
			idx: this.$rand(100),
			message: msg
		});
		delete missionVariables.stashdist;
	}
	// sort the list, based on the random index, so the items are mixed up.
	this._noticeboard.sort(compare);
}
//-------------------------------------------------------------------------------------------------------------
// builds the fake permit array
this.$buildFakePermitArray = function $buildFakePermitArray() {
	this._fakePermits = [];
	for (var i = 0; i <= 255; i++) {
		// fake permits can have, at best, a 40% chance of success
		var chance = (Math.random() * 40) / 100;
		if (chance > 0.5) {
			this._fakePermits.push(chance);
		} else {
			this._fakePermits.push(0);
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
// adds a waypoint to the first rock hermit if the player has purchased a waypoint for this system
this.$addRHWaypoint = function $addRHWaypoint() {
	// add a waypoint to the rock hermit, if the player has one
	if (this._waypoints.indexOf(system.ID) >= 0) {
		if (this._debug) log(this.name, "A Rock Hermit waypoint has been found for the current system");
		var stns = system.stations;
		for (var i = 0; i < stns.length; i++) {
			// is this a rockhermit?
			//log(this.name, "station " + stns[i].name);
			if (Ship.roleIsInCategory(stns[i].primaryRole, "oolite-rockhermits") && stns[i].hasRole("slaver_base") === false && stns[i].hasRole("rrs_slaverbase") === false) {
				if (this._debug) log(this.name, "A Rock Hermit has been found in the current system -- adding waypoint");
				// is the waypoint already set?
				if (!system.waypoints[this.name]) {
					system.setWaypoint(
						this.name, stns[i].position, stns[i].orientation, {
						size: 100,
						beaconCode: "H",
						beaconLabel: expandDescription("[blackmarket_rh_beacon]")
					}
					);
					break;
				}
			}
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$doesStationHaveBlackMarket = function $doesStationHaveBlackMarket(station) {
	if (this.$isBlackMarketShutdown(station) === true) return false;
	var alleg = station.allegiance;
	if (alleg == null) alleg = "neutral";
	if (((this._smugglingAllegiance.indexOf(alleg) >= 0 || this.$isStationIncluded(station) === true) && this.$isStationExcluded(station) === false) ||
		(station.isMainStation && this.$mainStationHasBlackMarket() === true)) return true;
	return false;
}
//-------------------------------------------------------------------------------------------------------------
// determines whether the black market will be visible in certain systems at the main station
this.$mainStationHasBlackMarket = function $mainStationHasBlackMarket() {
	// for tl 4 and below, at Anarchies, Feudals, Multi-Gov, Communists, Dictatorships, there's a 70% chance
	if (system.info.techlevel <= 3 && system.government <= 4 && system.pseudoRandomNumber > 0.3) return true;
	// for tl 5/6, at Anarchies, Feudals, Multi-Gov, there's a 50% chance
	if (system.info.techlevel >= 4 && system.info.techlevel <= 6 && system.government <= 2 && system.pseudoRandomNumber > 0.5) return true;
	// for tl 7+, at Anarchies only, have a reducing chance
	if (system.info.techlevel > 6 && system.government === 0 && system.pseudoRandomNumber > (system.info.techlevel / 10)) return true
	// otherwise false
	return false;
}
//-------------------------------------------------------------------------------------------------------------
this.$governmentDescription = function $governmentDescription(gov) {
	switch (gov) {
		case 0: return expandDescription("[blackmarket_gov_anarchy]");
		case 1: return expandDescription("[blackmarket_gov_feudal]");
		case 2: return expandDescription("[blackmarket_gov_multigov]");
		case 3: return expandDescription("[blackmarket_gov_dictatorship]");
		case 4: return expandDescription("[blackmarket_gov_communist]");
		case 5: return expandDescription("[blackmarket_gov_confederacy]");
		case 6: return expandDescription("[blackmarket_gov_democracy]");
		case 7: return expandDescription("[blackmarket_gov_corpstate]");
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$setupSting = function $setupSting(station) {
	this._stingStation = station;
	this._sting = true;
	// there's a chance that there'll be a police ship at the edge of scanner range at station where sting operation is in progress
	// if the player has never been to the black market, though, we need a clue other than the name of the agent. So we must include the ship in that case
	if (Math.random() > this._stingShipChance || this.$playerHasBeenToBlackMarket(station) === false) {
		this.$addStingShip(station);
	}
	if (this._debug) {
		//log(this.name, "Sting Operation In Progress at " + this._stingStation.displayName);
		//if (this._stingShip) log(this.name, "Added police ship clue");
		// flag the station so we can easily find it for testing
		if (!this._stingStation.beaconCode || this._stingStation.beaconCode === "") {
			this._stingStation.beaconCode = "S";
			this._stingStation.beaconLabel = "Sting operation in progress";
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
// adds the sting ship to the station
this.$addStingShip = function $addStingShip(station) {
	if (!station || station.isValid === false) return;
	// reduce the chance we'll get another sting ship each time we get one.
	if (this._stingShipChance < 0.9) this._stingShipChance += 0.1;
	// work out the position of the rh dock
	var dock = null;
	var dockpos = null;
	// find the dock object of the station so we can position launched ships
	for (var i = 0; i < station.subEntities.length; i++) {
		if (station.subEntities[i].isDock) {
			dock = station.subEntities[i];
			dockpos = station.position.add( //better formula for non-centered docks
				Vector3D(
					station.heading.direction().multiply(dock.position.z),
					station.vectorUp.direction().multiply(dock.position.y),
					station.vectorRight.direction().multiply(dock.position.x)
				)
			);
			break;
		}
	}
	// move away from dock in straight line, but backwards, towards the edge of scanner range...
	var pos = dockpos.add(station.vectorForward.multiply(-(player.ship.scannerRange * 0.92)));
	// add a police ship at that position, but switch it to null AI so it will just sit there
	this._stingShip = system.addShips("police", 1, pos, 1000)[0];
	if (this._stingShip) {
		this._stingShip.switchAI("nullAI.plist");
		// set up the ship so if it's attacked it doesn't just sit there.
		// monkeypatch if necessary
		if (this._stingShip.script.shipBeingAttacked) this._stingShip.script.$sbm_ovr_shipBeingAttacked = this._stingShip.script.shipBeingAttacked;
		this._stingShip.script.shipBeingAttacked = this.$policeSting_shipBeingAttacked;
		if (this._stingShip.script.shipBeingAttackedByCloaked) this._stingShip.script.$sbm_ovr_shipBeingAttackedByCloaked = this._stingShip.script.shipBeingAttackedByCloaked;
		this._stingShip.script.shipBeingAttackedByCloaked = this.$policeSting_shipBeingAttackedByCloaked;
		this._stingShipTimer = new Timer(this, this.$checkForPlayerProximity, 5, 5);
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$policeSting_shipBeingAttacked = function $policeSting_shipBeingAttacked(whom) {
	// run monkey patch, if required
	if (this.ship.script.$sbm_ovr_shipBeingAttacked) this.ship.script.$sbm_ovr_shipBeingAttacked(whom);
	// under attack, so revert to police AI
	var sbm = worldScripts.Smugglers_BlackMarket;
	sbm._stingShipTimer.stop();
	this.ship.switchAI("oolite-policeAI.js");
	this.ship.target = whom;
	// if the attacker is anyone but the player, then the sting is a bust
	if (whom.isPlayer === false) {
		sbm._stingStation = null;
		sbm._sting = false;
	}
	sbm._stingShip = null;
	sbm.$policeSting_removeMonkeyPatches(this.ship);
}
//-------------------------------------------------------------------------------------------------------------
this.$policeSting_shipBeingAttackedByCloaked = function $policeSting_shipBeingAttackedByCloaked() {
	// run monkey patch, if required
	if (this.ship.script.$sbm_ovr_shipBeingAttackedByCloaked) this.ship.script.$sbm_ovr_shipBeingAttackedByCloaked();
	// under attack, so revert to police AI
	var sbm = worldScripts.Smugglers_BlackMarket;
	sbm._stingShipTimer.stop();
	this.ship.switchAI("oolite-policeAI.js");
	sbm._stingStation = null;
	sbm._sting = false;
	sbm._stingShip = null;
	sbm.$policeSting_removeMonkeyPatches(this.ship);
}
//-------------------------------------------------------------------------------------------------------------
this.$policeSting_removeMonkeyPatches = function $policeSting_removeMonkeyPatches(ship) {
	// remove monkey patches
	if (!ship) return;
	delete ship.script.shipBeingAttacked;
	if (ship.script.$sbm_ovr_shipBeingAttacked) {
		ship.script.shipBeingAttacked = ship.script.$sbm_ovr_shipBeingAttacked;
		delete ship.script.$sbm_ovr_shipBeingAttacked;
	}
	delete ship.script.shipBeingAttackedByCloaked;
	if (ship.script.$sbm_ovr_shipBeingAttackedByCloaked) {
		ship.script.shipBeingAttackedByCloaked = ship.script.$sbm_ovr_shipBeingAttackedByCloaked;
		delete ship.script.$sbm_ovr_shipBeingAttackedByCloaked;
	}
}
//-------------------------------------------------------------------------------------------------------------
// timer deatination to check if the player has moved within 10km of sting ship
this.$checkForPlayerProximity = function $checkForPlayerProximity() {
	if (this._stingShip == null || this._stingShip.isValid === false || this._stingShip.isInSpace === false) {
		this._stingShipTimer.stop();
		return;
	}
	// if the player comes in range, switch back to police mode
	if (player.ship && player.ship.isValid && player.ship.isInSpace && this._stingShip.position.distanceTo(player.ship) < 10000) {
		this._stingShipTimer.stop();
		delete this._stingShip.script.shipBeingAttacked;
		this._stingShip.switchAI("oolite-policeAI.js");
		this._stingShip = null;
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$playerHasBeenToBlackMarket = function $playerHasBeenToBlackMarket(station) {
	for (var i = 0; i < this._blackMarketContacts.length; i++) {
		var contact = this._blackMarketContacts[i];
		if (contact.galaxy === galaxyNumber && contact.systemID === system.ID && contact.stationName === station.displayName) return true;
	}
	return false;
}
//-------------------------------------------------------------------------------------------------------------
this.$getBlackMarketContact = function $getBlackMarketContact(station) {
	// check if a sting is in progress at this station
	if (this._sting && station === this._stingStation) return expandDescription("%N [nom]");
	// see if we already have a contact for this station
	var contactName = "";
	for (var i = 0; i < this._blackMarketContacts.length; i++) {
		var contact = this._blackMarketContacts[i];
		if (contact.galaxy === galaxyNumber && contact.systemID === system.ID && contact.stationName === station.displayName) {
			contactName = contact.name;
			break;
		}
	}
	if (contactName === "") {
		contactName = expandDescription("%N [nom]");
		this._blackMarketContacts.push({
			galaxy: galaxyNumber,
			systemID: system.ID,
			stationName: station.displayName,
			name: contactName
		});
	}
	return contactName;
}
//-------------------------------------------------------------------------------------------------------------
this.$stingOperation = function $stingOperation() {
	var p = player.ship;
	this._newBounty = player.bounty + (parseInt(Math.random() * 10) + 20);
	this._blackMarketShutdown.push({
		galaxy: galaxyNumber,
		system: system.ID,
		stationName: p.dockedStation.displayName,
		date: clock.seconds
	});
	p.dockedStation.setInterface(this.name, null);
	this._noBribe = false;
	this.$runStingScreen();
	this._caughtDate = clock.seconds;
}
//-------------------------------------------------------------------------------------------------------------
this.$runStingScreen = function $runStingScreen() {
	if (player.credits > 2000) {
		var pct = 0.1;
		for (var i = 0; i < 4; i++) {
			if (player.credits * pct < 100000) pct += 0.1;
		}
		this._newPenalty = Math.floor(player.credits * pct);
		if (this._newPenalty > 100000) this._newPenalty = 100000;
		var curChoices = {};
		if (player.credits > 0) {
			if (this._noBribe === false) {
				curChoices["03_BRIBE"] = {
					text: "[blackmarket-attempt-bribe]",
					color: this._menuColor
				};
			}
			curChoices["01_CREDIT_ACCEPT"] = {
				text: "[blackmarket-credits-accept-penalty]",
				color: this._menuColor
			};
		}
		curChoices["02_LEGAL_ACCEPT"] = {
			text: "[blackmarket-legal-accept-penalty]",
			color: this._menuColor
		};
		mission.runScreen({
			screenID: "blackmarket-sting",
			title: expandDescription("[blackmarket_galcop_security]"),
			model: "[viper]",
			message: expandDescription("[blackmarket-sting]", {
				penalty: formatCredits(this._newPenalty, false, true)
			}),
			exitScreen: "GUI_SCREEN_STATUS",
			allowInterrupt: false,
			choices: curChoices,
			initialChoicesKey: "01_CREDIT_ACCEPT"
		}, this.$stingScreenHandler, this);
	} else {
		mission.runScreen({
			screenID: "blackmarket-sting",
			title: expandDescription("[blackmarket_galcop_security]"),
			model: "[viper]",
			message: expandDescription("[blackmarket-sting-no-credits]"),
			exitScreen: "GUI_SCREEN_INTERFACES",
		}, this.$stingScreenHandler, this);
		player.ship.setBounty(this._newBounty, "underworld activity");
		this.$sendStingNotificationEmail("legal");
		this._newBounty = 0;
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$stingScreenHandler = function $stingScreenHandler(choice) {
	if (choice === "03_BRIBE") {
		this.$getBribeAmount();
		return;
	}
	if (choice === "01_CREDIT_ACCEPT") {
		player.credits -= this._newPenalty;
		this.$sendStingNotificationEmail("credits");
		this._newPenalty = 0;
		return;
	}
	if (choice === "02_LEGAL_ACCEPT") {
		player.ship.setBounty(this._newBounty, "underworld activity");
		this.$sendStingNotificationEmail("legal");
		this._newBounty = 0;
		return;
	}
	if (choice === "01_RETURN") {
		this.$runStingScreen();
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$isBlackMarketShutdown = function $isBlackMarketShutdown(station) {
	for (var i = 0; i < this._blackMarketShutdown.length; i++) {
		var item = this._blackMarketShutdown[i];
		// 2592000 = 30 days in seconds
		if (item.galaxy === galaxyNumber && item.systemID === system.ID && item.stationName === station.displayName && (clock.seconds - item.date) < 2592000) return true;
	}
	return false;
}
//-------------------------------------------------------------------------------------------------------------
this.$setupStingAfterLoad = function $setupStingAfterLoad() {
	// configure the sting op from saved data
	var stnName = missionVariables.Smuggling_BlackMarketStingStation;
	var stns = system.stations;
	var found = false;
	for (var i = 0; i < stns.length; i++) {
		if (stns[i].displayName === stnName) {
			this._stingStation = stns[i];
			found = true;
			break;
		}
	}
	if (found) {
		if (missionVariables.BlackMarketStingShip) this.$addStingShip(this._stingStation);
	} else {
		// couldn't find the same station, so turn off the sting
		this._sting = false;
	}
	delete missionVariables.Smuggling_BlackMarketSting;
	delete missionVariables.Smuggling_BlackMarketStingStation;
	delete missionVariables.Smuggling_BlackMarketStingShip;
}
//-------------------------------------------------------------------------------------------------------------
// calculates a number between 0.2 and 1.7 (approx) 
this.$setupSystemFactor = function $setupSystemFactor() {
	// low tech, anarchic, low economy worlds will pay a premium, hitech worlds not so much
	var eco = system.economy; // 0 to 7 (7 = poor ag)
	var tl = 15 - system.techLevel; // 0 to 14; we're inverting this so that low tech worlds are rated higher
	var gov = (8 - system.government) * 2; // 0 - 7 (0 = anarchic) we're inverting this so that anarchic worlds are rated higher. we're also giving more weight to this number
	// convert this numbers into a factor with a random element as well
	var factor = ((eco + tl + gov) / 21) * (system.scrambledPseudoRandomNumber(clock.days) * 0.4) + 0.5;;
	if (factor < 0.2) factor = 0.2
	this._systemFactor = factor;
}
//-------------------------------------------------------------------------------------------------------------
this.$sellItemCallback = function $sellItemCallback(key) {
	if (player.ship.equipmentStatus(key) === "EQUIPMENT_OK") player.ship.removeEquipment(key);
}
//-------------------------------------------------------------------------------------------------------------
this.$addCoreEquipmentToBlackMarket = function $addCoreEquipmentToBlackMarket() {
	// make sure the list is clear for this worldscript
	this.$removeWorldScriptItems("Smugglers_BlackMarket");
	// add some rare equipment to the sale item list, if they're still functioning anyway - can't sell damaged equipment!
	if (player.ship.equipmentStatus("EQ_CLOAKING_DEVICE") === "EQUIPMENT_OK") {
		var item = EquipmentInfo.infoForKey("EQ_CLOAKING_DEVICE");
		this.$addSaleItem({
			key: "EQ_CLOAKING_DEVICE",
			text: item.name,
			cost: parseInt(item.price / 10),
			worldScript: "Smugglers_BlackMarket",
			sellCallback: "$sellItemCallback"
		});
	}
	var eq = player.ship.equipment;
	for (var i = 0; i < eq.length; i++) {
		if (eq[i].equipmentKey.indexOf("NAVAL") >= 0 || eq[i].equipmentKey.indexOf("MILITARY") >= 0) {
			var item = EquipmentInfo.infoForKey(eq[i].equipmentKey);
			this.$addSaleItem({
				key: eq[i].equipmentKey,
				text: item.name,
				cost: parseInt(item.price / 10),
				worldScript: "Smugglers_BlackMarket",
				sellCallback: "$sellItemCallback"
			});
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$getBribeAmount = function $getBribeAmount() {
	var text = "";
	var inttype = Math.floor((this._bribeChance * 4) + 1);
	var inttypedesc = expandDescription("[bribe-interest-type" + inttype + "]");
	text = expandDescription("[bribe-question]", {
		interesttype: inttypedesc
	}) + "\n\n" +
		"(" + expandDescription("[bribe-cost]", {
			credits: formatCredits(player.credits, false, true)
		}) + ")";
	var opts = {
		screenID: "smuggling_sting_bribe",
		title: expandDescription("[blackmarket_bribe_official]"),
		allowInterrupt: false,
		model: "[viper]",
		exitScreen: "GUI_SCREEN_STATUS",
		message: text,
		textEntry: true
	};
	mission.runScreen(opts, this.$getBribeAmountInput, this);
}
//-------------------------------------------------------------------------------------------------------------
// parses the input from the getBribeAmount screen
this.$getBribeAmountInput = function $getBribeAmountInput(param) {
	var p = player.ship;
	if (parseInt(param) >= 1 && parseInt(param) <= player.credits) {
		var amount = parseInt(param);
		// will this work
		var chance = this._bribeChance;
		// higher amounts are more likely to be accepted
		if (this._debug) log(this.name, "min bribe amount = " + (Math.pow(parseInt(chance * 50), 2.5) * (1 + system.productivity / 56300)));
		if ((Math.pow(parseInt(chance * 50), 2.5) * (1 + system.productivity / 56300)) <= amount) {
			player.credits -= amount;
			mission.runScreen({
				screenID: "sting_bribe",
				title: expandDescription("[blackmarket_bribe_official]"),
				model: "[viper]",
				message: expandDescription("[bribe-complete]"),
				exitScreen: "GUI_SCREEN_STATUS"
			});
		} else {
			// a failed bribe will result in the legal penalty being applied
			p.setBounty(this._newBounty, "underworld activity");
			this.$sendStingNotificationEmail("legal");
			this._newBounty = 0;
			if (Math.random() > chance) {
				mission.runScreen({
					screenID: "sting_bribe",
					title: expandDescription("[blackmarket_bribe_official]"),
					model: "[viper]",
					message: expandDescription("[bribe-angry-nopenalty]"),
					exitScreen: "GUI_SCREEN_STATUS"
				});
			} else {
				var penalty = (worldScripts.Smugglers_CoreFunctions.$rand(10) + 3);
				p.setBounty(player.bounty + penalty, "attempted bribe");
				mission.runScreen({
					screenID: "sting_bribe",
					title: expandDescription("[blackmarket_bribe_official]"),
					model: "[viper]",
					message: expandDescription("[bribe-angry-penalty]"),
					exitScreen: "GUI_SCREEN_STATUS"
				});
				// send email (if installed)
				var email = worldScripts.EmailSystem;
				if (email) {
					var ga = worldScripts.GalCopAdminServices;
					email.$createEmail({
						sender: expandDescription("[blackmarket_email_galcop_customs]"),
						subject: expandDescription("[blackmarket_email_bribe_subject]"),
						date: clock.seconds,
						message: expandDescription("[failed-bribe-email]", {
							legal_penalty: penalty,
							stationname: player.ship.dockedStation.displayName,
							systemname: System.systemNameForID(system.ID)
						}),
						expiryDays: ga._defaultExpiryDays
					});
				}
			}
		}
	} else {
		this._noBribe = true;
		mission.runScreen({
			screenID: "smuggling_bribe",
			title: expandDescription("[blackmarket_bribe_official]"),
			model: "[viper]",
			message: expandDescription("[blackmarket-sting-skipbribe]"),
			exitScreen: "GUI_SCREEN_STATUS",
			choices: {
				"01_RETURN": {
					text: "[blackmarket_press_enter]"
				}
			},
		}, this.$stingScreenHandler, this);
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$sendStingNotificationEmail = function $sendStingNotificationEmail(type) {
	// send email (if installed)
	var email = worldScripts.EmailSystem;
	if (email) {
		var ga = worldScripts.GalCopAdminServices;
		email.$createEmail({
			sender: expandDescription("[blackmarket_email_galcop_customs]"),
			subject: expandDescription("[blackmarket_email_illegal_subject]"),
			date: clock.seconds,
			message: expandDescription("[blackmarket-sting-email-" + type + "]", {
				legal_penalty: this.newBounty,
				credit_penalty: formatCredits(this._newPenalty, false, true),
				date: clock.clockStringForTime(this._caughtDate),
				stationname: player.ship.dockedStation.displayName,
				systemname: System.systemNameForID(system.ID)
			}),
			expiryDays: ga._defaultExpiryDays
		});
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$setupCargoStash = function () {
	var z = -0.4;
	if (system.shipsWithRole("constore").length > 0) z = -0.5;
	var pos = Vector3D(0, 0, z).fromCoordinateSystem("wpu");
	// add the cargo
	var count = this.$rand(5) + 3;
	var cargo = system.addShips("[barrel]", count, pos, 500);
	this._cargoStashCount = cargo.length;
	var cmdties = ["gold", "platinum", "gem_stones"];
	for (var i = 0; i < cargo.length; i++) {
		cargo[i].setCargo(cmdties[this.$rand(3) - 1], this.$rand(10) + 5);
		cargo[i].script.shipDied = this.$cargoDied;
	}
	var type = this.$rand(3);
	if (type == 1 && this.$rand(2) == 1) type = 2; // slightly less of a chance of a pure cargo drop
	switch (type) {
		case 1: // just cargo
			break;
		case 2: // proximity mines p.position.add(p.vectorForward.multiply(10000))
			system.addShips("bm_proximity_mine", 15, pos, 3000);
			break;
		case 3: // proximity qbomb
			var qbomb = system.addShips("bm_proximity_mine", 1, pos, 1);
			qbomb[0]["is_cascade_weapon"] = true;
			break;
		case 4: // pirate trap
			this.$createPirateTrap(this.$rand(3) + 2, pos, 10000);
			break;
	}
	// things to possibly add
	// asteroids
	if (Math.random() > 0.3) {
		system.addShips("asteroid", this.$rand(30) + 20, pos, 30600);
	}
	// derelict ship with some alloys (but not if there's proximity mines)
	if (Math.random() > 0.8 && type != 2 && type != 3) {
		this.$addDerelictShip(pos);
	}
	this._stashLocation = pos;
	if (this._stashTimer && this._stashTimer.isRunning) this._stashTimer.stop();
	this._stashTimer = new Timer(this, this.$checkPlayerLocationToStash, 3, 3);
}
//-------------------------------------------------------------------------------------------------------------
this.$cargoDied = function (whom, why) {
	var sbm = worldScripts.Smugglers_BlackMarket;
	sbm._cargoStashCount - 1;
	if (sbm._cargoStashCount <= 0) {
		sbm.$removeStashSystem();
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$checkPlayerLocationToStash = function $checkPlayerLocationToStash() {
	var p = player.ship;
	if (!p || !p.isValid) {
		this._stashTimer.stop();
		return;
	}
	if (p.position.distanceTo(this._stashLocation) < p.scannerRange) {
		this._stashTimer.stop();
		this.$removeStashSystem();
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$removeStashSystem = function () {
	for (var i = this._stash.length - 1; i >= 0; i--) {
		if (this._stash[i].systemID == system.ID) {
			this._stash.splice(i, 1);
			break;
		}
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$addDerelictShip = function (pos) {
	// add derelict ship
	// derelict ship creation code from Eric Walsh's DeepSpaceDredger OXP
	var derelict = null;
	var checkShips = system.addShips("trader", 1, pos, 5000);
	if (checkShips) derelict = checkShips[0];
	if (derelict) {
		derelict.bounty = 0;
		// set script to default, to avoid a special script for the trader doing stuff. (like setting a new AI)
		derelict.setScript("oolite-default-ship-script.js");
		derelict.switchAI("oolite-nullAI.js");
		// remove any escorts that came with the ship
		if (derelict.escorts) {
			for (var j = derelict.escorts.length - 1; j >= 0; j--) derelict.escorts[j].remove(true);
		}
		derelict.script.shipLaunchedEscapePod = this.$bm_derelict_shipLaunchedEscapePod; // function to remove the escapepod after launch.
		if (derelict.equipmentStatus("EQ_ESCAPE_POD") === "EQUIPMENT_UNAVAILABLE") derelict.awardEquipment("EQ_ESCAPE_POD");
		derelict.abandonShip(); // make sure no pilot is left behind and this command turns the ship into cargo.
		derelict.primaryRole = "bm_derelict"; // to avoid pirate attacks
		derelict.displayName = derelict.displayName + expandDescription("[blackmarket_derelict]");
		derelict.lightsActive = false;
		// add some alloys as well
		system.addShips("alloys", 5, derelict.position, 1000);
	}
}
//-------------------------------------------------------------------------------------------------------------
this.$bm_derelict_shipLaunchedEscapePod = function $bm_derelict_shipLaunchedEscapePod(pod, passengers) {
	pod.remove(true); // we don't want floating escapepods around but need them initially to create the derelict.
}
//-------------------------------------------------------------------------------------------------------------
this.$createPirateTrap = function $createPirateTrap(count, pos, spread) {
	var pop = worldScripts["oolite-populator"];
	var grp = system.addGroup("pirate", count, pos, spread);
	var gn = grp.ships;
	if (gn && gn.length > 0) {
		for (var j = 0; j < gn.length; j++) {
			// configure our pirates
			gn[j].setBounty(20 + system.info.government + count + Math.floor(Math.random() * 8), "setup actions");
			// make sure the pilot has a bounty
			gn[j].setCrew({
				name: randomName() + " " + randomName(),
				bounty: gn[j].bounty,
				insurance: 0
			});
			if (gn[j].hasHyperspaceMotor) {
				pop._setWeapons(gn[j], 1.75); // bigger ones sometimes well-armed
			} else {
				pop._setWeapons(gn[j], 1.3); // rarely well-armed
			}
			// in the safer systems, rarely highly skilled (the skilled ones go elsewhere)
			pop._setSkill(gn[j], 4 - system.info.government);
			if (Math.random() * 16 < system.info.government) {
				pop._setMissiles(gn[j], -1);
			}
			// make sure the AI is switched
			gn[j].switchAI("bm_pirateAI.js");
		}
		this._pirates.push({
			group: grp,
			position: pos
		});
	} else {
		log(this.name, "!!ERROR: Pirate trap group not spawned!");
	}
}
//-------------------------------------------------------------------------------------------------------------
// makes the pirates lurk in a particular position
this.$givePiratesLurkPosition = function $givePiratesLurkPosition() {
	var retry = false;
	for (var i = 0; i < this._pirates.length; i++) {
		var grp = this._pirates[i].group;
		var pos = this._pirates[i].position;
		if (grp.leader) {
			if (grp.leader.AIScript.oolite_priorityai) {
				grp.leader.AIScript.oolite_priorityai.setParameter("oolite_pirateLurk", pos);
				grp.leader.AIScript.oolite_priorityai.reconsiderNow();
				for (var j = 0; j < grp.ships.length; j++) {
					if (grp.leader !== grp.ships[j]) {
						var shp = grp.ships[j];
						if (shp.AIScript.oolite_priorityai) {
							shp.AIScript.oolite_priorityai.setParameter("oolite_pirateLurk", pos);
							shp.AIScript.oolite_priorityai.reconsiderNow();
						} else {
							retry = true;
							break;
						}
					}
				}
			} else {
				retry = true;
				break;
			}
		}
		if (retry === true) break;
	}
	if (retry === true) {
		this._pirateSetup = new Timer(this, this.$givePiratesLurkPosition, 1, 0);
	} else {
		this._pirates.length = 0;
	}
}
//-------------------------------------------------------------------------------------------------------------
// 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);
	var hairSpaceLength = defaultFont.measureString(hairSpace);
	// calculate number needed to fill remaining length
	var padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
	if (padsNeeded < 1) {
		// text is too long for column, so start pulling characters off
		var tmp = currentText;
		do {
			tmp = tmp.substring(0, tmp.length - 2) + ellip;
			if (tmp === ellip) break;
		} while (defaultFont.measureString(tmp) > desiredLength);
		currentLength = defaultFont.measureString(tmp);
		padsNeeded = Math.floor((desiredLength - currentLength) / hairSpaceLength);
		currentText = tmp;
	}
	// quick way of generating a repeated string of that number
	if (!leftSwitch || leftSwitch === false) {
		return currentText + new Array(padsNeeded).join(hairSpace);
	} else {
		return new Array(padsNeeded).join(hairSpace) + currentText;
	}
}
//-------------------------------------------------------------------------------------------------------------
// appends space to currentText to the specified length in 'em'
this.$padTextLeft = function $padTextLeft(currentText, desiredLength) {
	return this.$padTextRight(currentText, desiredLength, true);
}
//-------------------------------------------------------------------------------------------------------------
// return a random number between 1 and max
this.$rand = function $rand(max) {
	return Math.floor((Math.random() * max) + 1)
} |