104 lines
4.2 KiB
TypeScript
104 lines
4.2 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { TutorialState } from "@deadhead/schema";
|
|
import { realToGame } from "@deadhead/sim";
|
|
import { useClock } from "../clock/clock";
|
|
import { formatGameTime } from "../clock/GameClock";
|
|
import { Countdown } from "../components/Countdown";
|
|
import { GoalsCard } from "../components/GoalsCard";
|
|
import { LoanCard } from "../components/LoanCard";
|
|
import { formatCredits, locationName } from "../format";
|
|
import { useLoadedPlayer } from "../state/actions";
|
|
|
|
/** 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.";
|
|
}
|
|
|
|
export function HQ() {
|
|
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 next = [...state.activeDispatches].sort((a, b) => a.arrivalAt - b.arrivalAt)[0];
|
|
// 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!);
|
|
|
|
return (
|
|
<section className="space-y-4 p-4">
|
|
<h1 className="text-xl font-semibold">HQ</h1>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Stat label="Credits" value={formatCredits(state.company.credits)} />
|
|
<Stat label="Game date" value={formatGameTime(realToGame(now, content.config))} />
|
|
<Stat label="Ships idle" value={String(idle)} />
|
|
<Stat label="Ships in flight" value={String(flying)} />
|
|
</div>
|
|
|
|
<div className="rounded-lg border border-slate-800 p-3">
|
|
<h2 className="text-sm font-semibold text-slate-300">Next arrival</h2>
|
|
{next ? (
|
|
<p className="mt-1">
|
|
{locationName(next.originId)} → {locationName(next.destinationId)} in{" "}
|
|
<Countdown to={next.arrivalAt} />
|
|
</p>
|
|
) : (
|
|
<p className="mt-1 text-sm text-slate-400">Nothing in flight.</p>
|
|
)}
|
|
</div>
|
|
|
|
{!state.tutorial && (
|
|
<div aria-label="Pilot rest" className="rounded-lg border border-slate-800 p-3">
|
|
<h2 className="text-sm font-semibold text-slate-300">Pilots resting: {resting.length}</h2>
|
|
{resting[0] ? (
|
|
<p className="mt-1">
|
|
{resting[0].name} wakes in <Countdown to={resting[0].restEndsAt!} done="now" />
|
|
</p>
|
|
) : (
|
|
<p className="mt-1 text-sm text-slate-400">Everyone is ready to fly.</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{!state.tutorial && state.ships.length === 0 && (
|
|
<p className="text-sm text-slate-400">No ships yet — buy one in the Market.</p>
|
|
)}
|
|
|
|
{state.tutorial && (
|
|
<div className="rounded-lg border border-amber-500/40 bg-amber-500/10 p-3">
|
|
<h2 className="font-semibold text-amber-300">Flying for Halvorsen Lunar Freight</h2>
|
|
<p aria-label="Company runs" className="mt-1 text-sm text-slate-300">
|
|
Company runs: {Math.min(state.tutorial.runsCompleted, runsBeforeDeal)} of{" "}
|
|
{runsBeforeDeal}
|
|
</p>
|
|
<p aria-label="Next step" className="mt-1 text-xs text-slate-400">
|
|
{tutorialHint(state.tutorial, runsBeforeDeal)}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{!state.tutorial && <GoalsCard />}
|
|
|
|
{!state.tutorial && state.company.loanBalance > 0 && <LoanCard />}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function Stat({ label, value }: { label: string; value: string }) {
|
|
return (
|
|
<div className="rounded-lg border border-slate-800 p-3">
|
|
<div className="text-xs text-slate-400">{label}</div>
|
|
<div className="mt-1 font-semibold tabular-nums">{value}</div>
|
|
</div>
|
|
);
|
|
}
|