forked from phenom/healer-man
50 lines
2.8 KiB
TypeScript
50 lines
2.8 KiB
TypeScript
import { requireDungeonDefinition } from "../game/dungeonRegistry";
|
|
import { useGameStore } from "../game/store";
|
|
|
|
export function DungeonMapGraphic({ position, yaw, compact = false, dungeonId }: {
|
|
position: readonly [number, number, number];
|
|
yaw: number;
|
|
compact?: boolean;
|
|
dungeonId?: string;
|
|
}) {
|
|
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
|
|
const dungeon = requireDungeonDefinition(dungeonId ?? activeDungeonId);
|
|
const { width: viewWidth, height: viewHeight, padding, contours } = dungeon.presentation.map;
|
|
const width = Math.max(1, dungeon.bounds.max[0] - dungeon.bounds.min[0]);
|
|
const depth = Math.max(1, dungeon.bounds.max[2] - dungeon.bounds.min[2]);
|
|
const project = (point: readonly [number, number, number]): readonly [number, number] => [
|
|
padding + ((point[0] - dungeon.bounds.min[0]) / width) * (viewWidth - padding * 2),
|
|
padding + ((point[2] - dungeon.bounds.min[2]) / depth) * (viewHeight - padding * 2),
|
|
];
|
|
const [playerX, playerY] = project(position);
|
|
const headingX = Math.sin(yaw) * 12;
|
|
const headingY = Math.cos(yaw) * 12;
|
|
const id = `${dungeon.id}-${compact ? "companion" : "dialog"}-map`;
|
|
return (
|
|
<svg viewBox={`0 0 ${viewWidth} ${viewHeight}`} role="img" aria-label={`${dungeon.title} patrol and encounter map`}>
|
|
<defs>
|
|
<filter id={`${id}-glow`}><feGaussianBlur stdDeviation="2.5" result="blur" /></filter>
|
|
<pattern id={`${id}-grid`} width="28" height="28" patternUnits="userSpaceOnUse">
|
|
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="rgba(154,190,139,.09)" strokeWidth="1" />
|
|
</pattern>
|
|
</defs>
|
|
<rect width={viewWidth} height={viewHeight} fill={`url(#${id}-grid)`} />
|
|
{contours.map((path, index) => (
|
|
<path key={path} className={index === 0 ? "map-contour" : "map-contour map-contour--thin"} d={path} />
|
|
))}
|
|
{!compact && dungeon.roamingPacks.map((pack) => {
|
|
const points = pack.waypoints.map((waypoint) => project(waypoint).join(",")).join(" ");
|
|
const [startX, startY] = project(pack.waypoints[0]);
|
|
return <g key={pack.id} className="map-patrol"><polyline points={points} /><circle cx={startX} cy={startY} r="3" /><title>{pack.name}</title></g>;
|
|
})}
|
|
{dungeon.areas.map((checkpoint, index) => {
|
|
const [x, y] = project(checkpoint.center);
|
|
return <g key={checkpoint.id} className={index === 0 ? "map-anchor map-anchor--entrance" : "map-anchor"}><circle cx={x} cy={y} r={compact ? 4 : 5} /><title>{checkpoint.name}</title></g>;
|
|
})}
|
|
<circle className="map-player-glow" cx={playerX} cy={playerY} r="10" filter={`url(#${id}-glow)`} />
|
|
<circle className="map-player" cx={playerX} cy={playerY} r="5.5" />
|
|
<line className="map-heading" x1={playerX} y1={playerY} x2={playerX + headingX} y2={playerY + headingY} />
|
|
</svg>
|
|
);
|
|
}
|