diff --git a/index.php b/index.php index f9480dc..9909ca8 100644 --- a/index.php +++ b/index.php @@ -498,6 +498,10 @@ try { $accepted = $contract['accepted'] ? 'Accepted' : false; $fulfilled = $contract['fulfilled'] ? 'Fulfilled' : false; + if ($fulfilled) { + continue; + } + if (! $accepted && ! $fulfilled) { $status = 'Not Accepted'; } elseif ($accepted && ! $fulfilled) { diff --git a/lib/spacetraders-api.php b/lib/spacetraders-api.php index 49542a3..38016fd 100644 --- a/lib/spacetraders-api.php +++ b/lib/spacetraders-api.php @@ -291,6 +291,34 @@ class SpacetradersApi { ); } + /** + * Fulfill a contract for the current agent. + * + * @param string $contractId The contract ID to fulfill. + * + * @return array + */ + public function fulfillContract( string $contractId ): array { + return $this->request( + 'POST', + '/my/contracts/' . rawurlencode( $contractId ) . '/fulfill' + ); + } + + /** + * Negotiate a new contract using a ship. + * + * @param string $shipSymbol The ship symbol that will negotiate the contract. + * + * @return array + */ + public function negotiateContract( string $shipSymbol ): array { + return $this->request( + 'POST', + '/my/ships/' . rawurlencode( $shipSymbol ) . '/negotiate/contract' + ); + } + /** * Deliver cargo for a contract using a ship at the delivery destination. * diff --git a/mining-fleet.php b/mining-fleet.php index 6c87720..65c82a3 100644 --- a/mining-fleet.php +++ b/mining-fleet.php @@ -95,6 +95,37 @@ function buildContractDeliveryTargets( array $activeContracts ): array { return $targets; } +/** + * Determine if a trade symbol represents ore cargo. + * + * @param string $tradeSymbol Trade symbol. + * + * @return bool + */ +function isOreTradeSymbol( string $tradeSymbol ): bool { + return preg_match( '/_ORE$/', strtoupper( $tradeSymbol ) ) === 1; +} + +/** + * Determine which ship symbol should be used for contract negotiation. + * + * @param array> $miningShips Mining ship records. + * @param array> $ships All ship records. + * + * @return string + */ +function getContractNegotiationShipSymbol( array $miningShips, array $ships ): string { + if (! empty( $miningShips ) ) { + return (string) ( $miningShips[0]['symbol'] ?? '' ); + } + + if (! empty( $ships ) ) { + return (string) ( $ships[0]['symbol'] ?? '' ); + } + + return ''; +} + $config = require __DIR__ . '/lib/project-config.php'; $storage = new SpacetradersStorage( $config['db_path'] ); @@ -206,6 +237,73 @@ try { trim( (string) $_POST['contract_delivery_key'] ) : (string) ( $contractDeliveryTargets[0]['key'] ?? '' ); + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['contract_action'] ) ) { + $contractAction = (string) $_POST['contract_action']; + + if ($contractAction === 'fulfill_contract' ) { + $contractId = trim( (string) ( $_POST['contract_id'] ?? '' ) ); + + if ($contractId !== '' ) { + try { + $client->fulfillContract( $contractId ); + $statusMessage = 'Contract ' . $contractId . ' fulfilled.'; + $storage->clearAllCache(); + + $shipsResponse = $client->listMyShips(); + $contractsResponse = $client->listMyContracts(); + $ships = $shipsResponse['data'] ?? $shipsResponse; + $contracts = $contractsResponse['data'] ?? $contractsResponse; + $miningShips = filterMiningShips( $ships ); + $activeContracts = array_values( + array_filter( + (array) $contracts, + static function ( array $contract ): bool { + return (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false ); + } + ) + ); + $contractDeliveryTargets = buildContractDeliveryTargets( $activeContracts ); + $selectedContractDeliveryKey = (string) ( $contractDeliveryTargets[0]['key'] ?? '' ); + } catch (SpacetradersApiException $e) { + $errorMessage = 'Unable to fulfill contract: ' . $e->getMessage(); + } + } + } elseif ($contractAction === 'negotiate_contract' ) { + $negotiateShipSymbol = trim( (string) ( $_POST['negotiate_ship_symbol'] ?? '' ) ); + if ($negotiateShipSymbol === '' ) { + $negotiateShipSymbol = getContractNegotiationShipSymbol( $miningShips, $ships ); + } + + if ($negotiateShipSymbol === '' ) { + $errorMessage = 'No available ship found to negotiate a contract.'; + } else { + try { + $client->negotiateContract( $negotiateShipSymbol ); + $statusMessage = 'Negotiated a new contract using ' . $negotiateShipSymbol . '.'; + $storage->clearAllCache(); + + $shipsResponse = $client->listMyShips(); + $contractsResponse = $client->listMyContracts(); + $ships = $shipsResponse['data'] ?? $shipsResponse; + $contracts = $contractsResponse['data'] ?? $contractsResponse; + $miningShips = filterMiningShips( $ships ); + $activeContracts = array_values( + array_filter( + (array) $contracts, + static function ( array $contract ): bool { + return (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false ); + } + ) + ); + $contractDeliveryTargets = buildContractDeliveryTargets( $activeContracts ); + $selectedContractDeliveryKey = (string) ( $contractDeliveryTargets[0]['key'] ?? '' ); + } catch (SpacetradersApiException $e) { + $errorMessage = 'Unable to negotiate contract: ' . $e->getMessage(); + } + } + } + } + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['fleet_action'] ) ) { $fleetAction = (string) $_POST['fleet_action']; $successCount = 0; @@ -357,6 +455,35 @@ try { $actionResults[] = $shipSymbol . ': jettisoned ' . $jettisonedItems . ' cargo item type(s).'; $successCount++; + } elseif ($fleetAction === 'jettison_non_ore_cargo_all' ) { + if ($shipStatus === 'IN_TRANSIT' ) { + $actionResults[] = $shipSymbol . ': skipped (in transit).'; + continue; + } + + $inventory = (array) ( $shipData['cargo']['inventory'] ?? array() ); + $jettisonedItems = 0; + foreach ( $inventory as $item ) { + $tradeSymbol = (string) ( $item['symbol'] ?? '' ); + $units = (int) ( $item['units'] ?? 0 ); + if ($tradeSymbol === '' || $units <= 0 ) { + continue; + } + + if (isOreTradeSymbol( $tradeSymbol ) ) { + continue; + } + + try { + $client->jettisonCargo( $shipSymbol, $tradeSymbol, $units ); + $jettisonedItems++; + } catch (SpacetradersApiException $e) { + // Continue jettisoning other items. + } + } + + $actionResults[] = $shipSymbol . ': jettisoned ' . $jettisonedItems . ' non-ore cargo item type(s).'; + $successCount++; } elseif ($fleetAction === 'navigate_contract_delivery' ) { if (! is_array( $selectedContractDeliveryTarget ) ) { $actionResults[] = $shipSymbol . ': no contract delivery target selected.'; @@ -475,7 +602,7 @@ try { (string) ( $contractDeliveryTargets[0]['key'] ?? '' ); } - if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['ship_action'] ) && in_array( (string) $_POST['ship_action'], array( 'sell_ship_cargo', 'jettison_ship_cargo' ), true ) && isset( $_POST['ship_symbol'] )) { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['ship_action'] ) && in_array( (string) $_POST['ship_action'], array( 'sell_ship_cargo', 'jettison_ship_cargo', 'jettison_non_ore_ship_cargo' ), true ) && isset( $_POST['ship_symbol'] )) { $shipAction = (string) $_POST['ship_action']; $sellShipSymbol = trim( (string) $_POST['ship_symbol'] ); @@ -502,6 +629,10 @@ try { continue; } + if ($shipAction === 'jettison_non_ore_ship_cargo' && isOreTradeSymbol( $tradeSymbol ) ) { + continue; + } + try { if ($shipAction === 'sell_ship_cargo' ) { $client->sellCargo( $sellShipSymbol, $tradeSymbol, $units ); @@ -516,6 +647,8 @@ try { if ($shipAction === 'sell_ship_cargo' ) { $statusMessage = $sellShipSymbol . ': sold ' . $handledItems . ' cargo item type(s).'; + } elseif ($shipAction === 'jettison_non_ore_ship_cargo' ) { + $statusMessage = $sellShipSymbol . ': jettisoned ' . $handledItems . ' non-ore cargo item type(s).'; } else { $statusMessage = $sellShipSymbol . ': jettisoned ' . $handledItems . ' cargo item type(s).'; } @@ -561,6 +694,8 @@ foreach ( $miningShips as $miningShip ) { $deliveryReadyByTradeSymbol[ $tradeSymbol ] += $units; } } + +$contractNegotiationShipSymbol = getContractNegotiationShipSymbol( $miningShips, $ships ); ?> @@ -601,8 +736,23 @@ foreach ( $miningShips as $miningShip ) {

Global Controls

+ + + + + +
+
- +
-
- + + + +
+ +
+
- - - - - @@ -680,7 +824,21 @@ foreach ( $miningShips as $miningShip ) {

Active Contracts

-

No active contracts.

+

No active contracts.

+ + + + + + +

+ Uses ship: +

+ +

No ships available to negotiate a contract.

+
@@ -701,6 +859,13 @@ foreach ( $miningShips as $miningShip ) { +

+
+ + + +

Deliveries:

    @@ -792,6 +957,11 @@ foreach ( $miningShips as $miningShip ) { +
    + + + +