Back to Index Page generated: Apr 20, 2026, 4:52:50 AM

Expansion Mining Contracts

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Excavate asteroids for reward with deadline conditions. Excavate asteroids for reward with deadline conditions.
Identifier oolite.oxp.Diagoras.MiningContracts oolite.oxp.Diagoras.MiningContracts
Title Mining Contracts Mining Contracts
Category Activities Activities
Author Diagoras Diagoras
Version 1.15 1.15
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Mining_Contracts_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/3/34/MiningContracts_1.15.oxz n/a
License Ms-PL Ms-PL
File Size n/a
Upload date 1776471989

Documentation

Also read http://wiki.alioth.net/index.php/Mining%20Contracts

MiningContracts readme.txt

Mining Contracts OXP

This extension allows you to make some fast cash working as independent contractor for big mining corporations. You can find new mission type in Interfaces (F4) screen of Rock Hermits. Have your mining laser and fuel scoop ready.

Dependencies:
Oolite v1.82 or later (haven't checked on anything older)
Icesteroids if you want to mine them for alloys
Start Choices if you want to begin game on Mining Transporter.
Asteroid Tweaks if you want asteroids respawing without saving/loading

Instructions:
Unzip the file, and then move the folder named ".oxp" into the AddOns directory of your Oolite installation.
Hold down the Shift key when you start the game first time until the splash screen appear.

License:
This work is licensed under the Microsoft Public License (Ms-PL).
If you are re-using any piece of this OXP, please let me know by sending an e-mail to pdunan@gmail.com.

Changelog:
 2026.04.18. v1.15  Moved more text into descriptions.plist for easier localisation.
 2026.04.17. v1.14  Moved all text into descriptions.plist for easier localisation. Added more variety to contract page.
 2018.03.22. v1.13  Moved global functions into the local namespace, spelling corrections.
 2015.11.11. v1.12  Made average contract difficulty dependent on system government type, also chaotic rock hermits are giving contracts too now
 2015.11.03. v1.11  Bugfix release - docking with rock hermits after witchjump do not invalidates descrambilng keys anymore
 2015.10.30. v1.10  Obscure race condition bug in waiting for new contract squashed, also balance adjustments
 2015.10.28. v1.9   Shrinked contract details screen for smaller resolutions and some balance adjustments
 2015.10.27. v1.8   Mining Beacon Descrambler addon to ASC is now purchasable at all Rock Hermits for clean commanders
 2015.10.26. v1.7   Signing mining contract now adds Rock Hermit to your Advanced Space Compass, also some balance adjustments
 2015.10.19. v1.6   Waiting at the station for new contracts is not free now
 2015.10.19. v1.5   Various improvements in interface, added possibility of waiting at the station for new contract, slightly adjusted balance
 2015.10.14. v1.4   Iceteroids are not required now (thanks to Diziet Sma) and contracts for alloys made less frequent
 2015.10.12. v1.3   Added compatibility with Asteroid Tweaks by spara
 2015.10.11. v1.2   Added tracking of cargo amounts required to complete signed contracts to the Missions screen (F5F5)
 2015.10.10. v1.1   Fixed saving/loading (thanks to Norby)
 2015.10.06. v1.0   First version as a result of discussion in this topic: http://bb.oolite.space/viewtopic.php?f=4&t=13382&p=243101#p243094

Equipment

Name Visible Cost [deci-credits] Tech-Level
Mining Beacon Descrambler yes 500 1+

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
Scripts/mc_conditions.js
"use strict";
this.name = "MC_conditions";

this.allowAwardEquipment = function (eqKey, ship, context) {
	return context != "purchase" || ship.dockedStation.primaryRole == "rockhermit";
};
Scripts/mc_descrambler.js
"use strict";
this.name = "MC_descrambler";

this.activated = function () {
	var ws = worldScripts["miningcontracts"];

	if (ws.MC_subscr < clock.seconds) {
		player.consoleMessage(expandDescription("[mc_no_keys]"));
		return;
	}

	var hermits = system.shipsWithPrimaryRole("rockhermit");
	var number = hermits.length;
	var active = 0;

	for (var i = 0; i < number; i++) {
		// if (system.countShipsWithRole("asteroid", hermits[i], 25600) < 5) continue;
		hermits[i].beaconCode = hermits[i].displayName;
		active++;
	}

	player.consoleMessage(expandDescription("[mc_activated]", { remain: (active > 0 ? active : "no") }));
}

this.mode = function () {
	var ws = worldScripts["miningcontracts"];

	var msg = ws.MC_subscr < clock.seconds ?
		expandDescription("[mc_no_keys]") :
		expandDescription("[mc_keys_valid]", {until: MC_clockStringToMinutes(ws.MC_subscr)});

	player.consoleMessage(msg);
}

function MC_clockStringToMinutes(time) {
	return clock.clockStringForTime(time).slice(4, 13);
}
Scripts/miningcontracts.js
"use strict";
this.name = "miningcontracts";
this.author = "Diagoras";
this.copyright = "2015 Maxim Savchenko";
this.licence = "Ms-PL";
this.description = "Excavate asteroids for reward with deadline conditions.";

this.MC_stationPrimRoles = {
	"rockhermit": true,
	"rockhermit-chaotic": true,
	"rockhermit-pirate": false
}
this.MC_companyPatterns = expandDescription("[mc_company_patterns]").split("|");

this.startUp = function () {
	this.MC_contracts = [];

	if (missionVariables.MC_initialized) {
		this.MC_system = missionVariables.MC_system;
		this.MC_clock = missionVariables.MC_clock;
		this.MC_debt = missionVariables.MC_debt;
		this.MC_subscr = missionVariables.MC_subscr;
		var s = missionVariables.MC_contracts;
		if (s && s.length > 0) this.MC_contracts = JSON.parse(s);
	} else {
		this.MC_system = "";
		this.MC_clock = 0;
		this.MC_debt = 0;
		this.MC_subscr = 0;
	}
}

this.startUpComplete = function () {
	this.MC_countAsteroids();
	this.MC_setInstructions();
	if (player.ship.docked) this.shipDockedWithStation(player.ship.dockedStation);
}

this.shipDockedWithStation = function (station) {
	if (!this.MC_stationPrimRoles[station.primaryRole]) return;

	if (this.MC_system != system.name) {
		this.MC_system = system.name;
		this.MC_clock = 0;
		this.MC_contracts = [];
		this.MC_debt = 0;
	}

	station.setInterface("MiningContracts-bm", {
		title: expandDescription("[mc_contracts_title]"),
		category: expandDescription("[interfaces-category-employment]"),
		summary: expandDescription("[mc_contracts_summary]"),
		callback: this.MC_interface.bind(this)
	});
}

this.playerWillSaveGame = function (message) {
	missionVariables.MC_initialized = 1;
	missionVariables.MC_system = this.MC_system;
	missionVariables.MC_clock = this.MC_clock;
	missionVariables.MC_debt = this.MC_debt;
	missionVariables.MC_subscr = this.MC_subscr;
	missionVariables.MC_contracts = JSON.stringify(this.MC_contracts);
}

this.shipExitedWitchspace = function () {
	// this.MC_markHermits();
	this.MC_countAsteroids();

	var number = this.MC_contracts.length;
	var total = this.MC_debt;

	for (var i = 0; i < number; i++) {
		var contract = this.MC_contracts[i];
		if (contract.signed) total += contract.amount * contract.penalty - contract.reward;
	}

	this.MC_contracts = [];
	this.MC_debt = 0;
	this.MC_clock = 0;

	if (total > 0) {
		player.ship.setBounty(player.bounty + 50, "contract obligations");
		var targets = system.shipsWithPrimaryRole("buoy-witchpoint");
		if (targets.length > 0) {
			targets[0].commsMessage(
				expandDescription("[mc_applied_bounty]", { system: this.MC_system, bounty: formatCredits(50, true, true) }), player.ship);
		}
	}

	this.MC_setInstructions();
}

this.MC_countAsteroids = function () {
	this.MC_icequota = system.countShipsWithRole("staer9_asteroid") / system.countShipsWithRole("asteroid");
}

this.MC_setInstructions = function () {
	var instructions = [];
	var number = this.MC_contracts.length;

	for (var i = 0; i < number; i++) {
		var contract = this.MC_contracts[i];

		if (contract.signed == 1) {
			var msg =
				expandDescription("[mc_contract_signed]", { amount: contract.amount, commodity: displayNameForCommodity(contract.cargo), deadline: this.MC_clockStringToMinutes(contract.deadline) });

			instructions.push(msg);
		}
	}

	this.MC_active = instructions.length > 0;

	if (this.MC_active) instructions.unshift(expandDescription("[mc_instructions_header]"));
	else instructions = null;

	mission.setInstructions(instructions);
}

this.MC_clockStringToMinutes = function (time) {
	return clock.clockStringForTime(time).slice(4, 13);
}

this.MC_waitPrice = 5;

this.MC_interfaceNoGen = function () {
	var debtMsg =
		this.MC_debt > 0 ? expandDescription("[mc_debt]", { system: this.MC_system, debt: formatCredits(this.MC_debt, true, true) }) : "";

	var number = this.MC_contracts.length;

	var parameters = {
		title: expandDescription("[mc_mission_title]"),
		message:
			expandDescription("[mc_first_line][mc_second_line][mc_third_line]") +
			expandDescription("[mc_fax_machine]") +
			expandDescription("[mc_clerk]") +
			expandDescription("[mc_seventh_line]") + expandDescription("[mc_per_use]", { amount: formatCredits(this.MC_waitPrice, true, true) }) +
			debtMsg,
		choices: {
			"WAIT": expandDescription("[mc_wait]", { price: formatCredits(this.MC_waitPrice, true, true) }),
			"XXIT": expandDescription("[mc_exit]")
		},
		allowInterrupt: true
	}

	for (var i = 0; i < number; i++) {
		var contract = this.MC_contracts[i];
		var status = expandDescription("[mc_status_list]").split("|")[contract.signed];
		parameters.choices["C" + i]
			= expandDescription("[mc_option]", {
				status: status,
				amount: contract.amount,
				commodity: displayNameForCommodity(contract.cargo),
				deadline: this.MC_clockStringToMinutes(contract.deadline),
				price: formatCredits(contract.reward, true, true)
			});
	}

	if (this.MC_debt > 0) parameters.choices["DEBT"] = expandDescription("[mc_repay_debt]");

	mission.runScreen(parameters, this.MC_contractDetails.bind(this));
}

this.MC_interface = function () {
	this.MC_generateContracts();
	this.MC_interfaceNoGen();
}

this.MC_contractDetails = function (choice) {
	if (choice == null) return;

	if (choice == "DEBT") {
		if (this.MC_debt > player.credits) player.consoleMessage(expandDescription("[mc_not_enough_cash]"));
		else {
			player.commsMessage(expandDescription("[mc_paid]", { amount: formatCredits(this.MC_debt, true, true) }));
			player.credits -= this.MC_debt;
			this.MC_debt = 0;
		}

		this.MC_interface();
		return;
	}

	if (choice == "WAIT") {
		if (player.credits < this.MC_waitPrice) {
			player.consoleMessage(expandDescription("[mc_not_enough_cash]"));
			this.MC_interface();
			return;
		}

		player.commsMessage(expandDescription("[mc_paid]", { amount: formatCredits(this.MC_waitPrice, true, true) }));
		player.credits -= this.MC_waitPrice;
		this.MC_waitForContract();
		this.MC_interfaceNoGen();
		return;
	}

	if (choice == "XXIT") return;

	var cid = choice.replace("C", "").split(":");
	var contract = this.MC_contracts[cid[0]];
	var debt = this.MC_debt;
	var header, descr, deliv, ps, id, option;
	var mediator = expandDescription("[mc_mediator]", { system: this.MC_system });
	var warning = expandDescription("[mc_warning]", { mediator: mediator });

	var terms = expandDescription("[mc_terms]", { mediator: mediator, warning: warning });

	function debtMsg(reward) {
		return debt > 0 ?
			expandDescription("[mc_unpaid_withholding]", { mediator: mediator, amount: formatCredits(Math.min(reward, debt), true, true) }) : "";
	}

	switch (contract.signed) {
		case 0:
			header = expandDescription("[mc_contract_signed_header]");
			descr = expandDescription("[mc_contract_subject]");
			deliv = contract.amount;
			ps = terms;
			id = "SIGN";
			option = expandDescription("[mc_contract_option]");
			break;

		case 1:
			descr = expandDescription("[mc_contract_delivered]");
			deliv = (contract.gross - contract.amount) + "/" + contract.gross + " ";

			if (contract.amount > 0) {
				header = expandDescription("[mc_contract_operating]");
				ps = terms;
				id = "CARGO";
				option = expandDescription("[mc_contract_dispatch]", { amount: Math.min(contract.amount, manifest[contract.cargo]) });
			} else {
				header = expandDescription("[mc_contract_completed]");
				ps = expandDescription("[mc_contract_complete_details]", { reward: formatCredits(contract.reward, true, true), debt: debtMsg(contract.reward) })
				id = "REWARD";
				option = expandDescription("[mc_contract_collect]");
			}

			break;

		case 2:
			header = expandDescription("[mc_contract_terminated]");
			descr = expandDescription("[mc_contract_delivered]");
			deliv = (contract.gross - contract.amount) + "/" + contract.gross + " ";
			var balance = contract.reward - contract.amount * contract.penalty;

			if (balance < 0) {
				ps = expandDescription("[mc_contract_failed_debt]", { mediator: mediator, debt: formatCredits(-balance, true, true), warning: warning });
				id = "DEBT";
				option = expandDescription("[mc_contract_failed_option]");
			} else {
				ps = expandDescription("[mc_contract_failed_reward]", { reward: formatCredits(balance, true, true), message: debtMsg(balance) });
				id = "REWARD";
				option = expandDescription("[mc_contract_collect]");
			}

			break;
	}

	var parameters = {
		title: header,
		message:
			expandDescription("[mc_contract_description]", {
				company: contract.company,
				system: this.MC_system,
				descr: descr,
				amount: deliv,
				commodity: displayNameForCommodity(contract.cargo),
				deadline: clock.clockStringForTime(contract.deadline),
				reward: formatCredits(contract.reward, true, true),
				penalty: formatCredits(contract.penalty, true, true),
				postscript: ps
			}),
		choices: {},
		allowInterrupt: id != "REWARD" && id != "DEBT"
	}

	parameters.choices["0_" + id + ":" + cid[0]] = option;
	var next = "2_NEXT:" + cid[0];
	var prev = "3_PREV:" + cid[0];

	if (parameters.allowInterrupt) {
		if (id == "CARGO") parameters.choices["1_TERMINATE:" + cid[0]] = expandDescription("[mc_terminate]");

		if (contract.signed == 0) {
			parameters.choices[next] = expandDescription("[mc_contract_next_unsigned]");
			parameters.choices[prev] = expandDescription("[mc_contract_prev_unsigned]");
		} else if (contract.signed == 1) {
			parameters.choices[next] = expandDescription("[mc_contract_next_unfinished]");
			parameters.choices[prev] = expandDescription("[mc_contract_prev_unfinished]");
		}

		parameters.choices["4_BACK"] = expandDescription("[mc_back]");
		if (id == "SIGN") parameters.initialChoicesKey = "4_BACK";
	}

	switch (cid[1]) {
		case "N":
			parameters.initialChoicesKey = next;
			break;

		case "P":
			parameters.initialChoicesKey = prev;
			break;
	}

	mission.runScreen(parameters, this.MC_contractAction.bind(this));
}

this.MC_contractAction = function (choice) {
	if (choice == null) return;
	var ch = choice.split(":");

	switch (ch[0]) {
		case "0_SIGN":
			var idx = ch[1];
			this.MC_contracts[idx].signed = 1;
			this.MC_contracts[idx].gross = this.MC_contracts[idx].amount;
			player.consoleMessage(expandDescription("[mc_signed]"));
			this.MC_setInstructions();
			this.MC_contractDetails("C" + idx);
			break;

		case "0_DEBT":
			var idx = ch[1];
			var contract = this.MC_contracts[idx];
			var debt = contract.amount * contract.penalty - contract.reward;
			this.MC_debt += debt;
			this.MC_contracts.splice(idx, 1);
			this.MC_interface();
			break;

		case "0_REWARD":
			var idx = ch[1];
			var contract = this.MC_contracts[idx];
			var reward = contract.reward - contract.amount * contract.penalty;
			var repayment = Math.min(reward, this.MC_debt);
			reward -= repayment;
			this.MC_debt -= repayment;
			player.commsMessage(expandDescription("[mc_rewarded]", { reward: formatCredits(reward, true, true) }));
			player.credits += reward;
			this.MC_contracts.splice(idx, 1);
			this.MC_setInstructions();
			this.MC_interface();
			break;

		case "0_CARGO":
			var idx = ch[1];
			var contract = this.MC_contracts[idx];
			var amount = Math.min(contract.amount, manifest[contract.cargo]);

			if (amount > 0) {
				player.commsMessage(expandDescription("[mc_dispatched]", { amount: amount, commodity: displayNameForCommodity(contract.cargo) }));
				this.MC_contracts[idx].amount -= amount;
				manifest[contract.cargo] -= amount;
				this.MC_subscr = Math.max(this.MC_subscr, this.MC_floorScale(clock.seconds, 900)) + amount * 900;
				this.MC_setInstructions();
			}
			else player.consoleMessage(expandDescription("[mc_none]", { commodity: displayNameForCommodity(contract.cargo) }));

			this.MC_contractDetails("C" + idx);
			break;

		case "1_TERMINATE":
			var idx = ch[1];
			var contract = this.MC_contracts[idx];

			var parameters = {
				title: expandDescription("[mc_mission_title]"),
				message: expandDescription("[mc_terminate_confirm]", { company: contract.company }),
				choices: {},
				allowInterrupt: true
			}

			parameters.choices["0_TERMYES:" + idx] = expandDescription("[mc_yes]");
			parameters.choices["1_TERMNO:" + idx] = expandDescription("[mc_no]");
			parameters.initialChoicesKey = "1_TERMNO:" + idx;
			mission.runScreen(parameters, this.MC_contractAction.bind(this));
			break;

		case "0_TERMYES":
			var idx = ch[1];
			this.MC_contracts[idx].signed = 2;
			player.consoleMessage(expandDescription("[mc_terminated]"));
			this.MC_setInstructions();
			this.MC_contractDetails("C" + idx);
			break;

		case "1_TERMNO":
			var idx = ch[1];
			this.MC_contractDetails("C" + idx);
			break;

		case "2_NEXT":
			var idx = ch[1];
			var signed = this.MC_contracts[idx].signed;

			while (true) {
				idx++;
				if (idx >= this.MC_contracts.length) idx = 0;
				if (this.MC_contracts[idx].signed == signed) break;
			}

			this.MC_contractDetails("C" + idx + ":N");
			break;

		case "3_PREV":
			var idx = ch[1];
			var signed = this.MC_contracts[idx].signed;

			while (true) {
				if (idx <= 0) idx = this.MC_contracts.length;
				idx--;
				if (this.MC_contracts[idx].signed == signed) break;
			}

			this.MC_contractDetails("C" + idx + ":P");
			break;

		case "4_BACK":
			this.MC_interface();
			break;
	}
}

this.MC_randomElement = function (array) {
	return array[Math.floor(Math.random() * array.length)];
}

this.MC_randomExponential = function () {
	return -Math.log(1 - Math.random());
}

this.MC_randomNormals = function () {
	var u = Math.sqrt(this.MC_randomExponential());
	var v = 2 * Math.PI * Math.random();

	return {
		x: u * Math.cos(v),
		y: u * Math.sin(v)
	}
}

this.MC_ceilScale = function (value, scale) {
	return scale * Math.ceil(value / scale);
}

this.MC_floorScale = function (value, scale) {
	return scale * Math.floor(value / scale);
}

this.MC_companyName = function () {
	return this.MC_randomElement(this.MC_companyPatterns).replace("?", randomName());
}

this.MC_randomCargo = function () {
	return Math.random() < this.MC_icequota ? "alloys" : "minerals";
}

this.MC_logistic = function (x) {
	return 1 / (1 + Math.exp(x));
}

this.MC_uniform = function (x, y) {
	return x + (y - x) * Math.random();
}

this.MC_amountMu = 3;
this.MC_timeMu = 6;
this.MC_rateLambda = 4;
this.MC_generationRate = ((Math.exp(this.MC_amountMu) + 0.5) * Math.exp(this.MC_timeMu) + 450) / this.MC_rateLambda;

this.MC_createContract = function (time) {
	var rn = this.MC_randomNormals();
	rn.y += (system.government - 3.5) / 8;
	var amount = Math.ceil(Math.exp(rn.x + this.MC_amountMu));
	var deadline = this.MC_ceilScale(time + amount * Math.exp(rn.y / 2 + this.MC_timeMu), 900);
	var expires = this.MC_uniform(time, deadline);
	var cargo = this.MC_randomCargo();
	var price = player.ship.dockedStation.market[cargo].price / 10;
	var reward = this.MC_ceilScale(this.MC_floorScale(amount * price, 10) * this.MC_logistic(rn.y), 10);

	var contract = {
		company: this.MC_companyName(),
		expires: expires,
		deadline: deadline,
		cargo: cargo,
		amount: amount,
		reward: reward,
		penalty: price,
		signed: 0
	}

	this.MC_contracts.push(contract);
}

this.MC_generateContracts = function () {
	var prevClock = this.MC_clock;
	var nextClock = clock.seconds;
	var pregen = 96 * 3600;
	if (nextClock - prevClock > pregen) prevClock = nextClock - pregen;

	while (true) {
		prevClock += this.MC_randomExponential() * this.MC_generationRate;
		if (prevClock > nextClock) break;
		this.MC_createContract(prevClock);
	}

	this.MC_clock = nextClock;
	this.MC_expireContracts();
}

this.MC_waitForContract = function () {
	this.MC_clock += this.MC_randomExponential() * this.MC_generationRate;
	this.MC_createContract(this.MC_clock);
	var time = this.MC_clock - clock.seconds;
	if (time > 0) clock.addSeconds(time);
	this.MC_expireContracts();
}

this.MC_expireContracts = function () {
	var number = this.MC_contracts.length;
	var updated = [];

	for (var i = 0; i < number; i++) {
		var contract = this.MC_contracts[i];
		if (contract.signed == 1 && contract.deadline < clock.seconds) contract.signed = 2;
		if (contract.signed || contract.expires > this.MC_clock) updated.push(contract);
	}

	this.MC_contracts = updated;
	this.MC_setInstructions();
}