UNPKG

@boem312/minecraft-server

Version:

A pure JS library to create Minecraft Java 1.16.3 servers

188 lines (142 loc) 6.9 kB
const { version } = require('../../settings.json'); const { chunkSize } = require('../../functions/loader/data.js'); const crypto = require('crypto'); const path = require('path'); const CustomError = require('../utils/CustomError.js'); const Block = require('../utils/Block.js'); const { getBlockStateId } = require('../../functions/getBlockStateId.js'); let cachedPChunk; const PChunk = () => { if (cachedPChunk === undefined) cachedPChunk = require('prismarine-chunk')(`${version}`); return cachedPChunk; }; const _p = Symbol('_privates'); let chunkDataCache = {}; class Chunk { constructor(chunk, blockUpdateCallback, blocksOffset) { Object.defineProperty(this, _p, { configurable: false, enumerable: false, writable: false, value: {} }); this.blockUpdateCallback = blockUpdateCallback || chunk?.blockUpdateCallback || undefined; this.blocksOffset = blocksOffset || chunk?.blocksOffset || { x: 0, y: 0, z: 0 }; this.p._hash = chunk?.hash || null; this.p._chunk = null; this.p._blocks = null; this.inheritedChunk = chunk; } get p() { let callPath = new Error().stack.split('\n')[2]; if (callPath.includes('(')) callPath = callPath.split('(')[1].split(')')[0]; else callPath = callPath.split('at ')[1]; callPath = callPath.split(':').slice(0, 2).join(':'); let folderPath = path.resolve(__dirname, '../../'); if (!callPath.startsWith(folderPath)) console.warn('(minecraft-server) WARNING: Detected access to private properties from outside of the module. This is not recommended and may cause unexpected behavior.'); return this[_p]; } set p(value) { console.error('(minecraft-server) ERROR: Setting private properties is not supported. Action ignored.'); } get blocks() { if (this.p._blocks) return this.p._blocks; this.p._blocks = {}; //copy prismarine chunk to allow new chunk to be changed without affecting the original if (this.inheritedChunk?.blocks) for (const x in this.inheritedChunk.blocks) for (const y in this.inheritedChunk.blocks[x]) for (const z in this.inheritedChunk.blocks[x][y]) { if (!this.p._blocks[x]) this.p._blocks[x] = {}; if (!this.p._blocks[x][y]) this.p._blocks[x][y] = {}; this.p._blocks[x][y][z] = new Block(this.inheritedChunk.blocks[x][y][z].block, this.inheritedChunk.blocks[x][y][z].state, { x: parseInt(x) + this.blocksOffset.x, y: parseInt(y) + this.blocksOffset.y, z: parseInt(z) + this.blocksOffset.z }); }; return this.p._blocks; } get chunk() { if (this.p._chunk) return this.p._chunk; this.p._chunk = new (PChunk())(); for (const x in this.blocks) for (const y in this.blocks[x]) for (const z in this.blocks[x][y]) this.p._chunk.setBlockStateId({ x, y, z }, getBlockStateId(this.blocks[x][y][z].block, this.blocks[x][y][z].state)); return this.p._chunk; } get hash() { if (this.p._hash === null) this.p._hash = crypto.createHash('sha256').update(this.chunk.toJson()).digest('base64'); return this.p._hash; } get chunkData() { if (chunkDataCache[this.hash] === undefined) chunkDataCache[this.hash] = { biomes: this.chunk.dumpBiomes?.(), bitMap: this.chunk.getMask(), chunkData: this.chunk.dump() }; return chunkDataCache[this.hash]; } // setBlockChecks(blockName, { x, y, z }, state = {}) { checkNewBlock(blockName, { x, y, z }) { if (blockName === undefined) throw new CustomError('expectationNotMet', 'libraryUser', `blockName in ${this.constructor.name}.setBlock(blockName, ..., ...) `, { got: blockName, expectationType: 'type', expectation: 'blockName' }, this.setBlock).toString() if (x < chunkSize.x.min || x > chunkSize.x.max) throw new CustomError('expectationNotMet', 'libraryUser', `x in ${this.constructor.name}.setBlock(..., {x: x, ... }, ...) `, { got: x, expectationType: 'value', expectation: new Array(chunkSize.x.max - chunkSize.x.min).fill(0).map((_, i) => i + chunkSize.x.min) }, this.setBlock).toString() if (y < chunkSize.y.min || y > chunkSize.y.max) throw new CustomError('expectationNotMet', 'libraryUser', `y in ${this.constructor.name}.setBlock(..., {y: y, ... }, ...) `, { got: y, expectationType: 'value', expectation: new Array(chunkSize.y.max - chunkSize.y.min).fill(0).map((_, i) => i + chunkSize.y.min) }, this.setBlock).toString() if (z < chunkSize.z.min || z > chunkSize.z.max) throw new CustomError('expectationNotMet', 'libraryUser', `z in ${this.constructor.name}.setBlock(..., {z: z, ... }, ...) `, { got: z, expectationType: 'value', expectation: new Array(chunkSize.z.max - chunkSize.z.min).fill(0).map((_, i) => i + chunkSize.z.min) }, this.setBlock).toString() } updateBlock(blockName, { x, y, z }, state = {}) { this.checkNewBlock(blockName, { x, y, z }); if (this.p._chunk) this.chunk.setBlockStateId({ x, y, z }, getBlockStateId.call(this, blockName, state, { function: 'setBlock' })); if (blockName === 'air') { delete this.blocks[x][y][z]; if (Object.keys(this.blocks[x][y]).length === 0) delete this.blocks[x][y]; if (Object.keys(this.blocks[x]).length === 0) delete this.blocks[x]; } else { if (!this.blocks[x]) this.blocks[x] = {}; if (!this.blocks[x][y]) this.blocks[x][y] = {}; this.blocks[x][y][z] = new Block(blockName, state, { x: x + this.blocksOffset.x, y: y + this.blocksOffset.y, z: z + this.blocksOffset.z }); } } sendNewBlockPacket({ x, y, z }) { if (this.blockUpdateCallback) this.blockUpdateCallback(this.blocks[x]?.[y]?.[z] ?? new Block('air', {}, { x: x + this.blocksOffset.x, y: y + this.blocksOffset.y, z: z + this.blocksOffset.z })); } setBlock(blockName, { x, y, z }, state = {}) { this.updateBlock(blockName, { x, y, z }, state); this.sendNewBlockPacket({ x, y, z }); return this; } } module.exports = Chunk;