Back to Index Page generated: Nov 12, 2024, 11:02:04 PM

Expansion New Cargoes

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description New Cargoes adds over 100 new specialist trade goods to the 8 galaxies through the interfaces screen. In addition, a variety of new trading opportunities are available. New Cargoes adds over 100 new specialist trade goods to the 8 galaxies through the interfaces screen. In addition, a variety of new trading opportunities are available.
Identifier oolite.oxp.cim.new-cargoes oolite.oxp.cim.new-cargoes
Title New Cargoes New Cargoes
Category Mechanics Mechanics
Author cim cim
Version 2.1 2.1
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/New_Cargoes n/a
Download URL https://wiki.alioth.net/img_auth.php/6/6c/NewCargoes_2.1.oxz n/a
License CC-BY-SA 3.0 CC-BY-SA 3.0
File Size n/a
Upload date 1708757629

Documentation

Also read http://wiki.alioth.net/index.php/New%20Cargoes

Equipment

Name Visible Cost [deci-credits] Tech-Level
TraderNet Monthly Subscription yes 10000 2+
TraderNet Monthly Subscription (Renewal) yes 7500 2+

Ships

Name
cte_angryanaconda
cte_angryboa
cte_angryboa2

Models

This expansion declares no models.

Scripts

Path
Scripts/Stations/
Scripts/Stations/cargotypestationcasino.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-HoopyCasino";
this.description = "Hoopy Casino market definition";

this.startUp = function () {
	if (!worldScripts["hoopy_casino"]) {
		return;
	}
	if (worldScripts["hoopy_casino"] && worldScripts["hoopy_casino"].randomCargoChance) {
		return;
	}
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "casinoship");
}

/* Station API */
/* RCC: 2 for space minerals, 0 otherwise
	 RCA: 5
	 ECA: 1 for space minerals, 0 otherwise
	 ECP: 0.8
	 RIC: 0
	 SIC: 0
	 ICP: 1
	 IPC: false
	 EPC: false
	 SG: false */

this.randomCargoChance = function (good) {
	var specific = worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
	if (specific == "Fine clothing") {
		return 5; // might occasionally be trying to sell on someone's shirt.
	}
	return 0; // no exports
}

this.randomCargoAmount = function (good) {
	return 1;
}

this.exportCargoAmount = function (good) {
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.8;
}

this.randomImportChance = function (good) {
	if (good == "CTE_CTS_O2") { // low energy waste
		return 1;
	} else {
		var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
		if (generic == "liquor_wines") {
			return 0.2;
		}
		return 0;
	}
}

this.systemImportChance = function (good) {
	if (good == "CTE_CTS_O2") { // low energy waste
		return 1;
	} else {
		var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
		if (generic == "liquor_wines") {
			return 0.6;
		}
		return 0;
	}
}

this.importCargoPrice = function (good) {
	return 1.2;
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	// they don't sell or buy any illegal goods, they're right next to the
	// main station, and they canonically rely on Galcop for most of their
	// defense.
	return true;
}

this.stationGossip = function () {
	if (system.shipsWithPrimaryRole("casinoship").length > 0) {
		return "* The casino always needs more random numbers and drinks.";
	}
	return false;
}
Scripts/Stations/cargotypestationcommies.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-ZGF";
this.description = "Commies ZGF and SLAPU market definition";

this.startUp = function () {
	if (!worldScripts["communist_population"]) {
		return;
	}
	if (worldScripts["communist_population"] && worldScripts["communist_population"].randomCargoChance) {
		return;
	}

	// use the same for both: tech level variation should handle the differences
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "comczgf");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "comslapu");

	// astromines generally added as rock hermits or pirate coves, so they get the same market
}

/* Station API */

this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (system.techLevel < 10) { // ZGF
		if (generic == "alloys" || generic == "machinery" || generic == "radioactives") {
			return 2; // high tech goods
		}
	} else {
		if (generic == "computers" || generic == "radioactives" || generic == "machinery") {
			return 2; // SLAPU has different selection
		}
	}
	return 0;
}

this.randomCargoAmount = function (good) {
	return 5;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (system.techLevel < 10) { // ZGF
		if (generic == "machinery" || generic == "radioactives" || generic == "alloys") {
			return Math.random() + 0.5;
		}
	} else { // SLAPU
		if (generic == "computers" || generic == "radioactives" || generic == "machinery") {
			return Math.random() + 0.5;
		}
	}
}

this.exportCargoPrice = function (good) {
	return 0.8 + (0.2 * Math.random());
}

this.randomImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "slaves") {
		return 0.05;
	} else if (generic == "computers" || generic == "machinery" || generic == "radioactives" || generic == "alloys") {
		return 0.05;
	} else {
		return 0;
	}
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "machinery" || generic == "luxuries" || generic == "alloys" || generic == "radioactives" || generic == "computers") {
		return 0.5;
	} else {
		return 0;
	}
}

this.importCargoPrice = function (good) {
	return 0.8 + (Math.random() * 0.4);
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	return false;
}

this.stationGossip = function () {
	if (system.shipsWithPrimaryRole("comslapu").length > 0) {
		return "* The SLAPU sometimes sells declassified surplus goods.";
	} else if (system.shipsWithPrimaryRole("comczgf").length > 0) {
		return "* Try the ZGF for industrial goods.";
	} else {
		return false;
	}
}
Scripts/Stations/cargotypestationconstore.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-ConStore";
this.description = "Con Store market definition";

this.startUp = function () {
	if (!worldScripts["Pi-Forty-Two Con stores"]) {
		return;
	}
	if (worldScripts["Pi-Forty-Two Con stores"] && worldScripts["Pi-Forty-Two Con stores"].randomCargoChance) {
		return;
	}
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "constore");
}

this.dayChanged = this.shipWillExitWitchspace = function () {
	delete this.$saleCargo;
}

this.saleCargo = function () {
	if (this.$saleCargo) {
		return this.$saleCargo;
	}
	var cid = worldScripts["CargoTypeExtension"].extendableCargoSeeded("any", clock.days * (300 + system.ID));
	worldScripts["CargoTypeExtension"].debug("Constore importer: " + cid);
	this.$saleCargo = cid;
	return cid;
}

/* Station API */
/* RCC: 1
	 RCA: 3
	 ECA: (randomise 0-2)
	 ECP: (randomise 0.9-1.2)
	 RIC: pick one using daily chaos method; gossip can advertise it at main station
	 SIC: 0.8
	 ICP: (randomise 0.8-1.1)
	 IPC: false
	 EPC: false
	 SG: see RIC
*/

this.randomCargoChance = function (good) {
	return 1;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	return Math.random() * 2;
}

this.exportCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.3);
}

this.randomImportChance = function (good) {
	if (good == this.saleCargo()) {
		return 1;
	}
	return 0;
}

this.systemImportChance = function (good) {
	return 0.8;
}

this.importCargoPrice = function (good) {
	return 0.8 + (Math.random() * 0.3);
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	return false;
}

this.stationGossip = function () {
	if (system.shipsWithPrimaryRole("constore").length > 0) {
		var good = this.saleCargo();
		return "* I hear someone at the Constore needs " + worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
	} else {
		return false;
	}
}
Scripts/Stations/cargotypestationdictators.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-Astrofactory";
this.description = "Dictatorship Astrofactory market definition";

this.startUp = function () {
	if (!worldScripts["dictators.js"]) {
		return;
	}
	if (worldScripts["dictators.js"] && worldScripts["dictators.js"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "astrofactory");
}

/* Station API */

this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys" || generic == "machinery" || generic == "computers") {
		return 2; // high tech goods
	}
	return 0;
}

this.randomCargoAmount = function (good) {
	return 5;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys" || generic == "machinery" || generic == "computers") {
		return Math.random() + Math.random();
	}
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.8 + (0.3 * Math.random());
}

this.randomImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "slaves") {
		return 0.1;
	} else if (generic == "computers" || generic == "machinery" || generic == "radioactives" || generic == "alloys") {
		return 0.02;
	} else {
		return 0;
	}
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "slaves") {
		return 1;
	} else {
		return 0;
	}
}

this.importCargoPrice = function (good) {
	return 0.7 + (Math.random() * 0.4);
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	return false;
}

this.stationGossip = function () {
	if (system.shipsWithPrimaryRole("astrofactory").length > 0) {
		return "* The Astrofactory is good for industrial cargo.";
	}
	return false;
}
Scripts/Stations/cargotypestationgalnavy.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-Galnavy";
this.description = "Galnavy market definition";

this.startUp = function () {
	if (!worldScripts["GalNavy"]) {
		return;
	}
	if (worldScripts["GalNavy"] && worldScripts["GalNavy"].randomCargoChance) {
		return;
	}
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "navystat");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "navystat25");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "navystat50");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "navystat75");
}

/* Station API */

this.randomCargoChance = function (good) {
	return 0;
}

this.randomCargoAmount = function (good) {
	return 1;
}

this.exportCargoAmount = function (good) {
	return 0;
}

this.exportCargoPrice = function (good) {
	return 1;
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	if (good == "CTE_CTS_A3" || good == "CTE_CTS_C3" || good == "CTE_CTS_M7") {
		return 1;
	}
	return 0;
}

this.importCargoPrice = function (good) {
	return 1;
}

this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	if (system.shipsWithRole("navySeccomBuoy").length > 0) {
		return "* The naval station always needs more ship parts.";
	}
	return false;
}
Scripts/Stations/cargotypestationgrs.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-GRS";
this.description = "GRS Buoy Repair market definition";

this.startUp = function () {
	if (!worldScripts["buoyRepair"]) {
		return;
	}

	if (worldScripts["buoyRepair"] && worldScripts["buoyRepair"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "repaired-buoy-station");
}

/* Station API */

this.randomCargoChance = function (good) {
	return 0;
}

this.randomCargoAmount = function (good) {
	return 1;
}

this.exportCargoAmount = function (good) {
	return 0;
}

this.exportCargoPrice = function (good) {
	return 1;
}

this.randomImportChance = function (good) {
	if (good == "CTE_CTS_A2" || good == "CTE_CTS_A3" || good == "CTE_CTS_A4" || good == "CTE_CTS_A5") {
		return 0.1;
	} else {
		var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
		if (generic == "alloys") {
			return 0.02;
		} else {
			return 0;
		}
	}
}

this.systemImportChance = function (good) {
	return this.randomImportChance(good);
}

this.importCargoPrice = function (good) {
	return 0.7 + (Math.random() * 0.6);
}

this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	if (system.shipsWithPrimaryRole("repaired-buoy-station").length > 0) {
		return "* If you've got alloys to sell, GRS often need more.";
	} else {
		return false;
	}
}
Scripts/Stations/cargotypestationhermit.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-RockHermit";
this.description = "Rock Hermit market definition";

this.startUp = function () {
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "rockhermit");
}

/* Station API */
/* RCC: 2 for space minerals, 0 otherwise
	 RCA: 5
	 ECA: 1 for space minerals, 0 otherwise
	 ECP: 0.8
	 RIC: 0
	 SIC: 0
	 ICP: 1
	 IPC: false
	 EPC: false
	 SG: false */

this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	var specific = worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
	if (generic != "minerals") { return 0; }
	if (specific == "Arthropod shell" || specific == "Corals") { return 0; }
	return 2;
}

this.randomCargoAmount = function (good) {
	return 5;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	var specific = worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
	if (generic != "minerals") { return 0; }
	if (specific == "Arthropod shell" || specific == "Corals") { return 0; }
	if (Math.random() < 0.5) {
		return Math.random() * 2;
	}
}

this.exportCargoPrice = function (good) {
	return 0.8;
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	return 0;
}

this.importCargoPrice = function (good) {
	return 1;
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	return false;
}

this.stationGossip = function () {
	var exports = worldScripts["CargoTypeExtension"].systemExports(galaxyNumber, system.ID)
	for (var i = 1; i <= 8; i++) {
		if (i != 3 && i != 4) {
			if (exports.indexOf("CTE_CTS_R" + i) != -1) {
				return "* Head out to the rock hermits for the best minerals deals.";
			}
		}
	}
	return false;
}
Scripts/Stations/cargotypestationkiotabio.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-KiotaBiosphere";
this.description = "Kiota Biosphere market definition";

this.startUp = function () {
	if (!worldScripts["wildShips_populator.js"]) {
		return;
	}

	if (worldScripts["wildShips_populator.js"] && worldScripts["wildShips_populator.js"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota2Sphere");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota4Sphere");
}

/* Station API */


this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "food" || generic == "liquor_wines" || generic == "textiles" || generic == "furs") {
		return 0.75;
	}
	return 0.2;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "food" || generic == "liquor_wines" || generic == "textiles" || generic == "furs") {
		return 1;
	}
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "food" || generic == "minerals") {
		return 1;
	} else {
		return 0.2;
	}
}

this.importCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

// they have police patrol ships, so they probably obey Galcop rules here
this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	return false;
}
Scripts/Stations/cargotypestationkiotacomms.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-KiotaRelay";
this.description = "Kiota Relay market definition";

this.startUp = function () {
	if (!worldScripts["wildShips_populator.js"]) {
		return;
	}

	if (worldScripts["wildShips_populator.js"] && worldScripts["wildShips_populator.js"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota2Comms");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota4Comms");
}

/* Station API */


this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	return 0.2;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	return 0.1;
}

this.exportCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys" || generic == "machinery") {
		return 0.1;
	} else if (generic == "food" || generic == "liquor_wines") {
		return 0.2;
	}
	return 0;
}

this.importCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

// they have police patrol ships, so they probably obey Galcop rules here
this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	return false;
}
Scripts/Stations/cargotypestationkiotahabitat.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-KiotaHabitat";
this.description = "Kiota Habitat market definition";

this.startUp = function () {
	if (!worldScripts["wildShips_populator.js"]) {
		return;
	}
	if (worldScripts["wildShips_populator.js"] && worldScripts["wildShips_populator.js"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota2Ring");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota4Ring");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota4RingV");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota8Ring");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota8RingV");
}

/* Station API */


this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "textiles" || generic == "slaves" || generic == "luxuries") {
		return 0.75;
	}
	return 0.2;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "textiles" || generic == "slaves" || generic == "luxuries") {
		return 1;
	}
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "computers" || generic == "radioactives" || generic == "minerals") {
		return 0.2;
	} else {
		return 1;
	}
}

this.importCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

// they have police patrol ships, so they probably obey Galcop rules here
this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	return false;
}
Scripts/Stations/cargotypestationkiotamanufacturing.js
"use strict";
this.author = "cim";
this.copyright = "© 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-KiotaFactory";
this.description = "Kiota Manufacturing market definition";

this.startUp = function () {
	if (!worldScripts["wildShips_populator.js"]) {
		return;
	}
	if (worldScripts["wildShips_populator.js"] && worldScripts["wildShips_populator.js"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota2Disc");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota4Disc");
}

/* Station API */


this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys" || generic == "machinery" || generic == "luxuries" || generic == "computers") {
		return 0.75;
	}
	return 0.2;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys" || generic == "machinery" || generic == "luxuries" || generic == "computers") {
		return 1;
	}
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys" || generic == "radioactives" || generic == "minerals") {
		return 1;
	} else {
		return 0.2;
	}
}

this.importCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

// they have police patrol ships, so they probably obey Galcop rules here
this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	return false;
}
Scripts/Stations/cargotypestationkiotaresearch.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-KiotaResearch";
this.description = "Kiota Research market definition";

this.startUp = function () {
	if (!worldScripts["wildShips_populator.js"]) {
		return;
	}
	if (worldScripts["wildShips_populator.js"] && worldScripts["wildShips_populator.js"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota2Spur");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota4Spur");
}

/* Station API */


this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "computers" || generic == "machinery" || generic == "radioactives") {
		return 0.75;
	}
	return 0.2;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "computers" || generic == "machinery" || generic == "radioactives") {
		return 1;
	}
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "food" || generic == "textiles" || generic == "luxuries" || generic == "liquor_wines") {
		return 0.2;
	} else {
		return 1;
	}
}

this.importCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

// they have police patrol ships, so they probably obey Galcop rules here
this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	return false;
}
Scripts/Stations/cargotypestationkiotasolar.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-KiotaSolar";
this.description = "Kiota Solar market definition";

this.startUp = function () {
	if (!worldScripts["wildShips_populator.js"]) {
		return;
	}

	if (worldScripts["wildShips_populator.js"] && worldScripts["wildShips_populator.js"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota2Solar");
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "wildShips_kiota4Solar");
}

/* Station API */


this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	return 0.2;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	if (good == "CTE_CTS_O4") {
		return 2;
	}
	return 0.1;
}

this.exportCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys") {
		return 0.8;
	} else if (generic == "food" || generic == "liquor_wines") {
		return 0.2;
	}
	return 0;
}

this.importCargoPrice = function (good) {
	return 0.9 + (Math.random() * 0.2);
}

// they have police patrol ships, so they probably obey Galcop rules here
this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	return false;
}
Scripts/Stations/cargotypestationplanetfall.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-PlanetFall";
this.description = "PlanetFall market definition";

this.startUp = function () {
	if (!worldScripts["PlanetFall"]) {
		return;
	}

	if (worldScripts["PlanetFall"] && worldScripts["PlanetFall"].randomCargoChance) {
		return;
	}
	// it's trivial for players to produce an unlimited quantity of
	// these stations, so allowing cargo to be purchased at them would
	// let them get lots of goods very easily.
	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "planetFall_surface");
}

/* Station API */

this.randomCargoChance = function (good) {
	return 0;
}

this.randomCargoAmount = function (good) {
	return 1;
}

this.exportCargoAmount = function (good) {
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.9;
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	return 1;
}

this.importCargoPrice = function (good) {
	return 0.8 + (Math.random() * 0.3);
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	return false;
}

this.stationGossip = function () {
	return false;
}
Scripts/Stations/cargotypestationrrs.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-RRSWaystation";
this.description = "RRS Waystation market definition";

this.startUp = function () {
	if (!worldScripts["Rescue Stations"]) {
		return;
	}
	if (worldScripts["Rescue Stations"] && worldScripts["Rescue Stations"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "rescue_station");
}

/* Station API */

this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "alloys") { return 0.25; }
	if (good == "CTE_CTS_IN7") { return 2; } // Vaccines
	return 0;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	if (good == "CTE_CTS_IN7") {
		return 2;
	}
	return 0;
}

this.exportCargoPrice = function (good) {
	return 0.9;
}

this.randomImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "computers" || generic == "machinery" || generic == "alloys" || good == "CTE_CTS_R3") {
		return 0.01;
	}
	return 0;
}

this.systemImportChance = function (good) {
	return 0;
}

this.importCargoPrice = function (good) {
	return 1;
}

// they will trade in Vaccines, but they want you to have a permit
this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	if (system.shipsWithPrimaryRole("rescue_station").length > 0) {
		return "* If you have a permit, the RRS Waystation is good for Vaccines.";
	}
	return false;
}
Scripts/Stations/cargotypestationsalvage.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-SalvageGang";
this.description = "Salvage Gang market definition";

this.startUp = function () {
	if (!worldScripts["Anarchies"]) {
		return;
	}
	if (worldScripts["Anarchies"] && worldScripts["Anarchies"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "anarchies_salvage_gang");

}

/* Station API */

this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "minerals" || generic == "alloys") { return 1.5; }
	return 0;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	return 0;
}

this.exportCargoPrice = function (good) {
	return 1;
}

this.randomImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "liquor_wines") {
		return 0.05;
	}
	return 0;
}

this.systemImportChance = function (good) {
	return 0;
}

this.importCargoPrice = function (good) {
	return 1;
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	return false;
}

this.stationGossip = function () {
	if (system.shipsWithRole("anarchies_salvage_gang").length > 0) {
		return "* The salvage gang on the spacelane might have a few interesting things.";
	}
	return false;
}
Scripts/Stations/cargotypestationspacebar.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-SpaceBar";
this.description = "Space Bar market definition";

this.startUp = function () {
	if (!worldScripts["Random_Hits"]) {
		return;
	}
	if (worldScripts["Random_Hits"] && worldScripts["Random_Hits"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "random_hits_any_spacebar");

}

/* Station API */

this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	var specific = worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
	if (generic != "minerals") { return 0; }
	if (specific == "Arthropod shell" || specific == "Corals") { return 0; }
	return 2;
}

this.randomCargoAmount = function (good) {
	return 5;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	var specific = worldScripts["CargoTypeExtension"].cargoDefinition(good, "specificType");
	if (generic != "minerals") { return 0; }
	if (specific == "Arthropod shell" || specific == "Corals") { return 0; }
	if (Math.random() < 0.5) {
		return Math.random() * 2;
	}
}

this.exportCargoPrice = function (good) {
	return 0.8;
}

this.randomImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "liquor_wines") {
		return 0.1;
	}
	return 0;
}

this.systemImportChance = function (good) {
	return 0;
}

this.importCargoPrice = function (good) {
	return 1;
}

this.importPermitCheck = function () {
	return false;
}

this.exportPermitCheck = function () {
	return false;
}

this.stationGossip = function () {
	if (system.shipsWithRole("random_hits_any_spacebar").length > 0) {
		return "* It's a thirsty job, bounty hunting.";
	}
	return false;
}
Scripts/Stations/cargotypestationsuperhub.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Station-SuperHub";
this.description = "SuperHub market definition";

this.startUp = function () {
	if (!worldScripts["PAGroove_superhubPopulator"]) {
		return;
	}
	if (worldScripts["PAGroove_superhubPopulator"] && worldScripts["PAGroove_superhubPopulator"].randomCargoChance) {
		return;
	}

	worldScripts["CargoTypeExtension"].registerOXPStation(this.name, "pagroove_superhub");
}

/* Station API */
/* SuperHub:
	 RCC: 2 for Computers, 0.5 for Machinery, Luxuries, Alloys, Radioactives, 0 for rest. Maybe have some exclusions there.
	 RCA: 2
	 ECA: 0.5-1.5 for all categories in RCC
	 ECP: 0.9
	 RIC: 0.2*RCC, iff RCC check failed
	 SIC: 1 for RCC categories, 0 otherwise
	 ICP: (randomise 0.8-1.1)
	 IPC: true
	 EPC: true
	 SG: "You can get good deals on high-tech cargo at the SuperHub"
*/

this.randomCargoChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "computers") {
		return 2.5; // very high tech
	}
	if (generic == "machinery" || generic == "luxuries" || generic == "alloys" || generic == "radioactives") {
		return 0.5; // somewhat high tech
	}
	return 0;
}

this.randomCargoAmount = function (good) {
	return 10;
}

this.exportCargoAmount = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "machinery" || generic == "luxuries" || generic == "alloys" || generic == "radioactives" || generic == "computers") {
		return Math.random() + 0.5;
	} else {
		return 0;
	}
}

this.exportCargoPrice = function (good) {
	return 0.9;
}

this.randomImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "machinery" || generic == "luxuries" || generic == "alloys" || generic == "radioactives") {
		return 0.02;
	} else if (generic == "computers") {
		return 0.1;
	} else {
		return 0;
	}
}

this.systemImportChance = function (good) {
	var generic = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	if (generic == "machinery" || generic == "luxuries" || generic == "alloys" || generic == "radioactives" || generic == "computers") {
		return 1;
	} else {
		return 0;
	}
}

this.importCargoPrice = function (good) {
	return 0.8 + (Math.random() * 0.3);
}

this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	if (system.shipsWithPrimaryRole("pagroove_superhub").length > 0) {
		return "* Fly over to the SuperHub for better deals on high-tech goods";
	} else {
		return false;
	}
}
Scripts/Stations/license.txt
In addition to the general license for New Cargoes, the scripts in
this directory may be incorporated, with or without modification, in
to other OXPs to redefine (or undefine) New Cargoes behaviour for the
stations they modify, as if they were public domain.

Scripts/cargotypeauctions.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Auctions";
this.description = "Auctions for special cargo, with a variety of rules";

// deals with scoping problems by doing runScreen between two worldscripts
//var this = this;

this.auctioneers = new Array;

this.easteregg = 0;

this.startUp = function () {
	worldScripts["CargoTypeExtension"].registerPersonality(this.name);

	this.unserialiseAuctioneers();

}

this.playerWillSaveGame = function () {
	this.serialiseAuctioneers();
}

this.playerEnteredNewGalaxy = function () {
	this.reinitialiseAuctioneers();
}

this.reinitialiseAuctioneers = function () {
	for (var i = 1; i <= 5; i++) {
		reinitialiseAuctioneer(i);
	}
	localAuctioneer();
}

this.reinitialiseAuctioneer = function (aucnum) {
	var cte = worldScripts["CargoTypeExtension"];
	var auctioneer = new Object;
	auctioneer.name = expandDescription("[cte_auc_name" + galaxyNumber + "" + aucnum + "]");
	if (aucnum < 5) {
		var gentypeidx = (3 * (aucnum - 1)) + Math.floor(Math.random() * 4);
	} else {
		var gentypeidx = Math.floor(Math.random() * 13);
	}
	auctioneer.cargo = cte.extendableCargo(cte.cargoTypes[gentypeidx]);
	auctioneer.atype = Math.floor(Math.random() * 8);

	auctioneer.quantity = 25 * (3 + Math.floor(Math.random() * 6)); // 75-200 TC
	auctioneer.system = Math.floor(Math.random() * 256);
	var auctime = clock.adjustedSeconds + Math.floor(86400 * (5 + (11 * Math.random())));
	auctime -= auctime % 3600; // exact hour
	auctioneer.time = auctime;
	this.auctioneers[aucnum] = auctioneer;
	cte.debug("Initialised Auctioneer " + aucnum);
}

this.localAuctioneer = function () {
	var cte = worldScripts["CargoTypeExtension"];

	if (Math.random() < 0.3) {
		// not all the time
		this.auctioneers[0] = undefined;
		return;
	}
	for (var i = 1; i <= 5; i++) {
		if (this.auctioneers[i].system == system.ID) {
			// not if there's a celebrity auctioneer in the system
			this.auctioneers[0] = undefined;
			return;
		}
	}
	var auctioneer = new Object;
	auctioneer.name = expandDescription("%N [nom1]");
	var irregular = 0;
	if (Math.random() < 0.9) {
		// generally attempt to sell cargo that's normally exported
		auctioneer.cargo = cte.extendableCargo("");
	} else {
		irregular = 1;
		auctioneer.cargo = cte.extendableCargo(cte.cargoTypes[Math.floor(Math.random() * 13)]);
	}
	if (!auctioneer.cargo) {
		if (Math.random() < 0.8) {
			// generally not if the system doesn't have any regular exports
			this.auctioneers[0] = undefined;
			return;
		} else {
			irregular = 1;
			auctioneer.cargo = cte.extendableCargo(cte.cargoTypes[Math.floor(Math.random() * 13)]);
		}
	}
	auctioneer.atype = galaxyNumber;

	if (Math.random() < 0.25) {
		auctioneer.atype = Math.floor(Math.random() * 8);
	}
	if (auctioneer.atype == 7 && Math.random() < 0.5) {
		// Xrata rare, even in Gal8, for local auctioneers
		auctioneer.atype = Math.floor(Math.random() * 8);
	}

	if (irregular == 0) {
		auctioneer.quantity = 5 * (2 + Math.floor(Math.random() * 14)); //10-75TC
	} else {
		// generally auctioning small batches
		auctioneer.quantity = Math.min(5 * (2 + Math.floor(Math.random() * 14)), 5 * (2 + Math.floor(Math.random() * 14)), 5 * (2 + Math.floor(Math.random() * 14))); //10-75TC, biased to low end
	}
	auctioneer.system = system.ID;
	var auctime = clock.adjustedSeconds + (Math.random() * 36000); // on the hour
	auctime += 3600 - (auctime % 3600);
	auctioneer.time = auctime;
	this.auctioneers[0] = auctioneer;
	cte.debug("Initialised Local Auctioneer");
}

this.serialiseAuctioneers = function () {
	var serial = "1";
	for (var i = 0; i <= 5; i++) {
		var auctioneer = this.auctioneers[i];
		if (!auctioneer) {
			serial += "|";
		} else {
			serial += "|" + auctioneer.name + ";" +
				auctioneer.cargo + ";" +
				auctioneer.atype + ";" +
				auctioneer.quantity + ";" +
				auctioneer.system + ";" +
				auctioneer.time;
		}
	}

	missionVariables.cargotypeextension_auctions = serial;
}

this.unserialiseAuctioneers = function () {
	if (!missionVariables.cargotypeextension_auctions) {
		// gives time for cargo types to be initialised
		this.timer = new Timer(this, this.reinitialiseAuctioneers, 0.25);
	} else {
		var serialblocks = missionVariables.cargotypeextension_auctions.split("|");
		var format = serialblocks.shift();
		if (format == "1") {
			this.unserialiseAuctioneers1(serialblocks);
		} else {
			log(this.name, "Error: " + svars[0] + " is not a recognised auctioneer format");
			player.consoleMessage("Critical error decoding special cargo data. Please see Latest.log");
		}
	}
}

this.unserialiseAuctioneers1 = function (blocks) {
	this.auctioneers = new Array;
	for (var i = 0; i < blocks.length; i++) {
		if (blocks[i].length > 0) {
			var adata = blocks[i].split(";");
			var auc = new Object;
			auc.name = adata[0];
			auc.cargo = adata[1];
			auc.atype = parseInt(adata[2]);
			auc.quantity = parseInt(adata[3]);
			auc.system = parseInt(adata[4]);
			auc.time = parseInt(adata[5]);
			this.auctioneers[i] = auc;
		} else {
			this.auctioneers[i] = undefined; // should be slot 0 only
		}
	}
}

this.shipWillExitWitchspace = function () {
	for (var i = 1; i <= 5; i++) {
		if (!this.auctioneers[i] || this.auctioneers[i].time < clock.adjustedSeconds) {
			this.reinitialiseAuctioneer(i);
			this.tellTraderNet(i);
		}
	}
	// need to give some time to exit witchspace and have the clock adjust before this next bit.		
	if (!system.isInterstellarSpace) {
		this.timer = this.localAuctioneer();

		if (this.easteregg) {
			system.addShips("cte_angry", 1, player.ship.position, 20E3);
		}
	}
}

this.shipExitedWitchspace = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.easteregg) {
		var angry = system.shipsWithPrimaryRole("cte_angry");
		if (angry.length > 0) {
			angry[0].displayName = this.easteregg + "'s " + angry[0].name;
			angry[0].target = player.ship;
			angry[0].commsMessage("I'll have that " + cte.cargoDefinition(this.currentauction.cargo, "specificType") + " when I pry it from your cold dead hands!");
			angry[0].setAI("interceptAI.plist");
			for (var i = 0; i < angry[0].escorts.length; i++) {
				angry[0].escorts[i].target = player.ship;
				angry[0].escorts[i].setAI("interceptAI.plist");
			}
		}
		this.easteregg = 0;
	}
}

this.tellTraderNet = function (aucnum) {
	var cte = worldScripts["CargoTypeExtension"];
	var auctioneer = this.auctioneers[aucnum];
	var msg = expandDescription("[cte_auc_move" + (1 + Math.floor(Math.random() * 6)) + "]", {
		cte_auc_auctioneer: auctioneer.name,
		cte_auc_good: cte.cargoDefinition(auctioneer.cargo, "specificType"),
		cte_auc_system: System.infoForSystem(galaxyNumber, auctioneer.system).name,
		cte_auc_time: clock.clockStringForTime(auctioneer.time)
	});

	cte.addTraderNet(msg);
}

this.activeAuction = function () {
	for (var i = 0; i <= 5; i++) {
		if (this.auctioneers[i] && this.auctioneers[i].system == system.ID && this.auctioneers[i].time > clock.adjustedSeconds) {
			return this.auctioneers[i];
		}
	}
	return false;
}

this.auctionTypeName = function (num) {
	// One for each galaxy, names from Kaxgar/Feudal States
	var names = ["Santaari", "Colesque", "Lara'tan", "Angiana", "Proximus", "Solans", "Jaftra", "Xrata"];
	return names[num];
}

/* API calls */

this.traderGossip = function () {
	var aucnum = 1 + (clock.days % 5);
	if (this.auctioneers[aucnum]) {
		return "* There's a big auction coming up in " + System.infoForSystem(galaxyNumber, this.auctioneers[aucnum].system).name + ".";
	}
	return false;
}

this.describeContracts = function () {
	return "";
}

this.traderHere = function (srole) {
	if (srole != "") { // main stations only for now
		return false;
	}
	if (this.activeAuction()) {
		return true;
	}
	return false;
}

this.traderName = function () {
	return this.activeAuction().name + ", auctioneer";
}

this.traderDesc = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var auc = this.activeAuction();
	var msg = "Welcome, Trader. Bidding on " + auc.quantity + cte.getCommodityUnit(cte.cargoDefinition(auc.cargo, "genericType")) + " of " + cte.cargoDefinition(auc.cargo, "specificType") + " will start at " + clock.clockStringForTime(auc.time) + " and be conducted using " + this.auctionTypeName(auc.atype) + " rules.\n\nAs usual, we require all participants to have sufficient cargo space to load the goods immediately if they win, and to cover all auction fees and bids from cash in hand.";
	return msg;
}

this.runOffer = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var auc = this.activeAuction();

	var msg = "Welcome, Trader. Bidding doesn't start until " + clock.clockStringForTime(auc.time) + ", so feel free to get your ship and credit ready before we start. We've got " + auc.quantity + cte.getCommodityUnit(cte.cargoDefinition(auc.cargo, "genericType")) + " of " + cte.cargoDefinition(auc.cargo, "specificType") + " to sell.\n\nRemember, you must have sufficient cargo space to take the goods, and cover all fees and bids from your current credits.\n\nWe'll be using " + this.auctionTypeName(auc.atype) + " bidding:\n" + expandDescription("[cte_auc_rules" + auc.atype + "]");
	//		log(this.name,"Run offer");
	mission.runScreen({
		screenID:"newcargoes-auction",
		title: this.traderName(),
		message: msg,
		overlay: "cte_auction.png",
		choicesKey: "cte_auc_opening"
	}, this.checkAuction, this);


}

/** Use this. rather than this. in functions below this point to stop
 * scoping oddities **/

this.minSpaceRequired = function (auc) {
	if (auc.atype == 0) { // Santaari
		return 5; // might get a block size this low at some point
	}
	if (auc.atype == 2) { // Lara'tan
		return 1; // auction is a canister at a time
	}
	return auc.quantity;
}

this.checkAuction = function (choice) {
	//		log(this.name,"Check Auction");
	if (choice == "02_BEGIN") {
		var auc = this.activeAuction();
		if (player.ship.cargoSpaceAvailable >= this.minSpaceRequired(auc)) {
			this.startAuction();
			return;
		} else {
			player.consoleMessage("You do not currently have enough free cargo space.", 10);
		}
	}
	worldScripts["CargoTypeExtension"].tradeFloor();
}

this.startAuction = function () {
	var auc = this.activeAuction();
	this.currentauction = auc;

	var delay = auc.time - clock.adjustedSeconds;
	clock.addSeconds(delay);

	if (auc.atype == 0) {
		this.initSantaariAuction();
	} else if (auc.atype == 1) {
		this.initColesqueAuction();
	} else if (auc.atype == 2) {
		this.initLaratanAuction();
	} else if (auc.atype == 3) {
		this.initAngianaAuction();
	} else if (auc.atype == 4) {
		this.initProximusAuction();
	} else if (auc.atype == 5) {
		this.initSolansAuction();
	} else if (auc.atype == 6) {
		this.initJaftraAuction();
	} else if (auc.atype == 7) {
		this.initXrataAuction();

	} else {
		player.consoleMessage("This auction type is not implemented yet", 10);
	}
}

/* General Auction Functions */

this.blockSize = function (auc) {
	if (auc.atype == 0) { // Santaari
		if (auc.quantity >= 50) { // && auc.quantity % 25 == 0) {
			return 25;
		} else if (auc.quantity % 5 == 0) {
			return 5;
		} else {
			return 1; // shouldn't happen, but just in case
		}
	} else if (auc.atype == 2) { // Lara'tan
		return 1;
	} else {
		return auc.quantity;
	}
}

this.angianaBuyout = function () {
	return Math.floor(this.currentbid * 2.5);
}

this.colesqueIncrement = function (bid) {
	if (bid < 100) {
		return 5;
	} else if (bid < 250) {
		return 10;
	} else {
		return 25;
	}
}

this.guessValue = function () {

	var localdata = worldScripts["CargoTypeExtension"].localCargoData(this.currentauction.cargo);
	if (worldScripts["CargoTypeExtension"].cargoDefinition(this.currentauction.cargo, "buySystems")[galaxyNumber].indexOf(system.ID) === -1) { // not a buy system
		worldScripts["CargoTypeExtension"].debug("Unusual cargo auction");
		if (worldScripts["CargoTypeExtension"].cargoDefinition(this.currentauction.cargo, "sellSystems")[galaxyNumber].indexOf(system.ID) !== -1) {
			// being auctioned at a destination system? Expect the buyer to be planetary
			return Math.floor(localdata[1] * 2);
			//worldScripts["CargoTypeExtension"].debug("Sell system");
		} else {
			worldScripts["CargoTypeExtension"].debug("Unusual system");
			// alternative calculation needed
			var baseprice = worldScripts["CargoTypeExtension"].cargoPriceImport(this.currentauction.cargo, Math.floor(Math.random() * 100), worldScripts["CargoTypeExtension"].defaultMarketInfo()) / 10;
			var currentprice = baseprice / 2; // 100% gross profit seems about right
			worldScripts["CargoTypeExtension"].debug("Base:" + baseprice + ";Current:" + currentprice);
			if (currentprice < 40) {
				currentprice = 40;
			}
			if (currentprice > baseprice) {
				return Math.floor(currentprice / 10);
			} else {
				return Math.floor(baseprice / 10);
			}
		}
	} else if (localdata[0] > this.currentauction.quantity) { // is a buy system
		// can get it easily on the local market, so expect it to go cheaper here
		worldScripts["CargoTypeExtension"].debug("Buy system (small auction)");
		return Math.floor(localdata[1] * 0.5);
	} else {
		// this is a substantial amount of cargo, so it might go for a little more than the local market price (so we set the price less than that, of course)
		worldScripts["CargoTypeExtension"].debug("Buy system (big auction)");
		return Math.floor(localdata[1] * 0.8);
	}
}


this.initBidders = function (likelyvalue) {
	var numbidders = Math.floor(Math.random() * (2 + system.government)) + Math.floor(Math.random() * (2 + system.government));
	if (numbidders < 1) {
		numbidders = 1; // always have someone to bid against
	} else if (numbidders > 10) {
		numbidders = 10; // or we run out of space on the mission screen
	}
	if ((this.currentauction.atype == 2) && numbidders <= 5) {
		// Lara'tan auctions need a larger number of bidders to work
		numbidders += 5;
	}

	this.currentbidders = new Array();
	for (var i = 0; i < numbidders; i++) {
		var bidder = new Object;
		// TODO: Make bidders have persistent names, species and personalities
		// TODO: Allow occasional rumours about bidder behaviour to appear
		bidder.name = randomName() + " " + randomName();
		bidder.recklessness = Math.random();
		bidder.information = Math.random();
		bidder.preferredvalue = likelyvalue * (0.75 + (Math.random() * 0.5) + (Math.random() * (1 - bidder.information)));
		bidder.maxvalue = bidder.preferredvalue * (1.25 + Math.min(Math.random() * (1 - bidder.information), (Math.random() * bidder.recklessness)));
		bidder.lastbid = 0;
		this.currentbidders.push(bidder);
	}
	this.playerbid = 0;
}

this.auctionStatus = function () {
	var cte = worldScripts["CargoTypeExtension"];

	var msg = "You: " + player.ship.cargoSpaceAvailable + " TC free, " + player.credits.toFixed(1) + "₢ available\n";
	msg += "Current bid: " + this.currentbid + "₢/" + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " (" + (this.currentbid * this.blockSize(this.currentauction)) + "₢ total)\n";
	msg += "-----------------------------------\n";
	for (var i = 0; i < this.currentbidders.length; i++) {
		var line = this.currentbidders[i].name + ": ";
		if (this.currentbidders[i].lastbid == 0) {
			line += "no bid";
		} else {
			line += this.currentbidders[i].lastbid + "₢";
		}
		line += "\n";
		msg += line;
	}
	var line = "Commander " + player.name + ": ";
	if (this.playerbid == 0) {
		line += "no bid";
	} else {
		line += this.playerbid + "₢";
	}
	line += "\n";
	msg += line;

	return msg;
}

this.removeAuctionBlock = function () {
	this.currentauction.quantity -= this.blockSize(this.currentauction);
}

this.moveAuctionBlock = function () {
	var block = this.blockSize(this.currentauction);
	if (player.ship.cargoSpaceAvailable < block) {
		return false;
	}
	var price = this.currentbid * block;
	if (player.credits < price) {
		return false;
	}
	for (var i = 0; i < block; i++) {
		if (this.currentauction.atype == 2) {
			// no bid amount as such in Lara'tan auctions
			worldScripts["CargoTypeExtension"].addSpecialCargo(this.currentauction.cargo, "By auction in " + system.info.name);
		} else {
			worldScripts["CargoTypeExtension"].addSpecialCargo(this.currentauction.cargo, this.currentbid.toFixed(1) + "₢ by auction in " + system.info.name);
		}
	}
	player.credits -= price;
	this.currentauction.quantity -= block;
	return true;
}

this.endAuction = function () {
	mission.runScreen({
		screenID:"newcargoes-auction",
		title: "Auction over",
		overlay: "cte_auction.png",
		message: this.auctionmessage + "\nThe auction is over."
	});
	this.localAuctioneer();
}

/* Santaari Auctions */

this.initSantaariAuction = function () {
	var likelyvalue = this.guessValue();
	worldScripts["CargoTypeExtension"].debug("Initial valuation: " + likelyvalue);
	this.initBidders(likelyvalue);

	this.currentbid = Math.floor(likelyvalue * (2 + (0.5 * (Math.random() + Math.random()))));
	this.currentbid -= this.currentbid % 5;
	this.auctionmessage = "";
	this.playSantaariAuction();

}

this.playSantaariAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.currentauction.quantity < 1) {
		this.endAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "Auction: Santaari (" + this.blockSize(this.currentauction) + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " blocks)\n";
		if (this.auctionmessage != "") {
			msg += "* " + this.auctionmessage + "\n";
			this.auctionmessage = "";
		}
		msg += this.auctionStatus();
		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			overlay: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_santaari_bid"
		}, this.choiceSantaariAuction, this);
	}

}

this.choiceSantaariAuction = function (choice) {
	if (choice == "01_PASS") {
		this.updateSantaariAuction();
	} else if (choice == "02_BID") {
		this.playerbid = this.currentbid;
		if (this.moveAuctionBlock()) {
			player.consoleMessage("Cargo transferred", 5);
			this.playSantaariAuction();
		} else {
			player.consoleMessage("Insufficient funds/space", 5);
			this.updateSantaariAuction();
		}
	} else if (choice == "03_BIDALL") {
		this.playerbid = this.currentbid;
		if (this.moveAuctionBlock()) {
			while (this.moveAuctionBlock()) { }
			player.consoleMessage("Cargo transferred", 5);
			this.playSantaariAuction();
		} else {
			player.consoleMessage("Insufficient funds/space", 5);
			this.updateSantaariAuction();
		}

	} else if (choice == "04_LEAVE") {

		this.endAuction();
	}
}

this.updateSantaariAuction = function () {
	// Each bidder will buy at preferredvalue
	// Above preferred value, will buy if less than maxvalue depending on recklessness
	// If no-one bids, then drop the bid price by:
	// 25Cr if over 1000
	// 10Cr if over 250
	// 5Cr if over 100
	// 1Cr otherwise

	// If someone bids, 20% of the time they buy whatever is left,
	// otherwise they just buy 1 block.  If they're the only remaining
	// bidder except the player, they buy the lot, though

	var cte = worldScripts["CargoTypeExtension"];

	// go through in reverse order; means we can splice without breaking things
	for (var i = this.currentbidders.length - 1; i >= 0; i--) {
		var bidder = this.currentbidders[i];
		cte.debug(bidder.name + " prefs " + bidder.preferredvalue + " max " + bidder.maxvalue);
		var buynow = 0;
		if (this.currentbid < bidder.preferredvalue) {
			buynow = 1;
		} else if (this.currentbid < bidder.maxvalue) {
			var diff = Math.pow((this.currentbid - bidder.preferredvalue) / (bidder.maxvalue - bidder.preferredvalue), 1 / 3);
			diff *= 1 - Math.pow(Math.random() * bidder.recklessness, 3);
			if (Math.random() > diff) {
				buynow = 1
			}
		}

		var ctype = cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType"));
		if (buynow) {
			bidder.lastbid = this.currentbid;
			if (this.currentbidders.length == 1 || Math.random() < 0.2) {
				this.auctionmessage = bidder.name + " buys the remaining lots for " + this.currentbid + "₢/" + ctype;
				this.endAuction();
				return;
			} else {

				var blocks = this.currentauction.quantity / this.blockSize(this.currentauction);
				var buyblocks = Math.min(1 + Math.floor(Math.random() * (blocks - 1)), 1 + Math.floor(Math.random() * (blocks - 1))); // bias towards 1 block

				for (var j = 1; j <= buyblocks; j++) {
					this.removeAuctionBlock();
				}
				if (buyblocks > 1) {
					this.auctionmessage = bidder.name + " buys " + buyblocks + " blocks for " + this.currentbid + "₢/" + ctype + " and leaves";
					this.currentbidders.splice(i, 1);
				} else {
					this.auctionmessage = bidder.name + " buys 1 block for " + this.currentbid + "₢/" + ctype; // and stays in
				}
				break;
			}
		}
	}

	if (this.auctionmessage == "") {
		// no-one buys this round
		if (this.currentbid > 1000) {
			this.currentbid -= 25;
		} else if (this.currentbid > 250) {
			this.currentbid -= 10;
		} else if (this.currentbid > 100) {
			this.currentbid -= 5;
		} else {
			this.currentbid -= 1;
		}
	}
	if (this.currentbid < 5) {
		this.auctionmessage = "Unable to find a buyer, the lots are withdrawn";
		this.endAuction();
	} else {
		this.playSantaariAuction();
	}
}


/* Colesque Auctions */
this.initColesqueAuction = function () {
	var likelyvalue = this.guessValue();
	this.initBidders(likelyvalue);

	this.currentbid = Math.floor(likelyvalue * (0.4 + (0.15 * (Math.random() + Math.random()))));
	this.currentbid -= this.currentbid % 5;
	this.auctionmessage = "";
	this.lastcall = 0;
	this.aucwinner = "";
	this.playColesqueAuction();
}

this.playColesqueAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];

	if (this.currentauction.quantity < 1) {
		this.endAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "Auction: Colesque (minimum increment " + this.colesqueIncrement(this.currentbid) + "₢)\n";
		if (this.auctionmessage != "") {
			msg += "* " + this.auctionmessage + "\n";
			this.auctionmessage = "";
		}
		msg += this.auctionStatus();
		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			overlay: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_colesque_bid"
		}, this.choiceColesqueAuction, this);
	}

}

this.choiceColesqueAuction = function (choice) {
	if (choice == "01_PASS") {
		this.updateColesqueAuction();
	} else if (choice == "02_BID") {
		var newbid = this.currentbid + this.colesqueIncrement(this.currentbid);
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			this.currentbid = this.playerbid;
			this.lastcall = 0;
			this.aucwinner = "Commander " + player.name;
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
		}
		this.playColesqueAuction();
	} else if (choice == "03_BIDFIVE") {
		var newbid = this.currentbid + (5 * this.colesqueIncrement(this.currentbid));
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			this.currentbid = this.playerbid;
			this.lastcall = 0;
			this.aucwinner = "Commander " + player.name;
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
		}
		this.playColesqueAuction();
	} else if (choice == "04_LEAVE") {
		this.endAuction();
	}
}

this.updateColesqueAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var activity = 0;
	for (var i = 0; i < this.currentbidders.length; i++) {
		var bidder = this.currentbidders[i];
		cte.debug(bidder.name + " (" + bidder.recklessness + ") prefs " + bidder.preferredvalue + " max " + bidder.maxvalue);
		if (bidder.lastbid < this.currentbid) {
			if (this.currentbid < bidder.preferredvalue) {
				if (Math.random() < bidder.recklessness || this.lastcall > 0) {
					activity = 1;
					this.lastcall = 0;
					this.currentbid += this.colesqueIncrement(this.currentbid);
					bidder.lastbid = this.currentbid;
					this.aucwinner = bidder.name;
				}
			} else if (this.currentbid < bidder.maxvalue) {
				var diff = (this.currentbid - bidder.preferredvalue) / (bidder.maxvalue - bidder.preferredvalue);
				if ((this.lastcall > 0 && Math.random() < bidder.recklessness && Math.random() < bidder.recklessness) ||
					(Math.random() < Math.pow((1 - diff) * bidder.recklessness, 2))) {
					activity = 1;
					this.lastcall = 0;
					this.currentbid += this.colesqueIncrement(this.currentbid);
					bidder.lastbid = this.currentbid;
					this.aucwinner = bidder.name;
				}
			}
		}
	}

	if (activity == 0) {
		if (this.lastcall == 0) {
			this.lastcall = 1;
			this.auctionmessage = "Going once ... going twice ...";
		} else {
			var premsg = "Sold! ";
			var winmsg = " wins " + this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType") + " for " + (this.currentbid * this.currentauction.quantity) + "₢";
			if (this.currentbid == this.playerbid) {
				this.auctionmessage = premsg + "Commander " + player.name + winmsg;
				if (!this.moveAuctionBlock()) {
					// this shouldn't happen, in theory
					this.auctionmessage = "You don't have the credits/cargo hold to cover that bid! You are forcibly ejected from the room.";
				}
			} else {
				this.auctionmessage = premsg + this.aucwinner + winmsg;
			}
			this.endAuction();
			return;
		}
	} else {
		this.lastcall = 0;
	}
	this.playColesqueAuction();
}

/* Lara'tan auctions */
this.initLaratanAuction = function () {
	var likelyvalue = this.guessValue();
	this.initBidders(likelyvalue);

	this.currentbid = Math.floor((0.8 + (Math.random() * 0.4)) * this.currentauction.quantity * likelyvalue / 100);

	this.playerbid = 0;
	this.auctionmessage = "";
	this.lastcall = 1;
	this.aucwinner = "";
	this.playLaratanAuction();

}

this.playLaratanAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.lastcall == 6) {
		this.endLaratanAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "You: " + player.ship.cargoSpaceAvailable + " TC free, " + player.credits.toFixed(1) + "₢ available\n";
		msg += "Auction: Lara'tan (ticket price " + this.currentbid + "₢)\n";
		msg += "Round " + this.lastcall + "/5\n";

		msg += "-----------------------------------\n";
		for (var i = 0; i < this.currentbidders.length; i++) {
			var line = this.currentbidders[i].name + ": ";
			if (this.currentbidders[i].lastbid == 0) {
				line += "no bid";
			} else {
				line += this.currentbidders[i].lastbid + " ticket(s)";
			}
			line += "\n";
			msg += line;
		}
		var line = "Commander " + player.name + ": ";
		if (this.playerbid == 0) {
			line += "no bid";
		} else {
			line += this.playerbid + " ticket(s)";
		}
		line += "\n";
		msg += line;

		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			overlay: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_laratan_bid"
		}, this.choiceLaratanAuction, this);

	}
}

this.choiceLaratanAuction = function (choice) {
	if (choice == "01_PASS") {
		this.updateLaratanAuction();
	} else if (choice == "02_BID") {
		if (player.credits >= this.currentbid) {
			this.playerbid++;
			player.credits -= this.currentbid;
		} else {
			player.consoleMessage("You don't have enough credits to buy a ticket", 10);
		}
		this.playLaratanAuction();
	} else if (choice == "03_BIDFIVE") {
		if (player.credits >= this.currentbid * 5) {
			this.playerbid += 5;
			player.credits -= this.currentbid * 5;
		} else {
			player.consoleMessage("You don't have enough credits to buy 5 tickets", 10);
		}
		this.playLaratanAuction();
	} else if (choice == "04_BIDTFIVE") {
		if (player.credits >= this.currentbid * 25) {
			this.playerbid += 25;
			player.credits -= this.currentbid * 25;
		} else {
			player.consoleMessage("You don't have enough credits to buy 25 tickets", 10);
		}
		this.playLaratanAuction();
	}
}

this.totalLaratanSales = function () {
	var total = 0;
	for (var i = 0; i < this.currentbidders.length; i++) {
		total += this.currentbidders[i].lastbid;
	}
	total += this.playerbid;
	return total;
}

this.updateLaratanAuction = function () {
	for (var i = 0; i < this.currentbidders.length; i++) {
		var bidder = this.currentbidders[i];
		if (bidder.lastbid == 0) {
			bidder.lastbid = 1;
		}
		var total = 1 + this.totalLaratanSales();
		do {
			var bought = 0;
			var expectedvalue = ((bidder.lastbid / total) * bidder.preferredvalue * this.currentauction.quantity) - (bidder.lastbid * this.currentbid);
			var newvalue = (((1 + bidder.lastbid) / total) * bidder.preferredvalue * this.currentauction.quantity) - ((1 + bidder.lastbid) * this.currentbid);
			var diff = newvalue - expectedvalue;
			worldScripts["CargoTypeExtension"].debug(bidder.name + ": " + diff);
			if (diff > 0 && (bidder.lastbid * this.currentbid) < (Math.random() * bidder.recklessness * bidder.maxvalue * this.currentauction.quantity)) {
				bought = 1;
				bidder.lastbid++;
			} else if (Math.random() < bidder.recklessness && (bidder.lastbid / total) > (2 / (1 + this.currentbidders.length))) {
				bought = 1;
				bidder.lastbid++;
			}
		} while (bought == 1);
	}
	this.lastcall++;
	this.playLaratanAuction();

}

this.endLaratanAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];

	// Math.random() <= this.playerbid/this.totalLaratanSales() for each TC
	// until all distributed or we run out of cargo space
	var winchance = this.playerbid / this.totalLaratanSales();
	var won = 0;
	this.currentbid = 0;
	while (player.ship.cargoSpaceAvailable > 0 && this.currentauction.quantity > 0) {
		if (Math.random() < winchance) {
			if (this.moveAuctionBlock()) {
				won++;
			}
		} else {
			this.currentauction.quantity--;
		}
	}
	// give a message to say how many TCs were won by the player
	var msg = "Final purchases:\n";
	for (var i = 0; i < this.currentbidders.length; i++) {
		var line = this.currentbidders[i].name + ": ";
		if (this.currentbidders[i].lastbid == 0) {
			line += "no bid";
		} else {
			line += this.currentbidders[i].lastbid + " ticket(s)";
		}
		line += "\n";
		msg += line;
	}
	var line = "Commander " + player.name + ": ";
	if (this.playerbid == 0) {
		line += "no bid";
	} else {
		line += this.playerbid + " ticket(s)";
	}
	line += "\n";
	msg += line;
	this.auctionmessage = msg;
	this.auctionmessage += "--------------------------\n";
	if (won == 0) {
		this.auctionmessage += "Unfortunately, you didn't win anything.";
	} else {
		this.auctionmessage += "You won " + won + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "specificType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType") + ".";
	}

	this.endAuction();
}

/* Angiana auctions */

this.initAngianaAuction = function () {
	var likelyvalue = this.guessValue();
	this.initBidders(likelyvalue);

	this.currentbid = Math.floor(likelyvalue * (0.4 + (0.15 * (Math.random() + Math.random()))));
	this.currentbid -= this.currentbid % 5;
	this.auctionmessage = "";
	this.lastcall = 0;
	this.aucwinner = "";
	this.playAngianaAuction();
}

this.playAngianaAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.currentauction.quantity < 1) {
		this.endAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "Auction: Angiana (minimum increment " + this.colesqueIncrement(this.currentbid) + "₢, buyout " + this.angianaBuyout() + "₢/" + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + ")\n";
		if (this.auctionmessage != "") {
			msg += "* " + this.auctionmessage + "\n";
			this.auctionmessage = "";
		}
		msg += this.auctionStatus();
		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			auction: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_angiana_bid"
		}, this.choiceAngianaAuction, this);
	}

}

this.choiceAngianaAuction = function (choice) {
	var cte = worldScripts["CargoTypeExtension"];
	if (choice == "01_PASS") {
		this.updateAngianaAuction();
	} else if (choice == "02_BID") {
		// Angiana and Colesque use same increment amounts
		var newbid = this.currentbid + this.colesqueIncrement(this.currentbid);
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			this.currentbid = this.playerbid;
			this.lastcall = 0;
			this.aucwinner = "Commander " + player.name;
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
		}
		this.playAngianaAuction();
	} else if (choice == "03_BUYOUT") {
		var oldbid = this.currentbid;
		this.currentbid = this.angianaBuyout();
		var totalcost = this.currentbid * this.currentauction.quantity;
		if (this.moveAuctionBlock()) {
			this.auctionmessage = "You buy out the auction for " + this.currentbid + "₢/" + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " (" + totalcost.toFixed(1) + "₢ total)\n";
			this.endAuction();
		} else {
			player.consoleMessage("You don't have enough credits to buy out the auction", 10);
			this.currentbid = oldbid;
		}
	} else if (choice == "04_LEAVE") {
		this.endAuction();
	}
}

this.updateAngianaAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var activity = 0;
	for (var i = 0; i < this.currentbidders.length; i++) {
		var bidder = this.currentbidders[i];
		worldScripts["CargoTypeExtension"].debug(bidder.name + " (" + bidder.recklessness + ") prefs " + bidder.preferredvalue + " max " + bidder.maxvalue);
		// rare that they'll try to buy it out
		if (this.angianaBuyout() < bidder.preferredvalue && Math.random() < bidder.recklessness - 0.5) {
			this.auctionmessage = bidder.name + " buys out the auction for " + this.angianaBuyout() + "₢/" + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType"));
			this.endAuction();
			return;
		}
		if (bidder.lastbid < this.currentbid) {
			if (this.currentbid < bidder.preferredvalue) {
				// more common that they'll try to bid it up a little to discourage other buyouts.
				if (Math.random() < bidder.recklessness + 0.3 || this.lastcall > 0) {
					activity = 1;
					this.lastcall = 0;
					this.currentbid += this.colesqueIncrement(this.currentbid);
					bidder.lastbid = this.currentbid;
					this.aucwinner = bidder.name;
				}
			} else if (this.currentbid < bidder.maxvalue) {
				var diff = (this.currentbid - bidder.preferredvalue) / (bidder.maxvalue - bidder.preferredvalue);
				if ((this.lastcall > 0 && Math.random() < bidder.recklessness && Math.random() < bidder.recklessness) ||
					(Math.random() < Math.pow((1 - diff) * bidder.recklessness, 2))) {
					activity = 1;
					this.lastcall = 0;
					this.currentbid += this.colesqueIncrement(this.currentbid);
					bidder.lastbid = this.currentbid;
					this.aucwinner = bidder.name;
				}
			}
		}
	}


	if (activity == 0) {
		if (this.lastcall == 0) {
			this.lastcall = 1;
			this.auctionmessage = "Going once ... going twice ...";
		} else {
			var premsg = "Sold! ";
			var winmsg = " wins " + this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType") + " for " + (this.currentbid * this.currentauction.quantity) + "₢";
			if (this.currentbid == this.playerbid) {
				this.auctionmessage = premsg + "Commander " + player.name + winmsg;
				if (!this.moveAuctionBlock()) {
					// this shouldn't happen, in theory
					this.auctionmessage = "You don't have the credits/cargo hold to cover that bid! You are forcibly ejected from the room.";
				}
			} else {
				this.auctionmessage = premsg + this.aucwinner + winmsg;
			}
			this.endAuction();
			return;
		}
	} else {
		this.lastcall = 0;
	}
	this.playAngianaAuction();
}

/* Proximus auctions */

this.initProximusAuction = function () {
	var likelyvalue = this.guessValue();
	this.initBidders(likelyvalue);

	this.currentbid = 5;

	this.auctionmessage = "";
	this.lastcall = Math.floor((3 + (Math.random() * 5)) * this.currentbidders.length);
	this.aucwinner = "";
	this.playProximusAuction();
}


this.playProximusAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.currentauction.quantity < 1) {
		this.endAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "Auction: Proximus (minimum increment " + this.colesqueIncrement(this.currentbid) + "₢)\n";
		if (this.auctionmessage != "") {
			msg += "* " + this.auctionmessage + "\n";
			this.auctionmessage = "";
		}
		msg += this.auctionStatus();
		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			overlay: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_proximus_bid"
		}, this.choiceProximusAuction, this);
	}
}

this.choiceProximusAuction = function (choice) {

	if (choice == "01_PASS") {
		this.updateProximusAuction();
	} else if (choice == "02_BID") {
		var newbid = this.currentbid + this.colesqueIncrement(this.currentbid);
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			this.currentbid = this.playerbid;
			this.aucwinner = "Commander " + player.name;
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
		}
		this.playProximusAuction();
	} else if (choice == "03_BIDFIVE") {
		var newbid = this.currentbid + (5 * this.colesqueIncrement(this.currentbid));
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			this.currentbid = this.playerbid;
			this.aucwinner = "Commander " + player.name;
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
		}
		this.playProximusAuction();
	} else if (choice == "04_LEAVE") {
		this.endAuction();
	}
}

this.updateProximusAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	this.lastcall--;
	for (var i = 0; i < this.currentbidders.length; i++) {
		if (this.lastcall <= 0) {
			break;
		}
		cte.debug(this.lastcall);
		var bidder = this.currentbidders[i];
		cte.debug(bidder.name + " (" + bidder.recklessness + ") prefs " + bidder.preferredvalue + " max " + bidder.maxvalue);
		if (bidder.lastbid < this.currentbid) {
			do {
				// might raise a few times
				var madebid = 0;
				if (this.currentbid < bidder.preferredvalue) {
					if (Math.random() < 0.75 + (bidder.recklessness / 4)) {
						activity = 1;
						this.currentbid += this.colesqueIncrement(this.currentbid);
						bidder.lastbid = this.currentbid;
						this.aucwinner = bidder.name;
						madebid = 1;
					}
				} else if (this.currentbid < bidder.maxvalue) {
					var diff = (this.currentbid - bidder.preferredvalue) / (bidder.maxvalue - bidder.preferredvalue);
					if ((Math.random() < bidder.recklessness) ||
						(Math.random() < Math.pow((1 - diff) * bidder.recklessness, 2))) {
						activity = 1;
						this.currentbid += this.colesqueIncrement(this.currentbid);
						bidder.lastbid = this.currentbid;
						this.aucwinner = bidder.name;
						madebid = 1;
					}
				}
			} while (madebid == 1);
		}
		this.lastcall--;
	}

	if (this.lastcall <= 0) {
		var premsg = "The ship has arrived. Sold! ";
		var winmsg = " wins " + this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType") + " for " + (this.currentbid * this.currentauction.quantity) + "₢";
		if (this.currentbid == this.playerbid) {
			this.auctionmessage = premsg + "Commander " + player.name + winmsg;
			if (!this.moveAuctionBlock()) {
				// this shouldn't happen, in theory
				this.auctionmessage = "You don't have the credits/cargo hold to cover that bid! You are forcibly ejected from the room.";
			}
		} else {
			this.auctionmessage = premsg + this.aucwinner + winmsg;
		}
		this.endAuction();
		return;
	}
	this.playProximusAuction();
}


/* Solans Auctions */
this.initSolansAuction = function () {
	var likelyvalue = this.guessValue();
	this.initBidders(likelyvalue);

	this.currentbid = Math.floor(likelyvalue * (0.4 + (0.15 * (Math.random() + Math.random()))));
	this.currentbid -= this.currentbid % 5;
	this.auctionmessage = "";
	this.lastcall = 0;
	this.aucwinner = "";
	this.playSolansAuction();
}

this.playSolansAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.currentauction.quantity < 1) {
		this.endAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "Auction: Solans (increment " + this.colesqueIncrement(this.currentbid) + "₢)\n";
		if (this.auctionmessage != "") {
			msg += "* " + this.auctionmessage + "\n";
			this.auctionmessage = "";
		}
		msg += this.auctionStatus();
		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			auction: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_solans_bid"
		}, this.choiceSolansAuction, this);
	}

}

this.choiceSolansAuction = function (choice) {
	if (choice == "02_BID") {
		var newbid = this.currentbid + this.colesqueIncrement(this.currentbid);
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			this.currentbid = this.playerbid;
			this.aucwinner = "Commander " + player.name;
			this.updateSolansAuction();
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
			this.playSolansAuction();
		}
	} else if (choice == "04_LEAVE") {
		this.endAuction();
	}
}

this.updateSolansAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var dropouts = new Array;
	for (var i = 0; i < this.currentbidders.length; i++) {
		var bidder = this.currentbidders[i];
		cte.debug(bidder.name + " (" + bidder.recklessness + ") prefs " + bidder.preferredvalue + " max " + bidder.maxvalue);
		if (this.currentbid < bidder.preferredvalue) {
			this.currentbid += this.colesqueIncrement(this.currentbid);
			bidder.lastbid = this.currentbid;
			this.aucwinner = bidder.name;
		} else {
			if (Math.random() * (1 - bidder.recklessness) < 1 - ((this.currentbid - bidder.preferredvalue) / (bidder.maxvalue - bidder.preferredvalue))) {
				this.currentbid += this.colesqueIncrement(this.currentbid);
				bidder.lastbid = this.currentbid;
				this.aucwinner = bidder.name;
			} else {
				dropouts.unshift(i);
			}
		}
	}

	for (var j = 0; j < dropouts.length; j++) {
		this.currentbidders.splice(dropouts[j], 1);
	}

	if (this.currentbidders.length == 0) {
		var premsg = "Sold! ";
		var winmsg = " wins " + this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType") + " for " + (this.currentbid * this.currentauction.quantity) + "₢";
		if (this.currentbid == this.playerbid) {
			this.auctionmessage = premsg + "Commander " + player.name + winmsg;
			if (!this.moveAuctionBlock()) {
				// this shouldn't happen, in theory
				this.auctionmessage = "You don't have the credits/cargo hold to cover that bid! You are forcibly ejected from the room.";
			} else {
				this.auctionmessage = premsg + this.aucwinner + winmsg;
			}
			this.endAuction();
			return;
		}
	}
	this.playSolansAuction();
}

/* Jaftra Auctions */

this.initJaftraAuction = function () {
	var likelyvalue = this.guessValue();
	worldScripts["CargoTypeExtension"].debug("Initial valuation: " + likelyvalue);
	this.initBidders(likelyvalue);

	this.currentbid = Math.floor(likelyvalue * (2 + (0.5 * (Math.random() + Math.random()))));
	this.currentbid -= this.currentbid % 5;
	this.auctionmessage = "";
	this.playJaftraAuction();
}

this.playJaftraAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.currentauction.quantity < 1) {
		this.endAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "Auction: Jaftra\n";
		if (this.auctionmessage != "") {
			msg += "* " + this.auctionmessage + "\n";
			this.auctionmessage = "";
		}
		msg += this.auctionStatus();
		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			overlay: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_jaftra_bid"
		}, this.choiceJaftraAuction, this);
	}

}

this.choiceJaftraAuction = function (choice) {
	if (choice == "01_PASS") {
		this.updateJaftraAuction();
	} else if (choice == "02_BID") {
		this.playerbid = this.currentbid;
		if (this.moveAuctionBlock()) {
			player.consoleMessage("Cargo transferred", 5);
			this.playJaftraAuction();
		} else {
			player.consoleMessage("Insufficient funds/space", 5);
			this.updateJaftraAuction();
		}
	} else if (choice == "04_LEAVE") {

		this.endAuction();
	}
}

this.updateJaftraAuction = function () {
	// Each bidder will buy at preferredvalue
	// Above preferred value, will buy if less than maxvalue depending on recklessness
	// If no-one bids, then drop the bid price by:
	// 25Cr if over 1000
	// 10Cr if over 250
	// 5Cr if over 100
	// 1Cr otherwise

	// If someone bids, 20% of the time they buy whatever is left,
	// otherwise they just buy 1 block.  If they're the only remaining
	// bidder except the player, they buy the lot, though

	var cte = worldScripts["CargoTypeExtension"];

	// go through in reverse order; means we can splice without breaking things
	for (var i = this.currentbidders.length - 1; i >= 0; i--) {
		var bidder = this.currentbidders[i];
		cte.debug(bidder.name + " prefs " + bidder.preferredvalue + " max " + bidder.maxvalue);
		var buynow = 0;
		if (this.currentbid < bidder.preferredvalue) {
			buynow = 1;
		} else if (this.currentbid < bidder.maxvalue) {
			var diff = Math.pow((this.currentbid - bidder.preferredvalue) / (bidder.maxvalue - bidder.preferredvalue), 1 / 3);
			diff *= 1 - Math.pow(Math.random() * bidder.recklessness, 3);
			if (Math.random() > diff) {
				buynow = 1
			}
		}

		if (buynow) {
			bidder.lastbid = this.currentbid;
			this.auctionmessage = bidder.name + " buys the cargo for " + this.currentbid + "₢/" + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType"));
			this.endAuction();
			return;
		}
	}

	if (this.auctionmessage == "") {
		// no-one buys this round
		if (this.currentbid > 1000) {
			this.currentbid -= 25;
		} else if (this.currentbid > 250) {
			this.currentbid -= 10;
		} else if (this.currentbid > 100) {
			this.currentbid -= 5;
		} else {
			this.currentbid -= 1;
		}
	}
	if (this.currentbid < 5) {
		this.auctionmessage = "Unable to find a buyer, the lots are withdrawn";
		this.endAuction();
	} else {
		this.playJaftraAuction();
	}
}

/* Xrata Auctions */
this.initXrataAuction = function () {
	var likelyvalue = this.guessValue();
	this.initBidders(likelyvalue);

	this.currentbid = Math.floor(likelyvalue * (0.4 + (0.15 * (Math.random() + Math.random()))));
	this.currentbid -= this.currentbid % 5;
	this.previousbid = 0;
	this.auctionmessage = "";
	this.lastcall = 0;
	this.aucwinner = "";
	this.aucloser = "";
	this.playXrataAuction();
}

this.playXrataAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (this.currentauction.quantity < 1) {
		this.endAuction();
	} else {
		var title = this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction.cargo, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType");
		var msg = "Auction: Xrata (minimum increment " + this.colesqueIncrement(this.currentbid) + "₢)\n";
		if (this.auctionmessage != "") {
			msg += "* " + this.auctionmessage + "\n";
			this.auctionmessage = "";
		}
		msg += this.auctionStatus();
		mission.runScreen({
			screenID:"newcargoes-auction",
			title: title,
			overlay: "cte_auction.png",
			message: msg,
			choicesKey: "cte_auc_xrata_bid"
		}, this.choiceXrataAuction, this);
	}

}

this.choiceXrataAuction = function (choice) {
	if (choice == "01_PASS") {
		this.updateXrataAuction();
	} else if (choice == "02_BID") {
		var newbid = this.currentbid + this.colesqueIncrement(this.currentbid);
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			if (this.aucwinner != "Commander " + player.name) {
				this.previousbid = this.currentbid;
				worldScripts["CargoTypeExtension"].debug("Loser bid " + this.previousbid);
				this.aucloser = this.aucwinner;
			}

			this.currentbid = this.playerbid;
			this.lastcall = 0;
			this.aucwinner = "Commander " + player.name;
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
		}
		this.playXrataAuction();
	} else if (choice == "03_BIDFIVE") {
		var newbid = this.currentbid + (5 * this.colesqueIncrement(this.currentbid));
		if (newbid * this.currentauction.quantity <= player.credits) {
			this.playerbid = newbid;
			if (this.aucwinner != "Commander " + player.name) {
				this.previousbid = this.currentbid;
				this.aucloser = this.aucwinner;
			}

			this.currentbid = this.playerbid;
			this.lastcall = 0;
			this.aucwinner = "Commander " + player.name;
		} else {
			player.consoleMessage("You don't have enough credits to cover that bid", 10);
		}
		this.playColesqueAuction();
	} else if (choice == "04_LEAVE") {
		if (this.aucloser == "Commander " + player.name || this.aucwinner == "Commander " + player.name) {
			player.consoleMessage("You can't leave Xrata auctions if you're in first or second place", 10);
			this.playXrataAuction();
		} else {
			this.endAuction();
		}
	}
}

this.updateXrataAuction = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var activity = 0;
	var angryloser = 0;
	for (var i = 0; i < this.currentbidders.length; i++) {
		var bidder = this.currentbidders[i];
		cte.debug(bidder.name + " (" + bidder.recklessness + ") prefs " + bidder.preferredvalue + " max " + bidder.maxvalue);
		if (bidder.lastbid < this.currentbid) {
			if (bidder.name != this.aucloser) { // if not currently 2nd
				if (this.currentbid < bidder.preferredvalue) {
					if (Math.random() < bidder.recklessness || (this.previousbid == 0 && this.lastcall == 1)) {
						activity = 1;
						this.lastcall = 0;
						this.previousbid = this.currentbid;
						this.currentbid += this.colesqueIncrement(this.currentbid);
						bidder.lastbid = this.currentbid;
						this.aucloser = this.aucwinner;
						this.aucwinner = bidder.name;
					}
				} // if it's got above preferred and we're not first or second, don't bid!
			} else { // try to get out of 2nd!
				if (this.currentbid < bidder.maxvalue && (Math.random() < bidder.recklessness || (this.lastcall == 1 && Math.random() < bidder.recklessness))) {
					activity = 1;
					this.lastcall = 0;
					this.previousbid = this.currentbid;
					this.currentbid += this.colesqueIncrement(this.currentbid);
					bidder.lastbid = this.currentbid;
					this.aucloser = this.aucwinner;
					this.aucwinner = bidder.name;
				} else if (Math.random() < bidder.recklessness * (bidder.maxvalue / this.currentbid)) { // even if it means going over max bid
					activity = 1;
					this.lastcall = 0;
					this.previousbid = this.currentbid;
					this.currentbid += this.colesqueIncrement(this.currentbid);
					bidder.lastbid = this.currentbid;
					this.aucloser = this.aucwinner;
					this.aucwinner = bidder.name;
				} else if (bidder.maxvalue < this.currentbid) {
					angryloser = (this.currentbid / bidder.maxvalue) - 1;
				}
			}
		}
	}

	if (activity == 0) {
		if (this.lastcall == 0) {
			this.lastcall = 1;
			this.auctionmessage = "Going once ... going twice ...";
		} else {
			var premsg = "Sold! ";
			var winmsg = " wins " + this.currentauction.quantity + cte.getCommodityUnit(cte.cargoDefinition(this.currentauction, "genericType")) + " of " + cte.cargoDefinition(this.currentauction.cargo, "specificType") + " for " + (this.currentbid * this.currentauction.quantity) + "₢\n";
			var loserprice = (this.previousbid * this.currentauction.quantity);
			var losemsg = this.aucloser + " must also pay " + loserprice + "₢\n";

			if (this.currentbid == this.playerbid) {
				this.auctionmessage = premsg + "Commander " + player.name + winmsg;
				if (!this.moveAuctionBlock()) {
					// this shouldn't happen, in theory
					this.auctionmessage = "You don't have the credits/cargo hold to cover that bid! You are forcibly ejected from the room.";
				}
				if (angryloser > 0 && Math.random() < angryloser) {
					cte.debug("Easter egg activated!");
					this.easteregg = this.aucloser;
				}
			} else {
				this.auctionmessage = premsg + this.aucwinner + winmsg;
			}

			if (this.aucloser == "Commander " + player.name) {
				player.credits -= loserprice;
			}
			this.auctionmessage += losemsg;

			this.endAuction();
			return;
		}
	} else {
		this.lastcall = 0;
	}
	this.playXrataAuction();
}
Scripts/cargotypeconditions.js
"use strict";
this.name = "CargoTypeExtension-Conditions";
this.author = "phkb";
this.copyright = "2023 phkb";
this.description = "Condition script for equipment.";
this.licence = "CC BY-NC-SA 3.0";

//-------------------------------------------------------------------------------------------------------------
this.allowAwardEquipment = function (equipment, ship, context) {
    if (context == "scripted") return true;
    if (missionVariables.cargotypeextension_tradenetrenew == 1) return true;
    return false;
}
Scripts/cargotypedefaultmarket.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-DefaultMarket";
this.description = "The default marketinfo entry for main stations";

/* In addition to the general New Cargoes license, you can use and
 * modify this script as a baseline for your own marketinfo entries as
 * if it were public domain */

this.randomCargoChance = function (good) {
	return 1;
}

this.randomCargoAmount = function (good) {
	return 3;
}

this.exportCargoAmount = function (good) {
	return 1;
}

this.exportCargoPrice = function (good) {
	return 1;
}

this.randomImportChance = function (good) {
	return 0;
}

this.systemImportChance = function (good) {
	return 1;
}

this.importCargoPrice = function (good) {
	return 1;
}

this.importPermitCheck = function () {
	return true;
}

this.exportPermitCheck = function () {
	return true;
}

this.stationGossip = function () {
	return false;
}
Scripts/cargotypedemo.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "Cargo Type Extension Demo";
this.description = "Demo of the cargo type extensions: the evil juice trading game";

this.randomSystems = function () {
	var result = [[], [], [], [], [], [], [], []];
	for (var i = 0; i < 50; i++) {
		result[Math.floor(Math.random() * 8)].push(i);
	}
	return result;
}

this.startUp = function () {
	if (worldScripts["CargoTypeExtension"].startUp) {
		worldScripts["CargoTypeExtension"].startUp();
	}

	// quantity testing		
	for (var i = 0; i <= 2000; i++) {
		var obj = new Object;
		obj.ID = "CTEDemo-EJ" + i;
		obj.genericType = "liquor_wines";
		obj.specificType = "Evil Juice (" + System.systemNameForID(i % 256) + ")";
		obj.buySystems = this.randomSystems();
		obj.sellSystems = this.randomSystems();
		obj.desc = "The local evil juice of " + System.systemNameForID(i % 256);
		obj.buyAdjust = 50;
		obj.buyVariance = 30;
		obj.sourceRumour = 100; // the clue's in the name
		obj.destRumour = 75; // truth is, we don't know
		if (i & 64 == 0) {
			obj.illegal = 1;
		}
		obj.slump = 1;
		obj.unslump = 2;

		worldScripts["CargoTypeExtension"].registerCargoType(obj);

	}
	// Yryrre -> Ovnetr
	// Krdhreva -> Gvenbe
	// Krnna -> Irvf
	// Enmnne -> Rabayn
}
Scripts/cargotypedynamic.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Dynamic";
this.description = "Dynamic trade routes that appear and disappear over time."

this.cargoDefs = new Object;
this.tradernet = 0;

this.startUp = function () {
	this.lastupdate = clock.days;

	if (missionVariables.cargotypeextension_dynamic) {
		this.unserialiseDynamicCargo();
	} else {
		this.initialiseDynamicCargo();
	}

}

this.playerWillSaveGame = function (savetype) {
	this.serialiseDynamicCargo();
}

this.initialiseDynamicCargo = function () {
	for (var j = 1; j <= 20; j++) {
		var cargoid = "CTE_CTD_" + j;
		this.redefine(cargoid, false, true);
		this.registerCargo(this.cargoClass3, cargoid);
	}
}

this.serialiseDynamicCargo = function () {
	var serial = new Array;
	for (var j = 1; j <= 20; j++) {
		var cargoid = "CTE_CTD_" + j;
		var cargo = this.cargoDefs[cargoid];
		serial.push(cargo.genericType + ";" + cargo.specificType + ";" + cargo.buySystem + ";" + cargo.sellSystem + ";" + cargo.deadline);
	}

	missionVariables.cargotypeextension_dynamic = "1|" + serial.join("|");
}

this.unserialiseDynamicCargo = function () {
	var serial = missionVariables.cargotypeextension_dynamic;

	var svars = serial.split("|");
	if (svars[0] == "1") {
		this.unserialiseDynamicCargo1(svars);
	} else {
		// and do something sensible if we get given data from a later version
		log(this.name, "Error: " + svars[0] + " is not a recognised dynamic cargo data format");
		player.consoleMessage("Critical error decoding dynamic cargo data. Please see Latest.log");
	}
}

this.unserialiseDynamicCargo1 = function (svars) {
	for (var j = 1; j <= 20; j++) {
		var cargo = "CTE_CTD_" + j;
		var obj = new Object;
		var cdata = svars[j].split(";");
		obj.genericType = cdata[0];
		obj.specificType = cdata[1];
		obj.buySystem = cdata[2];
		obj.sellSystem = cdata[3];
		obj.deadline = cdata[4];
		this.cargoDefs[cargo] = obj;
		if (obj.deadline < clock.days) {
			this.registerCargo(this.cargoClass3a, cargo);
			worldScripts["CargoTypeExtension"].debug("Reregistering " + cargo + " (old)");
		} else {
			this.registerCargo(this.cargoClass3, cargo);
			worldScripts["CargoTypeExtension"].debug("Reregistering " + cargo + " (okay)");
		}
	}
}

this.shipWillEnterWitchspace = function (cause) {
	this.tradernet = 1;
	if (cause != "galactic jump") {
		// no point
		for (var j = 1; j <= 20; j++) {
			this.updateDynamicCargo("CTE_CTD_" + j, false);
		}
	}
	this.tradernet = 0;
}

this.playerEnteredNewGalaxy = function () {
	this.tradernet = 0;
	for (var j = 1; j <= 20; j++) {
		this.updateDynamicCargo("CTE_CTD_" + j, true);
	}
	this.tradernet = 0;
}

this.updateDynamicCargo = function (cargoid, forcenew) {
	if (!forcenew && this.cargoDefs[cargoid].deadline > clock.days) {
		return;
	}
	if (worldScripts["CargoTypeExtension"].hasSpecialCargo(cargoid)) {
		// If it's being carried, just slump it.
		// Otherwise, erase the ID
		this.registerCargo(this.cargoClass3a, cargoid);
	} else {
		this.redefine(cargoid, !forcenew, forcenew);
		this.registerCargo(this.cargoClass3, cargoid);
	}

}

this.registerCargo = function (cargoclass, cargoid) {
	var cargo = cargoclass();
	cargo.ID = cargoid;
	cargo.genericType = this.cargoDefs[cargoid].genericType;
	cargo.specificType = this.systemIan(this.cargoDefs[cargoid].buySystem) + " " + this.cargoDefs[cargoid].specificType;
	cargo.buySystems = this.systemList(this.cargoDefs[cargoid].buySystem);
	cargo.sellSystems = this.systemList(this.cargoDefs[cargoid].sellSystem);
	cargo.desc = this.cargoDescription(this.cargoDefs[cargoid]);

	worldScripts["CargoTypeExtension"].registerCargoType(cargo);
}

this.systemIan = function (sysid) {
	if (Math.floor(sysid / 256) == galaxyNumber) {
		return System.infoForSystem(galaxyNumber, sysid % 256).name + "ian";
	} else {
		return "Extragalactic";
	}
}

this.systemList = function (sysid) {
	var list = [[], [], [], [], [], [], [], []];
	list[Math.floor(sysid / 256)].push(sysid % 256);
	return list;
}


this.redefine = function (cargoid, snoopers, inmediares) {
	var cargodef = new Object;

	// not alien items
	var generics = ["food", "textiles", "radioactives", "slaves", "liquor_wines", "luxuries", "narcotics", "computers", "machinery", "alloys", "firearms", "furs", "minerals"];

	var gtype = generics[Math.floor(Math.random() * generics.length)];

	var stype = expandDescription("[cte_ctd_" + gtype + "]");

	cargodef.genericType = gtype;
	cargodef.specificType = stype;

	do {
		do {
			var source = Math.floor(Math.random() * 256);
			var sourceinfo = System.infoForSystem(galaxyNumber, source);
		} while (sourceinfo.sun_gone_nova);
		var dest = Math.floor(Math.random() * 256);
		var destinfo = System.infoForSystem(galaxyNumber, dest);
		var route = sourceinfo.routeToSystem(destinfo);
	} while (!(route && route.distance > 20 && !destinfo.sun_gone_nova));


	cargodef.buySystem = galaxyNumber * 256 + source;
	cargodef.sellSystem = galaxyNumber * 256 + destinfo.systemID;

	if (!inmediares) {
		cargodef.deadline = clock.days + Math.floor((1 + sourceinfo.routeToSystem(destinfo).time / 24) * (1 + (2 * Math.random())));
	} else {
		// initialisation or galjump: many of the deals will have existed for a while
		cargodef.deadline = clock.days + Math.floor(Math.floor(1 + sourceinfo.routeToSystem(destinfo).time / 24) * (3 * Math.random()));
	}

	this.cargoDefs[cargoid] = cargodef;

	for (var i = 0; i < 3; i++) {
		// shouldn't be more than three locations affected
		worldScripts["CargoTypeExtension"].undepressGood(cargoid);
	}

	worldScripts["CargoTypeExtension"].debug("Defined dynamic cargo " + stype + " from " + source + " to " + destinfo.systemID + " by " + cargodef.deadline);

	//		if (snoopers && this.tradernet == 1 && player.ship.equipmentStatus("EQ_CTE_TRADERNET") == "EQUIPMENT_OK" && clock.days <= missionVariables.cargotypeextension_tradernet) {
	if (this.tradernet == 1) { // don't notify on init or galjump!
		this.traderNetNotify(cargodef);
	}
	//		}

}

this.systemName = function (systemid) {
	if (Math.floor(systemid / 256) != galaxyNumber) {
		return "";
	}
	return System.infoForSystem(Math.floor(systemid / 256), systemid % 256).name;
}

this.cargoDescription = function (cargodef) {
	var sname = this.systemName(cargodef.buySystem);
	var dname = this.systemName(cargodef.sellSystem);
	if (!sname || !dname) { return "This cargo originates from chart " + (1 + Math.floor(cargodef.buySystem / 256)); }
	/*		if (cargodef.deadline > clock.days) {
					var desc = sname+"ian "+cargodef.specificType+" is currently needed urgently in "+dname+". This is expected to continue until around "+cargodef.deadline;
			} else {
					var desc = sname+"ian "+cargodef.specificType+" was needed urgently in "+dname+". Who knows - they might still need some.";
			} */
	var desc = "Canisters of " + sname + "ian " + cargodef.specificType + ".";
	return desc;
}

this.traderNetNotify = function (cargodef) {

	var msg = expandDescription("[cte_ctd_snoopers" + (1 + Math.floor(Math.random() * 9)) + "]",
		{
			cte_ctd_tradername: expandDescription("[nom1]"),
			cte_ctd_source: this.systemName(cargodef.buySystem),
			cte_ctd_dest: this.systemName(cargodef.sellSystem),
			cte_ctd_deadline: cargodef.deadline,
			cte_ctd_good: cargodef.specificType,
			cte_ctd_generic: worldScripts["CargoTypeExtension"].genericName(cargodef.genericType),
			cte_ctd_random: 50 + Math.floor(Math.random() * 75)
		});

	worldScripts["CargoTypeExtension"].addTraderNet(msg);

	//		this.tradernet = 0; // max 1 update of dynamic routes per jump
}

// cargo class 3 has a higher distance bonus and sale bonus for short-term deal
this.cargoClass3 = function () {
	var cargo = new Object;
	cargo.buyAdjust = 120; // a bit more than normal
	cargo.sellDistance = 20;
	cargo.sellAdjust = 250;
	cargo.slump = 0.3;
	cargo.unslump = 0.1;
	cargo.sourceRumour = 100;
	cargo.destinationRumour = 90;
	cargo.salvageMarket = 2; // less likely than normal
	cargo.forbidExtension = 1;
	return cargo;
}

// switch to 3a once the delivery deadline has passed
this.cargoClass3a = function () {
	var cargo = new Object;
	cargo.sellDistance = 2;
	cargo.sellAdjust = 25;
	cargo.slump = 30;
	cargo.unslump = 0.1;
	cargo.sourceRumour = -1;
	cargo.destinationRumour = -1;
	cargo.salvageMarket = 0.01;
	cargo.forbidExtension = 1;
	return cargo;
}
Scripts/cargotypeextension.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension";
this.description = "A script to allow cargoes other than the standard 17 to be stored in TC-sized containers in the hold";

/* "Public" functions. Interface should be consistent */
// todo: serialise individual item?

this.registerPersonality = function (name) {
	if (this.startUp) { this.startUp(); }

	this.debug("Registering " + name + " personality extension");
	this.personalities.push(name);
}

this.registerPermit = function (name) {
	if (this.startUp) { this.startUp(); }

	this.debug("Registering " + name + " permit extension");
	this.permits.push(name);
}

this.registerPriceChange = function (name) {
	if (this.startUp) { this.startUp(); }

	this.debug("Registering " + name + " price extension");
	this.pricefluxes.push(name);
}

this.registerOXPStation = function (name, role) {
	if (this.startUp) { this.startUp(); }

	this.debug("Registering " + name + " station role");
	this.oxpstations[role] = name;
}

this.registerCargoType = function (obj) {
	if (this.startUp) { this.startUp(); }

	if (!obj.ID) { return this.error("registerCargoType: No ID specified"); }
	if (!obj.ID.match(/^[A-Za-z0-9._-]+$/)) { return this.error("registerCargoType: " + obj.ID + " invalid format"); }
	// actually, allowing scripted changes in behaviour by re-registering makes more sense
	var already = false;
	if (this.specialCargoRegister[obj.ID]) { already = true; }
	if (this.cargoTypes.indexOf(obj.genericType) === -1) { return this.error("registerCargoType: " + obj.ID + ": " + obj.genericType + " is not a valid generic commodity"); }
	if (!obj.specificType) { return this.error("registerCargoType: " + obj.ID + ": No specific type specified"); }
	if (!this.isSystemList(obj.buySystems)) { return this.error("registerCargoType: " + obj.ID + ": Invalid source systems specified"); }
	if (!this.isSystemList(obj.sellSystems)) { return this.error("registerCargoType: " + obj.ID + ": Invalid destination systems specified"); }

	// default 80% more expensive than a generic bundle's average price here
	// so you don't just buy them normally for the Agri<->Ind trade
	if (!obj.buyAdjust) { obj.buyAdjust = 80; }
	// give or take 30%
	if (!obj.buyVariance) { obj.buyVariance = 30; }
	// quantity available (as a percentage of normal availability)
	// set to a negative number to prevent appearance on exporter main market
	if (!obj.buyQuantity) { obj.buyQuantity = 50; }
	// quantityvariance +/- 100% default
	if (!obj.buyAvailability) { obj.buyAvailability = 100; }
	// default 100% better sales price
	if (!obj.sellAdjust) { obj.sellAdjust = 100; }
	// sales price variance default 50%
	if (!obj.sellVariance) { obj.sellVariance = 50; }
	// max sales price bonus per LY from nearest source (cap at ~diagonal of 100LY)
	// tends towards higher end of distribution (roll twice pick best)
	// no route in galaxy counts as 150LY
	if (!obj.sellDistance) { obj.sellDistance = 10; }
	// default to being no more illegal than the base good
	if (!obj.illegal) { obj.illegal = 0; }
	// chance of a catastrophic market slump in percent (per day)
	if (!obj.slump) { obj.slump = 2; }
	// chance of recovering from slump in percent (per day)
	if (!obj.unslump) { obj.unslump = 7; }
	// chance of source rumours being reliable 
	// less than zero for no rumours
	if (!obj.sourceRumour) { obj.sourceRumour = 90; }
	// chance of destination rumours being reliable 
	if (!obj.destinationRumour) { obj.destinationRumour = 70; }
	// %chance of finding outside normal exporter (set negative to remove entirely)
	if (!obj.salvageMarket) { obj.salvageMarket = 5; }
	// forbid use by well-behaved extensions
	if (!obj.forbidExtension) { obj.forbidExtension = 0; }

	if (!obj.desc) { obj.desc = "No information available"; }


	this.specialCargoRegister[obj.ID] = obj;
	if (!already) {
		this.specialCargoList.push(obj.ID); // easier to iterate over
	}

	this.debug("Registered cargo type: " + obj.ID);

}

// remove 1 unit of cargo from the hold
this.removeSpecialCargo = function (id) {

	var generic = this.specialCargoRegister[id].genericType;

	for (var j = 0; j < this.specialCargoCarried[generic].length; j++) {
		if (this.specialCargoCarried[generic][j].type == id) {
			if (this.specialCargoCarried[generic][j].quantity > 0) {
				this.specialCargoCarried[generic][j].quantity--;
				if (this.specialCargoCarried[generic][j].quantity == 0) {
					this.specialCargoCarried[generic].splice(j, 1);
				}
				player.ship.manifest[generic]--;
				this.debug("Removed " + id + " in removeSpecialCargo");
				return true;
			}
		}
	}
	return false;
}

// add 1 unit of cargo to the hold
this.addSpecialCargo = function (id, origininfo) {
	if (!this.specialCargoRegister[id]) {
		this.error("Error: Cargo type " + id + " not defined!");
		return false;
	}

	var generic = this.specialCargoRegister[id].genericType;

	// try to add 1 unit - if it fails we have no room
	var current = player.ship.manifest[generic];
	player.ship.manifest[generic]++;
	if (player.ship.manifest[generic] == current) {
		return false;
	}

	// at this point the process can't fail.
	var found = false;
	for (var j = 0; j < this.specialCargoCarried[generic].length; j++) {
		if (this.specialCargoCarried[generic][j].type == id) {
			found = true;
			this.specialCargoCarried[generic][j].quantity++;
		}
	}
	if (!found) {
		var cargo = new Object;
		cargo.quantity = 1;
		cargo.type = id;
		if (origininfo) {
			cargo.origin = origininfo;
		} else {
			cargo.origin = "unknown origin";
		}
		this.specialCargoCarried[generic].push(cargo);
	}

	//player.ship.manifest[generic]++;
	//		this.debug("Added "+id);
	return true;
}

this.hasSpecialCargo = function (id) {
	if (!this.specialCargoRegister[id]) {
		return 0; // can't be carrying unregistered cargo
		// well, okay, you could be, but the OXP shouldn't be asking if you've
		// got any before it registers it...
	}
	var generic = this.specialCargoRegister[id].genericType;

	for (var j = 0; j < this.specialCargoCarried[generic].length; j++) {
		if (this.specialCargoCarried[generic][j].type == id) {
			return this.specialCargoCarried[generic][j].quantity;
		}
	}
	return 0;
}

this.specialCargoesCarried = function (generic) {
	var carried = new Array;
	for (var i = 0; i < this.cargoTypes.length; i++) {
		if (!generic || this.cargoTypes[i] == generic) {
			var cblock = this.specialCargoCarried[this.cargoTypes[i]];
			for (var j = 0; j < cblock.length; j++) {
				if (cblock[j].quantity > 0) {
					carried.push(cblock[j].type);
				}
			}
		}
	}
	return carried;
}

this.marketCollapsed = function (good, galaxy, system) {
	return this.marketCollapsedID(good, (galaxy * 256) + system);
}

this.importPermitLevel = function (good, quantity) {
	var score = 0;
	for (var k = 0; k < this.permits.length; k++) {
		var permitlevel = worldScripts[this.permits[k]].checkImport(good, quantity, true);
		score += permitlevel;
	}
	return score;
}

this.exportPermitLevel = function (good, quantity) {
	var score = 0;
	for (var k = 0; k < this.permits.length; k++) {
		var permitlevel = worldScripts[this.permits[k]].checkPermit(good, quantity, true);
		score += permitlevel;
	}
	return score;
}

this.importPermitDetails = function (good, quantity) {
	var score = new Array;
	for (var k = 0; k < this.permits.length; k++) {
		var permitlevel = worldScripts[this.permits[k]].checkImport(good, quantity, true);
		score.push(permitlevel);
	}
	return score;
}

this.exportPermitDetails = function (good, quantity) {
	var score = new Array;
	for (var k = 0; k < this.permits.length; k++) {
		var permitlevel = worldScripts[this.permits[k]].checkPermit(good, quantity, true);
		score.push(permitlevel);
	}
	return score;
}

this.extendableCargo = function (ctype) {
	return this.extendableCargoSeeded(ctype, 0);
}

this.extendableCargoSeeded = function (ctype, seed) {
	var opts = new Array();

	for (var i = 0; i < this.specialCargoList.length; i++) {
		var cargo = this.specialCargoRegister[this.specialCargoList[i]];
		if (ctype == "") {
			if (cargo.buySystems[galaxyNumber].indexOf(system.ID) !== -1 && !cargo.forbidExtension) {
				opts.push(this.specialCargoList[i]);
			}
		} else {
			if ((ctype == "any" || cargo.genericType == ctype) && !cargo.forbidExtension) {
				opts.push(this.specialCargoList[i]);
			}
		}
	}
	if (opts.length == 0) {
		return false;
	}
	if (seed == 0) {
		return opts[Math.floor(Math.random() * opts.length)];
	} else {
		return opts[Math.floor(this.weeklyChaosAux(seed) * opts.length)];
	}
}

this.localCargoData = function (goodid) {
	for (var i = 0; i < this.specialCargoSystem.length; i++) {
		if (this.specialCargoSystem[i].type == goodid) {
			return [this.specialCargoSystem[i].quantity, this.specialCargoSystem[i].price];
		}
	}
	return [0, 0];
}

this.controlledGood = function (good) {
	var details = this.specialCargoRegister[good];
	var illegality = details.illegal;
	illegality += system.mainStation.market[details.genericType].legality_import;
	illegality += system.mainStation.market[details.genericType].legality_export;
	/*if (details.genericType == "slaves" || details.genericType == "firearms") {
			illegality++;
	} else if (details.genericType == "narcotics") {
			illegality += 2;
	}*/
	return (illegality > 0);
}

this.systemImports = function (gal, sys) {
	var imports = new Array;
	for (var i = 0; i < this.specialCargoList.length; i++) {
		var good = this.specialCargoList[i];
		if (this.specialCargoRegister[good].sellSystems[gal].indexOf(sys) >= 0) {
			imports.push(good);
		}
	}
	return imports;
}

this.systemExports = function (gal, sys) {
	var exports = new Array;
	for (var i = 0; i < this.specialCargoList.length; i++) {
		var good = this.specialCargoList[i];
		if (this.specialCargoRegister[good].buySystems[gal].indexOf(sys) >= 0) {
			exports.push(good);
		}
	}
	return exports;
}

this.cargoDefinition = function (good, param) {
	if (this.specialCargoRegister[good]) {
		return this.specialCargoRegister[good][param];
	} else {
		this.debug(good + " not found in sCR");
		return false;
	}
}

this.cargoPriceExport = function (id, i, marketinfo) {
	var baseprice = this.genericPrice(this.specialCargoRegister[id].genericType, i);
	var chaos = i * 100;
	var pricemod = 1 + (this.specialCargoRegister[id].buyAdjust / 100) + ((this.weeklyChaos(chaos + 1) - 0.5) * this.specialCargoRegister[id].buyVariance / 100);
	var price = Math.floor(baseprice * pricemod);
	price *= this.marketPriceFluctuations(id, "FLUX_EXPORT");
	price *= marketinfo.exportCargoPrice(id);
	return price;
}

this.cargoPriceImport = function (id, i, marketinfo) {
	var baseprice = this.genericPrice(this.specialCargoRegister[id].genericType, i);
	var chaos = i * 100;
	if (this.marketCollapsed(id, galaxyNumber, system.ID)) {
		// might still make some profit on it, but not much
		var price = Math.floor(0.7 + (0.6 * this.weeklyChaos(chaos + 16)) * baseprice);
		this.debug("Collapsed: " + price);
	} else {
		var sa = (this.specialCargoRegister[id].sellAdjust / 100);
		var sv = ((this.weeklyChaos(chaos + 3) - 0.5) * this.specialCargoRegister[id].sellVariance / 100);
		var pricemod = 1 + sa + sv;
		var distmod = this.distanceBonus(id, i);
		this.debug("Sale: " + baseprice + " * (" + pricemod + "(" + sa + "," + sv + ") + " + distmod + ") = " + (pricemod + distmod) + " => " + (baseprice * (pricemod + distmod)) + " ?" + chaos);
		var price = 150 + Math.floor(baseprice * (pricemod + distmod)); // add on 15 Cr. flat bonus so that Food / Textiles / Minerals are still worth trading.
	}
	price *= this.marketPriceFluctuations(id, "FLUX_IMPORT");
	this.debug("After flux: " + price);
	price *= marketinfo.importCargoPrice(id);
	this.debug("After station mod: " + price);

	return Math.floor(price);
}

this.cargoPriceMisc = function (id, i, marketinfo) {
	var baseprice = this.genericPrice(this.specialCargoRegister[id].genericType, i);
	var chaos = i * 100;
	var price = Math.floor((1.5 + (0.5 * this.weeklyChaos(chaos + 15))) * baseprice);
	var pricemod = 1 + (this.specialCargoRegister[id].buyAdjust / 200);
	price = price * pricemod;
	price *= this.marketPriceFluctuations(id, "FLUX_MISC");

	if (price < 1) {
		price = 1; // always at least 0.1 Cr.
	}
	return price;
}

this.cargoQuantityExport = function (id, i, marketinfo) {
	if (this.specialCargoRegister[id].buyQuantity < 0) {
		return 0;
	}
	var basequantity = this.genericQuantity(this.specialCargoRegister[id].genericType);
	var chaos = i * 100;
	var quantmod = 1 + (this.specialCargoRegister[id].buyQuantity / 100) + (((this.weeklyChaos(chaos + 5) * 2) - 1) * this.specialCargoRegister[id].buyAvailability / 100);
	var quantity = basequantity * quantmod;
	if (quantity < 10) {
		quantity += 10;
	}
	quantity *= marketinfo.exportCargoAmount(id);
	if (this.marketCollapsed(id, galaxyNumber, system.ID)) {
		quantity /= 10;
	}
	return Math.floor(quantity);
}

this.cargoQuantityMisc = function (id, i, marketinfo) {
	var chaos = i * 100;

	if (Math.floor(this.weeklyChaos(chaos + 14) * 100) < this.specialCargoRegister[id].salvageMarket * marketinfo.randomCargoChance(id)) {
		var basequantity = this.genericQuantity(this.specialCargoRegister[id].genericType);
		return 1 + Math.floor(basequantity % marketinfo.randomCargoAmount(id)); // 1-n TC of a random good in stock
	} else {
		return 0;
	}
}

this.addTraderNet = function (msg) {
	worldScripts["CargoTypeExtension-TraderNet"].addTraderNet(msg);
}

this.suspendPlayerManifest = function (good_only) {
	var ncmanifest = this.serialiseCargoCarried(good_only);
	this.resetCargoCarried(good_only);
	return "2|" + ncmanifest;
}

this.restorePlayerManifest = function (ncmanifest) {
	this.resetCargoCarried();
	return this.mergePlayerManifest(ncmanifest);
}

this.mergePlayerManifest = function (ncmanifest) {
	if (ncmanifest == "") {
		this.error("Warning: manifest format for merge/restore not recognised!");
		return false;
	}
	var ncms = ncmanifest.split("|");
	if (ncms[0] == "1") {
		this.unserialiseCargoCarried1(ncms[1]);
	} else if (ncms[0] == "2") {
		this.unserialiseCargoCarried2(ncms[1]);
	} else {
		this.error("Warning: manifest format for merge/restore not recognised!");
		return false;
	}

	return true;
}

this.defaultMarketInfo = function () {
	return worldScripts["CargoTypeExtension-DefaultMarket"];
}

this.checkImports = function (station) {
	if (station.isMainStation) { return true; }
	var marketinfo = this.getOXPMarket(station);
	if (!marketinfo) { return false; }
	return worldScripts[marketinfo].importPermitCheck();
}

this.checkExports = function (station) {
	if (station.isMainStation) { return true; }
	var marketinfo = this.getOXPMarket(station);
	if (!marketinfo) { return false; }
	return worldScripts[marketinfo].exportPermitCheck();
}

/* "Private" functions. May change at any time */

// A list of Worldscripts providing more interesting trade offers 
this.personalities = new Array;
this.permits = new Array;
this.pricefluxes = new Array;
this.oxpstations = new Object;
this.lastscreenchoice = null;
this.recordBounty = true;

// only cover TC=sized for now
// matches the keys in Manifest
this.cargoTypes = ["food", "textiles", "radioactives", "slaves", "liquor_wines", "luxuries", "narcotics", "computers", "machinery", "alloys", "firearms", "furs", "minerals", "alien_items"];
// if any others added later, *add them to the end of the list*

this.initComplete = 0;
this.pointer = -1;

// copied straight out of commodities.plist
this.defaultCommodities = [
	["food", 0, 0, 19, -2, -2, 6, 1, 1, 0],
	["textiles", 0, 0, 20, -1, -1, 10, 3, 3, 0],
	["radioactives", 0, 0, 65, -3, -3, 2, 7, 7, 0],
	["slaves", 0, 0, 40, -5, -5, 226, 31, 31, 0],
	["liquor_wines", 0, 0, 83, -5, -5, 251, 15, 15, 0],
	["luxuries", 0, 0, 196, 8, 8, 54, 3, 3, 0],
	["narcotics", 0, 0, 235, 29, 29, 8, 120, 120, 0],
	["computers", 0, 0, 154, 14, 14, 56, 3, 3, 0],
	["machinery", 0, 0, 117, 6, 6, 40, 7, 7, 0],
	["alloys", 0, 0, 78, 1, 1, 17, 31, 31, 0],
	["firearms", 0, 0, 124, 13, 13, 29, 7, 7, 0],
	["furs", 0, 0, 176, -9, -9, 220, 63, 63, 0],
	["minerals", 0, 0, 32, -1, -1, 53, 3, 3, 0],
	//                [ "gold", 0, 0, 97, -1, -1, 66, 7, 7, 1 ],
	//                [ "platinum", 0, 0, 171, -2, -2, 55, 31, 31, 1 ],
	//                [ "gem_stones", 0, 0, 45, -1, -1, 250, 15, 15, 2 ],
	["alien_items", 0, 0, 53, 15, 0, 0, 7, 0, 0]
];


this.resetCargoCarried = function (good_only) {
	if (!good_only) this.specialCargoCarried = new Object;
	for (var i = 0; i < this.cargoTypes.length; i++) {
		if (!good_only || this.cargoTypes[i] == good_only) {
			this.specialCargoCarried[this.cargoTypes[i]] = new Array;
		}
	}
}

// some of this Array storage is speed-inefficient for search but on
// the other hand we run out of storage memory long before that
// actually becomes a practical issue, and we only need to do the
// searches at natural pauses anyway, so...
this.startUp = function () {
	// types of special cargo available
	this.specialCargoRegister = new Object;
	this.specialCargoList = new Array;
	this.localbuyables = new Array;
	this.localsellables = new Array;

	// types of special cargo currently onboard. Ish.
	this.resetCargoCarried();
	// special cargo for sale in this system
	this.specialCargoSystem = new Array;
	// cargo currently affected by market catastrophes
	this.marketCatastrophes = new Array;

	this.unserialiseCargoData();

	delete this.startUp;
}


// serialisation format 1
// version|specialCargoCarried|specialCargoSystem|marketCatastrophes
// version|foodid=quant;...;.../textilesid=quant/...////.....|id=ct=price;...|id=sysid;...

this.marketCollapsedID = function (good, sysid) {
	for (var i = 0; i < this.marketCatastrophes.length; i++) {
		if (this.marketCatastrophes[i].type == good && this.marketCatastrophes[i].system == sysid) {
			return true;
		}
	}
	return false;
}

this.serialiseCargoCarried = function (good_only) {
	var serial = "";
	for (var i = 0; i < this.cargoTypes.length; i++) {
		if (!good_only || this.cargoTypes[i] == good_only) {
			var generic = this.specialCargoCarried[this.cargoTypes[i]];
			for (var j = 0; j < generic.length; j++) {
				if (this.specialCargoRegister[generic[j].type]) {
					// only save those that are still registered
					serial += generic[j].type + "=" + generic[j].quantity + "=" + generic[j].origin;
					if (j + 1 < generic.length) {
						serial += ";";
					}
				}
			}
		}
		if (i + 1 < this.cargoTypes.length) {
			serial += "/";
		}
	}
	return serial;
}

this.serialiseCargoData = function () {
	//    this.specialCargoCarried		
	//    this.specialCargoSystem
	//		this.marketCatastrophes
	var serial = "2|"; // version number

	serial += serialiseCargoCarried();

	serial += "|";
	for (var i = 0; i < this.specialCargoSystem.length; i++) {
		var c = this.specialCargoSystem[i];
		if (this.specialCargoRegister[c.type]) {
			serial += c.type + "=" + c.quantity + "=" + Math.floor(10 * c.price);
			if (i + 1 < this.specialCargoSystem.length) {
				serial += ";";
			}
		}
	}
	serial += "|";
	for (var i = 0; i < this.marketCatastrophes.length; i++) {
		var catas = this.marketCatastrophes[i];
		if (this.specialCargoRegister[catas.type]) {
			// drop unregistered cargoes from the list
			serial += catas.type + "=" + catas.system;
			if (i + 1 < this.marketCatastrophes.length) {
				serial += ";";
			}
		}
	}

	missionVariables.cargotypeextension_state = serial;
}

this.unserialiseCargoData = function () {
	if (!missionVariables.cargotypeextension_state) {
		// first use
		this.debug("No serialised data, starting afresh");
		// can't do this yet, because we have to wait for everything else to
		// startUp and register the goods
		this.timer = new Timer(this, this.initialiseSystemMarket, 0.25);
		return;
	}
	var serial = missionVariables.cargotypeextension_state;

	var svars = serial.split("|");
	// make sure we can always load this data from previous versions
	if (svars[0] == "1") {
		this.unserialiseCargoData1(svars);
	} else if (svars[0] == "2") {
		this.unserialiseCargoData2(svars);
	} else {
		// and do something sensible if we get given data from a later version
		log(this.name, "Error: " + svars[0] + " is not a recognised cargo data format");
		player.consoleMessage("Critical error decoding special cargo data. Please see Latest.log");
	}
}

this.unserialiseCargoCarried1 = function (cargodata) {
	this.unserialiseCargoCarried2(cargodata);
}

this.unserialiseCargoCarried2 = function (cargodata) {
	var gens = cargodata.split("/");
	for (var i = 0; i < gens.length; i++) {
		log(this.name, "processing " + gens[i]);
		if (gens[i].trim() != "") {
			var sclist = new Array;
			var specs = gens[i].split(";");
			if (specs[0] != "") {
				for (var j = 0; j < specs.length; j++) {
					log(this.name, "processing " + specs[j]);
					var spcar = specs[j].split("=");
					var already = false;
					log(this.name, "searching for " + spcar[0] + "...");
					for (var k = 0; k < this.specialCargoCarried[this.cargoTypes[i]].length; k++) {
						if (spcar[0] == this.specialCargoCarried[this.cargoTypes[i]][k].type) {
							this.debug(spcar[0] + " add quantity");
							// don't need to worry about a zero amount here
							this.specialCargoCarried[this.cargoTypes[i]][k].quantity += parseInt(spcar[1]);
							already = true;
							break;
						}
					}
					if (!already) {
						var canisters = new Object;
						canisters.type = spcar[0];
						canisters.quantity = parseInt(spcar[1]);
						if (spcar.length >= 3) {
							canisters.origin = spcar[2];
						} else {
							canisters.origin = "unknown origin";
						}
						this.debug(spcar[0] + " new");
						// only add the cargo if the quantity is greater than 0
						if (canisters.quantity > 0)
							sclist.push(canisters);
					}
				}
			}
			this.specialCargoCarried[this.cargoTypes[i]] = this.specialCargoCarried[this.cargoTypes[i]].concat(sclist);
			this.debug(this.specialCargoCarried[this.cargoTypes[i]].length + " entries for " + this.cargoTypes[i]);
		}
	}
}


this.unserialiseCargoData1 = function (svars) {
	// cargo hold
	this.unserialiseCargoCarried1(svars[1]);

	var syss = svars[2].split(";");
	var sysclist = new Array;
	if (syss[0] != "") {
		for (var i = 0; i < syss.length; i++) {
			var spcar = syss[i].split("=");
			var canisters = new Object;
			canisters.type = spcar[0];
			canisters.quantity = parseInt(spcar[1]);
			canisters.price = parseInt(spcar[2]) / 10;
			sysclist.push(canisters);
		}
	}
	this.specialCargoSystem = sysclist;

	var mcats = svars[3].split(";");
	var mcatlist = new Array;
	if (mcats[0] != "") {
		for (var i = 0; i < mcats.length; i++) {
			var mcatdata = mcats[i].split("=");
			mcat = new Object;
			mcat.type = mcatdata[0];
			mcat.system = parseInt(mcatdata[1]);
			mcatlist.push(mcat);
		}
	}
	this.marketCatastrophes = mcatlist;
}

this.unserialiseCargoData2 = function (svars) {
	// cargo hold

	this.unserialiseCargoCarried2(svars[1]);

	var gens = svars[1].split("/");
	for (var i = 0; i < gens.length; i++) {
		var sclist = new Array;
		var specs = gens[i].split(";");
		if (specs[0] != "") {
			for (var j = 0; j < specs.length; j++) {
				var spcar = specs[j].split("=");
				var canisters = new Object;
				canisters.type = spcar[0];
				canisters.quantity = parseInt(spcar[1]);
				canisters.origin = spcar[2];
				sclist.push(canisters);
			}
		}
		this.specialCargoCarried[this.cargoTypes[i]] = sclist;
	}

	var syss = svars[2].split(";");
	var sysclist = new Array;
	if (syss[0] != "") {
		for (var i = 0; i < syss.length; i++) {
			var spcar = syss[i].split("=");
			var canisters = new Object;
			canisters.type = spcar[0];
			canisters.quantity = parseInt(spcar[1]);
			canisters.price = parseInt(spcar[2]) / 10;
			sysclist.push(canisters);
		}
	}
	this.specialCargoSystem = sysclist;

	var mcats = svars[3].split(";");
	var mcatlist = new Array;
	if (mcats[0] != "") {
		for (var i = 0; i < mcats.length; i++) {
			var mcatdata = mcats[i].split("=");
			var mcat = new Object;
			mcat.type = mcatdata[0];
			mcat.system = parseInt(mcatdata[1]);
			mcatlist.push(mcat);
		}
	}
	this.marketCatastrophes = mcatlist;
}


// returns true for system list
this.isSystemList = function (arr) {
	if (!arr) { return false; }
	if (arr.length != 8) { this.debug("Wrong length: " + arr.length); return false; }
	for (var i = 0; i < 8; i++) {
		for (var j = 0; j < arr[i].length; j++) {
			if (arr[i][j] < 0 || arr[i][j] > 255) {
				this.debug(arr[i][j]);
				return false; // invalid system ID
			}
		}
	}
	return true;
}

this.debug = function (msg) {
	log(this.name + ".debug", msg); //comment out for release versions
	return false;
}
this.error = function (msg) {
	log(this.name, msg);
	return false;
}


this.validateHold = function () {
	this.debug("Validating hold");
	// if there isn't enough generic cargo of a particular type
	// throw out special cargo entries of that type until there is
	for (var i = 0; i < this.cargoTypes.length; i++) {
		var totalspecial = 0;
		var totalgeneric = player.ship.manifest[this.cargoTypes[i]];
		for (var j = 0; j < this.specialCargoCarried[this.cargoTypes[i]].length; j++) {
			if (this.specialCargoRegister[this.specialCargoCarried[this.cargoTypes[i]][j].type]) {
				totalspecial += this.specialCargoCarried[this.cargoTypes[i]][j].quantity;
			} else { // no longer registered, make generic
				// yes, we're modifying the array in the middle of the loop
				// it works anyway
				this.specialCargoCarried[this.cargoTypes[i]].splice(j, 1);
				j--;
			}
		}
		this.debug(this.cargoTypes[i] + ": g=" + totalgeneric + ", s=" + totalspecial);
		if (totalspecial > totalgeneric) {
			for (var j = 0; j < totalspecial - totalgeneric; j++) {
				this.destroySpecialCargo(this.cargoTypes[i]);
			}
		}
	}

}

// destroy 1 item of special cargo of a particular generic type
// mainly for if it gets dumped or destroyed in battle
// or sold on the general market
this.destroySpecialCargo = function (generictype) {
	var types = this.specialCargoCarried[generictype];
	var type = Math.floor(Math.random() * types.length);

	this.debug("Removed " + generictype);
	if (this.specialCargoCarried[generictype][type].quantity > 1) {
		this.specialCargoCarried[generictype][type].quantity--;
	} else {
		this.specialCargoCarried[generictype].splice(type, 1);
	}
}

// give a pseudo-random number constant for the week, the system, and the salt
// range 0..1
this.weeklyChaos = function (salt) {
	// salts 1-11,14-149 used so far
	var n = (salt * 159217) + system.ID + (256 * galaxyNumber) + (103 * Math.floor((clock.days - 2084000) / 7));
	return this.weeklyChaosAux(n);
}

// RANROT (taken from native Oolite implementation)
this.weeklyChaosAux = function (n) {
	var high = (n & 0xFFFF0000) >> 16;
	var low = n & 0x0000FFFF;
	high = ((high << 8) + (high >> 8)) & 0x0000FFFF;
	high = (high + low) & 0x0000FFFF;
	low = high;
	high = ((high << 8) + (high >> 8)) & 0x0000FFFF;
	high = (high + low) & 0x0000FFFF;

	return high / 65536.0;
}

// work in decicredits for most of the function, return credits
this.cargoPrice = function (id, i, marketinfo, quantity) {

	if (this.specialCargoRegister[id].buySystems[galaxyNumber].indexOf(system.ID) !== -1) { // source system
		var price = this.cargoPriceExport(id, i, marketinfo);
	} else {
		var isimport = (this.specialCargoRegister[id].sellSystems[galaxyNumber].indexOf(system.ID) != -1);
		if (isimport) {
			if (Math.random() > marketinfo.systemImportChance(id)) {
				isimport = false; // override import
			}
		} else {
			if (Math.random() < marketinfo.randomImportChance(id)) {
				isimport = true; // override no-import
			}
		}

		if (isimport) { // dest system
			var price = this.cargoPriceImport(id, i, marketinfo);
		} else { // nothing special
			// more expensive than base price, probably
			var price = this.cargoPriceMisc(id, i, marketinfo);
			if (quantity == 0) {
				price = Math.floor(price * (0.1 + (0.4 * Math.random())));
			}
		}
	}
	return price / 10;
}


this.marketPriceFluctuations = function (good, context) {
	var basis = 1;
	for (var i = 0; i < this.pricefluxes.length; i++) {
		basis *= worldScripts[this.pricefluxes[i]].priceChange(good, context);
	}
	if (basis != 1) {
		this.debug("Fluctuation API reports " + basis + " for " + good + " in " + context);
	}
	return basis;
}


this.routeSorter = function (a, b) {
	return system.info.distanceToSystem(System.infoForSystem(galaxyNumber, a)) - system.info.distanceToSystem(System.infoForSystem(galaxyNumber, b));
}

this.distanceBonus = function (id, j) {
	//var chaos = j * 100;
	var basicavail = this.specialCargoRegister[id].buySystems[galaxyNumber];
	var avail = basicavail.slice(0);
	avail.sort(this.routeSorter);
	// check the closest pairs first to minimise recalculation
	var distance = 150;
	for (var i = 0; i < avail.length; i++) {
		var sysdist = system.info.distanceToSystem(System.infoForSystem(galaxyNumber, avail[i]));
		if (sysdist < distance) { // route calculation is really expensive
			var route = system.info.routeToSystem(System.infoForSystem(galaxyNumber, avail[i]));
			if (route && route.distance < distance) {
				distance = route.distance;
			}
		}
	}
	return Math.max(this.weeklyChaos(17 + j), this.weeklyChaos(18 + j)) * distance * this.specialCargoRegister[id].sellDistance / 100;
}

this.cargoQuantity = function (id, i, marketinfo) {
	if (this.specialCargoRegister[id].buySystems[galaxyNumber].indexOf(system.ID) != -1) { // source system
		return this.cargoQuantityExport(id, i, marketinfo);
	} else {
		return this.cargoQuantityMisc(id, i, marketinfo);
	}
}

this.genericPrice = function (generic, i) {
	var chaos = i * 100;
	var commodity = this.defaultCommodities[this.cargoTypes.indexOf(generic)];
	var price = (commodity[3] + (Math.floor(256 * this.weeklyChaos(chaos + 7)) & commodity[7]) + (system.economy * commodity[4])) & 255;
	return 10 * Math.floor(price * 0.4); // in decicredits
}
this.genericQuantity = function (generic) {
	var commodity = this.defaultCommodities[this.cargoTypes.indexOf(generic)];
	var quantity = (commodity[6] + (Math.floor(256 * this.weeklyChaos(8)) & commodity[8]) - (system.economy * commodity[5])) & 255;
	if (quantity > 127) { quantity = 0; }
	quantity &= 63;
	if (quantity <= 10) {
		quantity += 10; // adjust for systems where cargo is antieconomic in generic form
	}
	return quantity;
}

this.missionScreenOpportunity = function () {
	// on docking or startup
	// probably a little *too* frequent, but it should be a fairly fast
	// function unless you're flying an Anaconda and taking lots of small
	// packets of different sorts of special cargo
	if (this.initComplete == 0) {
		// this was originally system.mainStation, but the player might be docked at an OXP station where they saved the game
		// and that OXP station might have a local specialty market
		this._readyInterface(player.ship.dockedStation); 
		this.initComplete = 1;
	}
	this.validateHold();
}

this.guiScreenChanged = function (to, from) {
	if (to == "GUI_SCREEN_STATUS" && this.recordBounty) {
		this.savedBounty = player.bounty;
		this.recordBounty = false;
	}
	if (from == "GUI_SCREEN_MARKET" && to != "GUI_SCREEN_MAIN") {
		// in case player sold some specific cargo as generic
		// since we can't yet stop them doing that
		this.validateHold();
	}
	// replaced by interface in 1.77
	/*		if (player.ship.dockedStation) {
					// F8 F8 F8 screen for main stations.
					if (player.ship.dockedStation.isMainStation && from == "GUI_SCREEN_CONTRACTS" && to == "GUI_SCREEN_MARKET" && guiScreen == "GUI_SCREEN_MARKET") {
							this.startTrading();
							// for OXP stations, F2 F8
					} else if (player.ship.dockedStation.script.newCargoesMarket && from == "GUI_SCREEN_OPTIONS" && to == "GUI_SCREEN_MARKET" && guiScreen == "GUI_SCREEN_MARKET") {
							this.startTrading();
					}
			} */
}

this.shipWillExitWitchspace = function () {
	if (!system.isInterstellarSpace) {
		this.initialiseSystemMarket();
	}
}

this.getOXPMarket = function (station) {
	this.debug("Checking " + station.primaryRole);
	var oxpmarket = this.oxpstations[station.primaryRole];
	if (!oxpmarket) {
		for (var i = 0; i < station.roles.length; i++) {
			this.debug("No primary: checking " + station.roles[i]);
			oxpmarket = this.oxpstations[station.roles[i]];
			if (oxpmarket) {
				this.debug("Found secondary role");
				break;
			}
		}
		if (!oxpmarket) {
			this.debug("Station not supported");
			return false;
		}
	} // station not supported
	return oxpmarket;
}

this.shipWillDockWithStation = function (station) {
	this.debug("Checking station market");
	if (station.isMainStation) { return; } // not main stations
	this.debug("Is OXP station");
	if (station.script.newCargoesMarket) { return; } // already defined
	var oxpmarket = this.getOXPMarket(station);
	if (oxpmarket) {
		this.initialiseOXPStation(station, oxpmarket);
	}
	this._recordBounty = true;
}

/*
	bounty for standard illegal exports is normally set before we get to the "willLaunchFromStation" event
	thus, to fix this we will need to know the bounty beforehand (maybe guiScreenChanged to = GUI_SCREEN_STATUS after docking)
*/
this.shipWillLaunchFromStation = function (station) {
	this.today = clock.days;
	this.oreprocessorcompat = false;

	this.oldmanifest = new Array;
	for (var i = 0; i < this.cargoTypes.length; i++) {
		this.oldmanifest.push(manifest[this.cargoTypes[i]]);
	}

	if (this.checkExports(station)) {
		var penalty = this.basicLegalPenalty(); 
		var specpenalty = this.specialLegalPenalty();
		var to_apply = 0;

		// they might not always inspect the hold thoroughly
		if (penalty > (Math.random() * 30) - (system.government * 4)) {
			to_apply += specpenalty;
		} else if (specpenalty < 0) {
			// but presumably they'll be encouraged to if it's a benefit!
			to_apply += specpenalty;
		}

		this.debug("Old Bounty before launch: " + this.savedBounty);
		this.debug("Default export penalties: " + penalty);
		this.debug("Additional penalty to apply: " + to_apply + " (inc " + specpenalty + ")");
		if (to_apply > 0) {
			// apply legal status penalties
			if (player.setBounty) {
				player.setBounty(player.bounty | to_apply, "illegal exports");
			} else {
				player.bounty |= to_apply;
			}
		}
		this.debug("New Bounty: " + player.bounty);
	}
}

this.basicLegalPenalty = function () {
	// duplicates the effects of the stock illegal_goods.plist, which is
	// nullified by this OXP so that we can do more interesting things
	//return player.ship.manifest.slaves + player.ship.manifest.firearms + (2 * player.ship.manifest.narcotics);
	// what bounty was given to the player should be the difference between their current bounty and what we saved on docking
	return player.bounty - this.savedBounty;
}

this.specialLegalPenalty = function () {
	var penalty = 0;
	for (var i = 0; i < this.cargoTypes.length; i++) {
		var cblock = this.specialCargoCarried[this.cargoTypes[i]];
		for (var j = 0; j < cblock.length; j++) {
			var illegal = this.specialCargoRegister[cblock[j].type].illegal;

			for (var k = 0; k < this.permits.length; k++) {
				var permitlevel = worldScripts[this.permits[k]].checkPermit(cblock[j].type, cblock[j].quantity, false);
				this.debug("Permits for " + cblock[j].type + " = " + permitlevel);
				illegal += permitlevel;
			}

			if (illegal != 0) {
				penalty += cblock[j].quantity * illegal;
			}
		}
	}
	return penalty;
}

this.importPenalty = function () {
	var penalty = 0;
	for (var i = 0; i < this.cargoTypes.length; i++) {
		var cblock = this.specialCargoCarried[this.cargoTypes[i]];
		for (var j = 0; j < cblock.length; j++) {
			var illegal = this.specialCargoRegister[cblock[j].type].illegal;

			for (var k = 0; k < this.permits.length; k++) {
				var permitlevel = worldScripts[this.permits[k]].checkImport(cblock[j].type, cblock[j].quantity, false);
				this.debug("Imports for " + cblock[j].type + " = " + permitlevel);
				illegal += permitlevel;
			}

			if (illegal != 0) {
				penalty += cblock[j].quantity * illegal;
			}
		}
	}
	return penalty;
}

// Fixes: http://aegidian.org/bb/viewtopic.php?p=168462#p168462
this.shipScoopedOther = function (whom) {
	// don't assign special cargo flags to alloys and radioactives
	// from the ore processor
	if (worldScripts.oreProcessor && (player.ship.equipmentStatus("EQ_ORE_PROCESSOR") == "EQUIPMENT_OK" || player.ship.equipmentStatus("EQ_ORE_PROCESSOR") == "EQUIPMENT_DAMAGED")) {
		if (whom.scriptInfo && whom.scriptInfo.cargotype && (whom.scriptInfo.cargotype.toLowerCase() == "alloys" || whom.scriptInfo.cargotype.toLowerCase() == "radioactives")) {
			this.oreprocessorcompat = true;
			// or any other alloys or radioactives scooped on the same
			// trip; it's a lot of trouble to distinguish them.
		}
	}
}

this._readyInterface = function (station) {
	if (station.script.newCargoesMarket || station.isMainStation) {
		station.setInterface(worldScripts["CargoTypeExtension"].name,
			{
				title: "Speciality cargo trading",
				category: "Commerce",
				summary: "Trade in a range of speciality cargoes, view your current hold, and read the latest trade news",
				callback: worldScripts["CargoTypeExtension"].startTrading,
				cbThis: worldScripts["CargoTypeExtension"]
			}
		);
	}
}

/*
	bounty calculation normally happens after shipDockedWithStation
*/
this.shipDockedWithStation = function (station) {
	this.updateMarketCatastrophes();
	this._readyInterface(station);

	// do this before checking scooped goods. 
	if (this.checkImports(station)) {
		var specpenalty = this.importPenalty();

		// they might not always inspect the hold thoroughly
		if (specpenalty > 0 && specpenalty > (Math.random() * 30) - (system.government * 4)) {
			// apply legal status penalties
			this.debug("Illegal imports detected!");

			if (player.setBounty) {
				player.setBounty(player.bounty | specpenalty, "illegal imports");
			} else {
				player.bounty |= specpenalty;
			}
		}
	}

	// if scooped up goods
	for (var i = 0; i < this.cargoTypes.length; i++) {
		if (this.oreprocessorcompat && (i == 2 || i == 9)) {
			// if the ore processor has run, assume that
			// alloys and radioactives came from that
			continue;
		}
		var delta = manifest[this.cargoTypes[i]] - this.oldmanifest[i];
		this.debug(this.cargoTypes[i] + " delta=" + delta);
		if (delta > 0 && Math.random() < 0.15) {
			// then have a random chance that the new goods of that type are specific 
			var patience = 20;
			do {
				var good = this.specialCargoRegister[this.specialCargoList[Math.floor(Math.random() * this.specialCargoList.length)]];
				patience--;
			} while (good.genericType != this.cargoTypes[i] && patience > 0);
			if (good.genericType == this.cargoTypes[i]) {

				if (!this.hasSpecialCargo(good.ID)) {
					var cargo = new Object;
					cargo.quantity = delta;
					cargo.type = good.ID;
					cargo.origin = "scooped in " + system.info.name;
					this.specialCargoCarried[good.genericType].push(cargo);
				} else {
					for (j = 0; j < this.specialCargoCarried[good.genericType].length; j++) {
						if (this.specialCargoCarried[good.genericType][j].type == good.ID) {
							this.specialCargoCarried[good.genericType][j].quantity += delta;
						}
					}
				}
				player.consoleMessage("You scooped " + delta + this.getCommodityUnit(good.genericType) + " of " + good.specificType + " (" + this.genericName(good.genericType) + ")", 10);

			}
		}
	}
	this.oreprocessorcompat = false;
	this.validateHold(); // needed to guarantee compatibility with IGT
}

this.updateMarketCatastrophes = function () {
	var delta = clock.days - this.today;
	for (var i = 0; i < delta; i++) {
		var len = this.specialCargoList.length;
		for (var j = 0; j < len; j++) {
			var good = this.specialCargoRegister[this.specialCargoList[j]];
			// depress
			if (100 * Math.random() < good.slump) {
				this.debug("Depressing " + good.ID);
				this.depressGood(good);
				// slump takes priority over recovery for any particular good
			} else if (100 * Math.random() < good.unslump) {
				this.debug("Recovering " + good.ID);
				this.undepressGood(good.ID);
			}
		}
	}
}

this.flattenSystemList = function (syslist) {
	var flatlist = new Array;
	for (var i = 0; i < 8; i++) {
		for (var j = 0; j < syslist[i].length; j++) {
			flatlist.push((i * 256) + syslist[i][j]);
		}
	}
	return flatlist;
}

this.depressGood = function (good) {
	if (Math.random() < 0.33) {
		var flatlist = flattenSystemList(good.buySystems);
	} else {
		var flatlist = flattenSystemList(good.sellSystems);
	}
	var sysid = flatlist[Math.floor(Math.random() * flatlist.length)];
	this.depressGoodHere(good.ID, sysid);
}

this.depressGoodHere = function (goodid, sysid) {
	if (this.marketCatastrophes) {
		if (!this.marketCollapsedID(goodid, sysid)) {
			var slump = new Object;
			slump.type = goodid;
			slump.system = sysid;
			this.marketCatastrophes.push(slump);
		}
	}
}

this.undepressGood = function (good) {
	if (this.marketCatastrophes) {
		for (var i = 0; i < this.marketCatastrophes.length; i++) {
			if (this.marketCatastrophes[i].type == good) {
				this.marketCatastrophes.splice(i, 1);
				return;
			}
		}
	}
}

this.initialiseSystemMarket = function () {
	var newmarket = new Array;
	this.localbuyables = new Array;
	this.localsellables = new Array;
	var defaultmarket = this.defaultMarketInfo();
	for (var i = 0; i < this.specialCargoList.length; i++) {
		var id = this.specialCargoList[i];
		var good = this.specialCargoRegister[id];
		if (good.buySystems[galaxyNumber].indexOf(system.ID) !== -1) {
			this.localbuyables.push(id);
		}
		if (good.sellSystems[galaxyNumber].indexOf(system.ID) !== -1) {
			this.localsellables.push(id);
		}
		var marketentry = new Object;
		marketentry.type = id;
		marketentry.quantity = this.cargoQuantity(id, i, defaultmarket);
		marketentry.price = this.cargoPrice(id, i, defaultmarket, marketentry.quantity);

		if (marketentry.price < 0.1) {
			// cap minimum price
			marketentry.price = 0.1;
		}
		this.debug("Set price for " + id + " to " + marketentry.price + " with " + marketentry.quantity + this.getCommodityUnit(good.genericType) + " available");
		newmarket.push(marketentry);
	}
	this.specialCargoSystem = newmarket;
}

this.playerWillSaveGame = function (savetype) {
	this.serialiseCargoData();
}


/* 
* Init = (show manifest) buy special / sell special / exit
* Buy = buy 1 / buy 10 / buy all(ish) / next item in stock / back to init
* Sell = sell 1 / sell 10 / sell all / next item in hold / back to init
* Wanted = (view wanted items for system) next / back to init
*/

this.startTrading = function () {
	var choices = "cte_initscreenchoice";
	if (missionVariables.cargotypeextension_tradernet && missionVariables.cargotypeextension_tradernet >= clock.days) {
		choices = "cte_initscreenchoice_wtn";
	}

	mission.runScreen({
		screenID:"newcargoes_trading",
		title: "Specialist Trade Goods Market",
		message: this.showManifest(),
		allowInterrupt: true,
		overlay: "cte_containers.png",
        exitScreen: "GUI_SCREEN_INTERFACES",
		choicesKey: choices
	},
		this.initScreenChoice, this);

}

this.genericName = function (type) {
	if (type == "liquor_wines") {
		return expandDescription("[commodity-name liquor_wines]");
	} else if (type == "alien_items") {
		return expandDescription("[commodity-name alien_items]");
	} else if (type == "gem_stones") { // not used
		return expandDescription("[commodity-name gem_stones]");
	} else {
		return expandDescription("[commodity-name " + type + "]");
	}
}

this.showManifest = function () {
	var manstr = "";
	var lines = 0;
	var maxlines = 9;
	for (var i = 0; i < this.cargoTypes.length; i++) {
		var mblock = this.specialCargoCarried[this.cargoTypes[i]];
		for (var j = 0; j < mblock.length; j++) {
			var cargo = this.specialCargoRegister[mblock[j].type];
			lines++;
			if (lines <= maxlines) {
				manstr += cargo.specificType + " (" + mblock[j].quantity + this.getCommodityUnit(cargo.genericType) + ", " + this.genericName(cargo.genericType) + ")\n";
			}
		}
	}
	if (lines > maxlines) {
		manstr += "...and " + (lines - maxlines) + " more (see detailed manifest).\n";
	}
	if (manstr == "") {
		manstr = "No special cargo\n";
	}
	manstr += "\n" + this.holdStatus();
	return manstr;
}

this.initScreenChoice = function (choice) {
	if (player.ship.dockedStation.isMainStation) {
		this.stationMarket = this.specialCargoSystem;
	} else {
		this.stationMarket = player.ship.dockedStation.script.newCargoesMarket;
	}
	if (choice == "01_BUY") {
		this.startBuying();
	} else if (choice == "02_SELL") {
		this.startSelling();
	} else if (choice == "03_WANTED") {
		this.gatherInformation();
	} else if (choice == "04_TRADERS") {
		this.tradefloorpointer = 0;
		this.tradeFloor();
	} else if (choice == "05_MANIFEST") {
		this.dmanoffset = 0;
		this.detailedManifest();
	} else if (choice == "06_PERMITS") {
		this.permitListing();
	} else if (choice == "07_TRADERNET") {
		this.tradernetpointer = 1;
		this.readTraderNet();

	} else {
		// quit
	}
}

this.startBuying = function () {
	this.moveBuyPointer();
	if (this.pointer == -1) {
		this.showNoBuyScreen();
	} else {
		this.showBuyScreen();
	}
}

this.startSelling = function () {
	this.holdgoods = this.specialCargoesCarried();
	this.moveSellPointer();
	if (this.pointer == -1) {
		this.showNoSellScreen();
	} else {
		this.showSellScreen();
	}
}

this.showNoBuyScreen = function () {
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: "Buy Specialist Trade Goods",
		allowInterrupt: true,
		overlay: "cte_containers.png",
        exitScreen: "GUI_SCREEN_INTERFACES",
		message: "No specialist trade goods available for purchase"
	},
		this.startTrading, this);

}

this.showBuyScreen = function () {
	var goods = this.stationMarket[this.pointer];

	mission.runScreen({
		screenID:"newcargoes_trading",
		title: "Buy " + this.specialCargoRegister[goods.type].specificType,
		allowInterrupt: true,
		overlay: "cte_containers.png",
		message: this.buyMessage(goods),
        exitScreen: "GUI_SCREEN_INTERFACES",
		choicesKey: "cte_buyscreenchoice",
		initialChoicesKey: this.lastscreenchoice
	},
		this.handleBuyDecision, this);
}

this.holdStatus = function () {
	return "Hold: " + player.ship.cargoSpaceUsed + "/" + player.ship.cargoSpaceCapacity + "t used\nCredits: " + player.credits.toFixed(1) + " ₢";
}

this.buyMessage = function (goods) {
	var details = this.specialCargoRegister[goods.type];
	var message = "On offer: " + goods.quantity + this.getCommodityUnit(details.genericType) + " @ " + goods.price.toFixed(1) + "₢/" + this.getCommodityUnit(details.genericType) + "\n";
	message += details.specificType + " (" + this.genericName(details.genericType) + ")\n";
	message += details.desc + "\n";
	if (this.controlledGood(goods.type)) {
		message += "WARNING: This is a controlled commodity\n";
	}

	message += "Currently carried: " + this.hasSpecialCargo(goods.type) + this.getCommodityUnit(details.genericType) + "\n";

	message += "\n" + this.holdStatus();

	return message;
}

this.handleBuyDecision = function (choice) {
	this.lastscreenchoice = choice;
	if (choice == "09_EXIT") {
		this.startTrading();
	} else if (choice == "00_NEXT") {
		this.moveBuyPointer();
		if (this.pointer == -1) {
			this.showNoBuyScreen();
		} else {
			this.showBuyScreen();
		}
	} else {
		if (choice == "01_BUY1") {
			this.attemptPurchase(this.pointer, 1);
		} else if (choice == "02_BUY10") {
			this.attemptPurchase(this.pointer, 10);
		} else if (choice == "03_BUY100") {
			this.attemptPurchase(this.pointer, 100);
		}
		this.showBuyScreen();
	}
}

this.attemptPurchase = function (pointer, quantity) {
	var bought = 0;
	var fail = "";
	var goods = this.stationMarket[pointer];
	this.debug(goods);
	for (var i = 1; i <= quantity; i++) {
		if (player.credits >= goods.price) {
			if (goods.quantity > 0) {
				var attempt = this.addSpecialCargo(goods.type, goods.price.toFixed(1) + "₢ in " + system.info.name);
				if (attempt) {
					player.credits -= goods.price;
					bought++;
					goods.quantity--;
					if (Math.random() < 0.01) {
						// less likely to run out the supply by buying than to saturate by selling
						this.depressGoodHere(goods.type, system.ID);
					}
				} else {
					fail = "No room in hold";
					break;
				}
			} else {
				fail = "No goods available";
				break;
			}
		} else {
			fail = "Insufficient funds";
			break;
		}
	}
	if (bought > 0) {
		player.consoleMessage("Bought " + bought + this.getCommodityUnit(this.specialCargoRegister[goods.type].genericType), 2);
	} else {
		player.consoleMessage("Could not buy: " + fail, 3);
	}
}

this.moveBuyPointer = function () {
	if (this.pointer >= 0) {
		var last = this.pointer;
	} else {
		var last = 0;
		this.pointer = 0;
	}
	if (this.stationMarket.length > 0) {
		do {
			this.pointer++;
			if (this.pointer >= this.stationMarket.length) {
				this.pointer = 0;
			}
			if (this.stationMarket[this.pointer].quantity > 0) {
				this.debug("Pointer: " + this.pointer);
				return;
			}
		} while (this.pointer != last); // then no cargo in stock
	}
	this.debug("Pointer: -1");
	this.pointer = -1;
}

this.moveSellPointer = function () {
	if (this.pointer >= 0 && this.pointer < this.holdgoods.length) {
		var last = this.pointer;
	} else {
		var last = 0;
		this.pointer = 0;
	}
	if (this.holdgoods.length > 0) {
		do {
			this.pointer++;
			if (this.pointer >= this.holdgoods.length) {
				this.pointer = 0;
			}
			if (this.hasSpecialCargo(this.holdgoods[this.pointer]) > 0) {
				this.debug("Pointer: " + this.pointer);
				return;
			}
		} while (this.pointer != last); // then no cargo in hold
	}
	this.debug("Pointer: -1");
	this.pointer = -1;
}


this.showNoSellScreen = function () {
	mission.runScreen({
		screenID:"newcargoes_trading",
		overlay: "cte_containers.png",
		title: "Sell Specialist Trade Goods",
		allowInterrupt: true,
        exitScreen: "GUI_SCREEN_INTERFACES",
		message: "No specialist trade goods in hold"
	},
		this.startTrading, this);
}

this.showSellScreen = function () {

	var goods = this.holdgoods[this.pointer];

	mission.runScreen({
		screenID:"newcargoes_trading",
		overlay: "cte_containers.png",
		title: "Sell " + this.specialCargoRegister[goods].specificType,
		allowInterrupt: true,
		message: this.sellMessage(goods),
        exitScreen: "GUI_SCREEN_INTERFACES",
		choicesKey: "cte_sellscreenchoice",
		initialChoicesKey: this.lastscreenchoice
	},
		this.handleSellDecision, this);
}

this.marketIndex = function (market, goods) {
	for (var i = 0; i < market.length; i++) {
		if (market[i].type == goods) {
			return i;
		}
	}
	return -1;
}

this.sellMessage = function (goods) {
	var localmarket = this.stationMarket[this.marketIndex(this.stationMarket, goods)];

	var details = this.specialCargoRegister[goods];
	var message = "In hold: " + this.hasSpecialCargo(goods) + this.getCommodityUnit(details.genericType) + "\n";
	message += details.specificType + " (" + this.genericName(details.genericType) + ")\n";
	message += details.desc + "\n";
	if (details.illegal > 0) {
		message += "WARNING: This is a controlled commodity\n";
	}

	message += "Currently on market: " + localmarket.quantity + this.getCommodityUnit(details.genericType) + " @ " + localmarket.price.toFixed(1) + " ₢/" + this.getCommodityUnit(details.genericType) + "\n";

	message += "\n" + this.holdStatus();

	return message;
}

this.handleSellDecision = function (choice) {
	this.lastscreenchoice = choice;
	if (choice == "09_EXIT") {
		this.startTrading();
	} else if (choice == "00_NEXT") {
		this.moveSellPointer();
		if (this.pointer == -1) {
			this.showNoSellScreen();
		} else {
			this.showSellScreen();
		}
	} else {
		if (choice == "01_SELL1") {
			this.attemptSale(this.pointer, 1);
		} else if (choice == "02_SELL10") {
			this.attemptSale(this.pointer, 10);
		} else if (choice == "03_SELL100") {
			this.attemptSale(this.pointer, 100);
		}
		this.showSellScreen();
	}
}

this.attemptSale = function (pointer, quantity) {
	var sold = 0;
	var fail = "";
	var goods = this.holdgoods[pointer];
	this.debug(goods);
	var localmarket = this.stationMarket[this.marketIndex(this.stationMarket, goods)];
	for (var i = 1; i <= quantity; i++) {
		var attempt = this.removeSpecialCargo(goods);
		if (attempt) {
			player.credits += localmarket.price;
			sold++;
			localmarket.quantity++;
			// put too much on the market, and it'll need to recover.
			// won't affect immediate pricing
			if (Math.random() < 0.1) {
				this.depressGoodHere(localmarket.type, system.ID);
			}
		} else {
			fail = "No goods in hold";
			break;
		}
	}
	if (sold > 0) {
		player.consoleMessage("Sold " + sold + this.getCommodityUnit(this.specialCargoRegister[goods.type].genericType), 2);
	} else {
		player.consoleMessage("Could not sell: " + fail, 3);
	}
}

this.gatherInformation = function () {
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: "Local imports/exports",
		allowInterrupt: true,
		overlay: "cte_tradefloor.png",
        exitScreen: "GUI_SCREEN_INTERFACES",
		message: this.localTradeGoods()
	},
		this.gatherInformation2, this);
}

this.gatherInformation2 = function () {
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: "Gossip in the trade district",
		allowInterrupt: true,
		overlay: "cte_tradefloor.png",
        exitScreen: "GUI_SCREEN_INTERFACES",
		message: this.localGossip()
	},
		this.startTrading, this);
}

this.localTradeGoods = function () {
	var msg = "Major exports of %H\n--------------------------\n";
	var exports = systemExports(galaxyNumber, system.ID);
	if (exports.length == 0) {
		msg += "None";
	} else {
		for (var i = 0; i < exports.length; i++) {
			if (i != 0) {
				msg += ", ";
			}
			msg += this.cargoDefinition(exports[i], "specificType");
		}
	}
	msg += "\n\n";
	msg += "Major imports of %H\n--------------------------\n";
	var imports = systemImports(galaxyNumber, system.ID);
	if (imports.length == 0) {
		msg += "None";
	} else {
		for (var i = 0; i < imports.length; i++) {
			if (i != 0) {
				msg += ", ";
			}
			msg += this.cargoDefinition(imports[i], "specificType");
		}
	}

	return expandDescription(msg);
}

this.localGossip = function () {
	var l = this.specialCargoList.length;
	var goods = new Array;
	for (var i = 0; i <= 12; i++) {
		var chaos = 20 + (10 * i);
		if ((i == 0 || i == 2 || i == 4) && this.localbuyables.length > 0) {
			goods.push(this.localbuyables[Math.floor(this.localbuyables.length * this.weeklyChaos(chaos))]);
		} else if ((i == 1 || i == 3 || i == 5) && this.localsellables.length > 0) {
			goods.push(this.localsellables[Math.floor(this.localsellables.length * this.weeklyChaos(chaos))]);
			this.debug(this.localsellables.length + " " + this.weeklyChaos(chaos));
		} else {
			goods.push(this.specialCargoList[Math.floor(l * this.weeklyChaos(chaos))]);
		}
	}
	var gossip = new Array;
	for (var i = 0; i <= 12; i++) {
		var chaos = 21 + (10 * i);
		this.debug(goods[i]);
		var good = this.specialCargoRegister[goods[i]];
		if ((good.buySystems[galaxyNumber].length == 0 && good.sellSystems[galaxyNumber].length == 0) || good.sourceRumour < 0 || good.destRumour < 0) {
			// no rumour
		} else {
			//						this.debug(good.buySystems[galaxyNumber].length+" "+good.sellSystems[galaxyNumber].length);
			var sourcesys = Math.floor(this.weeklyChaos(chaos) * 256);
			if (good.sourceRumour > this.weeklyChaos(chaos + 1) * 100) {
				if (good.buySystems[galaxyNumber].length == 0) {
					sourcesys = -1;
				} else {
					//										this.debug(good.buySystems[galaxyNumber][0]);
					sourcesys = good.buySystems[galaxyNumber][Math.floor(good.buySystems[galaxyNumber].length * this.weeklyChaos(chaos + 2))];
				}
			}

			var destsys = Math.floor(this.weeklyChaos(chaos + 3) * 256);
			if (good.destinationRumour > this.weeklyChaos(chaos + 4) * 100) {
				if (good.sellSystems[galaxyNumber].length == 0) {
					destsys = -1;
				} else {
					destsys = good.sellSystems[galaxyNumber][Math.floor(good.sellSystems[galaxyNumber].length * this.weeklyChaos(chaos + 5))];
				}
			}
			//						this.debug(sourcesys+";"+destsys);

			gossip.push(expandDescription("* [cte_rumour" + Math.floor(this.weeklyChaos(chaos + 6) * 10) + "]",
				{
					cte_good: good.specificType,
					cte_source: this.tradeSystemName(sourcesys),
					cte_dest: this.tradeSystemName(destsys)
				}));
		}

	}
	if (this.marketCatastrophes.length > 0) {
		for (var i = 1; i <= 3; i++) {
			var catas = this.marketCatastrophes[Math.floor(this.marketCatastrophes.length * this.weeklyChaos(9 * i))];
			if (Math.floor(catas.system / 256) == galaxyNumber) {
				var good = this.specialCargoRegister[catas.type];
				if (good) { // might have been deleted after it collapsed
					if (good.sellSystems[galaxyNumber].indexOf(catas.system & 255) !== -1) {
						gossip.push(expandDescription("* [cte_collapserumour" + Math.floor(this.weeklyChaos(10 * i) * 6) + "]",
							{
								cte_good: good.specificType,
								cte_collapse: this.tradeSystemName(catas.system & 255)
							}));
					} else { // assume an exporter. Not strictly wrong if not ;)
						gossip.push(expandDescription("* [cte_shortagerumour" + Math.floor(this.weeklyChaos(10 * i) * 3) + "]",
							{
								cte_good: good.specificType,
								cte_collapse: this.tradeSystemName(catas.system & 255)
							}));
					}
				}
			}
		}
	}

	for (var i = 0; i < this.personalities.length; i++) {
		this.debug("Checking gossip personality " + i);
		if (worldScripts[this.personalities[i]].traderGossip) {
			var tradenews = worldScripts[this.personalities[i]].traderGossip();
			if (tradenews) {
				this.debug("Got gossip personality " + i);
				gossip.push(tradenews);
			}
		}
	}
	for (var i = 0; i < this.permits.length; i++) {
		this.debug("Checking gossip permit " + i);
		if (worldScripts[this.permits[i]].permitGossip) {
			var tradenews = worldScripts[this.permits[i]].permitGossip();
			if (tradenews) {
				this.debug("Got gossip permit " + i);
				gossip.push(tradenews);
			}
		}
	}
	for (var i = 0; i < this.pricefluxes.length; i++) {
		this.debug("Checking gossip priceflux " + i);
		if (worldScripts[this.pricefluxes[i]].priceGossip) {
			var tradenews = worldScripts[this.pricefluxes[i]].priceGossip();
			if (tradenews) {
				this.debug("Got gossip prices " + i);
				gossip.push(tradenews);
			}
		}
	}
	var stationsused = new Array;
	for (var role in this.oxpstations) {
		if (stationsused.indexOf(this.oxpstations[role]) == -1) {
			var tradenews = worldScripts[this.oxpstations[role]].stationGossip();
			if (tradenews) {
				this.debug("Got gossip station " + i);
				gossip.push(tradenews);
			}
			stationsused.push(this.oxpstations[role]);
		}
	}

	gossip.sort(function (a, b) { return Math.random() - 0.5; });
	if (gossip.length > 18) {
		gossip.length = 18; // truncate array
	}

	return gossip.join("\n");
}

this.tradeSystemName = function (sysid) {
	if (sysid >= 0 && sysid <= 255) {
		return System.systemNameForID(sysid);
	} else {
		return "another galaxy";
	}
}

this.tradeFloor = function () {
	var choice = "cte_tradefloorchoice";
	this.debug("Building trade floor");
	var traders = new Array;
	var srole = "";
	if (player.ship.dockedStation != system.mainStation) {
		var srole = player.ship.dockedStation.primaryRole;
	}
	for (var i = 0; i < this.personalities.length; i++) {
		if (worldScripts[this.personalities[i]].traderHere && worldScripts[this.personalities[i]].traderHere(srole)) {
			traders.push(this.personalities[i]);
			this.debug(this.personalities[i] + " is present");
		}
	}
	if (traders.length == 0) {
		this.tradeflooractive = 0;
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: "No-one about",
			allowInterrupt: true,
			overlay: "cte_tradefloor.png",
			exitScreen: "GUI_SCREEN_INTERFACES",
			message: "The trade floor is deserted."
		},
			this.startTrading, this
		);
	} else {
		if (traders.length == 1) {
			choice = "cte_tradeflooronlychoice";
		}
		this.tradeFloorEncounter(traders[this.tradefloorpointer % traders.length], choice);
	}

}

this.tradeFloorEncounter = function (traderscript, choice) {
	var title = worldScripts[traderscript].traderName();
	var desc = worldScripts[traderscript].traderDesc();

	this.traderscript = traderscript;
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: title,
		allowInterrupt: true,
		overlay: "cte_tradefloor.png",
        exitScreen: "GUI_SCREEN_INTERFACES",
		message: desc,
		choicesKey: choice
	}, this.tradeFloorChoice, this);

}

this.tradeFloorChoice = function (choice) {

	if (choice == "09_EXIT") {
		this.tradeflooractive = 0;
		this.startTrading();
	} else if (choice == "02_TALK") {
		worldScripts[this.traderscript].runOffer();
		this.tradefloorpointer++;
	} else {
		this.tradefloorpointer++;
		this.tradeFloor();
	}
}

this.detailedManifest = function () {
	var manlines = this.detailedManifestLines();
	var maxlines = 15;
	if (manlines.length <= maxlines) {
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: "Detailed Manifest",
			allowInterrupt: true,
			overlay: "cte_containers.png",
			exitScreen: "GUI_SCREEN_INTERFACES",
			message: this.viewDetailedManifest(manlines, 0, maxlines),
			choicesKey: "cte_manifestlast"
		},
			this.startTrading, this);
	} else {
		var pages = 1 + Math.floor((manlines.length - 1) / maxlines);
		var cpage = 1 + Math.floor((this.dmanoffset) / maxlines);
		var fn = this.startTrading;
		var choice = "cte_manifestlast";
		if (this.dmanoffset + maxlines < manlines.length) {
			fn = this.detailedManifest;
			var choice = "cte_manifestnext";
		}
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: "Detailed Manifest (" + cpage + "/" + pages + ")",
			allowInterrupt: true,
			overlay: "cte_containers.png",
			exitScreen: "GUI_SCREEN_INTERFACES",
			message: this.viewDetailedManifest(manlines, this.dmanoffset, maxlines),
			choicesKey: choice
		},
			fn, this);

		this.dmanoffset += maxlines;
	}
}

this.detailedManifestLines = function () {
	var manlines = new Array;
	for (var i = 0; i < this.cargoTypes.length; i++) {
		var mblock = this.specialCargoCarried[this.cargoTypes[i]];
		for (var j = 0; j < mblock.length; j++) {
			var cargo = this.specialCargoRegister[mblock[j].type];
			var manstr = mblock[j].quantity + this.getCommodityUnit(cargo.genericType) + " " + cargo.specificType + ", " + mblock[j].origin;
			var market = this.stationMarket[this.marketIndex(this.stationMarket, mblock[j].type)];
			manstr += ", local market: " + market.quantity + this.getCommodityUnit(cargo.genericType) + " @ " + market.price.toFixed(1) + "₢/" + this.getCommodityUnit(cargo.genericType);
			manlines.push(manstr);
		}
	}
	return manlines;
}

this.viewDetailedManifest = function (manlines, offset, maxlines) {
	var manstr = "";
	if (manlines.length == 0) {
		manstr = "No interesting cargo in hold.";
	} else {
		var manslice = manlines.slice(offset, offset + maxlines - 1);
		manstr = manslice.join("\n");
	}
	return manstr;
}

this.permitListing = function () {
	var msg = "";
	for (var k = 0; k < this.permits.length; k++) {
		this.debug("Trying " + this.permits[k]);
		msg += worldScripts[this.permits[k]].describePermits();
	}
	for (var k = 0; k < this.personalities.length; k++) {
		this.debug("Trying " + this.personalities[k]);
		msg += worldScripts[this.personalities[k]].describeContracts();
	}

	if (msg == "") {
		msg = "You have no permits or active contracts, and no local regulations apply to trading.";
	}
	mission.runScreen({
		screenID:"newcargoes_permits",
		title: "Permits, Contracts and Regulations",
		allowInterrupt: true,
        exitScreen: "GUI_SCREEN_INTERFACES",
		overlay: "cte_permit.png",
		message: msg
	},
		this.startTrading, this);

}

this.defaultMarketInfo = function () {
	return worldScripts["CargoTypeExtension-DefaultMarket"];
}

this.initialiseOXPStation = function (station, marketinfokey) {
	if (station.script.newCargoesMarket) {
		return;
	}
	var marketinfo = worldScripts[marketinfokey];
	var randomiser = Math.floor(Math.random() * 1000);
	var newmarket = new Array;
	for (var i = 0; i < this.specialCargoList.length; i++) {
		var j = i + randomiser;
		var id = this.specialCargoList[i];
		var good = this.specialCargoRegister[id];
		if (good.forbidExtension) { // OXP stations won't trade in these
			var marketentry = new Object;
			marketentry.type = id;
			marketentry.quantity = 0
			marketentry.price = 0.1
		} else {
			var marketentry = new Object;
			marketentry.type = id;
			marketentry.quantity = this.cargoQuantity(id, j, marketinfo);
			marketentry.price = this.cargoPrice(id, j, marketinfo, marketentry.quantity);
			if (marketentry.price < 0.1) {
				// cap minimum price
				marketentry.price = 0.1;
			}
		}

		this.debug("Set price for " + id + " to " + marketentry.price + " with " + marketentry.quantity + this.getCommodityUnit(good.genericType) + " available");
		newmarket.push(marketentry);
	}

	station.script.newCargoesMarket = newmarket;
}

this.readTraderNet = function () {
	var background = worldScripts["CargoTypeExtension-TraderNet"].getPic();
	var messages = worldScripts["CargoTypeExtension-TraderNet"].numMessages();
	if (messages == 0) {
		mission.runScreen({
			screenID:"newcargoes_news",
			title: "TraderNet News",
			allowInterrupt: true,
			overlay: background,
			exitScreen: "GUI_SCREEN_INTERFACES",
			message: "\n\n\n\n\n\n\n\nNo news available.",
			choicesKey: "cte_tradernet_last"
		},
			this.startTrading, this);
	} else {
		if (this.tradernetpointer >= messages) {
			var article = worldScripts["CargoTypeExtension-TraderNet"].getMessage(messages);
			var ckey = "cte_tradernet_last";
		} else {
			var ckey = "cte_tradernet";
			var article = worldScripts["CargoTypeExtension-TraderNet"].getMessage(this.tradernetpointer++);
		}
		mission.runScreen({
			screenID:"newcargoes_news",
			title: "TraderNet News",
			allowInterrupt: true,
			overlay: background,
			exitScreen: "GUI_SCREEN_INTERFACES",
			message: "\n\n\n\n\n\n\n\n" + article,
			choicesKey: ckey
		},
			function (choice) { if (choice == "01_NEXT") { this.readTraderNet(); } else { this.startTrading(); } }, this);

	}
}

this.getCommodityUnit = function(generic) {
	if (system.isInterstellarSpace) {
		var m = player.ship.manifest;
		if (m[generic] > 0) {
			for (var i = 0; i < m.list.length; i++) {
				if (m.list[i].commodity == generic) return m.list[i].unit;
			}
		} else {
			return "t";
		}
	}
	var types = ["t", "kg", "g"];
	return types[system.mainStation.market[generic].quantity_unit];
}
Scripts/cargotypefetch.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-FetchContracts";
this.description = "Contracts to bring a buyer some goods";

// var cte_fetch = this;

this.startUp = function () {
	worldScripts["CargoTypeExtension"].registerPersonality(this.name);
	this.unserialiseFetchData();
}

this.unserialiseFetchData = function () {
	if (!missionVariables.cargotypeextension_fetchcontract) {
		this.timer = new Timer(this, this.initialiseFetchData, 0.25);
	} else {
		var fetchdata = missionVariables.cargotypeextension_fetchcontract.split("|");
		var version = fetchdata.shift();
		if (version == "1") {
			this.unserialiseFetchData1(fetchdata);
		} else {
			log(this.name, "Error: " + svars[0] + " is not a recognised fetch format");
			player.consoleMessage("Critical error decoding special cargo data. Please see Latest.log");
		}
	}
}

this.initialiseFetchData = function () {
	this.fetchContracts = new Array; // contracts agreed by player
	this.localOffer = this.newContract();
}

this.playerWillSaveGame = function (cause) {
	this.serialiseFetchData();
}

this.serialiseFetchData = function () {
	var fetchdata = "1|";
	var contracts = new Array;
	for (var i = 0; i < this.fetchContracts.length; i++) {
		if (this.fetchContracts[i].amount > 0) {
			contracts.push(this.serialiseContract(this.fetchContracts[i]));
		}
	}
	fetchdata += contracts.join(";");
	fetchdata += "|";
	fetchdata += this.serialiseContract(this.localOffer);
	missionVariables.cargotypeextension_fetchcontract = fetchdata;
}

this.serialiseContract = function (contract) {
	if (contract) {
		return contract.galaxy + "/" + contract.system + "/" + contract.good + "/" + contract.amount + "/" + contract.price + "/" + contract.deadline + "/" + contract.name;
	} else {
		return "";
	}
}

this.unserialiseFetchData1 = function (fetchdata) {
	var actives = fetchdata[0].split(";");
	this.fetchContracts = new Array;
	for (var i = 0; i < actives.length; i++) {
		if (actives[i] != "") {
			this.fetchContracts.push(this.unserialiseContract1(actives[i]));
		}
	}
	this.localOffer = this.unserialiseContract1(fetchdata[1]);
}

this.unserialiseContract1 = function (cstr) {
	if (cstr == "") { return false; }
	var cdata = cstr.split("/");
	var contract = {
		galaxy: cdata[0],
		system: cdata[1],
		good: cdata[2],
		amount: cdata[3],
		price: cdata[4],
		deadline: cdata[5],
		name: cdata[6]
	};
	return contract;
}

this.newContract = function () {
	var imports = worldScripts["CargoTypeExtension"].systemImports(galaxyNumber, system.ID);
	if (Math.random() - 0.1 < imports.length / 3) {
		worldScripts["CargoTypeExtension"].debug("No local fetch: too many imports");
		return false; // bias away from systems which have imports anyway
	}
	var good = worldScripts["CargoTypeExtension"].extendableCargo("any");
	var exports = worldScripts["CargoTypeExtension"].systemImports(galaxyNumber, system.ID);
	if (exports.indexOf(good) >= 0 || imports.indexOf(good) >= 0) {
		worldScripts["CargoTypeExtension"].debug("No local fetch: picked wrong cargo");
		return false; // not a good the system usually trades in
	}

	var contract = new Object;
	contract.galaxy = galaxyNumber;
	contract.system = system.ID;
	contract.name = randomName() + " " + randomName();
	contract.deadline = 15 + (5 * Math.floor(Math.random() * 7)) + clock.days;
	if (Math.random() < 0.5) {
		contract.amount = 10 + (5 * Math.floor(Math.random() * 5));
	} else {
		contract.amount = 10 + (10 * Math.floor(Math.random() * 20));
	}

	contract.good = good;
	contract.price = Math.floor(worldScripts["CargoTypeExtension"].cargoPriceImport(
		good,
		Math.floor(Math.random() * 1000),
		worldScripts["CargoTypeExtension"].defaultMarketInfo()) / 8); // /8 rather than /10 for a slight premium
	worldScripts["CargoTypeExtension"].debug("Local fetch: " + contract.good + " by " + contract.deadline + " at " + contract.price);
	return contract;
}


this.shipWillExitWitchspace = function () {
	if (!system.isInterstellarSpace) {
		this.updateFetchContracts();
	}
}

this.updateFetchContracts = function () {
	var addlocal = true;
	for (var i = this.fetchContracts.length - 1; i >= 0; i--) {
		var contract = this.fetchContracts[i];
		if (contract.amount <= 0) {
			this.fetchContracts.splice(i, 1); // cleanup
		} else {
			if (contract.deadline < clock.days) {
				// too late!
				var penalty = contract.amount * contract.price;
				player.commsMessage(contract.name + ": You've failed to deliver the cargo promised in time. According to the automatic penalty clauses on our contract, " + penalty + "Cr. are now being transferred from your accounts.", 10);
				if (penalty > player.credits) {
					player.commsMessage(contract.name + ": What? Trying to avoid it by emptying that account first? Please refer to section 6.5: Legal status penalties', Commander!");
					player.bounty |= 25;
				}
				player.credits -= penalty;
				this.fetchContracts.splice(i, 1);
			} else if (contract.galaxy == galaxyNumber && contract.system == system.ID) {
				addlocal = false;
			}
		}
	}
	if (addlocal) {
		this.localOffer = this.newContract();
	} else {
		this.localOffer = false;
	}
}

this.currentOffer = function () {
	worldScripts["CargoTypeExtension"].debug(this.fetchContracts.length);
	for (var i = 0; i < this.fetchContracts.length; i++) {
		var contract = this.fetchContracts[i];
		worldScripts["CargoTypeExtension"].debug(this.serialiseContract(contract));
		if (contract.galaxy == galaxyNumber && contract.system == system.ID) {
			worldScripts["CargoTypeExtension"].debug("System match");
			if (contract.amount > 0) {
				worldScripts["CargoTypeExtension"].debug("Amount match");
				return contract;
			} else {
				worldScripts["CargoTypeExtension"].debug("Amount zero");
				return this.localOffer;
			}
		}
	}

	return this.localOffer;
}

/* Personality API */

this.traderGossip = function () {
	var sysid = clock.days % 256;
	if (worldScripts["CargoTypeExtension"].systemImports(galaxyNumber, sysid) < 2) {
		return "* I hear you can get some interesting deals at " + System.systemNameForID(sysid) + ".";
	}
	return false;
}

this.describeContracts = function () {
	if (this.fetchContracts.length == 0) {
		return "";
	}
	var cte = worldScripts["CargoTypeExtension"];
	var descs = new Array();
	for (var i = 0; i < this.fetchContracts.length; i++) {
		var contract = this.fetchContracts[i];
		var desc = "Get " + contract.amount + cte.getCommodityUnit(cte.cargoDefinition(contract.good, "genericType")) + " of " + cte.cargoDefinition(contract.good, "specificType") + " to ";
		if (contract.galaxy == galaxyNumber) {
			desc += System.infoForSystem(galaxyNumber, contract.system).name;
		} else {
			desc += "Chart " + (1 + contract.galaxy);
		}
		desc += " by " + contract.deadline + ".";
		descs.push(desc);
	}
	return descs.join("\n") + "\n";
}

this.traderHere = function (srole) {
	if (srole != "") { // main stations only for now
		return false;
	}
	return this.currentOffer() != false;
}

this.traderName = function () {
	var cte = worldScripts["CargoTypeExtension"];
	cte.debug(this.name + " " + this.currentOffer());
	return this.currentOffer().name + ", procurer";
}

this.traderDesc = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var offer = this.currentOffer();
	if (this.localOffer) {
		return "Hello, Trader. I'm looking for someone who can supply me with some " + cte.cargoDefinition(offer.good, "specificType") + ". If you know a suitable supplier and are willing to transport the goods I can offer an excellent price - come over and talk terms.";
	} else {
		return "Hello again, Trader. Have you got my " + cte.cargoDefinition(offer.good, "specificType") + " yet?";
	}
}

this.runOffer = function () {
	var offer = this.currentOffer();
	if (this.localOffer) {
		this.runNewOffer();
	} else {
		this.runOldOffer();
	}
}

this.runNewOffer = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var offer = this.currentOffer();
	var unit = cte.getCommodityUnit(cte.cargoDefinition(offer.good, "genericType"));
	var msg = "This is a standard procurement contract - you bring me " + offer.amount + unit + " of " + cte.cargoDefinition(offer.good, "specificType") + " by " + offer.deadline + ", at a rate of " + offer.price + "₢/" + unit + ".\nYou can bring them in more than one load, if you want, and how you obtain the goods is up to you.\nIf you fail to fulfil the contract, there will be a penalty charge of " + offer.price + "₢ for every " + unit + " you are short, to cover my risk of being short on what I need, so it's in your interests to have a good idea of how you're going to deliver the goods.\n";

	mission.runScreen({
		screenID:"newcargoes_trading",
		title: this.traderName(),
		message: msg,
		overlay: "cte_tradefloor.png",
		choicesKey: "cte_fetch_deal"
	}, this.newOfferChoice, this);
}

this.newOfferChoice = function (choice) {
	if (choice == "01_ACCEPT") {
		this.fetchContracts.push(this.localOffer);
		this.localOffer = false;
		var msg = "Thank you, Trader " + player.name + ". I look forward to you returning with the goods.";
	} else {
		var msg = "If you change your mind after a look at your charts, then I'll be around here for a little bit longer.";
	}

	mission.runScreen({
		screenID:"newcargoes_trading",
		title: this.traderName(),
		message: msg,
		overlay: "cte_tradefloor.png"
	}, this.leaveFetch, this);
}

this.leaveFetch = function () {
	worldScripts["CargoTypeExtension"].tradeFloor();
}


this.runOldOffer = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var offer = this.currentOffer();

	var carried = cte.hasSpecialCargo(offer.good);
	var unit = cte.getCommodityUnit(cte.cargoDefinition(offer.good, "genericType"));

	if (carried == 0) {
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			message: "You don't seem to have any " + cte.cargoDefinition(offer.good, "specificType") + " in your hold right now, Trader. Remember, I still need you to deliver " + offer.amount + unit + " by " + offer.deadline + ".",
			overlay: "cte_tradefloor.png"
		}, this.leaveFetch, this);
	} else {
		var msg = "Welcome back, Trader " + player.name + ". Your manifest indicates that you have " + carried + unit + " of " + cte.cargoDefinition(offer.good, "specificType") + " in your hold. Would you like to transfer them now, for the agreed price of " + offer.price + "₢ up to the " + offer.amount + unit + " remaining on this contract?";
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			message: msg,
			overlay: "cte_tradefloor.png",
			choicesKey: "cte_fetch_transfer"
		}, this.oldOfferChoice, this);
	}
}

this.oldOfferChoice = function (choice) {
	var cte = worldScripts["CargoTypeExtension"];
	if (choice != "01_ACCEPT") {
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			message: "Don't forget to drop them off here before you leave, then!",
			overlay: "cte_tradefloor.png"
		}, this.leaveFetch, this);
		return;
	}

	var offer = this.currentOffer();
	var transferred = 0;
	while (offer.amount > 0 && cte.hasSpecialCargo(offer.good) > 0) {
		if (cte.removeSpecialCargo(offer.good)) {
			transferred++;
			offer.amount--;
		} else {
			cte.error("Unable to transfer expected good in fetch contract?!");
			player.consoleMessage("Warning: Unexpected error in NewCargoes OXP. Please see Latest.Log");
			return;
		}
	}

	var unit = cte.getCommodityUnit(cte.cargoDefinition(offer.good, "genericType"));
	var price = offer.price * transferred;
	var msg = "Thank you. " + transferred + unit + " has been transferred, and " + price + "₢ has been credited to your account.\n";
	player.credits += price;

	if (offer.amount > 0) {
		msg += offer.amount + unit + " remain to be delivered by " + offer.deadline + ", Trader - I hope to see you back soon with more.";
	} else {
		msg += "Thank you, Trader. This is all of the goods we agreed. I hope to see you again soon.";
	}
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: this.traderName(),
		message: msg,
		overlay: "cte_tradefloor.png"
	}, this.leaveFetch, this);
}
Scripts/cargotypeopencontract.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-OpenContract";
this.description = "'Open' cargo contracts; high risk, high reward";

// var this = this;

this.startUp = function () {
	worldScripts["CargoTypeExtension"].registerPersonality(this.name);

	if (missionVariables.cargotypeextension_opencontract) {
		this.unserialiseOpenContract();
	} else {
		this.initialiseOpenContract();
	}
}

this.playerWillSaveGame = function (savetype) {
	this.serialiseOpenContract();
}

this.initialiseOpenContract = function () {
	this.contractdata = undefined;
	this.newOpenContract();
}

this.unserialiseOpenContract = function () {
	worldScripts["CargoTypeExtension"].debug(missionVariables.cargotypeextension_opencontract);
	var state = missionVariables.cargotypeextension_opencontract.toString().split("|");
	var format = state.shift();

	if (format == "1") {
		this.unserialiseOpenContract1(state);
	} else {
		log(this.name, "Error: " + svars[0] + " is not a recognised open contract format");
		player.consoleMessage("Critical error decoding special cargo data. Please see Latest.log");
	}
}

this.unserialiseOpenContract1 = function (state) {
	if (state.length == 0) {
		this.contractdata = undefined;
	} else {
		worldScripts["CargoTypeExtension"].debug(state.length + " " + state.join(" @ "));
		this.contractdata = new Object;
		this.contractdata.active = state[0];
		this.contractdata.source = state[1];
		this.contractdata.dest = state[2];
		this.contractdata.buyer = state[3];
		this.contractdata.seller = state[4];
		this.contractdata.deadline = state[5];
		this.contractdata.cargogeneric = state[6];
		this.contractdata.cargospecific = state[7];
		this.contractdata.galaxy = state[8];
		this.contractdata.amount = state[9];

		this.redefine();
	}
}

this.serialiseOpenContract = function () {
	if (!this.contractdata) {
		missionVariables.cargotypeextension_opencontract = "1";
	} else {
		missionVariables.cargotypeextension_opencontract = "1|" + this.contractdata.active + "|" + this.contractdata.source + "|" + this.contractdata.dest + "|" + this.contractdata.buyer + "|" + this.contractdata.seller + "|" + this.contractdata.deadline + "|" + this.contractdata.cargogeneric + "|" + this.contractdata.cargospecific + "|" + this.contractdata.galaxy + "|" + this.contractdata.amount;
	}
}

this.shipWillExitWitchspace = function () {
	if (!system.isInterstellarSpace) {
		this.newOpenContract();
	}
}

this.newOpenContract = function () {
	// never redefine contract if cargo from an old contract is aboard.
	// Fixes: http://aegidian.org/bb/viewtopic.php?p=168498#p168498
	if (worldScripts["CargoTypeExtension"].hasSpecialCargo("CTE_CTOC_1") > 0) {
		worldScripts["CargoTypeExtension"].debug("...and Cargo carried");
		return;
	}

	if (this.contractdata) { // always define a new one if it's undefined
		if (this.contractdata.active > 0) {
			worldScripts["CargoTypeExtension"].debug("Open Contract Active");
		}
		if (Math.random() > 0.75) { // usually have one available
			worldScripts["CargoTypeExtension"].debug("Skipping open contract");
			return;
		}
	} else {
		worldScripts["CargoTypeExtension"].debug("Open contract undefined");
	}

	var contract = new Object;
	var names = expandDescription("[nom1];[nom1]").split(";");
	contract.buyer = randomName() + " " + names[0];
	contract.seller = randomName() + " " + names[1];
	contract.active = 0;
	contract.galaxy = galaxyNumber;
	contract.source = system.ID;

	var possibledests = SystemInfo.filteredSystems(this, this.preferredSystems);
	if (possibledests.length < 1) {
		var possibledests = SystemInfo.filteredSystems(this, this.eligibleSystems);
	}
	if (possibledests.length < 1) {
		worldScripts["CargoTypeExtension"].debug("No valid open contract destinations");
		return;
	}
	var chosendest = possibledests[Math.floor(Math.random() * possibledests.length)];
	contract.dest = chosendest.systemID;
	worldScripts["CargoTypeExtension"].debug("Chose " + contract.dest + " as destination");
	var simpleroute = system.info.routeToSystem(chosendest);
	if (!simpleroute) {
		return; // either we're in a system too isolated to have the contracts, or the other system is an isolate.
	}
	var quickroute = system.info.routeToSystem(chosendest, "OPTIMIZED_BY_TIME");

	worldScripts["CargoTypeExtension"].debug("Quick: " + quickroute.time.toFixed(1) + " (+" + quickroute.route.length + ") / Simple: " + simpleroute.time.toFixed(1) + " (+" + simpleroute.route.length + ")");
	// allow an hour in each system for in-system transit, docking, servicing, etc.
	simpleroute.time += simpleroute.route.length;
	quickroute.time += quickroute.route.length;

	if (simpleroute.time < quickroute.time + 50) {
		worldScripts["CargoTypeExtension"].debug("Insufficient contrast");
		return; // need some contrast.
	}

	var reallength = Math.floor(Math.random() * (simpleroute.time - quickroute.time) * 3600) + (quickroute.time * 3600);
	// the winner - if the player does nothing - will take a route with
	// time between optimal and simplest. Quite possibly the ones trying
	// the optimal route did one Anarchy too many and got blown up or
	// stuck in drydock for repairs for weeks. Or it might be one on the
	// optimal route who succeeds.

	contract.deadline = Math.floor(reallength + clock.adjustedSeconds);
	// yes, this is to the second.

	var generics = ["food", "textiles", "radioactives", "slaves", "liquor_wines", "luxuries", "narcotics", "computers", "machinery", "alloys", "firearms", "furs", "minerals"];

	contract.cargogeneric = generics[Math.floor(Math.random() * generics.length)];
	contract.cargospecific = expandDescription("[cte_ctd_" + contract.cargogeneric + "]");

	var amounts = [10, 20, 25, 25, 25, 40, 50, 50, 60, 75, 100, 125];
	contract.amount = amounts[Math.floor(Math.random() * amounts.length)];

	worldScripts["CargoTypeExtension"].debug("Open Contract Initialising");
	this.contractdata = contract;
	this.redefine();

}

this.eligibleSystems = function (other) {
	if (other.systemID == system.ID) { return false; }
	if (system.info.distanceToSystem(other) < 40) {

		return false;
	}
	return true;
}
this.preferredSystems = function (other) {
	if (other.systemID == system.ID) { return false; }
	if (system.info.distanceToSystem(other) < 60) {
		return false;
	}
	return true;
}

this.redefine = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var cargo = new Object;
	cargo.ID = "CTE_CTOC_1";
	cargo.buySystems = [[], [], [], [], [], [], [], []];
	cargo.buySystems[this.contractdata.galaxy].push(this.contractdata.source);
	cargo.sellSystems = [[], [], [], [], [], [], [], []];
	cargo.slump = -1;
	cargo.unslump = -1;
	cargo.sourceRumour = -1;
	cargo.destinationRumour = -1;
	cargo.salvageMarket = -1;
	cargo.forbidExtension = 1;
	cargo.buyQuantity = -1; // not available on normal export market
	if (this.contractdata.cargogeneric && (this.contractdata.cargogeneric == "slaves" || this.contractdata.cargogeneric == "narcotics" || this.contractdata.cargogeneric == "firearms")) {
		cargo.buyAdjust = 200;
		cargo.sellAdjust = 600;
		cargo.sellDistance = 45;
		var verb = "smuggle";
	} else {
		cargo.buyAdjust = 200;
		cargo.sellAdjust = 500;
		cargo.sellDistance = 35;
		var verb = "transport";
	}
	cargo.genericType = this.contractdata.cargogeneric;
	cargo.specificType = this.contractdata.seller + "'s " + this.contractdata.cargospecific;

	if (this.contractdata.galaxy == galaxyNumber) {
		var destname = "at " + System.infoForSystem(galaxyNumber, this.contractdata.dest).name + " station";
	} else {
		var destname = "in Chart " + (1 + this.contractdata.galaxy);
	}

	cargo.desc = "You accepted a contract to " + verb + " " + this.contractdata.amount + cte.getCommodityUnit(cargo.genericType) + " of this cargo to " + this.contractdata.buyer + " " + destname + " as soon as possible. Rival traders have also accepted this contract, and only the first to arrive will be paid.";

	cte.registerCargoType(cargo);

}

this.missionScreenOpportunity = function () {
	if (this.contractdata && galaxyNumber == this.contractdata.galaxy && system.ID == this.contractdata.dest && this.contractdata.active == 1 && player.ship.dockedStation.isMainStation) {
		if (clock.adjustedSeconds <= this.contractdata.deadline) {
			this.openContractWin();
		} else {
			this.openContractLose();
		}
	}
}

/* API calls */

this.traderGossip = function () {
	return false; // not needed
}

this.describeContracts = function () {
	var cte = worldScripts["CargoTypeExtension"];
	if (!this.contractdata || this.contractdata.active == 0) {
		return "";
	} else {
		var desc = "Transfer " + this.contractdata.amount + cte.getCommodityUnit(this.contractdata.cargogeneric) + " of " + this.contractdata.cargospecific + " to ";
		if (this.contractdata.galaxy == galaxyNumber) {
			desc += System.infoForSystem(galaxyNumber, this.contractdata.dest).name;
		} else {
			desc += "Chart " + (1 + this.contractdata.galaxy);
		}
		desc += " quickly.\n";
		return desc;
	}
}


this.traderHere = function (srole) {
	if (srole != "") { // main stations only for now
		return false;
	}
	if (!this.contractdata) { return false; }

	return (system.ID == this.contractdata.source && this.contractdata.active == 0);
	// destination systems are handled by event handlers above
}

this.traderName = function () {
	if (system.ID == this.contractdata.source) {
		return this.contractdata.seller + ", contractor";
	}
}

this.traderDesc = function () {
	var cte = worldScripts["CargoTypeExtension"];
	return "Trader " + player.name + ", welcome. I have an open contract offer to carry " + this.contractdata.amount + cte.getCommodityUnit(this.contractdata.generictype) + "  of " + this.contractdata.cargospecific + " to a buyer on " + System.infoForSystem(galaxyNumber, this.contractdata.dest).name + ". Excellent profits for a trader with a fast ship.";
}

this.runOffer = function () {
	var cte = worldScripts["CargoTypeExtension"];

	var price = cte.cargoPriceExport("CTE_CTOC_1", 15, cte.defaultMarketInfo()) / 10;
	var totalprice = price * this.contractdata.amount;

	if (player.ship.cargoSpaceCapacity < this.contractdata.amount) {
		var msg = "Trader, I appreciate your enthusiasm, but unfortunately my buyer isn't interested in partial deliveries. Come back and see me when you've got a proper freighter.";

		if (player.ship.equipmentStatus("EQ_HYPERCARGO") === "EQUIPMENT_OK" || player.ship.equipmentStatus("EQ_MULTIBAY") === "EQUIPMENT_OK") {
			msg = "Unfortunately, Trader, my buyer is somewhat old-fashioned, and doesn't approve of all these new hyperspatial storage arrangements. I'm afraid I can only offer this contract to people with a sufficiently large primary cargo bay.";
		}
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			overlay: "cte_tradefloor.png",
			message: msg
		}, this.leaveOpenC, this);

	} else if (player.ship.cargoSpaceAvailable < this.contractdata.amount) {
		var msg = "I'm sorry, Trader. You'll need to free up some hold space before we can discuss this contract.";

		if (player.ship.equipmentStatus("EQ_HYPERCARGO") === "EQUIPMENT_OK" || player.ship.equipmentStatus("EQ_MULTIBAY") === "EQUIPMENT_OK") {
			msg = "Unfortunately, Trader, my buyer is somewhat old-fashioned, and doesn't approve of all these new hyperspatial storage arrangements. You'll have to reorganise your cargo to get enough space in the main hold area for the goods before I can transfer them.";
		}

		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			overlay: "cte_tradefloor.png",
			message: msg
		}, this.leaveOpenC, this);
	} else if (totalprice > player.credits) {
		var msg = "I'm sorry, Trader. You don't have enough credits to afford the security deposit. Come back some day when you're a little richer.";
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			overlay: "cte_tradefloor.png",
			message: msg
		}, this.leaveOpenC, this);

	} else {
		var unit = cte.getCommodityUnit(this.contractdata.generictype);
		var msg = "Here's the deal, " + player.name + ". I'll sell you " + this.contractdata.amount + " of " + this.contractdata.cargospecific + " for " + price.toFixed(1) + "₢/" + unit + " (" + totalprice + "₢) security deposit. When you get to " + System.infoForSystem(galaxyNumber, this.contractdata.dest).name + " station, " + this.contractdata.buyer + " will pay you over ten times that for delivery. I can't tell you the exact price, unfortunately, because that obsolete Galcop regulation is still being enforced. They'll then pay me the rest of the goods cost minus your delivery fee and the security deposit. Everyone's a winner.\n\nWhat's that? What's the catch? Well, you're not the only one taking this contract. " + this.contractdata.buyer + " is only going to pay the first one there, so you'll need to be fast. You're one of the first to show up, so your odds are pretty good.";

		if (player.ship.equipmentStatus("EQ_HYPERCARGO") === "EQUIPMENT_OK" || player.ship.equipmentStatus("EQ_MULTIBAY") === "EQUIPMENT_OK") {
			msg = "\n\nOh, by the way, Trader, my buyer is somewhat old-fashioned, and doesn't approve of all these new hyperspatial storage arrangements. For auditing purposes, you should keep the cargo in your main hold throughout the trip.";
		}


		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			overlay: "cte_tradefloor.png",
			message: msg,
			choicesKey: "cte_oc_deal"
		}, this.dealOpenC, this);

	}

}

this.dealOpenC = function (choice) {
	if (choice == "01_DEAL") {
		var price = worldScripts["CargoTypeExtension"].cargoPriceExport("CTE_CTOC_1", 15, worldScripts["CargoTypeExtension"].defaultMarketInfo()) / 10;
		for (var i = 1; i <= this.contractdata.amount; i++) {
			worldScripts["CargoTypeExtension"].addSpecialCargo("CTE_CTOC_1", price.toFixed(1) + "₢ for open contract in " + system.info.name);
		}
		worldScripts["CargoTypeExtension"].debug(price + " " + this.contractdata.amount);
		player.credits = player.credits - (price * this.contractdata.amount);
		this.contractdata.active = 1;

		var msg = "Thank you, Trader. The cargo is being loaded on to your ship as we speak. I recommend you launch as soon as possible.";
		if (player.ship.equipmentStatus("EQ_HYPERCARGO") === "EQUIPMENT_OK" || player.ship.equipmentStatus("EQ_MULTIBAY") === "EQUIPMENT_OK") {
			msg += "\n\nRemember that the buyer doesn't approve of hyperspatial cargo storage. You can carry the cargo there in flight at your own risk if you must, but make sure that all the goods are in your primary cargo hold when you dock at the destination station, or they probably won't take them.";
		}

	} else {
		var msg = "Understood, Trader. Now, if you'll excuse me, I have a few other potential couriers to talk to. I'll still be here for a little while if you change your mind.";
	}
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: this.traderName(),
		overlay: "cte_tradefloor.png",
		message: msg
	}, this.leaveOpenC, this);

}

this.leaveOpenC = function () {
	worldScripts["CargoTypeExtension"].tradeFloor();
}

/* Contract results */

this.openContractWin = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var price = cte.cargoPriceImport("CTE_CTOC_1", 15, cte.defaultMarketInfo()) / 10;
	var carried = cte.hasSpecialCargo("CTE_CTOC_1");
	var unit = cte.getCommodityUnit(this.contractdata.generictype);
	var msg = "Welcome, Trader " + player.name + ". Let's see those " + this.contractdata.cargospecific + " then.\n\n";
	if (carried == this.contractdata.amount) {
		msg += "Ah, excellent. All present and correct. If you'll just authorise the cargo transfer, the " + price.toFixed(1) + " ₢/" + unit + " will be yours.";

	} else if (carried >= this.contractdata.amount) { // shouldn't be possible, in general. Well, they probably deserve a small bonus.
		msg += "Ah, excellent. All present and correct. If you'll just authorise the cargo transfer, the " + price.toFixed(1) + " ₢/" + unit + " will be yours. I'll take the spares off your hands for the same price, I think. Don't tell " + this.contractdata.seller + ", though, or they'll be wanting their cut.";

	} else if (carried > 0) {
		price = Math.floor(price * (carried / this.contractdata.amount));
		msg += "You appear to be missing some of the cargo I need. I'll take what you have off your hands, now it's here, but your payment has been reduced to " + price.toFixed(1) + " ₢/" + unit + " to account for my costs in obtaining replacements.";
	} else if (carried == 0) {
		msg += "Ah. Apologies. I seem to have been misinformed. I thought you were carrying some goods for me, but it must have been someone with a similar name.";
	}

	player.credits += price * carried;
	for (var i = 1; i <= carried; i++) {
		worldScripts["CargoTypeExtension"].removeSpecialCargo("CTE_CTOC_1");
	}

	this.contractdata.active = 0;
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: this.contractdata.buyer,
		overlay: "cte_tradefloor.png",
		message: msg
	}, function () { }, this);

}

this.openContractLose = function () {
	this.contractdata.active = 0;

	var missed = clock.adjustedSeconds - this.contractdata.deadline;
	var msg = "You can't find " + this.contractdata.buyer + " when you dock. You ask the harbourmaster if they've seen them.\n\n"
	if (missed < 60) {
		msg += "You've literally just missed them. Some trader in a Boa Clipper was unloading cargo with them over there.";
	} else if (missed < 3600) {
		msg += "They were here a moment ago. I think they left a few minutes ago with a Boa II pilot.";
	} else if (missed < 86400) {
		msg += "I saw them around earlier today. An L-Crate full of cargo came in for them, I think, so they were supervising the unloading.";
	} else if (missed < (86400 * 7)) {
		msg += "Not for a few days, no. They were taking some cargo from a Boa planetside.";
	} else {
		msg += "No, they've not been up here for a while, not since that Anaconda came in last week.";
	}

	mission.runScreen({
		screenID:"newcargoes_trading",
		title: "Too late...",
		overlay: "cte_tradefloor.png",
		message: msg
	}, function () { }, this);
}
Scripts/cargotypepermits.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Permits";
this.description = "Permits for the transport of normally illegal goods";

// var this = this;

this.govTypes = [
	"Anarchy",
	"Feudal",
	"Multi-Government",
	"Dictatorship",
	"Communist",
	"Confederacy",
	"Democracy",
	"Corporate State"];
this.specTypes = [
	"Birds",
	"Felines",
	"Frogs",
	"Humanoids",
	"Insects",
	"Lizards",
	"Lobsters",
	"Rodents",
	"XEQjf8e"]; // nonsense string that won't match any species

this.startUp = function () {
	worldScripts["CargoTypeExtension"].registerPersonality(this.name);
	worldScripts["CargoTypeExtension"].registerPermit(this.name);

	if (missionVariables.cargotypeextension_permits) {
		this.unserialisePermits();
	} else {
		this.initialisePermits();
	}
}

this.playerWillSaveGame = function (cause) {
	this.serialisePermits();
}

this.initialisePermits = function () {
	this.currentPermits = new Array;
	this.permitSeller = null;
	this.localSeller();
}

this.serialisePermits = function () {
	var serial = "1|";

	var pdata = new Array;
	for (var i = 0; i < this.currentPermits.length; i++) {
		var cpermit = this.currentPermits[i];
		var permitcode = this.encodePermit(cpermit);

		var heldpermit = cpermit.good + "=" + cpermit.dest + "=" + cpermit.real + "=" + cpermit.deadline + "=" + permitcode;
		pdata.push(heldpermit);
	}
	serial += pdata.join(";");
	serial += "|";
	if (this.permitSeller) {
		serial += this.permitSeller.good + "=" + this.permitSeller.real + "=" + this.permitSeller.permitcode + "=" + this.permitSeller.name;
	}

	missionVariables.cargotypeextension_permits = serial;
}

this.unserialisePermits = function () {
	this.currentPermits = new Array;
	this.permitSeller = null;
	var pdata = missionVariables.cargotypeextension_permits.split("|");
	if (pdata[0] == "1") {
		this.unserialisePermits1(pdata);
	} else {
		log(this.name, "Error: " + pdata[0] + " is not a recognised permit format");
		player.consoleMessage("Critical error decoding special cargo data. Please see Latest.log");
	}
}

this.unserialisePermits1 = function (pdata) {
	if (pdata[1] != "") {
		var permits = pdata[1].split(";");
		for (var i = 0; i < permits.length; i++) {
			var pinfo = permits[i].split("=");
			var permit = this.decodePermit(pinfo[4]);
			permit.good = pinfo[0];
			permit.dest = pinfo[1];
			permit.real = pinfo[2];
			permit.deadline = pinfo[3];
			this.currentPermits.push(permit);
		}
	}

	if (pdata[2] != "") {
		var sdata = pdata[2].split("=");
		this.permitSeller = new Object;
		this.permitSeller.good = sdata[0];
		this.permitSeller.real = sdata[1];
		this.permitSeller.permitcode = sdata[2];
		this.permitSeller.name = sdata[3];
	}
}


/* 31 bit int describes permit properties:
 * 31: clean only if true
 * 30: closer only if true
 * 28: if true, TC limit applies
 * 26-25: if both true, avoid government applies
 * 24-23: if both true, avoid species applies
 * 20-17: time limit noise (7=none, +/-1 = +/-4%)
 * 16-14: 10+(TC limit * 10)
 * 12-10: Government ban
 * 8-6: Species ban (Bird=0, Feline, Frog, Humanoid, Insect, Lizard, Lobster, Rodent)
 * 4-1: forgery quality (ignored if real)
 */
this.randomPermit = function () {
	return Math.floor(Math.random() * 65536) + (Math.floor(Math.random() * 32768) << 15);
	//		return this.decodePermit(permit);
}

this.decodePermit = function (permitcode) {
	var permit = new Object;
	if (permitcode & 0x40000000) {
		permit.requireclean = 1;
	} else {
		permit.requireclean = 0;
	}
	if (permitcode & 0x20000000) {
		permit.requirecloser = 1;
	} else {
		permit.requirecloser = 0;
	}
	if (permitcode & 0x08000000 == 0x08000000) {
		permit.tclimit = 5 * (1 + ((permitcode & 0x0000E000) >> 13));
	} else {
		permit.tclimit = 0;
	}
	if (permitcode & 0x03000000 == 0x03000000) {
		permit.govrestrict = ((permitcode & 0x00000E00) >> 9);
	} else {
		permit.govrestrict = 8; // i.e. none
	}
	if (permitcode & 0x00C00000 == 0x00C00000) {
		permit.specrestrict = ((permitcode & 0x000000E0) >> 5);
	} else {
		permit.specrestrict = 8; // i.e. none
	}
	permit.timenoise = ((permitcode & 0x000F0000) >> 16) - 7;
	permit.quality = permitcode & 0x0000000F;

	return permit;
}

this.encodePermit = function (permit) {
	var permitcode = 0;
	if (permit.requireclean) {
		permitcode |= 0x40000000;
	}
	if (permit.requirecloser) {
		permitcode |= 0x20000000;
	}
	if (permit.tclimit > 0) {
		permitcode |= 0x08000000;
		permitcode |= ((permit.tclimit - 1) / 5) << 13;
	}
	if (permit.govrestrict < 8) {
		permitcode |= 0x03000000;
		permitcode |= permit.govrestrict << 9;
	}
	if (permit.specrestrict < 8) {
		permitcode |= 0x00C00000;
		permitcode |= permit.specrestrict << 5;
	}
	permitcode |= (permit.timenoise + 7) << 16;
	permitcode |= permit.quality;

	return permitcode;
}

this.shipWillEnterWitchspace = function () {
	if (!system.isInterstellarSpace) {
		this.previous = system.ID;
	}
}

this.shipExitedWitchspace = function () {
	if (!system.isInterstellarSpace) {
		// use later event as needs to be after market is initialised
		this.localSeller();

		this.expirePermits(this.previous);
	}
}

this.playerEnteredNewGalaxy = function () {
	if (this.currentPermits.length > 0) {
		player.commsMessage("Your export permits have been revoked.");
		this.currentPermits = new Array;
	}
}

this.localSeller = function () {
	var exports = worldScripts["CargoTypeExtension"].systemExports(galaxyNumber, system.ID);
	if (exports.length == 0) {
		worldScripts["CargoTypeExtension"].debug("Can't find local market; no permits issued");
		this.permitSeller = null;
		return;
	}
	if (Math.random() < 0.25) {
		// not always
		this.permitSeller = null;
		return;
	}
	var candidates = new Array;
	for (var i = 0; i < exports.length; i++) {
		var good = exports[i];
		if (worldScripts["CargoTypeExtension"].controlledGood(good)) {
			candidates.push(good);
		}
	}

	if (candidates.length == 0) {
		if (Math.random() < 0.9) {
			worldScripts["CargoTypeExtension"].debug("No suitable illegal goods on local market; no permits issued");
			this.permitSeller = null;
			return;
		} else {
			if (Math.random() < 0.5) {
				candidates.push(worldScripts["CargoTypeExtension"].extendableCargo("narcotics"));
			} else if (Math.random() < 0.5) {
				candidates.push(worldScripts["CargoTypeExtension"].extendableCargo("slaves"));
			} else {
				candidates.push(worldScripts["CargoTypeExtension"].extendableCargo("firearms"));
			}
			if (!candidates[0]) {
				worldScripts["CargoTypeExtension"].debug("No suitable illegal goods defined; no permits issued");
				this.permitSeller = null;
				return;
			}
		}
	}

	var seller = new Object;
	seller.good = candidates[Math.floor(Math.random() * candidates.length)];
	seller.permitcode = this.randomPermit();

	worldScripts["CargoTypeExtension"].debug("Defined permit " + seller.permitcode + " for " + seller.good);

	if ((10 * Math.random()) - 2 < system.government) {
		seller.real = 1;
		seller.name = this.sellerName(system.scrambledPseudoRandomNumber(15));//seed based on system
		if (this.decodePermit(seller.permitcode).govrestrict == system.government) {
			return; // a real seller wouldn't do this. Thanks to Capt. Murphy for spotting this one...
		}

	} else {
		seller.real = 0;
		seller.name = this.sellerName(Math.random());//random each time
	}

	worldScripts["CargoTypeExtension"].debug("Seller is " + seller.name + " (" + seller.real + ")");

	this.permitSeller = seller;
}

this.sellerName = function (seed) {
	var digrams = expandDescription("[digrams]");
	var transmute = function (val) { return 3.9324 * val * (1 - val); }
	var interpret = function (val) { return (32 * val) - Math.floor(32 * val); }

	var l1 = 2 + Math.floor(interpret(seed) * 3); seed = transmute(seed);
	var l2 = 2 + Math.floor(interpret(seed) * 3); seed = transmute(seed);

	var name = "";
	for (var i = 0; i < l1; i++) {
		if (i == 0) {
			name += digrams.substr(Math.floor(97 * interpret(seed)), 1) + digrams.substr(Math.floor(97 * interpret(seed)) + 1, 1).toLowerCase();
		} else {
			name += digrams.substr(Math.floor(97 * interpret(seed)), 2).toLowerCase();
		}
		seed = transmute(seed);
	}
	name += " ";
	for (var i = 0; i < l2; i++) {
		if (i == 0) {
			name += digrams.substr(Math.floor(97 * interpret(seed)), 1) + digrams.substr(Math.floor(97 * interpret(seed)) + 1, 1).toLowerCase();
		} else {
			name += digrams.substr(Math.floor(97 * interpret(seed)), 2).toLowerCase();
		}
		seed = transmute(seed);
	}

	return name;
}

this.calculatePermitDeadline = function (dest, timenoise) {
	var route = system.info.routeToSystem(System.infoForSystem(galaxyNumber, dest));
	if (!route) {
		return 0;
	}
	var hours = route.time + (6 * route.route.length);
	return clock.days + Math.floor(hours * (100 + (4 * timenoise)) / 2400) + 1;
}

// the level of legal status per TC it cancels
this.permitLevel = function (permit) {
	var gen = worldScripts["CargoTypeExtension"].cargoDefinition(permit.good, "genericType");
	var illegal = worldScripts["CargoTypeExtension"].cargoDefinition(permit.good, "illegal");
	if (gen == "slaves" || gen == "firearms") {
		illegal += 1;
	} else if (gen == "narcotics") {
		illegal += 2;
	}
	return illegal;
}

this.permitPrice = function (permit) {
	var hops = system.info.routeToSystem(System.infoForSystem(galaxyNumber, permit.dest)).route.length;
	var quantity = permit.tclimit;
	if (quantity == 0) {
		quantity = player.ship.cargoSpaceCapacity;
	}
	var price = 15 * hops * quantity;
	if (permit.govrestrict != 8) {
		price *= 0.9;
	}
	if (permit.specrestrict != 8) {
		price *= 0.95;
	}
	if (permit.requireclean) {
		price *= 0.95;
	}
	if (permit.requirecloser) {
		price *= 0.9;
	}
	if (permit.real == 0) {
		price *= permit.quality / 20;
	}
	price *= 0.7 + (system.government / 10);
	price *= Math.sqrt(this.permitLevel(permit));
	price *= 0.5 + (system.scrambledPseudoRandomNumber(12));

	return Math.floor(price); // nearest whole credit
}

this.permitDescription = function (permit) {
	var cte = worldScripts["CargoTypeExtension"];

	var desc = "This permit allows transport of " + cte.cargoDefinition(permit.good, "specificType") + " to " + System.systemNameForID(permit.dest) + " provided that transport is completed by the end of " + permit.deadline + ". The following restrictions apply:\nPermit will expire if you leave the galaxy.";
	if (permit.tclimit > 0) {
		desc += "\nNo more than " + permit.tclimit + cte.getCommodityUnit(cte.cargoDefinition(permit.good, "genericType")) + " may be transported";
	}
	if (permit.requireclean) {
		desc += "\nOnly valid for traders with a Clean legal record";
	}
	if (permit.requirecloser) {
		desc += "\nFlight plan must always get closer to destination system";
	}
	if (permit.govrestrict != 8) {
		desc += "\nTransport through " + this.govTypes[permit.govrestrict] + " systems is forbidden.";
	}
	if (permit.specrestrict != 8) {
		desc += "\nTransport through systems primarily inhabited by " + this.specTypes[permit.specrestrict] + " is forbidden.";
	}
	desc += "\nOther local by-laws may apply.";
	return desc;
}

this.permitConciseDescription = function (permit) {
	var cte = worldScripts["CargoTypeExtension"];
	var desc = "Permit for export of " + cte.cargoDefinition(permit.good, "specificType") + " to " + System.systemNameForID(permit.dest) + " by " + permit.deadline + ". ";
	if (permit.tclimit > 0) {
		desc += permit.tclimit + cte.getCommodityUnit(cte.cargoDefinition(permit.good, "genericType")) + " max; ";
	}
	if (permit.requireclean) {
		desc += "Clean only; ";
	}
	if (permit.requirecloser) {
		desc += "Always closer; ";
	}
	if (permit.govrestrict != 8) {
		desc += "No " + this.govTypes[permit.govrestrict] + "; ";
	}
	if (permit.specrestrict != 8) {
		desc += "No " + this.specTypes[permit.specrestrict] + "; ";
	}
	cte.debug(desc);
	desc += "\n";
	if (permit.real < 0) {
		desc = "FAKE " + desc;
	}
	return desc;
}

this.expirePermits = function (lastsys) {
	// discard any permits for which closer is required, and lastsys
	// is closer than this one by most direct route
	// also discard any for which clock.days > permit.deadline
	var n = this.currentPermits.length - 1;
	for (var i = n; i >= 0; i--) { // reverse order, so we can splice safely
		if (this.currentPermits[i].real == -1) {
			// silently remove discovered fakes
			this.currentPermits.splice(i, 1);
		} else if (this.currentPermits[i].deadline < clock.days) {
			this.currentPermits.splice(i, 1);
			player.commsMessage("Export permit expired.");
		} else if (!system.isInterstellarSpace && this.currentPermits[i].requirecloser) {
			var dist = system.info.routeToSystem(System.infoForSystem(galaxyNumber, this.currentPermits[i].dest)).distance;
			var lastdist = System.infoForSystem(galaxyNumber, lastsys).routeToSystem(System.infoForSystem(galaxyNumber, this.currentPermits[i].dest)).distance;
			if (dist > lastdist) {
				player.commsMessage("Your flight plan has invalidated an export permit!");
				this.currentPermits.splice(i, 1);
			}
		}
	}
}

/* Permit API */

this.permitGossip = function () {
	if (clock.days % 3 == 0) {
		return "* Need a permit? Look for " + this.sellerName(system.scrambledPseudoRandomNumber(15));
	}
	return false;
}

this.checkPermit = function (good, quantity, dryrun) {
	var score = 0;
	for (var i = 0; i < this.currentPermits.length; i++) {
		var permit = this.currentPermits[i];
		if (permit.good == good) {
			worldScripts["CargoTypeExtension"].debug("Found permit for " + good);
			if ((permit.real >= 0) &&
				(permit.tclimit == 0 || permit.tclimit >= quantity) &&
				(!permit.requireclean || player.bounty == 0) &&
				(permit.govrestrict != system.government) &&
				(system.inhabitantsDescription.indexOf(this.specTypes[permit.specrestrict]) == -1)) {
				// then this permit is applicable here

				// if a dry run, assume the forgery won't be discovered.
				if (!dryrun && permit.real == 0) {
					var check = Math.floor(Math.random() * (system.government + system.techLevel)); // max 0 in the worst anarchies, max 21 in a top corporate state.
					// permit quality is from 0-15 (so adjust to 5-20)
					worldScripts["CargoTypeExtension"].debug("Scan quality " + check + " versus forgery " + permit.quality);
					if (check > permit.quality + 5) {
						permit.real = -1; // discovered, will be garbage collected
						player.bounty |= 64;
						player.commsMessage(expandDescription("%H Port Authority: Your export license is a fake, [describe-pirate]! Surrender your cargo immediately!"));
						return 0; // don't even bother checking for other permits; take the full legal hit for these cargo pods too
					}
				}

				if (score > -this.permitLevel(permit)) {
					// use the best permit
					score = -this.permitLevel(permit);
				}
				worldScripts["CargoTypeExtension"].debug("Permit applicable for " + good);
			}
		}
	}
	return score;
}

this.checkImport = function (good, quantity, dryrun) {
	// all permits from this module are both import and export
	// forgeries only checked for on export, to make things simpler
	return this.checkPermit(good, quantity, true);
}

this.describePermits = function () {
	var desc = "";
	for (var i = 0; i < this.currentPermits.length; i++) {
		desc += this.permitConciseDescription(this.currentPermits[i]) + "\n";
	}
	return desc;
}

/* Personality API */

this.traderGossip = function () {
	return false;
}

this.describeContracts = function () {
	return "";
}

this.traderHere = function (srole) {
	if (srole != "") { // main stations only for now
		return false;
	}
	return (this.permitSeller != null);
}

this.traderName = function () {
	return this.permitSeller.name + ", permit official";
}

this.traderDesc = function () {
	return "Welcome to the " + system.name + " permit office, Trader. We currently have permits available for the export of " + worldScripts["CargoTypeExtension"].cargoDefinition(this.permitSeller.good, "specificType") + ". If you would like one, please set your navigation computer's destination system to your desired importer, and then come in to discuss permit terms.";
}

this.runOffer = function () {
	if (player.ship.targetSystem == system.ID) {
		this.permitDealings("Please set your navigation computer's destination system to your desired importer.");
		return;
	}
	this.currentpermit = this.decodePermit(this.permitSeller.permitcode);
	this.currentpermit.good = this.permitSeller.good;
	this.currentpermit.dest = player.ship.targetSystem;
	this.currentpermit.real = this.permitSeller.real;
	this.currentpermit.deadline = this.calculatePermitDeadline(this.currentpermit.dest, this.currentpermit.timenoise);
	if (this.currentpermit.deadline == 0) {
		this.permitDealings("I'm afraid we don't have a trade agreement with " + System.infoForSystem(galaxyNumber, player.ship.targetSystem).name + ", Trader.");
		return;
	} else {
		var price = this.permitPrice(this.currentpermit);
		var desc = this.permitDescription(this.currentpermit);
		var msg = "We can offer the following permit:\n";
		msg += "-------------------------\n";
		msg += desc + "\n";
		msg += "-------------------------\n";
		msg += "Inclusive of all fees and taxes, this will cost " + price + "₢";

		mission.runScreen({
			screenID:"newcargoes_permits",
			title: this.traderName(),
			message: msg,
			choicesKey: "cte_permit_deal",
			overlay: "cte_permit.png"
		}, this.permitChoice, this);

	}

}

this.permitChoice = function (choice) {
	if (choice == "01_ACCEPT") {
		var price = this.permitPrice(this.currentpermit);
		if (player.credits < price) {
			this.permitDealings("I'm sorry, Trader. You don't currently have enough credit to afford this permit.");
		} else {
			player.credits -= price;
			this.currentPermits.push(this.currentpermit);
			this.permitDealings("The permit has been registered to your ship, Trader, and the fee of " + price + "₢ has been charged to your account.");
			this.permitSeller = null;
		}
	} else {
		this.permitDealings("Get back in touch if you'd like a quote for a permit to a different destination.");
	}
}

this.permitDealings = function (msg) {
	mission.runScreen({
		screenID:"newcargoes_permits",
		title: this.traderName(),
		message: msg,
		overlay: "cte_permit.png"
	}, this.permitExit, this);
}

this.permitExit = function (choice) {
	worldScripts["CargoTypeExtension"].tradeFloor();
}
Scripts/cargotyperegional.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Regional";
this.description = "Significant Regional fluctuations in pricing to make trading more interesting.";

this.fluctypes = 12;
/* Cargo classes
0=import ban (not used for cargo classes)
1=export ban (not used for cargo classes)
2=price decrease (export)
3=price increase (export)
4=price decrease (import)
5=price increase (import)
* Specific Cargo Types
6-11 as above
*/
this.cargoTypes = ["food", "textiles", "radioactives", "slaves", "liquor_wines", "luxuries", "narcotics", "computers", "machinery", "alloys", "firearms", "furs", "minerals"];


this.startUp = function () {
	worldScripts["CargoTypeExtension"].registerPriceChange(this.name);
	worldScripts["CargoTypeExtension"].registerPermit(this.name);

	/* Define regions. Thanks to Svengali for sharing region data from BGS-X */
	this.regions = new Array;
	for (var i = 0; i < 8; i++) {
		this.regions[i] = new Array;
	}
	this.regions[0].push({ id: 0, name: "Old Worlds", systems: [7, 39, 46, 55, 129, 147], type: "area" });
	this.regions[0].push({ id: 1, name: "Xexedi Cluster", systems: [37, 43, 48, 58, 64, 101, 111, 140, 141, 188, 197, 250], type: "area" });
	this.regions[0].push({ id: 2, name: "Iron Stars", systems: [2, 3, 6, 94, 128, 132, 146, 153, 196, 234], type: "area" });
	this.regions[0].push({ id: 3, name: "Teraed Region", systems: [60, 84, 99, 120, 121, 185, 191, 221, 241], type: "area" });
	this.regions[0].push({ id: 4, name: "Galcenter G1", systems: [21, 42, 62, 93, 100, 131, 139, 167, 230, 249], type: "area" });
	this.regions[0].push({ id: 5, name: "Devils Triangle", systems: [20, 148, 181], type: "area" });
	this.regions[0].push({ id: 6, name: "Pulsar Worlds", systems: [8, 18, 49, 51, 102, 114, 158, 215, 218, 246], type: "area" });
	this.regions[0].push({ id: 7, name: "Tortuga Expanse", systems: [59, 142, 174, 213, 223, 238], type: "area" });
	this.regions[0].push({ id: 8, name: "Spaceway L1", systems: [7, 50, 73, 111, 129, 141, 186, 227, 35, 73, 188, 250], type: "lane" });
	this.regions[0].push({ id: 9, name: "Spaceway L2", systems: [7, 29, 42, 73, 89, 93, 129, 131, 222, 227], type: "lane" });
	this.regions[0].push({ id: 10, name: "Spaceway L3", systems: [16, 28, 36, 62, 99, 131, 150, 198, 221], type: "lane" });
	this.regions[0].push({ id: 11, name: "Spaceway L4", systems: [18, 86, 99, 126, 241, 246], type: "lane" });
	this.regions[1].push({ id: 12, name: "Citadels Of Chaos", systems: [5, 37, 100, 229], type: "area" });
	this.regions[1].push({ id: 13, name: "Equilibrium", systems: [61, 92, 156], type: "area" });
	this.regions[1].push({ id: 14, name: "Fertile Cresent", systems: [0, 30, 47, 64, 110, 135, 137, 190, 213, 217, 225, 254], type: "area" });
	this.regions[1].push({ id: 15, name: "Fiddlers Green", systems: [2, 7, 10, 70, 76, 104, 120, 172, 181, 194, 224], type: "area" });
	this.regions[1].push({ id: 16, name: "Galcenter G2", systems: [19, 33, 57, 91, 94, 113, 115, 167, 178, 226, 242], type: "area" });
	this.regions[1].push({ id: 17, name: "Kobs Ladder", systems: [13, 20, 29, 81, 97, 102, 123, 124, 149], type: "area" });
	this.regions[1].push({ id: 18, name: "Qucedi Bottleneck", systems: [16, 34, 59, 111, 139, 157, 159, 205, 248], type: "area" });
	this.regions[1].push({ id: 19, name: "Ramaans Keep", systems: [56, 117, 131, 161, 166, 170, 197, 228], type: "area" });
	this.regions[1].push({ id: 20, name: "Redline Stars", systems: [65, 108, 140, 147, 189, 204, 215, 231, 249], type: "area" });
	this.regions[1].push({ id: 21, name: "Teonan Web", systems: [18, 26, 32, 36, 42, 50, 55, 69, 114, 142, 146, 202, 251], type: "area" });
	this.regions[1].push({ id: 22, name: "Zaedvera Pocket", systems: [38, 44, 62, 87, 134, 155, 175, 185, 192, 220, 227, 235], type: "area" });
	this.regions[1].push({ id: 23, name: "Erlage Corridor", systems: [29, 57, 94, 113, 115], type: "lane" });
	this.regions[1].push({ id: 24, name: "Spaceway L1", systems: [23, 43, 45, 54, 57, 74, 82, 115, 122, 178, 188], type: "lane" });
	this.regions[1].push({ id: 25, name: "Spaceway L2", systems: [45, 58, 65, 66, 88, 127, 140, 144, 189, 193, 204, 221, 236, 240, 48], type: "lane" });
	this.regions[1].push({ id: 26, name: "Spaceway L3", systems: [42, 109, 114, 127, 202], type: "lane" });
	this.regions[1].push({ id: 27, name: "Maorin Pass", systems: [9, 41, 59, 61, 92, 100, 156, 179, 189, 205], type: "lane" });
	this.regions[1].push({ id: 28, name: "Tomoka Run", systems: [6, 24, 48, 53, 78, 127, 136, 207], type: "lane" });
	this.regions[2].push({ id: 29, name: "Galcenter G3", systems: [5, 22, 26, 49, 56, 58, 110, 140, 150, 175, 177, 228, 234, 239], type: "area" });
	this.regions[2].push({ id: 30, name: "Inner Reach", systems: [54, 118, 121, 129, 161, 165, 185, 186, 232], type: "area" });
	this.regions[2].push({ id: 31, name: "Outer Reach", systems: [18, 52, 62, 100, 145, 149, 201, 223], type: "area" });
	this.regions[2].push({ id: 32, name: "Prodigal Suns", systems: [133, 206], type: "area" });
	this.regions[2].push({ id: 33, name: "Runners Leap", systems: [126, 162, 200, 233], type: "area" });
	this.regions[2].push({ id: 34, name: "Sidewinder Expanse", systems: [35, 36, 40, 51, 61, 90, 99, 184, 210], type: "area" });
	this.regions[2].push({ id: 35, name: "Starbridge Cluster", systems: [19, 32, 46, 60, 64, 82, 88, 94, 101, 107, 114, 119, 134, 168, 173, 192, 205, 207, 215, 227, 236, 241, 244, 248], type: "area" });
	this.regions[2].push({ id: 36, name: "Yeon Pass", systems: [27, 45, 108, 127, 170, 199, 231, 247], type: "area" });
	this.regions[2].push({ id: 37, name: "Broken Circle", systems: [54, 118, 129, 161, 165, 186], type: "lane" });
	this.regions[2].push({ id: 38, name: "Chancers Alley", systems: [5, 58, 76, 77, 97, 140, 152, 168], type: "lane" });
	this.regions[2].push({ id: 39, name: "Glasnost Corridor", systems: [98, 106, 115, 245, 250, 95, 117, 160], type: "lane" });
	this.regions[2].push({ id: 40, name: "Spaceway L1", systems: [5, 17, 22, 26, 38, 50, 56, 75, 91, 110, 140, 144, 150, 177, 182, 209, 218, 246], type: "lane" });
	this.regions[2].push({ id: 41, name: "Spaceway L2", systems: [38, 42, 50, 65, 79, 135, 139, 154, 189], type: "lane" });
	this.regions[2].push({ id: 42, name: "Triskelion Conduit", systems: [6, 70, 101, 132, 164, 180, 205, 241, 10, 53, 70, 81, 85, 202, 225], type: "lane" });
	this.regions[3].push({ id: 43, name: "Razors Edge", systems: [16, 24, 131, 196, 219, 251, 139, 168, 243, 88, 160, 192], type: "area" });
	this.regions[3].push({ id: 44, name: "Vuesar Cordon", systems: [59, 200], type: "area" });
	this.regions[3].push({ id: 45, name: "Unified Democratic Star League", systems: [37, 38, 53, 73, 74, 82, 117, 125, 181, 234, 237, 245], type: "area" });
	this.regions[3].push({ id: 46, name: "Anintorian Irruption", systems: [96, 240], type: "area" });
	this.regions[3].push({ id: 47, name: "Ririla Influx", systems: [171], type: "area" });
	this.regions[3].push({ id: 48, name: "Parallel Accord", systems: [20, 31, 44, 51, 63, 68, 71, 79, 92, 99, 102, 110, 115, 151, 158, 215, 220, 224, 231, 248], type: "area" });
	this.regions[3].push({ id: 49, name: "Galcenter G4", systems: [10, 30, 34, 45, 49, 54, 65, 95, 101, 103, 105, 119, 137, 144, 148, 222, 241], type: "area" });
	this.regions[3].push({ id: 50, name: "Foundrys Reach", systems: [12, 35, 36, 55, 57, 86, 94, 116, 159, 163, 167, 184, 191, 228, 230], type: "area" });
	this.regions[3].push({ id: 51, name: "Lower Insurgency", systems: [64, 104, 112, 128, 211, 216, 232], type: "area" });
	this.regions[3].push({ id: 52, name: "Sunspan Pass", systems: [36, 48, 84, 85, 116, 118, 135, 150, 154, 162, 167, 193, 197, 206, 210, 213, 223, 230, 240, 154, 166, 213], type: "lane" });
	this.regions[3].push({ id: 53, name: "Supply Line 1", systems: [25, 33, 38, 53, 57, 58, 62, 74, 78, 82, 117, 134, 138, 181, 191, 209, 234, 242, 245], type: "lane" });
	this.regions[3].push({ id: 54, name: "Sanctuarys Way", systems: [1, 40, 65, 100, 105, 119, 144, 146, 172, 174, 189, 208, 222, 233], type: "lane" });
	this.regions[3].push({ id: 55, name: "Spaceway L1", systems: [2, 21, 45, 54, 76, 77, 95, 97, 103, 105, 113, 137, 177, 190, 249, 113, 141, 50, 103, 239, 103, 108], type: "lane" });
	this.regions[3].push({ id: 56, name: "Supply Line 2", systems: [1, 4, 6, 44, 63, 66, 68, 71, 79, 110, 121, 142, 145, 146, 158, 174, 215, 220, 231, 7, 11, 140, 142, 161, 165], type: "lane" });
	this.regions[3].push({ id: 57, name: "Esonlear Cakewalk", systems: [7, 32, 60, 216, 232], type: "lane" });
	this.regions[4].push({ id: 58, name: "Gate Of Damocles", systems: [49, 58, 66, 71, 78, 84, 125, 242], type: "area" });
	this.regions[4].push({ id: 59, name: "Siege Worlds", systems: [31, 37, 44, 63, 95, 127, 134, 198, 219], type: "area" });
	this.regions[4].push({ id: 60, name: "Tetitri Conclave", systems: [7, 70, 137, 140, 161, 173, 191, 205], type: "area" });
	this.regions[4].push({ id: 61, name: "Galcenter G5", systems: [1, 3, 19, 24, 28, 35, 47, 60, 69, 102, 111, 112, 122, 156, 180, 181, 230, 231], type: "area" });
	this.regions[4].push({ id: 62, name: "Galcon Bombard", systems: [36, 86, 98, 123, 133, 151, 223], type: "area" });
	this.regions[4].push({ id: 63, name: "Steel Halo", systems: [55, 72, 100, 109, 126, 128, 185], type: "area" });
	this.regions[4].push({ id: 64, name: "Serenic Arc", systems: [16, 20, 39, 74, 93, 131, 182, 207, 249, 40, 53, 80, 146, 153, 166, 182, 188, 220, 221, 225, 252], type: "lane" });
	this.regions[4].push({ id: 65, name: "Lesser Wheel", systems: [2, 10, 65, 91, 99, 114, 116, 118, 146, 153, 244, 248], type: "lane" });
	this.regions[4].push({ id: 66, name: "Spaceway L1", systems: [1, 19, 24, 28, 35, 41, 45, 60, 79, 102, 112, 117, 156, 169, 172, 180, 181, 213, 216, 217, 228, 230, 231], type: "lane" });
	this.regions[4].push({ id: 67, name: "Spaceway L2", systems: [7, 11, 54, 115, 135, 137, 140, 150, 184, 193, 225, 243, 253, 57, 115, 135, 189, 201, 202, 221, 226, 227, 12, 54, 85, 158, 175, 178, 204, 212, 215], type: "lane" });
	this.regions[4].push({ id: 68, name: "Zalexe Loop", systems: [77, 88, 89, 106, 152, 167, 170, 190, 194, 208, 246, 30, 87, 89], type: "lane" });
	this.regions[4].push({ id: 69, name: "Extrinsic Reach", systems: [9, 23, 61, 104, 120, 129, 136, 139, 163, 233], type: "lane" });
	this.regions[4].push({ id: 70, name: "Greater Wheel", systems: [8, 41, 149, 183, 194, 210, 222, 254, 29, 34, 42, 82, 92, 108, 121, 130, 149, 183, 229], type: "lane" });
	this.regions[5].push({ id: 71, name: "Galcenter G6", systems: [9, 40, 50, 79, 81, 85, 99, 107, 110, 117, 133, 164, 174, 176, 183, 187, 195, 213, 215, 232, 237], type: "area" });
	this.regions[5].push({ id: 72, name: "Raonbe Cluster", systems: [16, 21, 22, 53, 98, 102, 130, 139, 229, 248], type: "area" });
	this.regions[5].push({ id: 73, name: "Rabian Spur", systems: [3, 8, 84, 88, 111, 184, 196, 214, 236], type: "area" });
	this.regions[5].push({ id: 74, name: "Iron Alliance", systems: [14, 44, 82, 106, 113, 134, 146, 156, 171, 177, 185, 205, 216, 230, 245, 249], type: "area" });
	this.regions[5].push({ id: 75, name: "Isaanus Syndicate", systems: [10, 46, 66, 77, 118, 140, 159, 186, 240], type: "area" });
	this.regions[5].push({ id: 76, name: "Centreward Industrial Sector", systems: [1, 6, 17, 89, 143, 161, 189, 60, 67, 75, 127, 138, 147, 155, 233, 250, 252, 4, 37, 71, 86, 155, 225, 254, 0, 5, 19, 58, 69, 71, 144, 150, 182, 194, 200, 206, 210, 223, 254], type: "area" });
	this.regions[5].push({ id: 77, name: "Anener Commonwealth", systems: [32, 56, 160, 47, 242, 167, 129, 12], type: "area" });
	this.regions[5].push({ id: 78, name: "Anenbian Confederacy", systems: [224, 23, 2, 87, 201, 168, 208, 172, 114, 65, 35, 39, 246, 83, 42], type: "area" });
	this.regions[5].push({ id: 79, name: "Lost Worlds", systems: [41, 202, 55, 33], type: "area" });
	this.regions[5].push({ id: 80, name: "Jaws Of Cerberus", systems: [121, 203, 31, 30, 74, 126, 234], type: "area" });
	this.regions[5].push({ id: 81, name: "Rimward Industrial Sector", systems: [241, 149, 153, 90, 255, 137, 132, 72, 212, 95, 141, 218, 178, 116, 154, 45, 204, 163, 120, 7, 198, 251], type: "area" });
	this.regions[5].push({ id: 82, name: "Centreward Span", systems: [6, 17, 28, 29, 54, 104, 108, 148, 152, 162, 181, 211], type: "lane" });
	this.regions[5].push({ id: 83, name: "Spaceway L1", systems: [35, 39, 83, 114, 115, 246, 40, 115, 133, 164, 215, 13, 85, 92, 99, 136, 144, 206, 3, 8, 84, 88, 206], type: "lane" });
	this.regions[5].push({ id: 84, name: "Spaceway L2", systems: [51, 80, 103, 169, 243, 16, 98, 102, 243, 16, 67, 101, 109, 138, 142, 209, 112, 138, 225, 233], type: "lane" });
	this.regions[5].push({ id: 85, name: "Remlok Road", systems: [203, 31, 30, 226, 234, 240, 140, 159, 77, 10], type: "lane" });
	this.regions[5].push({ id: 86, name: "Rimward Arc", systems: [215, 124, 232, 50, 120, 154, 116, 177, 82, 216, 14, 113, 156], type: "lane" });
	this.regions[6].push({ id: 87, name: "Five Points Trade Barrier", systems: [139, 27, 228, 137, 153], type: "area" });
	this.regions[6].push({ id: 88, name: "Rubicon Crossroad", systems: [134, 15, 248, 97, 110, 186], type: "area" });
	this.regions[6].push({ id: 89, name: "Kleptocratic Swathe", systems: [187, 77, 53, 240, 100, 107, 174], type: "area" });
	this.regions[6].push({ id: 90, name: "Galcop Central Intelligence Sector", systems: [18, 234, 117, 49, 1], type: "area" });
	this.regions[6].push({ id: 91, name: "Isare Pact", systems: [124, 51, 172, 81, 204, 102], type: "area" });
	this.regions[6].push({ id: 92, name: "Galcentre", systems: [65, 39, 181, 215, 111, 133, 179, 41, 193, 247, 37, 202, 150, 72, 236, 44], type: "area" });
	this.regions[6].push({ id: 93, name: "Spaceway L1", systems: [103, 58, 253, 65, 39, 44, 236, 150, 247, 193, 179, 133, 215, 95, 109], type: "lane" });
	this.regions[6].push({ id: 94, name: "Zero-G Hockey Road", systems: [194, 73, 146, 239, 86, 74, 199, 23, 138, 18, 234, 244, 166, 152, 162, 242, 184], type: "lane" });
	this.regions[6].push({ id: 95, name: "Far Rift Reach", systems: [218, 130, 125, 205, 79, 191, 17, 178], type: "lane" });
	this.regions[6].push({ id: 96, name: "Isisian Detour", systems: [239, 19, 250, 209, 56, 67, 163, 7, 211], type: "lane" });
	this.regions[6].push({ id: 97, name: "Quandixe Connection", systems: [46, 36, 212, 187, 53, 107, 174], type: "lane" });
	this.regions[7].push({ id: 98, name: "Unified Tech-Core", systems: [216, 137, 147, 13, 9, 211, 88, 171, 2, 215], type: "area" });
	this.regions[7].push({ id: 99, name: "Crystalline Desolation", systems: [246, 229, 131, 36, 100, 89, 22, 207, 37, 176, 74, 228, 99, 5, 164, 101, 182, 48, 165, 208, 86, 80, 10, 153, 112, 217, 214, 239], type: "area" });
	this.regions[7].push({ id: 100, name: "Riven Hatachi Senate", systems: [231, 200, 72, 219, 75, 153, 10, 80, 86, 208, 6, 250, 81, 65, 169, 232, 105, 252, 59, 40], type: "area" });
	this.regions[7].push({ id: 101, name: "Galcentre", systems: [241, 233, 39, 168, 125, 41, 45, 64, 104, 110, 201, 7, 73, 193, 120, 127, 102, 184, 135, 172, 177, 210, 70, 130, 123, 38, 175, 23], type: "area" });
	this.regions[7].push({ id: 102, name: "Beggars' Loop", systems: [150, 242, 87, 170, 115, 160, 134, 90, 166, 11, 185, 196, 34, 186, 62, 240], type: "area" });
	this.regions[7].push({ id: 103, name: "Ladle", systems: [52, 223, 247, 144, 67], type: "area" });
	this.regions[7].push({ id: 104, name: "Far Arm Star Cluster", systems: [203, 255, 161, 16, 98, 84, 20, 107], type: "area" });
	this.regions[7].push({ id: 105, name: "Spaceway L1 Centreward Branch", systems: [215, 2, 211, 9, 13, 147, 216, 225, 21, 218, 71, 206, 236, 190, 18, 26, 152, 51, 209, 24, 174, 82, 179, 173, 30, 251, 233, 125, 45, 41, 104, 110, 73, 135, 117, 55], type: "lane" });
	this.regions[7].push({ id: 106, name: "Spaceway L1 Rimward Branch", systems: [33, 145, 1, 53, 98, 161, 255, 203, 76, 49, 148, 198, 213, 151, 230, 157, 46, 116, 243, 244, 61, 32, 93, 186, 62, 56, 91, 44, 8, 249, 136, 63, 167, 130, 123, 38, 23, 39, 241, 12, 114], type: "lane" });
	this.regions[7].push({ id: 107, name: "Centreward Flight", systems: [126, 6, 81, 65, 169, 232, 252], type: "lane" });
	this.regions[7].push({ id: 108, name: "Hatachi Trade Annex", systems: [75, 219, 248, 117], type: "lane" });
	this.regions[7].push({ id: 109, name: "Rimward Decamp", systems: [117, 248, 219, 72, 10], type: "lane" });

	this.unserialiseRegionalData();
}

this.unserialiseRegionalData = function () {
	if (!missionVariables.cargotypeextension_regional) {
		// give time for trade goods to be initialised
		this.timer = new Timer(this, this.initialiseRegionalData, 0.25);
		return;
	}
	var fluxdata = missionVariables.cargotypeextension_regional.split("|");
	var version = fluxdata.shift();
	if (version == "1") {
		this.unserialiseRegionalData1(fluxdata);
	} else {
		log(this.name, "Error: " + svars[0] + " is not a recognised regional format");
		player.consoleMessage("Critical error decoding special cargo data. Please see Latest.log");
	}
}

this.initialiseRegionalData = function () {
	this.variations = new Array;
	for (var i = 0; i <= 7; i++) {
		this.variations[i] = new Array;
		for (var j = 0; j <= 6; j++) {
			this.variations[i].push(this.newVariation(i));
		}
	}

}

this.newVariation = function (gal) {
	var variation = new Object;
	variation.type = Math.floor(Math.random() * this.fluctypes);
	if (variation.type < 2) {
		variation.type += 6;
	}
	variation.region = Math.floor(Math.random() * this.regions[gal].length);
	if (variation.type < 6) {
		variation.cargo = this.cargoTypes[Math.floor(Math.random() * this.cargoTypes.length)];
	} else {
		var ctype = this.cargoTypes[Math.floor(Math.random() * this.cargoTypes.length)];
		variation.cargo = worldScripts["CargoTypeExtension"].extendableCargo(ctype);
	}
	variation.intensity = 3 + Math.floor(Math.random() * 6);
	variation.halflife = 5 + (Math.floor(Math.random() * 11));
	worldScripts["CargoTypeExtension"].debug(gal + " " + variation.type + " " + variation.region + " " + variation.cargo + " " + variation.intensity + " " + variation.halflife);
	return variation;
}

this.playerWillSaveGame = function (cause) {
	this.serialiseRegionalData();
}

this.serialiseRegionalData = function () {
	var galdata = new Array;
	for (var i = 0; i <= 7; i++) {
		var vardata = new Array;
		for (var j = 0; j < this.variations[i].length; j++) {
			var variation = this.variations[i][j];
			// some undefs can get into this bit
			if (variation.type && variation.region) {
				vardata.push(variation.type + "/" + variation.region + "/" + variation.cargo + "/" + variation.intensity + "/" + variation.halflife);
			}
		}
		galdata.push(vardata.join(";"));
	}

	missionVariables.cargotypeextension_regional = "1|" + galdata.join("|");
}

this.unserialiseRegionalData1 = function (fluxdata) {
	this.variations = new Array;
	for (var i = 0; i <= 7; i++) {
		this.variations[i] = new Array;
		var galdata = fluxdata[i].split(";");
		for (var j = 0; j < galdata.length; j++) {
			var vardata = galdata[j].split("/");
			var variation = {
				type: vardata[0],
				region: vardata[1],
				cargo: vardata[2],
				intensity: vardata[3],
				halflife: vardata[4]
			};
			this.variations[i][j] = variation;
		}
	}
}

this.shipWillEnterWitchspace = function () {
	this.lastday = clock.days;
}

this.shipWillExitWitchspace = function () {
	if (!system.isInterstellarSpace) {
		// give enough time for the clock to adjust
		this.updateRegionalData();
	}
}

this.updateRegionalData = function () {
	var cycles = clock.days - this.lastday;
	for (var k = 0; k < cycles; k++) {
		// update existing variations
		for (var i = 0; i < 7; i++) {
			// reverse order for splicing out expired
			for (var j = this.variations[i].length - 1; j >= 0; j--) {
				if (Math.random() < 1 / this.variations[i][j].halflife) {
					this.variations[i][j].intensity--;
					if (this.variations[i][j].intensity <= 0) {
						this.variations[i].splice(j, 1);
					}
				}
			}
		}

		var gal = Math.floor(Math.random() * 8);

		var newvar = this.newVariation(gal);
		this.variations[gal].push(newvar);
		if (gal == galaxyNumber) {
			this.notifyTradernet(newvar);
		}
	}

}

this.notifyTradernet = function (newvar) {

	if (newvar.type < 6) {
		var cargotype = worldScripts["CargoTypeExtension"].genericName(newvar.cargo);
	} else {
		var cargotype = worldScripts["CargoTypeExtension"].cargoDefinition(newvar.cargo, "specificType");
	}
	var region = this.regions[galaxyNumber][newvar.region];

	var template = "[cte_regionnews_" + region.type + "_" + newvar.type + "_1]";

	var msg = expandDescription(template, {
		cte_region_cargo: cargotype,
		cte_region_name: region.name
	});

	worldScripts["CargoTypeExtension"].addTraderNet(msg);
}

/* Permit API */

this.permitGossip = function () {
	var len = this.variations[galaxyNumber].length;
	var variation = this.variations[galaxyNumber][clock.days % len];
	if (this.regions[galaxyNumber][variation.region].type == "lane") {
		var prepos = "on";
	} else {
		var prepos = "in";
	}
	if (variation.type == 6) {
		return "* Importing " + worldScripts["CargoTypeExtension"].cargoDefinition(variation.cargo, "specificType") + " is still prohibited " + prepos + " the " + this.regions[galaxyNumber][variation.region].name;
	} else if (variation.type == 7) {
		return "* Exporting " + worldScripts["CargoTypeExtension"].cargoDefinition(variation.cargo, "specificType") + " is still prohibited " + prepos + " the " + this.regions[galaxyNumber][variation.region].name;
	}
	return false;
}

this.checkPermit = function (good, quantity, dryrun) {
	var score = 0;
	for (var i = 0; i < this.variations[galaxyNumber].length; i++) {
		if (this.variations[galaxyNumber][i].type == 7 && this.variations[galaxyNumber][i].cargo == good) {
			if (this.regions[galaxyNumber][this.variations[galaxyNumber][i].region].systems.indexOf(system.ID) >= 0) {
				score += 1 + Math.floor(this.variations[galaxyNumber][i].intensity / 3);
			}
		}
	}
	return score;
}

this.checkImport = function (good, quantity, dryrun) {
	var score = 0;
	for (var i = 0; i < this.variations[galaxyNumber].length; i++) {
		if (this.variations[galaxyNumber][i].type == 6 && this.variations[galaxyNumber][i].cargo == good) {
			if (this.regions[galaxyNumber][this.variations[galaxyNumber][i].region].systems.indexOf(system.ID) >= 0) {
				score += 1 + Math.floor(this.variations[galaxyNumber][i].intensity / 3);
			}
		}
	}
	return score;
}

this.describePermits = function () {
	var desc = "";
	for (var i = 0; i < this.variations[galaxyNumber].length; i++) {
		if (this.regions[galaxyNumber][this.variations[galaxyNumber][i].region].systems.indexOf(system.ID) >= 0) {
			if (this.variations[galaxyNumber][i].type == 6) {
				desc += "Import of " + worldScripts["CargoTypeExtension"].cargoDefinition(this.variations[galaxyNumber][i].cargo, "specificType") + " is forbidden.\n";
			} else if (this.variations[galaxyNumber][i].type == 7) {
				desc += "Export of " + worldScripts["CargoTypeExtension"].cargoDefinition(this.variations[galaxyNumber][i].cargo, "specificType") + " is forbidden.\n";
			}
		}
	}
	return desc;
}

/* Pricing API */

this.priceGossip = function () {
	var len = this.variations[galaxyNumber].length;
	var variation = this.variations[galaxyNumber][clock.days % len];
	if (this.regions[galaxyNumber][variation.region].type == "lane") {
		var prepos = "on";
	} else {
		var prepos = "in";
	}
	if (variation.type % 6 < 2) {
		return false;
	}
	if (variation.type >= 6) {
		var cargo = worldScripts["CargoTypeExtension"].cargoDefinition(variation.cargo, "specificType");
	} else {
		var cargo = worldScripts["CargoTypeExtension"].genericName(variation.cargo);
	}
	var vt = variation.type % 6;
	var rn = this.regions[galaxyNumber][variation.region].name;
	if (vt == 2) {
		return "* You can get " + cargo + " cheaply " + prepos + " the " + rn;
	} else if (vt == 3) {
		return "* Don't expect bargains on " + cargo + " " + prepos + " the " + rn;
	} else if (vt == 4) {
		return "* The " + rn + " is no good to sell " + cargo + " now.";
	} else if (vt == 5) {
		return "* " + cargo + "? Good prices " + prepos + " the " + rn;
	}
	return false;
}

this.priceChange = function (good, context) {
	if (!this.variations) {
		worldScripts["CargoTypeExtension"].debug("Regional variations not initialised yet!");
		return 1;
	}

	var gen = worldScripts["CargoTypeExtension"].cargoDefinition(good, "genericType");
	var multiplier = 1;
	for (var i = 0; i < this.variations[galaxyNumber].length; i++) {
		var variation = this.variations[galaxyNumber][i];
		if (this.regions[galaxyNumber][variation.region] && this.regions[galaxyNumber][variation.region].systems.indexOf(system.ID) >= 0) {
			worldScripts["CargoTypeExtension"].debug("Currently in region");
			worldScripts["CargoTypeExtension"].debug(variation.type + " " + variation.cargo + " " + good + " " + gen + " " + variation.intensity);
			if ((variation.type < 6 && gen == variation.cargo) ||
				(variation.type >= 6 && good == variation.cargo)) {
				var vt = variation.type % 6;
				var mi = variation.intensity / 10;
				if (mi < 0) {
					mi = 0.1;
				}
				if (vt == 2 && (context == "FLUX_EXPORT" || context == "FLUX_MISC")) {
					multiplier *= (1 - mi);
				} else if (vt == 3 && (context == "FLUX_EXPORT" || context == "FLUX_MISC")) {
					multiplier *= (1 + mi);
				} else if (vt == 4 && context == "FLUX_IMPORT") {
					multiplier *= (1 - mi);
				} else if (vt == 5 && context == "FLUX_IMPORT") {
					multiplier *= (1 + mi);
					// some effects from illegal goods; premium for smuggling
				} else if (vt == 0 && context == "FLUX_IMPORT") {
					multiplier *= 1.5;
				} else if (vt == 1 && context == "FLUX_EXPORT") {
					multiplier *= 0.8;
				}
			}
		}
	}
	worldScripts["CargoTypeExtension"].debug("Multiplier for " + good + " in " + context + " is " + multiplier);
	return multiplier;
}

Scripts/cargotypescavenger.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Scavenger";
this.description = "Rare goods and their collectors";

// var this = this;

this.startUp = function () {
	worldScripts["CargoTypeExtension"].registerPersonality(this.name);

	var cargotypes = [
		"F1", "food", "Colonial Ration Packs", "The ration packs used by the first human settlers of the many worlds are of great interest to historians. Most of the packs are empty, but even the packaging can provide significant information about what humans ate several centuries ago, and how this varied between times and places.",
		"T1", "textiles", "Panels of Far Voyager Tapestry", "When the Far Voyager Generation Ship was found derelict in the Birior system in 2954, the salvage team discovered a tapestry over ten miles in length running much of the length of the ship, depicting the history of the vessel's voyage. Its panels were carefully loaded into a freighter, which was then ambushed by pirates. Fortunately they recognised the immense value of the tapestry; unfortunately to get away with it they had to spend years slowly fencing it panel by panel across the eight charts. Quonso University has been slowly buying up the panels from private ownership ever since with the aim of reconstructing the whole tapestry.",
		"O1", "radioactives", "Quantum Phase Matter", "Quantum Phase Matter appears to most instruments to be perfectly normal, but its quantum vibrations are a fraction of a degree out of phase with the rest of the universe. The implications of this are not known, and a few high-tech labs are desperate for further samples for their research.",
		"IS1", "slaves", "Suspended animation medpods", "Larivearian pirates made a daring raid on the experimental hospital there, stealing hundreds of thousands of medpods for slaves, and destroying much of the facility in the process. Unfortunately for them, the first capsule they opened contained a victim of Rirelaxean Exploding Disease, and the majority of the pirates died within hours. A few who had already left to try to sell the pods instead dumped them across the charts when they heard what had happened. No-one now dares open them until they can be brought back to Larivear to be correlated against the remains of the medical records there.",
		"L1", "liquor_wines", "Venerable Whisky Barrels", "It was inevitable that the very richest members of galactic society would no longer be content with whisky that had been aged for mere decades. These barrels, heavily reinforced, are well over a century old, and the whisky continues to mature inside them.",
		"X1", "luxuries", "Scrolls of the Poet Lexegez", "The work of Lexegez is said to be the finest and most accurate poetic description of the universe, with their Quanteisrion cycle, describing all of the 2,040 worlds known during their time, probably the most famous. These are first generation facsimiles, believed by the most pretentious of galactic society to be the best way to experience the poems. Most people just buy the book.",
		"IN1", "narcotics", "Stored Experience Cubes", "An extremely potent hallucinogen, which allows the recipient to experience the full sensory input of its creator. Unfortunately, the neural incompatibilities this causes are invariably fatal within seconds. The Star Bridge Biocomputing Consortium is working on a translator and reader for the cubes to try to recover their contents safely.",
		"C1", "computers", "Antique AIs", "First-hand information is the best, and so the few AIs surviving intact from centuries ago are highly sought after by historians who are willing to construct complex systems to overcome decades of protocol and hardware drift.",
		"M1", "machinery", "Perpetual Motion Machines", "The development of systems that can power themselves consistently from ambient energy sources, and so appear to be in perpetual motion, was a significant fad of the 3020s, and a wide variety of approaches ranging from zero-point energy collection to the use of tiny convection currents were perfected. None, of course, gave enough energy to do more than turn miniature clockwork assemblies, but the surviving machines are exquisite artworks in their own way.",
		"A1", "alloys", "Crystal plates", "The formation of semi-random artworks by growing crystals in rapidly-cooled alloys was the predominant rodent art form of the 3090s. Though it fell out of fashion as quickly as it had risen, some of the more aesthetically pleasing examples are still sought by the more cosmopolitan art galleries.",
		"IF1", "firearms", "Dueling Accelerators", "In the days before high-thrust inertialess drives for spacecraft were widespread, a common form of dueling was for the two ships to orbit a large gravity well, and use these mass accelerators to launch projectiles at each other. While a ship's shields would generally prevent immediate damage, the momentum transfer could potentially throw the ship towards the surface too fast to compensate - or into deep space to freeze. Nowadays they are utterly impractical as a weapon and only of interest to collectors.",
		"U1", "furs", "Dironothaxaurian Life-bones", "Bones of an unknown creature, over 40 million years old, that sing and wait for something. The predominant theory is that they are incubation pods, but if so, no-one has yet found the conditions that will make them hatch.",
		"R1", "minerals", "Celanni Remnants", "Named after the rock hermit who first discovered them, these small artefacts of unknown origin are occasionally found buried within asteroids, though never on planets. Research has so far only been able to determine that they are found throughout all eight galaxies and predate all sentient Galcop species by millions of years. The rest - including theories of alien rather than natural origin - is all speculation, and xenomineralogy labs will pay well for them."
	];

	this.scavengerCargoes = new Array();
	while (cargotypes.length > 0) {
		var cargo = this.cargoClassAntique();
		cargo.ID = "CTE_CTA_" + cargotypes.shift();
		cargo.genericType = cargotypes.shift();
		cargo.specificType = cargotypes.shift();
		cargo.desc = cargotypes.shift();
		worldScripts["CargoTypeExtension"].registerCargoType(cargo);
		this.scavengerCargoes.push(cargo.ID);
	}

	/*		var names = expandDescription("[cte_namegen]").split(";");
			for (var i=0;i<13;i++) {
					log(this.name,randomName()+" "+names[i]);
			} */

	this.scavengerLocations = new Array;

	if (missionVariables.cargotypeextension_scavenger) {
		this.unserialiseScavengers();
	} else {
		this.initialiseScavengers();
	}
}

this.playerWillSaveGame = function (savetype) {
	this.serialiseScavengers();
}

this.initialiseScavengers = function () {
	for (var i = 0; i < this.scavengerCargoes.length; i++) {
		this.scavengerLocations.push(Math.floor(Math.random() * 2048));
	}
}

this.serialiseScavengers = function () {
	missionVariables.cargotypeextension_scavenger = "1|" + this.scavengerLocations.join("|");
}

this.unserialiseScavengers = function () {
	var scavengers = missionVariables.cargotypeextension_scavenger.split("|");
	var version = scavengers.shift();
	if (version == "1") {
		this.unserialiseScavengers1(scavengers);
	} else {
		log(this.name, "Error: " + version + " is not a recognised scavenger data format");
		player.consoleMessage("Critical error decoding scavenger data. Please see Latest.log");
	}
}

this.unserialiseScavengers1 = function (scavengers) {
	this.scavengerLocations = scavengers;
}


this.cargoClassAntique = function () {
	var cargo = new Object;
	cargo.buySystems = [[], [], [], [], [], [], [], []];
	cargo.sellSystems = [[], [], [], [], [], [], [], []];
	cargo.slump = -1;
	cargo.unslump = -1;
	cargo.sourceRumour = -1;
	cargo.destinationRumour = -1;
	cargo.salvageMarket = 0.1;
	cargo.forbidExtension = 1; // extensions could give hundreds of tons too easily!
	cargo.buyAdjust = 2000;
	cargo.sellAdjust = 5000;
	return cargo;
}

this.shipWillEnterWitchspace = function () {
	if (Math.random() < 0.01) {
		this.moveScavenger(Math.floor(Math.random() * this.scavengerLocations.length));
	} else if (Math.random() < 0.2) {
		this.reportScavenger(Math.floor(Math.random() * this.scavengerLocations.length));
	}
}

this.moveScavenger = function (sid) {
	worldScripts["CargoTypeExtension"].debug("Moving Scavenger " + sid);
	do {
		var newpos = Math.floor(Math.random() * 2048);
	} while (this.scavengerLocations.indexOf(newpos) > -1); // avoid each other

	var oldpos = this.scavengerLocations[sid];
	this.scavengerLocations[sid] = newpos;

	if (Math.floor(oldpos / 256) == galaxyNumber) {
		var oldval = System.infoForSystem(galaxyNumber, oldpos % 256).name;
	} else {
		var oldval = "Chart " + (1 + (Math.floor(oldpos / 256)));
	}

	if (Math.floor(newpos / 256) == galaxyNumber) {
		var newval = System.infoForSystem(galaxyNumber, newpos % 256).name;
	} else {
		var newval = "Chart " + (1 + (Math.floor(newpos / 256)));
	}
	//		worldScripts["CargoTypeExtension"].debug(oldpos+" -> "+newpos);
	worldScripts["CargoTypeExtension"].debug(oldval + " -> " + newval);
	//		worldScripts["CargoTypeExtension"].debug(this.scavengerCargoes[sid]);
	var cargo = worldScripts["CargoTypeExtension"].cargoDefinition(this.scavengerCargoes[sid], "specificType");

	if (oldval == newval) {
		return; // moved within chart X but player not in chart X
	}
	var msg = expandDescription("[cte_scav_move]", {
		cte_scav_name: expandDescription("[cte_scav_name" + sid + "]"),
		cte_scav_cargo: cargo,
		cte_scav_oldpos: oldval,
		cte_scav_newpos: newval
	});
	//		worldScripts["CargoTypeExtension"].debug(oldval+" -> "+newval);
	worldScripts["CargoTypeExtension"].addTraderNet(msg);
}

this.reportScavenger = function (sid) {
	var oldpos = this.scavengerLocations[sid];

	if (Math.floor(oldpos / 256) == galaxyNumber) {
		var oldval = System.infoForSystem(galaxyNumber, oldpos % 256).name;
	} else {
		var oldval = "Chart " + (1 + (Math.floor(oldpos / 256)));
	}
	var cargo = worldScripts["CargoTypeExtension"].cargoDefinition(this.scavengerCargoes[sid], "specificType");
	var msg = expandDescription("[cte_scav_stay]", {
		cte_scav_name: expandDescription("[cte_scav_name" + sid + "]"),
		cte_scav_cargo: cargo,
		cte_scav_oldpos: oldval
	});
	//		worldScripts["CargoTypeExtension"].debug(oldval+" -> "+newval);
	worldScripts["CargoTypeExtension"].addTraderNet(msg);
}

this.activeScavenger = function () {
	var longsysid = (galaxyNumber * 256) + system.ID;
	if (this.scavengerLocations.indexOf(longsysid) > -1) {
		return this.scavengerLocations.indexOf(longsysid);
	}
	return -1;
}

/* API calls */

this.traderGossip = function () {
	var about = clock.days % this.scavengerLocations.length;
	var cargo = worldScripts["CargoTypeExtension"].cargoDefinition(this.scavengerCargoes[about], "specificType");

	if (Math.floor(this.scavengerLocations[about] / 256) == galaxyNumber) {
		var rumour = expandDescription("[cte_scav_rumour]", {
			cte_scav_name: expandDescription("[cte_scav_name" + about + "]"),
			cte_scav_cargo: cargo,
			cte_scav_pos: System.infoForSystem(galaxyNumber, this.scavengerLocations[about] % 256).name
		});

		return "* " + rumour;

	} else if (clock.days % 7 == 3) {
		return "* I hear someone is looking for " + cargo + " in Chart " + (1 + (Math.floor(this.scavengerLocations[about] / 256)));
	}
	return false;
}

this.describeContracts = function () {
	return "";
}

this.traderHere = function (srole) {
	if (srole != "") { // main stations only for now
		return false;
	}
	return this.activeScavenger() > -1;
}

this.traderName = function () {
	return expandDescription("[cte_scav_name" + this.activeScavenger() + "], collector");
}

this.traderDesc = function () {
	var scav = this.activeScavenger();
	var cargo = worldScripts["CargoTypeExtension"].cargoDefinition(this.scavengerCargoes[scav], "specificType");
	var msg = "Hello Trader " + player.name + ". I am " + expandDescription("[cte_scav_name" + scav + "]") + ", and I am looking for " + cargo + ". It only rarely appears on the market, and I don't have time to search the entire eight myself, so I'll pay at least one thousand credits for every genuine canister you bring me, and a bonus if you can save me time and bring me more than one at once.\n\nI don't know how long I'll be staying here - in my line of work one really has to follow up every credible lead - but if I move on I'll make sure my new location is posted.\n\nIf you find any, look for me again, and I'm sure we can make a deal.";
	return msg;
}

this.runOffer = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var scav = this.activeScavenger();

	var spec = cte.cargoDefinition(this.scavengerCargoes[scav], "specificType");
	var gen = cte.cargoDefinition(this.scavengerCargoes[scav], "genericType");

	var amount = cte.hasSpecialCargo(this.scavengerCargoes[scav]);
	if (amount == 0) {
		var msg = "I'm sorry, Trader. I'm sure the contents of your hold are fascinating to the right buyer, but I'm only looking for " + spec + ". I won't take up your time any further.";
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			overlay: "cte_tradefloor.png",
			message: msg
		}, this.leaveScavenger, this);

	} else {
		var price = Math.floor(1000 * amount * (Math.pow(amount, 0.25)));
		if (gen == "slaves" || gen == "narcotics" || gen == "firearms") {
			price *= 2; // illegal goods
		}

		var msg = "Excellent work, Trader. In exchange for the " + amount + cte.getCommodityUnit(gen) + " of " + spec + " in your hold, I will pay " + price + "₢. Do we have a deal?";
		mission.runScreen({
			screenID:"newcargoes_trading",
			title: this.traderName(),
			overlay: "cte_tradefloor.png",
			message: msg,
			choicesKey: "cte_scav_deal"
		}, this.dealScavenger, this);
	}
}

this.leaveScavenger = function () {
	worldScripts["CargoTypeExtension"].tradeFloor();
}

this.dealScavenger = function (choice) {
	if (choice == "01_DEAL") {
		var scav = this.activeScavenger();
		var gen = worldScripts["CargoTypeExtension"].cargoDefinition(this.scavengerCargoes[scav], "genericType");

		var amount = worldScripts["CargoTypeExtension"].hasSpecialCargo(this.scavengerCargoes[scav]);
		for (var i = 0; i < amount; i++) {
			worldScripts["CargoTypeExtension"].removeSpecialCargo(this.scavengerCargoes[scav]);
		}
		var price = Math.floor(1000 * amount * (Math.pow(amount, 0.25)));
		if (gen == "slaves" || gen == "narcotics" || gen == "firearms") {
			price *= 2; // illegal goods
		}

		player.credits += price;

		var msg = "A pleasure dealing with you, Trader. " + price + "₢ has been credited to your account. Contact me again if you find any more.";

	} else {
		var msg = "Your choice, but you won't find anyone else willing to pay this much. Come back when you've figured that out for yourself.";
	}
	mission.runScreen({
		screenID:"newcargoes_trading",
		title: this.traderName(),
		overlay: "cte_tradefloor.png",
		message: msg
	}, this.leaveScavenger, this);

}
Scripts/cargotypestatic.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-Base";
this.description = "Mostly-stable trade routes derived from planetinfo, Famous Planets, the galactic maps, and Oolite/Elite documentation and fiction. And some less reliable rumours ;)";

this.startUp = function () {
	// use buildcargoes.pl to regenerate these lists
	var basictypes = [
		["A1", "alloys", "Decorative alloys", "Bronze, polished Quirstone, alloys of gold and silver, and other largely decorative materials.", [[200, 182, 213, 227, 140, 113], [90, 57, 40, 188, 135, 15, 151, 45, 19, 222], [185, 230, 184], [18, 64, 138, 218, 96], [137, 112, 3, 125, 30], [], [11, 85, 206, 17, 15, 14], [76]], [[124, 54], [152, 178, 80], [124, 109, 91, 82], [34, 245], [155, 76], [238, 197], [54], [99, 101, 115]]],
		["A2", "alloys", "Ceramics", "High strength ceramics used for heat shielding for spacecraft and high-temperature worlds", [[246, 145, 165, 75, 129, 29, 176], [8, 56, 178, 188, 171, 65], [189], [169, 44], [231, 42], [152, 27, 155, 46], [], [85, 198, 188, 2]], [[124, 251, 53, 194, 139, 220, 153, 4, 93, 116, 175, 126], [71, 111, 107, 109, 243, 136, 24, 113], [46, 202], [128, 252, 161, 182, 228, 75, 227], [163, 81, 201, 148, 17], [35, 245], [101, 94, 91, 86], [25, 252, 71, 7, 157, 65]]],
		["A3", "alloys", "Duralium", "The primary alloy used in the construction of spaceship hulls.", [[170, 127, 32, 7, 227, 2, 166, 88, 240, 128, 108, 103, 145, 49, 35, 152, 131, 53, 91, 48, 79, 174, 219, 0, 106, 96, 149, 6, 159, 94, 199, 9, 41, 12, 226, 8, 73, 76, 54], [170, 68, 208, 26, 143, 79, 158, 212, 244, 46, 16, 100, 222, 82, 240, 238, 233, 39, 36, 57, 138, 58, 202, 153, 52, 73], [206, 142, 158, 174, 222, 126, 238, 252, 233, 190, 28, 39, 156, 9, 199, 249, 12, 109, 103, 172, 173, 45], [238, 6, 233, 241, 198, 17, 22, 209, 201, 46, 49, 214, 30, 169, 222], [130, 93, 219, 125, 157, 55, 147, 238, 251, 233, 49, 73, 66, 151, 232, 86, 76, 247], [189, 130, 42, 1, 93, 96, 65, 74, 120, 194, 64, 156, 138, 220, 37, 19, 31, 5, 150], [237, 143, 79, 48, 106, 186, 74, 128, 221, 185, 250, 138, 111, 41, 218, 14, 137, 254, 89, 175], [33, 225, 204, 139, 227, 129, 68, 42, 141, 74, 203, 190, 57, 94, 183, 146, 249, 153, 109, 4, 173, 56, 89, 247]], [[33, 80, 188, 193, 158, 154, 125, 141, 100, 39, 120, 210, 250, 220, 249, 15, 109, 24, 175, 150], [127, 33, 227, 48, 106, 105, 96, 251, 182, 185, 248, 236, 135, 243, 24, 150], [204, 21, 165, 245, 139, 26, 17, 205, 42, 99, 84, 251, 36, 97, 52, 31, 247, 5, 223], [237, 221, 39, 57, 188, 2, 254, 24, 79, 103, 34, 186, 66, 86, 140, 110], [152, 128, 190, 204, 71, 57, 198, 102, 249, 153, 88, 144, 29, 222], [6, 11, 85, 53, 227, 208, 146, 129, 51, 8, 60, 151, 66], [35, 127, 234, 154, 214, 23, 55, 203, 161, 39, 64, 202, 231, 24, 73, 140], [26, 180, 212, 87, 174, 116, 136, 55, 251, 161, 75, 156, 9, 218, 184, 98, 145, 31, 171]]],
		["A4", "alloys", "Heavy plastics", "Heavy plastics used as a substitute for metals where typical metal properties are unwanted.", [[233, 50, 141, 213, 41], [127, 82], [191, 68], [255, 224, 195], [219, 229, 107], [123, 229, 196, 58], [52, 62], []], [[124, 250, 188, 23], [209, 106, 40, 178, 236, 14], [6, 36, 43], [159], [40, 115], [40, 144], [119, 215, 131, 225, 7, 167], []]],
		["A5", "alloys", "Solar shielding", "A thick glass, providing protection from solar flares and other solar activity.", [[254, 99, 196, 54], [246, 231, 66], [], [2, 220], [230, 135], [152, 81], [149, 1, 88, 53, 71, 247], [244, 219]], [[84, 246, 131, 127, 91, 41, 143, 20, 243, 212, 216, 132, 247, 195], [128, 67, 28, 40, 36, 227, 109, 1, 244, 18, 232, 89, 101, 175, 117], [206, 70, 198, 1, 72, 16, 126, 147, 61, 94, 114, 230, 184, 243, 231, 112, 101, 62], [239, 129, 22, 214, 133, 82, 74, 238, 190, 251, 9, 52, 164, 232, 187, 113], [197, 159, 6, 251, 131, 130, 67, 142, 250, 199, 91, 78, 22, 125, 133], [33, 7, 167, 78, 22, 252, 161, 221, 185, 253, 94, 60, 101, 117, 140, 54, 242, 247], [123, 84, 252, 221, 190, 127, 182, 206, 80, 167, 48, 20, 254, 145, 162, 133], [6, 127, 134, 118, 36, 253, 146, 249, 12, 47, 60, 46, 73, 89, 43]]],
		["C1", "computers", "AIs ", "State-of-the-art hardware and software for automation of ship systems.", [[246, 33, 250, 79, 8, 163, 98, 174, 173, 219, 151, 23], [231, 98, 39, 36, 66], [69, 42, 38, 36, 79, 54], [197, 141], [99, 39, 70, 198, 249, 126], [149, 6, 50, 204, 40, 144], [6, 53, 71, 184, 119, 87, 103, 66, 229], [251]], [[39, 210, 141, 100], [251, 185, 24, 105], [52, 99, 97, 5], [34, 2, 247], [128, 88, 55], [11], [203, 39, 73], [180, 161, 145, 171, 31]]],
		["C2", "computers", "Earthquake predictors", "The Diso Digital OO32000 processor and sensor array, or a similar product from a less well-known manufacturer", [[205, 147, 203, 67, 182, 214, 227], [130, 127, 248, 108, 202, 109, 98, 172, 93, 151, 62], [84, 252, 33, 115, 38, 116, 37, 125, 132, 224], [138, 188, 167, 103, 49, 30, 13, 23, 117], [217, 182, 120, 28, 185, 220, 178, 43, 105, 126], [163, 104, 250, 9], [35, 181, 39, 139, 51, 218, 69, 215, 229], [128, 161, 102, 160, 218, 78, 231, 125]], [[39, 134, 97, 26, 78, 231, 137, 18, 34, 72, 24, 140, 133], [159, 104, 50, 32, 217, 63, 139, 208, 26, 196, 222], [159, 27, 221, 217, 57, 239, 108, 168, 99, 88, 136, 157, 19, 235, 110], [21, 64, 160, 20, 219, 56, 106, 177, 96, 171], [149, 71, 36, 129, 26, 218, 119, 72, 229, 177, 30], [234, 118, 248, 230, 215, 116, 214, 255, 157, 110], [124, 25, 142, 213, 188, 114, 249, 58, 212, 4, 244, 196, 10, 19, 43], [238, 33, 11, 95, 40, 239, 193, 176, 4, 116, 162, 144, 10]]],
		["C3", "computers", "NavCon electronics", "Computers and databases for in-system and witchspace navigation.", [[123, 85, 210, 7, 168, 153, 8, 52, 158, 212, 174, 219, 232, 76], [246, 85, 39, 36, 146, 114, 68, 167, 52, 215, 212, 209, 191, 244, 100], [147, 6, 131, 63, 40, 227, 184, 22, 99, 179, 56, 10], [104, 107, 99, 136, 219, 144, 147, 123, 203, 85, 120, 3, 36, 75, 192, 115, 184, 24, 117, 76, 10, 195], [159, 128, 90, 95, 64, 250, 230, 220, 218, 12, 135, 69, 191, 0, 229, 89, 133], [11, 210, 139, 102, 68, 184, 107, 69, 254, 200, 177, 43, 62, 222], [155, 11, 36, 91, 68, 244, 219, 132, 148, 235, 187], [241, 118, 239, 7, 188, 2, 143, 48, 200, 180, 244, 144, 125, 16, 82, 246, 147, 182, 194, 83, 111, 226, 218, 41, 137, 34, 145, 229, 86, 187]], [[33, 39, 250, 141], [170, 127, 33, 227, 48, 106, 96, 82, 251, 182, 185, 248, 236, 202, 135, 243, 24, 150], [204, 21, 165, 245, 139, 26, 17, 205, 42, 84, 251, 36, 97, 52, 31, 247, 5, 223], [237, 188, 2, 186, 110, 221, 39, 57, 9, 103, 254, 34, 49, 66, 86, 140, 247], [152, 233, 190, 204, 71, 57, 198, 102, 249, 153, 88, 73, 144, 29, 222, 55], [6, 120, 85, 53, 227, 208, 146, 129, 51, 220, 8, 60, 151, 66], [35, 127, 234, 154, 214, 23, 55, 203, 161, 39, 64, 250, 202, 231, 24, 73, 140], [26, 212, 87, 174, 116, 136, 55, 251, 161, 75, 156, 9, 184, 98, 31, 171]]],
		["C4", "computers", "Remote presence systems", "A variety of televisual and robotic remote presence systems, for those with an ingrained aversion to physical company.", [[104, 59, 95], [243, 59, 233, 161, 217, 207], [35, 114, 121], [4, 225, 182, 19, 202, 110], [104, 84, 69, 98, 239], [165, 229], [59, 212, 28, 56, 61, 196, 176], [166, 64, 148, 26]], [[173, 136, 63, 235], [229, 79], [206, 133, 249, 20], [], [123, 74, 8], [73], [197, 81, 173, 24], [162]]],
		["C5", "computers", "Weather control processors", "Sophisticated weather forecast and control computers used to coordinate planet-wide weather adjustments", [[35, 207, 3, 8, 254, 99, 49, 89, 141, 82, 195], [109, 127, 103, 106, 24, 82], [149, 155, 116, 34, 129, 43, 26, 58, 17, 223], [137, 50, 220, 58, 29], [240, 212, 156, 226, 2], [240, 109, 86, 20], [240, 64, 199, 231, 215, 136, 13, 247, 195], [161, 93, 232, 31]], [[28, 129, 15], [209, 88, 192, 202, 115], [22, 253, 148, 235], [254, 4], [95], [226], [67, 230, 148, 19], [25, 155, 28, 56, 143]]],
		["F1", "food", "Bananas", "The ancient Terran delicacy can only be made to grow on a few of the colonies, and is much prized by gourmets.", [[87, 156], [42, 150], [29, 223], [], [95, 55], [129], [32, 36, 126], []], [[40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["F2", "food", "Edible Grubs", "While most worlds have some sort of strictly edible grub, most are bland and tasteless. A few, like those of Ermaso, have an astonishing range of flavours.", [[85, 185, 0], [70], [204], [], [113, 48], [], [209, 234, 66, 129], [97, 135]], [[156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["F3", "food", "Edible Moth", "The rare edible moth is subject to stringent export controls to preserve its numbers. While an acquired taste, it is immensely popular with the discerning eater.", [[9], [86, 160, 171], [200, 210, 151], [147, 24, 255, 62, 153], [232, 14], [67, 61, 111], [158, 152, 81, 192, 140], [37]], [[156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["F5", "food", "Fish meat", "The difficulties in trading in fish are not the taste or interplanetary toxicity, which to the rich diner are mere trifles to be circumvented, but the need to transport it quickly before it rots. The meats of Inera and Zasoceat are particularly desired.", [[221, 33], [39, 182], [21, 179, 66, 7, 10], [43], [25], [243, 118, 3, 171], [203, 145, 138, 249], [106, 49, 114]], [[156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["F6", "food", "Goat Meat", "As the Aronar Deadly Goat shows, there is a significant trade-off between being able to kill the animal at all, and leaving it sufficiently intact that its meat is salvageable. Preparing the meat generally requires special tools.", [[73, 45, 79], [183], [246, 8], [51], [156, 79], [60, 192, 19, 167], [65], []], [[21, 156, 40, 138, 236, 160, 17, 200, 77, 46, 49, 232, 164, 105, 150], [251, 90, 236, 199, 193, 115, 79, 66, 43, 96, 126], [149, 203, 50, 165, 51, 208, 188, 122, 218, 52, 34, 145, 46, 76, 29, 133], [189, 250, 183, 208, 236, 78, 212, 141, 195], [246, 238, 206, 146, 115, 12, 166, 172, 106, 45, 186, 10, 23], [127, 204, 53, 83, 208, 12, 137, 205, 232, 86], [155, 204, 134, 253, 208, 143, 38, 109, 49, 72, 144], [104, 84, 120, 185, 139, 61, 111, 129, 41, 174, 222, 242]]],
		["F7", "food", "Goat Soup", "A popular avian delicacy, this is often made from extra-planetary goat meat for added variety.", [[201, 77, 21, 164, 236], [199, 193, 96, 115], [165, 76, 51], [236, 195], [115, 12], [127, 86], [49], [139, 111]], [[163, 85, 185, 151, 122, 105, 79], [238, 64, 57, 49, 208, 78, 58, 184], [152, 33, 90, 167, 22, 244, 88, 186, 125, 74, 203, 161, 85, 40, 253, 213, 138, 218, 153, 184, 52, 56, 150], [246, 182, 181, 53, 118, 70, 198, 133, 5, 54], [246, 159, 84, 189, 95, 122, 111, 9, 172, 175, 62], [149, 155, 70, 3, 183, 68, 168, 12, 4, 23, 76, 195, 150, 5], [32, 198, 227, 121, 158, 42, 147, 120, 194, 138, 199, 226, 20, 153, 137, 38, 209], [170, 7, 121, 255, 100, 238, 221, 182, 250, 61, 213, 14, 15, 81, 38, 98, 173, 56, 187, 195]]],
		["F8", "food", "Shrew meat", "Finding a way to make shrew meat edible is a major challenge. Only a few systems have so far succeeded, and their produce is in high demand at the fashionable restaurants. Trade in Shrew products is generally forbidden on Rodent worlds.", [[243], [33, 82, 5], [1, 45, 249, 105], [237, 88, 62, 17, 12, 153], [27, 6], [183], [25, 254, 66, 117], [35, 6]], [[156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["F9", "food", "Tree Grubs", "While no more naturally interesting in flavour than their edible counterparts, a tree grub will soak up chemicals from the wood it feeds on, and so makes excellent seasoning if dried and ground.", [[240, 155, 11, 7], [38, 64, 91, 133, 41], [85, 72, 36, 196, 242, 55], [104, 198], [251, 188, 157, 111, 140], [75, 248, 183, 78, 46, 30, 177, 223], [251, 234, 248, 43], [243, 191, 145, 164, 140, 115, 126]], [[228, 156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["F10", "food", "Wolf meat", "A particular speciality of Leriteanese cuisine, now gaining in popularity across the charts. Leritean itself has no wolves, and so the meat has to be imported at great expense.", [[128, 92, 139, 150], [74], [181], [237, 165, 40, 10, 167], [254, 203, 207, 95], [163, 69, 67, 173, 169, 23, 58, 242], [152, 42, 241, 10], [229, 7, 168, 153]], [[156, 40, 138, 160, 17, 46, 49, 232, 105], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["F11", "food", "Edible Arthropoid", "Creatures like the Vezadian Edible Aroid, while strictly edible, are generally highly toxic to most species unless properly cooked. This makes them a highly exclusive meal.", [[], [201, 21], [], [158], [50], [], [], [112, 27]], [[156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["U1", "furs", "Feline fur", "A warm fur generally taken from sub-sentient animals such as the Esusti spotted cat. Fur from sentient felines does find its way on to the black market, and the Feline Assembly has been lobbying Galcop to outlaw the trade entirely. While this has not yet been successful, import and export are generally illegal on Feline worlds.", [[62, 135], [39, 235, 62], [163, 109, 189, 134, 94], [205, 181, 56, 102, 122, 10, 235], [163, 245, 102, 195], [204, 72, 97], [], [109, 131, 125, 143]], [[170, 200, 89, 13, 150], [25, 57, 253, 23, 160], [25, 200, 253, 160, 62], [35, 229, 153], [35, 234, 253, 108, 48, 145, 201, 24, 157], [], [92, 244, 234, 134, 56, 192, 193], [67, 210, 70, 79, 166, 66, 73, 144, 223, 150]]],
		["U2", "furs", "Gold-flake reptile scales", "Shiny, decorative, but not of much practical value, these scales are often sewn in to high-class clothing.", [[195], [82], [137], [231, 114], [158], [], [205], [234, 23]], [[124, 21, 46, 249], [127, 224], [84, 198, 31], [175], [239], [22, 227, 242], [162], [142, 0, 73, 184]]],
		["U3", "furs", "High-toughness Leather", "Almost every planet produces some sort of leather, but a few planets have animals which through quirks of genetics and environment have hides comparable in resilience to tough industrial alloys.", [[73, 188], [119, 87, 57, 46, 192, 51, 115, 184], [103, 193], [53, 248, 216], [15], [166, 136], [118, 187], [172, 141, 230, 105]], [[145, 29], [152, 67, 101, 80, 14], [152, 189, 130, 242], [149, 57], [155, 76], [238, 197], [54], [99, 131, 63, 154, 126]]],
		["U4", "furs", "Shrew fur", "A simple patterned fur often used for accessories. Trade in Shrew products is generally forbidden on Rodent worlds.", [[243, 142, 220, 135], [238, 204, 177, 54, 143], [87, 18, 148, 171, 5], [152, 7, 175], [145, 101, 213, 102], [201, 68, 2, 31, 107], [116], [186, 255]], [[124, 204, 21, 46, 17, 249], [127, 224], [84, 198, 31], [], [239], [22, 227, 242], [162], [142, 0, 73, 184]]],
		["U5", "furs", "Snake skin", "The skin of the Leonedian or Israzaian tree snake is favoured by ship manufacturers for use on premium seat covers. After the Xerirea incident, many Lizard worlds required that traders in this good obtain full permits and proofs of authenticity.", [[97], [155, 174, 206, 70, 223], [246, 238, 51, 135], [152, 88, 17], [27, 249], [232, 29], [189, 179, 250, 117, 16, 143], [142, 235, 135]], [[33, 39, 210, 250, 141, 100], [251, 39, 185, 24, 105], [52, 42, 99, 36, 97, 5], [34, 2], [128, 88, 55], [11], [203, 39, 73], [180, 161, 145, 171, 31]]],
		["U6", "furs", "Wolf fur", "A very warm fur from the wolves and wolf-like creatures of the chart, especially prized by the ice planets.", [[22, 34, 185, 230, 249, 247, 47], [170, 74, 203, 230, 160, 12, 73, 177, 140, 195], [138, 160, 48, 14, 243, 215, 77, 144, 31, 105, 195], [126], [60, 130, 118, 198], [212, 117], [149, 88, 208, 129, 51, 105, 195], [243]], [[170, 200, 89, 13, 227, 150], [25, 221, 200, 57, 253, 23, 62], [25, 200, 253, 62], [35, 233, 229, 153], [35, 234, 253, 108, 48, 145, 201, 179, 24, 157], [162, 224], [92, 81, 244, 234, 134, 56, 192, 193], [35, 67, 210, 70, 193, 79, 112, 166, 66, 73, 144, 133, 223, 150]]],
		["L1", "liquor_wines", "Brandy", "In the varied high-tech world of intergalactic drinking, old traditional drinks like brandy almost disappeared. A change in fashion has led to plainer brandies from systems like Tibedied and Isbeus being in high demand at the hot nightspots of the sector.", [[0], [215, 77, 234, 228, 51, 171], [219, 56, 198, 30, 227, 108, 65], [158, 101, 242, 15], [237, 59, 42, 103, 126], [152, 119, 37, 188], [237, 57, 176], [112, 22, 1, 118, 46]], [[42, 239, 232, 111, 26], [114, 91, 26, 167, 211, 14, 109, 201, 216, 144, 178], [203, 154, 28, 75, 70, 151, 37, 167, 153], [173, 154, 63, 183, 177, 115], [209, 166, 32, 70, 141, 47], [185, 66, 249, 82], [240, 128, 38, 251, 144, 125], [93, 138, 9, 223]]],
		["L2", "liquor_wines", "Evil juice", "Call it evil juice, call it killer juice, every planet makes some form of this intoxicating beverage. Few are sufficiently interesting to be worth transporting out of their system of origin, however.", [[215, 217, 136, 55], [250, 214], [231, 170, 78], [172, 201, 187], [84, 25, 190, 161, 88, 134, 3, 230], [84], [112, 87, 160, 20], [212, 16]], [[42, 239, 232, 111, 26], [114, 91, 26, 167, 211, 14, 109, 215, 201, 216, 144, 178], [203, 154, 28, 75, 70, 151, 37, 167, 153], [173, 154, 63, 183, 177, 115, 242], [209, 166, 103, 32, 70, 141, 47], [185, 66, 37, 249, 82], [240, 128, 38, 6, 251, 144, 125], [93, 138, 9, 223]]],
		["L3", "liquor_wines", "Gargle Blasters", "Serve with lemon and a brick.", [[24, 76, 121], [63], [187], [119, 71], [183, 97, 7, 218], [159, 227], [27, 172, 36, 168], [124, 206, 106, 49, 122, 55]], [[42, 239, 232, 111, 26], [114, 91, 26, 167, 211, 14, 109, 215, 201, 216, 144, 178], [203, 154, 28, 75, 70, 151, 37, 167, 153], [173, 154, 63, 183, 177, 115, 242], [209, 166, 103, 32, 70, 141, 47], [185, 66, 37, 249, 82], [240, 128, 38, 251, 144, 125], [93, 138, 9, 223]]],
		["L4", "liquor_wines", "Lethal brandy", "One of the most popular drinks throughout the charts, lethal brandy's appeal is further boosted by its effects on humanoid and reptilian drinkers, enabling them to tolerate harsh environments for longer.", [[181, 248, 218, 153, 14, 119, 16, 100, 171], [197, 27, 116, 216, 178, 230], [219, 56, 198, 30, 227, 108, 65], [101, 242, 15], [237, 59, 42, 103, 126], [149, 142, 213, 66], [98, 125], [52, 241, 68, 16, 31]], [[170, 50, 239, 198, 227, 111, 26, 8, 42, 200, 232, 89, 13, 150], [91, 26, 167, 200, 136, 88, 144, 23, 25, 221, 57, 253, 192, 160, 114, 211, 14, 109, 215, 209, 201, 45, 62], [25, 203, 28, 75, 70, 253, 160, 167, 153, 22, 200, 154, 37, 151, 148, 62, 235], [35, 233, 63, 183, 115, 153, 154, 173, 177, 229], [35, 32, 234, 70, 48, 166, 179, 141, 157, 95, 253, 108, 47, 209, 145, 201, 24], [185, 226, 249, 162, 37, 117, 224, 82], [67, 234, 193, 244, 144, 128, 240, 251, 134, 192, 230, 92, 81, 38, 56, 148, 19], [35, 67, 155, 70, 193, 79, 143, 166, 93, 144, 133, 25, 28, 210, 138, 9, 112, 56, 66, 73, 223, 150]]],
		["L5", "liquor_wines", "Vicious brew", "Drinks that fought back were briefly popular a few centuries ago, but nowadays are rarely consumed. Only a few planets still produce this.", [[128, 60, 10, 193], [238, 182, 144, 193, 168], [75, 47], [215, 12, 107], [59, 254, 79], [38, 125], [131, 132, 255, 47], [35, 154, 53, 121]], [[42, 239, 232, 111, 26], [114, 91, 26, 167, 211, 14, 109, 215, 201, 216, 178], [203, 154, 28, 70, 151, 37, 167, 153], [173, 154, 63, 183, 177, 115, 242], [209, 166, 103, 32, 70, 141, 47], [185, 66, 37, 249, 82], [240, 128, 38, 251, 144, 125], [93, 138, 9, 223]]],
		["X1", "luxuries", "Entertainment systems", "Top-quality in-flight or home entertainment systems. Note that a few systems have banned the import of these devices, either because of concerns of corrupting effects on the people, or simply because they can't stand the programming.", [[28, 153], [61, 236, 140, 20], [59, 182, 234, 10, 195, 48], [246, 53, 249, 58, 48], [46, 247], [209, 87, 57, 239, 68, 193], [241, 83, 245, 78], [66, 208, 20]], [[63, 7, 224, 108], [137, 84, 145, 219, 73, 255, 47], [166, 206, 141, 80, 133, 126], [74, 191, 120, 165, 162, 71, 176], [183, 108, 153], [33, 130, 182, 97, 41, 109, 99, 88, 171, 150], [92, 6, 219, 198, 105, 113, 14], [92, 162, 178, 94, 89, 146]]],
		["X2", "luxuries", "Fine clothing", "Obtaining mere high quality fashionable outfits is straightforward in these days of intersystem trade. Those at the top of society naturally desire more. Clothing that uses nano-adjustments to tailor itself to you is lazy. The best outfitters make those adjustments by hand, to cater to the whims of their filthy rich clientele.", [[21, 46, 249], [127, 224], [84, 198, 31], [175], [239], [22, 227, 242], [162], [142, 0, 73, 184]], [[246, 33, 210, 7, 250, 168, 153, 158, 151, 232, 23], [246, 39, 146, 114, 167, 231, 215, 98, 191, 209, 66], [6, 63, 36, 79, 69, 22, 38, 42, 10, 5, 54], [197, 85, 36, 141, 117, 76, 10, 2], [39, 70, 198, 230, 249, 220, 12, 135, 99, 89, 126], [149, 6, 50, 204, 210, 40, 102, 184, 69, 200, 144, 177], [225, 181, 53, 241, 71, 7, 119, 87, 88, 23, 82, 55, 6, 149, 39, 3, 184, 103, 229, 66, 86], [241, 7, 188, 2, 200, 180, 244, 125, 82, 147, 251, 83, 194, 226, 41, 218, 137, 145, 34, 187]]],
		["X3", "luxuries", "Masks", "The Tionislan solution to their shyness problem has been adopted to some extents by other naturally aloof species. The sharing of mask designs and mechanisms is a profitable niche market.", [[124], [35, 233], [208], [124, 189, 39, 95, 232, 125, 78], [205, 189, 138, 58, 223, 135], [108], [74, 180, 182, 56, 24, 108, 167], [124, 181, 174, 151, 76]], [[104, 59, 95], [67, 110], [249, 20], [], [123, 74, 8], [73], [197, 81, 173], []]],
		["X4", "luxuries", "Poetry", "While the majority of poets in the galaxy are more renowned for their taste than their literacy, the rarefied mountain atmosphere of a few worlds gives the right environment for poetry with superb emotional resonance.", [[137, 142], [11, 117], [], [], [20], [121], [133], [116]], [[124, 246, 33, 210, 7, 250, 168, 153, 158, 151, 232, 23], [246, 39, 146, 114, 167, 231, 215, 98, 191, 209, 66], [63, 227, 79, 42, 22, 186, 84, 6, 64, 36, 41, 20, 38, 69, 10, 31, 5, 54], [197, 128, 85, 36, 2, 141, 76, 10, 117], [159, 74, 39, 70, 198, 230, 249, 220, 12, 135, 99, 89, 126], [149, 6, 50, 204, 210, 40, 102, 184, 69, 200, 144, 177], [225, 181, 53, 241, 71, 7, 119, 87, 88, 23, 82, 55, 197, 6, 149, 39, 3, 184, 103, 229, 66, 86], [241, 7, 188, 26, 2, 200, 180, 212, 244, 125, 82, 147, 251, 194, 83, 226, 218, 41, 137, 34, 145, 171, 187]]],
		["X5", "luxuries", "Vacuum Cricket Bats", "Ribiso in Chart 1 invented vacuum cricket, and for many centuries was the undisputed galactic champion. Their recent decision to allow the rules to be translated and the kit exported has led to a few of the richer systems putting together their own teams, and for the first time Ribiso's lead is no longer a foregone conclusion.", [[115], [], [], [], [], [], [], []], [[35, 33, 36, 3, 7, 26, 17, 12, 42, 8, 1, 23, 5], [237, 60, 140], [, 218, 2], [119, 60], [212, 34, 21, 213], [149, 69], [49, 177], [110]]],
		["X6", "luxuries", "Vacuum karate training videos", "Vacuum Karate is a rare martial art, and those wishing to learn must travel across the galaxy - and often between galaxies - to visit its masters. For the slightly less rich, these training videos fetch a good price.", [[126], [], [], [], [39], [48], [], []], [[67, 241, 58, 107, 79, 42, 99, 98, 49, 37, 45, 43, 100, 82, 5], [81, 146, 226], [198, 76], [209, 36], [152, 40, 138, 115, 202, 201, 116, 141], [50, 33, 248, 227, 17, 184, 109, 1, 86], [216, 82], [232]]],
		["X7", "luxuries", "Wooden furniture", "Thick woody plants are relatively rare in most systems, and that's not counting the 'trees' that are actually giant grubs. The production of furniture from real wood, especially in these days of cheap synthetics, is a major expense.", [[11, 225, 70, 7, 188, 143, 1, 244, 110, 25, 161, 61, 12, 218, 249, 184, 215, 151], [7, 122, 42, 100, 74, 149, 221, 28, 64, 95, 194, 138, 111, 146, 38, 175, 224], [130, 50, 71, 64, 239, 192, 97, 9, 158, 209, 173, 255, 13, 140], [6, 11, 57, 36, 108, 41, 137, 201, 255, 31, 126], [197, 104, 32, 207, 198, 160, 51, 14, 88, 116, 73, 89], [130, 213, 111, 20, 200, 244, 93, 140, 113, 222], [190, 189, 217, 134, 97, 184, 137, 99, 46, 76, 150], [120, 39, 64, 3, 230, 42, 92, 254, 180, 166, 1, 164, 65, 242]], [[246, 33, 210, 250, 168, 153, 158, 232, 23], [246, 39, 114, 167, 231, 98, 191, 209, 215, 66], [6, 63, 36, 79, 69, 22, 38, 42, 10, 5, 54], [197, 85, 141, 117, 76, 10, 2], [39, 70, 230, 249, 220, 12, 135, 99, 126], [149, 6, 50, 204, 210, 40, 102, 184, 144, 177], [225, 181, 53, 241, 71, 7, 119, 87, 88, 23, 82, 55, 6, 149, 39, 3, 103, 229, 66, 86], [241, 7, 188, 2, 200, 244, 125, 82, 147, 251, 83, 194, 41, 218, 226, 137, 145, 34, 187]]],
		["X8", "luxuries", "Zero-G cricket balls", "Zero-G cricket is a popular sport across the galaxies, though few systems have a team good enough to make the major leagues. Less skilled systems often have to make do with second-rate equipment, so a shipment of top-quality balls can fetch a high price.", [[237, 104, 99, 19, 195, 55], [81, 189, 36, 176], [209, 173, 165, 97, 188, 47], [215, 15], [246, 42, 97, 7], [205, 137, 123], [6, 26, 2], [148, 68, 167, 133]], [[131, 130, 42, 158, 77, 72, 125, 141, 110, 85, 250, 249, 135, 153, 163, 254, 151, 5], [231, 22, 209, 191, 127, 1, 41], [155, 225, 234, 245, 129, 79, 87, 116, 144, 125, 157, 149, 246, 84, 194, 228, 250, 230, 220, 226, 101, 175, 242, 223, 113], [137, 130, 103, 85, 134, 132, 101, 126, 113], [, 118, 194, 198, 249, 2, 1, 254, 66, 44], [240, 204, 165, 250, 91, 115, 2, 60, 18, 34], [225, 165, 71, 183, 61, 119, 87, 172, 136, 10, 247, 195], [147, 221, 206, 41, 14, 59, 88, 244, 169, 113]]],
		["X9", "luxuries", "Zero-G hockey sticks", "Consistent regulation of Zero-G hockey collapsed during the Esveor Embargo, and technological boosts to player skill became commonplace. While the League was able to re-regulate the sport afterwards, many of the innovations were kept as fan favourites. The top sticks are no mere piece of metal now - they often contain more sophisticated targeting systems than the average Mamba.", [[172, 95, 114, 44], [32, 228, 160], [30, 100, 2, 150], [124, 209, 19], [152, 138, 24, 175], [225], [194, 157, 184], [250, 151, 160]], [[207, 198, 42, 200, 235, 240, 246, 221, 218, 153, 184, 209, 178, 171, 195, 5, 113, 150], [128, 189, 136, 122, 10, 16, 242], [7, 26, 17, 205, 42, 22, 18, 214, 13, 55, 6, 50, 210, 75, 64, 36, 199, 202, 58, 38, 34, 10, 43, 31, 5, 54], [, 50, 63, 142, 138, 146, 58, 46, 141, 45, 148, 76, 65, 54], [74, 246, 70, 3, 245, 188, 184, 8, 126], [6, 208, 20, 38, 69, 99, 116, 169, 242], [53, 28, 64, 3, 230, 199, 122, 167, 141, 13, 76, 96], [251, 181, 210, 122, 17, 231, 93, 0, 73, 43]]],
		["X10", "luxuries", "Mud tennis rackets", "One of the more unusual sports in Galcop space is mud tennis. Invented, and most commonly played, in Chart 2, it has its devotees throughout the eight.", [[], [35, 165, 78, 135], [], [213, 247, 195], [243, 112, 75], [52, 27, 127, 10, 193], [17, 14], [39, 111]], [[98, 198, 17, 5], [246, 63, 40, 253, 198, 218, 98, 106, 66], [231, 251], [159, 25, 41, 38, 42, 34, 154, 30, 29, 150], [240, 130, 11, 239, 80, 173, 162, 89, 10], [118, 40, 102, 12, 243, 22, 172, 219, 73], [6, 131, 97, 188, 168, 231, 215, 178, 37, 66, 151, 16, 54], [, 159, 53, 75, 184, 8, 137, 98, 216, 31]]],
		["X11", "luxuries", "Mud hockey pucks", "The swamp worlds enjoy a game of hockey as much as anyone, but need special pucks to cut through the dense mud. A few other systems are taking up the game, finding it more comprehensible and strategic than the more common zero-G variation", [[], [159], [], [90, 196], [232, 65], [230, 105], [4], [218]], [[33, 161, 151, 5], [57, 129, 92, 103, 125, 224, 255, 82], [112, 124], [197, , 189, 225, 185, 245, 7, 220, 15, 215, 172, 244, 175], [137, 99, 98, 215, 156, 19, 226, 12, 153], [104, 182, 28, 245, 226, 14, 77, 49, 144], [240, 170, 189, 67, 181, 39, 7, 135, 103, 162, 23, 86, 55], [238, 161, 142, 56, 40, 192, 13, 82]]],
		["M1", "machinery", "Premium air filters", "Whether it's to keep heavy industry clean or to protect visitors from the native air, most systems need air filters. A few have a need for more sophisticated filters than they can produce themselves, however, and a profitable trade in spare parts has emerged.", [[53, 36, 139, 220, 153, 4, 99, 175], [103, 16], [], [161, 228, 227], [17], [35], [101, 86], [7, 157, 65]], [[251, 116, 194, 169, 250, 177, 86, 29, 126], [243, 136, 71, 157, 111, 249, 113, 107], [46, 202], [128, 252, 182, 75], [163, 81, 201, 148], [245], [0, 94, 91], [25, 252, 71]]],
		["M2", "machinery", "Boats", "From the smallest canoe to the largest ocean liner, the inability of most species to swim long distances creates a natural market on oceanic worlds. The inability of most space-born sentients to understand 2-dimensional sailing means that replacements are always necessary.", [[99, 28, 164, 148, 153], [25, 6, 200, 127, 195], [59, 206, 125, 43, 193, 115, 58], [216, 224, 148, 247], [93, 194, 61, 255, 65, 211], [182, 90, 168, 153, 69, 243, 212, 34, 247], [139, 23, 195], [149, 225, 139, 94, 58, 87, 229, 117, 44]], [[74, 42, 172, 154, 56, 102, 68, 29], [203, 160, 114, 115, 2, 22, 137, 154, 31], [215], [130, 179, 192, 178, 16], [71, 187, 62], [173, 77, 72, 26, 41], [42, 130, 165, 213, 61, 29], [221, 60, 199]]],
		["M3", "machinery", "Factory Equipment", "A range of high-tech industrial parts for use in mass production.", [[246, 33, 210, 250, 168, 153, 158, 151, 232, 23], [246, 39, 146, 114, 167, 231, 215, 98, 191, 209, 66], [6, 63, 36, 79, 69, 22, 38, 42, 10, 5, 54], [197, 85, 36, 141, 117, 76, 10, 2], [39, 70, 198, 230, 249, 220, 12, 135, 99, 89, 126], [149, 6, 50, 204, 210, 40, 102, 184, 69, 200, 144, 177], [225, 181, 53, 241, 71, 7, 119, 87, 88, 23, 82, 55, 6, 149, 39, 3, 184, 103, 229, 66, 86], [241, 7, 188, 2, 200, 180, 244, 125, 82, 147, 251, 83, 194, 226, 41, 218, 137, 145, 34, 187]], [[149, 233, 134, 245, 213, 61, 91, 78, 212, 219, 255, 110], [, 84, 32, 201, 229, 148, 196, 5], [25, 67, 40, 3, 108, 78, 200, 56, 179, 62], [152, 104, 161, 155, 32, 11, 3, 80, 51, 121, 0, 19, 43, 187], [159, 128, 90, 241, 160, 218, 14, 154, 132, 255], [121, 4, 254, 0, 106, 125, 196, 171], [203, 207, 239, 68, 9, 20, 52, 59, 116, 169, 110], [197, 234, 144, 175, 23, 195, 79, 15]]],
		["M4", "machinery", "Farming Equipment", "Super-high quality farming equipment, produced by the few agricultural systems to have a strong enough technological base. As the farmers say, would you trust a laser plough made by someone who'd never seen a field?", [[158, 161, 114, 111], [152, 210, 71, 66, 17, 135], [181, 49, 111], [69, 13], [190, 28, 134, 75, 56], [21], [8, 33, 210, 245], [209]], [[63, 21, 244, 116, 30, 44, 74, 84, 228, 95, 192, 236, 59, 191, 224, 10, 187, 223, 189, 181, 142, 167, 174, 46, 157, 123, 251, 85, 160, 38, 52, 137, 209, 34, 101, 169], [21, 206, 180, 179, 72, 74, 27, 190, 20, 243, 89, 175, 11, 225, 91, 48, 107, 174, 77, 29, 197, 149, 203, 137, 56, 19, 165, 139, 68, 88, 116, 30, 110, 252, 83, 228, 192, 236, 14, 59, 69, 224, 187, 155, 53, 181, 217, 239, 122, 212, 0, 235, 126, 251, 253, 94, 9, 160, 38, 4, 34, 37, 164, 169, 43], [118, 71, 139, 102, 7, 99, 136, 88, 162, 72, 30, 100, 82, 147, 128, 27, 194, 134, 83, 57, 168, 135, 231, 163, 215, 201, 232, 89, 24, 140, 150, 152, 35, 124, 208, 91, 48, 46, 65, 159, 203, 51, 114, 41, 211, 153, 8, 60, 229, 216, 169, 66], [127, 33, 21, 118, 71, 102, 200, 180, 99, 179, 16, 240, 74, 27, 108, 243, 232, 113, 35, 208, 78, 107, 48, 174, 41, 211, 81, 56, 66, 73, 86, 62, 67, 70, 139, 68, 88, 100, 110, 128, 147, 28, 120, 75, 134, 192, 249, 168, 59, 145, 24, 177, 140, 150, 131, 53, 158, 219, 105, 96, 251, 253, 94, 183, 160, 8, 171], [32, 63, 206, 129, 200, 180, 179, 186, 16, 44, 27, 252, 228, 250, 192, 108, 59, 191, 201, 177, 224, 10, 187, 31, 223, 150, 113, 104, 155, 91, 122, 78, 22, 87, 174, 0, 46, 197, 123, 149, 85, 183, 9, 111, 58, 81, 209, 4, 37, 101, 164, 196], [33, 32, 139, 244, 30, 16, 100, 128, 25, 190, 228, 57, 61, 249, 202, 231, 59, 215, 151, 24, 224, 187, 140, 113, 11, 131, 217, 234, 142, 167, 107, 158, 214, 157, 235, 105, 221, 36, 253, 97, 160, 211, 209, 56, 216, 101, 73, 164, 76], [127, 63, 188, 80, 180, 166, 244, 30, 255, 16, 25, 27, 233, 190, 120, 228, 95, 61, 220, 191, 201, 232, 24, 148, 31, 223, 155, 142, 91, 78, 107, 121, 212, 174, 219, 0, 157, 235, 29, 96, 105, 133, 126, 238, 159, 94, 160, 114, 211, 176, 209, 164, 73, 45, 132, 196, 76, 43, 19, 171], [170, 21, 118, 71, 99, 18, 162, 16, 55, 240, 182, 95, 61, 230, 152, 35, 11, 207, 48, 77, 214, 133, 50, 210, 138, 97, 12, 211, 45, 101, 76, 86, 62, 237, 1, 30, 186, 100, 222, 25, 120, 134, 135, 184, 69, 254, 172, 224, 131, 217, 245, 121, 22, 158, 205, 13, 126, 3, 36, 185, 253, 111, 47, 38, 164, 196]]],
		["M5", "machinery", "Food blenders", "The unusual love for food blenders a few species have is a mystery to the majority of sentients, but whether producing or collecting, the planetary hobby often becomes a religion. In other systems, of course, the pureeing of food is considered a capital offence.", [[131, 132], [6], [141], [243], [], [196], [174, 48], []], [[162, 224, 108, 13], [], [42, 58], [163, 50, 26], [171], [235], [92, 72, 113], [91]]],
		["M6", "machinery", "Parking Meters", "For all the systems that wish to regulate parking, there are very few willing to convert their production towards the high-tech meters needed to outfox the determined hacker. Parking meters worth shipping out of system are therefore extremely rare.", [[231, 100], [173], [33, 110], [156], [226], [114, 20], [137, 54], [231, 216]], [[156, 75, 249, 167, 158, 59, 92, 254, 60, 132, 223], [109, 189, 178, 188, 80], [155, 219, 43, 140, 107], [116, 36, 76, 105, 220], [217, 19], [84, 215, 206], [18, 3, 194, 7, 195, 82], [7, 160, 41, 31]]],
		["M7", "machinery", "Witchspace engines", "Most ships nowadays need a witchspace drive, and while shipyards have the capacity to produce their own, it's often cheaper for them to import the quirium condensers from elsewhere.", [[123, 85, 75, 210, 168, 153, 8, 52, 158, 98, 212, 174, 219, 232], [85, 36, 68, 58, 69, 52, 212, 244, 100], [147, 6, 131, 63, 40, 227, 184, 22, 99, 179, 56, 10], [104, 107, 99, 136, 219, 144, 147, 123, 203, 85, 120, 3, 36, 75, 192, 115, 184, 24, 117, 76, 10, 195], [159, 128, 90, 95, 64, 250, 230, 220, 218, 12, 135, 69, 191, 0, 229, 89, 133], [11, 210, 139, 102, 68, 184, 107, 69, 254, 200, 177, 43, 62, 222], [155, 11, 36, 91, 68, 244, 219, 132, 148, 235, 187], [241, 118, 239, 7, 188, 2, 143, 48, 200, 180, 244, 144, 125, 16, 82, 246, 147, 182, 194, 83, 111, 226, 218, 41, 137, 34, 145, 229, 86, 187]], [[33, 39, 250, 141, 91], [170, 127, 33, 227, 48, 106, 105, 96, 82, 251, 39, 182, 185, 248, 202, 135, 243, 24, 150], [204, 21, 165, 245, 139, 26, 17, 205, 42, 84, 251, 36, 97, 52, 31, 247, 5, 223], [34, 2, 247], [152, 233, 190, 204, 71, 57, 198, 102, 249, 153, 88, 73, 144, 29, 222, 55], [6, 120, 85, 53, 227, 208, 146, 129, 51, 220, 8, 60, 151, 66], [35, 127, 234, 154, 214, 23, 55, 203, 161, 39, 64, 250, 202, 231, 24, 73, 140], [26, 212, 87, 174, 116, 136, 55, 251, 161, 75, 156, 9, 184, 98, 31, 171]]],
		["R1", "minerals", "Quirstone", "Asteroids that tumble with one side consistently facing the sun eventually attract a thin coating of polarised Quirium from the solar wind. While too sparse to be extracted for fuel, it gives the underlying rock a brilliant sparkle, and the stone, if extracted intact, is valuable in the production of decorated goods.", [[90, 13, 65, 55, 128, 246, 50, 28, 120, 156, 228, 51, 220, 92, 254, 148], [170, 128, 155, 221, 120, 63, 53, 71, 230, 129, 188, 58, 103, 201, 66], [237, 198, 16, 65, 110, 55, 114, 236, 15, 52, 103, 145, 89, 24, 19, 117, 171], [238, 236, 114, 111, 108, 115, 205, 137, 151, 37, 144, 16, 242, 110, 113], [11, 21, 241, 134, 97, 121, 92, 112, 87, 216, 73, 19, 76], [35, 234, 142, 239, 138, 146, 51, 20, 92, 254, 191, 56, 105, 113], [147, 33, 11, 185, 142, 64, 57, 36, 138, 135, 8, 254, 116], [170, 104, 203, 39, 64, 248, 68, 215, 37, 62, 222]], [[147, 182, 39, 213, 7, 227, 129, 91, 200, 46, 255, 140, 113], [90, 57, 40, 135, 15, 151, 19, 222], [84, 31], [18, 64, 138, 218, 96], [137, 3, 125, 30], [238, 197], [85, 206, 17, 15, 14], [76]]],
		["R2", "minerals", "Crystal Ores", "Mineral ores and deposits rich in gems.", [[228, 102, 114, 51, 218, 8, 158, 215, 18, 49, 148, 55], [182, 57, 40, 192, 91, 188, 115, 103, 178, 113], [240, 84, 139], [163, 210], [87, 171, 96], [], [], []], [[124, 147, 39, 7, 129, 91, 93, 46, 255], [152, 80], [185, 230, 184], [156, 211], [155, 76], [238, 197], [54], [76]]],
		["R3", "minerals", "Arthropod shell", "Exoskeletal species such as lobsters are often able to use the shells of other arthropods to repair minor damage to their own. The raw materials are considerably cheaper than usual medical kits.", [[8, 212, 4, 194, 36, 65], [209, 130, 105], [90, 148, 62], [251, 241, 199, 220, 46, 219, 186, 23, 148, 175], [145, 227, 44, 211, 176], [80, 10, 218], [127, 164, 227, 114, 13, 211], [190, 185, 176]], [[238, 237, 159, 182, 207, 58, 211, 20, 174], [90, 234, 139, 122, 121, 158, 87, 136, 0, 46, 125, 29, 147, 85, 57, 248, 183, 138, 168, 15, 176, 254, 216, 43], [246, 170, 159, 182, 181, 118, 70, 42, 255, 235, 54], [152, 136, 88, 0, 110, 126, 238, 128, 190, 40, 57, 64, 185, 192, 168, 8, 254, 24, 216, 62], [238, 123, 203, 190, 11, 118, 160, 143, 81, 92, 254, 98, 106, 187], [131, 32, 21, 156, 134, 226, 158, 212, 151, 178, 255, 86], [74, 6, 190, 83, 57, 230, 220, 2, 135, 163, 180, 201, 88, 89, 224, 247], [152, 155, 33, 122, 42, 212, 244, 93, 16, 44, 222, 82, 240, 39, 85, 40, 95, 183, 41, 115, 153, 163, 59, 229, 196]]],
		["R4", "minerals", "Corals", "Corals, growing in certain life-rich oceans, are valuable for both their decorative properties, and in some species, their flavour. Those without the jaws to digest rock-like substances are advised to stick to decoration.", [[42, 28, 102, 29], [6, 31], [206, 125, 115, 58], [130, 192, 179, 178, 216, 16, 148, 224, 247], [93, 194, 71, 61, 255, 187, 65, 62, 211], [182, 90, 26, 168, 41, 153, 243, 69, 212, 173, 34, 77, 72, 247], [130, 165, 213, 139, 61, 42, 23, 29, 195], [149, 221, 225, 139, 94, 199, 58, 60, 87, 229, 117, 44]], [[238, 237, 159, 124, 182, 207, 58, 211, 20, 174], [90, 234, 139, 122, 121, 158, 87, 136, 0, 46, 125, 29, 147, 85, 57, 248, 183, 138, 168, 15, 176, 254, 216, 43], [246, 170, 159, 182, 181, 118, 70, 42, 255, 235, 54], [152, 136, 88, 0, 110, 126, 128, 238, 190, 40, 57, 64, 185, 168, 8, 254, 24, 62], [238, 123, 203, 190, 11, 118, 160, 143, 81, 92, 254, 98, 106], [131, 32, 21, 156, 134, 226, 158, 151, 178, 255, 86], [74, 6, 190, 83, 57, 230, 220, 2, 135, 163, 180, 201, 88, 89, 224, 247], [152, 155, 33, 122, 42, 212, 244, 93, 16, 222, 82, 240, 39, 85, 40, 95, 183, 41, 115, 153, 163, 59, 196]]],
		["R5", "minerals", "Durassion Ore", "A common ore in asteroid regions, this is refined and strengthened into the duralium alloy used in most ship hulls.", [[123, 228, 156, 102, 198, 114, 51, 158, 8, 52, 109, 215, 18, 49, 148, 55], [103, 182, 57, 40, 178, 91, 188, 115, 113], [240, 84, 172, 250, 139, 110, 5], [134, 9], [238, 87, 130, 50, 174, 0], [233, 131, 182, 210, 142, 227, 243, 22, 109, 60, 144], [170, 25, 64, 160, 81, 66, 216, 29], [25, 39, 234, 206, 83, 31, 14]], [[170, 127, 32, 7, 227, 2, 166, 88, 128, 240, 108, 103, 145, 35, 152, 131, 53, 91, 48, 79, 0, 106, 96, 6, 149, 159, 94, 199, 9, 12, 226, 41, 73, 76, 54], [170, 208, 26, 143, 79, 158, 46, 16, 222, 82, 240, 238, 233, 138, 58, 202, 153, 73], [206, 142, 158, 174, 222, 126, 238, 190, 233, 156, 9, 199, 249, 12, 109, 103, 173, 45], [238, 6, 233, 241, 198, 17, 22, 209, 201, 46, 49, 214, 30, 169, 222], [93, 219, 125, 157, 55, 147, 233, 251, 49, 73, 66, 151, 232, 86, 76, 247], [189, 130, 42, 1, 93, 96, 65, 74, 120, 194, 64, 156, 138, 220, 37, 19, 31, 5, 150], [237, 143, 79, 48, 106, 186, 74, 128, 221, 185, 250, 138, 111, 41, 218, 14, 137, 254, 89, 175], [33, 225, 204, 139, 227, 129, 68, 42, 141, 74, 203, 190, 57, 94, 183, 146, 249, 153, 109, 4, 173, 56, 89, 247]]],
		["R6", "minerals", "Igneous Rocks", "The residue of intense volcanic activity, these rocks are sometimes used decoratively, but the majority of exports go to be used as fertiliser on the richer agricultural worlds.", [[251, 53, 194, 139, 220, 153, 4, 116, 175, 126], [243, 136, 71, 157, 111, 249, 113, 107], [46, 202], [128, 252, 161, 182, 228, 75, 227], [163, 81, 201, 148, 17], [35, 245], [101, 94, 91, 86], [25, 252, 71, 7, 157, 65]], [[124, 130, 7, 26, 17, 107, 200, 1, 125, 235, 222, 58, 184, 178, 43, 19, 171], [120, 253, 230, 129, 122, 226, 41, 22, 112, 60, 169, 125, 224, 10, 140, 242], [124, 225, 234, 245, 87, 13, 246, 84, 251, 210, 228, 250, 218, 226, 112, 231, 101, 31, 113, 242], [189, 130, 225, 63, 142, 245, 7, 119, 244, 154, 126, 185, 134, 138, 146, 41, 172, 60, 101, 45, 132, 175, 113, 150], [152, 11, 21, 118, 245, 239, 188, 80, 1, 116, 141, 44, 246, 74, 3, 138, 202, 153, 8, 215, 173, 19, 10], [104, 33, 165, 118, 227, 208, 91, 2, 22, 99, 116, 18, 77, 219, 248, 226, 12, 38, 49, 73, 242], [189, 165, 188, 122, 162, 16, 96, 61, 230, 168, 49, 37, 177, 178, 216, 76, 10, 54], [181, 142, 122, 17, 0, 13, 238, 159, 221, 40, 75, 210, 184, 59, 8, 98, 216, 73, 169, 43, 113]]],
		["R7", "minerals", "Industrial lubricants", "A variety of industrial lubricants, generally transported in liquid or powder form. The original ones were mineral in nature; recent innovations in the use of mountain slug secretions have not led to a change in Galcop taxation category.", [[252, 210, 56, 198, 175], [35], [52], [223, 184, 150], [243, 174], [], [35, 154, 19], []], [[131, 162, 169, 132, 224, 108, 13], [6, 101], [42, 141, 58], [243, 163, 50, 26], [171], [196, 235], [92, 174, 72, 113, 48], [91]]],
		["R8", "minerals", "Sedimentary Rocks", "Mostly commonplace and worthless, a few systems produce sedimentary rocks of great decorative or commercial value.", [[8, 200, 182, 213, 227, 140, 113], [90, 57, 40, 135, 15, 151, 19, 222], [185, 230, 184], [18, 64, 138, 96, 218], [137, 112, 3, 125, 30], [], [11, 85, 206, 17, 15, 14], [76]], [[124, 93], [152, 178, 80], [251, 247], [233, 151], [155, 76], [238, 197], [54], [191, 21, 63, 47]]],
		["O1", "radioactives", "Gene-splicers", "Substances that rewrite genetic code, even in a controlled fashion, are not regularly used. For systems that have overused cloning in the past, though, this can be the only way to reintroduce genetic diversity into the population.", [[190, 162, 71, 49, 111], [81, 191, 127, 106, 138, 82], [34], [], [182, 51, 12], [152, 69, 184], [253, 13], [161, 82]], [[250, 211], [152, 178, 80], [206, 24, 236, 133], [104, 98, 143, 110], [201, 10, 105, 96], [1, 33, 53, 83, 43, 202, 41, 55], [231, 159, 11, 1, 28], [59, 162, 248, 222, 55]]],
		["O2", "radioactives", "Low-energy waste", "Low-level radioactive waste is mostly an annoyance. However, the unpredictable rate of its decay is essential to casinos as a source of unbiased random numbers.", [[112, 145, 36, 169, 178], [170, 244, 17], [191, 166, 213, 73, 61, 55], [97, 51, 168, 2], [155, 83, 13], [87, 9, 187], [34, 142, 216, 68, 47], []], [[35, 154, 83, 198, 73, 199], [158, 119, 25, 155, 50, 34, 198, 68, 135], [238, 99, 229, 68, 220, 187], [36, 248, 16, 82, 5], [52, 215, 226, 220, 167, 41, 55], [161, 250, 94, 146, 26, 42, 34, 106, 222], [128, 18, 188, 16, 187], [152, 233, 221, 189, 210, 142, 40, 213, 227, 162, 216, 30]]],
		["O3", "radioactives", "Terraforming nanobots", "If you need an inhospitable planet terraforming to be hospitable, these miniature robots will do the trick. Unless you want your ship turning into a tiny planetoid, however, do not open the container inside.", [[36, 177, 114], [135], [], [69, 245], [], [], [210], [66]], [[161, 222], [32, 234, 220, 223], [128, 81], [210, 83, 61], [152, 77, 105, 96], [190, 188, 202], [59, 131, 157, 44], [201, 173, 183]]],
		["O4", "radioactives", "Quirium Isotopes", "Larger, more active suns produce a wider range of quirium isotopes. There is therefore a natural surplus of certain isotopes in some systems, and a natural deficit in others. To produce a consistent mix for witchspace engines, some trade is needed.", [[128, 50, 90, 28, 120, 156, 51, 220, 92, 254, 13, 65], [170, 128, 155, 221, 120, 63, 53, 71, 230, 129, 58, 201, 66], [237, 198, 16, 65, 110, 55, 114, 236, 15, 52, 103, 145, 89, 24, 19, 117, 171], [238, 236, 114, 111, 108, 115, 205, 137, 151, 37, 144, 16, 242, 110, 113], [11, 21, 241, 134, 97, 121, 92, 112, 216, 73, 19, 76], [35, 234, 142, 239, 138, 146, 51, 20, 92, 254, 191, 56, 105, 113], [147, 33, 11, 185, 142, 64, 57, 36, 138, 135, 8, 254, 116], [170, 104, 203, 39, 64, 248, 68, 215, 37, 62, 222]], [[209, 85, 77, 72, 153, 150, 113, 110], [159, 124, 252, 33, 156, 57, 188, 220, 78, 79, 92, 215, 60], [246, 124, 40, 213, 199, 122, 158, 162, 151, 196], [159, 74, 35, 33, 85, 70, 57, 253, 198, 199, 202, 52, 180, 24], [27, 3, 245, 7, 218, 202, 135, 14, 215, 24, 13, 105], [147, 252, 190, 130, 11, 70, 97, 160, 78, 15, 158, 154, 151, 86, 223], [131, 234, 134, 56, 151, 58, 110], [6, 228, 95, 146, 129, 80, 12, 2, 14, 101, 86, 235, 96]]],
		["T1", "textiles", "Air gossamer", "Produced by the mysterious airborne plants of Larais, this thread generally needs weaving into fabric before it can be used in clothing.", [[177], [], [206], [], [], [], [], []], [[241, 167, 78, 99, 57, 156, 210, 253, 218, 249, 92, 69, 59, 231, 60, 5, 223], [53, 57, 115, 14, 81, 89, 144, 113], [84, 127, 67, 53, 95, 198, 107, 212, 31, 223, 195], [104, 217, 239, 121, 200, 174, 179, 46, 72, 25, 120, 248, 51, 111, 249, 115, 184, 47, 153, 243, 56, 232, 89, 175], [240, 35, 239, 51, 48, 47, 212, 191, 34, 216, 196, 43, 105, 44, 5], [35, 204, 53, 234, 227, 80, 22, 119, 96, 110, 159, 59, 34, 132, 232, 242], [124, 241, 129, 17, 87, 179, 162, 0, 106, 222, 84, 233, 39, 3, 97, 184], [67, 241, 142, 143, 22, 87, 154, 0, 72, 252, 156, 135, 184, 209, 73, 19, 54]]],
		["T2", "textiles", "Pink cotton", "A generic name for a variety of durable general purpose plant fabrics, most of which are in fact pink.", [[87, 244, 2], [250], [112], [197, 214, 41], [144, 16], [225, 77, 199, 129, 150], [163, 194, 118, 164, 227], [233, 204, 207, 141, 20]], [[21, 46, 249], [127, 224], [84, 198, 31], [175], [239], [22, 227, 242], [162], [142, 0, 73, 184]]],
		["T3", "textiles", "Tree ant silk", "The 'silk' produced by tree ants is a strong polymer based on compounds found in the bark and leaves of the trees. It is always in short supply at the chart's preeminent outfitters.", [[92, 85, 118, 117, 79, 48], [27, 21, 0], [77], [120, 101], [], [251, 151], [119, 232, 78], [170, 150]], [[124, 21, 46, 249], [127, 224], [84, 198, 31], [175], [239], [22, 227, 242], [162], [142, 0, 73, 184]]],
		["T4", "textiles", "Tulip petal weave", "A fabric produced by layering preserved tulip petals and a thin thread of another material, to produce a scale effect. Popular among the reptilian species for its scale-matching properties", [[147, 87, 64], [231, 13], [27], [52, 166, 98, 83, 213, 157, 14], [185, 78], [112, 74, 54], [163, 244], [251, 36]], [[197, 124, 33, 90, 217, 95, 97, 26, 15, 121, 14, 103, 125, 133], [240, 152, 149, 104, 32, 57, 239, 192, 51, 79, 145, 177, 186, 117, 247], [104, 189, 225, 21, 139, 7, 154, 93, 214, 157, 65, 149, 221, 61, 97, 135, 8, 232], [240, 85, 102, 230, 9, 176, 48, 22, 38, 112, 137, 150], [104, 221, 70, 91, 220, 176, 20, 163, 101, 96, 195, 110], [163, 104, 98, 174, 101, 37, 30, 16, 223], [71, 166, 99, 116, 65, 126, 183, 248, 9, 160, 41, 115, 231, 66, 24, 10, 195], []]],
		["T5", "textiles", "Vargorn mind-silk", "An unusual fabric, believed by some to be able to reduce stress in its wearer. Whether this effect is real, and if so, which species benefit from it, has yet to be ascertained.", [[87, 90, 232], [240], [133], [166, 102], [221], [98], [], [168]], [[33, 21, 46, 249], [152, 127, 224], [84, 198, 31], [175], [239], [22, 227, 242], [162], [142, 0, 73, 184]]],
		["T6", "textiles", "Weed hemp", "The production of Megaweed provides this strong fabric, often incorporated into light body armour. As a result, attempts to ban the production, as opposed to the export, of Megaweed itself have never really got anywhere.", [[87, 127, 71], [250, 105], [112], [214], [], [225, 150], [53, 194, 118, 227], [233, 204, 141, 20]], [[124, 240], [127, 224], [84, 198, 31], [175], [239], [22, 227, 242], [162], [142, 0, 73, 184]]],
		["T7", "textiles", "Woven fabrics", "Their six-limbed structure and compound vision gives insectoids the ideal anatomy for weaving fabrics. ", [[241, 210, 156, 57, 253, 218, 167, 78, 231, 59, 69, 92, 60, 99, 223, 5], [53, 57, 115, 14, 81, 89, 144, 113], [127, 67, 53, 95, 107, 212, 195, 223], [104, 217, 239, 121, 200, 174, 179, 46, 72, 25, 120, 248, 51, 111, 249, 115, 47, 153, 184, 243, 56, 232, 89], [240, 35, 51, 48, 47, 212, 191, 34, 216, 196, 43, 105, 44, 5], [159, 35, 204, 53, 234, 80, 59, 119, 34, 232, 132, 96, 110], [124, 241, 129, 17, 87, 179, 162, 0, 106, 222, 84, 233, 39, 3, 97, 184], [252, 67, 241, 156, 143, 135, 22, 209, 87, 154, 72, 19, 54]], [[221, 21, 46, 249], [127, 224], [84, 198, 31], [175], [239], [22, 227, 242], [], [142, 0, 73, 184]]],
		["T8", "textiles", "Yak wool", "A somewhat rough but extremely warm material, often used for cloaks and bedding. The Oninran variety is particularly prized.", [[23], [235], [], [], [81], [168], [], []], [[200, 89, 13, 227, 150], [25, 200, 57, 253, 23, 160, 62], [25, 200, 253, 160, 62], [35, 233, 229, 153], [35, 234, 253, 108, 48, 145, 201, 179, 24, 157], [162, 224, 117], [92, 81, 244, 234, 134, 56, 192, 193], [35, 67, 210, 70, 193, 79, 112, 166, 66, 73, 144, 133, 223, 150]]]
	];
	var illegaltypes = [
		["IF1", "firearms", "Hand weapons", "Projectile weapons, lasers, plasma rifles, and other small arms", [[120, 80, 188, 220, 249, 193, 15, 158, 109, 154, 24, 125, 175, 150], [170, 127, 33, 182, 248, 227, 202, 135, 48, 243, 106, 96, 82, 150], [84, 251, 204, 21, 165, 36, 245, 139, 26, 17, 205, 31, 247, 223], [237, 221, 39, 57, 188, 9, 254, 103, 49, 66, 186, 86, 140, 110], [152, 233, 190, 204, 71, 57, 198, 102, 249, 153, 73, 144, 29, 222], [6, 120, 85, 53, 227, 208, 146, 129, 51, 220, 8, 60, 151, 66], [35, 161, 127, 234, 64, 250, 202, 231, 154, 214, 24, 23, 140, 55], [251, 156, 75, 26, 9, 218, 184, 212, 87, 98, 174, 116, 136, 55]], [[118, 70, 2, 180, 1, 179, 162, 46, 6, 190, 233, 50, 57, 253, 94, 183, 226, 168, 209, 60, 43, 223], [124, 11, 118, 102, 80, 26, 116, 29, 55, 74, 123, 83, 226, 168, 220, 60, 148, 247, 5], [152, 155, 90, 71, 227, 107, 244, 93, 162, 186, 144, 23, 128, 149, 95, 156, 236, 41, 8, 86, 175], [35, 131, 11, 63, 234, 245, 48, 42, 77, 65, 246, 240, 159, 149, 194, 40, 134, 230, 199, 168, 14, 81, 137, 178, 169, 43, 54], [237, 252, 181, 228, 64, 40, 253, 111, 107, 20, 209, 244, 49, 106, 13, 16, 23], [124, 63, 245, 158, 119, 174, 0, 235, 65, 246, 251, 39, 228, 138, 202, 184, 14, 15, 92, 109, 209, 172, 169, 43, 19, 76, 117, 5], [237, 33, 217, 239, 26, 121, 79, 93, 106, 44, 100, 159, 203, 120, 57, 12, 135, 52, 150], [237, 130, 63, 165, 99, 77, 157, 96, 74, 190, 182, 248, 108, 137, 59, 38, 254, 103, 56, 66, 224, 86, 117, 62, 150, 5, 113]]],
		["IF2", "firearms", "Explosives", "High explosives stored in bulk form, suitable for local conversion into grenades, warheads, or bombs as the situation requires.", [[120, 80, 188, 220, 249, 193, 15, 158, 109, 154, 24, 125, 175, 150], [170, 127, 33, 182, 248, 227, 202, 135, 48, 243, 106, 96, 82, 150], [84, 251, 204, 21, 165, 36, 245, 139, 26, 17, 205, 31, 247, 223], [237, 221, 39, 57, 188, 9, 254, 103, 49, 66, 186, 86, 140, 110], [152, 233, 190, 204, 71, 57, 198, 102, 249, 153, 73, 144, 29, 222], [6, 120, 85, 53, 227, 208, 146, 129, 51, 220, 8, 60, 151, 66], [35, 161, 127, 234, 64, 250, 202, 231, 154, 214, 24, 23, 140, 55], [251, 156, 75, 26, 9, 218, 184, 212, 87, 98, 174, 116, 136, 55]], [[118, 70, 2, 180, 1, 179, 162, 6, 50, 94, 183, 253, 168, 226, 209, 43], [124, 11, 118, 80, 26, 116, 29, 55, 74, 123, 83, 220, 226, 60, 148, 5], [152, 155, 71, 227, 107, 93, 162, 144, 23, 128, 95, 156, 236, 41, 86, 175], [35, 131, 11, 63, 42, 65, 246, 240, 149, 159, 40, 134, 194, 230, 14, 81, 137, 169, 43, 54], [237, 252, 64, 40, 253, 20, 209, 244, 49, 16], [63, 158, 119, 0, 235, 65, 251, 228, 138, 202, 14, 184, 92, 172, 169, 117, 76, 43, 5], [237, 159, 203, 217, 120, 57, 239, 26, 79, 52, 93, 106, 44, 100, 150], [237, 130, 165, 99, 96, 74, 182, 248, 59, 38, 137, 254, 66, 86, 224, 62, 5, 150]]],
		["IF3", "firearms", "Plasma turrets", "High power high drain plasma turrets as found on large capital ships and major ground installations.", [[120, 80, 188, 220, 249, 193, 15, 158, 109, 154, 24, 125, 175, 150], [170, 127, 33, 182, 248, 227, 202, 135, 48, 243, 106, 96, 82, 150], [84, 251, 204, 21, 165, 36, 245, 139, 26, 17, 205, 31, 247, 223], [237, 221, 39, 57, 188, 9, 254, 103, 49, 66, 186, 86, 140, 110], [152, 233, 190, 204, 71, 57, 198, 102, 249, 153, 73, 144, 29, 222], [6, 120, 85, 53, 227, 208, 146, 129, 51, 220, 8, 60, 151, 66], [35, 161, 127, 234, 64, 250, 202, 231, 154, 214, 24, 23, 140, 55], [251, 156, 75, 26, 9, 218, 184, 212, 87, 98, 174, 116, 136, 55]], [[209, 180, 1, 162, 70, 183, 2], [74, 11, 204, 83, 220, 60, 116, 65, 55], [128, 71, 236, 227, 41, 93, 144, 86], [240, 159, 35, 131, 11, 63, 194, 40, 230, 81, 137, 43], [237, 209, 49, 253, 16, 20], [63, 202, 14, 119, 158, 172, 0, 169, 117, 235], [237, 159, 203, 217, 57, 26, 44, 150], [130, 248, 59, 137, 99, 254, 224, 96]]],
		["IF4", "firearms", "Artillery parts", "Spare parts and ready-to-build new parts for artillery and other heavy war machinery.", [[120, 165, 80, 188, 220, 249, 193, 15, 158, 109, 154, 141, 24, 125, 175, 150], [170, 127, 33, 182, 248, 227, 202, 135, 48, 243, 106, 96, 82, 150], [84, 251, 204, 21, 165, 36, 245, 139, 26, 17, 205, 31, 247, 223], [237, 221, 39, 57, 188, 9, 254, 103, 49, 66, 186, 86, 140, 110], [152, 233, 190, 204, 71, 57, 198, 102, 249, 153, 73, 144, 29, 222], [6, 120, 85, 53, 227, 208, 146, 129, 51, 220, 8, 60, 151, 66], [35, 161, 127, 234, 64, 250, 202, 231, 154, 214, 24, 23, 140, 55], [251, 156, 75, 26, 9, 218, 184, 212, 87, 98, 174, 116, 136, 55]], [[209, 180, 1, 162, 70, 183, 2], [74, 11, 204, 83, 220, 60, 116, 65, 55], [128, 71, 236, 227, 41, 93, 144, 86], [240, 159, 35, 131, 11, 63, 194, 40, 230, 81, 137, 43], [237, 209, 49, 253, 16, 20], [63, 202, 14, 119, 158, 172, 0, 169, 117, 235], [237, 159, 203, 217, 57, 26, 44, 150], [130, 248, 59, 137, 99, 254, 224, 96]]],
		["IF5", "firearms", "Evil poet", "The evil poet is classed as a Firearm for historical reasons. Do not open the canister unless you have control poems prepared, as they are generally stored in attack mode for rapid deployment.", [[114], [209, 77, 165], [100], [76], [227], [142, 41], [65], [22, 70, 44]], [[180, 209], [11, 116], [227, 41], [240, 35, 131, 11, 40, 43], [209, 49], [158, 63, 0, 235, 202], [203, 217, 57], [99]]],
		["FI1", "food", "Edible Arts Graduate", "The export of edible arts graduates breaches numerous local and galactic regulations, but the gourmets of worlds such as Malama may be prepared to break them.", [[27, 251], [212, 214, 54, 153], [111], [93, 85, 3, 227, 157, 108, 143], [127, 116, 37, 193, 31, 5], [35], [225, 210, 101, 140, 107], [147, 104, 215, 98, 19, 153, 48]], [[156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 186, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 7, 208, 143, 38, 109, 72, 144], [84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["FI2", "food", "Edible Poet", "After the infamous conservation disasters on Biarge, export and consumption of edible poets was placed under strict control. Shipping poets outside of their native system is an offence.", [[170, 123, 63, 3, 61, 146, 122, 37], [176], [190, 56, 65], [254, 203, 244, 185], [225, 186], [84, 52, 32, 89, 31, 14], [191, 219, 199], [81, 28, 83, 255, 96, 211, 82]], [[156, 40, 138, 160, 17, 46, 49, 232, 105, 150], [251, 90, 66, 236, 43, 126, 79], [149, 203, 50, 208, 188, 122, 218, 52, 34, 145, 46, 29, 133], [212, 189, 250, 183, 141, 208, 78], [246, 238, 206, 146, 166, 172, 106, 45, 10, 23], [205, 137, 204, 53, 83, 232, 208, 12], [155, 204, 134, 253, 208, 143, 38, 109, 72, 144], [104, 84, 120, 185, 61, 129, 41, 174, 222, 242]]],
		["OI1", "radioactives", "Next-generation personality editors", "The Communist states have been at the forefront of personality editing, though the technology has become more widespread across all systems. These next-generation editors, transmitted through a viral medium, are viewed with extreme suspicion.", [[190, 165, 75, 139, 158, 254, 132, 100], [109, 189, 172, 188, 140], [155, 219, 64, 144, 43, 16, 107], [159, 36, 220, 167, 135, 215, 191, 4, 103, 116, 23, 76, 44, 247, 223], [240, 170, 42, 106, 208, 175], [238, 84, 155, 206, 20, 14], [92, 172, 131, 60, 204, 3, 115, 195], [6, 85, 53, 198, 102, 160, 117, 31]], [[209, 162, 94, 192, 43, 226, 2, 15], [124, 127, 220, 205, 60, 89, 43, 29], [25, 128, 190, 218, 133], [63, 199], [181, 111, 16], [128, 251, 134, 103, 172, 174, 145, 177, 169], [209, 44, 12], [, 217, 63, 38, 209, 224, 96, 171]]],
		["IS1", "slaves", "Avian Slaves", "Non-humanoid creatures, while often preferring human slaves, will also take slaves of their own species class to carry out tasks unsuited to humanoid anatomy.", [[85, 185, 236, 122, 105], [119, 154, 75, 111, 211], [152, 74, 203, 161, 90, 85, 138, 218, 167, 153, 244, 88, 186, 150], [182, 53, 118, 70, 133], [246, 84, 122, 111, 9, 62], [3, 76, 23, 12], [147, 42, 209, 120, 194, 227, 153, 121], [238, 170, 221, 182, 213, 61, 121, 38, 81, 98, 255, 100]], [[163, 151, 79], [238, 64, 57, 49, 208, 78, 58, 184], [33, 40, 253, 213, 184, 22, 52, 56, 125], [246, 181, 198, 54, 5], [159, 189, 172, 95, 175], [149, 155, 70, 183, 68, 168, 4, 5, 150, 195], [158, 137, 38, 32, 198, 138, 199, 226, 20], [250, 7, 15, 14, 173, 56, 187, 195]]],
		["IS2", "slaves", "Budget Tourists", "While it is illegal to transport tourists and other passengers in cryo-suspension in your ship's hold, this does provide a cheap way for people to see parts of the galaxy they could otherwise not afford. The high risks involved mean that Galcop is unlikely to decriminalise the practice any time soon, but even with the danger money paid to the hauler, it's still cheaper than a berth on a real liner.", [[124, 131, 225, 210, 183, 250, 193, 168, 18, 72, 55], [130, 50, 146, 103, 87, 215, 34, 151, 23], [74, 197, 90, 85, 53, 106, 229, 122, 133], [65, 58], [163, 252, 217, 102, 230, 220, 195, 211], [181, 85, 225, 210, 114, 168, 8, 215, 87, 72, 232, 151, 23], [149, 21, 114, 17, 2, 184, 98, 18, 133, 5], [124, 252, 241, 206, 226, 81, 92, 34, 145, 49, 162, 177, 187]], [[246, 155, 93, 102, 188, 129, 9], [6, 127, 206, 185, 122, 107, 22, 200, 178], [36], [161, 130, 145, 198, 188], [182, 194, 26, 176, 0, 177, 82, 113], [244, 113], [99, 225, 40, 94, 224, 9, 29], [246, 119, 98, 71, 199, 100]]],
		["IS3", "slaves", "Feline Slaves", "Non-humanoid creatures, while often preferring human slaves, will also take slaves of their own species class to carry out tasks unsuited to humanoid anatomy.", [[147, 109, 39, 186, 169, 86, 140], [203, 190, 206, 83, 57, 80, 107, 56], [11, 1, 136, 250, 122, 86], [21, 73, 208, 86, 16, 105, 41], [152, 6, 83, 75, 3, 243, 59, 209, 88, 179, 186, 177, 169, 19], [181, 248, 114, 91, 87, 162, 214, 224, 187], [25, 33, 70, 249, 81, 98, 49, 30, 148, 19], [35, 27, 75, 193, 133, 55]], [[246, 149, 227, 31, 110, 82], [170, 221, 185, 61, 26, 202, 153, 200, 151], [129, 26, 249, 193, 29, 121], [149, 233, 201, 214, 169, 144, 80], [170, 251, 233, 165, 139, 68, 226, 18, 162, 132, 133, 82], [238, 84, 127, 220, 106, 186, 117, 31, 247], [170, 60, 28, 64, 23], [128, 161, 28, 68, 112, 200, 141, 10]]],
		["IS4", "slaves", "Frog Slaves", "Non-humanoid creatures, while often preferring human slaves, will also take slaves of their own species class to carry out tasks unsuited to humanoid anatomy.", [[251, 161, 21, 142, 118, 143, 18, 154, 235, 150, 242], [11, 120, 57, 174, 30, 10, 157, 126], [99, 4, 234, 134, 245, 164, 20], [127, 67, 95, 94, 96, 31], [67, 28, 134, 227, 58, 41, 174, 37, 224, 31, 223], [197, 165, 140, 133], [35, 130, 73, 202, 105, 96, 211], [254, 181, 13, 12, 242, 107]], [[233, 50, 250, 213, 41, 173, 214, 23], [8, 215, 209, 106, 40, 143], [6, 191, 36, 68, 43], [159, 255, 224, 223, 222, 195], [182, 210, 40, 146, 115, 107, 42, 219, 49, 229, 255, 29], [123, 27, 40, 198, 58, 46, 229, 144, 196], [131, 225, 217, 102, 7, 51, 167, 52, 119, 215, 1, 62], [228, 194, 198, 226, 65, 5]]],
		["IS5", "slaves", "Insect Slaves", "Non-humanoid creatures, while often preferring human slaves, will also take slaves of their own species class to carry out tasks unsuited to humanoid anatomy.", [[92, 59, 60, 156, 249, 167, 223], [53, 144, 89, 224, 115, 113, 14], [84, 95, 198, 31], [217, 120, 239, 249, 153, 243, 200, 174, 56, 179, 232, 175], [191, 239, 196, 43, 44, 48, 47], [59, 22, 234, 232, 227, 242, 110], [233, 162, 0], [252, 67, 156, 142, 184, 135, 22, 209, 154, 0, 72, 73]], [[241, 210, 57, 253, 218, 78, 69, 231, 99, 5], [81, 57], [212, 127, 67, 53, 223, 195, 107], [104, 25, 248, 111, 51, 115, 184, 121, 47, 46, 72, 89], [240, 35, 51, 212, 34, 216, 105, 5], [159, 35, 204, 53, 80, 119, 34, 132, 96], [124, 84, 39, 241, 3, 97, 129, 17, 184, 87, 179, 106, 222], [87, 241, 19, 143, 54]]],
		["IS6", "slaves", "Lizard Slaves", "Non-humanoid creatures, while often preferring human slaves, will also take slaves of their own species class to carry out tasks unsuited to humanoid anatomy.", [[197, 87, 90, 95, 125, 97, 26, 133, 15], [152, 149, 104, 57, 239, 192, 51, 145, 186, 247], [221, 225, 21, 139, 61, 7, 135, 8, 154, 93, 232, 214, 157, 65], [240, 166, 213, 102, 9, 150, 48], [163, 104, 221, 101, 91], [104, 98, 174, 101, 30, 16], [166, 99, 24, 160, 10, 65, 126], [243, 25, 207, 169, 140, 126, 113]], [[124, 103, 33, 121, 14], [240, 32, 177, 117, 79], [149, 104, 189, 97], [137, 112, 38, 22, 85, 230, 176], [70, 220, 96, 195, 176, 110, 20], [163, 37, 223], [71, 248, 183, 9, 115, 41, 231, 116, 66, 195], [147, 114, 26, 168, 167, 96]]],
		["IS7", "slaves", "Lobster Slaves", "Non-humanoid creatures, while often preferring human slaves, will also take slaves of their own species class to carry out tasks unsuited to humanoid anatomy.", [[238, 174, 58, 211], [147, 90, 234, 183, 139, 122, 168, 121, 136, 0, 125, 175, 43, 29, 110], [246, 170, 159, 182, 181, 118, 70, 255], [128, 185, 192, 168, 8, 88, 24, 62, 110, 126], [123, 92, 81, 190, 11, 118, 187], [158, 131, 32, 21, 134, 178, 151, 226], [163, 190, 180, 201, 83, 230, 220], [240, 152, 39, 95, 40, 122, 59, 163, 212, 196, 16, 222]], [[237, 159, 182, 207, 20], [85, 57, 248, 138, 176, 15, 158, 254, 87, 46, 216], [42, 235, 54], [238, 152, 190, 64, 57, 40, 254, 136, 0, 216], [238, 203, 160, 211, 143, 98, 254, 106], [212, 156, 255, 86], [74, 6, 57, 2, 135, 88, 89, 224, 247], [155, 33, 85, 183, 115, 41, 153, 42, 244, 93, 229, 44, 82]]],
		["IS8", "slaves", "Rodent Slaves", "Non-humanoid creatures, while often preferring human slaves, will also take slaves of their own species class to carry out tasks unsuited to humanoid anatomy.", [[123, 28, 83, 188, 114, 107, 46, 178, 177, 45, 43, 187, 44, 113], [74, 21, 57, 94, 160, 17, 42, 112, 179, 72, 235], [217, 75, 71, 57, 102, 230, 202, 168, 231, 166, 180, 72, 216, 24, 89], [69, 165, 134, 245, 101], [27, 32, 63, 108, 168, 79, 112, 201, 46], [99, 208, 160], [161, 244, 156, 192, 177, 188, 193], [84, 210, 70, 47, 172, 215, 66, 101, 223]], [[22, 204, 206, 61, 146, 220], [25, 189, 207, 245, 93, 24, 232, 23, 62], [25, 120, 39, 185, 248, 199, 58, 38, 200, 103, 116, 10], [197, 6, 229, 37, 117], [127, 241, 234, 156, 17, 15, 69, 99, 125, 157, 235, 171, 54, 126], [170, 6, 251, 50, 85, 63, 28, 250, 184, 69, 148, 222], [152, 67, 243, 103, 254, 151, 169, 55], [124, 53, 94, 116, 201, 24, 144, 235, 150]]],
		["IN1", "narcotics", "Anti-nausea medication", "Medication used to suppress nausea. Due to the ease with which it can be processed into illegal hallucinogens, an export license is required.", [[172, 177, 86], [42, 127, 120], [11, 176], [92, 69, 142], [109, 33, 168], [226], [147, 214, 37, 193], [23]], [[42, 239, 232, 111, 26], [144, 26, 14], [203, 154, 28, 75, 70, 151, 37, 167, 153], [173, 154, 63, 183, 177, 115, 242], [209, 166, 103, 32, 70, 141, 47], [185, 66, 37, 249, 82], [240, 128, 38, 251, 144, 125], [93, 138, 9, 223]]],
		["IN2", "narcotics", "Catnip", "Very popular among felines for the high it provides, but almost worthless to other species.", [[149, 180, 129, 110], [], [136], [144, 208, 80, 16], [165, 186, 133], [], [148], [112, 133]], [[246, 147, 39, 227, 109, 169, 186, 86, 31, 140, 82], [170, 206, 26, 80, 107, 200, 190, 203, 221, 57, 83, 185, 61, 202, 153, 56, 151], [11, 250, 129, 122, 26, 249, 193, 121, 1, 86, 29], [149, 233, 21, 41, 201, 214, 73, 169, 86, 105], [170, 152, 139, 68, 88, 18, 179, 162, 82, 6, 251, 233, 3, 75, 83, 226, 243, 59, 209, 177, 132, 169, 19], [127, 181, 91, 87, 162, 106, 214, 186, 84, 238, 248, 114, 220, 117, 224, 31, 187, 247], [170, 25, 33, 28, 70, 64, 249, 81, 98, 60, 49, 30, 19, 23], [128, 35, 27, 161, 28, 75, 68, 193, 200, 141, 10, 55]]],
		["IN3", "narcotics", "Healing waters", "The regenerative properties of the waters of some planets are not fully understood. As a precuation, Galcop has placed them under export bans, to avoid ecosystem contamination. There are, however, plenty of systems desperate enough to wish to circumvent this.", [[241, 29], [154, 31], [43, 58], [179, 178], [194, 61, 187], [69, 173, 77, 41], [42, 130, 23, 29], [225, 229, 117, 58, 44]], [[35, 181, 90, 21, 206, 245, 87, 250, 213, 51, 199, 202, 69, 101, 66, 169, 175, 113], [197, 156, 142, 239, 68, 121, 158, 59, 45, 66, 10, 16, 19, 86, 126], [25, 67, 53, 210, 194, 248, 183, 202, 12, 121, 69, 254, 180, 140, 54], [170, 90, 102, 79, 205, 212, 166, 99, 1, 141, 44, 6, 84, 253, 94, 146, 218, 202, 184, 135, 209, 145, 89, 24, 223], [238, 221, 203, 21, 165, 17, 12, 92, 215, 172, 174, 93, 0, 86, 148], [155, 17, 1, 141, 23, 44, 6, 149, 199, 58, 47, 153, 8, 4, 98, 56, 216, 178, 24, 62], [11, 90, 207, 154, 77, 222, 246, 50, 228, 253, 236, 230, 202, 226, 15, 153, 112, 163, 60, 151, 45, 169, 177, 178, 224], [189, 107, 18, 88, 30, 197, 203, 194, 228, 61, 183, 236, 168, 220, 37, 24, 247, 195]]],
		["IN4", "narcotics", "Lethal water", "Bima water and other 'waters' were originally transported as Food, until an alert customs official realised that they contained high proportions of easily extractable hallucinogens. Galcop regulations reclassifying them were rapidly introduced.", [[159, 189, 116, 206, 230, 202], [190, 18, 239, 169, 37, 7, 211], [106, 169, 138, 196, 226, 54, 14], [109], [22, 162, 157], [59, 190, 2], [240, 95, 171], [170, 203, 181, 220, 244, 88, 173, 132, 235, 140, 54]], [[21, 142, 118, 143, 154, 18, 214, 23, 235, 251, 50, 161, 233, 250, 213, 41, 173, 150, 242], [11, 40, 143, 8, 209, 174, 30, 10, 157, 126], [6, 234, 134, 36, 245, 68, 20, 191, 4, 99, 164, 43], [159, 127, 67, 95, 94, 255, 224, 31, 96, 223, 195, 222], [67, 227, 107, 42, 174, 219, 255, 29, 28, 182, 210, 40, 134, 146, 41, 58, 115, 49, 229, 37, 224, 31, 223], [197, 123, 27, 165, 40, 198, 58, 46, 229, 144, 196, 133, 140], [35, 131, 130, 225, 217, 102, 7, 167, 119, 1, 105, 96, 51, 202, 211, 52, 215, 73, 62], [228, 194, 198, 226, 12, 107, 254, 13, 65, 242, 5]]],
		["IN5", "narcotics", "Megaweed", "Edinsoian Maarleil, Gelaedian So and other so-called Megaweeds are widely sought after, and Galcop has so far failed to prevent their widespread dispersal. Agreements have been made, however, to prevent their growth outside their native planets.", [[127, 71], [250, 105], [112], [214], [209], [225, 150], [53, 194, 118, 227], [233, 204, 141, 20]], [[21, 142, 118, 143, 154, 18, 214, 23, 235, 251, 50, 161, 233, 250, 213, 41, 173, 150, 242], [11, 40, 143, 8, 209, 174, 30, 10, 157, 126], [6, 234, 134, 36, 245, 68, 20, 191, 4, 99, 164, 43], [159, 127, 67, 95, 94, 255, 224, 31, 96, 223, 195, 222], [67, 227, 107, 42, 174, 219, 255, 29, 28, 182, 210, 40, 134, 146, 41, 58, 115, 49, 229, 37, 224, 31, 223], [197, 123, 27, 165, 40, 198, 58, 46, 229, 144, 196, 133, 140], [35, 131, 130, 225, 217, 102, 7, 167, 119, 1, 105, 96, 51, 202, 211, 52, 215, 73, 62], [181, 228, 194, 198, 226, 12, 107, 254, 13, 65, 242, 5]]],
		["IN6", "narcotics", "Tobacco", "An old Terran narcotic, but still popular among some species. Its rapid toxic effects, especially on avians, make transport a risky business.", [[84, 180, 244, 116, 198, 16], [197, 69, 212, 165, 37, 68, 20], [163, 147, 35, 51], [59, 251, 131, 160, 171, 107], [0, 250, 192], [228, 57, 249, 100], [228, 164], [86]], [[21, 142, 118, 143, 154, 18, 214, 23, 235, 251, 50, 161, 233, 250, 213, 41, 173, 150, 242], [11, 40, 143, 8, 209, 174, 30, 10, 157, 126], [6, 234, 134, 36, 245, 68, 20, 191, 4, 99, 164, 43], [159, 127, 67, 95, 94, 255, 224, 31, 96, 223, 195, 222], [67, 227, 107, 42, 174, 219, 255, 29, 28, 182, 210, 40, 134, 146, 41, 58, 115, 49, 229, 37, 224, 31, 223], [197, 123, 27, 165, 40, 198, 58, 46, 229, 144, 196, 133, 140], [35, 131, 130, 225, 217, 102, 7, 167, 119, 1, 105, 96, 51, 202, 211, 52, 215, 73, 62], [181, 228, 194, 198, 226, 12, 107, 254, 13, 65, 242, 5]]],
		["IN7", "narcotics", "Vaccines", "A variety of anti-plague medicines. Transport of vaccines requires special permits to ensure that they reach the right destination.", [[237, 200, 171, 58, 17], [237, 98, 120, 136, 230, 129, 226], [246, 84, 251, 234, 228, 210, 75, 250, 245, 230, 218, 101, 31, 5, 242], [149, 189, 130, 244, 245, 138], [35, 246, 3, 245, 141, 188, 153], [104, 213, 245, 227, 12, 38, 99, 18, 49, 219, 242], [165, 230, 168, 162, 177, 178, 66, 19, 54], [159, 221, 75, 7, 122, 17, 59, 98, 169, 113]], [[35, 181, 90, 21, 206, 245, 87, 250, 213, 51, 199, 202, 69, 101, 66, 169, 175, 113], [197, 156, 142, 239, 68, 121, 158, 59, 37, 66, 10, 16, 19, 86, 126], [25, 67, 53, 194, 248, 183, 202, 12, 121, 69, 254, 180, 140, 54], [170, 90, 102, 79, 205, 212, 166, 99, 1, 141, 44, 6, 84, 253, 94, 146, 218, 202, 184, 135, 209, 145, 89, 24, 223], [238, 221, 203, 21, 165, 17, 12, 92, 215, 172, 174, 93, 0, 86, 148], [155, 17, 1, 141, 23, 44, 6, 149, 199, 58, 47, 153, 8, 4, 98, 56, 216, 178, 24, 62], [11, 90, 207, 154, 77, 222, 246, 50, 228, 253, 236, 202, 226, 15, 153, 112, 163, 60, 151, 45, 169, 224], [189, 107, 18, 88, 30, 197, 203, 194, 228, 61, 183, 236, 168, 220, 37, 24, 247, 195]]]
	];


	for (var i = 0; i < basictypes.length; i++) {
		var cdata = basictypes[i];
		var cargo = this.cargoClass1();
		cargo.ID = "CTE_CTS_" + cdata[0];
		cargo.genericType = cdata[1];
		cargo.specificType = cdata[2];
		cargo.desc = cdata[3];
		cargo.buySystems = cdata[4];
		cargo.sellSystems = cdata[5];
		worldScripts["CargoTypeExtension"].registerCargoType(cargo);
	}

	for (var i = 0; i < illegaltypes.length; i++) {
		var cdata = illegaltypes[i];
		var cargo = this.cargoClass2();
		cargo.ID = "CTE_CTS_" + cdata[0];
		cargo.genericType = cdata[1];
		if (cdata[1] != "firearms" && cdata[1] != "slaves" && cdata[1] != "narcotics") {
			cargo.illegal = 1;
		}
		cargo.specificType = cdata[2];
		cargo.desc = cdata[3];
		cargo.buySystems = cdata[4];
		cargo.sellSystems = cdata[5];
		worldScripts["CargoTypeExtension"].registerCargoType(cargo);
	}

	worldScripts["CargoTypeExtension"].registerPermit(this.name);

	this.hatesSitcoms = [[6, 28, 48, 101, 109], [20], [48, 182], [120, 165, 249], [153], [8, 76, 98, 254], [78, 241], []];
	this.hatesBlenders = [[74, 246, 252], [6, 47, 88, 219], [42, 141], [26, 243], [114, 161], [40], [92], [91]];
	this.hatesCasinos = [[112, 133, 145], [170, 244], [55, 61, 73, 166, 191, 213], [2, 51, 97, 168], [13, 83, 155], [9, 87, 187], [34, 47, 68, 142, 216], []];
}


// cargo class 1 uses the default settings for all goods
// (except volatility, which needs to be increased for pan-galactic cargoes)
this.cargoClass1 = function () {
	var cargo = new Object;
	cargo.slump = 8;
	cargo.unslump = 10;
	return cargo;
}

// cargo class 2 has a higher distance bonus for illegal goods smuggling
this.cargoClass2 = function () {
	var cargo = new Object;
	cargo.slump = 8;
	cargo.unslump = 10;
	cargo.sellDistance = 15;
	cargo.salvageMarket = 3; // so less chance of getting an easy ride
	return cargo;
}

this.illegalImports = function (sysid) {

	var ill = new Array;
	if (System.infoForSystem(galaxyNumber, sysid).inhabitants.indexOf("Feline") != -1) {
		ill.push("CTE_CTS_U1");
	}
	if (System.infoForSystem(galaxyNumber, sysid).inhabitants.indexOf("Rodent") != -1) {
		ill.push("CTE_CTS_U4");
		ill.push("CTE_CTS_F8");
	}
	if (System.infoForSystem(galaxyNumber, sysid).inhabitants.indexOf("Bird") != -1) {
		ill.push("CTE_CTS_IN6");
	}
	if (this.hatesSitcoms[galaxyNumber].indexOf(sysid) != -1) {
		ill.push("CTE_CTS_X1");
	}
	if (this.hatesBlenders[galaxyNumber].indexOf(sysid) != -1) {
		ill.push("CTE_CTS_M5");
	}
	if (this.hatesCasinos[galaxyNumber].indexOf(sysid) != -1) {
		ill.push("CTE_CTS_O2");
	}

	if (this.scrambledPseudoRandomNumber(sysid, 44) < 0.15) {
		var good = worldScripts["CargoTypeExtension"].specialCargoList[Math.floor(this.scrambledPseudoRandomNumber(sysid, 47) * worldScripts["CargoTypeExtension"].specialCargoList.length)];
		if (worldScripts["CargoTypeExtension"].cargoDefinition(good, "forbidExtension") == 0) {
			ill.push(good);
		}
	}

	return ill;
}

this.illegalExports = function (sysid) {
	var ill = new Array;
	if (System.infoForSystem(galaxyNumber, sysid).inhabitants.indexOf("Feline") != -1) {
		ill.push("CTE_CTS_U1");
	}
	if (System.infoForSystem(galaxyNumber, sysid).inhabitants.indexOf("Lizard") != -1) {
		ill.push("CTE_CTS_U5");
	}
	if (System.infoForSystem(galaxyNumber, sysid).inhabitants.indexOf("Rodent") != -1) {
		ill.push("CTE_CTS_U4");
		ill.push("CTE_CTS_F8");
	}

	if (this.scrambledPseudoRandomNumber(sysid, 45) < 0.15) {
		var good = worldScripts["CargoTypeExtension"].specialCargoList[Math.floor(this.scrambledPseudoRandomNumber(sysid, 48) * worldScripts["CargoTypeExtension"].specialCargoList.length)];
		if (worldScripts["CargoTypeExtension"].cargoDefinition(good, "forbidExtension") == 0) {
			ill.push(good);
		}
	}

	return ill;
}

this.scrambledPseudoRandomNumber = function (sysid, seed) {
	// TODO: not ideal since weeklyChaosAux isn't part of CTE API
	return worldScripts["CargoTypeExtension"].weeklyChaosAux((sysid * 2689) + (seed * 3371));
}

/* Permit API calls */

// used for special import/export rules

this.permitGossip = function () {
	var sysid = ((clock.days - 2084000) * 125) % 256;
	if (clock.hoursComponent > 12) {
		var rules = this.illegalImports(sysid);
		var keyword = "import";
	} else {
		var rules = this.illegalExports(sysid);
		var keyword = "export";
	}
	if (rules.length == 0) {
		return false;
	}

	return "* " + System.systemNameForID(sysid) + " bans the " + keyword + " of " + worldScripts["CargoTypeExtension"].cargoDefinition(rules[0], "specificType") + ".";
}

this.checkPermit = function (good, quantity, dryrun) {
	if (this.illegalExports(system.ID).indexOf(good) != -1) {
		return 1;
	}
	return 0;
}

this.checkImport = function (good, quantity, dryrun) {
	if (this.illegalImports(system.ID).indexOf(good) != -1) {
		return 1;
	}
	return 0;
}

this.describePermits = function () {
	var desc = new Array;
	var im = this.illegalImports(system.ID);
	for (var i = 0; i < im.length; i++) {
		desc.push("Import of " + worldScripts["CargoTypeExtension"].cargoDefinition(im[i], "specificType") + " is forbidden.");
	}
	var ex = this.illegalExports(system.ID);
	for (var i = 0; i < ex.length; i++) {
		desc.push("Export of " + worldScripts["CargoTypeExtension"].cargoDefinition(ex[i], "specificType") + " is forbidden.");
	}
	var dstr = desc.join("\n");
	if (dstr != "") { dstr += "\n"; }
	return dstr;
}
Scripts/cargotypetradernet.js
"use strict";
this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "2.0";
this.name = "CargoTypeExtension-TraderNet";
this.description = "Manages the TraderNet news";

this.tradernetbuffer = new Array;
this.maxbufferlength = 20;

this.startUp = function () {
	if (missionVariables.cargotypeextension_tradernet_messages && missionVariables.cargotypeextension_tradernet_messages.length > 0) {
		this.tradernetbuffer = missionVariables.cargotypeextension_tradernet_messages.split("|");
		var maxbuffer = 1;
		if (player.ship.equipmentStatus("EQ_CTE_TRADERNET") == "EQUIPMENT_OK" && clock.days <= missionVariables.cargotypeextension_tradernet) {
			maxbuffer = this.maxbufferlength;
		}
		if (this.tradernetbuffer.length > maxbuffer) {
			this.tradernetbuffer.splice(0, this.tradernetbuffer.length - maxbuffer);
			// trim spare elements in tradernet buffer
		}
	}

	// cleanup previous versions
	delete missionVariables.cargotypeextension_tradernet_waiting;
}

this.playerWillSaveGame = function () {
	if (this.tradernetbuffer.length > 0) {
		missionVariables.cargotypeextension_tradernet_messages = this.tradernetbuffer.join("|");
	} else {
		missionVariables.cargotypeextension_tradernet_messages = [];
	}
}

this.shipWillEnterWitchspace = function (cause) {
	if (player.ship.equipmentStatus("EQ_CTE_TRADERNET") == "EQUIPMENT_OK" && clock.days > missionVariables.cargotypeextension_tradernet) {
		this.subscriptionReminder();
		player.ship.removeEquipment("EQ_CTE_TRADERNET");
	}
	if (clock.days + 8 < missionVariables.cargotypeextension_tradernet) {
		missionVariables.cargotypeextension_tradenetrenew = 0;
	} else {
		missionVariables.cargotypeextension_tradenetrenew = 1;
	}

}

this.shipWillExitWitchspace = function () {
	if (!system.isInterstellarSpace && player.ship.equipmentStatus("EQ_CTE_TRADERNET") == "EQUIPMENT_OK") {
		this.profileGood();
	}
}

this.subscriptionReminder = function () {
	player.consoleMessage("Your TraderNet subscription has expired.", 10);
}

this.playerBoughtEquipment = function (equipment) {
	if (equipment == "EQ_CTE_TRADERNET") {
		missionVariables.cargotypeextension_tradernet = clock.days + 30;
	} else if (equipment == "EQ_CTE_TRADERNET_RENEWAL") {
		missionVariables.cargotypeextension_tradernet += 30;
		missionVariables.cargotypeextension_tradenetrenew = 0;
		player.ship.removeEquipment(equipment);
	}
}

this.equipmentDamaged = function (equipment) {
	if (equipment == "EQ_CTE_TRADERNET") {
		player.ship.setEquipmentStatus(equipment, "EQUIPMENT_OK");
	}
}

this.addTraderNet = function (msg) {
	this.tradernetbuffer.push(msg);
	if (this.tradernetbuffer.length > this.maxbufferlength) {
		var tmp = this.tradernetbuffer.shift(); // discard
	} else if (this.tradernetbuffer.length > 1 && !(player.ship.equipmentStatus("EQ_CTE_TRADERNET") == "EQUIPMENT_OK" && clock.days <= missionVariables.cargotypeextension_tradernet)) {
		// maximum buffer length is 1 if no equipment held
		var tmp = this.tradernetbuffer.shift(); // discard
	}
}

this.numMessages = function () {
	return this.tradernetbuffer.length;
}

this.getMessage = function (index) {
	return this.tradernetbuffer[this.tradernetbuffer.length - index];
}

this.getPic = function () {
	if (clock.days + 8 > missionVariables.cargotypeextension_tradernet) {
		missionVariables.cargotypeextension_tradenetrenew = 1;
		return "cte_tradenet-renewal.png";
	}
	var gn = galaxyNumber;
	if (clock.days % 10 == 0) { // rarely show another galaxy
		gn = system.ID % 8;
	}
	if (clock.days % 3 == 0) {
		return "cte_tradenet" + gn + "" + (1 + (system.ID % 7)) + ".png";
	}
	return "cte_tradenet" + gn + "0.png";
}

/*this.getBufferedMessages = function() {
		var msg = "";
		var allowedlen = 680;
		while (tradernetbuffer.length > 0 && this.tradernetbuffer[0].length < (allowedlen-80)) {
				if (msg != "") {
						allowedlen -= 80;
						msg += "\n\n";
				} 
				var tmp = this.tradernetbuffer.shift();
				allowedlen -= tmp.length;
				msg += tmp;
		}
		return msg;
}*/



/*this.callSnoopers = function() {
		if (!worldScripts.snoopers) {
				return;
		}
		if (this.cbwait) {
				return;
		}
		var msg = this.getBufferedMessages();
		if (msg == "") {
				return;
		}

		worldScripts.CargoTypeExtension.debug(msg);

		var obj = new Object;
		obj.ID = this.name;
		obj.Message = msg;
		obj.Priority = 1;
		if (clock.days + 8 < missionVariables.cargotypeextension_tradernet) {
				var gn = galaxyNumber;
				if (Math.random() < 0.1) { // rarely show another galaxy
						gn = Math.floor(Math.random()*8);
				}
				obj.Pic = "cte_tradenet"+gn+"0.png";
				if (Math.random() < 0.3) {
						obj.Pic = "cte_tradenet"+gn+""+Math.floor(1+(Math.random()*7))+".png";
				}
				missionVariables.cargotypeextension_tradenetrenew = 0;
		} else {
				obj.Pic = "cte_tradenet-renewal.png";
				missionVariables.cargotypeextension_tradenetrenew = 1;
		}

		worldScripts.snoopers.insertNews(obj);
		this.cbwait = true;
}

this.newsDisplayed = function(str) {
		this.cbwait = false;
}*/

this.profileGood = function () {
	var cte = worldScripts["CargoTypeExtension"];
	var good = cte.extendableCargo("any");

	var message = "Know Your Goods: " + cte.cargoDefinition(good, "specificType") + "\n";
	message += cte.cargoDefinition(good, "desc") + "\n";
	var exports = cte.cargoDefinition(good, "buySystems")[galaxyNumber];
	var imports = cte.cargoDefinition(good, "sellSystems")[galaxyNumber];
	var unit = cte.getCommodityUnit(cte.cargoDefinition(good, "genericType"));
	var dm = cte.defaultMarketInfo();
	message += "Buy at: " + this.systemListing(exports) + " for around " + this.approximately(cte.cargoPriceExport(good, 1, dm) / 10) + " ₢/" + unit + "\n";
	message += "Sell at: " + this.systemListing(imports) + " for around " + this.approximately(cte.cargoPriceImport(good, 1, dm) / 10) + " ₢/" + unit + "\n";

	this.addTraderNet(message);
}

this.approximately = function (price) {
	return Math.round(price / 10) * 10;
}

this.systemListing = function (list) {
	var copylist = list.slice(0);
	var i = 0;
	var names = [];
	while (i++ < 3 && copylist.length > 0) {
		var sysid = copylist.splice(Math.floor(Math.random() * copylist.length), 1);
		names.push(System.infoForSystem(galaxyNumber, sysid).name);
	}
	if (names.length == 0) {
		return "Other galaxies";
	} else if (names.length == 1) {
		return names[0];
	} else if (names.length == 2) {
		return names[0] + " or " + names[1];
	} else {
		if (copylist.length > 0) {
			return names[0] + ", " + names[1] + ", " + names[2] + ", etc.";
		} else {
			return names[0] + ", " + names[1] + " or " + names[2];
		}
	}
}