prismarine-provider-anvil
Version:
Anvil Storage Provider implementation.
268 lines (245 loc) • 9.11 kB
JavaScript
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 }
}
}