diff --git a/packages/cli/lib/behavior/jobs/mount_job.dart b/packages/cli/lib/behavior/jobs/mount_job.dart index 3ea8cd25..a8b86372 100644 --- a/packages/cli/lib/behavior/jobs/mount_job.dart +++ b/packages/cli/lib/behavior/jobs/mount_job.dart @@ -63,6 +63,13 @@ Future doMountJob( } } + final needed = mountsToAddToShip(ship, template); + jobAssert( + needed.contains(mountJob.mountSymbol), + 'Ship does not need ${mountJob.mountSymbol}?', + const Duration(minutes: 10), + ); + // 🛸#6 🔧 MOUNT_MINING_LASER_II on ESEIDEL-6 for 3,600c -> 🏦 89,172c // Mount the new mount. await installMountAndLog( diff --git a/packages/cli/lib/behavior/miner.dart b/packages/cli/lib/behavior/miner.dart index e601b30a..0c450500 100644 --- a/packages/cli/lib/behavior/miner.dart +++ b/packages/cli/lib/behavior/miner.dart @@ -467,6 +467,7 @@ Future emptyCargoIfNeeded( caches.marketPrices, caches.agent, currentMarket, + caches.ships, ship, AccountingType.goods, ); @@ -485,17 +486,21 @@ Future emptyCargoIfNeeded( } final largestCargo = ship.largestCargo(); - final nearestMarket = assertNotNull( - await nearbyMarketWhichTrades( - caches.systems, - caches.waypoints, - caches.markets, - currentWaypoint.waypointSymbol, - largestCargo!.tradeSymbol, - ), - 'No nearby market which trades ${largestCargo.symbol}.', - const Duration(hours: 1), + final nearestMarket = await nearbyMarketWhichTrades( + caches.systems, + caches.waypoints, + caches.markets, + currentWaypoint.waypointSymbol, + largestCargo!.tradeSymbol, ); + if (nearestMarket == null) { + shipErr( + ship, + 'No nearby market to sell ${largestCargo.symbol}, jetisoning cargo!', + ); + await jettisonAllCargoAndLog(api, caches.ships, ship); + return JobResult.complete(); + } final waitTime = await beingNewRouteAndLog( api, ship, @@ -571,6 +576,7 @@ Future sellCargoIfNeeded( caches.marketPrices, caches.agent, currentMarket, + caches.ships, ship, // We don't have a good way to know what type of cargo this is. // Assuming it's goods (rather than captial) is probably fine. diff --git a/packages/cli/lib/behavior/trader.dart b/packages/cli/lib/behavior/trader.dart index d873749e..b70e756a 100644 --- a/packages/cli/lib/behavior/trader.dart +++ b/packages/cli/lib/behavior/trader.dart @@ -226,6 +226,7 @@ Future _handleArbitrageDealAtDestination( caches.marketPrices, caches.agent, currentMarket, + caches.ships, ship, AccountingType.goods, ); @@ -606,6 +607,7 @@ Future handleUnwantedCargoIfNeeded( caches.marketPrices, caches.agent, currentMarket, + caches.ships, ship, // We don't have a good way to know what type of cargo this is. // Assuming it's goods (rather than captial) is probably fine. diff --git a/packages/cli/lib/net/actions.dart b/packages/cli/lib/net/actions.dart index 5dd01a50..33181681 100644 --- a/packages/cli/lib/net/actions.dart +++ b/packages/cli/lib/net/actions.dart @@ -52,6 +52,7 @@ Stream sellAllCargo( Api api, AgentCache agentCache, Market market, + ShipCache shipCache, Ship ship, { bool Function(TradeSymbol tradeSymbol)? where, }) async* { @@ -82,6 +83,7 @@ Stream sellAllCargo( api, agentCache, ship, + shipCache, item.tradeSymbol, toSell, ); @@ -99,6 +101,7 @@ Future> sellAllCargoAndLog( MarketPrices marketPrices, AgentCache agentCache, Market market, + ShipCache shipCache, Ship ship, AccountingType accounting, { bool Function(TradeSymbol tradeSymbol)? where, @@ -110,7 +113,7 @@ Future> sellAllCargoAndLog( final transactions = []; await for (final response - in sellAllCargo(api, agentCache, market, ship, where: where)) { + in sellAllCargo(api, agentCache, market, shipCache, ship, where: where)) { final marketTransaction = response.transaction; final agent = response.agent; logMarketTransaction(ship, marketPrices, agent, marketTransaction); @@ -125,6 +128,29 @@ Future> sellAllCargoAndLog( return transactions; } +/// Jettison all cargo. +Future jettisonAllCargoAndLog( + Api api, + ShipCache shipCache, + Ship ship, +) async { + if (ship.cargo.inventory.isEmpty) { + shipInfo(ship, 'No cargo to jettison'); + return; + } + + for (final item in ship.cargo.inventory) { + shipWarn(ship, 'Jettisoning ${item.units} ${item.symbol}'); + final response = await api.fleet.jettison( + ship.symbol, + jettisonRequest: + JettisonRequest(symbol: item.tradeSymbol, units: item.units), + ); + ship.cargo = response!.data.cargo; + shipCache.updateShip(ship); + } +} + /// Buy [amountToBuy] units of [tradeSymbol] and log the transaction. Future purchaseCargoAndLog( Api api, diff --git a/packages/cli/lib/net/direct.dart b/packages/cli/lib/net/direct.dart index 9c0b3e72..cf5c5279 100644 --- a/packages/cli/lib/net/direct.dart +++ b/packages/cli/lib/net/direct.dart @@ -125,6 +125,7 @@ Future sellCargo( Api api, AgentCache agentCache, Ship ship, + ShipCache shipCache, TradeSymbol tradeSymbol, int units, ) async { @@ -135,6 +136,7 @@ Future sellCargo( final response = await api.fleet.sellCargo(ship.symbol, sellCargoRequest: request); ship.cargo = response!.data.cargo; + shipCache.updateShip(ship); agentCache.agent = response.data.agent; return response.data; } diff --git a/packages/types/test/mount_test.dart b/packages/types/test/mount_test.dart index 43fb975a..17456d30 100644 --- a/packages/types/test/mount_test.dart +++ b/packages/types/test/mount_test.dart @@ -96,6 +96,37 @@ void main() { ); }); + test('mountsToRemoveFromShip', () { + final ship = _MockShip(); + final mountSymbols = [ + ShipMountSymbolEnum.MINING_LASER_II, + ShipMountSymbolEnum.SURVEYOR_I, + ShipMountSymbolEnum.MINING_LASER_II, + ]; + ShipMount mountForSymbol(ShipMountSymbolEnum symbol) { + return ShipMount( + symbol: symbol, + name: '', + description: '', + requirements: ShipRequirements(), + ); + } + + when(() => ship.mounts) + .thenReturn(mountSymbols.map(mountForSymbol).toList()); + final template = ShipTemplate( + frameSymbol: ShipFrameSymbolEnum.MINER, + mounts: MountSymbolSet.from([ + ShipMountSymbolEnum.MINING_LASER_II, + ShipMountSymbolEnum.MINING_LASER_II, + ShipMountSymbolEnum.MINING_LASER_I, + ]), + ); + final toRemove = mountsToRemoveFromShip(ship, template); + expect(toRemove, isNotEmpty); + expect(toRemove, MountSymbolSet.from([ShipMountSymbolEnum.SURVEYOR_I])); + }); + test('ShipTemplate.matches', () { final template = ShipTemplate( frameSymbol: ShipFrameSymbolEnum.FIGHTER,