import runewakerCatalog from "./generated/runewakerDungeonMaps.generated.json"; import { dungeonContentSourceId } from "./dungeonContentSource"; import type { DungeonDefinition, Vector3Tuple } from "./dungeonTypes"; import { wowDungeonMapFor, wowMapFloorFor, wowMapPoint, type WowDungeonMap, type WowMapFloor, } from "./wowDungeonMap"; export interface RunewakerDungeonMap { readonly mapId: number; readonly coordinateTransform: string; readonly visualChecksum: string; readonly kind: "client" | "navigation"; readonly image: string; readonly checksum: string; readonly sourceHash: string; /** Two affine rows converting runtime XYZ directly to image pixels. */ readonly runtimeToMap: readonly number[]; readonly width: number; readonly height: number; } export interface ArtworkFloor { readonly level: number; readonly name: string; readonly image: string; readonly checksum: string; } export type DungeonArtworkMap = | { readonly kind: "wow"; readonly source: WowDungeonMap; readonly floors: readonly WowMapFloor[]; readonly width: 1002; readonly height: 668 } | { readonly kind: "runewaker"; readonly source: RunewakerDungeonMap; readonly floors: readonly ArtworkFloor[]; readonly width: number; readonly height: number }; const runewakerMaps = runewakerCatalog.dungeons as unknown as Readonly>; export function dungeonArtworkMapFor(dungeon: DungeonDefinition): DungeonArtworkMap | null { const wow = wowDungeonMapFor(dungeon); if (wow) return { kind: "wow", source: wow, floors: wow.floors, width: 1002, height: 668 }; const source = runewakerMaps[dungeon.contentSourceDungeonId ?? dungeonContentSourceId(dungeon.id)]; if (source?.mapId !== dungeon.mapId || source.coordinateTransform !== dungeon.provenance.coordinateTransform || source.visualChecksum !== dungeon.assets.visual[0]?.checksum || source.runtimeToMap.length !== 8) return null; return { kind: "runewaker", source, floors: [{ level: 0, name: source.kind === "client" ? "Original dungeon map" : "Dungeon floorplan", image: source.image, checksum: source.checksum }], width: source.width, height: source.height, }; } export function dungeonArtworkFloorFor(map: DungeonArtworkMap, position: Vector3Tuple, level?: number | null): ArtworkFloor { return map.kind === "wow" ? wowMapFloorFor(map.source, position, level) : map.floors[0]; } export function dungeonArtworkPoint(map: DungeonArtworkMap, floor: ArtworkFloor, position: Vector3Tuple): readonly [number, number] { if (map.kind === "wow") return wowMapPoint(map.source, floor as WowMapFloor, position); const matrix = map.source.runtimeToMap; return [matrix[0] * position[0] + matrix[1] * position[1] + matrix[2] * position[2] + matrix[3], matrix[4] * position[0] + matrix[5] * position[1] + matrix[6] * position[2] + matrix[7]]; } export function dungeonArtworkContains(map: DungeonArtworkMap, floor: ArtworkFloor, position: Vector3Tuple): boolean { const [x, y] = dungeonArtworkPoint(map, floor, position); return Number.isFinite(x) && Number.isFinite(y) && x >= 0 && x <= map.width && y >= 0 && y <= map.height; } export function dungeonArtworkHeading(map: DungeonArtworkMap, floor: ArtworkFloor, position: Vector3Tuple, yaw: number): readonly [number, number] { const [x, y] = dungeonArtworkPoint(map, floor, position); const [targetX, targetY] = dungeonArtworkPoint(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]; }