prismarine-chunk
Version:
A class to hold chunk data for prismarine
245 lines (200 loc) • 7.81 kB
JavaScript
const CommonChunkColumn = require('../common/CommonChunkColumn')
const Section = require('./section')
const Vec3 = require('vec3').Vec3
const BIOME_SIZE = 256
const h = 256
const { w, l, sh } = Section
const sectionCount = h >> 4
module.exports = (registry) => {
const Block = require('prismarine-block')(registry)
return class Chunk extends CommonChunkColumn {
static get section () { return Section }
static get w () { return w }
static get l () { return l }
static get h () { return h }
static get version () { return registry.version }
constructor () {
super(registry)
this.skyLightSent = true
this.minY = 0
this.worldHeight = h
this.sections = new Array(sectionCount)
for (let i = 0; i < sectionCount; i++) { this.sections[i] = new Section() }
// alloc automatically zero initializes
this.biome = Buffer.alloc(BIOME_SIZE)
}
toJson () {
return JSON.stringify({
skyLightSent: this.skyLightSent,
biome: this.biome.toJSON(),
blockEntities: this.blockEntities,
sections: this.sections.map(section => section.toJson())
})
}
static fromJson (j) {
const parsed = JSON.parse(j)
const chunk = new Chunk()
chunk.skyLightSent = parsed.skyLightSent
chunk.biome = Buffer.from(parsed.biome)
chunk.blockEntities = parsed.blockEntities
chunk.sections = parsed.sections.map(s => Section.fromJson(s))
return chunk
}
initialize (iniFunc) {
let biome = 0
for (let i = 0; i < sectionCount; i++) {
this.sections[i].initialize((x, y, z, n) => {
const block = iniFunc(x, y % sh, z, n)
if (block == null) { return }
if (y === 0 && sectionCount === 0) {
this.biome.writeUInt8(block.biome.id || 0, biome)
biome++
}
return 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)
}
}
_getSection (pos) {
return this.sections[pos.y >> 4]
}
getBlockStateId (pos) {
const section = this._getSection(pos)
return section ? section.getBlockStateId(posInSection(pos)) : 0
}
getBlockType (pos) {
const section = this._getSection(pos)
return section ? section.getBlockType(posInSection(pos)) : 0
}
getBlockData (pos) {
const section = this._getSection(pos)
return section ? section.getBlockData(posInSection(pos)) : 0
}
getBlockLight (pos) {
const section = this._getSection(pos)
return section ? section.getBlockLight(posInSection(pos)) : 0
}
getSkyLight (pos) {
if (!this.skyLightSent) return 0
const section = this._getSection(pos)
return section ? section.getSkyLight(posInSection(pos)) : 15
}
getBiome (pos) {
const cursor = getBiomeCursor(pos)
return this.biome.readUInt8(cursor)
}
setBlockStateId (pos, stateId) {
const section = this._getSection(pos)
return section && section.setBlockStateId(posInSection(pos), stateId)
}
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)
}
setBlockLight (pos, light) {
const section = this._getSection(pos)
return section && section.setBlockLight(posInSection(pos), light)
}
setSkyLight (pos, light) {
const section = this._getSection(pos)
return section && section.setSkyLight(posInSection(pos), light)
}
setBiome (pos, biome) {
const cursor = getBiomeCursor(pos)
this.biome.writeUInt8(biome, cursor)
}
// These methods do nothing, and are present only for API compatibility
dumpBiomes () {
}
dumpLight () {
}
loadLight () {
}
loadBiomes () {
}
dump (bitMap = 0xFFFF, skyLightSent = true) {
const SECTION_SIZE = Section.sectionSize(this.skyLightSent && skyLightSent)
const { chunkIncluded, chunkCount } = parseBitMap(bitMap)
const bufferLength = chunkCount * SECTION_SIZE + BIOME_SIZE
const buffer = Buffer.alloc(bufferLength)
let offset = 0
let offsetLight = w * l * sectionCount * chunkCount * 2
let offsetSkyLight = (this.skyLightSent && skyLightSent) ? w * l * sectionCount * chunkCount / 2 * 5 : undefined
for (let i = 0; i < sectionCount; i++) {
if (chunkIncluded[i]) {
offset += this.sections[i].dump().copy(buffer, offset, 0, w * l * sh * 2)
offsetLight += this.sections[i].dump().copy(buffer, offsetLight, w * l * sh * 2, w * l * sh * 2 + w * l * sh / 2)
if (this.skyLightSent && skyLightSent) offsetSkyLight += this.sections[i].dump().copy(buffer, offsetSkyLight, w * l * sh / 2 * 5, w * l * sh / 2 * 5 + w * l * sh / 2)
}
}
this.biome.copy(buffer, w * l * sectionCount * chunkCount * ((this.skyLightSent && skyLightSent) ? 3 : 5 / 2))
return buffer
}
load (data, bitMap = 0xFFFF, skyLightSent = true, fullChunk = true) {
if (!Buffer.isBuffer(data)) { throw (new Error('Data must be a buffer')) }
this.skyLightSent = skyLightSent
const SECTION_SIZE = Section.sectionSize(skyLightSent)
const { chunkIncluded, chunkCount } = parseBitMap(bitMap)
let offset = 0
let offsetLight = w * l * sectionCount * chunkCount * 2
let offsetSkyLight = (this.skyLightSent) ? w * l * sectionCount * chunkCount / 2 * 5 : undefined
for (let i = 0; i < sectionCount; i++) {
if (chunkIncluded[i]) {
const sectionBuffer = Buffer.alloc(SECTION_SIZE)
offset += data.copy(sectionBuffer, 0, offset, offset + w * l * sh * 2)
offsetLight += data.copy(sectionBuffer, w * l * sh * 2, offsetLight, offsetLight + w * l * sh / 2)
if (this.skyLightSent) offsetSkyLight += data.copy(sectionBuffer, w * l * sh * 5 / 2, offsetSkyLight, offsetSkyLight + w * l * sh / 2)
this.sections[i].load(sectionBuffer, skyLightSent)
}
}
if (fullChunk) {
data.copy(this.biome, 0, w * l * sectionCount * chunkCount * (skyLightSent ? 3 : 5 / 2))
}
const expectedSize = SECTION_SIZE * chunkCount + (fullChunk ? w * l : 0)
if (data.length !== expectedSize) { throw (new Error(`Data buffer not correct size (was ${data.length}, expected ${expectedSize})`)) }
}
getMask () {
return 0xFFFF
}
}
}
const exists = function (val) {
return val !== undefined
}
const getBiomeCursor = function (pos) {
return (pos.z * w) + pos.x
}
function posInSection (pos) {
return pos.modulus(new Vec3(w, l, sh))
}
function parseBitMap (bitMap) {
const chunkIncluded = new Array(sectionCount)
let chunkCount = 0
for (let y = 0; y < sectionCount; ++y) {
chunkIncluded[y] = bitMap & (1 << y)
if (chunkIncluded[y]) chunkCount++
}
return { chunkIncluded, chunkCount }
}