Files
healer-man/src/game/dungeonArtworkMap.ts
T
2026-09-27 21:48:00 -04:00

80 lines
3.6 KiB
TypeScript

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<Record<string, RunewakerDungeonMap>>;
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];
}