126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { Dispatch, PlayerState } from "@deadhead/schema";
|
|
import { bodyPosition, dispatchProgress, loanerModel, realToGame } from "@deadhead/sim";
|
|
import { locationMapPoint, radialScale, type MapPoint } from "./projection";
|
|
import { mapStyle } from "./style";
|
|
|
|
export interface SceneLocation {
|
|
id: string;
|
|
name: string;
|
|
/**
|
|
* Map position; for a child body (the Moon) its parent's position, since
|
|
* children are drawn at a fixed screen offset from it (decision 0.5.39).
|
|
*/
|
|
point: MapPoint;
|
|
/** The parent location of a child body, else null. */
|
|
parentId: string | null;
|
|
/** Map radius of its orbit around the Sun (the faint ring); null for moons. */
|
|
orbitRadius: number | null;
|
|
discovered: boolean;
|
|
hasBay: boolean;
|
|
/** Idle ships here (the tutorial loaner included), shown as a badge. */
|
|
idleShips: number;
|
|
/** Map art asset ids (CR-003). */
|
|
sprite: string;
|
|
badge: string | null;
|
|
}
|
|
|
|
export interface SceneFlight {
|
|
dispatchId: string;
|
|
/** The ship's id, or "loaner" for a tutorial run. */
|
|
shipId: string;
|
|
originId: string;
|
|
destinationId: string;
|
|
/** Origin at departure and destination at arrival (anchors; see `point`): the route line. */
|
|
from: MapPoint;
|
|
to: MapPoint;
|
|
/** Where the ship is now, on the route line. */
|
|
marker: MapPoint;
|
|
progress: number;
|
|
/** The flying ship's model and its map art asset id (CR-003). */
|
|
modelId: string;
|
|
sprite: string;
|
|
}
|
|
|
|
export interface Scene {
|
|
locations: SceneLocation[];
|
|
flights: SceneFlight[];
|
|
}
|
|
|
|
function location(id: string) {
|
|
const found = content.location(id);
|
|
if (!found) throw new Error(`Unknown location ${id}`);
|
|
return found;
|
|
}
|
|
|
|
/** The location whose map position a location is drawn from: its parent, if it has one. */
|
|
export function anchorLocation(id: string) {
|
|
return location(mapStyle.childOffsets[id]?.parentId ?? id);
|
|
}
|
|
|
|
/**
|
|
* A flight's route and current marker (Step 10.2). The marker moves along the
|
|
* straight route line by the sim's progress, in map space, so it always stays
|
|
* on the line however the map is scaled.
|
|
*/
|
|
export function flightMarker(dispatch: Dispatch, now: number, modelId: string): SceneFlight {
|
|
const from = locationMapPoint(
|
|
anchorLocation(dispatch.originId),
|
|
realToGame(dispatch.departureAt, content.config),
|
|
);
|
|
const to = locationMapPoint(
|
|
anchorLocation(dispatch.destinationId),
|
|
realToGame(dispatch.arrivalAt, content.config),
|
|
);
|
|
const progress = dispatchProgress(dispatch, now);
|
|
return {
|
|
dispatchId: dispatch.id,
|
|
shipId: dispatch.shipId,
|
|
originId: dispatch.originId,
|
|
destinationId: dispatch.destinationId,
|
|
from,
|
|
to,
|
|
marker: { x: from.x + (to.x - from.x) * progress, y: from.y + (to.y - from.y) * progress },
|
|
progress,
|
|
modelId,
|
|
sprite: content.shipModel(modelId)?.sprite ?? `ship-${modelId}`,
|
|
};
|
|
}
|
|
|
|
/** Everything the map draws at real time `now`, in map coordinates. */
|
|
export function buildScene(state: PlayerState, now: number): Scene {
|
|
const gameTime = realToGame(now, content.config);
|
|
const idleAt = (locationId: string) =>
|
|
state.ships.filter((s) => s.status === "idle" && s.locationId === locationId).length +
|
|
(state.tutorial && !state.tutorial.runInFlight && state.tutorial.loanerLocationId === locationId
|
|
? 1
|
|
: 0);
|
|
|
|
return {
|
|
locations: content.locations.map((loc) => {
|
|
const sun = bodyPosition(loc.body, gameTime);
|
|
return {
|
|
id: loc.id,
|
|
name: loc.name,
|
|
point: locationMapPoint(anchorLocation(loc.id), gameTime),
|
|
parentId: mapStyle.childOffsets[loc.id]?.parentId ?? null,
|
|
orbitRadius: loc.body === "moon" ? null : radialScale(Math.hypot(sun.x, sun.y)),
|
|
discovered: state.locations.some((l) => l.id === loc.id && l.discovered),
|
|
hasBay: state.bays.some((b) => b.locationId === loc.id),
|
|
idleShips: idleAt(loc.id),
|
|
sprite: loc.sprite,
|
|
badge: loc.badge ?? null,
|
|
};
|
|
}),
|
|
flights: state.activeDispatches.map((d) =>
|
|
flightMarker(
|
|
d,
|
|
now,
|
|
d.tutorial
|
|
? loanerModel(content).id
|
|
: (state.ships.find((s) => s.id === d.shipId)?.modelId ?? "courier"),
|
|
),
|
|
),
|
|
};
|
|
}
|