UNPKG

prismarine-chunk

Version:
302 lines (257 loc) 10 kB
const ChunkColumn13 = require('../1.3/ChunkColumn') const SubChunk = require('./SubChunk') const BiomeSection = require('./BiomeSection') const ProxyBiomeSection = require('./ProxyBiomeSection') const { BlobType, BlobEntry } = require('../common/BlobCache') const { StorageType } = require('../common/constants') const Stream = require('../common/Stream') const nbt = require('prismarine-nbt') class ChunkColumn180 extends ChunkColumn13 { Section = SubChunk bounds = this.setBounds(-4, 20) // World height changed // BIOMES getBiome (pos) { return new this.Biome(this.getBiomeId(pos)) } setBiome (pos, biome) { this.setBiomeId(pos, biome.id) } getBiomeId (pos) { const Y = pos.y >> 4 const sec = this.biomes[this.co + Y] return sec?.getBiomeId(pos.x, pos.y & 0xf, pos.z) ?? 0 } setBiomeId (pos, biomeId) { const Y = pos.y >> 4 let sec = this.biomes[this.co + Y] if (!sec) { this.biomes[this.co + Y] = sec = new BiomeSection(this.registry, Y) } else if (!sec.setBiomeId) { this.biomes[this.co + Y] = sec = sec.promote(Y) } sec.setBiomeId(pos.x, pos.y & 0xf, pos.z, biomeId) this.biomesUpdated = true } // Load 3D biome data loadBiomes (buf, storageType) { if (buf instanceof Buffer) buf = new Stream(buf) this.biomes = [] let last for (let y = this.minCY; buf.peek(); y++) { if (buf.peek() === 0xff) { // re-use the last data if (!last) throw new Error('No last biome') const biome = new ProxyBiomeSection(this.registry, last) this.biomes.push(biome) // skip peek'ed buf.readByte() } else { const biome = new BiomeSection(this.registry, y) biome.read(storageType, buf) last = biome this.biomes.push(biome) } } } writeBiomes (stream) { for (let i = 0; i < (this.worldHeight >> 4); i++) { let biomeSection = this.biomes[i] if (!biomeSection) { if (this.biomes[i - 1]) { this.biomes[i] = biomeSection = new ProxyBiomeSection(this.registry, this.biomes[i - 1]) } else { this.biomes[i] = biomeSection = new BiomeSection(this.registry, i) } } biomeSection.export(StorageType.Runtime, stream) } } // CACHING /** * Encodes this chunk column for the network with no caching * @param buffer Full chunk buffer */ async networkEncodeNoCache () { const stream = new Stream() for (const biomeSection of this.biomes) { biomeSection.export(StorageType.Runtime, stream) } const biomeBuf = stream.getBuffer() return Buffer.concat([ biomeBuf, Buffer.from([0]) // border blocks count ]) } /** * Encodes this chunk column for use over network with caching enabled * * @param blobStore The blob store to write chunks in this section to * @returns {Promise<Buffer[]>} The blob hashes for this chunk, the last one is biomes, rest are sections */ async networkEncodeBlobs (blobStore, includeBiomes) { const blobHashes = [] if (includeBiomes) { if (this.biomesUpdated || !this.biomesHash || !blobStore.get(this.biomesHash.toString())) { const stream = new Stream() for (const biomeSection of this.biomes) { biomeSection.export(StorageType.Runtime, stream) } const biomeBuf = stream.getBuffer() await this.updateBiomeHash(biomeBuf) this.biomesUpdated = false blobStore.set(this.biomesHash.toString(), new BlobEntry({ x: this.x, z: this.z, type: BlobType.Biomes, buffer: this.biomes })) } blobHashes.push({ hash: this.biomesHash, type: BlobType.Biomes }) } return blobHashes } async networkEncode (blobStore) { const blobs = await this.networkEncodeBlobs(blobStore, true, false) return { blobs, // cache blobs payload: Buffer.concat([ // non-cached stuff Buffer.from([0]) // border blocks ]) } } networkDecodeNoCache (buffer, sectionCount) { const stream = buffer instanceof Buffer ? new Stream(buffer) : buffer if (sectionCount !== -1 && sectionCount !== -2) { // In 1.18+, with sectionCount as -1/-2 we only get the biomes here this.sections = [] for (let i = 0; i < sectionCount; i++) { // in 1.17.30+, chunk index is sent in payload const section = new SubChunk(this.registry, this.Block, { y: i, subChunkVersion: this.subChunkVersion }) section.decode(StorageType.Runtime, stream) this.setSection(i, section) } } this.loadBiomes(stream, StorageType.Runtime) const borderBlocks = stream.readBuffer(stream.readZigZagVarInt()) if (borderBlocks.length) { throw new Error(`Can't handle border blocks (length: ${borderBlocks.length})`) } let startOffset = stream.readOffset while (stream.peek() === 0x0A) { const { data, metadata } = nbt.protos.littleVarint.parsePacketBuffer('nbt', stream.buffer, startOffset) stream.readOffset += metadata.size startOffset += metadata.size this.addBlockEntity(data) } } /** * Decodes cached chunks sent over the network * @param blobs The blob hashes sent in the Chunk packet * @param blobStore Our blob store for cached data * @param {Buffer} payload The rest of the non-cached data * @returns {CCHash[]} A list of hashes we don't have and need. If len > 0, decode failed. */ async networkDecode (blobs, blobStore, payload) { if (payload) { const stream = new Stream(payload) const borderblocks = stream.readBuffer(stream.readZigZagVarInt()) if (borderblocks.length) { throw new Error(`Can't handle border blocks (length: ${borderblocks.length})`) } } // Block NBT data is now inside individual sections const misses = [] for (const blob of blobs) { if (!blobStore.has(blob.toString())) { misses.push(blob) } } if (misses.length > 0) { // missing blobs, call this again once the server replies with our MISSing // blobs and don't try to load this column until we have all the data return misses } // Reset the sections & length, when we add a section, it will auto increment this.sections = [] for (const blob of blobs) { const entry = blobStore.get(blob.toString()) if (entry.type === BlobType.Biomes) { const stream = new Stream(entry.buffer) this.loadBiomes(stream, StorageType.NetworkPersistence, blob) } else if (entry.type === BlobType.ChunkSection) { throw new Error("Can't accept chunk sections in networkDecode, these Blobs should be sent as individual sections") } else { throw Error('Unknown blob type: ' + entry.type) } } return misses // return empty array if everything was loaded } async networkDecodeSubChunkNoCache (y, buffer) { const stream = new Stream(buffer) const section = new SubChunk(this.registry, this.Block, { y, subChunkVersion: this.subChunkVersion }) section.decode(StorageType.Runtime, stream) this.setSection(y, section) let startOffset = stream.readOffset while (stream.peek() === 0x0A) { const { data, metadata } = nbt.protos.littleVarint.parsePacketBuffer('nbt', buffer, startOffset) stream.readOffset += metadata.size startOffset += metadata.size this.addBlockEntity(data) } } async networkEncodeSubChunkNoCache (y) { const tiles = this.getSectionBlockEntities(y) const section = this.getSectionAtIndex(y) const subchunk = await section.encode(StorageType.Runtime, false, this.compactOnSave) const tileBufs = [] for (const tile of tiles) { tileBufs.push(nbt.writeUncompressed(tile, 'littleVarint')) } return Buffer.concat([subchunk, ...tileBufs]) } async networkDecodeSubChunk (blobs, blobStore, payload) { if (payload) { const nbtStream = new Stream(payload) let startOffset = 0 while (nbtStream.peekUInt8() === 0x0A) { const { data, metadata } = nbt.protos.littleVarint.parsePacketBuffer('nbt', payload, startOffset) nbtStream.readOffset += metadata.size startOffset += metadata.size this.addBlockEntity(data) } } const misses = [] for (const blob of blobs) { if (!blobStore.has(blob.toString())) { misses.push(blob) } } if (misses.length > 0) { // missing blobs, call this again once the server replies with our MISSing // blobs and don't try to load this column until we have all the data return misses } // Reset the sections & length, when we add a section, it will auto increment this.sections = [] this.sectionsLen = 0 for (const blob of blobs) { const entry = blobStore.get(blob.toString()) const stream = new Stream(entry.buffer) const subchunk = new SubChunk(this.registry, this.Block, { y: blob.y, subChunkVersion: this.subChunkVersion }) await subchunk.decode(StorageType.Runtime, stream) this.setSection(subchunk.y, subchunk) } return misses // return empty array if everything was loaded } async networkEncodeSubChunk (y, blobStore) { const tiles = this.getSectionBlockEntities(y) const section = this.getSectionAtIndex(y) if (section.updated) { const terrainBuffer = await section.encode(StorageType.Runtime, true, this.compactOnSave) // note Runtime, not NetworkPersistence const blob = new BlobEntry({ x: this.x, y: section.y, z: this.z, type: BlobType.ChunkSection, buffer: terrainBuffer }) blobStore.set(section.hash.toString(), blob) } const tileBufs = [] for (const tile of tiles) { tileBufs.push(nbt.writeUncompressed(tile, 'littleVarint')) } return [section.hash, Buffer.concat(tileBufs)] } toObject () { return { ...super.toObject(), version: this.registry.version.minecraftVersion } } } module.exports = ChunkColumn180