UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

924 lines (922 loc) 45.9 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. Object.defineProperty(exports, "__esModule", { value: true }); exports.SubChunkFormatType = void 0; const Log_1 = require("../core/Log"); const BlockPalette_1 = require("./BlockPalette"); const DataUtilities_1 = require("../core/DataUtilities"); const BlockCube_1 = require("../minecraft/BlockCube"); const Block_1 = require("./Block"); const NbtBinary_1 = require("./NbtBinary"); const GenericBlockActor_1 = require("./blockActors/GenericBlockActor"); const BlockActorFactory_1 = require("./blockActors/BlockActorFactory"); const Database_1 = require("./Database"); const CHUNK_X_SIZE = 16; const CHUNK_Z_SIZE = 16; const SUBCHUNK_Y_SIZE = 16; const MAX_LEGACY_Y = 128; var SubChunkFormatType; (function (SubChunkFormatType) { SubChunkFormatType[SubChunkFormatType["paletteFrom1dot2dot13"] = 0] = "paletteFrom1dot2dot13"; SubChunkFormatType[SubChunkFormatType["subChunk1dot0"] = 1] = "subChunk1dot0"; })(SubChunkFormatType = exports.SubChunkFormatType || (exports.SubChunkFormatType = {})); class WorldChunk { get absoluteMinY() { return this.absoluteZeroY; } get minY() { return this.absoluteZeroY + this.minSubChunkIndex * 16; } get maxY() { return this.absoluteZeroY + (this.maxSubChunkIndex + 1) * 16; } get absoluteMaxY() { return this.absoluteZeroY + this.subChunks.length * 16; } get blockActors() { if (!this.blockActorsEnsured) { this.ensureBlockActors(); } return this._blockActors; } constructor(world, inX, inZ) { this.blockActorKeys = []; this._blockActorsRelLoc = []; this._blockActors = []; this.blockActorsEnsured = false; this.absoluteZeroY = -512; this.chunkMinY = 0; this.x = 0; this.z = 0; this.maxSubChunkIndex = -512; // set it very low this.minSubChunkIndex = 512; // set it very high this.world = world; this.subChunks = []; this.subChunkFormatType = []; this.blockPalettes = []; this.bitsPerBlock = []; this.blockDataStart = []; this.auxBlockPalettes = []; this.auxBitsPerBlock = []; this.auxBlockDataStart = []; this.pendingSubChunksToProcess = []; this.actorDigests = []; for (let i = 0; i < 64; i++) { this.pendingSubChunksToProcess[i] = false; } this.x = inX; this.z = inZ; } clearCachedData() { for (let i = 0; i < 64; i++) { if (this.subChunks[i] !== undefined) { this.blockDataStart[i] = -1; this.bitsPerBlock[i] = -1; this.blockPalettes[i] = undefined; this.pendingSubChunksToProcess[i] = true; } } this.blockTops = undefined; this.blockActorsEnsured = false; this._blockActorsRelLoc = []; this._blockActors = []; } addActorDigest(digest) { this.actorDigests.push(digest); } translateSubChunkIndex(storageSubChunk) { Log_1.default.assert((storageSubChunk >= 0 && storageSubChunk <= 31) || (storageSubChunk >= 224 && storageSubChunk <= 255), "Unexpected subchunk index (" + storageSubChunk + ")"); if (storageSubChunk >= 224) { return storageSubChunk - 224; } return storageSubChunk + 32; } processSubChunk(index) { if (this.pendingSubChunksToProcess[index] === true) { this.pendingSubChunksToProcess[index] = false; this.parseSubChunk(index); } } addKeyValue(keyValue) { let keyBytes = keyValue.keyBytes; if (keyBytes) { const dimExtensionBytes = keyBytes.length >= 13 ? 4 : 0; const val = keyBytes[8 + dimExtensionBytes]; // disabling the "duplicate unexpected versions" since this assumption is violated in C&C R17 world switch (val) { case 43: Log_1.default.assert(keyValue.value !== undefined && keyValue.value.length > 512, "Unexpected length for a type 43 record."); if (keyValue.value && keyValue.value.length >= 512) { this.blockTops = []; for (let i = 0; i < 16; i++) { const arr = []; for (let j = 0; j < 16; j++) { let val = keyValue.value[j * 32 + i * 2] + keyValue.value[j * 32 + (i * 2 + 1)] * 256; arr.push(val - 65); // subtracting - 65 is arbitrary here } this.blockTops.push(arr); } } break; case 115: // not sure what chunk #115 is, or if this is a parsing bug. observed to be one byte with a value of 0 // Log.assert(false, "Unexpected type 115 record."); break; case 118: // 118 = legacy version Log_1.default.assert(keyValue.value !== undefined && (keyValue.value.length === 0 || keyValue.value.length === 1), "Unexpected type 118 record."); if (keyValue.value && keyValue.value.length === 1) { this.legacyVersion = keyValue.value[0]; } else if (keyValue.value && keyValue.value.length === 0) { this.legacyVersion = undefined; } break; case 44: // version this.chunkVersion = keyValue; break; case 45: // data2d this.biomesAndElevation = keyValue; break; case 46: // data2d legacy // Log.assert(false, "Data 2D legacy (NYI)."); break; case 47: // subchunk prefix let subChunkIndex = this.translateSubChunkIndex(keyBytes[9 + dimExtensionBytes]); if (subChunkIndex < 0) { Log_1.default.fail("Unexpected sub chunk index."); return; } if (this.subChunks[subChunkIndex] !== undefined) { // Log.fail("Unexpected subchunk already defined."); } if (!keyValue.value || keyValue.value.length <= 0) { Log_1.default.assert(this.subChunks[subChunkIndex] === undefined, "Empty subchunk defined."); return; } this.subChunks[subChunkIndex] = keyValue; this.maxSubChunkIndex = Math.max(this.maxSubChunkIndex, subChunkIndex); this.minSubChunkIndex = Math.min(this.minSubChunkIndex, subChunkIndex); this.pendingSubChunksToProcess[subChunkIndex] = true; break; case 48: // legacy terrain const bytes = keyValue.value; if (bytes && bytes.length > 0) { Log_1.default.assert(bytes.length === 83200, "LegacyTerrain record should be 83,200 bytes"); this.legacyTerrainBytes = bytes; } break; case 49: // block entity this.blockActorKeys.push(keyValue); this.blockActorsEnsured = false; break; case 50: // entity // Log.assert(!this.entity, "Unexpected multiple entities."); this.entity = keyValue; break; case 51: // pending ticks //Log.assert(!this.pendingTicks, "Unexpected multiple pending ticks."); this.pendingTicks = keyValue; break; case 52: // legacy block extra data // Log.assert(false, "Legacy block extra data - NYI"); break; case 53: // biome state //Log.assert(!this.biomeState, "Unexpected multiple biome states."); this.biomeState = keyValue; break; case 54: // finalized state // Log.assert(!this.finalizedState, "Unexpected multiple states."); this.finalizedState = keyValue; break; case 55: // conversion data. data that the converter provides, that are used at runtime for things like blending. no longer used? break; case 56: // EDU border blocks? break; case 57: // spawn areas (hard coded spawners) break; case 58: // random tick break; case 59: // check sums // Log.assert(!this.checksumKey, "Unexpected multiple states."); this.checksumKey = keyValue; break; case 60: // generation seed break; case 61: // generated pre caves and cliffs blending (unused) break; case 62: // blending biome height (unused) break; case 63: // metadata hash break; case 64: // blending data break; case 65: // actor digest version break; case 119: // ?? //Log.assert(false, "Unexpected type 119 data."); break; default: Log_1.default.debugAlert("Unsupported chunk type: " + val); } } } clearKeyValue(keyBytes) { if (keyBytes) { const dimExtensionBytes = keyBytes.length > 18 || keyBytes.length === 13 || keyBytes.length === 14 ? 4 : 0; const val = keyBytes.charCodeAt(8 + dimExtensionBytes); // disabling the "duplicate unexpected versions" since this assumption is violated in C&C R17 world switch (val) { case 43: // not sure what chunk #43 is, or if this is a parsing bug. observed to be 578 bytes. "data3d" break; case 115: // not sure what chunk #61 is, or if this is a parsing bug. observed to be one byte with a value of 0 break; case 118: // 118 = legacy version case 44: // version // Log.assert(!this.chunkVersion, "Unexpected multiple chunk versions."); this.chunkVersion = undefined; break; case 45: // data2d // Log.assert(!this.biomesAndElevation, "Unexpected multiple biomes and elevations."); this.biomesAndElevation = undefined; break; case 46: // data2d legacy break; case 47: // subchunk prefix break; case 48: // legacy terrain this.legacyTerrainBytes = undefined; break; case 49: // block entity break; case 50: // entity // Log.assert(!this.entity, "Unexpected multiple entities."); this.entity = undefined; break; case 51: // pending ticks //Log.assert(!this.pendingTicks, "Unexpected multiple pending ticks."); this.pendingTicks = undefined; break; case 52: // legacy block extra data break; case 53: // biome state //Log.assert(!this.biomeState, "Unexpected multiple biome states."); this.biomeState = undefined; break; case 54: // finalized state // Log.assert(!this.finalizedState, "Unexpected multiple states."); this.finalizedState = undefined; break; case 55: // conversion data. data that the converter provides, that are used at runtime for things like blending. no longer used? break; case 56: // EDU border blocks? break; case 57: // spawn areas (hard coded spawners) break; case 58: // random tick break; case 59: // check sums // Log.assert(!this.checksumKey, "Unexpected multiple states."); this.checksumKey = undefined; break; case 60: // generation seed break; case 61: // generated pre caves and cliffs blending (unused) break; case 62: // blending biome height (unused) break; case 63: // metadata hash break; case 64: // blending data break; case 65: // actor digest version break; case 72: // actor digest version break; case 119: // ?? break; default: throw new Error("Unsupported chunk type: " + val); } } } ensureBlockActors() { if (!this._blockActorsRelLoc || this.blockActorsEnsured) { return; } this._blockActorsRelLoc = []; this._blockActors = []; for (const keyValue of this.blockActorKeys) { if (keyValue.value && keyValue.value.length > 0) { const tag = new NbtBinary_1.default(); tag.context = this.world.name + " chunk at x:" + this.x * 16 + " z:" + this.z * 16; try { tag.fromBinary(keyValue.value, true, false, 0, true, true); } catch (e) { Log_1.default.error("Could not parse a block actor."); } if (tag.roots) { for (let i = 0; i < tag.roots.length; i++) { const ba = new GenericBlockActor_1.default(tag.roots[i]); if (ba.x !== undefined && ba.z !== undefined && ba.y !== undefined && ba.x >= this.x * 16 && ba.x < (this.x + 1) * 16 && ba.z >= this.z * 16 && ba.z < (this.z + 1) * 16) { let actorRelX = ba.x % 16; let actorRelZ = ba.z % 16; if (actorRelX < 0) { actorRelX = 16 + actorRelX; } if (actorRelZ < 0) { actorRelZ = 16 + actorRelZ; } if (ba.id) { const specificBa = BlockActorFactory_1.default.create(ba.id, tag.roots[i]); if (specificBa) { specificBa.load(); if (!this._blockActorsRelLoc[actorRelX]) { this._blockActorsRelLoc[actorRelX] = []; } if (!this._blockActorsRelLoc[actorRelX][ba.y]) { this._blockActorsRelLoc[actorRelX][ba.y] = []; } this._blockActorsRelLoc[actorRelX][ba.y][actorRelZ] = specificBa; if (specificBa.x !== undefined && specificBa.y !== undefined && specificBa.z !== undefined) { // this.removeBlockActorAtLoc(specificBa.x, specificBa.y, specificBa.z); } this._blockActors.push(specificBa); } Log_1.default.assert(specificBa !== undefined, "Could not find an actor implementation for '" + ba.id + "'"); } } } } } } this.blockActorsEnsured = true; } removeBlockActorAtLoc(x, y, z) { const newBlockActors = []; for (const blockActor of this._blockActors) { if (blockActor.x !== x || blockActor.y !== y || blockActor.z !== z) { newBlockActors.push(blockActor); } } this._blockActors = newBlockActors; } getSubChunkCube(subChunkId) { const bc = new BlockCube_1.default(); bc.maxX = CHUNK_X_SIZE; bc.maxY = SUBCHUNK_Y_SIZE; bc.maxZ = CHUNK_Z_SIZE; this.fillCube(bc, 0, 0, 0, 16, 16, 16, 0, subChunkId * SUBCHUNK_Y_SIZE, 0); return bc; } fillCubeLegacy(cube, cubeX, cubeY, cubeZ, maxCubeX, maxCubeY, maxCubeZ, internalOffsetX, internalOffsetY, internalOffsetZ) { Log_1.default.assert(cubeX >= 0 && cubeY >= 0 && cubeZ >= 0 && maxCubeX > cubeX && maxCubeY > cubeY && maxCubeZ > cubeZ && internalOffsetX < CHUNK_X_SIZE && internalOffsetZ < CHUNK_Z_SIZE && this.legacyTerrainBytes !== undefined, "Fill cube legacy not within bounds."); if (!this.legacyTerrainBytes) { return; } for (let iX = cubeX; iX < maxCubeX && iX - cubeX + internalOffsetX < CHUNK_X_SIZE; iX++) { const inChunkX = iX - cubeX + internalOffsetX; const plane = cube.x(iX); for (let iY = cubeY; iY < maxCubeY && iY - cubeY + internalOffsetY < 128; iY++) { const blockLine = plane.y(iY); const inChunkY = iY - cubeY + internalOffsetY; for (let iZ = cubeZ; iZ < maxCubeZ && iZ - cubeZ + internalOffsetZ < CHUNK_Z_SIZE; iZ++) { const inChunkZ = iZ - cubeZ + internalOffsetZ; const byte = this.legacyTerrainBytes[inChunkX * 128 * 16 + inChunkZ * 128 + inChunkY]; if (byte) { blockLine.z(iZ).copyFrom(Block_1.default.fromLegacyId(byte)); } } } } } fillCube(cube, cubeX, cubeY, cubeZ, maxCubeX, maxCubeY, maxCubeZ, internalOffsetX, internalOffsetY, internalOffsetZ) { if (this.legacyTerrainBytes) { this.fillCubeLegacy(cube, cubeX, cubeY, cubeZ, maxCubeX, maxCubeY, maxCubeZ, internalOffsetX, internalOffsetY, internalOffsetZ); return; } Log_1.default.assert(cubeX >= 0 && cubeY >= 0 && cubeZ >= 0 && maxCubeX > cubeX && maxCubeY > cubeY && maxCubeZ > cubeZ && internalOffsetX < CHUNK_X_SIZE && internalOffsetZ < CHUNK_Z_SIZE, "Fill cube not within bounds."); const zHeight = maxCubeY - cubeY; const initialChunkId = this.getSubChunkIndexFromY(internalOffsetY); const finalChunkId = this.getSubChunkIndexFromY(internalOffsetY + zHeight); Log_1.default.assert(initialChunkId >= 0 && finalChunkId >= initialChunkId, "WCFC"); for (let i = 0; i <= finalChunkId - initialChunkId; i++) { const subChunkId = initialChunkId + i; const subChunk = this.subChunks[subChunkId]; if (subChunk) { const subChunkYExtent = this.getStartYFromSubChunkIndex(subChunkId + 1); let cubeYStartForThisSubChunk = 0; if (i >= 1) { cubeYStartForThisSubChunk = 16 - ((Math.abs(this.absoluteZeroY) + internalOffsetY) % 16); } if (i >= 2) { cubeYStartForThisSubChunk += (i - 1) * 16; } if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) { const blockTemplates = []; const bytes = subChunk.value; if (bytes) { Log_1.default.assert(bytes.length === 10251 || bytes.length === 10241 || bytes.length === 6145, "Expected 6145 or 10241 bytes for a legacy subchunk. (" + bytes.length + ")"); for (let i = 0; i < 4096; i++) { let blockTypeIndex = bytes[1 + i]; let blockAuxIndex = bytes[4097 + i]; let templateIndex = blockTypeIndex * 256 + blockAuxIndex; if (!blockTemplates[templateIndex]) { const blockType = Database_1.default.getBlockTypeByLegacyId(blockTypeIndex); if (!blockType || !blockType.typeId) { throw new Error("Expected a block type for index " + blockTypeIndex); } const block = new Block_1.default("minecraft:" + blockType.typeId); block.data = blockAuxIndex; blockTemplates[templateIndex] = block; } cube .x(i % 16) .y(Math.floor(i / 256)) .z(Math.floor(i / 16)) .copyFrom(blockTemplates[templateIndex]); } } } else { const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId]; const bpw = Math.floor(32 / subChunkBitsPerBlock); const subChunkBlockDataStart = this.blockDataStart[subChunkId]; const bytes = subChunk.value; const blockPalette = this.blockPalettes[subChunkId]; if (bytes && blockPalette) { for (let iX = cubeX; iX < maxCubeX && iX - cubeX + internalOffsetX < CHUNK_X_SIZE; iX++) { const inChunkX = iX - cubeX + internalOffsetX; const plane = cube.x(iX); const blockIndexXStart = inChunkX * 256; for (let iY = cubeY + cubeYStartForThisSubChunk; iY < maxCubeY && iY - cubeY + internalOffsetY < subChunkYExtent; iY++) { const inSubChunkY = (Math.abs(this.absoluteZeroY) + (iY - cubeY + internalOffsetY)) % 16; Log_1.default.assert(inSubChunkY >= 0, "WCFCA"); const blockLine = plane.y(iY); for (let iZ = cubeZ; iZ < maxCubeZ && iZ - cubeZ + internalOffsetZ < CHUNK_Z_SIZE; iZ++) { const inChunkZ = iZ - cubeZ + internalOffsetZ; const blockWordByteStart = subChunkBlockDataStart + Math.floor((blockIndexXStart + inChunkZ * 16 + inSubChunkY) / bpw) * 4; const blocksIn = (blockIndexXStart + inChunkZ * 16 + inSubChunkY) % bpw; let word = DataUtilities_1.default.getUnsignedInteger(bytes[blockWordByteStart], bytes[blockWordByteStart + 1], bytes[blockWordByteStart + 2], bytes[blockWordByteStart + 3], true); word >>>= subChunkBitsPerBlock * blocksIn; let value = 0; for (let i = 0; i < subChunkBitsPerBlock; i++) { let inc = word % 2; inc <<= i; value += inc; word >>>= 1; } if (blockPalette.blocks.length > 0) { Log_1.default.assert(value < blockPalette.blocks.length, "Unexpected block index."); const block = blockPalette.blocks[value]; if (block) { blockLine.z(iZ).copyFrom(block); } } } } } } } } } } getTopBlockY(x, z) { if (!this.blockTops) { this.determineBlockTops(); } if (!this.blockTops) { throw new Error("Unexpected block top error."); } return this.blockTops[x][z]; } getTopBlock(x, z) { if (!this.blockTops) { this.determineBlockTops(); } if (!this.blockTops) { throw new Error("Unexpected block top error."); } return this.getBlock(x, this.blockTops[x][z], z); } _getBlockLegacy(x, y, z) { if (!this.legacyTerrainBytes || z < 0 || x < 0 || y < 0) { throw new Error(); } const byte = this.legacyTerrainBytes[x * 128 * 16 + z * 128 + y]; return Block_1.default.fromLegacyId(byte); } _getBlockLegacyList() { if (!this.legacyTerrainBytes) { throw new Error(); } const blocks = []; for (let y = 0; y < MAX_LEGACY_Y; y++) { for (let z = 0; z < 16; z++) { for (let x = 0; x < 16; x++) { const byte = this.legacyTerrainBytes[x * 128 * 16 + z * 128 + y]; blocks.push(Block_1.default.fromLegacyId(byte)); } } } return blocks; } doesBlockPaletteExist(y) { if (this.legacyTerrainBytes) { return true; } const subChunkId = this.getSubChunkIndexFromY(y); const blockPalettes = this.blockPalettes[subChunkId]; if (blockPalettes) { return true; } return false; } // x and z should be between 0 and 15 getBlock(x, y, z) { if (y < this.absoluteZeroY) { return undefined; } Log_1.default.assert(x >= 0 && x < 16 && z >= 0 && z < 16, "Retrieving an out-of-range block from a chunk."); if (this.legacyTerrainBytes) { return this._getBlockLegacy(x, y, z); } const subChunkId = this.getSubChunkIndexFromY(y); if (this.pendingSubChunksToProcess[subChunkId] === true) { this.processSubChunk(subChunkId); } // legacy subchunk format 1.0 -> 1.2.13 if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) { const subChunk = this.subChunks[subChunkId]; if (subChunk === undefined) { return undefined; } const bytes = subChunk.value; if (bytes) { const inSubChunkY = y - this.getStartYFromSubChunkIndex(subChunkId); Log_1.default.assert(inSubChunkY >= 0 && inSubChunkY < 16, "Unexpected Y for a sub chunk (" + inSubChunkY + ")"); Log_1.default.assert(bytes.length === 10251 || bytes.length === 10241 || bytes.length === 6145, "1.00 subchunk format should be 6145 or 10241 bytes. (" + bytes.length + ")"); const blockTypeIndex = bytes[1 + (inSubChunkY + z * 16 + x * 256)]; const blockAuxIndex = bytes[4097 + (inSubChunkY + z * 16 + x * 256)]; const baseType = Database_1.default.getBlockTypeByLegacyId(blockTypeIndex); Log_1.default.assertDefined(baseType.typeId); const block = new Block_1.default("minecraft:" + baseType.typeId); block.data = blockAuxIndex; return block; } return undefined; } const index = this.getBlockPaletteIndex(x, y, z); if (index === undefined) { return undefined; } const blockPalettes = this.blockPalettes[subChunkId]; if (!blockPalettes) { return undefined; } const blocks = blockPalettes.blocks; Log_1.default.assert(index < blocks.length, "Unexpected block index"); return blocks[index]; } getBlockList() { if (this.legacyTerrainBytes) { return this._getBlockLegacyList(); } const blocks = []; for (let subChunkId = this.minSubChunkIndex; subChunkId < this.maxSubChunkIndex; subChunkId++) { const subChunk = this.subChunks[subChunkId]; if (subChunk !== undefined) { if (this.pendingSubChunksToProcess[subChunkId] === true) { this.processSubChunk(subChunkId); } if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) { const blockTemplates = []; const bytes = subChunk.value; if (bytes) { Log_1.default.assert(bytes.length === 10251 || bytes.length === 10241 || bytes.length === 6145 || bytes.length === 6155, "Expected 6145 or 10241 bytes for a legacy subchunk in getblock. (" + bytes.length + ")"); // 6145 bytes if the light information is omitted; // 10241 bytes if there is 2kb + 2kb of light information for (let i = 0; i < 4096; i++) { let blockTypeIndex = bytes[1 + i]; let blockAuxIndex = bytes[4097 + i]; let templateIndex = blockTypeIndex * 256 + blockAuxIndex; if (!blockTemplates[templateIndex]) { const blockType = Database_1.default.getBlockTypeByLegacyId(blockTypeIndex); if (!blockType || !blockType.typeId) { throw new Error("Expected a block type for index " + blockTypeIndex); } const block = new Block_1.default("minecraft:" + blockType.typeId); block.data = blockAuxIndex; blockTemplates[templateIndex] = block; } blocks.push(blockTemplates[templateIndex]); } } } else { let indices = this.getBlockPaletteIndexList(subChunkId); if (indices) { const blockPalettes = this.blockPalettes[subChunkId]; if (blockPalettes) { const blockTemplates = blockPalettes.blocks; for (let i = 0; i < indices.length; i++) { blocks.push(blockTemplates[indices[i]]); } } } } } } return blocks; } _determineBlockTopsLegacy() { if (!this.legacyTerrainBytes || !this.blockTops) { return; } for (let iX = 0; iX < 16; iX++) { const iXByte = iX * 128 * 16; for (let iZ = 0; iZ < 16; iZ++) { const iZByte = iZ * 128; for (let iY = 127; iY >= 0; iY--) { const byte = this.legacyTerrainBytes[iXByte + iZByte + iY]; if (byte !== 0) { this.blockTops[iX][iZ] = iY; iY = -1; } } } } } determineBlockTops() { this.blockTops = []; for (let i = 0; i < 16; i++) { const arr = []; for (let j = 0; j < 16; j++) { arr.push(-32768); } this.blockTops.push(arr); } if (this.legacyTerrainBytes) { this._determineBlockTopsLegacy(); return; } let matchCount = 0; for (let subChunkId = this.maxSubChunkIndex; subChunkId >= 0; subChunkId--) { if (this.pendingSubChunksToProcess[subChunkId] === true) { this.parseSubChunk(subChunkId); } const subChunk = this.subChunks[subChunkId]; if (subChunk !== undefined) { const bytes = subChunk.value; if (bytes !== undefined) { if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) { for (let iY = 15; iY >= 0; iY--) { for (let iZ = 0; iZ < 16; iZ++) { for (let iX = 0; iX < 16; iX++) { let blockTypeId = bytes[iX * 256 + iZ * CHUNK_Z_SIZE + iY]; if (blockTypeId !== 0 /* air */ && blockTypeId !== 37 /* flower */ && blockTypeId !== 31 /* tallgrass*/ && this.blockTops[iX][iZ] < -1024) { const yIndex = iY + this.getStartYFromSubChunkIndex(subChunkId); this.blockTops[iX][iZ] = yIndex - 1; matchCount++; if (matchCount === 256) { return; } } } } } } else { //y z x const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId]; const bpw = Math.floor(32 / subChunkBitsPerBlock); const disallowedIndices = []; const blockPals = this.blockPalettes[subChunkId]; if (blockPals) { Log_1.default.assert(blockPals.blocks !== undefined, "WCDBTA"); for (let iPal = 0; iPal < blockPals.blocks.length; iPal++) { const block = blockPals.blocks[iPal]; if (block.shortTypeName === "air" || block.shortTypeName === "flower" || block.shortTypeName === "tallgrass") { disallowedIndices.push(iPal); } } for (let iY = 15; iY >= 0; iY--) { const yIndex = iY + this.getStartYFromSubChunkIndex(subChunkId); for (let iZ = 0; iZ < 16; iZ++) { for (let iX = 0; iX < 16; iX++) { if (yIndex >= this.blockTops[iX][iZ]) { const blockIndex = iX * 256 + iZ * CHUNK_Z_SIZE + iY; const byteStart = this.blockDataStart[subChunkId] + Math.floor(blockIndex / bpw) * 4; const blocksIn = blockIndex % bpw; let word = DataUtilities_1.default.getUnsignedInteger(bytes[byteStart], bytes[byteStart + 1], bytes[byteStart + 2], bytes[byteStart + 3], true); word >>>= subChunkBitsPerBlock * blocksIn; let value = 0; for (let i = 0; i < subChunkBitsPerBlock; i++) { let inc = word % 2; inc <<= i; value += inc; word >>>= 1; } let matchesSolidIndex = true; for (let iDis = 0; iDis < disallowedIndices.length; iDis++) { if (value === disallowedIndices[iDis]) { matchesSolidIndex = false; } } if (matchesSolidIndex) { matchCount++; this.blockTops[iX][iZ] = yIndex; if (matchCount === 256) { return; } } } } } } } } } } } } getSubChunkIndexFromY(y) { return Math.floor((y - this.absoluteZeroY) / SUBCHUNK_Y_SIZE); } getStartYFromSubChunkIndex(subChunkIndex) { return subChunkIndex * SUBCHUNK_Y_SIZE + this.absoluteZeroY; } getBlockPaletteIndex(x, y, z) { Log_1.default.assert(x >= 0 && x <= 15 && z >= 0 && z <= 15, "Unexpected x/z"); const subChunkId = this.getSubChunkIndexFromY(y); const subChunk = this.subChunks[subChunkId]; if (subChunk === undefined) { return undefined; } const bytes = subChunk.value; if (bytes === undefined) { return undefined; } //y z x const subChunkY = Math.abs(y % 16); const blockIndex = x * 256 + z * CHUNK_Z_SIZE + subChunkY; const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId]; const bpw = Math.floor(32 / subChunkBitsPerBlock); const byteStart = this.blockDataStart[subChunkId] + Math.floor(blockIndex / bpw) * 4; const blocksIn = blockIndex % bpw; let word = DataUtilities_1.default.getUnsignedInteger(bytes[byteStart], bytes[byteStart + 1], bytes[byteStart + 2], bytes[byteStart + 3], true); // Log.assert(x !== 15 || y !== 80 || z !== 15); word >>>= subChunkBitsPerBlock * blocksIn; let value = 0; for (let i = 0; i < subChunkBitsPerBlock; i++) { let inc = word % 2; inc <<= i; value += inc; word >>>= 1; } return value; } getBlockPaletteIndexList(subChunkId) { const blockIndices = []; const subChunk = this.subChunks[subChunkId]; if (subChunk !== undefined) { const bytes = subChunk.value; if (bytes === undefined) { return undefined; } //y z x const subChunkY = this.absoluteZeroY + this.minSubChunkIndex * 16; for (let x = 0; x < 16; x++) { for (let z = 0; z < 16; z++) { const blockIndex = x * 256 + z * CHUNK_Z_SIZE + subChunkY; const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId]; const bpw = Math.floor(32 / subChunkBitsPerBlock); const byteStart = this.blockDataStart[subChunkId] + Math.floor(blockIndex / bpw) * 4; const blocksIn = blockIndex % bpw; let word = DataUtilities_1.default.getUnsignedInteger(bytes[byteStart], bytes[byteStart + 1], bytes[byteStart + 2], bytes[byteStart + 3], true); word >>>= subChunkBitsPerBlock * blocksIn; let value = 0; for (let i = 0; i < subChunkBitsPerBlock; i++) { let inc = word % 2; inc <<= i; value += inc; word >>>= 1; } blockIndices.push(value); } } } return blockIndices; } parseSubChunk(subChunkIndex) { const bytes = this.subChunks[subChunkIndex].value; if (bytes === undefined || bytes.length <= 0) { Log_1.default.fail("Unexpected no bytes supplied for a chunk."); return; } let bitsPerBlock = 1; const subChunkVersion = bytes[0]; // should be 1 or 8 or (9 = C&C pt 2?) // legacy subchunk format for version 1.0/1.2.13, that predates palette-ized subchunks if (subChunkVersion < 8 && subChunkVersion !== 1) { this.subChunkFormatType[subChunkIndex] = SubChunkFormatType.subChunk1dot0; this.chunkMinY = 0; this.world.chunkMinY = Math.min(this.chunkMinY, this.world.chunkMinY); return; } if (!(subChunkVersion === 1 || subChunkVersion === 8 || subChunkVersion === 9)) { Log_1.default.fail("Unexpected sub chunk version (" + subChunkVersion + ")"); return; } let storageAreas = 1; let index = 1; if (subChunkVersion !== 1) { storageAreas = bytes[1]; // is either one or two, to indicate the number of block storage areas. the second is the auxiliary "water logged" block area Log_1.default.assert(storageAreas === 1 || storageAreas === 2, "Storage areas > 2 not expected"); index++; } if (subChunkVersion === 9) { // not sure what this second byte is for. observed to be of values 0, 1, and 252 // maybe this is the minimum subchunk level? const interimVal = bytes[2]; Log_1.default.assert((interimVal >= 0 && interimVal <= 32) || (interimVal >= 224 && interimVal <= 256), "Unexpected chunk index"); index++; this.chunkMinY = -512; } else if (subChunkVersion === 8) { this.chunkMinY = 0; } if (this.chunkMinY !== undefined) { this.world.chunkMinY = Math.min(this.chunkMinY, this.world.chunkMinY); } for (let sI = 0; sI < storageAreas; sI++) { if (bytes[index] % 2 === 1) { // if version LSB is set, this indicates a non-save persistence which we should probably never see Log_1.default.unexpectedError("Unexpected non-save persistence version found."); } bitsPerBlock = bytes[index] >>> 1; index++; // have observed empty subchunks with no actual data besides NBT records, where bitsPerBlock === 0 if (bitsPerBlock === 0) { // const bp = new BlockPalette(); // index = bp.parseFromBytes(bytes, index, 1); } else { const blocksPerWord = Math.floor(32 / bitsPerBlock); const blockBytes = Math.ceil(4096 / blocksPerWord) * 4; let numPaletteEntries = DataUtilities_1.default.getUnsignedInteger(bytes[blockBytes + index], bytes[blockBytes + index + 1], bytes[blockBytes + index + 2], bytes[blockBytes + index + 3], true); if (numPaletteEntries >= 4096) { // this is an odd workaround; but it seeems like some worlds have their num palette entries listed as big endian numPaletteEntries = DataUtilities_1.default.getUnsignedInteger(bytes[blockBytes + index], bytes[blockBytes + index + 1], bytes[blockBytes + index + 2], bytes[blockBytes + index + 3], false); } Log_1.default.assert(numPaletteEntries <= 4096, "Unexpectedly large number of palette entries"); const bp = new BlockPalette_1.default(); const blockDataStartIndex = index; index = bp.parseFromBytes(bytes, blockBytes + index + 4, numPaletteEntries); if (bp.blocks.length !== numPaletteEntries) { Log_1.default.unexpectedError("Unexpected block palette count mismatch."); } Log_1.default.assert(sI !== storageAreas - 1 || index === bytes.length, "Unexpectedly didn't consume entire subchunk."); if (sI === 0) { this.blockDataStart[subChunkIndex] = blockDataStartIndex; this.bitsPerBlock[subChunkIndex] = bitsPerBlock; this.blockPalettes[subChunkIndex] = bp; this.subChunkFormatType[subChunkIndex] = SubChunkFormatType.paletteFrom1dot2dot13; } else { this.auxBlockDataStart[subChunkIndex] = blockDataStartIndex; this.auxBitsPerBlock[subChunkIndex] = bitsPerBlock; this.auxBlockPalettes[subChunkIndex] = bp; this.subChunkFormatType[subChunkIndex] = SubChunkFormatType.paletteFrom1dot2dot13; } } } } } exports.default = WorldChunk; //# sourceMappingURL=../maps/minecraft/WorldChunk.js.map