import { content, type ShipModel } from "@deadhead/content"; import { fullTankReach } from "@deadhead/sim"; import { useState } from "react"; import { formatCredits, formatMillionKm, locationName } from "../format"; import { shipReach, shipStatsOf } from "../game"; import { errorMessage, useGameAction, useLoadedPlayer } from "../state/actions"; import { Art, Button, Icon, Panel, Screen, ScreenHeader, Sheet, type IconName } from "../ui"; const LANDING_LABELS = { vacuum: "Vacuum", atmospheric: "Atmospheric", hardened: "Hardened" }; const MODELS = content.purchasableShipModels; /** How far a full tank flies from Earth, in million km (decision 0.5.95). */ const reachFromEarth = (model: ShipModel) => fullTankReach(model, content.location("earth")!, content.config); const best = { speed: Math.max(...MODELS.map((m) => m.cruiseSpeedKmPerGameHour)), capacity: Math.max(...MODELS.map((m) => m.cargoCapacityTonnes)), range: Math.max(...MODELS.map(reachFromEarth)), }; /** Reach spans 5 to 1,250M km, so its bar uses a log scale to stay readable. */ const rangeShare = (range: number) => Math.log10(1 + range) / Math.log10(1 + best.range); /** Ship models for sale, delivered to the HQ (bay) the player picks (Steps 9.8, 10B.9). */ export function Market() { const state = useLoadedPlayer(); const buy = useGameAction("buyShip"); const [buying, setBuying] = useState(null); return ( {buy.isError && (

{errorMessage(buy.error)}

)} {buy.isSuccess && (

{buy.data.ship.name} is waiting at {locationName(buy.data.ship.locationId ?? "")}.

)}
    {MODELS.map((model) => { const affordable = state.company.credits >= model.price; return (
    {model.name} {formatCredits(model.price)}

    {model.role}

    ); })}
{buying && ( buy.mutate({ modelId: buying.id, bayId }, { onSettled: () => setBuying(null) }) } onClose={() => setBuying(null)} /> )}
); } /** One spec line: label and value (dt directly before dd), with a bar relative to the best model. */ function Spec(props: { icon: IconName; label: string; value: string; share?: number }) { return (
{props.label}
{props.value}
{props.share !== undefined && ( ); } const names = (ids: string[]) => ids.map(locationName).join(", "); /** * Where this model can fly from the player's HQs (CR-012), e.g. * "From Earth: Moon, Mars · By hopping: Ceres". Computed with the sim. */ function Reach({ model }: { model: ShipModel }) { const state = useLoadedPlayer(); const origins = [...new Set(state.bays.map((b) => b.locationId))]; const reach = shipReach(model, origins); const parts = [ ...reach.fromOrigins.map( (leg) => `From ${locationName(leg.originId)}: ${leg.destinationIds.length > 0 ? names(leg.destinationIds) : "nowhere"}`, ), ...(reach.byHopping.length > 0 ? [`By hopping: ${names(reach.byHopping)}`] : []), ]; if (parts.length === 0) return null; return (

Reach {parts.join(" · ")}

); } /** "Deliver to which HQ?": choose the bay the new ship appears at (decision 0.5.35). */ function DeliveryPicker(props: { model: ShipModel; pending: boolean; onBuy: (bayId: string) => void; onClose: () => void; }) { const state = useLoadedPlayer(); const [bayId, setBayId] = useState(state.bays.length === 1 ? state.bays[0]!.id : null); return (
HQ {state.bays.map((bay) => { const sprite = content.location(bay.locationId)?.sprite; return ( ); })}
); } /** The player's upgraded ships of a model, with their effective stats, to compare (CR-009). */ function OwnedUpgraded({ modelId }: { modelId: string }) { const state = useLoadedPlayer(); const upgraded = state.ships.filter( (s) => s.modelId === modelId && Object.keys(s.upgrades).length > 0, ); if (upgraded.length === 0) return null; return ( ); }