import * as CONST from '../constants'; import * as segmentHandlers from './segmentHandlers/'; import { bytesToAscii } from './segmentHandlers/common'; export default class sc2 { import (buffer) { let size = CONST.MAP_SIZE; let x = 0; let y = 0; let map = { cells: [], _segmentData: {} }; for (let i = 0; i < size * size; i++) { map.cells.push({ x: x, y: y, _segmentData: {} }); if (y === 127) { y = 0; x += 1; } else { y += 1; } } // iterate through each data segment and parse let bytes = buffer.subarray(12); let segments = this.splitSegments(bytes); Object.keys(segments).sort().forEach((title) => { let type = title; // handle special case for scenario // text sections if (title.startsWith('TEXT')) type = 'TEXT'; let handler = segmentHandlers[type]; if (handler) handler(segments[title], map); else console.log('Unknown Segment:',type); }); // city metadata map.info = { name: map._segmentData.CNAM.text || 'Default', height: size, width: size, rotation: map._segmentData.MISC.rotation, waterLevel: map._segmentData.MISC.globalSeaLevel, }; // populate data cells for (let i = 0; i < map.cells.length; i++) { let cell = map.cells[i]; let data = cell._segmentData; cell.tiles = { _list: [] }; if (data.XTER.terrain) cell.tiles._list.push({ id: data.XTER.terrain, type: CONST.T_TERRAIN }); if (data.XTER.water) cell.tiles._list.push({ id: data.XTER.water, type: CONST.T_WATER }); if (cell.x == 0 || cell.x == 127 || cell.y == 0 || cell.y == 127) if (data.XTER.terrain) cell.tiles._list.push({ id: data.XTER.terrain, type: CONST.T_EDGE }); //if (data.XTER.terrain) cell.tiles._list.push({ id: data.XTER.terrain, type: CONST.T_HEIGHTMAP }); //if (data.XZON.zone) cell.tiles._list.push({ id: data.XZON.zone, type: CONST.T_ZONE }); if (data.XBLD.id != 0) cell.tiles._list.push(data.XBLD); //if (data.XUND.subway) cell.tiles._list.push({ id: data.XUND.subway, type: CONST.T_SUBWAY }); //if (data.XUND.pipes) cell.tiles._list.push({ id: data.XUND.pipes, type: CONST.T_PIPES }); cell.corners = { left: data.XZON.left, top: data.XZON.top, bottom: data.XZON.bottom, right: data.XZON.right, none: data.XZON.none, }; cell.zone = { id: data.XZON.zone, type: data.XZON.zoneType, }; cell.z = data.ALTM.altitude; cell.rotate = data.XBIT.rotate; cell.power = { wired: data.XBIT.wired, powered: data.XBIT.powered, }; cell.pipes = { piped: data.XBIT.piped, watered: data.XBIT.watered, }; cell.water = { type: data.XTER.type, covered: data.XBIT.waterCovered, salt: data.XBIT.saltWater, }; map.cells[i] = cell; } return map; } splitSegments (bytes) { let segments = {}; while (bytes.length > 0) { let title = bytesToAscii(bytes.subarray(0x00, 0x04)); let length = new DataView(bytes.subarray(0x04, 0x08).buffer).getUint32(bytes.subarray(0x04, 0x08).byteOffset); let contents = bytes.subarray(0x08, 0x08 + length); if (!['ALTM','CNAM','TEXT','PICT','SCEN','TMPL'].includes(title)) contents = this.decompressSegment(contents); // can have multilpe TEXT segments if (title == 'TEXT') { let type = bytes.subarray(0x08, 0x09); if (type == 0x80) title = 'TEXT_1'; else if (type == 0x81) title = 'TEXT_2'; } segments[title] = contents; bytes = bytes.subarray(0x08 + length); } return segments; } decompressSegment (bytes) { let output = []; let dataCount = 0; for (let i = 0; i < bytes.length; i++) { if (dataCount > 0) { output.push(bytes[i]); dataCount -= 1; continue; } // data bytes if (bytes[i] < 128) { dataCount = bytes[i]; // run-length encoded byte } else { let repeatCount = bytes[i] - 127; let repeated = bytes[i + 1]; for (let i = 0; i < repeatCount; i++) output.push(repeated); // skip the next byte i += 1; } } return Uint8Array.from(output); } }