UNPKG

mudlet-map-binary-reader

Version:

Reads Mudlet's map binary file (v20 only!). Can output .js files needed for Mudlet Map Reader. Mudlet map JSON format is yet unsupported.

302 lines (271 loc) 8.94 kB
const fs = require('fs'); const _ = require('lodash'); const directions = [ 'north', 'northeast', 'northwest', 'east', 'west', 'south', 'southeast', 'southwest', 'up', 'down', 'in', 'out', ]; const directionShortNames = [ 'n', 'ne', 'nw', 'e', 'w', 's', 'se', 'sw', 'up', 'down', 'in', 'out', ]; const doorMap = [undefined, 'open', 'closed', 'locked']; const exportMap = (map, mapFile, minified) => { const mudletMap = convertMapToMudletFormat(map); const mapJson = JSON.stringify(mudletMap, null, minified ? 0 : 2); fs.writeFileSync(mapFile, mapJson); }; const convertMapToMudletFormat = (map) => { const convertedObject = { formatVersion: 1.0, }; convertUserData(map, convertedObject); convertAreas(map, convertedObject); addMapStatistics(map, convertedObject); addDefaultAreaName(map, convertedObject); addAnonymousAreaName(map, convertedObject); convertEnvironmentColors(map, convertedObject); convertPlayerRooms(map, convertedObject); convertCustomEnvironmentColours(map, convertedObject); convertMapSymbolInfo(map, convertedObject); convertPlayerRoomLook(map, convertedObject); return convertedObject; }; const convertUserData = (map, convertedObject) => { if (_.isEmpty(map.mUserData)) { return; } convertedObject.userData = map.mUserData; }; const convertAreas = (map, convertedObject) => { const convertedAreas = _.map(map.areas, (area, areaid) => convertArea(area, areaid, map)) .sort((a, b) => a.id - b.id); convertedObject.areas = convertedAreas; }; const convertArea = (area, areaid, map) => { const convertedArea = { id: parseInt(areaid), name: map.areaNames[areaid] || '', gridMode: area.gridMode ? true : undefined, roomCount: area.rooms.length, rooms: _.map(area.rooms, (roomId) => convertRoom(roomId, map)) .sort((a, b) => a.id - b.id), }; if (!_.isEmpty(area.userData)) { convertedArea.userData = area.userData; } if (!_.isEmpty(map.labels[areaid])) { convertedArea.labels = _.map(map.labels[areaid], convertLabel); } return convertedArea; }; const convertRoom = (roomId, map) => { const room = map.rooms[roomId]; const convertedRoom = { id: roomId, name: room.name !== '' ? room.name : undefined, coordinates: [room.x, room.y, room.z], locked: room.isLocked ? true : undefined, weight: room.weight !== 1 ? room.weight : undefined, symbol: room.symbol !== '' ? { text: room.symbol } : undefined, //binary map does not seem to support symbol colours environment: room.environment, hash: room.hash, exits: [], stubExits: convertStubExits(room.stubs, room.doors), userData: _.isEmpty(room.userData) ? undefined : room.userData, }; for (let i = 0; i < directions.length; i++) { const direction = directions[i]; const shortDirection = directionShortNames[i]; if (room[direction] !== -1) { convertedRoom.exits.push({ exitId: room[direction], name: direction, weight: getExitWeight(shortDirection, room.exitWeights, 1), locked: _.find(room.exitLocks, (num) => i === num - 1) ? true : undefined, door: room.doors[shortDirection] !== undefined ? doorMap[room.doors[shortDirection]] : undefined, customLine: convertCustomLine( room.customLines[shortDirection], room.customLinesArrow[shortDirection], room.customLinesColor[shortDirection], room.customLinesStyle[shortDirection] ), }); } } for (const specialExit in room.mSpecialExits) { convertedRoom.exits.push({ name: specialExit, exitId: room.mSpecialExits[specialExit], weight: getExitWeight(specialExit, room.exitWeights, 1), locked: _.find( room.mSpecialExitLocks, (destinationRoom) => room.mSpecialExits[specialExit] === destinationRoom ) ? true : undefined, door: room.doors[specialExit] !== undefined ? doorMap[room.doors[specialExit]] : undefined, customLine: convertCustomLine( room.customLines[specialExit], room.customLinesArrow[specialExit], room.customLinesColor[specialExit], room.customLinesStyle[specialExit] ), }); } return convertedRoom; }; const getExitWeight = (direction, weights, defaultValue) => { const weight = weights[direction]; if (weight === undefined || weight === defaultValue) { return undefined; } return weight; }; const convertCustomLine = (coordinates, arrow, color, style) => { if (coordinates === undefined || _.isEmpty(coordinates)) { return undefined; } // use the color object as base as we don't have a nesting in this case const line = convertColor(color); line.coordinates = coordinates; line.endsInArrow = arrow; line.style = convertLineStyle(style); return line; }; const lineStyles = { 2: 'dash line', 3: 'dot line', 4: 'dash dot line', 5: 'dash dot dot line', }; const convertLineStyle = (style) => { return lineStyles[style]; }; const convertStubExits = (stubs, doors) => { if (stubs.length === 0) { return undefined; } const convertedStubs = []; for (const dirNum of stubs) { const direction = directions[dirNum - 1]; const shortDirection = directionShortNames[dirNum - 1]; const stub = { name: direction, door: doors[shortDirection] !== undefined ? doorMap[doors[shortDirection]] : undefined, }; convertedStubs.push(stub); } return convertedStubs; }; const convertLabel = (label) => { return { colors: [convertColor(label.fgColor), convertColor(label.bgColor)], coordinates: [label.pos[0], label.pos[1], label.pos[2]], id: label.labelId, image: chunkString(Buffer.from(label.pixMap).toString('base64'), 64), // not the same base64 string as in Mudlet's JSON, but should yield the same result scaledels: !label.noScaling, showOnTop: label.showOnTop, text: label.text, size: [label.size[0], label.size[1]], }; }; const chunkString = (str, length) => { return str.match(new RegExp(`.{1,${length}}`, 'g')); }; const addMapStatistics = (map, convertedObject) => { convertedObject.areaCount = Object.keys(map.areas).length; convertedObject.roomCount = Object.keys(map.rooms).length; convertedObject.labelCount = _.flatMap(map.labels, (label) => label).length; }; // currently not in the binary map const addDefaultAreaName = (_map, convertedObject) => { convertedObject.defaultAreaName = 'Default Area'; }; // currently not in the binary map const addAnonymousAreaName = (_map, convertedObject) => { convertedObject.anonymousAreaName = 'Unnamed Area'; }; const convertEnvironmentColors = (map, convertedObject) => { convertedObject.envToColorMapping = map.envColors; }; const convertPlayerRooms = (map, convertedObject) => { convertedObject.playersRoomId = map.mRoomIdHash; }; const convertCustomEnvironmentColours = (map, convertedObject) => { const colors = _.map(map.mCustomEnvColors, (color, index) => { const convertedColor = convertColor(color); convertedColor.id = parseInt(index); return convertedColor; }); convertedObject.customEnvColors = colors; }; const convertColor = (color) => { const ret = {}; const colorArray = [color.r, color.g, color.b]; if (color.alpha < 255) { colorArray.push(color.alpha); ret.color32RGBA = colorArray; } else { ret.color24RGB = colorArray; } return ret; }; const convertMapSymbolInfo = (map, convertedObject) => { const f = map.mapSymbolFont; const firstFamilyComma = f.family.indexOf(','); const family = firstFamilyComma > -1 ? f.family.substr(0, firstFamilyComma) : f.family; // the string produced by the QFont source does not seem to match the one in the QFont::toString() documentation as it's missing some values // However, the source code string seems to match what is in our JSON so we reproduce that. const fontString = `${family},${f.pointSize},${f.pixelSize},${f.styleHint},${ f.weight },${f.styleSetting ? 1 : 0},${f.underline ? 1 : 0},${f.strikeOut ? 1 : 0},${ f.fixedPitch ? 1 : 0 },0`; convertedObject.mapSymbolFontDetails = fontString; convertedObject.mapSymbolFontFudgeFactor = map.mapFontFudgeFactor; convertedObject.onlyMapSymbolFontToBeUsed = map.useOnlyMapFont; }; const convertPlayerRoomLook = (_map, convertedObject) => { // not part of the binary map... These are some kind of setting, why are they even part of the JSON map? // so use a fixed value convertedObject.playerRoomColors = [ { color24RGB: [255, 0, 0] }, { color24RGB: [255, 255, 255] }, ]; convertedObject.playerRoomInnerDiameterPercentage = 70; convertedObject.playerRoomOuterDiameterPercentage = 120; convertedObject.playerRoomStyle = 0; }; module.exports = exportMap;