feature: Final mining touches, with closing contracts and negotiating new ones

This commit is contained in:
Keith Solomon
2026-02-11 18:23:37 -06:00
parent 7dc7ce6e50
commit c935ac0dda
3 changed files with 222 additions and 20 deletions

View File

@@ -498,6 +498,10 @@ try {
$accepted = $contract['accepted'] ? 'Accepted' : false;
$fulfilled = $contract['fulfilled'] ? 'Fulfilled' : false;
if ($fulfilled) {
continue;
}
if (! $accepted && ! $fulfilled) {
$status = 'Not Accepted';
} elseif ($accepted && ! $fulfilled) {

View File

@@ -291,6 +291,34 @@ class SpacetradersApi {
);
}
/**
* Fulfill a contract for the current agent.
*
* @param string $contractId The contract ID to fulfill.
*
* @return array<string,mixed>
*/
public function fulfillContract( string $contractId ): array {
return $this->request(
'POST',
'/my/contracts/' . rawurlencode( $contractId ) . '/fulfill'
);
}
/**
* Negotiate a new contract using a ship.
*
* @param string $shipSymbol The ship symbol that will negotiate the contract.
*
* @return array<string,mixed>
*/
public function negotiateContract( string $shipSymbol ): array {
return $this->request(
'POST',
'/my/ships/' . rawurlencode( $shipSymbol ) . '/negotiate/contract'
);
}
/**
* Deliver cargo for a contract using a ship at the delivery destination.
*

View File

@@ -95,6 +95,37 @@ function buildContractDeliveryTargets( array $activeContracts ): array {
return $targets;
}
/**
* Determine if a trade symbol represents ore cargo.
*
* @param string $tradeSymbol Trade symbol.
*
* @return bool
*/
function isOreTradeSymbol( string $tradeSymbol ): bool {
return preg_match( '/_ORE$/', strtoupper( $tradeSymbol ) ) === 1;
}
/**
* Determine which ship symbol should be used for contract negotiation.
*
* @param array<int,array<string,mixed>> $miningShips Mining ship records.
* @param array<int,array<string,mixed>> $ships All ship records.
*
* @return string
*/
function getContractNegotiationShipSymbol( array $miningShips, array $ships ): string {
if (! empty( $miningShips ) ) {
return (string) ( $miningShips[0]['symbol'] ?? '' );
}
if (! empty( $ships ) ) {
return (string) ( $ships[0]['symbol'] ?? '' );
}
return '';
}
$config = require __DIR__ . '/lib/project-config.php';
$storage = new SpacetradersStorage( $config['db_path'] );
@@ -206,6 +237,73 @@ try {
trim( (string) $_POST['contract_delivery_key'] ) :
(string) ( $contractDeliveryTargets[0]['key'] ?? '' );
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['contract_action'] ) ) {
$contractAction = (string) $_POST['contract_action'];
if ($contractAction === 'fulfill_contract' ) {
$contractId = trim( (string) ( $_POST['contract_id'] ?? '' ) );
if ($contractId !== '' ) {
try {
$client->fulfillContract( $contractId );
$statusMessage = 'Contract ' . $contractId . ' fulfilled.';
$storage->clearAllCache();
$shipsResponse = $client->listMyShips();
$contractsResponse = $client->listMyContracts();
$ships = $shipsResponse['data'] ?? $shipsResponse;
$contracts = $contractsResponse['data'] ?? $contractsResponse;
$miningShips = filterMiningShips( $ships );
$activeContracts = array_values(
array_filter(
(array) $contracts,
static function ( array $contract ): bool {
return (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false );
}
)
);
$contractDeliveryTargets = buildContractDeliveryTargets( $activeContracts );
$selectedContractDeliveryKey = (string) ( $contractDeliveryTargets[0]['key'] ?? '' );
} catch (SpacetradersApiException $e) {
$errorMessage = 'Unable to fulfill contract: ' . $e->getMessage();
}
}
} elseif ($contractAction === 'negotiate_contract' ) {
$negotiateShipSymbol = trim( (string) ( $_POST['negotiate_ship_symbol'] ?? '' ) );
if ($negotiateShipSymbol === '' ) {
$negotiateShipSymbol = getContractNegotiationShipSymbol( $miningShips, $ships );
}
if ($negotiateShipSymbol === '' ) {
$errorMessage = 'No available ship found to negotiate a contract.';
} else {
try {
$client->negotiateContract( $negotiateShipSymbol );
$statusMessage = 'Negotiated a new contract using ' . $negotiateShipSymbol . '.';
$storage->clearAllCache();
$shipsResponse = $client->listMyShips();
$contractsResponse = $client->listMyContracts();
$ships = $shipsResponse['data'] ?? $shipsResponse;
$contracts = $contractsResponse['data'] ?? $contractsResponse;
$miningShips = filterMiningShips( $ships );
$activeContracts = array_values(
array_filter(
(array) $contracts,
static function ( array $contract ): bool {
return (bool) ( $contract['accepted'] ?? false ) && ! (bool) ( $contract['fulfilled'] ?? false );
}
)
);
$contractDeliveryTargets = buildContractDeliveryTargets( $activeContracts );
$selectedContractDeliveryKey = (string) ( $contractDeliveryTargets[0]['key'] ?? '' );
} catch (SpacetradersApiException $e) {
$errorMessage = 'Unable to negotiate contract: ' . $e->getMessage();
}
}
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['fleet_action'] ) ) {
$fleetAction = (string) $_POST['fleet_action'];
$successCount = 0;
@@ -357,6 +455,35 @@ try {
$actionResults[] = $shipSymbol . ': jettisoned ' . $jettisonedItems . ' cargo item type(s).';
$successCount++;
} elseif ($fleetAction === 'jettison_non_ore_cargo_all' ) {
if ($shipStatus === 'IN_TRANSIT' ) {
$actionResults[] = $shipSymbol . ': skipped (in transit).';
continue;
}
$inventory = (array) ( $shipData['cargo']['inventory'] ?? array() );
$jettisonedItems = 0;
foreach ( $inventory as $item ) {
$tradeSymbol = (string) ( $item['symbol'] ?? '' );
$units = (int) ( $item['units'] ?? 0 );
if ($tradeSymbol === '' || $units <= 0 ) {
continue;
}
if (isOreTradeSymbol( $tradeSymbol ) ) {
continue;
}
try {
$client->jettisonCargo( $shipSymbol, $tradeSymbol, $units );
$jettisonedItems++;
} catch (SpacetradersApiException $e) {
// Continue jettisoning other items.
}
}
$actionResults[] = $shipSymbol . ': jettisoned ' . $jettisonedItems . ' non-ore cargo item type(s).';
$successCount++;
} elseif ($fleetAction === 'navigate_contract_delivery' ) {
if (! is_array( $selectedContractDeliveryTarget ) ) {
$actionResults[] = $shipSymbol . ': no contract delivery target selected.';
@@ -475,7 +602,7 @@ try {
(string) ( $contractDeliveryTargets[0]['key'] ?? '' );
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['ship_action'] ) && in_array( (string) $_POST['ship_action'], array( 'sell_ship_cargo', 'jettison_ship_cargo' ), true ) && isset( $_POST['ship_symbol'] )) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset( $_POST['ship_action'] ) && in_array( (string) $_POST['ship_action'], array( 'sell_ship_cargo', 'jettison_ship_cargo', 'jettison_non_ore_ship_cargo' ), true ) && isset( $_POST['ship_symbol'] )) {
$shipAction = (string) $_POST['ship_action'];
$sellShipSymbol = trim( (string) $_POST['ship_symbol'] );
@@ -502,6 +629,10 @@ try {
continue;
}
if ($shipAction === 'jettison_non_ore_ship_cargo' && isOreTradeSymbol( $tradeSymbol ) ) {
continue;
}
try {
if ($shipAction === 'sell_ship_cargo' ) {
$client->sellCargo( $sellShipSymbol, $tradeSymbol, $units );
@@ -516,6 +647,8 @@ try {
if ($shipAction === 'sell_ship_cargo' ) {
$statusMessage = $sellShipSymbol . ': sold ' . $handledItems . ' cargo item type(s).';
} elseif ($shipAction === 'jettison_non_ore_ship_cargo' ) {
$statusMessage = $sellShipSymbol . ': jettisoned ' . $handledItems . ' non-ore cargo item type(s).';
} else {
$statusMessage = $sellShipSymbol . ': jettisoned ' . $handledItems . ' cargo item type(s).';
}
@@ -561,6 +694,8 @@ foreach ( $miningShips as $miningShip ) {
$deliveryReadyByTradeSymbol[ $tradeSymbol ] += $units;
}
}
$contractNegotiationShipSymbol = getContractNegotiationShipSymbol( $miningShips, $ships );
?>
<!DOCTYPE html>
@@ -601,8 +736,23 @@ foreach ( $miningShips as $miningShip ) {
<div class="mb-6 border border-gray-600 rounded p-4">
<h2 class="text-xl font-bold mb-3">Global Controls</h2>
<form method="post" class="flex flex-wrap items-end gap-3">
<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>
<button type="submit" name="fleet_action" value="dock_refuel_all" class="px-4 py-2 bg-blue-700 rounded hover:bg-blue-600">
Command All: Dock & Refuel
</button>
<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="jettison_non_ore_cargo_all" class="px-4 py-2 bg-red-700 rounded hover:bg-red-600">
Command All: Jettison Non-Ore Cargo
</button>
<div class="w-full border-t border-gray-600 my-4 grow"></div>
<div>
<label for="market_waypoint" class="block text-sm mb-1">Market Waypoint</label>
<label for="market_waypoint" class="block text-xl mb-1">Market Waypoint</label>
<select id="market_waypoint" name="market_waypoint" class="px-3 py-2 rounded text-black min-w-64">
<?php foreach ( $marketWaypoints as $waypoint ) : ?>
<option value="<?php echo htmlspecialchars( $waypoint ); ?>" <?php echo $waypoint === $selectedMarketWaypoint ? 'selected' : ''; ?>>
@@ -612,8 +762,17 @@ foreach ( $miningShips as $miningShip ) {
</select>
</div>
<div>
<label for="contract_delivery_key" class="block text-sm mb-1">Contract Delivery Target</label>
<button type="submit" name="fleet_action" value="move_and_sell_all" class="px-4 py-2 bg-amber-700 rounded hover:bg-amber-600">
Command All: Move to Market & Sell
</button>
<button type="submit" name="fleet_action" value="sell_cargo_here_all" class="px-4 py-2 bg-rose-700 rounded hover:bg-rose-600">
Command All: Sell Cargo Here
</button>
<div class="w-full border-t border-gray-600 my-4 grow"></div>
<div class="">
<label for="contract_delivery_key" class="block text-xl 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>
@@ -638,21 +797,6 @@ foreach ( $miningShips as $miningShip ) {
</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>
<button type="submit" name="fleet_action" value="dock_refuel_all" class="px-4 py-2 bg-blue-700 rounded hover:bg-blue-600">
Command All: Dock & Refuel
</button>
<button type="submit" name="fleet_action" value="move_and_sell_all" class="px-4 py-2 bg-amber-700 rounded hover:bg-amber-600">
Command All: Move to Market & Sell
</button>
<button type="submit" name="fleet_action" value="sell_cargo_here_all" class="px-4 py-2 bg-rose-700 rounded hover:bg-rose-600">
Command All: Sell Cargo Here
</button>
<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>
@@ -680,7 +824,21 @@ foreach ( $miningShips as $miningShip ) {
<div class="mb-6 border border-gray-600 rounded p-4">
<h2 class="text-xl font-bold mb-3">Active Contracts</h2>
<?php if (empty( $activeContracts ) ) : ?>
<p class="text-gray-300">No active contracts.</p>
<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 else : ?>
<p class="text-sm text-gray-400">No ships available to negotiate a contract.</p>
<?php endif; ?>
<?php else : ?>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<?php foreach ( $activeContracts as $contract ) : ?>
@@ -701,6 +859,13 @@ foreach ( $miningShips as $miningShip ) {
+
<?php echo number_format( (int) ( $contract['terms']['payment']['onFulfilled'] ?? 0 ) ); ?>
</p>
<form method="post" class="mt-3">
<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">
@@ -792,6 +957,11 @@ foreach ( $miningShips as $miningShip ) {
<input type="hidden" name="ship_symbol" value="<?php echo htmlspecialchars( $shipSymbol ); ?>">
<button type="submit" class="px-3 py-1 bg-red-800 rounded hover:bg-red-700">Jettison This Ship's Cargo</button>
</form>
<form method="post" class="mt-2">
<input type="hidden" name="ship_action" value="jettison_non_ore_ship_cargo">
<input type="hidden" name="ship_symbol" value="<?php echo htmlspecialchars( $shipSymbol ); ?>">
<button type="submit" class="px-3 py-1 bg-red-700 rounded hover:bg-red-600">Jettison Non-Ore Cargo</button>
</form>
<?php endif; ?>
</div>
<?php endforeach; ?>