prismarine-chunk
Version:
A class to hold chunk data for prismarine
275 lines (228 loc) • 7.89 kB
JavaScript
const SmartBuffer = require('smart-buffer').SmartBuffer
const ChunkSection = require('./ChunkSection')
const constants = require('../common/constants')
const BitArray = require('../common/BitArray')
const varInt = require('../common/varInt')
const CommonChunkColumn = require('../common/CommonChunkColumn')
const neededBits = require('../common/neededBits')
const exists = val => val !== undefined
// 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.skyLightSent = true
this.sections = Array(constants.NUM_SECTIONS).fill(null)
this.biomes = Array(
constants.SECTION_WIDTH * constants.SECTION_WIDTH
).fill(1)
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())
})
}
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))
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 block = new Block(this.getBlockType(pos), this.getBiome(pos), this.getBlockData(pos))
block.light = this.getBlockLight(pos)
block.skyLight = this.getSkyLight(pos)
block.entity = this.getBlockEntity(pos)
return block
}
setBlock (pos, block) {
if (exists(block.type)) { this.setBlockType(pos, block.type) }
if (exists(block.metadata)) { this.setBlockData(pos, block.metadata) }
if (exists(block.biome)) { this.setBiome(pos, block.biome.id) }
if (exists(block.skyLight) && this.skyLightSent) { this.setSkyLight(pos, block.skyLight) }
if (exists(block.light)) { this.setBlockLight(pos, block.light) }
if (block.entity) {
this.setBlockEntity(pos, block.entity)
} else {
this.removeBlockEntity(pos)
}
}
getBlockType (pos) {
return this.getBlockStateId(pos) >> 4
}
getBlockData (pos) {
return this.getBlockStateId(pos) & 15
}
getBlockStateId (pos) {
const section = this.sections[pos.y >> 4]
return section ? section.getBlock(toSectionPos(pos)) : 0
}
getBlockLight (pos) {
const section = this.sections[pos.y >> 4]
return section ? section.getBlockLight(toSectionPos(pos)) : 15
}
getSkyLight (pos) {
const section = this.sections[pos.y >> 4]
return section ? section.getSkyLight(toSectionPos(pos)) : 15
}
getBiome (pos) {
return this.biomes[getBiomeIndex(pos)]
}
setBlockType (pos, id) {
const data = this.getBlockData(pos)
this.setBlockStateId(pos, (id << 4) | data)
}
setBlockData (pos, data) {
const id = this.getBlockType(pos)
this.setBlockStateId(pos, (id << 4) | 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 section = this.sections[pos.y >> 4]
return section && section.setBlockLight(toSectionPos(pos), light)
}
setSkyLight (pos, light) {
const section = this.sections[pos.y >> 4]
return section && section.setSkyLight(toSectionPos(pos), light)
}
setBiome (pos, biome) {
this.biomes[getBiomeIndex(pos)] = biome
}
getMask () {
return this.sectionMask
}
// These methods do nothing, and are present only for API compatibility
dumpBiomes () {
}
dumpLight () {
}
loadLight () {
}
loadBiomes () {
}
dump () {
const smartBuffer = new SmartBuffer()
this.sections.forEach((section, i) => {
if (section !== null && !section.isEmpty()) {
section.write(smartBuffer)
}
})
// write biome data
this.biomes.forEach(biome => {
smartBuffer.writeUInt8(biome)
})
return smartBuffer.toBuffer()
}
load (data, bitMap = 0xffff, skyLightSent = true, fullChunk = true) {
// make smartbuffer from node buffer
// so that we doesn't need to maintain a cursor
const reader = SmartBuffer.fromBuffer(data)
this.skyLightSent = skyLightSent
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
let skyLight
// 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 {
// remove the 0 length signifying the missing palette array
varInt.read(reader)
// 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 blockLight = new BitArray({
bitsPerValue: 4,
capacity: 4096
}).readBuffer(reader)
if (skyLightSent) {
skyLight = new BitArray({
bitsPerValue: 4,
capacity: 4096
}).readBuffer(reader)
}
const section = new ChunkSection({
data: dataArray,
palette,
blockLight,
...(skyLightSent ? { skyLight } : { skyLight: null }),
maxBitsPerBlock: this.maxBitsPerBlock
})
this.sections[y] = section
}
// read biomes
if (fullChunk) {
const p = { x: 0, y: 0, z: 0 }
for (p.z = 0; p.z < constants.SECTION_WIDTH; p.z++) {
for (p.x = 0; p.x < constants.SECTION_WIDTH; p.x++) {
this.setBiome(p, reader.readUInt8())
}
}
}
}
}
}
function getBiomeIndex (pos) {
return (pos.z * 16) | pos.x
}
function toSectionPos (pos) {
return { x: pos.x, y: pos.y & 15, z: pos.z }
}