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