mudlet-map-renderer 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkeletonMapReader-B6PzEWGP.js","names":[],"sources":["../src/bigmap/Skeleton.ts","../src/bigmap/PlaneIndex.ts","../src/bigmap/SkeletonMapReader.ts"],"sourcesContent":["/**\n * Compact, fully-resident skeleton of an entire map: one slot per room across\n * parallel typed arrays. This is what lets a multi-million-room map stay in\n * memory — positions/env/area/id as flat columns, no per-room object graph.\n *\n * @public seam — this is the stable contract between skeleton producers and\n * {@link SkeletonMapReader}. Producers include {@link buildSkeleton} (eager,\n * over an already-parsed map) and out-of-process sources such as a Web Worker\n * streaming a Mudlet `.dat` (see `demo/streaming/streaming-worker.ts`). New\n * fields must be optional.\n *\n * Coordinates are RAW Mudlet map space: `y` is stored exactly as it appears in\n * the map data. `SkeletonMapReader` converts to renderer space (negated y)\n * once at construction — producers never need to know renderer conventions.\n *\n * Array ownership transfers to the consumer: `SkeletonMapReader` mutates the\n * arrays in place (the renderer-space conversion). Producers hand over freshly\n * built (or structured-clone-transferred) arrays and must not reuse them.\n */\nexport interface MapSkeleton {\n count: number;\n x: Int32Array;\n y: Int32Array;\n z: Int32Array;\n area: Int32Array;\n env: Int32Array;\n id: Int32Array;\n /** 12 cardinal exit targets per room (row-major: room i → exits[i*12 + d]); -1 = none. */\n exits: Int32Array;\n areaNames: Record<number, string>;\n /** Areas drawn as adjacency grids — cardinal exit lines are suppressed for these. */\n areaGridMode: Record<number, boolean>;\n customEnvColors: Record<number, {r: number; g: number; b: number}>;\n /** Per-room name in slot order (parallel to the typed arrays). */\n names?: string[];\n /** userData only for rooms that have any (sparse). */\n userData?: {id: number; data: Record<string, string>}[];\n /**\n * Fully-materialised rooms that carry visual detail (custom lines, symbols,\n * special exits, doors, stubs, exit locks). The bulk of the map stays\n * skeleton-only; these override the synthesised room for their id.\n * Raw map space, like the arrays.\n */\n detailRooms?: MapData.Room[];\n labels?: MapData.Label[];\n}\n\n/**\n * Cardinal exit slots per room, in packing order: room i's exit in direction\n * `SKELETON_DIRS[d]` lives at `exits[i * 12 + d]`.\n */\nexport const SKELETON_DIRS: readonly MapData.direction[] = [\n \"north\", \"northeast\", \"east\", \"southeast\", \"south\", \"southwest\",\n \"west\", \"northwest\", \"up\", \"down\", \"in\", \"out\",\n];\n","import type {ViewportBounds} from \"../types/Settings\";\nimport type {MapSkeleton} from \"./Skeleton\";\n\n/**\n * A (area, z) plane's room slots bucketed into a uniform grid via counting\n * sort. Queries touch only the cells a viewport overlaps, so visiting the\n * rooms in view is O(occupied cells + hits) instead of O(plane).\n */\nexport interface PlaneIndex {\n /** Skeleton slot indices of every room on this plane. */\n indices: Int32Array;\n minX: number;\n minY: number;\n /** Cell size in map units. */\n cs: number;\n cols: number;\n rows: number;\n /** Prefix sums: cell c's members are order[cellStart[c] .. cellStart[c+1]). */\n cellStart: Int32Array;\n order: Int32Array;\n /** Full plane bounds (same coordinate space as the skeleton). */\n bounds: ViewportBounds;\n}\n\n/**\n * Bucket every room of one (area, z) plane into a uniform grid. The cell size\n * targets a 128×128 grid over the plane's extent — coarse enough that the\n * cellStart array stays small, fine enough that a viewport query skips the\n * overwhelming majority of a dense plane.\n */\nexport function buildPlaneIndex(sk: MapSkeleton, areaId: number, z: number): PlaneIndex {\n const tmp: number[] = [];\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (let i = 0; i < sk.count; i++) {\n if (sk.area[i] !== areaId || sk.z[i] !== z) continue;\n tmp.push(i);\n const x = sk.x[i], y = sk.y[i];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n const indices = Int32Array.from(tmp);\n if (indices.length === 0) {\n return {indices, minX: 0, minY: 0, cs: 1, cols: 1, rows: 1,\n cellStart: new Int32Array(2), order: indices,\n bounds: {minX: 0, maxX: 0, minY: 0, maxY: 0}};\n }\n\n const extent = Math.max(maxX - minX, maxY - minY, 1);\n const cs = Math.max(1, Math.ceil(extent / 128));\n const cols = Math.floor((maxX - minX) / cs) + 1;\n const rows = Math.floor((maxY - minY) / cs) + 1;\n const cellOf = (i: number) =>\n Math.floor((sk.y[i] - minY) / cs) * cols + Math.floor((sk.x[i] - minX) / cs);\n\n const counts = new Int32Array(cols * rows);\n for (let k = 0; k < indices.length; k++) counts[cellOf(indices[k])]++;\n const cellStart = new Int32Array(cols * rows + 1);\n for (let c = 0; c < cols * rows; c++) cellStart[c + 1] = cellStart[c] + counts[c];\n const cursor = cellStart.slice(0, cols * rows);\n const order = new Int32Array(indices.length);\n for (let k = 0; k < indices.length; k++) {\n const i = indices[k];\n const c = cellOf(i);\n order[cursor[c]++] = i;\n }\n\n return {indices, minX, minY, cs, cols, rows, cellStart, order, bounds: {minX, maxX, minY, maxY}};\n}\n\nfunction cellRange(p: PlaneIndex, b: ViewportBounds):\n {cx0: number; cx1: number; cy0: number; cy1: number} | null {\n const cx0 = Math.max(0, Math.floor((b.minX - p.minX) / p.cs));\n const cx1 = Math.min(p.cols - 1, Math.floor((b.maxX - p.minX) / p.cs));\n const cy0 = Math.max(0, Math.floor((b.minY - p.minY) / p.cs));\n const cy1 = Math.min(p.rows - 1, Math.floor((b.maxY - p.minY) / p.cs));\n return cx1 < cx0 || cy1 < cy0 ? null : {cx0, cx1, cy0, cy1};\n}\n\n/**\n * Visit the skeleton slot of every room whose centre lies inside `b` — exact\n * (each candidate is bounds-tested), uncapped. This is the hot path for both\n * room materialisation and raster painting; it never allocates.\n */\nexport function forEachInBounds(\n sk: MapSkeleton, p: PlaneIndex, b: ViewportBounds, fn: (i: number) => void,\n): void {\n const r = cellRange(p, b);\n if (!r) return;\n const {cols, cellStart, order} = p;\n for (let cy = r.cy0; cy <= r.cy1; cy++) {\n const base = cy * cols;\n for (let cx = r.cx0; cx <= r.cx1; cx++) {\n const c = base + cx;\n for (let q = cellStart[c]; q < cellStart[c + 1]; q++) {\n const i = order[q];\n if (sk.x[i] >= b.minX && sk.x[i] <= b.maxX &&\n sk.y[i] >= b.minY && sk.y[i] <= b.maxY) fn(i);\n }\n }\n }\n}\n\n/**\n * Cheap UPPER BOUND on the rooms inside `b`: sums the occupancy of every cell\n * the bounds overlap without per-room tests. Cells at the edge contribute\n * rooms that are actually outside — do not use this where exactness matters\n * (an earlier LOD used it as an exact count and flipped modes spuriously).\n */\nexport function countInBounds(p: PlaneIndex, b: ViewportBounds): number {\n const r = cellRange(p, b);\n if (!r) return 0;\n let total = 0;\n for (let cy = r.cy0; cy <= r.cy1; cy++) {\n const base = cy * p.cols;\n for (let cx = r.cx0; cx <= r.cx1; cx++) {\n const c = base + cx;\n total += p.cellStart[c + 1] - p.cellStart[c];\n }\n }\n return total;\n}\n","import type {IMapReader} from \"../reader/MapReader\";\nimport {type IArea, pairLinkExits} from \"../reader/Area\";\nimport type {IPlane} from \"../reader/Plane\";\nimport type IExit from \"../reader/Exit\";\nimport type {ViewportDataSource} from \"../reader/ViewportDataSource\";\nimport type {ViewportBounds} from \"../types/Settings\";\nimport type {MapSkeleton} from \"./Skeleton\";\nimport {SKELETON_DIRS} from \"./Skeleton\";\nimport type {PlaneIndex} from \"./PlaneIndex\";\nimport {buildPlaneIndex, countInBounds, forEachInBounds} from \"./PlaneIndex\";\n\nconst INFINITE_VIEWPORT: ViewportBounds = {\n minX: -Infinity, maxX: Infinity, minY: -Infinity, maxY: Infinity,\n};\n\nfunction hslToRgb(h: number, s: number, l: number): [number, number, number] {\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n let r = 0, g = 0, b = 0;\n if (h < 60) [r, g, b] = [c, x, 0];\n else if (h < 120) [r, g, b] = [x, c, 0];\n else if (h < 180) [r, g, b] = [0, c, x];\n else if (h < 240) [r, g, b] = [0, x, c];\n else if (h < 300) [r, g, b] = [x, 0, c];\n else [r, g, b] = [c, 0, x];\n return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];\n}\n\n/**\n * {@link IMapReader} over a {@link MapSkeleton}: rooms are synthesised on\n * demand from compact typed-array columns instead of held as an object graph,\n * so maps far too large for the concrete `MapReader` stay renderable.\n *\n * It is also a {@link ViewportDataSource}: planes materialise only the rooms\n * inside the current viewport. The interactive backend detects this and pushes\n * padded camera bounds before every build (rebuild-on-pan); standalone use\n * (e.g. `SvgExporter`) works too — the default viewport is infinite, so\n * everything materialises unless {@link setViewport} narrows it.\n *\n * Construction takes ownership of the skeleton: coordinates are converted to\n * renderer space (y negated, matching `MapReader`) in place.\n */\nexport default class SkeletonMapReader implements IMapReader, ViewportDataSource {\n readonly viewportAware = true as const;\n\n private readonly sk: MapSkeleton;\n private viewport: ViewportBounds = INFINITE_VIEWPORT;\n private version = 0;\n\n private readonly planeCache = new Map<string, PlaneIndex>();\n private readonly areaCache = new Map<number, SkeletonArea>();\n private readonly rgbCache = new Map<number, [number, number, number]>();\n private idIndex?: Map<number, number>;\n // Per-viewport materialisation cache so getRooms() and getLinkExits()\n // share one synthesized set within a single scene build.\n private readonly visibleCache = new Map<string, MapData.Room[]>();\n\n /** Fully-converted rooms with visual detail, keyed by id (override the skeleton synth). */\n private readonly detail = new Map<number, MapData.Room>();\n private readonly userDataMap = new Map<number, Record<string, string>>();\n\n constructor(skeleton: MapSkeleton) {\n this.sk = skeleton;\n // To renderer space, once: MapReader stores rooms as y = -room.y.\n const y = skeleton.y;\n for (let i = 0; i < skeleton.count; i++) y[i] = -y[i];\n for (const u of skeleton.userData ?? []) this.userDataMap.set(u.id, u.data);\n for (const r of skeleton.detailRooms ?? []) {\n r.y = -r.y;\n if (this.isGrid(r.area)) {\n // gridMode suppresses cardinal exit visuals; keep special exits / lines / symbol.\n r.exits = {} as Record<MapData.direction, number>;\n r.stubs = [];\n }\n this.detail.set(r.id, r);\n }\n }\n\n skeleton(): MapSkeleton {\n return this.sk;\n }\n\n // --- ViewportDataSource ---\n\n setViewport(bounds: ViewportBounds): void {\n const v = this.viewport;\n if (bounds.minX === v.minX && bounds.maxX === v.maxX &&\n bounds.minY === v.minY && bounds.maxY === v.maxY) return;\n this.viewport = {...bounds};\n this.visibleCache.clear();\n this.version++;\n }\n\n getViewport(): ViewportBounds {\n return this.viewport;\n }\n\n getPlaneRoomCount(areaId: number, z: number): number {\n return this.planeIndex(areaId, z).indices.length;\n }\n\n estimateVisibleCount(areaId: number, z: number, bounds: ViewportBounds): number {\n return countInBounds(this.planeIndex(areaId, z), bounds);\n }\n\n forEachInBounds(\n areaId: number, z: number, bounds: ViewportBounds,\n fn: (x: number, y: number, envId: number) => void,\n ): void {\n const sk = this.sk;\n forEachInBounds(sk, this.planeIndex(areaId, z), bounds,\n i => fn(sk.x[i], sk.y[i], sk.env[i]));\n }\n\n // --- IMapReader ---\n\n getArea(areaId: number): IArea {\n let a = this.areaCache.get(areaId);\n if (!a) {\n a = new SkeletonArea(areaId, this);\n this.areaCache.set(areaId, a);\n }\n return a;\n }\n\n getAreas(): IArea[] {\n return [...new Set(Array.from(this.sk.area))].map(id => this.getArea(id));\n }\n\n getRooms(): MapData.Room[] {\n // Never materialise all rooms — nothing in the hot path needs it.\n return [];\n }\n\n getRoom(roomId: number): MapData.Room {\n const d = this.detail.get(roomId);\n if (d) return d;\n if (!this.idIndex) {\n this.idIndex = new Map();\n for (let i = 0; i < this.sk.count; i++) this.idIndex.set(this.sk.id[i], i);\n }\n const i = this.idIndex.get(roomId);\n return i === undefined ? (undefined as unknown as MapData.Room) : this.makeRoom(i);\n }\n\n getColorValue(envId: number): string {\n const [r, g, b] = this.rgb(envId);\n return `rgb(${r},${g},${b})`;\n }\n\n getSymbolColor(envId: number, opacity?: number): string {\n const [r, g, b] = this.rgb(envId);\n const lum = (Math.max(r, g, b) + Math.min(r, g, b)) / 2 / 255;\n // Format matches MapReader.getSymbolColor exactly (incl. the space).\n const c = lum > 0.41 ? '25,25,25' : '225,255,255';\n const o = Math.min(Math.max(opacity ?? 1, 0), 1);\n return o !== 1 ? `rgba(${c}, ${o})` : `rgba(${c})`;\n }\n\n // --- internals shared with SkeletonArea / SkeletonPlane ---\n\n getReaderVersion(): number {\n return this.version;\n }\n\n labelsFor(areaId: number, z: number): MapData.Label[] {\n return (this.sk.labels ?? []).filter(l => l.areaId === areaId && l.Z === z);\n }\n\n isGrid(areaId: number): boolean {\n return !!this.sk.areaGridMode[areaId];\n }\n\n private rgb(envId: number): [number, number, number] {\n let v = this.rgbCache.get(envId);\n if (!v) {\n const c = this.sk.customEnvColors[envId];\n v = c ? [c.r, c.g, c.b] : hslToRgb(((envId * 2654435761) >>> 0) % 360, 0.5, 0.55);\n this.rgbCache.set(envId, v);\n }\n return v;\n }\n\n private makeRoom(i: number): MapData.Room {\n const sk = this.sk;\n const d = this.detail.get(sk.id[i]);\n if (d) return d; // full detail (special exits, custom lines, symbol, …)\n // Exits are only meaningful for non-grid areas; grid areas draw rooms as\n // adjacency with no exit lines (and would otherwise emit millions).\n const exits: Record<string, number> = {};\n if (!this.isGrid(sk.area[i])) {\n const base = i * 12;\n for (let dir = 0; dir < 12; dir++) {\n const t = sk.exits[base + dir];\n if (t !== -1) exits[SKELETON_DIRS[dir]] = t;\n }\n }\n return {\n id: sk.id[i],\n area: sk.area[i],\n x: sk.x[i],\n y: sk.y[i],\n z: sk.z[i],\n areaId: String(sk.area[i]),\n weight: 1,\n roomChar: '',\n name: sk.names?.[i] ?? '',\n userData: this.userDataMap.get(sk.id[i]) ?? {},\n customLines: {},\n stubs: [],\n hash: '',\n env: sk.env[i],\n exits: exits as Record<MapData.direction, number>,\n doors: {},\n specialExits: {},\n };\n }\n\n planeIndex(areaId: number, z: number): PlaneIndex {\n const key = `${areaId}:${z}`;\n let p = this.planeCache.get(key);\n if (!p) {\n p = buildPlaneIndex(this.sk, areaId, z);\n this.planeCache.set(key, p);\n }\n return p;\n }\n\n /**\n * Every room of the plane inside the current viewport — exact, uncapped\n * (the backend's LOD budget bounds how many this can be in vector mode),\n * memoised until the viewport changes.\n */\n visibleRooms(areaId: number, z: number): MapData.Room[] {\n const key = `${areaId}:${z}`;\n let rooms = this.visibleCache.get(key);\n if (!rooms) {\n const collected: MapData.Room[] = [];\n forEachInBounds(this.sk, this.planeIndex(areaId, z), this.viewport,\n i => collected.push(this.makeRoom(i)));\n rooms = collected;\n this.visibleCache.set(key, rooms);\n }\n return rooms;\n }\n}\n\nclass SkeletonArea implements IArea {\n private readonly planeCache = new Map<number, SkeletonPlane>();\n\n constructor(\n private readonly areaId: number,\n private readonly reader: SkeletonMapReader,\n ) {}\n\n getAreaName(): string {\n return this.reader.skeleton().areaNames[this.areaId] ?? `#${this.areaId}`;\n }\n getAreaId(): number {\n return this.areaId;\n }\n getVersion(): number {\n // Viewport-dependent content: the reader bumps its version on every\n // setViewport() with new bounds, which marks all cached-area state stale.\n return this.reader.getReaderVersion();\n }\n getPlane(zIndex: number): IPlane {\n let p = this.planeCache.get(zIndex);\n if (!p) {\n p = new SkeletonPlane(this.areaId, zIndex, this.reader);\n this.planeCache.set(zIndex, p);\n }\n return p;\n }\n getPlanes(): IPlane[] {\n return this.getZLevels().map(z => this.getPlane(z));\n }\n getZLevels(): number[] {\n const sk = this.reader.skeleton();\n const zs = new Set<number>();\n for (let i = 0; i < sk.count; i++) if (sk.area[i] === this.areaId) zs.add(sk.z[i]);\n return [...zs].sort((a, b) => a - b);\n }\n getRooms(): MapData.Room[] {\n return [];\n }\n getFullBounds(): {minX: number; maxX: number; minY: number; maxY: number} {\n const zs = this.getZLevels();\n let b = {minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity};\n for (const z of zs) {\n const pb = this.reader.planeIndex(this.areaId, z).bounds;\n b = {\n minX: Math.min(b.minX, pb.minX), maxX: Math.max(b.maxX, pb.maxX),\n minY: Math.min(b.minY, pb.minY), maxY: Math.max(b.maxY, pb.maxY),\n };\n }\n return b;\n }\n getLinkExits(zIndex: number): IExit[] {\n // Grid areas are adjacency grids — Mudlet draws no exit lines for them\n // (and a dense grid would emit millions).\n if (this.reader.isGrid(this.areaId)) return [];\n const rooms = this.reader.visibleRooms(this.areaId, zIndex);\n if (rooms.length === 0) return [];\n // Pair exits directly over the visible rooms (no throwaway Area/Plane —\n // that also grouped-by-z and computed bounds we never use, pure waste\n // since visibleRooms is already single-z). Exits to off-screen/other-area\n // rooms become one-way and still draw — the pipeline resolves the far\n // endpoint through reader.getRoom().\n return Array.from(pairLinkExits(rooms).values())\n .filter(e => e.zIndex.includes(zIndex));\n }\n}\n\nclass SkeletonPlane implements IPlane {\n constructor(\n private readonly areaId: number,\n private readonly z: number,\n private readonly reader: SkeletonMapReader,\n ) {}\n\n getRooms(): MapData.Room[] {\n return this.reader.visibleRooms(this.areaId, this.z);\n }\n getLabels(): MapData.Label[] {\n return this.reader.labelsFor(this.areaId, this.z);\n }\n getBounds(): {minX: number; maxX: number; minY: number; maxY: number} {\n // Full plane bounds (NOT the viewport) so fitArea frames the whole map.\n // Labels extend the bounds exactly like the concrete Plane's do.\n const b = {...this.reader.planeIndex(this.areaId, this.z).bounds};\n for (const label of this.reader.labelsFor(this.areaId, this.z)) {\n const lx = label.X;\n const ly = -label.Y;\n b.minX = Math.min(b.minX, lx);\n b.maxX = Math.max(b.maxX, lx + label.Width);\n b.minY = Math.min(b.minY, ly);\n b.maxY = Math.max(b.maxY, ly + label.Height);\n }\n return b;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { ViewportBounds } from '../types/Settings';
2
+ import { MapSkeleton } from './Skeleton';
3
+ /**
4
+ * A (area, z) plane's room slots bucketed into a uniform grid via counting
5
+ * sort. Queries touch only the cells a viewport overlaps, so visiting the
6
+ * rooms in view is O(occupied cells + hits) instead of O(plane).
7
+ */
8
+ export interface PlaneIndex {
9
+ /** Skeleton slot indices of every room on this plane. */
10
+ indices: Int32Array;
11
+ minX: number;
12
+ minY: number;
13
+ /** Cell size in map units. */
14
+ cs: number;
15
+ cols: number;
16
+ rows: number;
17
+ /** Prefix sums: cell c's members are order[cellStart[c] .. cellStart[c+1]). */
18
+ cellStart: Int32Array;
19
+ order: Int32Array;
20
+ /** Full plane bounds (same coordinate space as the skeleton). */
21
+ bounds: ViewportBounds;
22
+ }
23
+ /**
24
+ * Bucket every room of one (area, z) plane into a uniform grid. The cell size
25
+ * targets a 128×128 grid over the plane's extent — coarse enough that the
26
+ * cellStart array stays small, fine enough that a viewport query skips the
27
+ * overwhelming majority of a dense plane.
28
+ */
29
+ export declare function buildPlaneIndex(sk: MapSkeleton, areaId: number, z: number): PlaneIndex;
30
+ /**
31
+ * Visit the skeleton slot of every room whose centre lies inside `b` — exact
32
+ * (each candidate is bounds-tested), uncapped. This is the hot path for both
33
+ * room materialisation and raster painting; it never allocates.
34
+ */
35
+ export declare function forEachInBounds(sk: MapSkeleton, p: PlaneIndex, b: ViewportBounds, fn: (i: number) => void): void;
36
+ /**
37
+ * Cheap UPPER BOUND on the rooms inside `b`: sums the occupancy of every cell
38
+ * the bounds overlap without per-room tests. Cells at the edge contribute
39
+ * rooms that are actually outside — do not use this where exactness matters
40
+ * (an earlier LOD used it as an exact count and flipped modes spuriously).
41
+ */
42
+ export declare function countInBounds(p: PlaneIndex, b: ViewportBounds): number;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Compact, fully-resident skeleton of an entire map: one slot per room across
3
+ * parallel typed arrays. This is what lets a multi-million-room map stay in
4
+ * memory — positions/env/area/id as flat columns, no per-room object graph.
5
+ *
6
+ * @public seam — this is the stable contract between skeleton producers and
7
+ * {@link SkeletonMapReader}. Producers include {@link buildSkeleton} (eager,
8
+ * over an already-parsed map) and out-of-process sources such as a Web Worker
9
+ * streaming a Mudlet `.dat` (see `demo/streaming/streaming-worker.ts`). New
10
+ * fields must be optional.
11
+ *
12
+ * Coordinates are RAW Mudlet map space: `y` is stored exactly as it appears in
13
+ * the map data. `SkeletonMapReader` converts to renderer space (negated y)
14
+ * once at construction — producers never need to know renderer conventions.
15
+ *
16
+ * Array ownership transfers to the consumer: `SkeletonMapReader` mutates the
17
+ * arrays in place (the renderer-space conversion). Producers hand over freshly
18
+ * built (or structured-clone-transferred) arrays and must not reuse them.
19
+ */
20
+ export interface MapSkeleton {
21
+ count: number;
22
+ x: Int32Array;
23
+ y: Int32Array;
24
+ z: Int32Array;
25
+ area: Int32Array;
26
+ env: Int32Array;
27
+ id: Int32Array;
28
+ /** 12 cardinal exit targets per room (row-major: room i → exits[i*12 + d]); -1 = none. */
29
+ exits: Int32Array;
30
+ areaNames: Record<number, string>;
31
+ /** Areas drawn as adjacency grids — cardinal exit lines are suppressed for these. */
32
+ areaGridMode: Record<number, boolean>;
33
+ customEnvColors: Record<number, {
34
+ r: number;
35
+ g: number;
36
+ b: number;
37
+ }>;
38
+ /** Per-room name in slot order (parallel to the typed arrays). */
39
+ names?: string[];
40
+ /** userData only for rooms that have any (sparse). */
41
+ userData?: {
42
+ id: number;
43
+ data: Record<string, string>;
44
+ }[];
45
+ /**
46
+ * Fully-materialised rooms that carry visual detail (custom lines, symbols,
47
+ * special exits, doors, stubs, exit locks). The bulk of the map stays
48
+ * skeleton-only; these override the synthesised room for their id.
49
+ * Raw map space, like the arrays.
50
+ */
51
+ detailRooms?: MapData.Room[];
52
+ labels?: MapData.Label[];
53
+ }
54
+ /**
55
+ * Cardinal exit slots per room, in packing order: room i's exit in direction
56
+ * `SKELETON_DIRS[d]` lives at `exits[i * 12 + d]`.
57
+ */
58
+ export declare const SKELETON_DIRS: readonly MapData.direction[];
@@ -0,0 +1,59 @@
1
+ import { IMapReader } from '../reader/MapReader';
2
+ import { IArea } from '../reader/Area';
3
+ import { ViewportDataSource } from '../reader/ViewportDataSource';
4
+ import { ViewportBounds } from '../types/Settings';
5
+ import { MapSkeleton } from './Skeleton';
6
+ import { PlaneIndex } from './PlaneIndex';
7
+ /**
8
+ * {@link IMapReader} over a {@link MapSkeleton}: rooms are synthesised on
9
+ * demand from compact typed-array columns instead of held as an object graph,
10
+ * so maps far too large for the concrete `MapReader` stay renderable.
11
+ *
12
+ * It is also a {@link ViewportDataSource}: planes materialise only the rooms
13
+ * inside the current viewport. The interactive backend detects this and pushes
14
+ * padded camera bounds before every build (rebuild-on-pan); standalone use
15
+ * (e.g. `SvgExporter`) works too — the default viewport is infinite, so
16
+ * everything materialises unless {@link setViewport} narrows it.
17
+ *
18
+ * Construction takes ownership of the skeleton: coordinates are converted to
19
+ * renderer space (y negated, matching `MapReader`) in place.
20
+ */
21
+ export default class SkeletonMapReader implements IMapReader, ViewportDataSource {
22
+ readonly viewportAware: true;
23
+ private readonly sk;
24
+ private viewport;
25
+ private version;
26
+ private readonly planeCache;
27
+ private readonly areaCache;
28
+ private readonly rgbCache;
29
+ private idIndex?;
30
+ private readonly visibleCache;
31
+ /** Fully-converted rooms with visual detail, keyed by id (override the skeleton synth). */
32
+ private readonly detail;
33
+ private readonly userDataMap;
34
+ constructor(skeleton: MapSkeleton);
35
+ skeleton(): MapSkeleton;
36
+ setViewport(bounds: ViewportBounds): void;
37
+ getViewport(): ViewportBounds;
38
+ getPlaneRoomCount(areaId: number, z: number): number;
39
+ estimateVisibleCount(areaId: number, z: number, bounds: ViewportBounds): number;
40
+ forEachInBounds(areaId: number, z: number, bounds: ViewportBounds, fn: (x: number, y: number, envId: number) => void): void;
41
+ getArea(areaId: number): IArea;
42
+ getAreas(): IArea[];
43
+ getRooms(): MapData.Room[];
44
+ getRoom(roomId: number): MapData.Room;
45
+ getColorValue(envId: number): string;
46
+ getSymbolColor(envId: number, opacity?: number): string;
47
+ getReaderVersion(): number;
48
+ labelsFor(areaId: number, z: number): MapData.Label[];
49
+ isGrid(areaId: number): boolean;
50
+ private rgb;
51
+ private makeRoom;
52
+ planeIndex(areaId: number, z: number): PlaneIndex;
53
+ /**
54
+ * Every room of the plane inside the current viewport — exact, uncapped
55
+ * (the backend's LOD budget bounds how many this can be in vector mode),
56
+ * memoised until the viewport changes.
57
+ */
58
+ visibleRooms(areaId: number, z: number): MapData.Room[];
59
+ }
@@ -0,0 +1,23 @@
1
+ import { MapSkeleton } from './Skeleton';
2
+ /**
3
+ * Default detail promotion: a room is materialised in full only if it carries
4
+ * visual detail the skeleton columns can't describe. Everything else (plain
5
+ * terrain) renders from the compact columns alone.
6
+ */
7
+ export declare function hasVisualDetail(room: MapData.Room): boolean;
8
+ export interface BuildSkeletonOptions {
9
+ /** Override which rooms are promoted to full-detail (default: {@link hasVisualDetail}). */
10
+ isDetailRoom?: (room: MapData.Room) => boolean;
11
+ /** Area ids to mark as adjacency grids (cardinal exit lines suppressed). */
12
+ gridAreas?: number[];
13
+ }
14
+ /**
15
+ * Eagerly build a {@link MapSkeleton} from an already-parsed map — the same
16
+ * `(map, envs)` inputs the concrete `MapReader` constructor takes. Use this
17
+ * when the map parses fine but is too dense to render as one Konva scene;
18
+ * pair the result with `SkeletonMapReader` and `settings.lodEnabled`.
19
+ *
20
+ * The output is raw map space (room `y` copied verbatim). Detail rooms are
21
+ * cloned, so the input map data is never mutated.
22
+ */
23
+ export declare function buildSkeleton(map: MapData.Map, envs?: MapData.Env[], options?: BuildSkeletonOptions): MapSkeleton;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Big-map support: render maps far too dense for a full object graph / Konva
3
+ * scene by pairing a compact typed-array {@link MapSkeleton} with the
4
+ * viewport-virtualized {@link SkeletonMapReader} and the renderer's raster
5
+ * LOD overview (`settings.lodEnabled`).
6
+ *
7
+ * Eager path (map parses fine but renders slowly):
8
+ * ```ts
9
+ * import {buildSkeleton, SkeletonMapReader} from "mudlet-map-renderer/bigmap";
10
+ * const reader = new SkeletonMapReader(buildSkeleton(mapData, colors));
11
+ * const renderer = new MapRenderer(reader, {...createSettings(), lodEnabled: true}, container);
12
+ * ```
13
+ *
14
+ * Streamed path (map too large to parse eagerly): produce a `MapSkeleton` in a
15
+ * Web Worker and transfer the typed arrays — see `demo/streaming/` for a
16
+ * reference implementation.
17
+ */
18
+ export { default as SkeletonMapReader } from './SkeletonMapReader';
19
+ export { buildSkeleton, hasVisualDetail } from './buildSkeleton';
20
+ export type { BuildSkeletonOptions } from './buildSkeleton';
21
+ export type { MapSkeleton } from './Skeleton';
22
+ export { SKELETON_DIRS } from './Skeleton';
23
+ export { buildPlaneIndex, forEachInBounds, countInBounds } from './PlaneIndex';
24
+ export type { PlaneIndex } from './PlaneIndex';
@@ -0,0 +1,60 @@
1
+ import { a as e, i as t, n, r, t as i } from "./SkeletonMapReader-B6PzEWGP.js";
2
+ //#region src/bigmap/buildSkeleton.ts
3
+ var a = (e) => !!e && Object.keys(e).length > 0;
4
+ function o(e) {
5
+ return !!e.roomChar || a(e.customLines) || a(e.specialExits) || (e.stubs?.length ?? 0) > 0 || a(e.doors) || (e.exitLocks?.length ?? 0) > 0;
6
+ }
7
+ function s(t, n = [], r = {}) {
8
+ let i = r.isDetailRoom ?? o, s = 0;
9
+ for (let e of t) s += e.rooms.length;
10
+ let c = new Int32Array(s), l = new Int32Array(s), u = new Int32Array(s), d = new Int32Array(s), f = new Int32Array(s), p = new Int32Array(s), m = new Int32Array(s * 12).fill(-1), h = Array(s), g = [], _ = [], v = [], y = {}, b = 0;
11
+ for (let n of t) {
12
+ let t = parseInt(n.areaId);
13
+ y[t] = n.areaName;
14
+ for (let e of n.labels ?? []) v.push({
15
+ ...e,
16
+ areaId: e.areaId ?? t
17
+ });
18
+ for (let t of n.rooms) {
19
+ c[b] = t.x, l[b] = t.y, u[b] = t.z, d[b] = t.area, f[b] = t.env, p[b] = t.id;
20
+ let n = b * 12;
21
+ for (let r = 0; r < 12; r++) {
22
+ let i = t.exits?.[e[r]];
23
+ i !== void 0 && (m[n + r] = i);
24
+ }
25
+ h[b] = t.name ?? "", a(t.userData) && g.push({
26
+ id: t.id,
27
+ data: t.userData
28
+ }), i(t) && _.push({ ...t }), b++;
29
+ }
30
+ }
31
+ let x = {};
32
+ for (let e of n) x[e.envId] = {
33
+ r: e.colors[0],
34
+ g: e.colors[1],
35
+ b: e.colors[2]
36
+ };
37
+ let S = {};
38
+ for (let e of r.gridAreas ?? []) S[e] = !0;
39
+ return {
40
+ count: s,
41
+ x: c,
42
+ y: l,
43
+ z: u,
44
+ area: d,
45
+ env: f,
46
+ id: p,
47
+ exits: m,
48
+ areaNames: y,
49
+ areaGridMode: S,
50
+ customEnvColors: x,
51
+ names: h,
52
+ userData: g,
53
+ detailRooms: _,
54
+ labels: v
55
+ };
56
+ }
57
+ //#endregion
58
+ export { e as SKELETON_DIRS, i as SkeletonMapReader, n as buildPlaneIndex, s as buildSkeleton, r as countInBounds, t as forEachInBounds, o as hasVisualDetail };
59
+
60
+ //# sourceMappingURL=bigmap.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bigmap.mjs","names":[],"sources":["../src/bigmap/buildSkeleton.ts"],"sourcesContent":["import type {MapSkeleton} from \"./Skeleton\";\nimport {SKELETON_DIRS} from \"./Skeleton\";\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean =>\n !!o && Object.keys(o).length > 0;\n\n/**\n * Default detail promotion: a room is materialised in full only if it carries\n * visual detail the skeleton columns can't describe. Everything else (plain\n * terrain) renders from the compact columns alone.\n */\nexport function hasVisualDetail(room: MapData.Room): boolean {\n return !!room.roomChar || hasKeys(room.customLines) || hasKeys(room.specialExits) ||\n (room.stubs?.length ?? 0) > 0 || hasKeys(room.doors) || (room.exitLocks?.length ?? 0) > 0;\n}\n\nexport interface BuildSkeletonOptions {\n /** Override which rooms are promoted to full-detail (default: {@link hasVisualDetail}). */\n isDetailRoom?: (room: MapData.Room) => boolean;\n /** Area ids to mark as adjacency grids (cardinal exit lines suppressed). */\n gridAreas?: number[];\n}\n\n/**\n * Eagerly build a {@link MapSkeleton} from an already-parsed map — the same\n * `(map, envs)` inputs the concrete `MapReader` constructor takes. Use this\n * when the map parses fine but is too dense to render as one Konva scene;\n * pair the result with `SkeletonMapReader` and `settings.lodEnabled`.\n *\n * The output is raw map space (room `y` copied verbatim). Detail rooms are\n * cloned, so the input map data is never mutated.\n */\nexport function buildSkeleton(\n map: MapData.Map,\n envs: MapData.Env[] = [],\n options: BuildSkeletonOptions = {},\n): MapSkeleton {\n const isDetail = options.isDetailRoom ?? hasVisualDetail;\n\n let count = 0;\n for (const area of map) count += area.rooms.length;\n\n const x = new Int32Array(count), y = new Int32Array(count), z = new Int32Array(count);\n const areaCol = new Int32Array(count), env = new Int32Array(count), id = new Int32Array(count);\n const exits = new Int32Array(count * 12).fill(-1);\n const names: string[] = new Array(count);\n const userData: {id: number; data: Record<string, string>}[] = [];\n const detailRooms: MapData.Room[] = [];\n const labels: MapData.Label[] = [];\n const areaNames: Record<number, string> = {};\n\n let i = 0;\n for (const area of map) {\n const areaId = parseInt(area.areaId);\n areaNames[areaId] = area.areaName;\n for (const label of area.labels ?? []) {\n labels.push({...label, areaId: label.areaId ?? areaId});\n }\n for (const room of area.rooms) {\n x[i] = room.x;\n y[i] = room.y;\n z[i] = room.z;\n areaCol[i] = room.area;\n env[i] = room.env;\n id[i] = room.id;\n const base = i * 12;\n for (let d = 0; d < 12; d++) {\n const t = room.exits?.[SKELETON_DIRS[d]];\n if (t !== undefined) exits[base + d] = t;\n }\n names[i] = room.name ?? \"\";\n if (hasKeys(room.userData)) userData.push({id: room.id, data: room.userData});\n if (isDetail(room)) detailRooms.push({...room});\n i++;\n }\n }\n\n const customEnvColors: Record<number, {r: number; g: number; b: number}> = {};\n for (const e of envs) {\n customEnvColors[e.envId] = {r: e.colors[0], g: e.colors[1], b: e.colors[2]};\n }\n\n const areaGridMode: Record<number, boolean> = {};\n for (const a of options.gridAreas ?? []) areaGridMode[a] = true;\n\n return {\n count, x, y, z, area: areaCol, env, id, exits,\n areaNames, areaGridMode, customEnvColors,\n names, userData, detailRooms, labels,\n };\n}\n"],"mappings":";;AAGA,IAAM,KAAW,MACb,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,CAAC,SAAS;AAOnC,SAAgB,EAAgB,GAA6B;AACzD,QAAO,CAAC,CAAC,EAAK,YAAY,EAAQ,EAAK,YAAY,IAAI,EAAQ,EAAK,aAAa,KAC5E,EAAK,OAAO,UAAU,KAAK,KAAK,EAAQ,EAAK,MAAM,KAAK,EAAK,WAAW,UAAU,KAAK;;AAmBhG,SAAgB,EACZ,GACA,IAAsB,EAAE,EACxB,IAAgC,EAAE,EACvB;CACX,IAAM,IAAW,EAAQ,gBAAgB,GAErC,IAAQ;AACZ,MAAK,IAAM,KAAQ,EAAK,MAAS,EAAK,MAAM;CAE5C,IAAM,IAAI,IAAI,WAAW,EAAM,EAAE,IAAI,IAAI,WAAW,EAAM,EAAE,IAAI,IAAI,WAAW,EAAM,EAC/E,IAAU,IAAI,WAAW,EAAM,EAAE,IAAM,IAAI,WAAW,EAAM,EAAE,IAAK,IAAI,WAAW,EAAM,EACxF,IAAQ,IAAI,WAAW,IAAQ,GAAG,CAAC,KAAK,GAAG,EAC3C,IAAsB,MAAM,EAAM,EAClC,IAAyD,EAAE,EAC3D,IAA8B,EAAE,EAChC,IAA0B,EAAE,EAC5B,IAAoC,EAAE,EAExC,IAAI;AACR,MAAK,IAAM,KAAQ,GAAK;EACpB,IAAM,IAAS,SAAS,EAAK,OAAO;AACpC,IAAU,KAAU,EAAK;AACzB,OAAK,IAAM,KAAS,EAAK,UAAU,EAAE,CACjC,GAAO,KAAK;GAAC,GAAG;GAAO,QAAQ,EAAM,UAAU;GAAO,CAAC;AAE3D,OAAK,IAAM,KAAQ,EAAK,OAAO;AAM3B,GALA,EAAE,KAAK,EAAK,GACZ,EAAE,KAAK,EAAK,GACZ,EAAE,KAAK,EAAK,GACZ,EAAQ,KAAK,EAAK,MAClB,EAAI,KAAK,EAAK,KACd,EAAG,KAAK,EAAK;GACb,IAAM,IAAO,IAAI;AACjB,QAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;IACzB,IAAM,IAAI,EAAK,QAAQ,EAAc;AACrC,IAAI,MAAM,KAAA,MAAW,EAAM,IAAO,KAAK;;AAK3C,GAHA,EAAM,KAAK,EAAK,QAAQ,IACpB,EAAQ,EAAK,SAAS,IAAE,EAAS,KAAK;IAAC,IAAI,EAAK;IAAI,MAAM,EAAK;IAAS,CAAC,EACzE,EAAS,EAAK,IAAE,EAAY,KAAK,EAAC,GAAG,GAAK,CAAC,EAC/C;;;CAIR,IAAM,IAAqE,EAAE;AAC7E,MAAK,IAAM,KAAK,EACZ,GAAgB,EAAE,SAAS;EAAC,GAAG,EAAE,OAAO;EAAI,GAAG,EAAE,OAAO;EAAI,GAAG,EAAE,OAAO;EAAG;CAG/E,IAAM,IAAwC,EAAE;AAChD,MAAK,IAAM,KAAK,EAAQ,aAAa,EAAE,CAAE,GAAa,KAAK;AAE3D,QAAO;EACH;EAAO;EAAG;EAAG;EAAG,MAAM;EAAS;EAAK;EAAI;EACxC;EAAW;EAAc;EACzB;EAAO;EAAU;EAAa;EACjC"}
@@ -0,0 +1,19 @@
1
+ import { RendererRoom, RendererLabel, MudletRoom } from 'mudlet-map-binary-reader';
2
+ /**
3
+ * RendererRoom -> MapData.Room: fills the fields RendererRoom leaves optional
4
+ * (`env`/`roomChar` are only set upstream when truthy; `hash` is resolved
5
+ * separately from the header's `mpRoomDbHashToRoomId` index since streamRooms
6
+ * doesn't hydrate it per-room). `customLines` colour drops `alpha` upstream —
7
+ * SpecialExitStyle only ever reads r/g/b, so 255 (opaque) is a safe filler,
8
+ * never a real behavioural difference.
9
+ */
10
+ export declare function toMapRoom(r: RendererRoom, areaId: string, hash?: string): MapData.Room;
11
+ /** RendererLabel -> MapData.Label; areaId/labelId are stamped from the enclosing header.labels[areaId] key when omitted. */
12
+ export declare function toMapLabel(l: RendererLabel, fallbackAreaId: number): MapData.Label;
13
+ /**
14
+ * A room carries non-default metadata the skeleton's compact columns can't
15
+ * encode (or that a plain visual-detail check would silently drop —
16
+ * `weight`, `exitWeights`) and must be promoted to a fully materialised
17
+ * MapData.Room.
18
+ */
19
+ export declare function hasExtraDetail(room: MudletRoom): boolean;
@@ -1,2 +1,4 @@
1
1
  export { default as BinaryMapReader } from './BinaryMapReader';
2
2
  export type { MudletMap, MudletArea, MudletRoom, MudletColor, MudletLabel } from 'mudlet-map-binary-reader';
3
+ export { parseMudletMap, readerFromLoadedMap, loadMudletMap } from './loadMudletMap';
4
+ export type { LoadMode, LoadMudletMapOptions, LoadedMudletMap } from './loadMudletMap';
@@ -0,0 +1,49 @@
1
+ import { IMapReader } from '../reader/MapReader';
2
+ import { MapSkeleton } from '../bigmap/Skeleton';
3
+ export type LoadMode = "auto" | "plain" | "streaming";
4
+ export interface LoadMudletMapOptions {
5
+ /** 'auto' (default) picks 'plain' or 'streaming' from the header's total room count vs. {@link threshold}. */
6
+ mode?: LoadMode;
7
+ /**
8
+ * Total-room-count cutoff for 'auto' (default 50,000). Below it: a full
9
+ * parse (every field, no skeleton overhead — a `MapSkeleton` has no
10
+ * upside for a map small enough to hold as an object graph). Above it:
11
+ * stream straight into a `MapSkeleton` — the full parsed map and the
12
+ * skeleton are never both resident in memory at once.
13
+ */
14
+ threshold?: number;
15
+ /** Invoked periodically (every 200,000 rooms) — the streaming path only; the full parse has no per-room hook. */
16
+ onProgress?: (roomsRead: number, total: number) => void;
17
+ }
18
+ /**
19
+ * Portable, structured-clone/transfer-safe parse result — no live reader yet
20
+ * (see {@link readerFromLoadedMap}). Splitting parse from materialisation
21
+ * lets a Web Worker run the (potentially expensive) parse and hand back only
22
+ * the result; the skeleton kind's typed arrays should be passed as
23
+ * transferables (`[skeleton.x.buffer, skeleton.y.buffer, ...]`).
24
+ */
25
+ export type LoadedMudletMap = {
26
+ kind: "plain";
27
+ map: MapData.Map;
28
+ envs: MapData.Env[];
29
+ } | {
30
+ kind: "skeleton";
31
+ skeleton: MapSkeleton;
32
+ };
33
+ /**
34
+ * Parse a Mudlet binary `.dat` buffer, picking the loading strategy from its
35
+ * total room count (see {@link LoadMudletMapOptions}). Returns portable data
36
+ * only — call {@link readerFromLoadedMap} to get a live `IMapReader`, or use
37
+ * {@link loadMudletMap} to do both in one call.
38
+ */
39
+ export declare function parseMudletMap(bytes: Uint8Array, options?: LoadMudletMapOptions): LoadedMudletMap;
40
+ /** Build the live {@link IMapReader} for data produced by {@link parseMudletMap}. */
41
+ export declare function readerFromLoadedMap(loaded: LoadedMudletMap): IMapReader;
42
+ /**
43
+ * Convenience: parse and materialise in one call, on the current thread.
44
+ * `mudlet-map-binary-reader` is a peer dependency; consumers who don't call
45
+ * this never pay the bundle cost. For very large maps, prefer running
46
+ * {@link parseMudletMap} in a Web Worker and calling {@link readerFromLoadedMap}
47
+ * with its result on the main thread.
48
+ */
49
+ export declare function loadMudletMap(bytes: Uint8Array, options?: LoadMudletMapOptions): IMapReader;
package/dist/binary.mjs CHANGED
@@ -1,13 +1,83 @@
1
- import { t as e } from "./MapReader-Brzg-X7T.js";
2
- import { readMapFromBuffer as t, readerExport as n } from "mudlet-map-binary-reader";
1
+ import { t as e } from "./MapReader-BeVNpm6y.js";
2
+ import { t } from "./SkeletonMapReader-B6PzEWGP.js";
3
+ import { convertLabel as n, convertRoom as r, readMapFromBuffer as i, readerExport as a, streamRooms as o } from "mudlet-map-binary-reader";
4
+ //#region src/binary/convert.ts
5
+ function s(e, t, n = "") {
6
+ let r = {};
7
+ for (let t in e.customLines) {
8
+ let n = e.customLines[t];
9
+ r[t] = {
10
+ points: n.points,
11
+ attributes: {
12
+ color: {
13
+ alpha: 255,
14
+ r: n.attributes.color.r,
15
+ g: n.attributes.color.g,
16
+ b: n.attributes.color.b
17
+ },
18
+ style: n.attributes.style,
19
+ arrow: n.attributes.arrow
20
+ }
21
+ };
22
+ }
23
+ return {
24
+ id: e.id,
25
+ area: e.area,
26
+ x: e.x,
27
+ y: e.y,
28
+ z: e.z,
29
+ areaId: t,
30
+ weight: e.weight,
31
+ roomChar: e.roomChar ?? "",
32
+ name: e.name,
33
+ userData: e.userData,
34
+ customLines: r,
35
+ stubs: e.stubs,
36
+ hash: e.hash ?? n,
37
+ env: e.env ?? 0,
38
+ exits: e.exits,
39
+ doors: e.doors,
40
+ specialExits: e.specialExits,
41
+ exitLocks: e.exitLocks,
42
+ exitWeights: e.exitWeights,
43
+ mSpecialExitLocks: e.mSpecialExitLocks
44
+ };
45
+ }
46
+ function c(e, t) {
47
+ return {
48
+ labelId: e.labelId ?? e.id,
49
+ areaId: e.areaId ?? t,
50
+ pixMap: e.pixMap || void 0,
51
+ X: e.X,
52
+ Y: e.Y,
53
+ Z: e.Z,
54
+ Width: e.Width,
55
+ Height: e.Height,
56
+ Text: e.Text,
57
+ FgColor: e.FgColor,
58
+ BgColor: e.BgColor,
59
+ noScaling: e.noScaling,
60
+ showOnTop: e.showOnTop
61
+ };
62
+ }
63
+ var l = (e) => !!e && Object.keys(e).length > 0;
64
+ function u(e) {
65
+ return !!e.symbol || l(e.customLines) || l(e.mSpecialExits) || (e.stubs?.length ?? 0) > 0 || l(e.doors) || (e.exitLocks?.length ?? 0) > 0 || e.weight !== 1 || l(e.exitWeights);
66
+ }
67
+ //#endregion
3
68
  //#region src/binary/BinaryMapReader.ts
4
- var r = class r {
69
+ var d = class t {
5
70
  constructor(t) {
6
- let { mapData: r, colors: i } = n(t);
7
- this.reader = new e(r, i);
71
+ let { mapData: n, colors: r } = a(t);
72
+ this.reader = new e(n.map((e) => ({
73
+ areaName: e.areaName,
74
+ areaId: e.areaId,
75
+ rooms: e.rooms.map((t) => s(t, e.areaId, t.hash)),
76
+ labels: e.labels.map((t) => c(t, parseInt(e.areaId)))
77
+ })), r);
8
78
  }
9
79
  static fromBuffer(e) {
10
- return new r(t(e));
80
+ return new t(i(e));
11
81
  }
12
82
  getArea(e) {
13
83
  return this.reader.getArea(e);
@@ -27,8 +97,124 @@ var r = class r {
27
97
  getSymbolColor(e, t) {
28
98
  return this.reader.getSymbolColor(e, t);
29
99
  }
30
- };
100
+ }, f = [
101
+ "north",
102
+ "northeast",
103
+ "east",
104
+ "southeast",
105
+ "south",
106
+ "southwest",
107
+ "west",
108
+ "northwest",
109
+ "up",
110
+ "down",
111
+ "in",
112
+ "out"
113
+ ], p = class extends Error {};
114
+ function m(e) {
115
+ let t;
116
+ try {
117
+ o(e, () => {
118
+ throw new p();
119
+ }, (e) => {
120
+ let n = 0;
121
+ for (let t in e.areas ?? {}) n += e.areas[t].rooms.length;
122
+ throw t = {
123
+ header: e,
124
+ total: n
125
+ }, new p();
126
+ });
127
+ } catch (e) {
128
+ if (!(e instanceof p)) throw e;
129
+ }
130
+ if (!t) throw Error("failed to decode map header");
131
+ return t;
132
+ }
133
+ function h(e) {
134
+ let t = {};
135
+ for (let n in e.areas ?? {}) t[n] = e.areas[n].gridMode;
136
+ return t;
137
+ }
138
+ var g = (e) => !!e && Object.keys(e).length > 0;
139
+ function _(e) {
140
+ let { mapData: t, colors: n } = a(i(e));
141
+ return {
142
+ kind: "plain",
143
+ map: t.map((e) => ({
144
+ areaName: e.areaName,
145
+ areaId: e.areaId,
146
+ rooms: e.rooms.map((t) => s(t, e.areaId, t.hash)),
147
+ labels: e.labels.map((t) => c(t, parseInt(e.areaId)))
148
+ })),
149
+ envs: n
150
+ };
151
+ }
152
+ function v(e, t, i, a) {
153
+ let l = new Int32Array(i), d = new Int32Array(i), p = new Int32Array(i), m = new Int32Array(i), _ = new Int32Array(i), v = new Int32Array(i), y = new Int32Array(i * 12).fill(-1), b = Array(i), x = [], S = [], C = {};
154
+ for (let e in t.mpRoomDbHashToRoomId) C[t.mpRoomDbHashToRoomId[e]] = e;
155
+ let w = () => typeof performance < "u" ? performance.now() : Date.now(), T = w(), E = 0;
156
+ o(e, (e, t) => {
157
+ l[E] = t.x, d[E] = t.y, p[E] = t.z, m[E] = t.area, _[E] = t.environment, v[E] = e;
158
+ let n = E * 12;
159
+ for (let e = 0; e < 12; e++) y[n + e] = t[f[e]];
160
+ if (b[E] = t.name ?? "", g(t.userData) && x.push({
161
+ id: e,
162
+ data: t.userData
163
+ }), u(t)) {
164
+ let n = r(e, t, C[e]);
165
+ S.push(s(n, String(t.area), C[e]));
166
+ }
167
+ if (E++, a) {
168
+ let e = w();
169
+ e - T >= 80 && (T = e, a(E, i));
170
+ }
171
+ }), a?.(E, i);
172
+ let D = {};
173
+ for (let e in t.mCustomEnvColors ?? {}) {
174
+ let n = t.mCustomEnvColors[e];
175
+ D[e] = {
176
+ r: n.r,
177
+ g: n.g,
178
+ b: n.b
179
+ };
180
+ }
181
+ let O = [];
182
+ for (let e in t.labels ?? {}) {
183
+ let r = Number(e);
184
+ for (let e of t.labels[r]) O.push(c(n(e), r));
185
+ }
186
+ return {
187
+ kind: "skeleton",
188
+ skeleton: {
189
+ count: E,
190
+ x: l,
191
+ y: d,
192
+ z: p,
193
+ area: m,
194
+ env: _,
195
+ id: v,
196
+ exits: y,
197
+ areaNames: t.areaNames ?? {},
198
+ areaGridMode: h(t),
199
+ customEnvColors: D,
200
+ names: b,
201
+ userData: x,
202
+ detailRooms: S,
203
+ labels: O
204
+ }
205
+ };
206
+ }
207
+ function y(e, t = {}) {
208
+ let { mode: n = "auto", threshold: r = 5e4, onProgress: i } = t, { header: a, total: o } = m(e);
209
+ return (n === "auto" ? o > r ? "streaming" : "plain" : n) === "plain" ? _(e) : v(e, a, o, i);
210
+ }
211
+ function b(n) {
212
+ return n.kind === "plain" ? new e(n.map, n.envs) : new t(n.skeleton);
213
+ }
214
+ function x(e, t) {
215
+ return b(y(e, t));
216
+ }
31
217
  //#endregion
32
- export { r as BinaryMapReader };
218
+ export { d as BinaryMapReader, x as loadMudletMap, y as parseMudletMap, b as readerFromLoadedMap };
33
219
 
34
220
  //# sourceMappingURL=binary.mjs.map