Automated the extraction and delivery loop for a ship
This commit is contained in:
parent
b1157af9cd
commit
19107b153e
4 changed files with 94 additions and 112 deletions
187
lib/agent.js
187
lib/agent.js
|
@ -4,114 +4,85 @@ import * as contracts from './contracts.js';
|
|||
import * as ships from './ships.js';
|
||||
import * as systems from './systems.js';
|
||||
|
||||
// This starts a delivery loop with a ship which ends when the delivery is fullfilled
|
||||
// ctx must must have multiple attributes: `ship`, `contract` (with the contractId), `good`, `destination` (the delivery waypoint), `field` (the asteroid field waypoint). The ship needs to be orbiting the asteroid field.
|
||||
//export function deliver(ctx, response) {
|
||||
// let next = {...ctx};
|
||||
// next.action = deliver; // calling ourselves after the action is a good default
|
||||
// if (response !== undefined) {
|
||||
// if (response.error !== undefined) {
|
||||
// console.log("ctx:", JSON.stringify(ctx, null, 2));
|
||||
// console.log("response:", JSON.stringify(response, null, 2));
|
||||
// switch(response.error.code) {
|
||||
// case 4000: // ship is on cooldown
|
||||
// setTimeout(deliver, response.error.data.cooldown.remainingSeconds * 1000, ctx);
|
||||
// return;
|
||||
// case 4228: // ship cannot extract because it is full. Running the ship inventory function to list the cargo so that we know if we need to sell
|
||||
// ships.ship({ship: ctx.ship, next: next});
|
||||
// return;
|
||||
// default: // yet unhandled error
|
||||
// throw response;
|
||||
// }
|
||||
// }
|
||||
// if (response.data.extraction !== undefined && response.data.extraction.yield !== undefined) { // yield won't be defined if we reached this point from an inventory request
|
||||
// console.log(`${ctx.ship}: extracted ${response.data.extraction.yield.units} of ${response.data.extraction.yield.symbol}`);
|
||||
// }
|
||||
// if (response.data.cargo !== undefined && response.data.cargo.capacity * 0.9 <= response.data.cargo.units) { // > 90% full
|
||||
// const good = response.data.cargo.inventory.filter(i => i.symbol === ctx.good)[0];
|
||||
// const inventory = response.data.cargo.inventory.filter(i => i.symbol !== ctx.good);
|
||||
// if (good?.units >= response.data.cargo.capacity * 0.9) { // > 90% full
|
||||
// console.log(`ship's cargo is full with ${good.units} of ${ctx.good}!`);
|
||||
// next.units = good.units; // set the unit property so that we know how much to deliver
|
||||
// ships.navigate({ship: ctx.ship, waypoint: ctx.destination, next: next});
|
||||
// }
|
||||
// let actions = [{ action: ships.dock, ship: ctx.ship }];
|
||||
// inventory.forEach(i => actions.push({action: ships.sell, ship: ctx.ship, good: i.symbol, units: i.units}));
|
||||
// actions.push({action: ships.orbit, ship: ctx.ship});
|
||||
// actions.push({action: deliver, ship: ctx.ship, good: ctx.good});
|
||||
// api.chain(actions);
|
||||
// return;
|
||||
// } else if (response.data.nav !== undefined) { // we are moving
|
||||
// const delay = new Date(response.data.nav.route.arrival) - new Date();
|
||||
// console.log(new Date(response.data.nav.route.arrival), new Date(), delay);
|
||||
// if (delay > 0) { // the ship did not arrive yet, we wait
|
||||
// setTimeout(deliver, delay+1000, ctx, response);
|
||||
// return;
|
||||
// }
|
||||
// if (response.data.nav.route.destination.symbol === ctx.destination) { // we arrived to the delivery point
|
||||
// api.chain([{ action: ships.dock, ship: ctx.ship },
|
||||
// { action: ships.refuel, ship: ctx.ship },
|
||||
// { action: ships.deliver, contract: ctx.contract, ship: ctx.ship, good: ctx.good, units: ctx.units },
|
||||
// { action: ships.navigate, ship: ctx.ship, waypoint: ctx.field, next: next }]);
|
||||
// return;
|
||||
// } else if (next.units !== undefined) { // we arrived to the asteroid field
|
||||
// delete next.units; // remove the unit property
|
||||
// api.chain([{ action: ships.dock, ship: ctx.ship },
|
||||
// { action: ships.refuel, ship: ctx.ship },
|
||||
// { action: ships.orbit, ship: ctx.ship, next: next }]); // TODO we are on cooldown and cannot mine right now
|
||||
// return;
|
||||
// }
|
||||
// } else { // we need to mine more
|
||||
// if (response.data.cooldown) { // we are on cooldown, call ourselves again in a moment
|
||||
// setTimeout(deliver, response.data.cooldown.remainingSeconds * 1000, ctx);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ships.extract({ship: ctx.ship, good: ctx.good, next: next});
|
||||
//}
|
||||
//
|
||||
//// This starts an extraction loop with a ship which ends when the ship's cargo is at least 90% full with only one desired good
|
||||
//// ctx must must have two attributes: `ship` and `good`. The ship needs to be orbiting an asteroid field
|
||||
//export function extract(ctx, response) {
|
||||
// if (response !== undefined) {
|
||||
// if (response.error !== undefined) {
|
||||
// switch(response.error.code) {
|
||||
// case 4000: // ship is on cooldown
|
||||
// setTimeout(extract, response.error.data.cooldown.remainingSeconds * 1000, ctx);
|
||||
// return;
|
||||
// case 4228: // ship is full. Running the ship inventory function to list the cargo so that know if we need to sell
|
||||
// ships.ship({ship: ctx.ship, next:{action: extract, ship: ctx.ship, good: ctx.good}});
|
||||
// return;
|
||||
// default:
|
||||
// throw response;
|
||||
// }
|
||||
// }
|
||||
// if (response.data.extraction !== undefined && response.data.extraction.yield !== undefined) { // yield won't be defined if we reached this point from an inventory request
|
||||
// console.log(`${ctx.ship}: extracted ${response.data.extraction.yield.units} of ${response.data.extraction.yield.symbol}`);
|
||||
// }
|
||||
// if (response.data.cargo !== undefined && response.data.cargo.capacity * 0.9 <= response.data.cargo.units) { // > 90% full
|
||||
// const good = response.data.cargo.inventory.filter(i => i.symbol === ctx.good)[0];
|
||||
// const inventory = response.data.cargo.inventory.filter(i => i.symbol !== ctx.good);
|
||||
// if (good?.units >= response.data.cargo.capacity * 0.9) { // > 90% full
|
||||
// console.log(`ship's cargo is full with ${response.data.cargo.units} of ${ctx.good}!`);
|
||||
// return;
|
||||
// }
|
||||
// let actions = [{ action: ships.dock, ship: ctx.ship }];
|
||||
// inventory.forEach(i => actions.push({action: ships.sell, ship: ctx.ship, good: i.symbol, units: i.units}));
|
||||
// actions.push({action: ships.orbit, ship: ctx.ship});
|
||||
// actions.push({action: extract, ship: ctx.ship, good: ctx.good});
|
||||
// api.chain(actions);
|
||||
// return;
|
||||
// } else { // we need to mine more
|
||||
// if (response.data.cooldown) { // we are on cooldown, call ourselves again in a moment
|
||||
// setTimeout(extract, response.data.cooldown.remainingSeconds * 1000, ctx);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ships.extract({ship: ctx.ship, good: ctx.good, next: { action: extract, ship: ctx.ship, good: ctx.good }});
|
||||
//}
|
||||
export async function auto(ctx) {
|
||||
let ship = await ships.ship({ship: ctx.ship});
|
||||
// Fetch our contracts in the system the ship currently is in
|
||||
let cs = await contracts.contracts();
|
||||
cs = cs.data.filter(c => c.terms.deliver[0].destinationSymbol.startsWith(ship.data.nav.systemSymbol));
|
||||
if (cs === []) throw `No contract at ${ctx.ship}'s location`;
|
||||
let contract = cs[0];
|
||||
if (!contract.accepted) {
|
||||
console.log(new Date(), `accepting contract ${contract.id}`);
|
||||
await contracts.accept({id: contract.id});
|
||||
}
|
||||
const good = contract.terms.deliver[0].tradeSymbol;
|
||||
const deliveryPoint = contract.terms.deliver[0].destinationSymbol;
|
||||
const asteroidFields = await systems.type({symbol: ship.data.nav.systemSymbol, type: 'ASTEROID_FIELD'});
|
||||
const asteroidField = asteroidFields[0].symbol;
|
||||
while (true) {
|
||||
ship = await ships.ship({ship: ctx.ship}); // TODO we should not need to fetch this
|
||||
// If we are in transit, we wait until we arrive
|
||||
const delay = new Date(ship.data.nav.route.arrival) - new Date();
|
||||
if (delay > 0) await api.sleep(delay);
|
||||
// Then it depends on where we are
|
||||
let goodCargo = ship.data.cargo.inventory.filter(i => i.symbol === good)[0];
|
||||
// the switch makes this 'resumable'
|
||||
switch (ship.data.nav.waypointSymbol) {
|
||||
case asteroidField:
|
||||
let response = await mineUntilFullOf({good: good, ship: ctx.ship});
|
||||
//console.log(`${ctx.ship}'s cargo is full with ${response.units} of ${good}!`);
|
||||
await ships.navigate({ship: ctx.ship, waypoint: deliveryPoint});
|
||||
break;
|
||||
case deliveryPoint:
|
||||
await ships.dock({ship: ctx.ship});
|
||||
await ships.refuel({ship: ctx.ship});
|
||||
console.log(`delivering ${goodCargo.units} of ${good}`);
|
||||
await contracts.deliver({contract: contract.id, ship: ctx.ship, good: good, units: goodCargo.units });
|
||||
await ships.navigate({ship: ctx.ship, waypoint: asteroidField});
|
||||
await ships.dock({ship: ctx.ship});
|
||||
await ships.refuel({ship: ctx.ship});
|
||||
await ships.orbit({ship: ctx.ship});
|
||||
break;
|
||||
default:
|
||||
throw `where is the ship?`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// example ctx { good: 'SILVER_ORE', ship: 'ADYXAX-2' }
|
||||
// returns the number of units of the good the ship extracted
|
||||
async function mineUntilFullOf(ctx) {
|
||||
while(true) {
|
||||
let response = await mineUntilFull({ship: ctx.ship});
|
||||
if (response === null) response = await ships.ship({ship: ctx.ship}); // TODO we should not need to fetch this
|
||||
let good = response.data.cargo.inventory.filter(i => i.symbol === ctx.good)[0];
|
||||
const inventory = response.data.cargo.inventory.filter(i => i.symbol !== ctx.good);
|
||||
const antimatter = response.data.cargo.inventory.filter(i => i.symbol === 'ANTIMATTER')[0];
|
||||
if (good?.units + antimatter?.units >= response.data.cargo.capacity * 0.9) { // > 90% full of the valuable goods
|
||||
return good.units;
|
||||
} else { // we are full but need to sell junk
|
||||
await ships.dock({ship: ctx.ship});
|
||||
for (let i=0; i<inventory.length; ++i) {
|
||||
if (inventory[i].symbol === 'ANTIMATTER') continue;
|
||||
//console.log(`selling ${inventory[i].units} of ${inventory[i].symbol}`);
|
||||
await ships.sell({ship: ctx.ship, good: inventory[i].symbol, units: inventory[i].units});
|
||||
}
|
||||
await ships.orbit({ship: ctx.ship});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// example ctx { ship: 'ADYXAX-2' }
|
||||
// returns the last ship's extract api response
|
||||
async function mineUntilFull(ctx) {
|
||||
while(true) {
|
||||
const response = await ships.extract(ctx);
|
||||
if (response === null) return null;
|
||||
//console.log(`${ctx.ship}: extracted ${response.data.extraction.yield.units} of ${response.data.extraction.yield.symbol}`);
|
||||
await api.sleep(response.data.cooldown.remainingSeconds*1000);
|
||||
if (response.data.cargo.units >= response.data.cargo.capacity * 0.9) return response;
|
||||
}
|
||||
}
|
||||
|
||||
// This function inits the database in case we have an already registered game
|
||||
export function init(symbol, faction, token) {
|
||||
|
|
11
lib/api.js
11
lib/api.js
|
@ -1,3 +1,5 @@
|
|||
import * as fs from 'fs';
|
||||
|
||||
import { getToken } from '../database/config.js';
|
||||
import { PriorityQueue } from './priority_queue.js';
|
||||
|
||||
|
@ -58,10 +60,15 @@ function send_this(data) {
|
|||
if (data.request.payload !== undefined) {
|
||||
options['body'] = JSON.stringify(data.request.payload);
|
||||
}
|
||||
fs.writeFileSync('log', JSON.stringify({event: 'send', date: new Date(), data: data}) + '\n', {flag: 'a+'});
|
||||
fetch(`https://api.spacetraders.io/v2${data.request.endpoint}`, options)
|
||||
.then(response => response.json())
|
||||
.then(response => data.resolve(response))
|
||||
.catch(err => data.reject(err));
|
||||
.then(response => {
|
||||
fs.writeFileSync('log', JSON.stringify({event: 'response', date: new Date(), data: response}) + '\n', {flag: 'a+'});
|
||||
return data.resolve(response);})
|
||||
.catch(err => {
|
||||
fs.writeFileSync('log', JSON.stringify({event: 'error', date: new Date(), data: err}) + '\n', {flag: 'a+'});
|
||||
data.reject(err)});
|
||||
setTimeout(send_next, 500);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ export async function extract(ctx) {
|
|||
if (response.error !== undefined) {
|
||||
switch(response.error.code) {
|
||||
case 4000: // ship is on cooldown
|
||||
await api.sleep(response.error.data.remainingSeconds * 1000);
|
||||
await api.sleep(response.error.data.cooldown.remainingSeconds * 1000);
|
||||
return extract(ctx);
|
||||
case 4228: // ship is full
|
||||
return null;
|
||||
|
@ -31,7 +31,9 @@ export async function dock(ctx) {
|
|||
}
|
||||
|
||||
export async function navigate(ctx) {
|
||||
return await api.send({endpoint: `/my/ships/${ctx.ship}/navigate`, method: 'POST', payload: { waypointSymbol: ctx.waypoint }});
|
||||
const response = await api.send({endpoint: `/my/ships/${ctx.ship}/navigate`, method: 'POST', payload: { waypointSymbol: ctx.waypoint }});
|
||||
const delay = new Date(response.data.nav.route.arrival) - new Date();
|
||||
await api.sleep(delay);
|
||||
}
|
||||
|
||||
export async function orbit(ctx) {
|
||||
|
|
2
main.js
2
main.js
|
@ -14,6 +14,8 @@ ships\t\t\tRetrieve all of your ships.`);
|
|||
}
|
||||
|
||||
switch(process.argv[2]) {
|
||||
case 'auto':
|
||||
await agent.auto({ship: process.argv[3]});
|
||||
break;
|
||||
//case 'deliver':
|
||||
// agent.deliver({
|
||||
|
|
Loading…
Add table
Reference in a new issue