Skip to content

Commit

Permalink
feat: sketch out a scrap ship behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
eseidel committed Mar 19, 2024
1 parent e09ff11 commit 2715cd1
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 13 deletions.
15 changes: 15 additions & 0 deletions packages/cli/bin/report_profit_loss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ class ReportBuilder {
// CapEx doesn't show up in P&L.
}

void _processScrapShipTransaction(Transaction transaction) {
_expect(
transaction.accounting == AccountingType.capital,
'Shipyard transaction is not capital',
);
_expect(transaction.isSale, 'Ship is not a purchase');
_expect(
transaction.creditsChange >= 0,
'Ship value is not positive',
);
// CapEx doesn't show up in P&L.
}

void _processShipModificationTransaction(Transaction transaction) {
_expect(
transaction.accounting == AccountingType.capital,
Expand Down Expand Up @@ -138,6 +151,8 @@ class ReportBuilder {
_processMarketTransaction(transaction);
case TransactionType.shipyard:
_processShipyardTransaction(transaction);
case TransactionType.scrapShip:
_processScrapShipTransaction(transaction);
case TransactionType.shipModification:
_processShipModificationTransaction(transaction);
case TransactionType.contract:
Expand Down
13 changes: 0 additions & 13 deletions packages/cli/lib/behavior/mount_from_buy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,8 @@ import 'package:cli/behavior/jobs/mount_job.dart';
import 'package:cli/caches.dart';
import 'package:cli/central_command.dart';
import 'package:cli/plan/trading.dart';
import 'package:collection/collection.dart';
import 'package:types/types.dart';

/// Compute the nearest shipyard to the given start.
Future<ShipyardListing?> nearestShipyard(
RoutePlanner routePlanner,
ShipyardListingSnapshot shipyards,
WaypointSymbol start,
) async {
final listings = shipyards.listingsInSystem(start.system);
// TODO(eseidel): Sort by distance.
// TODO(eseidel): Consider reachable systems not just this one.
return listings.firstOrNull;
}

/// Compute a mount request for the given ship and template.
Future<MountRequest?> mountRequestForShip(
CentralCommand centralCommand,
Expand Down
62 changes: 62 additions & 0 deletions packages/cli/lib/behavior/scrap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:cli/behavior/job.dart';
import 'package:cli/caches.dart';
import 'package:cli/central_command.dart';
import 'package:cli/logger.dart';
import 'package:cli/nav/navigation.dart';
import 'package:cli/net/actions.dart';
import 'package:db/db.dart';
import 'package:types/types.dart';

/// Scrap the ship.
Future<JobResult> doScrapJob(
BehaviorState state,
Api api,
Database db,
CentralCommand centralCommand,
Caches caches,
Ship ship, {
DateTime Function() getNow = defaultGetNow,
}) async {
// If we're at a shipyard, scrap the ship.
// If we're not, find the nearest shipyard.
// Navigate to that shipyard.

final shipyardListings = await ShipyardListingSnapshot.load(db);
final shipyard = assertNotNull(
await nearestShipyard(
caches.routePlanner,
shipyardListings,
ship.waypointSymbol,
),
'No shipyard found.',
const Duration(minutes: 5),
);

if (shipyard.waypointSymbol == ship.waypointSymbol) {
// We're at the shipyard, scrap the ship.
await scrapShipAndLog(
api,
db,
caches.agent,
ship,
);
shipErr(ship, 'Scrapped ship!');
return JobResult.complete();
}

final waitTime = await beingNewRouteAndLog(
api,
db,
centralCommand,
caches,
ship,
state,
shipyard.waypointSymbol,
);
return JobResult.wait(waitTime);
}

/// Advance the scrap.
final advanceScrap = const MultiJob('Scrap', [
doScrapJob,
]).run;
13 changes: 13 additions & 0 deletions packages/cli/lib/logic/printing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,19 @@ void logShipyardTransaction(
'🏦 ${creditsString(agent.credits)}');
}

/// Log a shipyard transaction to the console.
void logScrapTransaction(
Ship ship,
Agent agent,
ScrapTransaction t,
) {
shipInfo(
ship,
'Scrapped ${t.shipSymbol} for '
'${creditsString(t.totalPrice)} -> '
'🏦 ${creditsString(agent.credits)}');
}

/// Log a ship modification transaction to the console.
void logShipModificationTransaction(
Ship ship,
Expand Down
17 changes: 17 additions & 0 deletions packages/cli/lib/nav/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,3 +614,20 @@ String describeRoutePlan(RoutePlan plan) {
);
return buffer.toString();
}

/// Compute the nearest shipyard to the given start.
Future<ShipyardListing?> nearestShipyard(
RoutePlanner routePlanner,
ShipyardListingSnapshot shipyards,
WaypointSymbol start,
) async {
final listings = shipyards.listingsInSystem(start.system);

// If not in this system. Should list all shipyardListings.
// Filter by ones which are reachable (e.g. if this ship can warp).
// Pick the one with the shortest route.

// TODO(eseidel): Sort by distance.
// TODO(eseidel): Consider reachable systems not just this one.
return listings.firstOrNull;
}
26 changes: 26 additions & 0 deletions packages/cli/lib/net/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,32 @@ Future<PurchaseShip201ResponseData> purchaseShipAndLog(
return result;
}

/// Scrap the ship and log the transaction.
Future<ScrapShip200ResponseData> scrapShipAndLog(
Api api,
Database db,
AgentCache agentCache,
Ship ship,
) async {
final result = await scrapShip(
db,
api,
agentCache,
ship.symbol,
);
final agent = Agent.fromOpenApi(result.agent);
logScrapTransaction(ship, agent, result.transaction);
shipErr(ship, 'Scrapped ship for ${result.transaction.totalPrice}');
final transaction = Transaction.fromScrapTransaction(
result.transaction,
// scrapShip updated the agent cache.
agentCache.agent.credits,
ship.symbol,
);
await db.insertTransaction(transaction);
return result;
}

bool _shouldRefuelAfterCheckingPrice(
Ship ship, {
required TradeSymbol fuelSymbol,
Expand Down
17 changes: 17 additions & 0 deletions packages/cli/lib/net/direct.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ Future<PurchaseShip201ResponseData> purchaseShip(
return data;
}

/// Scrap the ship with [shipSymbol]
/// Must be docked at a shipyard.
Future<ScrapShip200ResponseData> scrapShip(
Database db,
Api api,
AgentCache agentCache,
ShipSymbol shipSymbol,
) async {
final scrapResponse = await api.fleet.scrapShip(shipSymbol.symbol);
final data = scrapResponse!.data;
// Remove the ship from our cache.
await db.deleteShip(shipSymbol);
await agentCache.updateAgent(Agent.fromOpenApi(data.agent));
// Caller records the transaction.
return data;
}

/// Set the [flightMode] of [ship]
Future<ShipNav> setShipFlightMode(
Database db,
Expand Down
5 changes: 5 additions & 0 deletions packages/db/lib/db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,11 @@ class Database {
await execute(upsertShipQuery(ship));
}

/// Delete a ship from the database.
Future<void> deleteShip(ShipSymbol symbol) async {
await execute(deleteShipQuery(symbol));
}

Future<bool> _hasRecentPrice(Query query, Duration maxAge) async {
final result = await execute(query);
if (result.isEmpty) {
Expand Down
8 changes: 8 additions & 0 deletions packages/db/lib/src/ship.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ Query upsertShipQuery(Ship ship) => Query(
},
);

/// Delete a ship from the database by its [symbol].
Query deleteShipQuery(ShipSymbol symbol) => Query(
'DELETE FROM ship_ WHERE symbol = @symbol',
parameters: {
'symbol': symbol.toJson(),
},
);

/// Convert a Ship to a column map.
Map<String, dynamic> shipToColumnMap(Ship ship) => {
'symbol': ship.symbol.toJson(),
Expand Down
10 changes: 10 additions & 0 deletions packages/types/lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,16 @@ extension ShipyardTransactionUtils on ShipyardTransaction {
WaypointSymbol.fromString(waypointSymbol);
}

/// Extensions onto ScrapTransaction to make it easier to work with.
extension ScrapTransactionUtils on ScrapTransaction {
/// Returns the ShipSymbol for the given transaction.
ShipSymbol get shipSymbolObject => ShipSymbol.fromString(shipSymbol);

/// Returns the WaypointSymbol for the given transaction.
WaypointSymbol get waypointSymbolObject =>
WaypointSymbol.fromString(waypointSymbol);
}

/// Extensions onto ShipModificationTransaction to make it easier to work with.
extension ShipModificationTransactionUtils on ShipModificationTransaction {
/// Returns the ShipSymbol for the given transaction.
Expand Down
29 changes: 29 additions & 0 deletions packages/types/lib/src/transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ enum TransactionType {
/// A shipyard transaction (ship purchase).
shipyard,

/// A shipyard transaction (ship sale).
scrapShip,

/// A ship modification transaction (mount).
shipModification,

Expand Down Expand Up @@ -176,6 +179,32 @@ class Transaction extends Equatable {
);
}

/// Create a new transaction from a shipyard transaction.
factory Transaction.fromScrapTransaction(
ScrapTransaction transaction,
int agentCredits,
ShipSymbol shipSymbol,
) {
// shipSymbol is the trade symbol for the shipyard transaction, not
// the new ship's id.
// Using a local to force non-null.
return Transaction(
transactionType: TransactionType.scrapShip,
shipSymbol: shipSymbol,
waypointSymbol: transaction.waypointSymbolObject,
shipType: null, // Could use guessShipType.
tradeSymbol: null,
quantity: 1,
tradeType: MarketTransactionTypeEnum.SELL,
perUnitPrice: transaction.totalPrice,
timestamp: transaction.timestamp,
agentCredits: agentCredits,
accounting: AccountingType.capital,
contractId: null,
contractAction: null,
);
}

/// Create a new transaction from a ship modification transaction.
factory Transaction.fromShipModificationTransaction(
ShipModificationTransaction transaction,
Expand Down

0 comments on commit 2715cd1

Please sign in to comment.