import catalog from "./generated/wowDungeonMaps.generated.json"; import type { DungeonDefinition, Vector3Tuple } from "./dungeonTypes"; export interface WowMapFloor { readonly level: number; readonly name: string; /** World MinX, MaxX, MinY, MaxY; X points north and Y points west. */ readonly bounds: readonly [number, number, number, number]; readonly image: string; readonly checksum: string; } export interface WowDungeonMap { readonly mapId: number; readonly coordinateTransform: string; readonly visualChecksum?: string; /** Three row-major affine rows converting package position to WoW XYZ. */ readonly runtimeToWow: readonly number[]; readonly floors: readonly WowMapFloor[]; } const maps = catalog.dungeons as unknown as Readonly>; export function wowDungeonMapFor(dungeon: DungeonDefinition): WowDungeonMap | null { const map = maps[dungeon.contentSourceDungeonId ?? dungeon.id]; // A replacement environment must be reimported, never silently misaligned. return map?.mapId === dungeon.mapId && map.coordinateTransform === dungeon.provenance.coordinateTransform && map.visualChecksum === dungeon.assets.visual[0]?.checksum ? map : null; } export function wowMapWorldPosition(map: WowDungeonMap, position: Vector3Tuple): Vector3Tuple { return [0, 1, 2].map((row) => { const i = row * 4; return map.runtimeToWow[i] * position[0] + map.runtimeToWow[i + 1] * position[1] + map.runtimeToWow[i + 2] * position[2] + map.runtimeToWow[i + 3]; }) as unknown as Vector3Tuple; } export function wowMapPoint(map: WowDungeonMap, floor: WowMapFloor, position: Vector3Tuple): readonly [number, number] { const [x, y] = wowMapWorldPosition(map, position); const [minX, maxX, minY, maxY] = floor.bounds; return [(maxY - y) / (maxY - minY) * 1002, (maxX - x) / (maxX - minX) * 668]; } export function wowMapContains(map: WowDungeonMap, floor: WowMapFloor, position: Vector3Tuple): boolean { const [x, y] = wowMapPoint(map, floor, position); return Number.isFinite(x) && Number.isFinite(y) && x >= 0 && x <= 1002 && y >= 0 && y <= 668; } /** Smallest containing floor wins; stacked rooms can be selected explicitly in the map panel. */ export function wowMapFloorFor(map: WowDungeonMap, position: Vector3Tuple, level?: number | null): WowMapFloor { const explicit = map.floors.find((floor) => floor.level === level); if (explicit) return explicit; const containing = map.floors.filter((floor) => floor.level !== 0 && wowMapContains(map, floor, position)); const size = (floor: WowMapFloor) => (floor.bounds[1] - floor.bounds[0]) * (floor.bounds[3] - floor.bounds[2]); return containing.sort((a, b) => size(a) - size(b) || a.level - b.level)[0] ?? map.floors.find((floor) => floor.level === 0) ?? map.floors[0]; } export function wowMapHeading(map: WowDungeonMap, floor: WowMapFloor, position: Vector3Tuple, yaw: number): readonly [number, number] { const [x, y] = wowMapPoint(map, floor, position); const [targetX, targetY] = wowMapPoint(map, floor, [position[0] + Math.sin(yaw), position[1], position[2] + Math.cos(yaw)]); const length = Math.hypot(targetX - x, targetY - y) || 1; return [(targetX - x) / length * 23, (targetY - y) / length * 23]; }