849 lines
40 KiB
PHP
849 lines
40 KiB
PHP
<?php
|
|
/**
|
|
* Spacetraders dashboard page.
|
|
*
|
|
* @package SpacetradersAPI
|
|
* @author Keith Solomon <keith@keithsolomon.net>
|
|
* @license MIT License
|
|
* @version GIT: <git_id>
|
|
* @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 = '';
|
|
|
|
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.';
|
|
}
|
|
|
|
$agent = array();
|
|
$ships = array();
|
|
$contracts = array();
|
|
$system = array();
|
|
$systemWaypoints = array();
|
|
$paginatedWaypoints = array();
|
|
$marketDetails = array();
|
|
$shipyardDetails = array();
|
|
$activeContracts = array();
|
|
$pendingContracts = array();
|
|
$deliveryReadyByTradeSymbol = array();
|
|
$contractNegotiationShipSymbol = '';
|
|
$selectedShipSymbol = '';
|
|
$selectedWaypointSymbol = '';
|
|
$waypointPageSize = 15;
|
|
$waypointPage = isset( $_GET['waypoint_page'] ) ? max( 1, (int) $_GET['waypoint_page'] ) : 1;
|
|
$waypointTotalPages = 1;
|
|
|
|
if (! isset( $tokenError ) ) {
|
|
$client = new SpacetradersApi(
|
|
trim( $token ),
|
|
$config['api_base_url'],
|
|
(int) $config['api_timeout'],
|
|
$storage,
|
|
(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'] ) );
|
|
$storage->clearAllCache();
|
|
$statusMessage = 'Contract accepted.';
|
|
} catch (SpacetradersApiException $e) {
|
|
$errorMessage = 'Unable to accept contract: ' . $e->getMessage();
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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 !== '' ) {
|
|
$client->fulfillContract( $contractId );
|
|
$storage->clearAllCache();
|
|
$statusMessage = 'Contract ' . $contractId . ' fulfilled.';
|
|
}
|
|
} elseif ($contractAction === 'deliver_contract' ) {
|
|
$contractId = trim( (string) ( $_POST['contract_id'] ?? '' ) );
|
|
if ($contractId !== '' ) {
|
|
$contractToDeliver = null;
|
|
foreach ( (array) $contracts as $contract ) {
|
|
if ((string) ( $contract['id'] ?? '' ) === $contractId ) {
|
|
$contractToDeliver = $contract;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (! is_array( $contractToDeliver ) ) {
|
|
throw new SpacetradersApiException( 'Contract not found.' );
|
|
}
|
|
|
|
$deliveries = (array) ( $contractToDeliver['terms']['deliver'] ?? array() );
|
|
$deliveryAttempts = 0;
|
|
|
|
foreach ( $deliveries as $delivery ) {
|
|
$tradeSymbol = (string) ( $delivery['tradeSymbol'] ?? '' );
|
|
$destinationSymbol = (string) ( $delivery['destinationSymbol'] ?? '' );
|
|
$unitsRequired = (int) ( $delivery['unitsRequired'] ?? 0 );
|
|
$unitsFulfilled = (int) ( $delivery['unitsFulfilled'] ?? 0 );
|
|
$unitsRemaining = max( 0, $unitsRequired - $unitsFulfilled );
|
|
|
|
if ($tradeSymbol === '' || $destinationSymbol === '' || $unitsRemaining <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
foreach ( $ships as $ship ) {
|
|
if ($unitsRemaining <= 0 ) {
|
|
break;
|
|
}
|
|
|
|
$shipSymbol = (string) ( $ship['symbol'] ?? '' );
|
|
$shipStatus = (string) ( $ship['nav']['status'] ?? '' );
|
|
$shipWaypoint = (string) ( $ship['nav']['waypointSymbol'] ?? '' );
|
|
|
|
if ($shipSymbol === '' || $shipStatus === 'IN_TRANSIT' || $shipWaypoint !== $destinationSymbol ) {
|
|
continue;
|
|
}
|
|
|
|
$shipResponse = $client->getShip( $shipSymbol );
|
|
$shipData = $shipResponse['data'] ?? array();
|
|
|
|
$currentStatus = (string) ( $shipData['nav']['status'] ?? '' );
|
|
if ($currentStatus === 'IN_TRANSIT' ) {
|
|
continue;
|
|
}
|
|
|
|
if ($currentStatus !== 'DOCKED' ) {
|
|
$client->dockShip( $shipSymbol );
|
|
}
|
|
|
|
$availableUnits = 0;
|
|
$inventory = (array) ( $shipData['cargo']['inventory'] ?? array() );
|
|
foreach ( $inventory as $item ) {
|
|
if ((string) ( $item['symbol'] ?? '' ) === $tradeSymbol ) {
|
|
$availableUnits = (int) ( $item['units'] ?? 0 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($availableUnits <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
$deliverUnits = min( $availableUnits, $unitsRemaining );
|
|
if ($deliverUnits <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
$client->deliverContractCargo( $contractId, $shipSymbol, $tradeSymbol, $deliverUnits );
|
|
$unitsRemaining -= $deliverUnits;
|
|
$deliveryAttempts++;
|
|
}
|
|
}
|
|
|
|
if ($deliveryAttempts > 0 ) {
|
|
$storage->clearAllCache();
|
|
$statusMessage = 'Delivered contract cargo using ' . $deliveryAttempts . ' shipment(s) for contract ' . $contractId . '.';
|
|
} else {
|
|
$errorMessage = 'No eligible cargo/ships available to deliver for contract ' . $contractId . '.';
|
|
}
|
|
}
|
|
} elseif ($contractAction === 'negotiate_contract' ) {
|
|
$negotiateShipSymbol = trim( (string) ( $_POST['negotiate_ship_symbol'] ?? '' ) );
|
|
if ($negotiateShipSymbol === '' ) {
|
|
$negotiateShipSymbol = (string) ( $ships[0]['symbol'] ?? '' );
|
|
}
|
|
|
|
if ($negotiateShipSymbol === '' ) {
|
|
throw new SpacetradersApiException( 'No available ship found to negotiate a contract.' );
|
|
}
|
|
|
|
$client->negotiateContract( $negotiateShipSymbol );
|
|
$storage->clearAllCache();
|
|
$statusMessage = 'Negotiated a new contract using ' . $negotiateShipSymbol . '.';
|
|
}
|
|
|
|
if ($statusMessage !== '' || $errorMessage !== '' ) {
|
|
$shipsResponse = $client->listMyShips();
|
|
$contractsResponse = $client->listMyContracts();
|
|
$ships = $shipsResponse['data'] ?? $shipsResponse;
|
|
$contracts = $contractsResponse['data'] ?? $contractsResponse;
|
|
}
|
|
}
|
|
|
|
$activeContracts = array_values(
|
|
array_filter(
|
|
(array) $contracts,
|
|
static function ( array $contract ): bool {
|
|
return (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false );
|
|
}
|
|
)
|
|
);
|
|
|
|
$pendingContracts = array_values(
|
|
array_filter(
|
|
(array) $contracts,
|
|
static function ( array $contract ): bool {
|
|
return ! (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false );
|
|
}
|
|
)
|
|
);
|
|
|
|
foreach ( $ships as $ship ) {
|
|
$inventory = (array) ( $ship['cargo']['inventory'] ?? array() );
|
|
foreach ( $inventory as $item ) {
|
|
$tradeSymbol = (string) ( $item['symbol'] ?? '' );
|
|
$units = (int) ( $item['units'] ?? 0 );
|
|
if ($tradeSymbol === '' || $units <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
if (! isset( $deliveryReadyByTradeSymbol[ $tradeSymbol ] ) ) {
|
|
$deliveryReadyByTradeSymbol[ $tradeSymbol ] = 0;
|
|
}
|
|
|
|
$deliveryReadyByTradeSymbol[ $tradeSymbol ] += $units;
|
|
}
|
|
}
|
|
|
|
$contractNegotiationShipSymbol = (string) ( $ships[0]['symbol'] ?? '' );
|
|
|
|
$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(
|
|
'error' => $e->getMessage(),
|
|
'code' => $e->getCode(),
|
|
'payload' => $e->getErrorPayload(),
|
|
);
|
|
|
|
http_response_code( 500 );
|
|
header( 'Content-Type: application/json; charset=utf-8' );
|
|
echo json_encode( $error, JSON_PRETTY_PRINT );
|
|
}
|
|
?>
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Spacetraders - Dashboard</title>
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
</head>
|
|
|
|
<body class="container mx-auto px-4 py-8 bg-stone-800 text-gray-200">
|
|
<?php if (isset( $tokenError ) ) : ?>
|
|
<div class="mb-6 border border-red-500 p-4 rounded">
|
|
<p class="mb-3 text-red-300"><?php echo htmlspecialchars( $tokenError ); ?></p>
|
|
<a href="config.php" class="text-blue-400 hover:underline">Open Configuration Page</a>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
exit;
|
|
endif;
|
|
|
|
require __DIR__ . '/main-menu.php';
|
|
|
|
$msg = '';
|
|
$class = '';
|
|
|
|
if ($statusMessage !== '' ) {
|
|
$msg = $statusMessage;
|
|
$class = 'border-green-500 text-green-300';
|
|
} elseif ($errorMessage !== '' ) {
|
|
$msg = $errorMessage;
|
|
$class = 'border-red-500 text-red-300';
|
|
}
|
|
|
|
if ($msg !== '' ) :
|
|
?>
|
|
<div class="mb-6 border p-4 rounded <?php echo $class; ?>">
|
|
<?php echo htmlspecialchars( $msg ); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<h1 class="text-3xl font-bold mb-6 underline decoration-gray-300 w-full"><a href="index.php" class="">Spacetraders - Dashboard</a></h1>
|
|
|
|
<h2 class="text-2xl font-bold mb-2">
|
|
Agent: <?php echo htmlspecialchars( ucfirst( strtolower( $agent['symbol'] ) ) ); ?>
|
|
</h2>
|
|
|
|
<h3 class="text-xl font-bold mb-4">
|
|
Headquarters: <span class="font-normal"><?php echo htmlspecialchars( $agent['headquarters'] ); ?></span><br>
|
|
Faction: <span class="font-normal"><?php echo htmlspecialchars( ucfirst( strtolower( $agent['startingFaction'] ) ) ); ?></span><br>
|
|
Ship Count: <span class="font-normal"><?php echo htmlspecialchars( $agent['shipCount'] ); ?></span><br>
|
|
System: <span class="font-normal"><?php echo htmlspecialchars( $ships[0]['nav']['systemSymbol'] ); ?></span>
|
|
</h3>
|
|
|
|
<div class="mb-8 border border-gray-600 p-4 rounded">
|
|
<h2 class="text-2xl font-bold mb-2">System Details</h2>
|
|
<p class="mb-3 text-gray-300">
|
|
Current System:
|
|
<span class="font-semibold"><?php echo htmlspecialchars( (string) ( $system['symbol'] ?? 'Unknown' ) ); ?></span>
|
|
<?php if (isset( $system['type'] ) ) : ?>
|
|
(<?php echo htmlspecialchars( (string) $system['type'] ); ?>)
|
|
<?php endif; ?>
|
|
</p>
|
|
|
|
<form method="post" class="mb-4 flex flex-wrap items-end gap-3">
|
|
<input type="hidden" name="navigate_ship" value="1">
|
|
<div>
|
|
<label for="ship_symbol" class="block text-sm mb-1">Ship</label>
|
|
<select id="ship_symbol" name="ship_symbol" class="px-3 py-2 rounded text-black min-w-52">
|
|
<?php foreach ( $ships as $ship ) : ?>
|
|
<?php $shipSymbol = (string) ( $ship['symbol'] ?? '' ); ?>
|
|
<option value="<?php echo htmlspecialchars( $shipSymbol ); ?>" <?php echo ( $shipSymbol === $selectedShipSymbol ) ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars( $shipSymbol ); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="waypoint_symbol" class="block text-sm mb-1">Destination Waypoint</label>
|
|
<select id="waypoint_symbol" name="waypoint_symbol" class="px-3 py-2 rounded text-black min-w-64">
|
|
<?php foreach ( $systemWaypoints as $waypoint ) : ?>
|
|
<?php $waypointSymbol = (string) ( $waypoint['symbol'] ?? '' ); ?>
|
|
<?php $waypointType = (string) ( $waypoint['type'] ?? '' ); ?>
|
|
<option value="<?php echo htmlspecialchars( $waypointSymbol ); ?>" <?php echo ( $waypointSymbol === $selectedWaypointSymbol ) ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars( $waypointSymbol . ' (' . $waypointType . ')' ); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="px-4 py-2 bg-blue-600 rounded hover:bg-blue-500">Navigate</button>
|
|
</form>
|
|
|
|
<div class="mb-3 flex gap-2">
|
|
<button type="button" class="system-tab px-3 py-2 bg-gray-700 rounded hover:bg-gray-600" data-tab-target="tab-waypoints">Waypoints</button>
|
|
<button type="button" class="system-tab px-3 py-2 bg-gray-700 rounded hover:bg-gray-600" data-tab-target="tab-markets">Markets</button>
|
|
<button type="button" class="system-tab px-3 py-2 bg-gray-700 rounded hover:bg-gray-600" data-tab-target="tab-shipyards">Shipyards</button>
|
|
</div>
|
|
|
|
<div id="tab-waypoints" class="system-tab-panel">
|
|
<?php
|
|
$waypointCount = count( $systemWaypoints );
|
|
$waypointStart = $waypointCount > 0 ? ( ( $waypointPage - 1 ) * $waypointPageSize ) + 1 : 0;
|
|
$waypointEnd = min( $waypointPage * $waypointPageSize, $waypointCount );
|
|
?>
|
|
<div class="mb-3 flex items-center justify-between">
|
|
<p class="text-sm text-gray-300">
|
|
Showing <?php echo number_format( $waypointStart ); ?>-<?php echo number_format( $waypointEnd ); ?>
|
|
of <?php echo number_format( $waypointCount ); ?> waypoints
|
|
</p>
|
|
<div class="flex gap-2">
|
|
<?php if ($waypointPage > 1 ) : ?>
|
|
<a
|
|
href="index.php?waypoint_page=<?php echo (int) ( $waypointPage - 1 ); ?>"
|
|
class="px-3 py-1 bg-gray-700 rounded hover:bg-gray-600"
|
|
>
|
|
Previous
|
|
</a>
|
|
<?php endif; ?>
|
|
<?php if ($waypointPage < $waypointTotalPages ) : ?>
|
|
<a
|
|
href="index.php?waypoint_page=<?php echo (int) ( $waypointPage + 1 ); ?>"
|
|
class="px-3 py-1 bg-gray-700 rounded hover:bg-gray-600"
|
|
>
|
|
Next
|
|
</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="table-auto border-collapse border border-gray-300 w-full">
|
|
<tr>
|
|
<th class="border border-gray-300 px-3 py-2">Symbol</th>
|
|
<th class="border border-gray-300 px-3 py-2">Type</th>
|
|
<th class="border border-gray-300 px-3 py-2">Coordinates</th>
|
|
<th class="border border-gray-300 px-3 py-2">Traits</th>
|
|
</tr>
|
|
<?php foreach ( $paginatedWaypoints as $waypoint ) : ?>
|
|
<tr>
|
|
<td class="border border-gray-300 px-3 py-2"><?php echo htmlspecialchars( (string) ( $waypoint['symbol'] ?? '' ) ); ?></td>
|
|
<td class="border border-gray-300 px-3 py-2"><?php echo htmlspecialchars( (string) ( $waypoint['type'] ?? '' ) ); ?></td>
|
|
<td class="border border-gray-300 px-3 py-2">
|
|
<?php echo htmlspecialchars( (string) ( $waypoint['x'] ?? 0 ) ); ?>,
|
|
<?php echo htmlspecialchars( (string) ( $waypoint['y'] ?? 0 ) ); ?>
|
|
</td>
|
|
<td class="border border-gray-300 px-3 py-2">
|
|
<?php
|
|
$traitNames = array();
|
|
foreach ( (array) ( $waypoint['traits'] ?? array() ) as $trait ) {
|
|
$traitNames[] = (string) ( $trait['symbol'] ?? '' );
|
|
}
|
|
echo htmlspecialchars( implode( ', ', array_filter( $traitNames ) ) );
|
|
?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tab-markets" class="system-tab-panel hidden">
|
|
<?php if (empty( $marketDetails ) ) : ?>
|
|
<p class="text-gray-300">No markets found in this system.</p>
|
|
<?php endif; ?>
|
|
<?php foreach ( $marketDetails as $market ) : ?>
|
|
<div class="mb-3 border border-gray-500 p-3 rounded">
|
|
<h3 class="font-bold">
|
|
<?php echo htmlspecialchars( (string) ( $market['waypoint']['symbol'] ?? '' ) ); ?>
|
|
</h3>
|
|
<?php if ($market['error'] !== '' ) : ?>
|
|
<p class="text-red-300"><?php echo htmlspecialchars( (string) $market['error'] ); ?></p>
|
|
<?php else : ?>
|
|
<?php $tradeGoods = (array) ( $market['data']['tradeGoods'] ?? array() ); ?>
|
|
<p class="text-sm text-gray-300">Trade Goods: <?php echo htmlspecialchars( (string) count( $tradeGoods ) ); ?></p>
|
|
<?php if (! empty( $tradeGoods ) ) : ?>
|
|
<p class="text-sm text-gray-300">
|
|
<?php
|
|
$symbols = array();
|
|
foreach ( $tradeGoods as $tradeGood ) {
|
|
$symbols[] = (string) ( $tradeGood['symbol'] ?? '' );
|
|
}
|
|
echo htmlspecialchars( implode( ', ', array_filter( $symbols ) ) );
|
|
?>
|
|
</p>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div id="tab-shipyards" class="system-tab-panel hidden">
|
|
<?php if (empty( $shipyardDetails ) ) : ?>
|
|
<p class="text-gray-300">No shipyards found in this system.</p>
|
|
<?php endif; ?>
|
|
<?php foreach ( $shipyardDetails as $shipyard ) : ?>
|
|
<div class="mb-3 border border-gray-500 p-3 rounded">
|
|
<h3 class="font-bold">
|
|
<?php echo htmlspecialchars( (string) ( $shipyard['waypoint']['symbol'] ?? '' ) ); ?>
|
|
</h3>
|
|
<?php if ($shipyard['error'] !== '' ) : ?>
|
|
<p class="text-red-300"><?php echo htmlspecialchars( (string) $shipyard['error'] ); ?></p>
|
|
<?php else : ?>
|
|
<?php $shipTypes = (array) ( $shipyard['data']['shipTypes'] ?? array() ); ?>
|
|
<p class="text-sm text-gray-300">Ships Available: <?php echo htmlspecialchars( (string) count( $shipTypes ) ); ?></p>
|
|
<?php if (! empty( $shipTypes ) ) : ?>
|
|
<p class="text-sm text-gray-300">
|
|
<?php
|
|
$types = array();
|
|
foreach ( $shipTypes as $shipType ) {
|
|
$types[] = (string) ( $shipType['type'] ?? '' );
|
|
}
|
|
echo htmlspecialchars( implode( ', ', array_filter( $types ) ) );
|
|
?>
|
|
</p>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="text-2xl font-bold my-4">Ships</h2>
|
|
<table class="table-auto border-collapse border border-gray-300">
|
|
<tr>
|
|
<th class="border border-gray-300 px-4 py-2">Name</th>
|
|
<th class="border border-gray-300 px-4 py-2">Role</th>
|
|
<th class="border border-gray-300 px-4 py-2">Type</th>
|
|
<th class="border border-gray-300 px-4 py-2">Status</th>
|
|
<th class="border border-gray-300 px-4 py-2">Nav Timer</th>
|
|
<th class="border border-gray-300 px-4 py-2">Flight Mode</th>
|
|
<th class="border border-gray-300 px-4 py-2">Route</th>
|
|
</tr>
|
|
|
|
<?php foreach ( $ships as $ship ) : ?>
|
|
<?php
|
|
$shipStatus = (string) ( $ship['nav']['status'] ?? '' );
|
|
$arrivalIso = (string) ( $ship['nav']['route']['arrival'] ?? '' );
|
|
?>
|
|
<tr>
|
|
<td class="border border-gray-300 px-4 py-2">
|
|
<a
|
|
href="ship-details.php?ship=<?php echo urlencode( (string) ( $ship['symbol'] ?? '' ) ); ?>"
|
|
class="text-blue-400 hover:underline"
|
|
>
|
|
<?php echo htmlspecialchars( ucfirst( strtolower( $ship['registration']['name'] ) ) ); ?>
|
|
</a>
|
|
</td>
|
|
<td class="border border-gray-300 px-4 py-2"><?php echo htmlspecialchars( ucfirst( strtolower( $ship['registration']['role'] ) ) ); ?></td>
|
|
<td class="border border-gray-300 px-4 py-2"><?php echo htmlspecialchars( $ship['frame']['name'] ); ?></td>
|
|
<td class="border border-gray-300 px-4 py-2"><?php echo htmlspecialchars( formatString( $shipStatus ) ); ?></td>
|
|
<td class="border border-gray-300 px-4 py-2">
|
|
<span
|
|
class="ship-nav-timer"
|
|
data-status="<?php echo htmlspecialchars( $shipStatus ); ?>"
|
|
data-arrival="<?php echo htmlspecialchars( $arrivalIso ); ?>"
|
|
></span>
|
|
</td>
|
|
<?php if ($shipStatus !== 'DOCKED' ) : ?>
|
|
<td class="border border-gray-300 px-4 py-2"><?php echo htmlspecialchars( $ship['nav']['flightMode'] ); ?></td>
|
|
<td class="border border-gray-300 px-4 py-2"><?php echo htmlspecialchars( $ship['nav']['route']['origin']['symbol'] . ' - ' . $ship['nav']['route']['destination']['symbol'] ); ?></td>
|
|
<?php else : ?>
|
|
<td class="border border-gray-300 px-4 py-2">N/A</td>
|
|
<td class="border border-gray-300 px-4 py-2">N/A</td>
|
|
<?php endif; ?>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</table>
|
|
|
|
<h2 class="text-2xl font-bold my-4">Contracts</h2>
|
|
<div class="mb-6 border border-gray-600 rounded p-4">
|
|
<h3 class="text-xl font-bold mb-3">Active Contracts</h3>
|
|
<?php if (empty( $activeContracts ) ) : ?>
|
|
<p class="text-gray-300 mb-3">No active contracts.</p>
|
|
<?php if ($contractNegotiationShipSymbol !== '' ) : ?>
|
|
<form method="post">
|
|
<input type="hidden" name="contract_action" value="negotiate_contract">
|
|
<input type="hidden" name="negotiate_ship_symbol" value="<?php echo htmlspecialchars( $contractNegotiationShipSymbol ); ?>">
|
|
<button type="submit" class="px-3 py-2 bg-emerald-700 rounded hover:bg-emerald-600">
|
|
Negotiate New Contract
|
|
</button>
|
|
</form>
|
|
<p class="text-sm text-gray-400 mt-2">
|
|
Uses ship: <?php echo htmlspecialchars( $contractNegotiationShipSymbol ); ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
<?php else : ?>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<?php foreach ( $activeContracts as $contract ) : ?>
|
|
<?php
|
|
$contractType = (string) ( $contract['type'] ?? '' );
|
|
$deliveries = (array) ( $contract['terms']['deliver'] ?? array() );
|
|
?>
|
|
<div class="border border-gray-500 rounded p-3">
|
|
<p><span class="font-bold">Contract:</span> <?php echo htmlspecialchars( (string) ( $contract['id'] ?? '' ) ); ?></p>
|
|
<p><span class="font-bold">Type:</span> <?php echo htmlspecialchars( ucfirst( strtolower( str_replace( '_', ' ', $contractType ) ) ) ); ?></p>
|
|
<p>
|
|
<span class="font-bold">Deadline:</span>
|
|
<?php echo htmlspecialchars( (string) date( 'Y-m-d H:i:s', strtotime( (string) ( $contract['terms']['deadline'] ?? '' ) ) ) ); ?>
|
|
</p>
|
|
<p>
|
|
<span class="font-bold">Payment:</span>
|
|
<?php echo number_format( (int) ( $contract['terms']['payment']['onAccepted'] ?? 0 ) ); ?>
|
|
+
|
|
<?php echo number_format( (int) ( $contract['terms']['payment']['onFulfilled'] ?? 0 ) ); ?>
|
|
</p>
|
|
<form method="post" class="mt-3">
|
|
<input type="hidden" name="contract_action" value="deliver_contract">
|
|
<input type="hidden" name="contract_id" value="<?php echo htmlspecialchars( (string) ( $contract['id'] ?? '' ) ); ?>">
|
|
<button type="submit" class="px-3 py-1 bg-violet-700 rounded hover:bg-violet-600">
|
|
Deliver Contract Goods
|
|
</button>
|
|
</form>
|
|
<form method="post" class="mt-2">
|
|
<input type="hidden" name="contract_action" value="fulfill_contract">
|
|
<input type="hidden" name="contract_id" value="<?php echo htmlspecialchars( (string) ( $contract['id'] ?? '' ) ); ?>">
|
|
<button type="submit" class="px-3 py-1 bg-emerald-700 rounded hover:bg-emerald-600">
|
|
Fulfill Contract
|
|
</button>
|
|
</form>
|
|
<?php if (! empty( $deliveries ) ) : ?>
|
|
<p class="mt-2 font-bold">Deliveries:</p>
|
|
<ul class="list-disc list-inside text-sm text-gray-300">
|
|
<?php foreach ( $deliveries as $delivery ) : ?>
|
|
<?php
|
|
$tradeSymbol = (string) ( $delivery['tradeSymbol'] ?? '' );
|
|
$unitsRequired = (int) ( $delivery['unitsRequired'] ?? 0 );
|
|
$unitsFulfilled = (int) ( $delivery['unitsFulfilled'] ?? 0 );
|
|
$remainingUnits = max( 0, $unitsRequired - $unitsFulfilled );
|
|
$readyUnits = (int) ( $deliveryReadyByTradeSymbol[ $tradeSymbol ] ?? 0 );
|
|
$deliverableNow = min( $readyUnits, $remainingUnits );
|
|
?>
|
|
<li>
|
|
<?php echo htmlspecialchars( $tradeSymbol ); ?>:
|
|
<?php echo number_format( $unitsFulfilled ); ?>/<?php echo number_format( $unitsRequired ); ?>
|
|
to <?php echo htmlspecialchars( (string) ( $delivery['destinationSymbol'] ?? '' ) ); ?>
|
|
| Ready: <?php echo number_format( $deliverableNow ); ?>
|
|
<?php if ($readyUnits > $deliverableNow ) : ?>
|
|
(<?php echo number_format( $readyUnits ); ?> in cargo)
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if (! empty( $pendingContracts ) ) : ?>
|
|
<div class="mb-6 border border-gray-600 rounded p-4">
|
|
<h3 class="text-xl font-bold mb-3">Pending Contracts</h3>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<?php foreach ( $pendingContracts as $contract ) : ?>
|
|
<div class="border border-gray-500 rounded p-3">
|
|
<?php $deliveries = (array) ( $contract['terms']['deliver'] ?? array() ); ?>
|
|
<p><span class="font-bold">Contract:</span> <?php echo htmlspecialchars( (string) ( $contract['id'] ?? '' ) ); ?></p>
|
|
<p><span class="font-bold">Type:</span> <?php echo htmlspecialchars( ucfirst( strtolower( str_replace( '_', ' ', (string) ( $contract['type'] ?? '' ) ) ) ) ); ?></p>
|
|
<p><span class="font-bold">Deadline To Accept:</span> <?php echo htmlspecialchars( date( 'Y-m-d H:i:s', strtotime( (string) ( $contract['deadlineToAccept'] ?? '' ) ) ) ); ?></p>
|
|
<?php if (! empty( $deliveries ) ) : ?>
|
|
<p class="mt-2 font-bold">Requirements:</p>
|
|
<ul class="list-disc list-inside text-sm text-gray-300">
|
|
<?php foreach ( $deliveries as $delivery ) : ?>
|
|
<?php
|
|
$tradeSymbol = (string) ( $delivery['tradeSymbol'] ?? '' );
|
|
$unitsRequired = (int) ( $delivery['unitsRequired'] ?? 0 );
|
|
$unitsFulfilled = (int) ( $delivery['unitsFulfilled'] ?? 0 );
|
|
$destinationSymbol = (string) ( $delivery['destinationSymbol'] ?? '' );
|
|
?>
|
|
<li>
|
|
<?php echo htmlspecialchars( $tradeSymbol ); ?>:
|
|
<?php echo number_format( $unitsFulfilled ); ?>/<?php echo number_format( $unitsRequired ); ?>
|
|
to <?php echo htmlspecialchars( $destinationSymbol ); ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
<a
|
|
href="index.php?accept_contract=<?php echo urlencode( (string) ( $contract['id'] ?? '' ) ); ?>"
|
|
class="mt-2 inline-block px-3 py-1 bg-blue-700 rounded hover:bg-blue-600"
|
|
>
|
|
Accept Contract
|
|
</a>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<script>
|
|
(function() {
|
|
const tabs = document.querySelectorAll('.system-tab');
|
|
const panels = document.querySelectorAll('.system-tab-panel');
|
|
const navTimerNodes = document.querySelectorAll('.ship-nav-timer');
|
|
|
|
function formatNavTimer(seconds) {
|
|
if (seconds <= 0) {
|
|
return 'Arriving';
|
|
}
|
|
|
|
const total = Math.max(0, Math.floor(seconds));
|
|
const hours = Math.floor(total / 3600);
|
|
const minutes = Math.floor((total % 3600) / 60);
|
|
const secs = total % 60;
|
|
|
|
if (hours > 0) {
|
|
return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
|
}
|
|
|
|
return `${minutes}:${String(secs).padStart(2, '0')}`;
|
|
}
|
|
|
|
function renderNavTimers() {
|
|
const nowMs = Date.now();
|
|
|
|
navTimerNodes.forEach((node) => {
|
|
const status = node.dataset.status || '';
|
|
const arrival = node.dataset.arrival || '';
|
|
|
|
if (status !== 'IN_TRANSIT') {
|
|
node.textContent = 'Ready';
|
|
return;
|
|
}
|
|
|
|
const arrivalMs = Date.parse(arrival);
|
|
if (!Number.isFinite(arrivalMs)) {
|
|
node.textContent = 'In transit';
|
|
return;
|
|
}
|
|
|
|
const remainingSeconds = Math.max(0, Math.floor((arrivalMs - nowMs) / 1000));
|
|
node.textContent = formatNavTimer(remainingSeconds);
|
|
});
|
|
}
|
|
|
|
function activateTab(targetId) {
|
|
panels.forEach((panel) => {
|
|
panel.classList.toggle('hidden', panel.id !== targetId);
|
|
});
|
|
|
|
tabs.forEach((tab) => {
|
|
const isActive = tab.getAttribute('data-tab-target') === targetId;
|
|
tab.classList.toggle('bg-blue-700', isActive);
|
|
tab.classList.toggle('bg-gray-700', !isActive);
|
|
});
|
|
}
|
|
|
|
tabs.forEach((tab) => {
|
|
tab.addEventListener('click', () => {
|
|
const targetId = tab.getAttribute('data-tab-target');
|
|
if (targetId) {
|
|
activateTab(targetId);
|
|
}
|
|
});
|
|
});
|
|
|
|
if (tabs.length > 0) {
|
|
activateTab('tab-waypoints');
|
|
}
|
|
|
|
renderNavTimers();
|
|
if (navTimerNodes.length > 0) {
|
|
window.setInterval(renderNavTimers, 1000);
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|