summaryrefslogtreecommitdiff
path: root/nodejs/automation/contracting.ts
blob: fa9a4c45730dc35315e758c95ff9f6334471c279 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { Contract } from '../model/contract.ts';
import { Ship } from '../model/ship.ts';
import * as mining from './mining.js';
import * as selling from './selling.js';
import * as dbContracts from '../database/contracts.ts';
import * as dbShips from '../database/ships.ts';
import * as api from '../lib/api.ts';
import * as contracts from '../lib/contracts.ts';
import * as libShips from '../lib/ships.ts';
import * as libSystems from '../lib/systems.ts';
import * as systems from '../lib/systems.ts';
import * as utils from '../lib/utils.ts';

export async function init(): Promise<void> {
	const ship = dbShips.getShips()[0]; // This should always be the command ship
	while(true) { // we use the fact that there can only be at most one active contract at a time
		const contracts = dbContracts.getContracts().filter(c => !c.fulfilled);
		let contract: Contract;
		if (contracts.length === 0) {
			contract = await libShips.negotiate(ship);
		} else {
			contract = contracts[0];
		}
		await run(contract, ship);
		await libShips.negotiate(ship);
	}
}

async function run(contract: Contract, ship: Ship): Promise<void> {
	await contracts.accept(contract);
	switch(contract.type) {
		case 'PROCUREMENT':
			if (contract.terms.deliver[0].tradeSymbol.match(/_ORE$/)) {
				await runOreProcurement(contract, ship);
			} else {
				await runTradeProcurement(contract, ship);
			}
			break;
		default:
			throw `Handling of contract type ${contract.type} is not implemented yet`;
	}
}

async function runOreProcurement(contract: Contract, ship: Ship): Promise<void> {
	const wantedCargo = contract.terms.deliver[0].tradeSymbol;
	const deliveryPoint = contract.terms.deliver[0].destinationSymbol;
	const asteroids = await systems.type(ship.nav.systemSymbol, 'ENGINEERED_ASTEROID');
	const asteroidSymbol = asteroids[0].symbol;
	while (!contract.fulfilled) {
		ship = dbShips.getShip(ship.symbol);
		const goodCargo = ship.cargo.inventory.filter(i => i.symbol === wantedCargo)[0]
		// what we do depends on where we are
		switch (ship.nav.waypointSymbol) {
			case asteroidSymbol:
				await mining.mineUntilFullFor(contract, ship, asteroidSymbol);
				await libShips.navigate(ship, deliveryPoint);
				break;
			case deliveryPoint:
				if (goodCargo !== undefined) { // we could be here if a client restart happens right after selling before we navigate away
					contract = await contracts.deliver(contract, ship);
					if (contract.fulfilled) return;
				}
				await libShips.navigate(ship, asteroidSymbol);
				break;
			default:
				await selling.sell(ship, wantedCargo);
				await libShips.navigate(ship, asteroidSymbol);
		}
	}
}

async function runTradeProcurement(contract: Contract, ship: Ship): Promise<void> {
	const deliver = contract.terms.deliver[0];
	const deliveryPoint = deliver.destinationSymbol;
	const wantedCargo = deliver.tradeSymbol;
	while (!contract.fulfilled) {
		ship = dbShips.getShip(ship.symbol);
		const goodCargo = ship.cargo.inventory.filter(i => i.symbol === wantedCargo)[0]
		// make sure we are not carrying useless stuff
		await selling.sell(ship, wantedCargo);
		// go buy what we need
		const rawMarkets = await libSystems.trait(ship.nav.systemSymbol, 'MARKETPLACE');
		// sorted by distance from where we are
		const markets = rawMarkets.map(function (m) { return {
			data: m,
			distance: (m.x - ship.nav.route.destination.x) ** 2 + (m.y - ship.nav.route.destination.y) ** 2,
		}});
		markets.sort(function(a, b) {
			if (a.distance < b.distance) {
				return -1;
			} else if (a.distance > b.distance) {
				return 1;
			}
			return 0;
		});
		// check from the closest one that exports what we need
		let buyingPoint: string = "";
		outer: for (let i = 0; i < markets.length; i++) {
			const waypointSymbol = markets[i].data.symbol;
			const market = await libSystems.market(waypointSymbol);
			for (let j = 0; j < market.exports.length; j++) {
				if (market.exports[j].symbol === wantedCargo) {
					buyingPoint = market.symbol;
					break outer;
				}
			}
		}
		// if we did not find an exporting market we look for an exchange
		if (buyingPoint === "") {
			outer: for (let i = 0; i < markets.length; i++) {
				const waypointSymbol = markets[i].data.symbol;
				const market = await libSystems.market(waypointSymbol);
				for (let j = 0; j < market.exchange.length; j++) {
					if (market.exports[j].symbol === wantedCargo) {
						buyingPoint = market.symbol;
						break outer;
					}
				}
			}
		}
		if (buyingPoint === "") {
			throw `runTradeProcurement failed, no market exports or exchanges ${wantedCargo}`;
		}
		// go buy what we need
		await libShips.navigate(ship, buyingPoint);
		const units = Math.min(
			deliver.unitsRequired - deliver.unitsFulfilled,
			ship.cargo.capacity - ship.cargo.units,
		);
		await libShips.buy(ship, wantedCargo, units);
		// then make a delivery
		await libShips.navigate(ship, deliveryPoint);
		contract = await contracts.deliver(contract, ship);
		if (contract.fulfilled) return;
	}
	console.log("runTradeProcurement not implemented");
	throw "not implemented";
}