diff --git a/agent-info.php b/agent-info.php index 95192f3..b4e1046 100644 --- a/agent-info.php +++ b/agent-info.php @@ -35,6 +35,16 @@ if (! is_string( $token ) || trim( $token ) === '') { $agent = array(); $ships = array(); $contracts = array(); +$system = array(); +$systemWaypoints = array(); +$paginatedWaypoints = array(); +$marketDetails = array(); +$shipyardDetails = array(); +$selectedShipSymbol = ''; +$selectedWaypointSymbol = ''; +$waypointPageSize = 25; +$waypointPage = isset( $_GET['waypoint_page'] ) ? max( 1, (int) $_GET['waypoint_page'] ) : 1; +$waypointTotalPages = 1; if (! isset( $tokenError ) ) { $client = new SpacetradersApi( @@ -45,6 +55,35 @@ if (! isset( $tokenError ) ) { (int) $config['cache_ttl'] ); + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['navigate_ship'] ) && isset( $_POST['ship_symbol'] ) && isset( $_POST['waypoint_symbol'] )) { + $selectedShipSymbol = trim( (string) $_POST['ship_symbol'] ); + $selectedWaypointSymbol = trim( (string) $_POST['waypoint_symbol'] ); + + if ($selectedShipSymbol !== '' && $selectedWaypointSymbol !== '' ) { + try { + $selectedShipResponse = $client->getShip( $selectedShipSymbol ); + $selectedShipData = $selectedShipResponse['data'] ?? array(); + $shipNavStatus = (string) ( $selectedShipData['nav']['status'] ?? '' ); + + if ($shipNavStatus === 'IN_TRANSIT' ) { + throw new SpacetradersApiException( + 'Selected ship is currently in transit and cannot navigate yet.' + ); + } + + if ($shipNavStatus === 'DOCKED' ) { + $client->orbitShip( $selectedShipSymbol ); + } + + $client->navigateShip( $selectedShipSymbol, $selectedWaypointSymbol ); + $storage->clearAllCache(); + $statusMessage = 'Navigation started for ' . $selectedShipSymbol . '.'; + } catch (SpacetradersApiException $e) { + $errorMessage = 'Unable to navigate ship: ' . $e->getMessage(); + } + } + } + if (isset( $_GET['accept_contract'] ) && is_string( $_GET['accept_contract'] ) && trim( $_GET['accept_contract'] ) !== '') { try { $client->acceptContract( trim( $_GET['accept_contract'] ) ); @@ -65,6 +104,101 @@ try { $agent = $agentResponse['data'] ?? $agentResponse; $ships = $shipsResponse['data'] ?? $shipsResponse; $contracts = $contractsResponse['data'] ?? $contractsResponse; + + $currentSystemSymbol = ''; + if (isset( $ships[0]['nav']['systemSymbol'] ) && is_string( $ships[0]['nav']['systemSymbol'] ) ) { + $currentSystemSymbol = $ships[0]['nav']['systemSymbol']; + } + + if ($currentSystemSymbol === '' && isset( $agent['headquarters'] ) && is_string( $agent['headquarters'] ) ) { + $hqParts = explode( '-', $agent['headquarters'] ); + if (count( $hqParts ) >= 2 ) { + $currentSystemSymbol = $hqParts[0] . '-' . $hqParts[1]; + } + } + + if ($currentSystemSymbol !== '' ) { + $systemResponse = $client->getSystem( $currentSystemSymbol ); + $system = $systemResponse['data'] ?? $systemResponse; + + $page = 1; + $total = 0; + do { + $waypointsResponse = $client->listWaypoints( + $currentSystemSymbol, + array( + 'page' => $page, + 'limit' => 20, + ) + ); + $pageData = $waypointsResponse['data'] ?? array(); + if (! is_array( $pageData ) || empty( $pageData ) ) { + break; + } + + $systemWaypoints = array_merge( $systemWaypoints, $pageData ); + $total = (int) ( $waypointsResponse['meta']['total'] ?? count( $systemWaypoints ) ); + $page++; + } while (count( $systemWaypoints ) < $total); + + foreach ( $systemWaypoints as $waypoint ) { + $waypointSymbol = (string) ( $waypoint['symbol'] ?? '' ); + $traits = $waypoint['traits'] ?? array(); + $hasMarket = false; + $hasShipyard = false; + + foreach ( $traits as $trait ) { + $traitSymbol = (string) ( $trait['symbol'] ?? '' ); + if ($traitSymbol === 'MARKETPLACE' ) { + $hasMarket = true; + } + + if ($traitSymbol === 'SHIPYARD' ) { + $hasShipyard = true; + } + } + + if ($hasMarket ) { + $marketRecord = array( + 'waypoint' => $waypoint, + 'data' => array(), + 'error' => '', + ); + try { + $marketResponse = $client->getWaypointMarket( $currentSystemSymbol, $waypointSymbol ); + $marketRecord['data'] = $marketResponse['data'] ?? array(); + } catch (SpacetradersApiException $e) { + $marketRecord['error'] = $e->getMessage(); + } + + $marketDetails[] = $marketRecord; + } + + if ($hasShipyard ) { + $shipyardRecord = array( + 'waypoint' => $waypoint, + 'data' => array(), + 'error' => '', + ); + try { + $shipyardResponse = $client->getWaypointShipyard( $currentSystemSymbol, $waypointSymbol ); + $shipyardRecord['data'] = $shipyardResponse['data'] ?? array(); + } catch (SpacetradersApiException $e) { + $shipyardRecord['error'] = $e->getMessage(); + } + + $shipyardDetails[] = $shipyardRecord; + } + } + + $waypointCount = count( $systemWaypoints ); + if ($waypointCount > 0 ) { + $waypointTotalPages = (int) ceil( $waypointCount / $waypointPageSize ); + $waypointPage = min( $waypointPage, $waypointTotalPages ); + $offset = ( $waypointPage - 1 ) * $waypointPageSize; + $paginatedWaypoints = array_slice( $systemWaypoints, $offset, $waypointPageSize ); + } + } } } catch (SpacetradersApiException $e) { $error = array( @@ -107,6 +241,10 @@ try {
Configuration + | + Buy Ships + | + Mining Fleet
@@ -135,6 +273,172 @@ try { System: +
+

System Details

+

+ Current System: + + + () + +

+ +
+ +
+ + +
+
+ + +
+ +
+ +
+ + + +
+ +
+ 0 ? ( ( $waypointPage - 1 ) * $waypointPageSize ) + 1 : 0; + $waypointEnd = min( $waypointPage * $waypointPageSize, $waypointCount ); + ?> +
+

+ Showing - + of waypoints +

+
+ 1 ) : ?> + + Previous + + + + + Next + + +
+
+
+ + + + + + + + + + + + + + + +
SymbolTypeCoordinatesTraits
+ , + + + +
+
+
+ + + + +
+

Ships

@@ -148,7 +452,14 @@ try { - + @@ -218,5 +529,37 @@ try { endforeach; ?> + + diff --git a/buy-ships.php b/buy-ships.php new file mode 100644 index 0000000..c6da396 --- /dev/null +++ b/buy-ships.php @@ -0,0 +1,241 @@ + + * @license MIT License + * @link https://git.keithsolomon.net/keith/Spacetraders + */ + +require_once __DIR__ . '/lib/spacetraders-api.php'; +require_once __DIR__ . '/lib/spacetraders-storage.php'; + +$config = require __DIR__ . '/lib/project-config.php'; + +$storage = new SpacetradersStorage( $config['db_path'] ); +$token = $storage->getAgentToken(); + +$statusMessage = ''; +$errorMessage = ''; +$ships = array(); +$shipyards = array(); +$agent = array(); +$currentSystemSymbol = ''; + +if (! is_string( $token ) || trim( $token ) === '' ) { + $envToken = getenv( 'SPACETRADERS_TOKEN' ); + if (is_string( $envToken ) && trim( $envToken ) !== '' ) { + $token = trim( $envToken ); + $storage->setAgentToken( $token ); + } +} + +if (! is_string( $token ) || trim( $token ) === '' ) { + $tokenError = 'No token found. Set one in config.php or SPACETRADERS_TOKEN.'; +} + +if (! isset( $tokenError ) ) { + $client = new SpacetradersApi( + trim( $token ), + $config['api_base_url'], + (int) $config['api_timeout'], + $storage, + (int) $config['cache_ttl'] + ); +} + +try { + if (! isset( $tokenError ) ) { + if ( + $_SERVER['REQUEST_METHOD'] === 'POST' && + isset( $_POST['ship_type'] ) && + isset( $_POST['waypoint_symbol'] ) + ) { + $shipType = trim( (string) $_POST['ship_type'] ); + $waypointSymbol = trim( (string) $_POST['waypoint_symbol'] ); + + if ($shipType !== '' && $waypointSymbol !== '' ) { + $purchaseResponse = $client->purchaseShip( $shipType, $waypointSymbol ); + $shipSymbol = (string) ( $purchaseResponse['data']['ship']['symbol'] ?? '' ); + $storage->clearAllCache(); + $statusMessage = 'Purchased ' . $shipType . ( $shipSymbol !== '' ? ' (' . $shipSymbol . ')' : '' ) . '.'; + } + } + + $agentResponse = $client->getMyAgent(); + $shipsResponse = $client->listMyShips(); + + $agent = $agentResponse['data'] ?? $agentResponse; + $ships = $shipsResponse['data'] ?? $shipsResponse; + + if (isset( $ships[0]['nav']['systemSymbol'] ) && is_string( $ships[0]['nav']['systemSymbol'] ) ) { + $currentSystemSymbol = $ships[0]['nav']['systemSymbol']; + } + + if ($currentSystemSymbol === '' && isset( $agent['headquarters'] ) && is_string( $agent['headquarters'] ) ) { + $hqParts = explode( '-', $agent['headquarters'] ); + if (count( $hqParts ) >= 2 ) { + $currentSystemSymbol = $hqParts[0] . '-' . $hqParts[1]; + } + } + + if ($currentSystemSymbol !== '' ) { + $page = 1; + $total = 0; + $waypoints = array(); + do { + $waypointsResponse = $client->listWaypoints( + $currentSystemSymbol, + array( + 'page' => $page, + 'limit' => 20, + ) + ); + $pageData = $waypointsResponse['data'] ?? array(); + if (! is_array( $pageData ) || empty( $pageData ) ) { + break; + } + + $waypoints = array_merge( $waypoints, $pageData ); + $total = (int) ( $waypointsResponse['meta']['total'] ?? count( $waypoints ) ); + $page++; + } while (count( $waypoints ) < $total); + + foreach ( $waypoints as $waypoint ) { + $traits = $waypoint['traits'] ?? array(); + $hasShipyard = false; + foreach ( $traits as $trait ) { + if ((string) ( $trait['symbol'] ?? '' ) === 'SHIPYARD' ) { + $hasShipyard = true; + break; + } + } + + if (! $hasShipyard ) { + continue; + } + + $waypointSymbol = (string) ( $waypoint['symbol'] ?? '' ); + $record = array( + 'waypoint' => $waypoint, + 'shipTypes' => array(), + 'prices' => array(), + 'error' => '', + ); + + try { + $shipyardResponse = $client->getWaypointShipyard( $currentSystemSymbol, $waypointSymbol ); + $shipyardData = $shipyardResponse['data'] ?? array(); + $record['shipTypes'] = (array) ( $shipyardData['shipTypes'] ?? array() ); + + $transactions = (array) ( $shipyardData['transactions'] ?? array() ); + foreach ( $transactions as $transaction ) { + $shipType = (string) ( $transaction['shipType'] ?? '' ); + $price = (int) ( $transaction['price'] ?? 0 ); + if ($shipType !== '' && $price > 0 && ! isset( $record['prices'][ $shipType ] ) ) { + $record['prices'][ $shipType ] = $price; + } + } + } catch (SpacetradersApiException $e) { + $record['error'] = $e->getMessage(); + } + + $shipyards[] = $record; + } + } + } +} catch (SpacetradersApiException $e) { + $errorMessage = $e->getMessage(); +} +?> + + + + + + Spacetraders - Buy Ships + + + + +
+ Agent Info + Mining Fleet + Configuration +
+ +

Buy Ships

+ + +
+ +
+ + + + + + +
+ +
+ + + +
+ +
+ + +

+ Credits: + + | Current System: + +

+ + +
+ No shipyards were found in your current system. +
+ + + +
+

+ Shipyard: +

+ + +

+ +
+ + + +
+ + + + + + + + + + + + + + +
Ship TypeKnown PriceAction
+ 0 ? number_format( $knownPrice ) : 'Unknown'; ?> + +
+ + + +
+
+ + + + + diff --git a/config.php b/config.php index 8b42974..50d422a 100644 --- a/config.php +++ b/config.php @@ -89,6 +89,10 @@ $hasToken = is_string( $token ) && trim( $token ) !== ''; - Back to Agent Info +
+ Agent Info + Buy Ships + Mining Fleet +
diff --git a/lib/spacetraders-api.php b/lib/spacetraders-api.php index 34346c4..d9c8cf6 100644 --- a/lib/spacetraders-api.php +++ b/lib/spacetraders-api.php @@ -329,6 +329,51 @@ class SpacetradersApi { ); } + /** + * Get information about a single waypoint. + * + * @param string $systemSymbol The system symbol. + * @param string $waypointSymbol The waypoint symbol. + * + * @return array + */ + public function getWaypoint( string $systemSymbol, string $waypointSymbol ): array { + return $this->request( + 'GET', + '/systems/' . rawurlencode( $systemSymbol ) . '/waypoints/' . rawurlencode( $waypointSymbol ) + ); + } + + /** + * Get market data for a waypoint with a marketplace. + * + * @param string $systemSymbol The system symbol. + * @param string $waypointSymbol The waypoint symbol. + * + * @return array + */ + public function getWaypointMarket( string $systemSymbol, string $waypointSymbol ): array { + return $this->request( + 'GET', + '/systems/' . rawurlencode( $systemSymbol ) . '/waypoints/' . rawurlencode( $waypointSymbol ) . '/market' + ); + } + + /** + * Get shipyard data for a waypoint with a shipyard. + * + * @param string $systemSymbol The system symbol. + * @param string $waypointSymbol The waypoint symbol. + * + * @return array + */ + public function getWaypointShipyard( string $systemSymbol, string $waypointSymbol ): array { + return $this->request( + 'GET', + '/systems/' . rawurlencode( $systemSymbol ) . '/waypoints/' . rawurlencode( $waypointSymbol ) . '/shipyard' + ); + } + /** * Navigate a ship to a specific waypoint. * @@ -387,6 +432,61 @@ class SpacetradersApi { ); } + /** + * Create a survey at the ship's current waypoint. + * + * @param string $shipSymbol The symbol of the ship creating the survey. + * + * @return array + */ + public function surveyWaypoint( string $shipSymbol ): array { + return $this->request( + 'POST', + '/my/ships/' . rawurlencode( $shipSymbol ) . '/survey' + ); + } + + /** + * Siphon resources at the ship's current waypoint. + * + * @param string $shipSymbol The symbol of the ship performing siphon. + * + * @return array + */ + public function siphonResources( string $shipSymbol ): array { + return $this->request( + 'POST', + '/my/ships/' . rawurlencode( $shipSymbol ) . '/siphon' + ); + } + + /** + * Refuel a ship. + * + * @param string $shipSymbol The symbol of the ship to refuel. + * @param bool $fromCargo Whether to refuel from cargo instead of market. + * @param int|null $units Optional number of fuel units to buy. + * + * @return array + */ + public function refuelShip( string $shipSymbol, bool $fromCargo = false, ?int $units = null ): array { + $payload = array(); + + if ($fromCargo ) { + $payload['fromCargo'] = true; + } + + if ($units !== null && $units > 0 ) { + $payload['units'] = $units; + } + + return $this->request( + 'POST', + '/my/ships/' . rawurlencode( $shipSymbol ) . '/refuel', + $payload + ); + } + /** * Purchase cargo for a specific ship at its current waypoint. * @@ -427,6 +527,45 @@ class SpacetradersApi { ); } + /** + * Jettison cargo from a ship. + * + * @param string $shipSymbol The symbol of the ship. + * @param string $tradeSymbol The cargo symbol to jettison. + * @param int $units Number of units to jettison. + * + * @return array + */ + public function jettisonCargo( string $shipSymbol, string $tradeSymbol, int $units ): array { + return $this->request( + 'POST', + '/my/ships/' . rawurlencode( $shipSymbol ) . '/jettison', + array( + 'symbol' => $tradeSymbol, + 'units' => $units, + ) + ); + } + + /** + * Purchase a new ship from a shipyard. + * + * @param string $shipType The ship type to purchase. + * @param string $waypointSymbol The shipyard waypoint symbol. + * + * @return array + */ + public function purchaseShip( string $shipType, string $waypointSymbol ): array { + return $this->request( + 'POST', + '/my/ships', + array( + 'shipType' => $shipType, + 'waypointSymbol' => $waypointSymbol, + ) + ); + } + /** * Build the full URL for an API endpoint with optional query parameters. * diff --git a/mining-fleet.php b/mining-fleet.php new file mode 100644 index 0000000..8e89988 --- /dev/null +++ b/mining-fleet.php @@ -0,0 +1,552 @@ + + * @license MIT License + * @link https://git.keithsolomon.net/keith/Spacetraders + */ + +require_once __DIR__ . '/lib/spacetraders-api.php'; +require_once __DIR__ . '/lib/spacetraders-storage.php'; + +/** + * Filter only mining ships. + * + * @param array> $ships + * + * @return array> + */ +function spacetraders_filter_mining_ships( array $ships ): array { + return array_values( + array_filter( + $ships, + static function ( array $ship ): bool { + $role = strtoupper( (string) ( $ship['registration']['role'] ?? '' ) ); + return $role === 'EXCAVATOR'; + } + ) + ); +} + +$config = require __DIR__ . '/lib/project-config.php'; + +$storage = new SpacetradersStorage( $config['db_path'] ); +$token = $storage->getAgentToken(); + +$statusMessage = ''; +$errorMessage = ''; +$actionResults = array(); +$agent = array(); +$ships = array(); +$miningShips = array(); +$activeContracts = array(); +$marketWaypoints = array(); +$selectedMarketWaypoint = ''; + +if (! is_string( $token ) || trim( $token ) === '' ) { + $envToken = getenv( 'SPACETRADERS_TOKEN' ); + if (is_string( $envToken ) && trim( $envToken ) !== '' ) { + $token = trim( $envToken ); + $storage->setAgentToken( $token ); + } +} + +if (! is_string( $token ) || trim( $token ) === '' ) { + $tokenError = 'No token found. Set one in config.php or SPACETRADERS_TOKEN.'; +} + +if (! isset( $tokenError ) ) { + $client = new SpacetradersApi( + trim( $token ), + $config['api_base_url'], + (int) $config['api_timeout'], + $storage, + (int) $config['cache_ttl'] + ); +} + +try { + if (! isset( $tokenError ) ) { + $agentResponse = $client->getMyAgent(); + $shipsResponse = $client->listMyShips(); + $contractsResponse = $client->listMyContracts(); + + $agent = $agentResponse['data'] ?? $agentResponse; + $ships = $shipsResponse['data'] ?? $shipsResponse; + $contracts = $contractsResponse['data'] ?? $contractsResponse; + $miningShips = spacetraders_filter_mining_ships( $ships ); + $activeContracts = array_values( + array_filter( + (array) $contracts, + static function ( array $contract ): bool { + return (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false ); + } + ) + ); + + $currentSystemSymbol = ''; + if (! empty( $miningShips ) && isset( $miningShips[0]['nav']['systemSymbol'] ) ) { + $currentSystemSymbol = (string) $miningShips[0]['nav']['systemSymbol']; + } elseif (! empty( $ships ) && isset( $ships[0]['nav']['systemSymbol'] ) ) { + $currentSystemSymbol = (string) $ships[0]['nav']['systemSymbol']; + } elseif (isset( $agent['headquarters'] ) && is_string( $agent['headquarters'] ) ) { + $hqParts = explode( '-', $agent['headquarters'] ); + if (count( $hqParts ) >= 2 ) { + $currentSystemSymbol = $hqParts[0] . '-' . $hqParts[1]; + } + } + + if ($currentSystemSymbol !== '' ) { + $page = 1; + $total = 0; + $waypoints = array(); + do { + $waypointsResponse = $client->listWaypoints( + $currentSystemSymbol, + array( + 'page' => $page, + 'limit' => 20, + ) + ); + $pageData = $waypointsResponse['data'] ?? array(); + if (! is_array( $pageData ) || empty( $pageData ) ) { + break; + } + + $waypoints = array_merge( $waypoints, $pageData ); + $total = (int) ( $waypointsResponse['meta']['total'] ?? count( $waypoints ) ); + $page++; + } while (count( $waypoints ) < $total); + + foreach ( $waypoints as $waypoint ) { + foreach ( (array) ( $waypoint['traits'] ?? array() ) as $trait ) { + if ((string) ( $trait['symbol'] ?? '' ) === 'MARKETPLACE' ) { + $marketWaypoints[] = (string) ( $waypoint['symbol'] ?? '' ); + break; + } + } + } + } + + $selectedMarketWaypoint = isset( $_POST['market_waypoint'] ) ? + trim( (string) $_POST['market_waypoint'] ) : + ( $marketWaypoints[0] ?? '' ); + + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['fleet_action'] ) ) { + $fleetAction = (string) $_POST['fleet_action']; + $successCount = 0; + + foreach ( $miningShips as $fleetShip ) { + $shipSymbol = (string) ( $fleetShip['symbol'] ?? '' ); + if ($shipSymbol === '' ) { + continue; + } + + try { + $shipResponse = $client->getShip( $shipSymbol ); + $shipData = $shipResponse['data'] ?? array(); + $shipStatus = (string) ( $shipData['nav']['status'] ?? '' ); + $waypointSymbol = (string) ( $shipData['nav']['waypointSymbol'] ?? '' ); + + if ($fleetAction === 'excavate_all' ) { + if ($shipStatus === 'IN_TRANSIT' ) { + $actionResults[] = $shipSymbol . ': skipped (in transit).'; + continue; + } + + if ($shipStatus === 'DOCKED' ) { + $client->orbitShip( $shipSymbol ); + } + + $client->extractResources( $shipSymbol ); + $actionResults[] = $shipSymbol . ': extracting.'; + $successCount++; + } elseif ($fleetAction === 'dock_refuel_all' ) { + if ($shipStatus === 'IN_TRANSIT' ) { + $actionResults[] = $shipSymbol . ': skipped (in transit).'; + continue; + } + + if ($shipStatus !== 'DOCKED' ) { + $client->dockShip( $shipSymbol ); + } + + $client->refuelShip( $shipSymbol ); + $actionResults[] = $shipSymbol . ': docked/refueled.'; + $successCount++; + } elseif ($fleetAction === 'move_and_sell_all' ) { + if ($selectedMarketWaypoint === '' ) { + $actionResults[] = $shipSymbol . ': no market waypoint selected.'; + continue; + } + + if ($shipStatus === 'IN_TRANSIT' ) { + $actionResults[] = $shipSymbol . ': already in transit.'; + continue; + } + + if ($waypointSymbol !== $selectedMarketWaypoint ) { + if ($shipStatus === 'DOCKED' ) { + $client->orbitShip( $shipSymbol ); + } + + $client->navigateShip( $shipSymbol, $selectedMarketWaypoint ); + $actionResults[] = $shipSymbol . ': navigating to ' . $selectedMarketWaypoint . '.'; + $successCount++; + continue; + } + + if ($shipStatus !== 'DOCKED' ) { + $client->dockShip( $shipSymbol ); + } + + $inventory = (array) ( $shipData['cargo']['inventory'] ?? array() ); + $soldItems = 0; + foreach ( $inventory as $item ) { + $tradeSymbol = (string) ( $item['symbol'] ?? '' ); + $units = (int) ( $item['units'] ?? 0 ); + if ($tradeSymbol === '' || $units <= 0 ) { + continue; + } + + try { + $client->sellCargo( $shipSymbol, $tradeSymbol, $units ); + $soldItems++; + } catch (SpacetradersApiException $e) { + // Ignore individual sell failures and continue. + } + } + + $actionResults[] = $shipSymbol . ': sold ' . $soldItems . ' cargo item type(s).'; + $successCount++; + } elseif ($fleetAction === 'sell_cargo_here_all' ) { + if ($shipStatus === 'IN_TRANSIT' ) { + $actionResults[] = $shipSymbol . ': skipped (in transit).'; + continue; + } + + if ($shipStatus !== 'DOCKED' ) { + $client->dockShip( $shipSymbol ); + } + + $inventory = (array) ( $shipData['cargo']['inventory'] ?? array() ); + $soldItems = 0; + foreach ( $inventory as $item ) { + $tradeSymbol = (string) ( $item['symbol'] ?? '' ); + $units = (int) ( $item['units'] ?? 0 ); + if ($tradeSymbol === '' || $units <= 0 ) { + continue; + } + + try { + $client->sellCargo( $shipSymbol, $tradeSymbol, $units ); + $soldItems++; + } catch (SpacetradersApiException $e) { + // Continue selling other items. + } + } + + $actionResults[] = $shipSymbol . ': sold ' . $soldItems . ' cargo item type(s) at current waypoint.'; + $successCount++; + } elseif ($fleetAction === 'jettison_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; + } + + try { + $client->jettisonCargo( $shipSymbol, $tradeSymbol, $units ); + $jettisonedItems++; + } catch (SpacetradersApiException $e) { + // Continue jettisoning other items. + } + } + + $actionResults[] = $shipSymbol . ': jettisoned ' . $jettisonedItems . ' cargo item type(s).'; + $successCount++; + } + } catch (SpacetradersApiException $e) { + $actionResults[] = $shipSymbol . ': ' . $e->getMessage(); + } + } + + if ($successCount > 0 ) { + $storage->clearAllCache(); + } + + $statusMessage = 'Action complete for ' . $successCount . ' ship(s).'; + + $shipsResponse = $client->listMyShips(); + $contractsResponse = $client->listMyContracts(); + $ships = $shipsResponse['data'] ?? $shipsResponse; + $contracts = $contractsResponse['data'] ?? $contractsResponse; + $miningShips = spacetraders_filter_mining_ships( $ships ); + $activeContracts = array_values( + array_filter( + (array) $contracts, + static function ( array $contract ): bool { + return (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false ); + } + ) + ); + } + + 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'] ) + ) { + $shipAction = (string) $_POST['ship_action']; + $sellShipSymbol = trim( (string) $_POST['ship_symbol'] ); + + if ($sellShipSymbol !== '' ) { + try { + $shipResponse = $client->getShip( $sellShipSymbol ); + $shipData = $shipResponse['data'] ?? array(); + $shipStatus = (string) ( $shipData['nav']['status'] ?? '' ); + + if ($shipStatus === 'IN_TRANSIT' ) { + throw new SpacetradersApiException( 'Ship is in transit and cargo action is unavailable right now.' ); + } + + if ($shipAction === 'sell_ship_cargo' && $shipStatus !== 'DOCKED' ) { + $client->dockShip( $sellShipSymbol ); + } + + $inventory = (array) ( $shipData['cargo']['inventory'] ?? array() ); + $handledItems = 0; + foreach ( $inventory as $item ) { + $tradeSymbol = (string) ( $item['symbol'] ?? '' ); + $units = (int) ( $item['units'] ?? 0 ); + if ($tradeSymbol === '' || $units <= 0 ) { + continue; + } + + try { + if ($shipAction === 'sell_ship_cargo' ) { + $client->sellCargo( $sellShipSymbol, $tradeSymbol, $units ); + } else { + $client->jettisonCargo( $sellShipSymbol, $tradeSymbol, $units ); + } + $handledItems++; + } catch (SpacetradersApiException $e) { + // Continue processing other items. + } + } + + if ($shipAction === 'sell_ship_cargo' ) { + $statusMessage = $sellShipSymbol . ': sold ' . $handledItems . ' cargo item type(s).'; + } else { + $statusMessage = $sellShipSymbol . ': jettisoned ' . $handledItems . ' cargo item type(s).'; + } + $storage->clearAllCache(); + + $shipsResponse = $client->listMyShips(); + $ships = $shipsResponse['data'] ?? $shipsResponse; + $miningShips = spacetraders_filter_mining_ships( $ships ); + } catch (SpacetradersApiException $e) { + $errorMessage = $sellShipSymbol . ': ' . $e->getMessage(); + } + } + } + } +} catch (SpacetradersApiException $e) { + $errorMessage = $e->getMessage(); +} +?> + + + + + + Spacetraders - Mining Fleet + + + + + +

Mining Fleet

+ + +
+ +
+ + + + + + +
+ +
+ + + +
+ +
+ + +
+

Global Controls

+
+
+ + +
+ + + + + + +
+
+ + +
+

Action Results

+
    + +
  • + +
+
+ + +

+ Mining Ships: +

+ +
+

Active Contracts

+ +

No active contracts.

+ +
+ + +
+

Contract:

+

Type:

+

+ Deadline: + +

+

+ Payment: + + + + +

+ +

Deliveries:

+
    + + +
  • + : + / + to +
  • + +
+ +
+ +
+ +
+ + +
No mining ships found (role: EXCAVATOR).
+ + +
+ + +
+

+ + + +

+

Name:

+

Status:

+

Waypoint:

+

Fuel: /

+

Cargo: /

+ + + +

Cargo Contents:

+
    + +
  • + : + +
  • + +
+ +
+ + + +
+
+ + + +
+ +
+ +
+ + diff --git a/ship-details.php b/ship-details.php new file mode 100644 index 0000000..50c492b --- /dev/null +++ b/ship-details.php @@ -0,0 +1,422 @@ + + * @license MIT License + * @link https://git.keithsolomon.net/keith/Spacetraders + */ + +require_once __DIR__ . '/lib/spacetraders-api.php'; +require_once __DIR__ . '/lib/spacetraders-storage.php'; + +$config = require __DIR__ . '/lib/project-config.php'; + +$storage = new SpacetradersStorage( $config['db_path'] ); +$token = $storage->getAgentToken(); + +$statusMessage = ''; +$errorMessage = ''; +$ship = array(); +$shipList = array(); +$shipSymbol = isset( $_GET['ship'] ) ? trim( (string) $_GET['ship'] ) : ''; + +if (! is_string( $token ) || trim( $token ) === '' ) { + $envToken = getenv( 'SPACETRADERS_TOKEN' ); + if (is_string( $envToken ) && trim( $envToken ) !== '' ) { + $token = trim( $envToken ); + $storage->setAgentToken( $token ); + } +} + +if (! is_string( $token ) || trim( $token ) === '' ) { + $tokenError = 'No token found. Set one in config.php or SPACETRADERS_TOKEN.'; +} + +if (! isset( $tokenError ) ) { + $client = new SpacetradersApi( + trim( $token ), + $config['api_base_url'], + (int) $config['api_timeout'], + $storage, + (int) $config['cache_ttl'] + ); +} + +try { + if (! isset( $tokenError ) ) { + $shipListResponse = $client->listMyShips(); + $shipList = $shipListResponse['data'] ?? array(); + + if ($shipSymbol === '' && ! empty( $shipList ) ) { + $shipSymbol = (string) ( $shipList[0]['symbol'] ?? '' ); + } + + if ($shipSymbol !== '' ) { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['ship_action'] ) ) { + $action = (string) $_POST['ship_action']; + switch ($action) { + case 'orbit': + $client->orbitShip( $shipSymbol ); + $statusMessage = 'Ship set to orbit.'; + break; + case 'dock': + $client->dockShip( $shipSymbol ); + $statusMessage = 'Ship docked.'; + break; + case 'extract': + $client->extractResources( $shipSymbol ); + $statusMessage = 'Resource extraction started.'; + break; + case 'survey': + $client->surveyWaypoint( $shipSymbol ); + $statusMessage = 'Survey completed.'; + break; + case 'siphon': + $client->siphonResources( $shipSymbol ); + $statusMessage = 'Siphon action completed.'; + break; + case 'refuel': + $client->refuelShip( $shipSymbol ); + $statusMessage = 'Ship refueled.'; + break; + case 'sell_all_cargo': + $shipResponse = $client->getShip( $shipSymbol ); + $shipData = $shipResponse['data'] ?? array(); + $shipStatus = (string) ( $shipData['nav']['status'] ?? '' ); + + if ($shipStatus === 'IN_TRANSIT' ) { + throw new SpacetradersApiException( 'Ship is in transit and cannot sell cargo right now.' ); + } + + if ($shipStatus !== 'DOCKED' ) { + $client->dockShip( $shipSymbol ); + } + + $inventory = (array) ( $shipData['cargo']['inventory'] ?? array() ); + $soldItems = 0; + foreach ( $inventory as $item ) { + $tradeSymbol = (string) ( $item['symbol'] ?? '' ); + $units = (int) ( $item['units'] ?? 0 ); + if ($tradeSymbol === '' || $units <= 0 ) { + continue; + } + + try { + $client->sellCargo( $shipSymbol, $tradeSymbol, $units ); + $soldItems++; + } catch (SpacetradersApiException $e) { + // Continue selling other items. + } + } + + $statusMessage = 'Attempted to sell all cargo item types. Sold: ' . $soldItems . '.'; + break; + case 'sell_cargo_item': + $tradeSymbol = trim( (string) ( $_POST['trade_symbol'] ?? '' ) ); + $units = (int) ( $_POST['units'] ?? 0 ); + + if ($tradeSymbol === '' || $units <= 0 ) { + throw new SpacetradersApiException( 'Trade symbol and units are required to sell cargo.' ); + } + + $shipResponse = $client->getShip( $shipSymbol ); + $shipData = $shipResponse['data'] ?? array(); + $shipStatus = (string) ( $shipData['nav']['status'] ?? '' ); + + if ($shipStatus === 'IN_TRANSIT' ) { + throw new SpacetradersApiException( 'Ship is in transit and cannot sell cargo right now.' ); + } + + if ($shipStatus !== 'DOCKED' ) { + $client->dockShip( $shipSymbol ); + } + + $client->sellCargo( $shipSymbol, $tradeSymbol, $units ); + $statusMessage = 'Sold ' . number_format( $units ) . ' units of ' . $tradeSymbol . '.'; + break; + case 'jettison_all_cargo': + $shipResponse = $client->getShip( $shipSymbol ); + $shipData = $shipResponse['data'] ?? array(); + $shipStatus = (string) ( $shipData['nav']['status'] ?? '' ); + if ($shipStatus === 'IN_TRANSIT' ) { + throw new SpacetradersApiException( 'Ship is in transit and cannot jettison cargo right now.' ); + } + + $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; + } + + try { + $client->jettisonCargo( $shipSymbol, $tradeSymbol, $units ); + $jettisonedItems++; + } catch (SpacetradersApiException $e) { + // Continue attempting the rest. + } + } + + $statusMessage = 'Attempted to jettison all cargo item types. Jettisoned: ' . $jettisonedItems . '.'; + break; + case 'jettison_cargo_item': + $tradeSymbol = trim( (string) ( $_POST['trade_symbol'] ?? '' ) ); + $units = (int) ( $_POST['units'] ?? 0 ); + if ($tradeSymbol === '' || $units <= 0 ) { + throw new SpacetradersApiException( 'Trade symbol and units are required to jettison cargo.' ); + } + + $shipResponse = $client->getShip( $shipSymbol ); + $shipData = $shipResponse['data'] ?? array(); + $shipStatus = (string) ( $shipData['nav']['status'] ?? '' ); + if ($shipStatus === 'IN_TRANSIT' ) { + throw new SpacetradersApiException( 'Ship is in transit and cannot jettison cargo right now.' ); + } + + $client->jettisonCargo( $shipSymbol, $tradeSymbol, $units ); + $statusMessage = 'Jettisoned ' . number_format( $units ) . ' units of ' . $tradeSymbol . '.'; + break; + } + + if ($statusMessage !== '' ) { + $storage->clearAllCache(); + } + } + + $shipResponse = $client->getShip( $shipSymbol ); + $ship = $shipResponse['data'] ?? $shipResponse; + } + } +} catch (SpacetradersApiException $e) { + $errorMessage = $e->getMessage(); +} +?> + + + + + + Spacetraders - Ship Details + + + + + +

Ship Details

+ + +
+ +
+ + + + + + +
+ +
+ + + +
+ +
+ + +
+
+ + +
+ +
+ + + 0 && $fuelCurrent < $fuelCapacity; + ?> +
+

Ship Controls

+
+ + + + + + + + + + + + + + + + + + + + + + + + + 0 ) : ?> + + + +
+

+ Role: | + Status: | + Fuel: / +

+
+ +
+
+

Overview

+

Symbol:

+

Name:

+

Role:

+

Faction:

+
+ +
+

Navigation

+

Status:

+

Flight Mode:

+

System:

+

Waypoint:

+
+ +
+

Frame / Engine / Reactor

+

Frame:

+

Engine:

+

Reactor:

+
+ +
+

Cargo / Fuel

+

Cargo: /

+

Fuel: /

+

Cooldown Remaining: s

+
+
+ +
+

Mounts

+ + +

No mounts equipped.

+ +
    + +
  • + +
+ +
+ +
+

Modules

+ + +

No modules installed.

+ +
    + +
  • + +
+ +
+ +
+

Cargo Inventory

+ + +

Cargo hold is empty.

+ + + + + + + + + + + + + + + + + + + + + +
SymbolNameUnitsDescriptionSellJettison
+ 0 ) : ?> +
+ + + + +
+ +
+ 0 ) : ?> +
+ + + + +
+ +
+ +
+ + +