UNPKG

prismarine-chunk

Version:
343 lines (294 loc) 10.4 kB
const SmartBuffer = require('smart-buffer').SmartBuffer const BitArray = require('../common/BitArrayNoSpan') const ChunkSection = require('../common/CommonChunkSection')(BitArray) const CommonChunkColumn = require('../common/CommonChunkColumn') const constants = require('../common/constants') const varInt = require('../common/varInt') const neededBits = require('../common/neededBits') // wrap with func to provide version specific Block module.exports = (Block, mcData) => { return class ChunkColumn extends CommonChunkColumn { static get section () { return ChunkSection } constructor () { super(mcData) this.sectionMask = 0 this.sections = Array(constants.NUM_SECTIONS).fill(null) this.biomes = Array(4 * 4 * 64).fill(127) this.skyLightMask = 0 this.blockLightMask = 0 this.skyLightSections = Array(constants.NUM_SECTIONS + 2).fill(null) this.blockLightSections = Array(constants.NUM_SECTIONS + 2).fill(null) this.maxBitsPerBlock = neededBits(Object.values(mcData.blocks).reduce((high, block) => Math.max(high, block.maxStateId), 0)) } toJson () { return JSON.stringify({ biomes: this.biomes, blockEntities: this.blockEntities, sectionMask: this.sectionMask, sections: this.sections.map(section => section === null ? null : section.toJson()), skyLightMask: this.skyLightMask, blockLightMask: this.blockLightMask, skyLightSections: this.skyLightSections.map(section => section === null ? null : section.toJson()), blockLightSections: this.blockLightSections.map(section => section === null ? null : section.toJson()) }) } static fromJson (j) { const parsed = JSON.parse(j) const chunk = new ChunkColumn() chunk.biomes = parsed.biomes chunk.blockEntities = parsed.blockEntities chunk.sectionMask = parsed.sectionMask chunk.sections = parsed.sections.map(s => s === null ? null : ChunkSection.fromJson(s)) chunk.skyLightMask = parsed.skyLightMask chunk.blockLightMask = parsed.blockLightMask chunk.skyLightSections = parsed.skyLightSections.map(s => s === null ? null : BitArray.fromJson(s)) chunk.blockLightSections = parsed.blockLightSections.map(s => s === null ? null : BitArray.fromJson(s)) return chunk } initialize (func) { const p = { x: 0, y: 0, z: 0 } for (p.y = 0; p.y < constants.CHUNK_HEIGHT; p.y++) { for (p.z = 0; p.z < constants.SECTION_WIDTH; p.z++) { for (p.x = 0; p.x < constants.SECTION_WIDTH; p.x++) { const block = func(p.x, p.y, p.z) if (block === null) { continue } this.setBlock(p, block) } } } } getBlock (pos) { const section = this.sections[pos.y >> 4] const biome = this.getBiome(pos) if (!section) { return Block.fromStateId(0, biome) } const stateId = section.getBlock(toSectionPos(pos)) const block = Block.fromStateId(stateId, biome) block.light = this.getBlockLight(pos) block.skyLight = this.getSkyLight(pos) block.entity = this.getBlockEntity(pos) return block } setBlock (pos, block) { if (typeof block.stateId !== 'undefined') { this.setBlockStateId(pos, block.stateId) } if (typeof block.biome !== 'undefined') { this.setBiome(pos, block.biome.id) } if (typeof block.skyLight !== 'undefined') { this.setSkyLight(pos, block.skyLight) } if (typeof block.light !== 'undefined') { this.setBlockLight(pos, block.light) } if (block.entity) { this.setBlockEntity(pos, block.entity) } else { this.removeBlockEntity(pos) } } getBlockType (pos) { const blockStateId = this.getBlockStateId(pos) return mcData.blocksByStateId[blockStateId].id } getBlockData (pos) { const blockStateId = this.getBlockStateId(pos) return mcData.blocksByStateId[blockStateId].metadata } getBlockStateId (pos) { const section = this.sections[pos.y >> 4] return section ? section.getBlock(toSectionPos(pos)) : 0 } getBlockLight (pos) { const section = this.blockLightSections[getLightSectionIndex(pos)] return section ? section.get(getSectionBlockIndex(pos)) : 0 } getSkyLight (pos) { const section = this.skyLightSections[getLightSectionIndex(pos)] return section ? section.get(getSectionBlockIndex(pos)) : 0 } getBiome (pos) { if (pos.y < 0 || pos.y >= 256) return 0 return this.biomes[getBiomeIndex(pos)] } setBlockType (pos, id) { this.setBlockStateId(pos, mcData.blocks[id].minStateId) } setBlockData (pos, data) { this.setBlockStateId(pos, mcData.blocksByStateId[this.getBlockStateId(pos)].minStateId + data) } setBlockStateId (pos, stateId) { const sectionIndex = pos.y >> 4 if (sectionIndex < 0 || sectionIndex >= 16) return let section = this.sections[sectionIndex] if (!section) { // if it's air if (stateId === 0) { return } section = new ChunkSection({ maxBitsPerBlock: this.maxBitsPerBlock }) this.sectionMask |= 1 << sectionIndex this.sections[sectionIndex] = section } section.setBlock(toSectionPos(pos), stateId) } setBlockLight (pos, light) { const sectionIndex = getLightSectionIndex(pos) let section = this.blockLightSections[sectionIndex] if (section === null) { if (light === 0) { return } section = new BitArray({ bitsPerValue: 4, capacity: 4096 }) this.blockLightMask |= 1 << sectionIndex this.blockLightSections[sectionIndex] = section } section.set(getSectionBlockIndex(pos), light) } setSkyLight (pos, light) { const sectionIndex = getLightSectionIndex(pos) let section = this.skyLightSections[sectionIndex] if (section === null) { if (light === 0) { return } section = new BitArray({ bitsPerValue: 4, capacity: 4096 }) this.skyLightMask |= 1 << sectionIndex this.skyLightSections[sectionIndex] = section } section.set(getSectionBlockIndex(pos), light) } setBiome (pos, biome) { if (pos.y < 0 || pos.y >= 256) return this.biomes[getBiomeIndex(pos)] = biome } getMask () { return this.sectionMask } dump () { const smartBuffer = new SmartBuffer() this.sections.forEach((section, i) => { if (section !== null && !section.isEmpty()) { section.write(smartBuffer) } }) return smartBuffer.toBuffer() } loadBiomes (biomes) { this.biomes = biomes } dumpBiomes (biomes) { return this.biomes } load (data, bitMap = 0xffff) { // make smartbuffer from node buffer // so that we doesn't need to maintain a cursor const reader = SmartBuffer.fromBuffer(data) this.sectionMask |= bitMap for (let y = 0; y < constants.NUM_SECTIONS; ++y) { // does `data` contain this chunk? if (!((bitMap >> y) & 1)) { // we can skip write a section if it isn't requested continue } // keep temporary palette let palette const solidBlockCount = reader.readInt16BE() // get number of bits a palette item use const bitsPerBlock = reader.readUInt8() // check if the section uses a section palette if (bitsPerBlock <= constants.MAX_BITS_PER_BLOCK) { palette = [] // get number of palette items const numPaletteItems = varInt.read(reader) // save each palette item for (let i = 0; i < numPaletteItems; ++i) { palette.push(varInt.read(reader)) } } else { // global palette is used palette = null } // number of items in data array const dataArray = new BitArray({ bitsPerValue: bitsPerBlock > constants.MAX_BITS_PER_BLOCK ? this.maxBitsPerBlock : bitsPerBlock, capacity: 4096 }).readBuffer(reader, varInt.read(reader) * 2) const section = new ChunkSection({ data: dataArray, palette, solidBlockCount, maxBitsPerBlock: this.maxBitsPerBlock }) this.sections[y] = section } } loadLight (data, skyLightMask, blockLightMask, emptySkyLightMask = 0, emptyBlockLightMask = 0) { const reader = SmartBuffer.fromBuffer(data) // Read sky light this.skyLightMask |= skyLightMask for (let y = 0; y < constants.NUM_SECTIONS + 2; y++) { if (!((skyLightMask >> y) & 1)) { continue } varInt.read(reader) // always 2048 this.skyLightSections[y] = new BitArray({ bitsPerValue: 4, capacity: 4096 }).readBuffer(reader) } // Read block light this.blockLightMask |= blockLightMask for (let y = 0; y < constants.NUM_SECTIONS + 2; y++) { if (!((blockLightMask >> y) & 1)) { continue } varInt.read(reader) // always 2048 this.blockLightSections[y] = new BitArray({ bitsPerValue: 4, capacity: 4096 }).readBuffer(reader) } } dumpLight () { const smartBuffer = new SmartBuffer() this.skyLightSections.forEach((section, i) => { if (section !== null) { varInt.write(smartBuffer, 2048) section.writeBuffer(smartBuffer) } }) this.blockLightSections.forEach((section, i) => { if (section !== null) { varInt.write(smartBuffer, 2048) section.writeBuffer(smartBuffer) } }) return smartBuffer.toBuffer() } } } function getLightSectionIndex (pos) { return Math.floor(pos.y / 16) + 1 } function getBiomeIndex (pos) { return ((pos.y >> 2) & 63) << 4 | ((pos.z >> 2) & 3) << 2 | ((pos.x >> 2) & 3) } function toSectionPos (pos) { return { x: pos.x, y: pos.y & 15, z: pos.z } } function getSectionBlockIndex (pos) { return ((pos.y & 15) << 8) | (pos.z << 4) | pos.x }