"use strict";
this.name = "GalCopBB_Derelict";
this.author = "phkb";
this.copyright = "2017 phkb";
this.description = "Control code for derelict/blackbox missions (missions 22/23/24)";
this.license = "CC BY-NC-SA 4.0";

this._intermittentSignalTimer = null; // timer to control when an intermittent signal is sent from the derelict
this._maxLocations = 0;
this._setData = [];

//-------------------------------------------------------------------------------------------------------------
this.startUp = function () {
	var gcm = worldScripts.GalCopBB_Missions;
	// add these mission types into the main control
	var list = [22, 23, 24];
	gcm._availableMissionTypes = gcm._availableMissionTypes.concat(list);
	gcm._interstellarMissionTypes.push(23);
	this._maxLocations = gcm._positions.length;

	// exclude some equipment items from being sold/stored by ShipConfig
	var sc = worldScripts.ShipConfiguration_Core;
	if (sc) {
		sc._noSell.push("EQ_GCM_BLACK_BOX");
		sc._noSell.push("EQ_GCM_RECOVERED_CARGO");
	}
}

//-------------------------------------------------------------------------------------------------------------
// set up interstellar space mission entities here
//this.shipExitedWitchspace = function() {
this.interstellarSpaceWillPopulate = function () {
	//if (system.ID === -1) {
	var gcm = worldScripts.GalCopBB_Missions;
	var list = gcm.$getListOfMissions(true, 23);
	if (list.length > 0) {
		// loop through all active missions and see if any need to be set up for this system
		for (var i = 0; i < list.length; i++) {
			// *** type 23 - blackbox/derelict ship in interstellar
			if (((gcm._fromSystem === list[i].destination || gcm._fromSystem === list[i].source) &&
				(gcm._toSystem === list[i].destination || gcm._toSystem === list[i].source)) &&
				list[i].data.quantity === 0 &&
				list[i].data.destroyedQuantity === 0 &&
				list[i].expiry > clock.adjustedSeconds) {

				// add the derelict ship just inside scanner range
				var dist = (Math.random() * 5000) + (player.ship.scannerRange - 5200);
				var dir = Vector3D.randomDirection(dist);
				//var position = player.ship.position.add(dir);
				var position = Vector3D(0, 0, 0).add(dir);

				// add derelict ship
				// derelict ship creation code from Eric Walsh's DeepSpaceDredger OXP
				var derelict = null;
				var checkShips = null;
				for (var j = 1; j <= 5; j++) {
					checkShips = system.addShips("trader", 1, position, 1);
					if (checkShips) derelict = checkShips[0];
					if (derelict) break;
				}
				if (derelict) {
					if (gcm._rsnInstalled) derelict.shipUniqueName = gcm.$getRandomShipName(derelict, "trader");
					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.$gcm_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 = "gcm_derelict"; // to avoid pirate attacks
					derelict.displayName = derelict.displayName + expandDescription("[gcm_ship_derelict]");
					derelict.lightsActive = false;
					derelict.script._missionID = list[i].ID;
					// monkey patch if necessary
					// add our shipDied event to the derelict
					if (derelict.script.shipDied) derelict.script.$gcm_hold_shipDied = derelict.script.shipDied;
					derelict.script.shipDied = this.$gcm_derelict_shipDied;
				} else {
					log(this.name, "!!ERROR: Derelict ship not spawned!");
				}
			}
		}
	}
	//}
}

//-------------------------------------------------------------------------------------------------------------
this.systemWillPopulate = function () {

	// reset the mission populator data array
	this._setData.length = 0;
	var gcm = worldScripts.GalCopBB_Missions;
	var list = gcm.$getListOfMissions(true, [22, 24]);

	if (list.length > 0) {
		// loop through all active missions and see if any need to be set up for this system
		for (var i = 0; i < list.length; i++) {
			// *** type 22 - blackbox/derelict ship
			if (list[i].data.missionType === 22 &&
				list[i].destination === system.ID &&
				list[i].data.quantity === 0 &&
				list[i].data.destroyedQuantity === 0 &&
				list[i].expiry > clock.adjustedSeconds) {

				// based on danger level, add some pirate ships around the derelict
				var goonCount = Math.floor(Math.random() * (9 - system.info.government)) - 1;
				if (goonCount === 1) goonCount = 2; // make sure we don't have a single pirate

				this._setData.push({
					missionType: 22,
					missionID: list[i].ID,
					source: list[i].source,
					goons: goonCount,
					quantity: 0,
					target: list[i].data.targetQuantity
				});

				// add derelict ship
				system.setPopulator("gcm-derelict-" + list[i].ID, {
					callback: function (pos) {
						var gcd = worldScripts.GalCopBB_Derelict;
						var missData = gcd.$getMissionData(22);
						if (missData.target === 0) {
							// in this case, the derelict was destroyed, but the blackbox wasn't scooped
							// so spawn the black box
							var bb = null;
							var checkShips = null;
							for (var j = 1; j <= 5; j++) {
								checkShips = system.addShips("gcm_blackbox", 1, pos, 0);
								if (checkShips) bb = checkShips[0];
								if (bb) break;
							}
							if (bb) {
								bb.setScript("oolite-default-ship-script.js");
								bb.script._missionID = missData.missionID;
								bb.script._gcmSpecial = true;
								bb.script._gcmBlackBox = true;
								bb.performTumble();
								// monkey patch if necessary
								// add our shipDied event to the blackbox
								if (bb.script.shipDied) bb.script.$gcm_hold_shipDied = bb.script.shipDied;
								bb.script.shipDied = gcd.$gcm_entity_shipDied;
							} else {
								log(this.name, "!!ERROR: Black box not spawned");
							}
						} else {
							// derelict ship creation code from Eric Walsh's DeepSpaceDredger OXP
							var g = worldScripts.GalCopBB_Missions;
							var gns = worldScripts.GalCopBB_GoonSquads;
							var derelict = null;
							var checkShips = null;
							for (var j = 1; j <= 5; j++) {
								checkShips = system.addShips("trader", 1, pos, player.ship.scannerRange);
								if (checkShips) derelict = checkShips[0];
								if (derelict) break;
							}
							if (derelict) {
								if (g._rsnInstalled) derelict.shipUniqueName = g.$getRandomShipName(derelict, "trader");
								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 = gcd.$gcm_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 = "gcm_derelict"; // to avoid pirate attacks
								derelict.displayName = derelict.displayName + expandDescription("[gcm_ship_derelict]");
								derelict.lightsActive = false;
								derelict.script._missionID = missData.missionID;
								// monkey patch if necessary
								// add our shipDied event to the derelict
								if (derelict.script.shipDied) derelict.script.$gcm_hold_shipDied = derelict.script.shipDied;
								derelict.script.shipDied = gcd.$gcm_derelict_shipDied;

								if (missData.goons > 0) {
									gns.$createGoonSquad(missData.goons, derelict.position, player.ship.scannerRange * 0.5);
								}
								if (g._debug) g.$setWaypoint(pos, [0, 0, 0, 0], "D", "Debug position (22)", "22");

							} else {
								log(this.name, "!!ERROR: Derelict ship not spawned!");
							}
						}
					}.bind(this),
					priority: 120,
					location: "INNER_SYSTEM",
					locationSeed: list[i].ID
				});

				// set up the intermittent signal
				this._intermittentSignalTimer = new Timer(this, this.$sendIntermittentSignal, (Math.random() * 30) + 10, 0);
			}

			// *** type 24 - special cargo/derelict ship
			if (list[i].data.missionType === 24 &&
				list[i].destination === system.ID &&
				list[i].data.quantity === 0 &&
				list[i].data.destroyedQuantity === 0 &&
				list[i].expiry > clock.adjustedSeconds) {

				// based on danger level, add some pirate ships around the derelict
				var goonCount = Math.floor(Math.random() * (9 - system.info.government)) - 1;
				if (goonCount === 1) goonCount = 2; // make sure we don't have a single pirate

				var position = gcm.$getRandomPosition(list[i].data.locationType, 0.1, list[i].ID).position;

				this._setData.push({
					missionType: 24,
					missionID: list[i].ID,
					source: list[i].source,
					goons: goonCount,
					quantity: 0,
					target: list[i].data.targetQuantity
				});

				// add derelict ship
				system.setPopulator("gcm-derelict2-" + list[i].ID, {
					callback: function (pos) {
						var g = worldScripts.GalCopBB_Missions;
						var gns = worldScripts.GalCopBB_GoonSquads;
						var gcd = worldScripts.GalCopBB_Derelict;
						var missData = gcd.$getMissionData(24);
						if (missData.target === 0) {
							// in this case, the derelict was destroyed, but the cargo wasn't scooped
							// so spawn the cargo
							var cg = null;
							var checkShips = null;
							for (var j = 1; j <= 5; j++) {
								checkShips = system.addShips("gcm_specialcargo", 1, pos, 0);
								if (checkShips) cg = checkShips[0];
								if (cg) break;
							}
							if (cg) {
								cg.setScript("oolite-default-ship-script.js");
								cg.script._missionID = missData.missionID;
								cg.script._gcmSpecial = true;

								// monkey patch if necessary
								// add our shipDied event to the special cargo
								if (cg.script.shipDied) cg.script.$gcm_hold_shipDied = cg.script.shipDied;
								cg.script.shipDied = gcd.$gcm_entity_shipDied;
							} else {
								log("galcopBB_derelict", "!!ERROR: Special cargo not spawned");
							}
						} else {
							// derelict ship creation code from Eric Walsh's DeepSpaceDredger OXP
							var derelict = null;
							var checkShips = null;
							for (var j = 1; j <= 5; j++) {
								checkShips = system.addShips("trader-courier", 1, pos, player.ship.scannerRange);
								if (checkShips) derelict = checkShips[0];
								if (derelict) break;
							}
							if (derelict) {
								if (g._rsnInstalled) derelict.shipUniqueName = g.$getRandomShipName(derelict, "trader");
								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 = gcd.$gcm_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 = "gcm_derelict2"; // to avoid pirate attacks
								derelict.displayName = derelict.displayName + expandDescription("[gcm_ship_derelict]");
								derelict.lightsActive = false;
								derelict.script._missionID = missData.missionID;
								// monkey patch if necessary
								// add our shipDied event to the derelict
								if (derelict.script.shipDied) derelict.script.$gcm_hold_shipDied = derelict.script.shipDied;
								derelict.script.shipDied = gcd.$gcm_derelict_shipDied;

								if (missData.goons > 0) {
									gns.$createGoonSquad(missData.goons, derelict.position, player.ship.scannerRange * 0.5);
								}
								if (g._debug) g.$setWaypoint(pos, [0, 0, 0, 0], "D", "Debug position (24)", "24");

							} else {
								log("galcopBB_derelict", "!!ERROR: Derelict ship not spawned!");
							}
						}
					}.bind(this),
					priority: 130,
					location: "COORDINATES",
					coordinates: position
				});
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipWillEnterWitchspace = function (cause, destination) {
	this.$stopTimers();
}

//-------------------------------------------------------------------------------------------------------------
this.shipDied = function (whom, why) {
	this.$stopTimers();
}

//-------------------------------------------------------------------------------------------------------------
this.shipScoopedOther = function (whom) {
	var gcm = worldScripts.GalCopBB_Missions;
	var bb = worldScripts.BulletinBoardSystem;
	// black box collected
	if (whom && whom.isCargo && whom.primaryRole === "gcm_blackbox") {
		var item = bb.$getItem(whom.script._missionID);
		if (item) {
			if (item.data.destroyedQuantity === 0 && item.data.quantity === 0) {
				item.data.quantity = 1;
				bb.$updateBBMissionPercentage(item.ID, 1);

				gcm.$logMissionData(item.ID);
				player.consoleMessage(expandDescription("[goal_updated]"));

				gcm.$postScoopMissionCreation(item.ID, 7);
			}
		}
		// convert blackbox cargo into an equipment item
		player.ship.manifest["machinery"] -= 1;
		whom.remove(true);
		player.ship.awardEquipment("EQ_GCM_BLACK_BOX");
	}
	// special cargo collected
	if (whom && whom.hasRole("gcm_specialcargo") === true) {
		var item = bb.$getItem(whom.script._missionID);
		if (item) {
			item.data.quantity += 1;
			bb.$updateBBMissionPercentage(item.ID, 1);
			gcm.$logMissionData(item.ID);
			player.consoleMessage(expandDescription("[goal_updated]"));
		}
		whom.remove(true);
		player.ship.awardEquipment("EQ_GCM_RECOVERED_CARGO");
	}
	// leveraging the generic black boxes added by rescue stations
	// actual black box missions in RS use the "rescue_blackbox" primary role
	if (whom && whom.isCargo && whom.primaryRole === "rescue_blackbox_generic" && whom.script && whom.script.hasOwnProperty("_gcm_already_asked") === false) {
		if (gcm._debug) log(this.name, "found a generic blackbox");
		if (Math.random() > 0.75) {
			if (gcm._debug) log(this.name, "adding post-scoop mission to blackbox");
			gcm.$postScoopMissionCreation(-22, 7);
		}
		whom.script._gcm_already_asked = true;
	}
}

//-------------------------------------------------------------------------------------------------------------
// stop all timers
this.$stopTimers = function $stopTimers() {
	if (this._intermittentSignalTimer && this._intermittentSignalTimer.isRunning) this._intermittentSignalTimer.stop();
	delete this._intermittentSignalTimer;
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_derelict_shipDied = function $gcm_derelict_shipDied(whom, why) {
	var gcm = worldScripts.GalCopBB_Missions;
	var bb = worldScripts.BulletinBoardSystem;

	var item = bb.$getItem(this.ship.script._missionID);
	if (item.data.missionType === 22 || item.data.missionType === 23) {
		if (gcm._debug) log(this.name, "Derelict destroyed - spawning blackbox");

		// spawn the black box
		//var bkb = system.addShips("gcm_blackbox", 1, this.ship.position.add(this.ship.vectorUp.multiply((this.ship.boundingBox.y + 25) * -1)), 0)[0]; 
		var bkb = this.ship.spawnOne("gcm_blackbox");
		if (bkb) {
			bkb.setScript("oolite-default-ship-script.js");
			bkb.script._missionID = item.ID;
			bkb.script._gcmSpecial = true;
			bkb.script._gcmBlackBox = true;

			item.data.targetQuantity === 0;

			// monkey patch if necessary
			// add our shipDied event to the blackbox
			if (bkb.script.shipDied && bkb.script.$gcm_hold_shipDied == null) {
				bkb.script.$gcm_hold_shipDied = bkb.script.shipDied;
				bkb.script.shipDied = worldScripts.GalCopBB_Derelict.$gcm_entity_shipDied;
			}

			if (whom && whom.isPlayer) {
				// give the bb some velocity
				if (Math.random() > 0.1) {
					bkb.maxSpeed = player.ship.maxSpeed; // / ((Math.random() * 3) + 2);
				} else {
					bkb.maxSpeed = Math.random() * 20;
				}
				bkb.maxThrust = 10;
			} else {
				bkb.maxThrust = 1;
				bkb.thrust = 1;
			}
			// give the blackbox a heading and velocity
			bkb.orientation = Quaternion.random();
			bkb.desiredSpeed = bb.maxSpeed;

		} else {
			log(this.name, "!!ERROR: Black box not spawned");
		}
	} else if (item.data.missionType === 24) {
		var sc = this.ship.spawnOne("gcm_specialcargo");
		//var sc = system.addShips("gcm_specialcargo", 1, this.ship.position.add(this.ship.vectorUp.multiply((this.ship.boundingBox.y + 15) * -1)), 0)[0];
		if (sc) {
			sc.setScript("oolite-default-ship-script.js");
			sc.script.shipDied = gcm.$gcm_cargo_shipDied;
			sc.script._missionID = item.ID;
		} else {
			log(this.name, "!!ERROR: Special cargo not spawned")
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_derelict_shipLaunchedEscapePod = function $gcm_derelict_shipLaunchedEscapePod(pod, passengers) {
	pod.remove(true); // we don't want floating escapepods around but need them initially to create the derelict.
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_entity_shipDied = function $gcm_entity_shipDied(whom, why) {
	var gcm = worldScripts.GalCopBB_Missions;
	var bb = worldScripts.BulletinBoardSystem;

	if (gcm._debug) log(this.name, "running shipDied for " + this.ship + ": reason " + why + ", " + whom);
	if (this.ship.script.$gcm_hold_shipDied) this.ship.script.$gcm_hold_shipDied(whom, why);

	var item = bb.$getItem(this.ship.script._missionID);
	if (item) item.data.destroyedQuantity = 1;
	if (this.ship.hasRole("gcm_blackbox") === false) {
		if (gcm._distressMessageTimer && gcm._distressMessageTimer.isRunning) gcm._distressMessageTimer.stop();
	}
}

//-------------------------------------------------------------------------------------------------------------
// gets mission specific data for the populator routines
// this works on a first in/first out basis - if there are multiple missions of the same type being populated, the mission specific data would
// get pushed in to the setData array in order, and then this routine pulls that data out in the same order
// that's the theory, anyway!
this.$getMissionData = function $getMissionData(missionType) {
	for (var i = 0; i < this._setData.length; i++) {
		if (this._setData[i].missionType === missionType) {
			var result = {
				missionID: this._setData[i].missionID,
				trueMissionType: (this._setData[i].trueMissionType ? this._setData[i].trueMissionType : missionType),
				source: this._setData[i].source,
				goons: this._setData[i].goons,
				quantity: this._setData[i].quantity,
				target: this._setData[i].target
			};
			this._setData.splice(i, 1);
			return result;
		}
	}
	return null;
}

//-------------------------------------------------------------------------------------------------------------
// turns on an intermittent navigational beacon the player can use to find the derelict
this.$sendIntermittentSignal = function $sendIntermittentSignal() {
	// we should only ever get 1 ship in this array, but just in case we'll do the loop
	var targets = system.shipsWithPrimaryRole("gcm_derelict");
	if (targets && targets.length > 0) {
		for (var i = 0; i < targets.length; i++) {
			if (targets[i].isValid) {
				if (worldScripts.GalCopBB_Missions._debug) log(this.name, "activating intermittent signal");
				// get the distance to the derelict, in number of scanner ranges
				var dist = player.ship.position.distanceTo(targets[i]);
				if (dist > player.ship.scannerRange) {
					var err_dist = dist / 3;
					var pos = targets[i].position.add(Vector3D.randomDirection(err_dist));
					if (worldScripts.GalCopBB_Missions._debug) {
						log(this.name, "player dist = " + dist + ", err_dist = " + err_dist);
						log(this.name, "sigsrc = " + targets[i]);
						log(this.name, "pos = " + pos);
						log(this.name, "pos dist from derelict " + targets[i].position.distanceTo(pos));
					}

					// create dummy object that has a beacon on it
					system.addVisualEffect("gcm_unidentified_signal", pos);
					if (player.ship.isInSpace === true) {
						player.consoleMessage(expandDescription("[gcm_unrecognised_beacon]"), 3);
					}
					// setup timer to remove intermittent signal
					this._intermittentSignalTimer = new Timer(this, this.$removeIntermittentSignal, (Math.random() * 15) + 5, 0);
					break;
				} else {
					var replay = parseInt(Math.random() * 60 + 40);
					this._intermittentSignalTimer = new Timer(this, this.$sendIntermittentSignal, replay, 0);
					break;
				}
				break;
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
// turns off the intermittent signal
this.$removeIntermittentSignal = function $removeIntermittentSignal() {
	var targets = system.allVisualEffects;
	if (targets.length > 0) {
		for (var i = targets.length - 1; i >= 0; i--) {
			if (targets[i].dataKey === "gcm_unidentified_signal") targets[i].remove(true);
		}
	}
	if (worldScripts.GalCopBB_Missions._debug) log(this.name, "deactivating intermittent signal");
	if (player.ship.isInSpace) {
		player.consoleMessage(expandDescription("[gcm_beacon_lost]"), 3);
	}
	// set up a timer to show the intermittent signal again
	var derelict = system.shipsWithPrimaryRole("gcm_derelict");
	for (var i = 0; i < derelict.length; i++) {
		if (derelict[i].isValid && player.ship.position.distanceTo(derelict[i]) > player.ship.scannerRange) {
			var replay = parseInt(Math.random() * 60 + 40);
			this._intermittentSignalTimer = new Timer(this, this.$sendIntermittentSignal, replay, 0);
			break;
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$acceptedMission = function $acceptedMission(missID) {
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	var gcm = worldScripts.GalCopBB_Missions;

	if (gcm._debug) log(this.name, "accepted mission id = " + missID);

	if (!item) {
		log(this.name, "!!ERROR: BB returned null value from $getItem on mission ID " + missID);
		return;
	}
	gcm.$updateLastMissionDate(item.source, item.data.missionType);

}

//-------------------------------------------------------------------------------------------------------------
this.$completedMission = function $completedMission(missID) {
	var p = player.ship;
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	var gcm = worldScripts.GalCopBB_Missions;
	var sbm = worldScripts.Smugglers_BlackMarket;

	// update mission history
	gcm.$updateSuccessHistoryReputation(item);

	// *** type 22 & 23 - Black box recovery
	if (item.data.missionType === 22 || item.data.missionType === 23) {
		p.removeEquipment("EQ_GCM_BLACK_BOX");
		if (sbm) sbm.$removeSaleItem("EQ_GCM_BLACK_BOX:" + missID);
	}
	// *** type 24 - recovery special cargo
	if (item.data.missionType === 24) {
		p.removeEquipment("EQ_GCM_RECOVERED_CARGO");
	}

}

//-------------------------------------------------------------------------------------------------------------
this.$confirmCompleted = function $confirmCompleted(missID) {
	var p = player.ship;
	var result = "";
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	if (item) {
		// *** type 22/23 - black box recovery - make sure we still have it
		if (item.data.missionType === 22 || item.data.missionType === 23) {
			var chk = p.equipmentStatus("EQ_GCM_BLACK_BOX");
			if (chk !== "EQUIPMENT_OK" && chk !== "EQUIPMENT_DAMAGED") {
				result += (result === "" ? "" : "\n") + expandDescription("[gcm_blackbox_not_found]");
			}
			if (chk === "EQUIPMENT_DAMAGED") {
				result += (result === "" ? "" : "\n") + expandDescription("[gcm_blackbox_damaged]");
			}
		}
		// *** type 24 - special cargo - make sure we still have it
		if (item.data.missionType === 24) {
			var chk = p.equipmentStatus("EQ_GCM_RECOVERED_CARGO");
			if (chk !== "EQUIPMENT_OK" && chk !== "EQUIPMENT_DAMAGED") {
				result += (result === "" ? "" : "\n") + expandDescription("[gcm_cargo_not_found]");
			}
			if (chk === "EQUIPMENT_DAMAGED") {
				result += (result === "" ? "" : "\n") + expandDescription("[gcm_cargo_damaged]");
			}
		}
	}
	return result;
}

//-------------------------------------------------------------------------------------------------------------
this.$terminateMission = function $terminateMission(missID) {
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	var sbm = worldScripts.Smugglers_BlackMarket;
	var gcm = worldScripts.GalCopBB_Missions;

	// adjust reputation only when the terminatePenalty flag is set to true 
	if (item.data.terminatePenalty === true) {
		// update mission history
		gcm.$updateFailedHistoryReputation(item);
	}

	// *** type 22 & 23 - Black box recovery
	if (item.data.missionType === 22 || item.data.missionType === 23) {
		var eqsts = player.ship.equipmentStatus("EQ_GCM_BLACK_BOX");
		if (eqsts === "EQUIPMENT_OK" || eqsts === "EQUIPMENT_DAMAGED") {
			player.ship.removeEquipment("EQ_GCM_BLACK_BOX");
			if (sbm) sbm.$removeSaleItem("EQ_GCM_BLACK_BOX:" + missID);
		}
	}
	if (item.data.missionType === 24) {
		var eqsts = player.ship.equipmentStatus("EQ_GCM_RECOVERED_CARGO");
		if (eqsts === "EQUIPMENT_OK" || eqsts === "EQUIPMENT_DAMAGED") {
			player.ship.removeEquipment("EQ_GCM_RECOVERED_CARGO");
			if (sbm) sbm.$removeSaleItem("EQ_GCM_RECOVERED_CARGO:" + missID);
		}
	}

}

//-------------------------------------------------------------------------------------------------------------
this.$failedMission = function $failedMission(missID) {
	var bb = worldScripts.BulletinBoardSystem;
	var sbm = worldScripts.Smugglers_BlackMarket;
	var gcm = worldScripts.GalCopBB_Missions;

	if (!sbm) {
		// if there's no blackmarket option, just remove the equipment
		this.$terminateMission(missID);
		return;
	}

	var item = bb.$getItem(missID);
	// update mission history
	gcm.$updateFailedHistoryReputation(item);

	// we need to record the equipment/data element that the player will have in their possession, and the system that wanted it
	var eq = "";
	if ((item.data.missionType === 22 || item.data.missionType === 23) && item.data.quantity === 1) {
		var eqsts = player.ship.equipmentStatus("EQ_GCM_BLACK_BOX");
		if (eqsts === "EQUIPMENT_OK" || eqsts === "EQUIPMENT_DAMAGED") eq = "EQ_GCM_BLACK_BOX";
	}
	if (item.data.missionType === 24 && item.data.quantity === 1) {
		var eqsts = player.ship.equipmentStatus("EQ_GCM_RECOVERED_CARGO");
		if (eqsts === "EQUIPMENT_OK" || eqsts === "EQUIPMENT_DAMAGED") eq = "EQ_GCM_RECOVERED_CARGO";
	}

	if (eq != "") gcm._equipmentFromFailedMissions.push({
		missionType: item.data.missionType,
		source: item.source,
		equip: eq,
		date: clock.adjustedSeconds
	});
}

//-------------------------------------------------------------------------------------------------------------
this.$updateManifestEntry = function $updateManifestEntry(missID) {
	var gcm = worldScripts.GalCopBB_Missions;
	gcm.$updateManifestEntry(missID);
}

//-------------------------------------------------------------------------------------------------------------
this.$missionAvailability = function $missionAvailability(missID, missType, origSysID) {
	return "";
}

//-------------------------------------------------------------------------------------------------------------
// 22 - blackbox recovery
this.$missionType22_Values = function $missionType22_Values(workTime, routeTime, routeDistance, destSysInfo) {
	var result = {};
	result["quantity"] = 1;
	result["price"] = parseInt((parseInt(Math.random() * 50) + 100) / 10) * 10 + (7 - destSysInfo.government) * 20 +
		worldScripts.GalCopBB_CoreMissionValues.$calcDistanceBonus(routeDistance, 10) // plus a distance bonus
		+
		worldScripts.GalCopBB_CoreMissionValues.$calcPlayerBonus(500); // plus a possible bonus price, based on player score 
	result["expiry"] = clock.adjustedSeconds + routeTime + workTime; // transit time + 1 hour to complete
	result["penalty"] = parseInt(result.price / 5);
	return result;
}

//-------------------------------------------------------------------------------------------------------------
// 23 - blackbox recovery interstellar
this.$missionType23_Values = function $missionType23_Values(workTime, routeTime, routeDistance, destSysInfo) {
	var result = {};
	result["quantity"] = 1;
	result["price"] = parseInt((parseInt(Math.random() * 500) + 500) / 10) * 10 +
		worldScripts.GalCopBB_CoreMissionValues.$calcDistanceBonus(routeDistance, 10) // plus a distance bonus
		+
		worldScripts.GalCopBB_CoreMissionValues.$calcPlayerBonus(1000); // plus a possible bonus price, based on player score 
	result["expiry"] = clock.adjustedSeconds + (routeTime * 1.5) + workTime; // 1.5 transit time + 1 hour to complete
	result["penalty"] = parseInt(result.price / 5);
	return result;
}

//-------------------------------------------------------------------------------------------------------------
// 24 - recover special cargo
this.$missionType24_Values = function $missionType24_Values(workTime, routeTime, routeDistance, destSysInfo) {
	var result = {};
	result["locationType"] = Math.floor(Math.random() * this._maxLocations);
	result["quantity"] = 1;
	result["price"] = parseInt((parseInt(Math.random() * 500) + 500) / 10) * 10 +
		worldScripts.GalCopBB_CoreMissionValues.$calcDistanceBonus(routeDistance, 10) // plus a distance bonus
		+
		worldScripts.GalCopBB_CoreMissionValues.$calcPlayerBonus(500); // plus a possible bonus price, based on player score 
	result["expiry"] = clock.adjustedSeconds + routeTime + workTime; // transit time + 1 hour to complete
	result["penalty"] = parseInt(result.price / 5);
	return result;
}