Files
healer-man/src/ui/DungeonMapGraphic.tsx
T
2026-08-14 15:56:39 -04:00

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>
);
}