import { content } from "@deadhead/content";
import type { Pilot, PilotOrders, PlayerState } from "@deadhead/schema";
import { useState } from "react";
import { Link } from "react-router";
import { formatCredits, locationName } from "../format";
import { formatLocalTime } from "../screens/DispatchSheet";
import { errorMessage, useGameAction, useLoadedPlayer } from "../state/actions";
import { Button, Icon, Panel, Pill } from "../ui";
import { goodName } from "./Goods";
import { serviceStatus } from "./Service";
const inputClass =
"rounded-lg border border-space-600 bg-space-950/70 px-3 py-2 text-sm text-ink-50 focus:border-amber-400 focus:outline-none";
/** The one-time Dispatch Office on the Bridge (decision 0.5.123). */
export function DispatchOfficePanel() {
const state = useLoadedPlayer();
const buy = useGameAction("buyDispatchOffice");
if (state.tutorial) return null;
const price = content.config.independentPilots.dispatchOfficePrice;
if (state.dispatchOffice.owned) {
return (
Dispatch Office
Your hired pilots can run jobs on their own. Set them to Independent on{" "}
Pilots
.
);
}
return (
Dispatch Office
Lets your hired pilots run jobs on their own, so the company keeps earning while you're
away.
{buy.isError && (
{errorMessage(buy.error)}
)}
);
}
/**
* What an Independent pilot is doing (CR-021 §2): flying a job, resting, or
* waiting for new contracts; null for a Manual pilot.
*/
export function independentStatus(state: PlayerState, pilot: Pilot): string | null {
if (pilot.mode !== "independent") return null;
// Booked service comes first (CR-027 §4): heading in, in the shop, waiting for funds.
const ship = state.ships.find((s) => s.id === pilot.assignedShipId);
const service = ship && ship.booking?.status !== "cant-reach" ? serviceStatus(ship, pilot) : null;
if (service) return service;
const flight = state.activeDispatches.find((d) => d.pilotId === pilot.id);
if (flight) {
const where = locationName(flight.destinationId);
return flight.contract
? `Independent: flying ${goodName(flight.contract)} to ${where}`
: `Independent: flying empty to ${where}`;
}
if (pilot.restingInShopUntil !== null) {
return `Resting while in the shop until ${formatLocalTime(pilot.restingInShopUntil)}`;
}
if (pilot.resting && pilot.restEndsAt !== null) {
return `Resting until ${formatLocalTime(pilot.restEndsAt)}`;
}
if (!pilot.assignedShipId) return "Independent: needs a ship";
// The fleet has gone quiet: nothing new until the player decides (decision 0.5.137).
if (state.fleetSleep.asleep) return "Waiting for orders";
return `Waiting for new contracts at ${locationName(pilot.locationId ?? "")}`;
}
/** Manual or Independent, the status line, and the orders (decisions 0.5.122–0.5.124). */
export function PilotIndependence({ pilot }: { pilot: Pilot }) {
const state = useLoadedPlayer();
const setMode = useGameAction("setPilotMode");
// The player is an owner-operator: never Independent (decision 0.5.122).
if (pilot.isAvatar || state.tutorial) return null;
const owned = state.dispatchOffice.owned;
const status = independentStatus(state, pilot);
return (
{status && (
{status}
)}
{setMode.isError && (
{errorMessage(setMode.error)}
)}
{owned && }
);
}
/** A pilot's orders (decision 0.5.124), edited as a draft and saved together. */
function OrdersEditor({ pilot }: { pilot: Pilot }) {
const state = useLoadedPlayer();
const save = useGameAction("setPilotOrders");
const [draft, setDraft] = useState(pilot.orders);
const discovered = state.locations.filter((l) => l.discovered).map((l) => l.id);
const allPlaces = draft.destinationIds === null;
const toggle = (list: readonly string[], id: string, on: boolean) =>
on ? [...new Set([...list, id])] : list.filter((x) => x !== id);
return (
Orders
);
}
/** Settings → Company: the credits pilots never spend the company below (decision 0.5.127). */
export function SpendingFloor() {
const state = useLoadedPlayer();
const save = useGameAction("setSpendingFloor");
const [draft, setDraft] = useState(String(state.dispatchOffice.spendingFloor));
if (state.tutorial) return null;
const value = Math.floor(Number(draft));
const valid = draft.trim() !== "" && Number.isFinite(value) && value >= 0;
return (
);
}
/** The Fleet card's badge for a ship whose pilot flies on their own. */
export function IndependentBadge({ pilot }: { pilot: Pilot | undefined }) {
if (pilot?.mode !== "independent") return null;
return (
Independent
);
}