mudlet-map-binary-reader
Version:
Reads and writes Mudlet's map binary file (v20), with read-only support for older formats (v16-v19). Can output .js files needed for Mudlet Map Reader.
2,804 lines • 69.7 kB
JavaScript
import { buffer } from "qtdatastream-web";
import { QBool, QClass, QDouble, QInt, QString, QUInt, QUserType, Types, qtype } from "qtdatastream-web/types";
import { concat, fromInt8, fromUint16BE } from "qtdatastream-web/bytes";
//#region src/models/qstream-types.ts
var QEnum = class extends QClass {
static read(buffer) {
return buffer.readInt8();
}
toBuffer() {
return fromInt8(this.__obj);
}
};
var QUint16 = class extends QClass {
static read(buffer) {
return buffer.readUInt16BE();
}
toBuffer() {
return fromUint16BE(this.__obj);
}
};
/**
* Supports RGB only
*/
var QColor = class extends QClass {
static read(buffer) {
return {
spec: QEnum.read(buffer),
alpha: QUint16.read(buffer) >> 8,
r: QUint16.read(buffer) >> 8,
g: QUint16.read(buffer) >> 8,
b: QUint16.read(buffer) >> 8,
pad: QUint16.read(buffer) >> 8
};
}
toBuffer() {
const color = this.__obj;
const bufs = [];
bufs.push(QEnum.from(color.spec).toBuffer(false));
bufs.push(QUint16.from(color.alpha * 257).toBuffer(false));
bufs.push(QUint16.from(color.r * 257).toBuffer(false));
bufs.push(QUint16.from(color.g * 257).toBuffer(false));
bufs.push(QUint16.from(color.b * 257).toBuffer(false));
bufs.push(QUint16.from((color.pad ?? 0) * 257).toBuffer(false));
return concat(bufs);
}
};
var QString$1 = class extends QString {
toBuffer() {
if (this.__obj === "") return QUInt.from(4294967295).toBuffer();
return super.toBuffer();
}
};
var QFont = class extends QClass {
static read(buffer) {
const family = QString$1.read(buffer);
const style = QString$1.read(buffer);
const pointSize = QDouble.read(buffer);
const pixelSize = QInt.read(buffer);
const styleHint = QEnum.read(buffer);
const styleStrategy = QUint16.read(buffer);
buffer.readInt8();
const weight = buffer.readInt8() >>> 0;
const fontBits = buffer.readInt8() >>> 0;
const stretch = buffer.readUInt16BE();
const extendedFontBits = buffer.readInt8() >>> 0;
return {
family,
style,
pointSize,
pixelSize,
styleHint,
styleStrategy,
weight,
fontBits,
stretch,
extendedFontBits,
letterSpacing: QInt.read(buffer),
wordSpacing: QInt.read(buffer),
hintingPreference: buffer.readInt8() >>> 0,
capital: buffer.readInt8() >>> 0,
styleSetting: (fontBits & 1) !== 0,
underline: (fontBits & 2) !== 0,
overline: (fontBits & 64) !== 0,
strikeOut: (fontBits & 4) !== 0,
fixedPitch: (fontBits & 8) !== 0,
kerning: (fontBits & 16) !== 0,
styleOblique: (fontBits & 128) !== 0,
ignorePitch: (extendedFontBits & 1) !== 0,
letterSpacingIsAbsolute: (extendedFontBits & 2) !== 0
};
}
toBuffer() {
const f = this.__obj;
return concat([
QString$1.from(f.family).toBuffer(),
QString$1.from(f.style).toBuffer(),
QDouble.from(f.pointSize).toBuffer(),
QInt.from(f.pixelSize).toBuffer(),
QEnum.from(f.styleHint).toBuffer(),
QUint16.from(f.styleStrategy).toBuffer(),
new Uint8Array(1),
fromInt8(f.weight),
fromInt8(f.fontBits),
fromUint16BE(f.stretch),
fromInt8(f.extendedFontBits),
QInt.from(f.letterSpacing).toBuffer(),
QInt.from(f.wordSpacing).toBuffer(),
fromInt8(f.hintingPreference),
fromInt8(f.capital)
]);
}
};
var QPoint = class extends QClass {
static read(buffer) {
return [QDouble.read(buffer), QDouble.read(buffer)];
}
toBuffer() {
const [x, y] = this.__obj;
return concat([QDouble.from(x).toBuffer(), QDouble.from(y).toBuffer()]);
}
};
var QVector = class extends QClass {
static read(buffer) {
return [
QDouble.read(buffer),
QDouble.read(buffer),
QDouble.read(buffer)
];
}
toBuffer() {
const [x, y, z] = this.__obj;
return concat([
QDouble.from(x).toBuffer(),
QDouble.from(y).toBuffer(),
QDouble.from(z).toBuffer()
]);
}
};
/**
* Supports .pngs
*/
var QPixMap = class extends QClass {
static read(buffer) {
QUInt.read(buffer);
const start = buffer.read_offset;
if (buffer.readUInt32BE() !== 2303741511) {
buffer.read_offset -= 4;
return "";
}
while (buffer.readUInt32BE() !== 1229278788) buffer.read_offset -= 3;
const end = buffer.read_offset;
buffer.read_offset = start;
const size = end - start;
return buffer.slice(size + 4);
}
toBuffer() {
const data = this.__obj;
return concat([QUInt.from(1).toBuffer(), data !== "" ? data : new Uint8Array(0)]);
}
};
//#endregion
//#region src/models/base-types.ts
/**
* Qt type IDs used across every map version. These are standard Qt
* QMetaType ids (QPoint, QFont, QColor, …) and don't change between Mudlet
* map versions, so they're registered once and shared by all version models.
*/
const Types$1 = {
...Types,
POINT: 25,
FONT: 64,
PIXMAP: 65,
COLOR: 67,
VECTOR: 84
};
let registered = false;
/** Register the version-independent Qt types. Idempotent. */
function registerBaseTypes() {
if (registered) return;
registered = true;
qtype(Types$1.POINT)(QPoint);
qtype(Types$1.FONT)(QFont);
qtype(Types$1.PIXMAP)(QPixMap);
qtype(Types$1.COLOR)(QColor);
qtype(Types$1.VECTOR)(QVector);
}
//#endregion
//#region src/models/mudlet-types.ts
/** Build the per-area label-list container for a given `MudletLabel` type name. */
function createMudletLabels(labelType) {
return class MudletLabels extends QClass {
static read(buffer) {
const areasWithLabelsTotal = QInt.read(buffer);
const labels = {};
for (let index = 0; index < areasWithLabelsTotal; index++) {
const totalLabels = QInt.read(buffer);
const areaId = QInt.read(buffer);
labels[areaId] = [];
for (let i = 0; i < totalLabels; i++) labels[areaId].push(QUserType.get(labelType).read(buffer));
}
return labels;
}
toBuffer() {
const obj = this.__obj;
const buffers = [];
buffers.push(QInt.from(Object.keys(obj).length).toBuffer());
for (const key of Object.keys(obj)) {
const areaId = parseInt(key);
buffers.push(QInt.from(obj[areaId].length).toBuffer());
buffers.push(QInt.from(areaId).toBuffer());
for (const label of obj[areaId]) buffers.push(QUserType.get(labelType).from(label).toBuffer(true));
}
return concat(buffers);
}
};
}
/** Build the area-table container for a given `MudletArea` type name. */
function createMudletAreas(areaType) {
return class MudletAreas extends QClass {
static read(buffer) {
const areas = {};
const areaSize = QInt.read(buffer);
for (let index = 0; index < areaSize; index++) {
const id = QInt.read(buffer);
areas[id] = QUserType.get(areaType).read(buffer);
}
return areas;
}
toBuffer() {
const obj = this.__obj;
const buffers = [];
buffers.push(QInt.from(Object.keys(obj).length).toBuffer());
for (const [key, area] of Object.entries(obj).sort((a, b) => parseInt(a[0]) - parseInt(b[0]))) {
buffers.push(QInt.from(parseInt(key)).toBuffer());
buffers.push(QUserType.get(areaType).from(area).toBuffer(true));
}
return concat(buffers);
}
};
}
/** Build the room-table container for a given `MudletRoom` type name. */
function createMudletRooms(roomType) {
return class MudletRooms extends QClass {
static read(buffer) {
const rooms = {};
while (buffer.buffer.length > buffer.read_offset) {
const id = QInt.read(buffer);
rooms[id] = QUserType.get(roomType).read(buffer);
}
return rooms;
}
toBuffer() {
const obj = this.__obj;
const buffers = [];
for (const [key, room] of Object.entries(obj).reverse()) {
buffers.push(QInt.from(parseInt(key)).toBuffer());
buffers.push(QUserType.get(roomType).from(room).toBuffer(true));
}
return concat(buffers);
}
};
}
//#endregion
//#region src/models/qstream-containers.ts
let customCounter = 1e3;
const customMapCache = {};
const customMultiMapCache = {};
const customArrayCache = {};
const customPairCache = {};
function resolveQType(typeOrClass) {
if (typeof typeOrClass === "number" || !typeOrClass.qtype) return QClass.types.get(typeOrClass);
return typeOrClass;
}
function mudletSorter(a, b) {
if (parseInt(a[0]) === -1) return -1;
if (parseInt(b[0]) === -1) return 1;
return parseInt(a[0]) - parseInt(b[0]);
}
function createTypedMultiMap(keyClass, valueClass) {
return class QTypedMultiMap extends QClass {
static read(buffer) {
const map = {};
const count = QUInt.read(buffer);
for (let index = 0; index < count; index++) {
const key = keyClass.read(buffer);
const value = valueClass.read(buffer);
if (map[key] === void 0) map[key] = [];
map[key].push(value);
}
return map;
}
toBuffer() {
const bufs = [];
const obj = this.__obj;
if (obj instanceof Map) {
bufs.push(QUInt.from(obj.size).toBuffer());
for (const [key, value] of obj) {
bufs.push(keyClass.from(key).toBuffer());
bufs.push(valueClass.from(value).toBuffer());
}
} else {
let counter = 0;
for (const [key, value] of Object.entries(obj).reverse()) for (const item of value) {
counter++;
bufs.push(keyClass.from(key).toBuffer());
bufs.push(valueClass.from(item).toBuffer());
}
bufs.unshift(QUInt.from(counter).toBuffer());
}
return concat(bufs);
}
};
}
function createTypedMap(keyClass, valueClass) {
return class QTypedMap extends QClass {
static read(buffer) {
const map = {};
const count = QUInt.read(buffer);
for (let index = 0; index < count; index++) {
const key = keyClass.read(buffer);
map[key] = valueClass.read(buffer);
}
return map;
}
toBuffer() {
const bufs = [];
const obj = this.__obj;
if (obj instanceof Map) {
bufs.push(QUInt.from(obj.size).toBuffer());
for (const [key, value] of obj) {
bufs.push(keyClass.from(key).toBuffer());
bufs.push(valueClass.from(value).toBuffer());
}
} else {
const entries = Object.entries(obj);
bufs.push(QUInt.from(entries.length).toBuffer());
for (const [key, value] of entries.sort(mudletSorter)) {
bufs.push(keyClass.from(key).toBuffer());
bufs.push(valueClass.from(value).toBuffer());
}
}
return concat(bufs);
}
};
}
function createTypedList(valueClass) {
return class QTypedList extends QClass {
static read(buffer) {
const list = [];
const count = QUInt.read(buffer);
for (let index = 0; index < count; index++) list.push(valueClass.read(buffer));
return list;
}
toBuffer() {
const bufs = [];
const arr = this.__obj;
bufs.push(QUInt.from(arr.length).toBuffer());
for (const el of arr) bufs.push(valueClass.from(el).toBuffer());
return concat(bufs);
}
};
}
function createTypedPair(first, second) {
return class QTypedPair extends QClass {
static read(buffer) {
return [first.read(buffer), second.read(buffer)];
}
toBuffer() {
const pair = this.__obj;
return concat([first.from(pair[0]).toBuffer(), second.from(pair[1]).toBuffer()]);
}
};
}
function QMultiMap(keyClass, valueClass) {
const resolvedKey = resolveQType(keyClass);
const resolvedValue = resolveQType(valueClass);
const keyStr = String(resolvedKey);
const valStr = String(resolvedValue);
if (!customMultiMapCache[keyStr]) customMultiMapCache[keyStr] = {};
if (!customMultiMapCache[keyStr][valStr]) {
const clazz = createTypedMultiMap(resolvedKey, resolvedValue);
const counter = customCounter++;
qtype(counter)(clazz);
customMultiMapCache[keyStr][valStr] = counter;
}
return customMultiMapCache[keyStr][valStr];
}
function QMap(keyClass, valueClass, _reversed) {
const resolvedKey = resolveQType(keyClass);
const resolvedValue = resolveQType(valueClass);
const keyStr = String(resolvedKey);
const valStr = String(resolvedValue);
if (!customMapCache[keyStr]) customMapCache[keyStr] = {};
if (!customMapCache[keyStr][valStr]) {
const clazz = createTypedMap(resolvedKey, resolvedValue);
const counter = customCounter++;
qtype(counter)(clazz);
customMapCache[keyStr][valStr] = counter;
}
return customMapCache[keyStr][valStr];
}
function QList(valueClass) {
const cacheKey = String(valueClass);
if (!customArrayCache[cacheKey]) {
const clazz = createTypedList(valueClass);
const counter = customCounter++;
qtype(counter)(clazz);
customArrayCache[cacheKey] = counter;
}
return customArrayCache[cacheKey];
}
function QPair(first, second) {
const key = `${first.name}#${second.name}`;
if (!customPairCache[key]) {
const clazz = createTypedPair(first, second);
const counter = customCounter++;
qtype(counter)(clazz);
customPairCache[key] = counter;
}
return customPairCache[key];
}
//#endregion
//#region src/models/model-registry.ts
const models = /* @__PURE__ */ new Map();
/** Register the model for a format version. A later call for the same version wins. */
function registerMapModel(model) {
models.set(model.version, model);
}
/** Look up the model for a format version, or `undefined` if unsupported. */
function getMapModel(version) {
return models.get(version);
}
/** Every format version that currently has a registered model, ascending. */
function getSupportedVersions() {
return [...models.keys()].sort((a, b) => a - b);
}
//#endregion
//#region src/models/legacy.ts
registerBaseTypes();
const PEN_STYLE = {
"dot line": 3,
"dash line": 2,
"dash dot line": 4,
"dash dot dot line": 5
};
const LEGACY_DIRECTION_KEYS = {
N: "n",
E: "e",
S: "s",
W: "w",
UP: "up",
DOWN: "down",
NE: "ne",
SE: "se",
SW: "sw",
NW: "nw",
IN: "in",
OUT: "out"
};
/** Userdata key Mudlet uses to carry the real symbol of a pre-v19 room. */
const FALLBACK_SYMBOL_KEY = "system.fallback_symbol";
/** Lower-case known direction keys, mirroring Mudlet's legacy custom-line load. */
function normalizeDirectionKeys(record) {
const out = {};
for (const key of Object.keys(record)) out[LEGACY_DIRECTION_KEYS[key] ?? key] = record[key];
return out;
}
const DEFAULT_FONT = {
family: "Bitstream Vera Sans Mono",
style: "",
pointSize: 10,
pixelSize: -1,
styleHint: 0,
styleStrategy: 0,
weight: 50,
fontBits: 0,
stretch: 0,
extendedFontBits: 0,
letterSpacing: 0,
wordSpacing: 0,
hintingPreference: 0,
capital: 0,
styleSetting: false,
underline: false,
overline: false,
strikeOut: false,
fixedPitch: true,
kerning: false,
styleOblique: false,
ignorePitch: false,
letterSpacingIsAbsolute: false
};
/** Reads a v16-v18 room symbol: a single signed byte (Qt qint8 char code). */
var QSymbolByte = class extends QClass {
static read(buffer) {
const code = buffer.readInt8();
return code > 32 ? String.fromCodePoint(code) : "";
}
toBuffer() {
throw new Error("writing legacy Mudlet map versions is not supported");
}
};
/** Reads a pre-v20 custom-line colour: a QList<int> of [r, g, b] -> MudletColor. */
var QColorFromIntList = class extends QClass {
static read(buffer) {
const count = QUInt.read(buffer);
const channels = [];
for (let i = 0; i < count; i++) channels.push(QInt.read(buffer));
if (channels.length >= 3) return {
spec: 1,
alpha: 255,
r: channels[0],
g: channels[1],
b: channels[2],
pad: 0
};
return {
spec: 1,
alpha: 255,
r: 255,
g: 0,
b: 0,
pad: 0
};
}
toBuffer() {
throw new Error("writing legacy Mudlet map versions is not supported");
}
};
/** Reads a pre-v20 custom-line style: a QString style-name -> Qt::PenStyle int. */
var QStyleFromString = class extends QClass {
static read(buffer) {
return PEN_STYLE[QString$1.read(buffer)] ?? 1;
}
toBuffer() {
throw new Error("writing legacy Mudlet map versions is not supported");
}
};
/**
* Reads the pre-v18 mRoomIdHash: a single legacy "current room" int. The
* profile name it was keyed by in C++ isn't in the stream, so the value can't
* be reattached meaningfully — we consume the int and surface an empty map.
*/
var QLegacyRoomId = class extends QClass {
static read(buffer) {
QInt.read(buffer);
return {};
}
toBuffer() {
throw new Error("writing legacy Mudlet map versions is not supported");
}
};
const LEGACY_TYPE = {
SYMBOL_BYTE: 300,
COLOR_INTLIST: 301,
STYLE_STRING: 302,
ROOM_ID: 303
};
let legacyTypesRegistered = false;
function registerLegacyValueTypes() {
if (legacyTypesRegistered) return;
legacyTypesRegistered = true;
qtype(LEGACY_TYPE.SYMBOL_BYTE)(QSymbolByte);
qtype(LEGACY_TYPE.COLOR_INTLIST)(QColorFromIntList);
qtype(LEGACY_TYPE.STYLE_STRING)(QStyleFromString);
qtype(LEGACY_TYPE.ROOM_ID)(QLegacyRoomId);
}
const CONFIGS = {
16: {
hasMapUserData: false,
hasMapFont: false,
modernRoomIdHash: false,
modernArea: false,
stringSymbol: false
},
17: {
hasMapUserData: true,
hasMapFont: false,
modernRoomIdHash: false,
modernArea: true,
stringSymbol: false
},
18: {
hasMapUserData: true,
hasMapFont: false,
modernRoomIdHash: true,
modernArea: true,
stringSymbol: false
},
19: {
hasMapUserData: true,
hasMapFont: true,
modernRoomIdHash: true,
modernArea: true,
stringSymbol: true
}
};
/**
* Backfill header-level (non-room) fields this version's layout doesn't
* carry, so the canonical `MudletMapHeader`/`MudletMap` is always fully
* populated for downstream consumers. Shared by the full `read` and the
* streaming `readHeader`.
*/
function backfillHeader(header, cfg) {
if (!cfg.hasMapUserData) header.mUserData = {};
if (!cfg.hasMapFont) {
header.mapSymbolFont = { ...DEFAULT_FONT };
header.mapFontFudgeFactor = 1;
header.useOnlyMapFont = false;
}
if (!cfg.modernArea) for (const value of Object.values(header.areas)) {
const area = value;
delete area.legacyForZUnused1;
delete area.legacyForZUnused2;
area.userData = {};
}
}
/**
* Backfill a single room's fields this version's layout doesn't carry.
* Shared by the full `read` and the streaming `readRoom`.
*/
function backfillRoom(room, cfg) {
if (!cfg.stringSymbol && room.userData) {
const fallback = room.userData[FALLBACK_SYMBOL_KEY];
if (typeof fallback === "string" && fallback.length > 0) room.symbol = fallback;
delete room.userData[FALLBACK_SYMBOL_KEY];
}
room.customLines = normalizeDirectionKeys(room.customLines);
room.customLinesArrow = normalizeDirectionKeys(room.customLinesArrow);
room.customLinesColor = normalizeDirectionKeys(room.customLinesColor);
room.customLinesStyle = normalizeDirectionKeys(room.customLinesStyle);
}
/**
* Register a read-only MapModel for one of the legacy versions 16-19. Each
* version gets its own version-qualified QUserType names and Qt container ids
* so the global registry never clobbers another version (including v20).
*/
function registerLegacyMapModel(version) {
const cfg = CONFIGS[version];
if (!cfg) throw new Error(`No legacy config for Mudlet map version ${version}`);
registerLegacyValueTypes();
const TYPE = {
MAP: `MudletMap@${version}`,
HEADER: `MudletMapHeader@${version}`,
AREA: `MudletArea@${version}`,
ROOM: `MudletRoom@${version}`,
LABEL: `MudletLabel@${version}`
};
const CONTAINER = {
LABELS: version * 10,
ROOMS: version * 10 + 1,
AREAS: version * 10 + 2
};
qtype(CONTAINER.LABELS)(createMudletLabels(TYPE.LABEL));
qtype(CONTAINER.ROOMS)(createMudletRooms(TYPE.ROOM));
qtype(CONTAINER.AREAS)(createMudletAreas(TYPE.AREA));
const areaFields = [
{ rooms: QList(QUInt) },
{ zLevels: QList(QInt) },
{ mAreaExits: QMultiMap(QInt, QPair(QInt, QInt)) },
{ gridMode: Types$1.BOOL },
{ max_x: Types$1.INT },
{ max_y: Types$1.INT },
{ max_z: Types$1.INT },
{ min_x: Types$1.INT },
{ min_y: Types$1.INT },
{ min_z: Types$1.INT },
{ span: Types$1.VECTOR }
];
if (cfg.modernArea) areaFields.push({ xmaxForZ: QMap(QInt, QInt) }, { ymaxForZ: QMap(QInt, QInt) }, { xminForZ: QMap(QInt, QInt) }, { yminForZ: QMap(QInt, QInt) });
else areaFields.push({ xmaxForZ: QMap(QInt, QInt) }, { ymaxForZ: QMap(QInt, QInt) }, { legacyForZUnused1: QMap(QInt, QInt) }, { xminForZ: QMap(QInt, QInt) }, { yminForZ: QMap(QInt, QInt) }, { legacyForZUnused2: QMap(QInt, QInt) });
areaFields.push({ pos: Types$1.VECTOR }, { isZone: Types$1.BOOL }, { zoneAreaRef: Types$1.INT });
if (cfg.modernArea) areaFields.push({ userData: QMap(QString$1, QString$1) });
QUserType.register(TYPE.AREA, areaFields);
QUserType.register(TYPE.ROOM, [
{ area: Types$1.INT },
{ x: Types$1.INT },
{ y: Types$1.INT },
{ z: Types$1.INT },
{ north: Types$1.INT },
{ northeast: Types$1.INT },
{ east: Types$1.INT },
{ southeast: Types$1.INT },
{ south: Types$1.INT },
{ southwest: Types$1.INT },
{ west: Types$1.INT },
{ northwest: Types$1.INT },
{ up: Types$1.INT },
{ down: Types$1.INT },
{ in: Types$1.INT },
{ out: Types$1.INT },
{ environment: Types$1.INT },
{ weight: Types$1.INT },
{ name: Types$1.STRING },
{ isLocked: Types$1.BOOL },
{ rawSpecialExits: QMultiMap(QUInt, QString$1) },
{ symbol: cfg.stringSymbol ? Types$1.STRING : LEGACY_TYPE.SYMBOL_BYTE },
{ userData: QMap(QString$1, QString$1) },
{ customLines: QMap(QString$1, QList(QPoint)) },
{ customLinesArrow: QMap(QString$1, QBool) },
{ customLinesColor: QMap(QString$1, QColorFromIntList) },
{ customLinesStyle: QMap(QString$1, QStyleFromString) },
{ exitLocks: QList(QInt) },
{ stubs: QList(QInt) },
{ exitWeights: QMap(QString$1, QInt) },
{ doors: QMap(QString$1, QInt) }
]);
QUserType.register(TYPE.LABEL, [
{ id: Types$1.INT },
{ pos: Types$1.VECTOR },
{ dummy1: Types$1.DOUBLE },
{ dummy2: Types$1.DOUBLE },
{ size: QPair(QDouble, QDouble) },
{ text: Types$1.STRING },
{ fgColor: Types$1.COLOR },
{ bgColor: Types$1.COLOR },
{ pixMap: Types$1.PIXMAP },
{ noScaling: Types$1.BOOL },
{ showOnTop: Types$1.BOOL }
]);
const mapFields = [
{ version: Types$1.INT },
{ envColors: QMap(QInt, QInt) },
{ areaNames: QMap(QInt, QString$1, true) },
{ mCustomEnvColors: QMap(QInt, QColor) },
{ mpRoomDbHashToRoomId: QMap(QString$1, QUInt) }
];
if (cfg.hasMapUserData) mapFields.push({ mUserData: QMap(QString$1, QString$1) });
if (cfg.hasMapFont) mapFields.push({ mapSymbolFont: Types$1.FONT }, { mapFontFudgeFactor: Types$1.DOUBLE }, { useOnlyMapFont: Types$1.BOOL });
mapFields.push({ areas: CONTAINER.AREAS });
mapFields.push(cfg.modernRoomIdHash ? { mRoomIdHash: QMap(QString$1, QInt) } : { mRoomIdHash: LEGACY_TYPE.ROOM_ID });
mapFields.push({ labels: CONTAINER.LABELS });
QUserType.register(TYPE.HEADER, mapFields);
QUserType.register(TYPE.MAP, [...mapFields, { rooms: CONTAINER.ROOMS }]);
registerMapModel({
version,
read: (rb) => {
const map = QUserType.read(rb, TYPE.MAP);
backfillHeader(map, cfg);
for (const room of Object.values(map.rooms)) backfillRoom(room, cfg);
return map;
},
write: () => {
throw new Error(`Writing Mudlet map version ${version} is not supported (read-only). Mudlet only saves the latest format.`);
},
readHeader: (rb) => {
const header = QUserType.read(rb, TYPE.HEADER);
backfillHeader(header, cfg);
return header;
},
readRoom: (rb) => {
const id = QInt.read(rb);
const room = QUserType.get(TYPE.ROOM).read(rb);
backfillRoom(room, cfg);
return {
id,
room
};
}
});
}
//#endregion
//#region src/models/v16.ts
registerLegacyMapModel(16);
//#endregion
//#region src/models/v17.ts
registerLegacyMapModel(17);
//#endregion
//#region src/models/v18.ts
registerLegacyMapModel(18);
//#endregion
//#region src/models/v19.ts
registerLegacyMapModel(19);
//#endregion
//#region src/models/v20.ts
const VERSION = 20;
registerBaseTypes();
const TYPE = {
MAP: `MudletMap@${VERSION}`,
HEADER: `MudletMapHeader@${VERSION}`,
AREA: `MudletArea@${VERSION}`,
ROOM: `MudletRoom@${VERSION}`,
LABEL: `MudletLabel@${VERSION}`
};
const CONTAINER = {
LABELS: 200,
ROOMS: 201,
AREAS: 202
};
qtype(CONTAINER.LABELS)(createMudletLabels(TYPE.LABEL));
qtype(CONTAINER.ROOMS)(createMudletRooms(TYPE.ROOM));
qtype(CONTAINER.AREAS)(createMudletAreas(TYPE.AREA));
QUserType.register(TYPE.AREA, [
{ rooms: QList(QUInt) },
{ zLevels: QList(QInt) },
{ mAreaExits: QMultiMap(QInt, QPair(QInt, QInt)) },
{ gridMode: Types$1.BOOL },
{ max_x: Types$1.INT },
{ max_y: Types$1.INT },
{ max_z: Types$1.INT },
{ min_x: Types$1.INT },
{ min_y: Types$1.INT },
{ min_z: Types$1.INT },
{ span: Types$1.VECTOR },
{ xmaxForZ: QMap(QInt, QInt) },
{ ymaxForZ: QMap(QInt, QInt) },
{ xminForZ: QMap(QInt, QInt) },
{ yminForZ: QMap(QInt, QInt) },
{ pos: Types$1.VECTOR },
{ isZone: Types$1.BOOL },
{ zoneAreaRef: Types$1.INT },
{ userData: QMap(QString$1, QString$1) }
]);
QUserType.register(TYPE.ROOM, [
{ area: Types$1.INT },
{ x: Types$1.INT },
{ y: Types$1.INT },
{ z: Types$1.INT },
{ north: Types$1.INT },
{ northeast: Types$1.INT },
{ east: Types$1.INT },
{ southeast: Types$1.INT },
{ south: Types$1.INT },
{ southwest: Types$1.INT },
{ west: Types$1.INT },
{ northwest: Types$1.INT },
{ up: Types$1.INT },
{ down: Types$1.INT },
{ in: Types$1.INT },
{ out: Types$1.INT },
{ environment: Types$1.INT },
{ weight: Types$1.INT },
{ name: Types$1.STRING },
{ isLocked: Types$1.BOOL },
{ rawSpecialExits: QMultiMap(QUInt, QString$1) },
{ symbol: Types$1.STRING },
{ userData: QMap(QString$1, QString$1) },
{ customLines: QMap(QString$1, QList(QPoint)) },
{ customLinesArrow: QMap(QString$1, QBool) },
{ customLinesColor: QMap(QString$1, QColor) },
{ customLinesStyle: QMap(QString$1, QUInt) },
{ exitLocks: QList(QInt) },
{ stubs: QList(QInt) },
{ exitWeights: QMap(QString$1, QInt) },
{ doors: QMap(QString$1, QInt) }
]);
QUserType.register(TYPE.LABEL, [
{ id: Types$1.INT },
{ pos: Types$1.VECTOR },
{ dummy1: Types$1.DOUBLE },
{ dummy2: Types$1.DOUBLE },
{ size: QPair(QDouble, QDouble) },
{ text: Types$1.STRING },
{ fgColor: Types$1.COLOR },
{ bgColor: Types$1.COLOR },
{ pixMap: Types$1.PIXMAP },
{ noScaling: Types$1.BOOL },
{ showOnTop: Types$1.BOOL }
]);
const HEADER_FIELDS = [
{ version: Types$1.INT },
{ envColors: QMap(QInt, QInt) },
{ areaNames: QMap(QInt, QString$1, true) },
{ mCustomEnvColors: QMap(QInt, QColor) },
{ mpRoomDbHashToRoomId: QMap(QString$1, QUInt) },
{ mUserData: QMap(QString$1, QString$1) },
{ mapSymbolFont: Types$1.FONT },
{ mapFontFudgeFactor: Types$1.DOUBLE },
{ useOnlyMapFont: Types$1.BOOL },
{ areas: CONTAINER.AREAS },
{ mRoomIdHash: QMap(QString$1, QInt) },
{ labels: CONTAINER.LABELS }
];
QUserType.register(TYPE.HEADER, HEADER_FIELDS);
QUserType.register(TYPE.MAP, [...HEADER_FIELDS, { rooms: CONTAINER.ROOMS }]);
registerMapModel({
version: VERSION,
read: (rb) => QUserType.read(rb, TYPE.MAP),
write: (map) => QUserType.get(TYPE.MAP).from(map).toBuffer(true),
readHeader: (rb) => QUserType.read(rb, TYPE.HEADER),
readRoom: (rb) => {
return {
id: QInt.read(rb),
room: QUserType.get(TYPE.ROOM).read(rb)
};
}
});
//#endregion
//#region src/map-operations.ts
const { ReadBuffer } = buffer;
const CARDINAL_DIRS = [
"north",
"northeast",
"east",
"southeast",
"south",
"southwest",
"west",
"northwest",
"up",
"down",
"in",
"out"
];
const DIR_INDEX = {
north: 1,
northeast: 2,
northwest: 3,
east: 4,
west: 5,
south: 6,
southeast: 7,
southwest: 8,
up: 9,
down: 10,
in: 11,
out: 12
};
const DIR_OTHER = 13;
/**
* Rebuild every area's `mAreaExits` from the current room graph. This field
* is a cache Mudlet's autowalker/pathfinder uses to navigate between zones,
* and mirrors TArea::determineAreaExits in Mudlet's C++ source. Any editor
* that mutates rooms / exits and re-saves must regenerate it — otherwise the
* cache goes stale and Mudlet's routing breaks.
*/
function rebuildAreaExits(map) {
for (const areaId in map.areas) {
if (!Object.hasOwn(map.areas, areaId)) continue;
map.areas[areaId].mAreaExits = {};
}
for (const idStr in map.rooms) {
if (!Object.hasOwn(map.rooms, idStr)) continue;
const roomId = Number(idStr);
const room = map.rooms[roomId];
const srcArea = map.areas[room.area];
if (!srcArea) continue;
const entries = [];
for (const dir of CARDINAL_DIRS) {
const targetId = room[dir];
if (!targetId || targetId < 1) continue;
const target = map.rooms[targetId];
if (!target || target.area === room.area) continue;
entries.push([targetId, DIR_INDEX[dir]]);
}
const specials = room.mSpecialExits ?? {};
const specialNames = Object.keys(specials).sort();
for (const name of specialNames) {
const targetId = specials[name];
if (!targetId || targetId < 1) continue;
const target = map.rooms[targetId];
if (!target || target.area === room.area) continue;
entries.push([targetId, DIR_OTHER]);
}
if (entries.length > 0) srcArea.mAreaExits[roomId] = entries;
}
}
/**
* Rebuild every area's spatial-extent cache (zLevels, min/max x/y/z, and the
* per-Z xminForZ/xmaxForZ/yminForZ/ymaxForZ maps) from the current room graph.
* Mirrors Mudlet's TArea::calcSpan in src/TArea.cpp.
*
* Two quirks inherited from C++:
* 1. y is negated when stored on the area. room.y is plain, but
* area.min_y / max_y / yminForZ / ymaxForZ all hold `-room.y`.
* 2. span and pos are NOT recomputed. calcSpan never touches them, and
* the TArea.h header flags them both as effectively dead. We leave
* them as-is so round-tripping an untouched map stays byte-identical.
* 3. For an area with zero valid rooms, the per-Z maps and zLevels are
* cleared but min/max are preserved — again, matching C++.
*/
function rebuildAreaExtents(map) {
for (const areaIdStr in map.areas) {
if (!Object.hasOwn(map.areas, areaIdStr)) continue;
const area = map.areas[areaIdStr];
area.xminForZ = {};
area.xmaxForZ = {};
area.yminForZ = {};
area.ymaxForZ = {};
area.zLevels = [];
let first = true;
for (const roomId of area.rooms) {
const room = map.rooms[roomId];
if (!room) continue;
const x = room.x;
const y = -room.y;
const z = room.z;
if (first) {
area.min_x = x;
area.max_x = x;
area.min_y = y;
area.max_y = y;
area.min_z = z;
area.max_z = z;
area.zLevels.push(z);
area.xminForZ[z] = x;
area.xmaxForZ[z] = x;
area.yminForZ[z] = y;
area.ymaxForZ[z] = y;
first = false;
continue;
}
if (!area.zLevels.includes(z)) area.zLevels.push(z);
if (area.xminForZ[z] === void 0 || x < area.xminForZ[z]) area.xminForZ[z] = x;
if (x < area.min_x) area.min_x = x;
if (area.xmaxForZ[z] === void 0 || x > area.xmaxForZ[z]) area.xmaxForZ[z] = x;
if (x > area.max_x) area.max_x = x;
if (area.yminForZ[z] === void 0 || y < area.yminForZ[z]) area.yminForZ[z] = y;
if (y < area.min_y) area.min_y = y;
if (y > area.max_y) area.max_y = y;
if (area.ymaxForZ[z] === void 0 || y > area.ymaxForZ[z]) area.ymaxForZ[z] = y;
if (z < area.min_z) area.min_z = z;
if (z > area.max_z) area.max_z = z;
}
if (area.zLevels.length > 1) area.zLevels.sort((a, b) => a - b);
}
}
/**
* Populate each `room.hash` from the inverse of `map.mpRoomDbHashToRoomId`.
* The binary format doesn't carry a per-room hash field — the hash lives
* only in the map-level index. Copying it onto the room on load lets
* editors treat `room.hash` as the source of truth, and lets
* {@link rebuildRoomHashIndex} rebuild the index cleanly on save.
*
* NOTE: this is specifically about the content hash index
* (`mpRoomDbHashToRoomId` — mirrors Mudlet's `TRoomDB::hashToRoomID`).
* The similarly-named `mRoomIdHash` is something else entirely: it maps
* Mudlet profile name → player's current room ID. Do not touch it here.
*/
function populateRoomHashes(map) {
for (const hash in map.mpRoomDbHashToRoomId) {
if (!Object.hasOwn(map.mpRoomDbHashToRoomId, hash)) continue;
const roomId = map.mpRoomDbHashToRoomId[hash];
const room = map.rooms[roomId];
if (room) room.hash = hash;
}
}
/**
* Rebuild `map.mpRoomDbHashToRoomId` from the `hash` field on each room.
* Mirrors Mudlet's `TRoomDB::hashToRoomID`. Callers can freely mutate
* `room.hash` (or add/remove rooms) between load and save; this helper
* keeps the on-disk index consistent.
*
* Rooms with `hash === undefined`, `''`, or `null` are skipped. If two
* rooms declare the same non-empty hash (should not happen in a sane
* map) the later iteration wins and a warning is logged — the underlying
* data is already inconsistent at that point.
*
* NOTE: does not touch `mRoomIdHash`. That's the per-profile player
* cursor (profile name → room ID) and is not derivable from rooms.
*/
function rebuildRoomHashIndex(map) {
map.mpRoomDbHashToRoomId = {};
for (const idStr in map.rooms) {
if (!Object.hasOwn(map.rooms, idStr)) continue;
const hash = map.rooms[idStr].hash;
if (typeof hash !== "string" || hash.length === 0) continue;
const roomId = Number(idStr);
if (Object.hasOwn(map.mpRoomDbHashToRoomId, hash)) {
const prev = map.mpRoomDbHashToRoomId[hash];
console.warn(`[mudlet-map-binary-reader] duplicate room hash "${hash}" on rooms ${prev} and ${roomId}; last wins`);
}
map.mpRoomDbHashToRoomId[hash] = roomId;
}
}
/**
* Hydrate a single room's `mSpecialExits` / `mSpecialExitLocks` fields by
* parsing the `rawSpecialExits` layout Qt stores on disk.
*/
function hydrateRoomSpecialExits(room) {
room.mSpecialExits = {};
room.mSpecialExitLocks = [];
for (const key in room.rawSpecialExits) {
if (!Object.hasOwn(room.rawSpecialExits, key)) continue;
for (const ex of room.rawSpecialExits[key]) if (ex.startsWith("0")) room.mSpecialExits[ex.substring(1)] = parseInt(key);
else if (ex.startsWith("1")) {
room.mSpecialExits[ex.substring(1)] = parseInt(key);
room.mSpecialExitLocks.push(parseInt(key));
} else room.mSpecialExits[ex] = parseInt(key);
}
}
/**
* Hydrate the `mSpecialExits` / `mSpecialExitLocks` fields on every room
* by parsing the `rawSpecialExits` layout Qt stores on disk.
*/
function hydrateSpecialExits(map) {
for (const roomId in map.rooms) {
if (!Object.hasOwn(map.rooms, roomId)) continue;
hydrateRoomSpecialExits(map.rooms[roomId]);
}
}
/**
* Inverse of {@link hydrateSpecialExits}: repacks `mSpecialExits` /
* `mSpecialExitLocks` back into the `rawSpecialExits` layout Qt expects
* before serialising.
*/
function dehydrateSpecialExits(map) {
for (const roomId in map.rooms) {
const room = map.rooms[roomId];
const rawSpecialExits = {};
for (const exit in room.mSpecialExits) {
if (!Object.hasOwn(room.mSpecialExits, exit)) continue;
const exRoomId = room.mSpecialExits[exit];
if (rawSpecialExits[exRoomId] === void 0) rawSpecialExits[exRoomId] = [];
const locked = room.mSpecialExitLocks.indexOf(exRoomId) > -1;
rawSpecialExits[exRoomId].push((locked ? "1" : "0") + exit);
}
room.rawSpecialExits = rawSpecialExits;
}
}
/**
* Read just the leading version int, on a throwaway buffer, so we can pick the
* right version model before parsing the rest of the stream.
*/
function readMapVersion(buf) {
return QInt.read(new ReadBuffer(buf));
}
/**
* Parse a Mudlet binary map from an in-memory buffer. Environment-
* independent: no `fs`, no file path. Callers in Node can pass
* `fs.readFileSync(path)`; callers in the browser can pass a `Buffer`
* constructed from a `File` / `ArrayBuffer`.
*
* The format version (the first int in the stream) selects the model. An
* unsupported version fails fast with a clear error rather than silently
* mis-parsing a layout it doesn't match (reading a v16 map as v20 desyncs the
* stream and dies with an opaque "Invalid array length").
*/
function readMapFromBuffer$1(buf) {
const version = readMapVersion(buf);
const model = getMapModel(version);
if (!model) throw new Error(`Unsupported Mudlet map version ${version}. Supported version(s): ${getSupportedVersions().join(", ")}.`);
const map = model.read(new ReadBuffer(buf));
hydrateSpecialExits(map);
populateRoomHashes(map);
return map;
}
/**
* Stream a Mudlet binary map room-by-room without ever holding the whole
* room graph in memory. Decodes the (small) header sections eagerly — areas,
* labels, colours, names — then walks the trailing rooms blob, invoking
* `onRoom(id, room)` for each room and discarding it as soon as the callback
* returns. Peak memory is therefore `buffer + one room`, not the multi-GB
* fully-materialised object graph that {@link readMapFromBuffer} builds.
*
* This is the building block for chunking/packing very large maps (260 MB+)
* that cannot be loaded whole. The format is strictly sequential with no
* index, so this still reads every byte once — but it does not accumulate.
* Supported for every version {@link readMapFromBuffer} supports (v16-v20);
* an unsupported version throws the same error `readMapFromBuffer` would.
*
* Each emitted room has its `mSpecialExits` / `mSpecialExitLocks` hydrated,
* matching {@link readMapFromBuffer}. The per-room content `hash` is NOT set
* (it lives in the header's `mpRoomDbHashToRoomId` index, returned here so a
* caller can resolve hashes itself if needed).
*
* `onHeader`, if given, is invoked with the decoded header *before* the room
* loop begins. The rooms section itself is unframed (no count), but every
* area's `rooms` id-list is in the header, so a caller can sum them there to
* learn the total room count up front (e.g. for a progress bar / preallocation).
*
* @returns the map header (everything except `rooms`).
*/
function streamRooms$1(buf, onRoom, onHeader) {
const version = readMapVersion(buf);
const model = getMapModel(version);
if (!model) throw new Error(`Unsupported Mudlet map version ${version}. Supported version(s): ${getSupportedVersions().join(", ")}.`);
const rb = new ReadBuffer(buf);
const header = model.readHeader(rb);
onHeader?.(header);
while (rb.read_offset < rb.buffer.length) {
const { id, room } = model.readRoom(rb);
hydrateRoomSpecialExits(room);
onRoom(id, room);
}
return header;
}
/**
* Serialise a map model to a Mudlet binary buffer. Environment-
* independent: no `fs`. Node callers persist with
* `fs.writeFileSync(path, writeMapToBuffer(map))`; browser callers can
* hand it to a `Blob` or HTTP response.
*/
function writeMapToBuffer$1(map) {
const model = getMapModel(map.version);
if (!model) throw new Error(`Cannot write Mudlet map: unsupported version ${map.version}. Supported version(s): ${getSupportedVersions().join(", ")}.`);
dehydrateSpecialExits(map);
rebuildAreaExits(map);
rebuildAreaExtents(map);
rebuildRoomHashIndex(map);
return model.write(map);
}
//#endregion
//#region mudlet-colors.json
var mudlet_colors_default = {
light_gray: [
211,
211,
211
],
ansi_027: [
0,
95,
255
],
pale_turquoise: [
175,
238,
238
],
ansi_178: [
215,
175,
0
],
purple: [
160,
32,
240
],
ansi_079: [
95,
215,
175
],
ansi_047: [
0,
255,
95
],
PaleGreen: [
152,
251,
152
],
ansi_041: [
0,
215,
95
],
sky_blue: [
135,
206,
235
],
light_goldenrod_yellow: [
250,
250,
210
],
OrangeRed: [
255,
69,
0
],
ansi_185: [
215,
215,
95
],
ansi_014: [
0,
255,
255
],
OliveDrab: [
107,
142,
35
],
PapayaWhip: [
255,
239,
213
],
chocolate: [
210,
105,
30
],
cornflower_blue: [
100,
149,
237
],
ansi_160: [
215,
0,
0
],
ansi_black: [
0,
0,
0
],
ansi_058: [
95,
95,
0
],
gold: [
255,
215,
0
],
lawn_green: [
124,
252,
0
],
ansi_036: [
0,
175,
135
],
grey: [
190,
190,
190
],
NavajoWhite: [
255,
222,
173
],
ansi_008: [
128,
128,
128
],
ansi_204: [
255,
95,
135
],
sandy_brown: [
244,
164,
96
],
ansi_024: [
0,
95,
135
],
ansi_048: [
0,
255,
135
],
MediumSpringGreen: [
0,
250,
154
],
DarkOliveGreen: [
85,
107,
47
],
ansi_244: [
128,
128,
128
],
ansi_012: [
0,
0,
255
],
ansi_025: [
0,
95,
175
],
light_salmon: [
255,
160,
122
],
ansi_182: [
215,
175,
215
],
ForestGreen: [
34,
139,
34
],
ansi_194: [
215,
255,
215
],
burlywood: [
222,
184,
135
],
BlanchedAlmond: [
255,
235,
205
],
ansi_131: [
175,
95,
95
],
HotPink: [
255,
105,
180
],
dark_sea_green: [
143,
188,
143
],
MediumSlateBlue: [
123,
104,
238
],
LightGrey: [
211,
211,
211
],
dark_violet: [
148,
0,
211
],
saddle_brown: [
139,
69,
19
],
medium_orchid: [
186,
85,
211
],
ansi_yellow: [
128,
128,
0
],
ansi_230: [
255,
255,
215
],
ansi_238: [
68,
68,
68
],
MintCream: [
245,
255,
250
],
ansi_188: [
215,
215,
215
],
pale_green: [
152,
251,
152
],
pale_goldenrod: [
238,
232,
170
],
ansi_164: [
215,
0,
215
],
mint_cream: [
245,
255,
250
],
violet_red: [
208,
32,
144
],
ansi_215: [
255,
175,
95
],
ansi_248: [
168,
168,
168
],
ansi_163: [
215,
0,
175
],
ansi_023: [
0,
95,
95
],
ansi_044: [
0,
215,
215
],
spring_green: [
0,
255,
127
],
orange: [
255,
165,
0
],
LightCyan: [
224,
255,
255
],
ansi_143: [
175,
175,
95
],
LightSeaGreen: [
32,
178,
170
],
salmon: [
250,
128,
114
],
LightSteelBlue: [
176,
196,
222
],
ansi_000: [
0,
0,
0
],
indian_red: [
205,
92,
92
],
ansi_144: [
175,
175,
135
],
light_steel_blue: [
176,
196,
222
],
ansi_251: [
198,
198,
198
],
ansi_090: [
135,
0,
135
],
dark_green: [
0,
100,
0
],
ansi_064: [
95,
135,
0
],
ghost_white: [
248,
248,
255
],
ansi_016: [
0,
0,
0
],
gray: [
190,
190,
190
],
ansi_127: [
175,
0,
175
],
ansi_222: [
255,
215,
135
],
DarkViolet: [
148,
0,
211
],
ansi_098: [
135,
95,
215
],
old_lace: [
253,
245,
230
],
maroon: [
176,
48,
96
],
snow: [
255,
250,
250
],
ansi_094: [
135,
95,
0
],
ansi_050: [
0,
255,
215
],
ansi_139: [
175,
135,
175
],
ansi_171: [
215,
95,
255
],
MediumTurquoise: [
72,
209,
204
],
blanched_almond: [
255,
235,
205
],
ansi_087: [
95,
255,
255
],
LightBlue: [
173,
216,
230
],
seashell: [
255,
245,
238
],
ansi_111: [
135,
175,
255
],
ansi_013: [
255,
0,
255
],
ansi_light_red: [
255,
0,
0
],
blue: [
0,
0,
255
],
dark_slate_grey: [
47,
79,
79
],
LightGray: [
211,
211,
211
],
ansi_121: [
135,
255,
175
],
light_blue: [
173,
216,
230
],
ansi_119: [
135,
255,
95
],
DarkSalmon: [
233,
150,
122
],
ansi_211: [
255,
135,
175
],
ansi_214: [
255,
175,
0
],
ansi_077: [
95,
215,
95
],
floral_white: [
255,
250,
240
],
ansiCyan: [
0,
128,
128
],
ansi_086: [
95,
255,
215
],
ansi_002: [
0,
128,
0
],
ansi_156: [
175,
255,
135
],
ansi_042: [
0,
215,
135
],
SaddleBrown: [
139,
69,
19
],
ansi_199: [
255,
0,
175
],
honeydew: [
240,
255,
240
],
LightSlateGrey: [
119,
136,
153
],
ansi_217: [
255,
175,
175
],
tomato: [
255,
99,
71
],
ansi_184: [
215,
215,
0
],
forest_green: [
34,
139,
34
],
ansi_212: [
255,
135,
215
],
LightSlateBlue: [
132,
112,
255
],
light_slate_gray: [
119,
136,
153
],
ansi_light_black: [
128,
128,
128
],
PaleVioletRed: [
219,
112,
147
],
LightGoldenrod: [
238,
221,
130
],
light_slate_blue: [
132,
112,
255
],
medium_purple: [
147,
112,
219
],
ansi_175: [
215,
135,
175
],
ansi_183: [
215,
175,
255
],
PaleGoldenrod: [
238,
232,
170
],
ansi_234: [
28,
28,
28
],
ansi_129: [
175,
0,
255
],
red: [
255,
0,
0
],
ansi_010: [
0,
255,
0
],
ansi_176: [
215,
135,
215
],
ansi_magenta: [
128,
0,
128
],
ansi_001: [
128,
0,
0
],
lavender: [
230,
230,
250
],
green_yellow: [
173,
255,
47
],
ansi_046: [
0,
255,
0
],
dark_olive_green: [
85,
107,
47
],
ansi_068: [
95,
135,
215
],
midnight_blue: [
25,
25,
112
],
ansi_104: [
135,
135,
215
],
moccasin: [
255,
228,
181
],
DarkOrange: [
255,
140,
0
],
ansi_017: [
0,
0,
95
],
NavyBlue: [
0,
0,
128
],
papaya_whip: [
255,
239,
213
],
ansi_240: [
88,
88,
88
],
light_sea_green: [
32,
178,
170
],
ansi_109: [
135,
175,
175
],
ansi_126: [
175,
0,
135
],
ansi_168: [
215,
95,
135
],
ansi_005: [
128,
0,
128
],
black: [
0,
0,
0
],
ansi_009: [
255,
0,
0
],
yellow: [
255,
255,
0
],
light_slate_grey: [
119,
136,
153
],
goldenrod: [
218,
165,
32
],
lavender_blush: [
255,
240,
245
],
ansi_227: [
255,
255,
95
],
ansi_053: [
95,
0,
95
],
ansiWhite: [
192,
192,
192
],
ansi_197: [
255,
0,
95
],
GreenYellow: [
173,
255,
47
],
magenta: [
255,
0,
255
],
ansi_100: [
135,
135,
0
],
ansi_145: [
175,
175,
175
],
ansi_140: [
175,
135,
215
],
ansi_202: [
255,
95,
0
],
MediumAquamarine: [
102,
205,
170
],
ansi_235: [
38,
38,
38
],
thistle: [
216,
191,
216
],
ansi_162: [
215,
0,
135
],
ansi_066: [
95,
135,
135
],
ansi_221: [
255,
215,
95
],
ansi_097: [
135,
95,
175
],
ansi_236: [
48,
48,
48
],
ansi_102: [
135,
135,
135
],
ansi_011: [
255,
255,
0
],
ansi_029: [
0,
135,
95
],
ansi_146: [
175,
175,
215
],
ansi_223: [
255,
215,
175
],
ansi_070: [
95,
175,
0
],
ansi_231: [
255,
255,
255
],
ansi_233: [
18,
18,
18
],
ansi_055: [
95,
0,
175
],
antique_white: [
250,
235,
215
],
ansi_115: [
135,
215,
175
],
ansi_133: [
175,
95,
175
],
ansi_red: [
128,
0,
0
],
ansi_255: [
238,
238,
238
],
PaleTurquoise: [
175,
238,
238
],
ansi_037: [
0,
175,
175
],
ansi_063: [
95,
95,
255
],
ansi_242: [
108,
108,
108
],
ansi_191: [
215,
255,
95
],
DarkGoldenrod: [
184,
134,
11
],
ansi_071: [
95,
175,
95
],
ansi_228: [
255,
255,
135
],
SpringGreen: [
0,
255,
127
],
ansi_093: [
135,
0,
255
],
dark_khaki: [
189,
183,
107
],
SlateBlue: [
106,
90,
205
],
ansiRed: [
128,
0,
0
],
ansi_153: [
175,
215,
255
],
ansi_167: [
215,
95,
95
],
ansi_018: [
0,
0,
135
],
ansi_033: [
0,
135,
255
],
ansi_022: [
0,
95,
0
],
alice_blue: [
240,
248,
255
],
ansi_241: [
98,
98,
98
],
DarkSlateGray: [
47,
79,
79
],
ansi_035: [
0,
175,
95
],
slate_blue: [
106,
90,
205
],
ansi_237: [
58,
58,
58
],
bisque: [
255,
228,
196
],
AntiqueWhite: [
250,
235,
215
],
IndianRed: [
205,
92,
92
],
ansi_white: [
192,
192,
192
],
ansi_148: [
175,
215,
0
],
light_goldenrod: [
238,
221,
130
],
ansi_blue: [
0,
0,
128
],
ansi_253: [
218,
218,
218
],
ansiLightMagenta: [
255,
0,
255
],
DimGrey: [
105,
105,
105
],
ansi_103: [
135,
135,
175
],
ansi_172: [
215,
135,
0
],
LightSlateGray: [
119,
136,
153
],
ansi_038: [
0,
175,
215
],
lime_green: [
50,
205,
50
],
deep_sky_blue: [
0,
191,
255
],
ansiYellow: [
128,
128,
0
],
misty_rose: [
255,
228,
225
],
rosy_brown: [
188,
143,
143
],
ansi_245: [
138,
138,
138
],
DimGray: [
105,
105,
105
],
ansi_light_cyan: [
0,
255,
255
],
ansi_light_green: [
0,
255,
0
],
ansi_207: [
255,
95,
255
],
white_smoke: [
245,
245,
245
],
dark_slate_gray: [
47,
79,
79
],
ansi_141: [
175,
135,
255
],
DeepSkyBlue: [
0,
191,
255
],
ansi_213: [
255,
135,
255
],
gainsboro: [
220,
220,
220
],
medium_blue: [
0,
0,
205
],
ansi_193: [
215,
255,
175
],
MediumPurple: [
147,
112,
219
],
ansi_089: [
135,
0,
95
],
ansi_198: [
255,
0,
135
],
ansi_166: [
215,
95,
0
],
firebrick: [
178,
34,
34
],
VioletRed: [
208,
32,
144
],
ansi_065: [
95,
135,
95
],
DarkSeaGreen: [
143,
188,
143
],
ansi_122: [
135,
255,
215
],
navy: [
0,
0,
128
],
cornsilk: [
255,
248,
220
],
ansi_189: [
215,
215,
255
],
LightPink: [
255,
182,
193
],
LightSkyBlue: [
135,
206,
250
],
ansi_060: [
95,
95,
135
],
ansi_088: [
135,
0,
0
],
DarkSlateGrey: [
47,
79,
79
],
pink: [
255,
192,
203
],
medium_violet_red: [
199,
21,
133
],
peru: [
205,
133,
63
],
ansi_021: [
0,
0,
255
],
ansi_200: [
255,
0,
215
],
ansi_028: [
0,
135,
0
],
ansi_069: [
95,
135,
255
],
ansi_142: [
175,
175,
0
],
ansi_052: [
95,
0,
0
],
beige: [
245,
245,
220
],
ansi_007: [
192,
192,
192
],
deep_pink: [
255,
20,
147
],
medium_turquoise: [
72,
209,
204
],
ansi_161: [
215,
0,
95
],
RoyalBlue: [
65,
105,
225
],
ansi_135: [
175,
95,
255
],
ansi_020: [
0,
0,
215
],
ansi_136: [
175,
135,
0
],
ansi_225: [
255,
215,
255
],
ansi_249: [
178,
178,
178
],
DarkTurquoise: [
0,
206,
209
],
ansi_light_white: [
255,
255,
255
],
GhostWhite: [
248,
248,
255
],
ansi_123: [
135,
255,
255
],
ansi_208: [
255,
135,
0
],
ansi_062: [
95,
95,
215
],
ansi_015: [
255,
255,
255
],
ansi_125: [
175,
0,
95
],
LightGoldenrodYellow: [
250,
250,
210
],
DarkKhaki: [
189,
183,
107
],
ansi_232: [
8,
8,
8
],
ansi_239: [
78,
78,
78
],
ansi_219: [
255,
175,
255
],
ansi_083: [
95,
255,
95
],
ansi_061: [
95,
95,
175
],
ansi_075: [
95,
175,
255
],
linen: [
250,
240,
230
],
light_coral: [
240,
128,
128
],
AliceBlue: [
240,
248,
255
],
ansi_059: [
95,
95,
95
],
LightSalmon: [
255,
160,
122
],
khaki: [
240,
230,
140
],
ansi_073: [
95,
175,
175
],
ansi_201: [
255,
0,
255
],
ansi_128: [
175,
0,
215
],
ansiBlue: [
0,
0,
128
],
hot_pink: [
255,
105,
180
],
LemonChiffon: [
255,
250,
205
],
OldLace: [
253,
245,
23