UNPKG

prismarine-provider-anvil

Version:
268 lines (245 loc) 9.11 kB
const nbt = require('prismarine-nbt') const neededBits = require('prismarine-chunk/src/pc/common/neededBits') module.exports = (mcVersion, worldVersion, noSpan) => { const BitArray = require(`prismarine-chunk/src/pc/common/BitArray${noSpan ? 'NoSpan' : ''}`) const ChunkSection = require('prismarine-chunk')(mcVersion).section return (Chunk, mcData) => { const newFormat = mcData.supportFeature('newLightingDataFormat') // 1.17+ function nbtChunkToPrismarineChunk (data) { const nbtd = nbt.simplify(data) const chunk = new Chunk() readSections(chunk, nbtd.Level.Sections) chunk.biomes = nbtd.Level.Biomes return chunk } function prismarineChunkToNbt (chunk, chunkXPos, chunkZPos) { return { name: '', type: 'compound', value: { Level: { type: 'compound', value: { Biomes: { value: chunk.biomes, type: 'intArray' }, Sections: writeSections(chunk), isLightOn: { // Vanilla server will discard light data if this is missing type: 'byte', value: 1 }, Status: { // Vanilla server will discard block data if this is missing type: 'string', value: 'full' }, shouldSave: { // Force vanilla server to regenerate missing properties like heightmaps type: 'byte', value: 1 }, xPos: { type: 'int', value: chunkXPos }, zPos: { type: 'int', value: chunkZPos } } }, DataVersion: { // Vanilla server will discard Level->Status field if this is missing type: 'int', value: worldVersion } } } } function readSections (chunk, sections) { for (const section of sections) { if (section.Palette) { readSection(chunk, section) } } } function writeSections (chunk) { const sections = [] for (let sectionY = 0; sectionY < 16; sectionY++) { const section = chunk.sections[sectionY] if (section) sections.push(writeSection(section, sectionY, chunk.blockLightSections[sectionY + 1], chunk.skyLightSections[sectionY + 1])) } return { type: 'list', value: { type: 'compound', value: sections } } } function readSection (chunk, section) { if (section.Y >= 0 && section.Y <= 16) { let chunkSection = chunk.sections[section.Y] if (!chunkSection) { chunkSection = new ChunkSection() chunk.sections[section.Y] = chunkSection chunk.sectionMask |= 1 << section.Y } readPalette(chunkSection, section.Palette) // Empty (filled with air) sections can be stored, but make the client crash if // they are sent over. Remove them as soon as possible if (chunkSection.palette.length === 1 && chunkSection.palette[0] === 0) { chunk.sections[section.Y] = null chunk.sectionMask &= ~(1 << section.Y) return } readBlocks(chunkSection, section.BlockStates) } if (section.BlockLight) { chunk.blockLightMask |= 1 << (section.Y + 1) chunk.blockLightSections[section.Y + 1] = new BitArray({ bitsPerValue: 4, capacity: 4096 }) readByteArray(chunk.blockLightSections[section.Y + 1], section.BlockLight) } if (section.SkyLight) { chunk.skyLightMask |= 1 << (section.Y + 1) chunk.skyLightSections[section.Y + 1] = new BitArray({ bitsPerValue: 4, capacity: 4096 }) readByteArray(chunk.skyLightSections[section.Y + 1], section.SkyLight) } if (newFormat) { chunk.blockLightMask = new BitArray({ bitsPerValue: 1, capacity: 16 }) chunk.skyLightMask = new BitArray({ bitsPerValue: 1, capacity: 16 }) chunk.sectionMask = new BitArray({ bitsPerValue: 1, capacity: 16 }) for (let i = 0; i < 16; i++) { if (chunk.sections[i]) chunk.sectionMask.set(i, true) if (chunk.blockLightSections[i]) chunk.blockLightMask.set(i, true) if (chunk.skyLightSections[i]) chunk.skyLightMask.set(i, true) } } } function parseValue (value, state) { if (state.type === 'enum') { return state.values.indexOf(value) } if (value === 'true') return 0 if (value === 'false') return 1 return parseInt(value, 10) } function getStateValue (states, name, value) { let offset = 1 for (let i = states.length - 1; i >= 0; i--) { const state = states[i] if (state.name === name) { return offset * parseValue(value, state) } offset *= state.num_values } return 0 } function readPalette (section, palette = []) { section.palette = [] for (const type of palette) { const name = type.Name.split(':')[1] const block = mcData.blocksByName[name] let data = 0 if (type.Properties) { for (const [key, value] of Object.entries(type.Properties)) { data += getStateValue(block.states, key, value) } } const stateId = block.minStateId + data section.palette.push(stateId) } } function readBlocks (section, blockStates = []) { section.data = section.data.resizeTo(Math.max(4, neededBits(section.palette.length - 1))) for (let i = 0; i < blockStates.length; i++) { section.data.data[i * 2] = blockStates[i][1] >>> 0 section.data.data[i * 2 + 1] = blockStates[i][0] >>> 0 } section.solidBlockCount = 0 for (let i = 0; i < 4096; i++) { if (section.data.get(i) !== 0) { section.solidBlockCount += 1 } } } function makeUInt (a, b, c, d) { return (((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | (d & 0xFF)) >>> 0 } function readByteArray (bitArray, array) { for (let i = 0; i < bitArray.data.length; i += 2) { const i4 = i * 4 bitArray.data[i + 1] = makeUInt(array[i4], array[i4 + 1], array[i4 + 2], array[i4 + 3]) bitArray.data[i] = makeUInt(array[i4 + 4], array[i4 + 5], array[i4 + 6], array[i4 + 7]) } } function writeSection (section, sectionY, blockLight, skyLight) { const nbtSection = { Y: { type: 'byte', value: sectionY }, BlockStates: writeBlocks(section.data) } if (section.palette !== null) nbtSection.Palette = writePalette(section.palette) if (blockLight !== null) nbtSection.BlockLight = writeByteArray(blockLight) if (skyLight !== null) nbtSection.SkyLight = writeByteArray(skyLight) return nbtSection } function writeValue (state, value) { if (state.type === 'enum') return state.values[value] if (state.type === 'bool') return value ? 'false' : 'true' return value + '' } function writePalette (palette) { const nbtPalette = [] for (const state of palette) { const block = mcData.blocksByStateId[state] const nbtBlock = {} if (block.states.length > 0) { let data = state - block.minStateId nbtBlock.Properties = { type: 'compound', value: {} } for (let i = block.states.length - 1; i >= 0; i--) { const prop = block.states[i] nbtBlock.Properties.value[prop.name] = { type: 'string', value: writeValue(prop, data % prop.num_values) } data = Math.floor(data / prop.num_values) } } nbtBlock.Name = { type: 'string', value: 'minecraft:' + block.name } nbtPalette.push(nbtBlock) } return { type: 'list', value: { type: 'compound', value: nbtPalette } } } function writeBlocks (blocks) { const buffer = new Array(blocks.data.length / 2) for (let i = 0; i < buffer.length; i++) { buffer[i] = [blocks.data[i * 2 + 1] << 0, blocks.data[i * 2] << 0] } return { type: 'longArray', value: buffer } } function writeByteArray (bitArray) { const buffer = [] for (let i = 0; i < bitArray.data.length; i += 2) { let a = bitArray.data[i + 1] buffer.push(((a >> 24) & 0xFF) << 24 >> 24) buffer.push(((a >> 16) & 0xFF) << 24 >> 24) buffer.push(((a >> 8) & 0xFF) << 24 >> 24) buffer.push((a & 0xFF) << 24 >> 24) a = bitArray.data[i] buffer.push(((a >> 24) & 0xFF) << 24 >> 24) buffer.push(((a >> 16) & 0xFF) << 24 >> 24) buffer.push(((a >> 8) & 0xFF) << 24 >> 24) buffer.push((a & 0xFF) << 24 >> 24) } return { type: 'byteArray', value: buffer } } return { nbtChunkToPrismarineChunk, prismarineChunkToNbt } } }