import { content } from "@deadhead/content";
import { FleetAsleep } from "../components/FleetSleep";
import { NeedsYou } from "../components/NeedsYou";
import { DispatchOfficePanel } from "../components/IndependentPilots";
import type { Dispatch, Pilot, PlayerState, Ship, TutorialState } from "@deadhead/schema";
import { dispatchProgress } from "@deadhead/sim";
import { Link } from "react-router";
import { useClock } from "../clock/clock";
import { Countdown } from "../components/Countdown";
import { formatFu, FuelGauge } from "../components/Fuel";
import { GoalsCard } from "../components/GoalsCard";
import { goodName, PriorityChip } from "../components/Goods";
import { LoanCard } from "../components/LoanCard";
import { RouteStrip } from "../components/RouteStrip";
import { formatGameHours, locationName, shipModelName } from "../format";
import { lowFuel } from "../game";
import { ShopRest } from "../components/ShopRest";
import { useLoadedPlayer } from "../state/actions";
import {
Art,
buttonClass,
Eyebrow,
Icon,
Meter,
Panel,
Pill,
Screen,
ScreenHeader,
StatTile,
} from "../ui";
/** What the player needs to do next to reach the deal (decision 0.5.30). */
export function tutorialHint(tutorial: TutorialState, runsBeforeDeal: number): string {
if (tutorial.dealAvailable) return "Halvorsen is waiting at Earth with an offer.";
if (tutorial.runsCompleted < runsBeforeDeal) {
return "Keep flying — the old man has an offer for you.";
}
// Enough runs, but the deal is made at Earth.
return tutorial.runInFlight
? "Halvorsen will talk once you land at Earth."
: "One more run: bring the ship back to Earth — Halvorsen is waiting there.";
}
/** The ship the player's own pilot flies (decision 0.5.119). */
export type PilotShip =
| { kind: "loaner"; avatar: Pilot; locationId: string; dispatch: Dispatch | undefined }
| { kind: "ship"; avatar: Pilot; ship: Ship; dispatch: Dispatch | undefined }
| { kind: "none"; avatar: Pilot | undefined };
/** The avatar's assigned ship, or the company loaner during the tutorial. */
export function pilotShip(state: PlayerState): PilotShip {
const avatar = state.pilots.find((p) => p.isAvatar);
if (!avatar) return { kind: "none", avatar };
if (state.tutorial) {
return {
kind: "loaner",
avatar,
locationId: state.tutorial.loanerLocationId,
dispatch: state.activeDispatches.find((d) => d.tutorial),
};
}
const ship = state.ships.find((s) => s.id === avatar.assignedShipId);
if (!ship) return { kind: "none", avatar };
return {
kind: "ship",
avatar,
ship,
dispatch: state.activeDispatches.find((d) => d.id === ship.dispatchId),
};
}
/** The display name of the ship a dispatch flies (the loaner isn't in the ships list). */
function flightShipName(state: PlayerState, dispatch: Dispatch): string {
if (dispatch.tutorial) return "Company loaner";
return state.ships.find((s) => s.id === dispatch.shipId)?.name ?? "A ship";
}
/** The Dashboard ("Bridge"): your ship first, then the company (CR-020). */
export function Dashboard() {
const state = useLoadedPlayer();
const now = useClock((s) => s.now);
const idle = state.ships.filter((s) => s.status === "idle").length;
const flying = state.ships.filter((s) => s.status === "in-flight").length;
const runsBeforeDeal = content.config.tutorialRunsBeforeDeal;
const onJobs = [...state.activeDispatches].sort((a, b) => a.arrivalAt - b.arrivalAt);
// Resting pilots and who wakes first (CR-004).
const resting = state.pilots
.filter((p) => p.resting && p.restEndsAt !== null)
.sort((a, b) => a.restEndsAt! - b.restEndsAt!);
// Ships in the upgrade shop, soonest out first (CR-009).
const inShop = state.ships
.filter((s) => s.inShopUntil !== null && now < s.inShopUntil)
.sort((a, b) => a.inShopUntil! - b.inShopUntil!);
return (
Company runs: {Math.min(state.tutorial.runsCompleted, runsBeforeDeal)} of{" "}
{runsBeforeDeal}
{tutorialHint(state.tutorial, runsBeforeDeal)}
Nothing in flight.
Pilots resting: {resting.length}
{resting[0]!.name} wakes in No ships yet — buy one in the Shipyard. You're not flying a ship right now. {shipModelName(modelId)}
{dispatch
? `${locationName(dispatch.originId)} → ${locationName(dispatch.destinationId)}`
: `At ${locationName(hereId ?? "")}`}
In the shop — ready in
Low fuel ·{" "}
{locationName(low.destinationId)} needs {formatFu(low.fuelFu)}
Supplied by HalvorsenFlying for {content.tutorialCompanyName}
In flight
{flying} flying · {idle} idle
{onJobs.map((d) => {
const name = flightShipName(state, d);
return (
)}
In the shop: {inShop.length}
{inShop.map((ship) => (
{name}
{dispatch ? (
Duty {formatGameHours(pilot.dutyHours)} of {formatGameHours(pilot.dutyLimit)}
> )}