Back to Index Page generated: May 8, 2024, 6:16:03 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.13 1.13
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/7/73/MiningContracts_1.13.oxz n/a
License Ms-PL Ms-PL
File Size n/a
Upload date 1702430333

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:
 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://aegidian.org/bb/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.allowAwardEquipment = function(eqKey, ship, context)
{
	return context != "purchase" || ship.dockedStation.primaryRole == "rockhermit";
};
Scripts/mc_descrambler.js
"use strict";

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

	if (ws.MC_subscr < clock.seconds)
	{
		player.consoleMessage("You have no valid descrambling 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((active > 0 ? active : "no") + " scrambled beacons detected.");
}

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

	var msg = ws.MC_subscr < clock.seconds ?
		"You have no valid descrambling keys." :
		"Your descrambling keys are valid till " + 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.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: "Mining Contracts",
		category: "Contracts",
		summary: "Sign up for local mining operations.",
		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(
				"The Mining Association of " +
				this.MC_system +
				" has put a bounty of " +
				formatCredits(50, true, true) +
				" on your head.", player.ship);
		}
	}

	this.MC_setInstructions();
}

this.MC_stationPrimRoles = {
	"rockhermit": true,
	"rockhermit-chaotic": true,
	"rockhermit-pirate": false
}

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 =
				contract.amount +
				"t of " +
				contract.cargo +
				" before " +
				this.MC_clockStringToMinutes(contract.deadline);

			instructions.push(msg);
		}
	}

	this.MC_active = instructions.length > 0;

	if (this.MC_active) instructions.unshift("Mining Contracts:");
	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 ? "\n\nYou are in debt to the Mining Association of " + this.MC_system +
		" (" + formatCredits(this.MC_debt, true, true) + ")" : "";

	var number = this.MC_contracts.length;

	var parameters = {
		title: "Mining Contracts",
		message:
			"The Rock Hermit main lounge is as dusty as everything in this place. " +
			"A couple of grumpy miners are playing cards at the only table in the corner. " +
			"A balding clerk is sleeping at the front desk of the local Mining Association, " +
			"and large blackboard with a list of current contracts is hanging on the wall behind him. " +
			"Suddenly an ancient fax machine standing on the desk squeals. " +
			"Dragged out of the hay, the clerk swears, wipes one line from a blackboard and falls back to sleep. " +
			"On the opposite wall you notice numerous square hatches layered in two rows under a flaking advert:\n\n" +
			"RESTING PODS! NO TIME LIMIT! ONLY " + formatCredits(this.MC_waitPrice, true, true) + " PER USE!" +
			debtMsg,
		choices: {
			"WAIT": "Wait for new contracts (" + formatCredits(this.MC_waitPrice, true, true) + ")",
			"XXIT": "Exit Mining Contracts"
		},
		allowInterrupt: true
	}

	for (var i = 0; i < number; i++)
	{
		var contract = this.MC_contracts[i];
		var status = ["OFFER", "TAKEN", "BLOWN"][contract.signed];
		parameters.choices["C" + i]
			= status + ": "
			+ contract.amount + "t of "
			+ contract.cargo + " before "
			+ this.MC_clockStringToMinutes(contract.deadline) + " for "
			+ formatCredits(contract.reward, true, true);
	}

	if (this.MC_debt > 0) parameters.choices["DEBT"] = "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("NOT ENOUGH CREDITS");
		else
		{
			player.commsMessage(formatCredits(this.MC_debt, true, true) + " deducted");
			player.credits -= this.MC_debt;
			this.MC_debt = 0;
		}

		this.MC_interface();
		return;
	}

	if (choice == "WAIT")
	{
		if (player.credits < this.MC_waitPrice)
		{
			player.consoleMessage("NOT ENOUGH CREDITS");
			this.MC_interface();
			return;
		}

		player.commsMessage(formatCredits(this.MC_waitPrice, true, true) + " deducted");
		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 = "Mining Association of " + this.MC_system;
	var warning = "\n\nn.b. The " + mediator + " reserves the right to set a bounty on the heads of defaulting contractors.";

	var terms =
		"Terms and conditions:\n" +
		"Payment is calculated and carried out after one of three events - " +
		"full delivery of required cargo before due time, " +
		"premature contract termination initiated by contractor or " +
		"coming of due time. " +
		"If forfeit penalty exceeds fee, the difference becomes contractor's debt liability to the " + mediator + ". " +
		"Contract fee meant for indebted contractor will be detained for repayment first. " +
		"Contractors leaving system before completion of obligations or debt retirement will be recognised as defaulters." +
		warning;

	function debtMsg(reward)
	{
		return debt > 0 ?
			"\n\nDue to an unpaid debt to the " + mediator + ", we are withholding " +
			formatCredits(Math.min(reward, debt), true, true) +
			" from your reward." : "";
	}

	switch (contract.signed)
	{
		case 0:
			header = "Contractual Offer";
			descr = "Subject of delivery";
			deliv = contract.amount;
			ps = terms;
			id = "SIGN";
			option = "Sign contract";
			break;

		case 1:
			descr = "Delivered";
			deliv = (contract.gross - contract.amount) + "/" + contract.gross + " ";

			if (contract.amount > 0)
			{
				header = "Operating Contract";
				ps = terms;
				id = "CARGO";
				option = "Dispatch cargo (" + Math.min(contract.amount, manifest[contract.cargo]) + "t)";
			}
			else
			{
				header = "Contract Completion";
				ps =
					"You have successfully completed a contract. " +
					"According to the terms of the contract, your reward is " + formatCredits(contract.reward, true, true) + ". " +
					"We are looking forward to working with you in the future." + debtMsg(contract.reward);
				id = "REWARD";
				option = "Collect reward";
			}

			break;

		case 2:
			header = "Terminated Contract";
			descr = "Delivered";
			deliv = (contract.gross - contract.amount) + "/" + contract.gross + " ";
			var balance = contract.reward - contract.amount * contract.penalty;

			if (balance < 0)
			{
				ps =
					"You have failed to perform a contract and the forfeit penalty exceeds your fee. " +
					"According to the terms of the contract, your debt to the " + mediator + " is increased by " + formatCredits(-balance, true, true) + ". " +
					"This debt may be liquidated with a cash deposit. " +
					"Until full debt liquidation all your future contract fees will be detained for repayment first. " +
					"If you leave system before full debt liquidation, you will be recognised as defaulter. " +
					"Please, do not delay your payments." +
					warning;
				id = "DEBT";
				option = "Take debt notification";
			}
			else
			{
				ps =
					"You have failed to perform a contract fully and the forfeit penalty has been deducted from your fee. " +
					"According to the terms of the contract, your reward is " + formatCredits(balance, true, true) + ". " +
					"Please, be more diligent next time." + debtMsg(balance);
				id = "REWARD";
				option = "Collect reward";
			}

			break;
	}

	var parameters = {
		title: header,
		message:
			"Ordering party: " + contract.company + "\n" +
			"Mediator: Mining Association of " + this.MC_system + "\n" +
			descr + ": " + deliv + "t of " + contract.cargo + "\n" +
			"Due time: " + clock.clockStringForTime(contract.deadline) + "\n" +
			"Fee: " + formatCredits(contract.reward, true, true) + "\n" +
			"Forfeit penalty: " + formatCredits(contract.penalty, true, true) + " per ton of shortage\n" +
			"\n" +
			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]] = "Terminate contract";

		if (contract.signed == 0)
		{
			parameters.choices[next] = "Next unsigned contract";
			parameters.choices[prev] = "Previous unsigned contract";
		}
		else if (contract.signed == 1)
		{
			parameters.choices[next] = "Next unfinished contract";
			parameters.choices[prev] = "Previous unfinished contract";
		}

		parameters.choices["4_BACK"] = "Back to the list";
		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("CONTRACT 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(formatCredits(reward, true, true) + " received");
			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(amount + "t of " + contract.cargo + " dispatched");
				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("no " + contract.cargo + " on board");

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

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

			var parameters = {
				title: "Mining Contracts",
				message: "Are you sure you want to prematurely terminate the contract with " + contract.company + "?",
				choices: {},
				allowInterrupt: true
			}

			parameters.choices["0_TERMYES:" + idx] = "Yes";
			parameters.choices["1_TERMNO:" + idx] = "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("CONTRACT 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_companyPatterns = [
	"? Company Limited",
	"? plc",
	"? S.A.",
	"? Group",
	"? Gold Corporation",
	"? Inc.",
	"Potash Corporation of ? Inc.",
	"? Copper Corporation",
	"MMC ? Nickel",
	"? Platinum Limited",
	"? Mining Corporation",
	"Aluminum Corporation of ? Limited",
	"? Platinum Holdings Limited",
	"? Mining Limited",
	"?Gold",
	"? Goldfields Corporation",
	"National Copper Corporation of ?",
	"? Industries Ltd."
];

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();
}