import type { MudletMap, MudletRoom, MudletColor } from '../../mapIO'; import type { LabelSnapshot } from '../types'; import { type Direction, type LabelFont } from '../types'; import { PlaneRoomIndex, type Bounds } from './PlaneRoomIndex'; /** Editor-side Exit — mirrors the renderer's Exit type. */ export interface EditorExit { a: number; b: number; aDir?: Direction; bDir?: Direction; zIndex: number[]; } /** Live view over a raw MudletRoom. Y is flipped for render-space consumption. */ export interface LiveRoom { readonly id: number; x: number; y: number; z: number; readonly area: number; readonly name: string; readonly weight: number; readonly env: number | undefined; readonly roomChar: string | undefined; readonly userData: Record; readonly doors: Record; readonly isLocked: boolean; readonly exitLocks: number[]; readonly stubs: number[]; readonly exitWeights: Record; readonly mSpecialExitLocks: number[]; readonly exits: Record; readonly specialExits: Record; readonly customLines: Record; readonly hash?: string; /** Backing raw room (for direct mutation). */ readonly __raw: MudletRoom; } /** * The window the renderer last pushed through `EditorMapReader.setViewport`, * shared by reference with every area and plane. `revision` changes whenever * `bounds` does, so derived results can be cached against it. */ export type ViewportWindow = { bounds: Bounds; revision: number; }; export declare class EditorPlane { private rooms; private labels; private readonly viewport; private index; /** Narrowed room list, cached against (viewport, index) revisions. */ private visible; private visibleKey; constructor(rooms: LiveRoom[], labels: any[], viewport: ViewportWindow); /** * Rooms inside the current viewport window — this is what the renderer builds * a scene from, so it is what bounds the cost of a rebuild. Editor code that * reasons about the level as a whole (marquee, bounds, counts) wants * {@link getAllRooms} instead. */ getRooms(): LiveRoom[]; /** Every room on the plane, viewport or not. */ getAllRooms(): LiveRoom[]; getIndex(): PlaneRoomIndex; getLabels(): any[]; /** Full-plane extent (NOT the viewport) so fitArea keeps framing the whole level. */ getBounds(): { minX: number; maxX: number; minY: number; maxY: number; }; setRooms(rooms: LiveRoom[]): void; setLabels(labels: any[]): void; } export declare class EditorArea { private readonly areaId; private readonly areaName; private rooms; private labels; private readonly viewport; private planes; /** Own rooms by id — the area-scoped resolver `buildExitsFor` pairs halves through. */ private byId; private version; private suspendCount; private pendingPlanes; /** Exits of one plane, cached against (area version, viewport, plane index). */ private exitCache; constructor(areaId: number, areaName: string, rooms: LiveRoom[], labels: any[], viewport: ViewportWindow); getAreaId(): number; getAreaName(): string; /** * Content version. Includes the viewport revision because the plane content the * renderer sees is viewport-dependent — the `ViewportDataSource` contract * requires areas to look stale when the window moves. */ getVersion(): number; markDirty(): void; getPlane(z: number): EditorPlane; getPlanes(): EditorPlane[]; getZLevels(): number[]; getRooms(): LiveRoom[]; getFullBounds(): { minX: number; maxX: number; minY: number; maxY: number; }; /** * Exits to draw on level `zIndex`, paired over the rooms the plane currently * materialises — so this costs O(viewport), not O(area). Pairing happens per * build instead of being cached area-wide, which is also why nothing has to * invalidate exits after a room changes z-level or area: the zIndex snapshot * that used to go stale is now recomputed every time. */ getLinkExits(zIndex: number): EditorExit[]; setLabels(labels: any[]): void; addRoomLive(room: LiveRoom): void; addRoomsLive(newRooms: LiveRoom[]): void; removeRoomById(id: number): void; removeRoomsById(ids: Set): void; renameRoomId(fromId: number, toId: number): void; /** * Re-file a room in its plane's spatial index after its coordinates changed. * A move within one plane needs nothing else: exits are paired per build and * the room objects are live views over the raw map. */ roomMoved(room: LiveRoom): void; /** * Defer `rebuildPlanes` until the matching `resumeRebuilds`. Nestable. While * suspended the rooms/labels arrays still mutate immediately — only the * derived planes are stale, and nothing reads those between a suspend and its * resume. */ suspendRebuilds(): void; resumeRebuilds(): void; rebuildPlanes(): void; } /** * Editor-side MapReader. Drop-in replacement for `mudlet-map-renderer`'s MapReader * — exposes the same read surface the renderer needs, but: * - Rooms are live getters over raw `MudletRoom` objects (no clone, no snapshot). * - Y is flipped via a getter so mutations to raw.y propagate with correct sign. * - Mutation methods are public: `moveRoom`, `setExit`, `addRoom`, `removeRoom`, * `setRoomField`. Each updates the raw map and invalidates the relevant Area * cache, so `renderer.refresh()` picks up the change. * - It is a `ViewportDataSource`: planes hand the renderer only the rooms inside * the window pushed by `setViewport`, so a scene rebuild — one per pointer * event while dragging — costs O(viewport) rather than O(level). The renderer * detects this by duck-typing `viewportAware` and rebuilds on pan when the * camera leaves the padded window it last applied. */ export declare class EditorMapReader { private readonly raw; readonly viewportAware: true; private readonly rooms; private readonly areas; private readonly colors; /** Shared by reference with every area/plane; `revision` bumps on each change. */ private readonly viewport; constructor(raw: MudletMap); /** Narrow what planes materialise. Bounds are render space, same as the renderer reports. */ setViewport(bounds: Bounds): void; getViewport(): Bounds; /** Rooms on the whole (area, z) plane — the input to the renderer's LOD tier decision. */ getPlaneRoomCount(areaId: number, z: number): number; estimateVisibleCount(areaId: number, z: number, bounds: Bounds): number; forEachInBounds(areaId: number, z: number, bounds: Bounds, fn: (x: number, y: number, envId: number) => void): void; /** * Suspend per-mutation plane/exit rebuilds across every area until the * matching `endBatch`. Use around a run of mutations that would otherwise pay * a full rebuild each — a 40-room merge goes from ~80 rebuilds to one per * area. Nestable; always pair in a `try/finally`. */ beginBatch(): void; endBatch(): void; private toRendererLabel; private syncRendererLabels; getRoom(id: number): LiveRoom | undefined; getArea(areaId: number): EditorArea | undefined; getAreas(): EditorArea[]; getRooms(): LiveRoom[]; getExplorationArea(): undefined; decorateWithExploration(): Set | undefined; getVisitedRooms(): Set | undefined; clearExplorationDecoration(): void; isExplorationEnabled(): boolean; setVisitedRooms(): Set; addVisitedRoom(): boolean; addVisitedRooms(): number; hasVisitedRoom(): boolean; getColorValue(envId: number): string; getSymbolColor(envId: number, opacity?: number): string; /** Move a room. Coordinates are in RENDER space (same as what culling returns / cursor maps to). */ moveRoom(id: number, x: number, y: number, z: number): void; /** Set a cardinal exit. `toId < 0` removes it. */ setExit(fromId: number, dir: Direction, toId: number): void; setRoomField(id: number, field: 'name' | 'environment' | 'weight' | 'symbol', value: string | number): void; setRoomLock(id: number, lock: boolean): void; setUserDataEntry(id: number, key: string, value: string | null): void; renameRoomId(fromId: number, toId: number): void; /** Add a raw room (expected `raw.rooms[id]` already set or not, we set it). */ addRoom(id: number, rawRoom: MudletRoom): void; /** Bulk-add many rooms. Does one rebuildPlanes per affected area. */ addRooms(rooms: Array<{ id: number; room: MudletRoom; }>): void; setSpecialExit(roomId: number, name: string, toId: number): void; removeSpecialExit(roomId: number, name: string): void; setDoor(roomId: number, dir: Direction, value: number): void; setSpecialExitDoor(roomId: number, name: string, value: number): void; setExitWeight(roomId: number, dir: Direction, value: number): void; setSpecialExitWeight(roomId: number, name: string, value: number): void; setExitLock(roomId: number, dir: Direction, lock: boolean): void; setStub(roomId: number, dir: Direction, stub: boolean): void; /** Move a single custom line waypoint. renderX/renderY are render-space (y-down). */ setCustomLinePoint(roomId: number, exitName: string, index: number, renderX: number, renderY: number): void; setCustomLine(roomId: number, exitName: string, points: [number, number][], color: MudletColor, style: number, arrow: boolean): void; removeCustomLine(roomId: number, exitName: string): void; addArea(id: number, name: string): void; removeArea(id: number): void; moveRoomsToArea(roomIds: number[], fromAreaId: number, toAreaId: number): void; renameArea(id: number, name: string): void; setCustomEnvColor(envId: number, color: MudletColor | null): void; getLabelSnapshot(areaId: number, labelId: number): LabelSnapshot | null; addLabel(areaId: number, snapshot: LabelSnapshot): void; removeLabel(areaId: number, labelId: number): void; /** Move a label. renderX/renderY are render-space (y-down); stored as raw Mudlet (y-up). */ moveLabel(areaId: number, labelId: number, renderX: number, renderY: number): void; setLabelText(areaId: number, labelId: number, text: string): void; setLabelSize(areaId: number, labelId: number, width: number, height: number): void; setLabelPixmap(areaId: number, labelId: number, dataUrl: string): void; setLabelImageSrc(areaId: number, labelId: number, imageSrc: string | undefined): void; setLabelFont(areaId: number, labelId: number, font: LabelFont): void; setLabelOutlineColor(areaId: number, labelId: number, color: import('../../mapIO').MudletColor | undefined): void; setLabelStyle(areaId: number, labelId: number, styleId: string | undefined): void; setLabelAlign(areaId: number, labelId: number, align: import('../types').LabelTextAlign | undefined): void; setLabelNoScaling(areaId: number, labelId: number, noScaling: boolean): void; setLabelShowOnTop(areaId: number, labelId: number, showOnTop: boolean): void; setLabelColors(areaId: number, labelId: number, fg: MudletColor, bg: MudletColor): void; getAllEnvColors(): { envId: number; rgbValue: string; }[]; /** * Bulk-delete an entire area and its rooms. Assumes the caller has already * severed cross-area incoming exits on raw rooms (same pattern as `deleteRoom` * using `neighborEdits`); `affectedOtherAreaIds` are marked stale so they * re-pair their exits on the next build. */ removeAreaWithRooms(areaId: number, roomIds: number[], affectedOtherAreaIds: number[]): void; /** Restore an area + its rooms (for `deleteAreaWithRooms` undo). */ restoreAreaWithRooms(areaId: number, areaName: string, rooms: Array<{ id: number; room: MudletRoom; }>, affectedOtherAreaIds: number[]): void; removeRoom(id: number): void; /** Bulk-remove many rooms. Caller must have already severed neighbor exits in raw map. * Does one rebuildPlanes per affected area instead of one per room. */ removeRooms(ids: number[]): void; }