80 lines
3.6 KiB
TypeScript
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];
|
|
}
|