✨feature: Add cooldown/navigation timers, move credits display to header
This commit is contained in:
65
index.php
65
index.php
@@ -264,8 +264,7 @@ try {
|
||||
<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'] ) ) ); ?><br>
|
||||
Credits: <span class="font-normal"><?php echo number_format( $agent['credits'] ); ?></span>
|
||||
Agent: <?php echo htmlspecialchars( ucfirst( strtolower( $agent['symbol'] ) ) ); ?>
|
||||
</h2>
|
||||
|
||||
<h3 class="text-xl font-bold mb-4">
|
||||
@@ -448,11 +447,16 @@ try {
|
||||
<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
|
||||
@@ -464,8 +468,15 @@ try {
|
||||
</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( ucfirst( strtolower( $ship['nav']['status'] ) ) ); ?></td>
|
||||
<?php if ($ship['nav']['status'] !== 'DOCKED' ) : ?>
|
||||
<td class="border border-gray-300 px-4 py-2"><?php echo htmlspecialchars( ucfirst( strtolower( $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 : ?>
|
||||
@@ -536,6 +547,47 @@ try {
|
||||
(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) => {
|
||||
@@ -561,6 +613,11 @@ try {
|
||||
if (tabs.length > 0) {
|
||||
activateTab('tab-waypoints');
|
||||
}
|
||||
|
||||
renderNavTimers();
|
||||
if (navTimerNodes.length > 0) {
|
||||
window.setInterval(renderNavTimers, 1000);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -291,6 +291,33 @@ class SpacetradersApi {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver cargo for a contract using a ship at the delivery destination.
|
||||
*
|
||||
* @param string $contractId The contract ID.
|
||||
* @param string $shipSymbol The ship symbol delivering cargo.
|
||||
* @param string $tradeSymbol The trade symbol being delivered.
|
||||
* @param int $units The number of units to deliver.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function deliverContractCargo(
|
||||
string $contractId,
|
||||
string $shipSymbol,
|
||||
string $tradeSymbol,
|
||||
int $units
|
||||
): array {
|
||||
return $this->request(
|
||||
'POST',
|
||||
'/my/contracts/' . rawurlencode( $contractId ) . '/deliver',
|
||||
array(
|
||||
'shipSymbol' => $shipSymbol,
|
||||
'tradeSymbol' => $tradeSymbol,
|
||||
'units' => $units,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all systems in the universe.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,69 @@
|
||||
<div class="mb-6">
|
||||
<?php
|
||||
/**
|
||||
* Spacetraders navigation.
|
||||
*
|
||||
* @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();
|
||||
|
||||
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();
|
||||
$agent = $agentResponse['data'] ?? $agentResponse;
|
||||
}
|
||||
} 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 );
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<a href="index.php" class="text-blue-400 hover:underline">Dashboard</a>
|
||||
<span class="mx-2">|</span>
|
||||
<a href="market.php" class="text-blue-400 hover:underline">Markets</a>
|
||||
@@ -9,3 +74,8 @@
|
||||
<span class="mx-2">|</span>
|
||||
<a href="config.php" class="text-blue-400 hover:underline">Configuration</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="font-bold">Credits: <span class="font-normal"><?php echo number_format( $agent['credits'] ); ?></span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
346
mining-fleet.php
346
mining-fleet.php
@@ -31,6 +31,70 @@ function filterMiningShips( array $ships ): array {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build contract delivery targets for outstanding delivery requirements.
|
||||
*
|
||||
* @param array<int,array<string,mixed>> $activeContracts
|
||||
*
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
function buildContractDeliveryTargets( array $activeContracts ): array {
|
||||
$targetsByKey = array();
|
||||
|
||||
foreach ( $activeContracts as $contract ) {
|
||||
$contractId = (string) ( $contract['id'] ?? '' );
|
||||
$deliveries = (array) ( $contract['terms']['deliver'] ?? array() );
|
||||
|
||||
if ($contractId === '' || empty( $deliveries ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $deliveries as $delivery ) {
|
||||
$tradeSymbol = (string) ( $delivery['tradeSymbol'] ?? '' );
|
||||
$destinationSymbol = (string) ( $delivery['destinationSymbol'] ?? '' );
|
||||
$unitsRequired = (int) ( $delivery['unitsRequired'] ?? 0 );
|
||||
$unitsFulfilled = (int) ( $delivery['unitsFulfilled'] ?? 0 );
|
||||
$remainingUnits = max( 0, $unitsRequired - $unitsFulfilled );
|
||||
|
||||
if ($tradeSymbol === '' || $destinationSymbol === '' || $remainingUnits <= 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $contractId . '|' . $destinationSymbol . '|' . $tradeSymbol;
|
||||
|
||||
if (! isset( $targetsByKey[ $key ] ) ) {
|
||||
$targetsByKey[ $key ] = array(
|
||||
'key' => $key,
|
||||
'contractId' => $contractId,
|
||||
'destinationSymbol' => $destinationSymbol,
|
||||
'tradeSymbol' => $tradeSymbol,
|
||||
'remainingUnits' => 0,
|
||||
);
|
||||
}
|
||||
|
||||
$targetsByKey[ $key ]['remainingUnits'] += $remainingUnits;
|
||||
}
|
||||
}
|
||||
|
||||
$targets = array_values( $targetsByKey );
|
||||
|
||||
usort(
|
||||
$targets,
|
||||
static function ( array $left, array $right ): int {
|
||||
$leftSort = (string) ( $left['destinationSymbol'] ?? '' ) . '|' .
|
||||
(string) ( $left['tradeSymbol'] ?? '' ) . '|' .
|
||||
(string) ( $left['contractId'] ?? '' );
|
||||
$rightSort = (string) ( $right['destinationSymbol'] ?? '' ) . '|' .
|
||||
(string) ( $right['tradeSymbol'] ?? '' ) . '|' .
|
||||
(string) ( $right['contractId'] ?? '' );
|
||||
|
||||
return strcmp( $leftSort, $rightSort );
|
||||
}
|
||||
);
|
||||
|
||||
return $targets;
|
||||
}
|
||||
|
||||
$config = require __DIR__ . '/lib/project-config.php';
|
||||
|
||||
$storage = new SpacetradersStorage( $config['db_path'] );
|
||||
@@ -44,7 +108,9 @@ $ships = array();
|
||||
$miningShips = array();
|
||||
$activeContracts = array();
|
||||
$marketWaypoints = array();
|
||||
$contractDeliveryTargets = array();
|
||||
$selectedMarketWaypoint = '';
|
||||
$selectedContractDeliveryKey = '';
|
||||
|
||||
if (! is_string( $token ) || trim( $token ) === '' ) {
|
||||
$envToken = getenv( 'SPACETRADERS_TOKEN' );
|
||||
@@ -135,10 +201,24 @@ try {
|
||||
$selectedMarketWaypoint = isset( $_POST['market_waypoint'] ) ?
|
||||
trim( (string) $_POST['market_waypoint'] ) :
|
||||
( $marketWaypoints[0] ?? '' );
|
||||
$contractDeliveryTargets = buildContractDeliveryTargets( $activeContracts );
|
||||
$selectedContractDeliveryKey = isset( $_POST['contract_delivery_key'] ) ?
|
||||
trim( (string) $_POST['contract_delivery_key'] ) :
|
||||
(string) ( $contractDeliveryTargets[0]['key'] ?? '' );
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['fleet_action'] ) ) {
|
||||
$fleetAction = (string) $_POST['fleet_action'];
|
||||
$successCount = 0;
|
||||
$selectedContractDeliveryTarget = null;
|
||||
$remainingContractUnits = 0;
|
||||
|
||||
foreach ( $contractDeliveryTargets as $target ) {
|
||||
if ((string) ( $target['key'] ?? '' ) === $selectedContractDeliveryKey ) {
|
||||
$selectedContractDeliveryTarget = $target;
|
||||
$remainingContractUnits = (int) ( $target['remainingUnits'] ?? 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $miningShips as $fleetShip ) {
|
||||
$shipSymbol = (string) ( $fleetShip['symbol'] ?? '' );
|
||||
@@ -277,6 +357,93 @@ try {
|
||||
|
||||
$actionResults[] = $shipSymbol . ': jettisoned ' . $jettisonedItems . ' cargo item type(s).';
|
||||
$successCount++;
|
||||
} elseif ($fleetAction === 'navigate_contract_delivery' ) {
|
||||
if (! is_array( $selectedContractDeliveryTarget ) ) {
|
||||
$actionResults[] = $shipSymbol . ': no contract delivery target selected.';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($shipStatus === 'IN_TRANSIT' ) {
|
||||
$actionResults[] = $shipSymbol . ': already in transit.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$destinationSymbol = (string) ( $selectedContractDeliveryTarget['destinationSymbol'] ?? '' );
|
||||
if ($destinationSymbol === '' ) {
|
||||
$actionResults[] = $shipSymbol . ': delivery destination is missing.';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($waypointSymbol !== $destinationSymbol ) {
|
||||
if ($shipStatus === 'DOCKED' ) {
|
||||
$client->orbitShip( $shipSymbol );
|
||||
}
|
||||
|
||||
$client->navigateShip( $shipSymbol, $destinationSymbol );
|
||||
$actionResults[] = $shipSymbol . ': navigating to contract delivery waypoint ' . $destinationSymbol . '.';
|
||||
$successCount++;
|
||||
} else {
|
||||
$actionResults[] = $shipSymbol . ': already at contract delivery waypoint.';
|
||||
}
|
||||
} elseif ($fleetAction === 'deliver_contract_goods' ) {
|
||||
if (! is_array( $selectedContractDeliveryTarget ) ) {
|
||||
$actionResults[] = $shipSymbol . ': no contract delivery target selected.';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($shipStatus === 'IN_TRANSIT' ) {
|
||||
$actionResults[] = $shipSymbol . ': skipped (in transit).';
|
||||
continue;
|
||||
}
|
||||
|
||||
$contractId = (string) ( $selectedContractDeliveryTarget['contractId'] ?? '' );
|
||||
$destinationSymbol = (string) ( $selectedContractDeliveryTarget['destinationSymbol'] ?? '' );
|
||||
$tradeSymbol = (string) ( $selectedContractDeliveryTarget['tradeSymbol'] ?? '' );
|
||||
|
||||
if ($contractId === '' || $destinationSymbol === '' || $tradeSymbol === '' ) {
|
||||
$actionResults[] = $shipSymbol . ': selected contract delivery target is incomplete.';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($remainingContractUnits <= 0 ) {
|
||||
$actionResults[] = $shipSymbol . ': nothing remaining to deliver for this target.';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($waypointSymbol !== $destinationSymbol ) {
|
||||
$actionResults[] = $shipSymbol . ': not at delivery waypoint (' . $destinationSymbol . ').';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($shipStatus !== 'DOCKED' ) {
|
||||
$client->dockShip( $shipSymbol );
|
||||
}
|
||||
|
||||
$inventory = (array) ( $shipData['cargo']['inventory'] ?? array() );
|
||||
$availableUnits = 0;
|
||||
foreach ( $inventory as $item ) {
|
||||
if ((string) ( $item['symbol'] ?? '' ) === $tradeSymbol ) {
|
||||
$availableUnits = (int) ( $item['units'] ?? 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($availableUnits <= 0 ) {
|
||||
$actionResults[] = $shipSymbol . ': no ' . $tradeSymbol . ' in cargo.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$deliverUnits = min( $availableUnits, $remainingContractUnits );
|
||||
|
||||
if ($deliverUnits <= 0 ) {
|
||||
$actionResults[] = $shipSymbol . ': no units available to deliver.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$client->deliverContractCargo( $contractId, $shipSymbol, $tradeSymbol, $deliverUnits );
|
||||
$remainingContractUnits -= $deliverUnits;
|
||||
$actionResults[] = $shipSymbol . ': delivered ' . $deliverUnits . ' of ' . $tradeSymbol . ' for contract ' . $contractId . '.';
|
||||
$successCount++;
|
||||
}
|
||||
} catch (SpacetradersApiException $e) {
|
||||
$actionResults[] = $shipSymbol . ': ' . $e->getMessage();
|
||||
@@ -302,6 +469,10 @@ try {
|
||||
}
|
||||
)
|
||||
);
|
||||
$contractDeliveryTargets = buildContractDeliveryTargets( $activeContracts );
|
||||
$selectedContractDeliveryKey = isset( $_POST['contract_delivery_key'] ) ?
|
||||
trim( (string) $_POST['contract_delivery_key'] ) :
|
||||
(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'] )) {
|
||||
@@ -362,6 +533,34 @@ try {
|
||||
} catch (SpacetradersApiException $e) {
|
||||
$errorMessage = $e->getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a string by replacing underscores with spaces and capitalizing the first letter.
|
||||
*
|
||||
* @param string $value The string to format.
|
||||
* @return string The formatted string.
|
||||
*/
|
||||
function formatString( $value ): string {
|
||||
return ucfirst( strtolower( str_replace( '_', ' ', $value ) ) );
|
||||
}
|
||||
|
||||
$deliveryReadyByTradeSymbol = array();
|
||||
foreach ( $miningShips as $miningShip ) {
|
||||
$inventory = (array) ( $miningShip['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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
@@ -413,6 +612,32 @@ try {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="contract_delivery_key" class="block text-sm mb-1">Contract Delivery Target</label>
|
||||
<select id="contract_delivery_key" name="contract_delivery_key" class="px-3 py-2 rounded text-black min-w-80">
|
||||
<?php if (empty( $contractDeliveryTargets ) ) : ?>
|
||||
<option value="">No outstanding delivery targets</option>
|
||||
<?php else : ?>
|
||||
<?php foreach ( $contractDeliveryTargets as $target ) : ?>
|
||||
<?php
|
||||
$targetKey = (string) ( $target['key'] ?? '' );
|
||||
$targetLabel = 'Contract ' .
|
||||
(string) ( $target['contractId'] ?? '' ) .
|
||||
' | ' .
|
||||
(string) ( formatString( $target['tradeSymbol'] ?? '' ) ) .
|
||||
' | Remaining ' .
|
||||
number_format( (int) ( $target['remainingUnits'] ?? 0 ) ) .
|
||||
' | To ' .
|
||||
(string) ( $target['destinationSymbol'] ?? '' );
|
||||
?>
|
||||
<option value="<?php echo htmlspecialchars( $targetKey ); ?>" <?php echo $targetKey === $selectedContractDeliveryKey ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars( $targetLabel ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="fleet_action" value="excavate_all" class="px-4 py-2 bg-emerald-700 rounded hover:bg-emerald-600">
|
||||
Command All: Excavate
|
||||
</button>
|
||||
@@ -428,6 +653,12 @@ try {
|
||||
<button type="submit" name="fleet_action" value="jettison_cargo_all" class="px-4 py-2 bg-red-800 rounded hover:bg-red-700">
|
||||
Command All: Jettison Cargo
|
||||
</button>
|
||||
<button type="submit" name="fleet_action" value="navigate_contract_delivery" class="px-4 py-2 bg-indigo-700 rounded hover:bg-indigo-600">
|
||||
Command All: Navigate to Contract Delivery
|
||||
</button>
|
||||
<button type="submit" name="fleet_action" value="deliver_contract_goods" class="px-4 py-2 bg-violet-700 rounded hover:bg-violet-600">
|
||||
Command All: Deliver Contract Goods
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -458,8 +689,8 @@ try {
|
||||
$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( $contractType ); ?></p>
|
||||
<p><span class="font-bold">Contract:</span> <?php echo htmlspecialchars( (string) ( formatString( $contract['id'] ?? '' ) ) ); ?></p>
|
||||
<p><span class="font-bold">Type:</span> <?php echo htmlspecialchars( formatString( $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'] ?? '' ) ) ) ); ?>
|
||||
@@ -475,13 +706,21 @@ try {
|
||||
<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( (string) ( $delivery['tradeSymbol'] ?? '' ) ); ?>:
|
||||
<?php echo htmlspecialchars( formatString( $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>
|
||||
@@ -504,18 +743,32 @@ try {
|
||||
$fuelCapacity = (int) ( $ship['fuel']['capacity'] ?? 0 );
|
||||
$cargoUnits = (int) ( $ship['cargo']['units'] ?? 0 );
|
||||
$cargoCapacity = (int) ( $ship['cargo']['capacity'] ?? 0 );
|
||||
$cooldownRemaining = (int) ( $ship['cooldown']['remainingSeconds'] ?? 0 );
|
||||
$shipStatus = (string) ( $ship['nav']['status'] ?? '' );
|
||||
$arrivalIso = (string) ( $ship['nav']['route']['arrival'] ?? '' );
|
||||
?>
|
||||
<div class="border border-gray-600 rounded p-4">
|
||||
<h3 class="text-lg font-bold mb-2">
|
||||
<a href="ship-details.php?ship=<?php echo urlencode( $shipSymbol ); ?>" class="text-blue-400 hover:underline">
|
||||
<?php echo htmlspecialchars( $shipSymbol ); ?>
|
||||
<?php echo htmlspecialchars( formatString( $shipSymbol ) ); ?>
|
||||
</a>
|
||||
</h3>
|
||||
<p><span class="font-bold">Name:</span> <?php echo htmlspecialchars( (string) ( $ship['registration']['name'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Status:</span> <?php echo htmlspecialchars( (string) ( $ship['nav']['status'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Status:</span> <?php echo htmlspecialchars( formatString( $shipStatus ) ); ?></p>
|
||||
<p><span class="font-bold">Waypoint:</span> <?php echo htmlspecialchars( (string) ( $ship['nav']['waypointSymbol'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Fuel:</span> <?php echo number_format( $fuelCurrent ); ?>/<?php echo number_format( $fuelCapacity ); ?></p>
|
||||
<p><span class="font-bold">Cargo:</span> <?php echo number_format( $cargoUnits ); ?>/<?php echo number_format( $cargoCapacity ); ?></p>
|
||||
<p>
|
||||
<span class="font-bold">Navigation Timer:</span>
|
||||
<span
|
||||
class="ship-nav-timer"
|
||||
data-status="<?php echo htmlspecialchars( $shipStatus ); ?>"
|
||||
data-arrival="<?php echo htmlspecialchars( $arrivalIso ); ?>"
|
||||
></span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="font-bold">Cooldown:</span>
|
||||
<span class="ship-cooldown" data-seconds="<?php echo htmlspecialchars( (string) max( 0, $cooldownRemaining ) ); ?>"></span>
|
||||
</p>
|
||||
|
||||
<?php $inventory = (array) ( $ship['cargo']['inventory'] ?? array() ); ?>
|
||||
<?php if (! empty( $inventory ) ) : ?>
|
||||
@@ -523,7 +776,7 @@ try {
|
||||
<ul class="list-disc list-inside text-sm text-gray-300">
|
||||
<?php foreach ( $inventory as $item ) : ?>
|
||||
<li>
|
||||
<?php echo htmlspecialchars( (string) ( $item['symbol'] ?? '' ) ); ?>:
|
||||
<?php echo htmlspecialchars( (string) ( formatString( $item['symbol'] ?? '' ) ) ); ?>:
|
||||
<?php echo number_format( (int) ( $item['units'] ?? 0 ) ); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
@@ -543,5 +796,84 @@ try {
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const cooldownNodes = document.querySelectorAll('.ship-cooldown');
|
||||
const navTimerNodes = document.querySelectorAll('.ship-nav-timer');
|
||||
|
||||
function formatCooldown(seconds) {
|
||||
if (seconds <= 0) {
|
||||
return 'Ready';
|
||||
}
|
||||
|
||||
const total = Math.max(0, Math.floor(seconds));
|
||||
const mins = Math.floor(total / 60);
|
||||
const secs = total % 60;
|
||||
const paddedSecs = secs.toString().padStart(2, '0');
|
||||
return mins > 0 ? `${mins}:${paddedSecs}` : `${secs}s`;
|
||||
}
|
||||
|
||||
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 render() {
|
||||
cooldownNodes.forEach((node) => {
|
||||
const seconds = Number(node.dataset.seconds || '0');
|
||||
node.textContent = formatCooldown(seconds);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
if (cooldownNodes.length === 0 && navTimerNodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.setInterval(() => {
|
||||
cooldownNodes.forEach((node) => {
|
||||
const current = Number(node.dataset.seconds || '0');
|
||||
const next = Math.max(0, current - 1);
|
||||
node.dataset.seconds = String(next);
|
||||
});
|
||||
|
||||
render();
|
||||
}, 1000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -136,7 +136,7 @@ try {
|
||||
}
|
||||
|
||||
$client->sellCargo( $shipSymbol, $tradeSymbol, $units );
|
||||
$statusMessage = 'Sold ' . number_format( $units ) . ' units of ' . $tradeSymbol . '.';
|
||||
$statusMessage = 'Sold ' . number_format( $units ) . ' units of ' . formatString( $tradeSymbol ) . '.';
|
||||
break;
|
||||
case 'jettison_all_cargo':
|
||||
$shipResponse = $client->getShip( $shipSymbol );
|
||||
@@ -180,7 +180,7 @@ try {
|
||||
}
|
||||
|
||||
$client->jettisonCargo( $shipSymbol, $tradeSymbol, $units );
|
||||
$statusMessage = 'Jettisoned ' . number_format( $units ) . ' units of ' . $tradeSymbol . '.';
|
||||
$statusMessage = 'Jettisoned ' . number_format( $units ) . ' units of ' . formatString( $tradeSymbol ) . '.';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -196,6 +196,16 @@ try {
|
||||
} catch (SpacetradersApiException $e) {
|
||||
$errorMessage = $e->getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a string by replacing underscores with spaces and capitalizing the first letter.
|
||||
*
|
||||
* @param string $str The string to format.
|
||||
* @return string The formatted string.
|
||||
*/
|
||||
function formatString( $str ) {
|
||||
return ucfirst( strtolower( str_replace( '_', ' ', $str ) ) );
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
@@ -240,7 +250,7 @@ try {
|
||||
<?php foreach ( $shipList as $listShip ) : ?>
|
||||
<?php $listSymbol = (string) ( $listShip['symbol'] ?? '' ); ?>
|
||||
<option value="<?php echo htmlspecialchars( $listSymbol ); ?>" <?php echo ( $listSymbol === $shipSymbol ) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars( $listSymbol ); ?>
|
||||
<?php echo htmlspecialchars( formatString( $listSymbol ) ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
@@ -289,8 +299,8 @@ try {
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<p class="mt-2 text-sm text-gray-300">
|
||||
Role: <?php echo htmlspecialchars( $shipRole ); ?> |
|
||||
Status: <?php echo htmlspecialchars( $shipStatus ); ?> |
|
||||
Role: <?php echo htmlspecialchars( formatString( $shipRole ) ); ?> |
|
||||
Status: <?php echo htmlspecialchars( formatString( $shipStatus ) ); ?> |
|
||||
Fuel: <?php echo number_format( $fuelCurrent ); ?>/<?php echo number_format( $fuelCapacity ); ?>
|
||||
</p>
|
||||
</div>
|
||||
@@ -298,18 +308,26 @@ try {
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="border border-gray-600 rounded p-4">
|
||||
<h2 class="text-xl font-bold mb-2">Overview</h2>
|
||||
<p><span class="font-bold">Symbol:</span> <?php echo htmlspecialchars( (string) ( $ship['symbol'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Name:</span> <?php echo htmlspecialchars( (string) ( $ship['registration']['name'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Role:</span> <?php echo htmlspecialchars( (string) ( $ship['registration']['role'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Faction:</span> <?php echo htmlspecialchars( (string) ( $ship['registration']['factionSymbol'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Symbol:</span> <?php echo htmlspecialchars( (string) ( formatString( $ship['symbol'] ?? '' ) ) ); ?></p>
|
||||
<p><span class="font-bold">Name:</span> <?php echo htmlspecialchars( (string) ( formatString( $ship['registration']['name'] ?? '' ) ) ); ?></p>
|
||||
<p><span class="font-bold">Role:</span> <?php echo htmlspecialchars( (string) ( formatString( $ship['registration']['role'] ?? '' ) ) ); ?></p>
|
||||
<p><span class="font-bold">Faction:</span> <?php echo htmlspecialchars( (string) ( formatString( $ship['registration']['factionSymbol'] ?? '' ) ) ); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-600 rounded p-4">
|
||||
<h2 class="text-xl font-bold mb-2">Navigation</h2>
|
||||
<p><span class="font-bold">Status:</span> <?php echo htmlspecialchars( (string) ( $ship['nav']['status'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Flight Mode:</span> <?php echo htmlspecialchars( (string) ( $ship['nav']['flightMode'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Status:</span> <?php echo htmlspecialchars( (string) ( formatString( $ship['nav']['status'] ?? '' ) ) ); ?></p>
|
||||
<p><span class="font-bold">Flight Mode:</span> <?php echo htmlspecialchars( (string) ( formatString( $ship['nav']['flightMode'] ?? '' ) ) ); ?></p>
|
||||
<p><span class="font-bold">System:</span> <?php echo htmlspecialchars( (string) ( $ship['nav']['systemSymbol'] ?? '' ) ); ?></p>
|
||||
<p><span class="font-bold">Waypoint:</span> <?php echo htmlspecialchars( (string) ( $ship['nav']['waypointSymbol'] ?? '' ) ); ?></p>
|
||||
<p>
|
||||
<span class="font-bold">Navigation Timer:</span>
|
||||
<span
|
||||
class="ship-nav-timer"
|
||||
data-status="<?php echo htmlspecialchars( (string) ( $ship['nav']['status'] ?? '' ) ); ?>"
|
||||
data-arrival="<?php echo htmlspecialchars( (string) ( $ship['nav']['route']['arrival'] ?? '' ) ); ?>"
|
||||
></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="border border-gray-600 rounded p-4">
|
||||
@@ -363,7 +381,6 @@ try {
|
||||
<?php else : ?>
|
||||
<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">Name</th>
|
||||
<th class="border border-gray-300 px-3 py-2">Units</th>
|
||||
<th class="border border-gray-300 px-3 py-2">Description</th>
|
||||
@@ -373,7 +390,6 @@ try {
|
||||
<?php foreach ( $inventory as $item ) : ?>
|
||||
<?php $itemUnits = (int) ( $item['units'] ?? 0 ); ?>
|
||||
<tr>
|
||||
<td class="border border-gray-300 px-3 py-2"><?php echo htmlspecialchars( (string) ( $item['symbol'] ?? '' ) ); ?></td>
|
||||
<td class="border border-gray-300 px-3 py-2"><?php echo htmlspecialchars( (string) ( $item['name'] ?? '' ) ); ?></td>
|
||||
<td class="border border-gray-300 px-3 py-2"><?php echo number_format( $itemUnits ); ?></td>
|
||||
<td class="border border-gray-300 px-3 py-2"><?php echo htmlspecialchars( (string) ( $item['description'] ?? '' ) ); ?></td>
|
||||
@@ -417,5 +433,56 @@ try {
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
renderNavTimers();
|
||||
if (navTimerNodes.length > 0) {
|
||||
window.setInterval(renderNavTimers, 1000);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user