diff --git a/funkyArray.ts b/funkyArray.ts index c769c20..f02690a 100644 --- a/funkyArray.ts +++ b/funkyArray.ts @@ -48,6 +48,10 @@ export class FunkyArray { return this.items.get(key); } + public has(key:T) : boolean { + return this.itemKeys.includes(key); + } + public get keys() : Array { return this.itemKeys; } diff --git a/server/Chunk.ts b/server/Chunk.ts index 66437e8..b84e04a 100644 --- a/server/Chunk.ts +++ b/server/Chunk.ts @@ -1,5 +1,6 @@ import { FunkyArray } from "../funkyArray"; import { NibbleArray } from "../nibbleArray"; +import { Block } from "./blocks/Block"; import { Player } from "./entities/Player"; import { QueuedBlockUpdate } from "./queuedUpdateTypes/BlockUpdate"; import { World } from "./World"; @@ -16,26 +17,39 @@ export class Chunk { private blocks:Uint8Array; private metadata:NibbleArray; + public skyLight:NibbleArray; + public blockLight:NibbleArray; public static CreateCoordPair(x:number, z:number) { return (x >= 0 ? 0 : 2147483648) | (x & 0x7fff) << 16 | (z >= 0 ? 0 : 0x8000) | z & 0x7fff; } - public constructor(world:World, x:number, z:number, generateOrBlockData?:boolean|Uint8Array, metadata?:Uint8Array) { + public constructor(world:World, x:number, z:number, generateOrBlockData?:boolean|Uint8Array, metadata?:Uint8Array, blockLight?:Uint8Array, skyLight?:Uint8Array) { this.world = world; this.x = x; this.z = z; this.playersInChunk = new FunkyArray(); - if (generateOrBlockData instanceof Uint8Array && metadata instanceof Uint8Array) { + if (generateOrBlockData instanceof Uint8Array && metadata instanceof Uint8Array && blockLight instanceof Uint8Array && skyLight instanceof Uint8Array) { this.blocks = new Uint8Array(generateOrBlockData); this.metadata = new NibbleArray(metadata); + this.skyLight = new NibbleArray(blockLight); + this.blockLight = new NibbleArray(skyLight); + } else if (generateOrBlockData instanceof Uint8Array && metadata instanceof Uint8Array && !(blockLight instanceof Uint8Array) && !(skyLight instanceof Uint8Array)) { + this.blocks = new Uint8Array(generateOrBlockData); + this.metadata = new NibbleArray(metadata); + this.skyLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT); + this.blockLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT); + this.calculateLighting(); } else { this.blocks = new Uint8Array(16 * 16 * this.MAX_HEIGHT); this.metadata = new NibbleArray(16 * 16 * this.MAX_HEIGHT); + this.skyLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT); + this.blockLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT); if (typeof(generateOrBlockData) === "boolean" && generateOrBlockData) { this.world.generator.generate(this); + this.calculateLighting(); } } } @@ -51,7 +65,32 @@ export class Chunk { } public calculateLighting() { + let blockId = 0; + for (let y = 0; y < 128; y++) { + let colLight = 255; + for (let x = 0; x < 16; x++) { + for (let z = 0; z < 16; z++) { + blockId = this.getBlockId(x, y, z); + if (blockId == 0) { + if (colLight <= 0) { + this.setBlockLight(0, x, y, z); + this.setSkyLight(0, x, y, z); + } else { + this.setBlockLight(Math.round((colLight / 255) * 15), x, y, z); + this.setSkyLight(Math.round((colLight / 255) * 15), x, y, z); + } + continue; + } + if (colLight <= 0) { + this.setBlockLight(0, x, y, z); + } else { + this.setBlockLight(Math.round((colLight / 255) * 15), x, y, z); + colLight -= (255 - Block.blocks[blockId].lightPassage); + } + } + } + } } public queueBlockUpdateForOuterChunkBlock(blockId:number, metadata:number, x:number, y:number, z:number) { @@ -91,10 +130,38 @@ export class Chunk { return this.metadata.get(x << 11 | z << 7 | y); } + public getBlockLight(x:number, y:number, z:number) { + return this.blockLight.get(x << 11 | z << 7 | y); + } + + public setBlockLight(value:number, x:number, y:number, z:number) { + return this.blockLight.set(x << 11 | z << 7 | y, value); + } + + public getSkyLight(x:number, y:number, z:number) { + return this.skyLight.get(x << 11 | z << 7 | y); + } + + public setSkyLight(value:number, x:number, y:number, z:number) { + return this.skyLight.set(x << 11 | z << 7 | y, value); + } + + public getBlockBuffer() { + return Buffer.from(this.blocks); + } + public getMetadataBuffer() { return this.metadata.toBuffer(); } + public getBlockLightBuffer() { + return this.metadata.toBuffer(); + } + + public getSkyLightBuffer() { + return this.metadata.toBuffer(); + } + public getData() { return this.blocks; } diff --git a/server/World.ts b/server/World.ts index 9bd00ac..0e3d722 100644 --- a/server/World.ts +++ b/server/World.ts @@ -45,11 +45,13 @@ export class World { public removeEntity(entity:IEntity) { if (entity instanceof Player) { for (const coordPair of entity.loadedChunks) { - const chunk = this.getChunkByCoordPair(coordPair); - chunk.playersInChunk.remove(entity.entityId); + if (this.chunkExists(coordPair)) { + const chunk = this.getChunkByCoordPair(coordPair); + chunk.playersInChunk.remove(entity.entityId); - if (!chunk.forceLoaded && chunk.playersInChunk.length === 0) { - this.unloadChunk(coordPair); + if (!chunk.forceLoaded && chunk.playersInChunk.length === 0) { + this.unloadChunk(coordPair); + } } } this.players.remove(entity.entityId); @@ -59,6 +61,14 @@ export class World { // TODO: Inform clients about entity removal } + public chunkExists(coordPairOrX:number, chunkZ?:number) { + if (typeof(coordPairOrX) === "number" && typeof(chunkZ) === "number") { + return this.chunks.has(Chunk.CreateCoordPair(coordPairOrX, chunkZ)); + } + + return this.chunks.has(coordPairOrX); + } + public getChunk(x:number, z:number, generate:boolean = true) { const coordPair = Chunk.CreateCoordPair(x, z); const existingChunk = this.chunks.get(coordPair); @@ -206,10 +216,13 @@ export class World { if (entity instanceof Player) { if (entity.justUnloaded.length > 0) { for (const coordPair of entity.justUnloaded) { - const chunkToUnload = this.getChunkByCoordPair(coordPair); - chunkToUnload.playersInChunk.remove(entity.entityId); - if (!chunkToUnload.forceLoaded && chunkToUnload.playersInChunk.length === 0) { - this.unloadChunk(coordPair); + if (this.chunks.get(coordPair) != undefined) + { + const chunkToUnload = this.getChunkByCoordPair(coordPair); + chunkToUnload.playersInChunk.remove(entity.entityId); + if (!chunkToUnload.forceLoaded && chunkToUnload.playersInChunk.length === 0) { + this.unloadChunk(coordPair); + } } } diff --git a/server/WorldSaveManager.ts b/server/WorldSaveManager.ts index 38a5ca4..a5f5476 100644 --- a/server/WorldSaveManager.ts +++ b/server/WorldSaveManager.ts @@ -86,13 +86,11 @@ export class WorldSaveManager { } public writeChunkToDisk(chunk:Chunk) { - /*return new Promise((resolve, reject) => { - resolve(true); - });*/ return new Promise((resolve, reject) => { const saveType = this.config.saveCompression; const chunkFileWriter = new Writer(10); chunkFileWriter.writeUByte(0xFC); // Chunk File Magic + // TODO: Change to 1 when lighting actually works chunkFileWriter.writeUByte(0); // File Version chunkFileWriter.writeUByte(saveType); // Save compression type chunkFileWriter.writeUByte(16); // Chunk X @@ -101,13 +99,15 @@ export class WorldSaveManager { const chunkData = new Writer() .writeBuffer(Buffer.from(chunk.getData())) - .writeBuffer(chunk.getMetadataBuffer()).toBuffer(); + .writeBuffer(chunk.getMetadataBuffer()).toBuffer() + //.writeBuffer(chunk.getBlockLightBuffer()) + //.writeBuffer(chunk.getSkyLightBuffer()).toBuffer(); if (saveType === SaveCompressionType.NONE) { chunkFileWriter.writeInt(chunkData.length); // Data length chunkFileWriter.writeBuffer(chunkData); // Chunk data - writeFile(`${this.worldChunksFolderPath}/${chunk.x},${chunk.z}.hwc`, chunkFileWriter.toBuffer(), () => { + writeFile(`${this.worldChunksFolderPath}/${Chunk.CreateCoordPair(chunk.x, chunk.z)}.hwc`, chunkFileWriter.toBuffer(), () => { const cPair = Chunk.CreateCoordPair(chunk.x, chunk.z); if (!this.chunksOnDisk.includes(cPair)) { this.chunksOnDisk.push(cPair); @@ -124,7 +124,7 @@ export class WorldSaveManager { chunkFileWriter.writeInt(data.length); chunkFileWriter.writeBuffer(data); - writeFile(`${this.worldChunksFolderPath}/${chunk.x},${chunk.z}.hwc`, chunkFileWriter.toBuffer(), () => { + writeFile(`${this.worldChunksFolderPath}/${Chunk.CreateCoordPair(chunk.x, chunk.z)}.hwc`, chunkFileWriter.toBuffer(), () => { const cPair = Chunk.CreateCoordPair(chunk.x, chunk.z); if (!this.chunksOnDisk.includes(cPair)) { this.chunksOnDisk.push(cPair); @@ -141,7 +141,7 @@ export class WorldSaveManager { readChunkFromDisk(world:World, x:number, z:number) { return new Promise((resolve, reject) => { - readFile(`${this.worldChunksFolderPath}/${x},${z}.hwc`, (err, data) => { + readFile(`${this.worldChunksFolderPath}/${Chunk.CreateCoordPair(x, z)}.hwc`, (err, data) => { if (err) { return reject(err); } @@ -177,6 +177,29 @@ export class WorldSaveManager { resolve(chunk); }); } + } else if (fileVersion === 1) { + const saveCompressionType:SaveCompressionType = chunkFileReader.readUByte(); + const chunkX = chunkFileReader.readUByte(); + const chunkY = chunkFileReader.readUByte(); + const chunkZ = chunkFileReader.readUByte(); + const totalByteSize = chunkX * chunkZ * chunkY; + + const contentLength = chunkFileReader.readInt(); + if (saveCompressionType === SaveCompressionType.NONE) { + const chunkData = new Reader(chunkFileReader.readBuffer(contentLength)); + const chunk = new Chunk(world, x, z, chunkData.readUint8Array(totalByteSize), chunkData.readUint8Array(totalByteSize / 2)); + resolve(chunk); + } else if (saveCompressionType === SaveCompressionType.DEFLATE) { + inflate(chunkFileReader.readBuffer(contentLength), (err, data) => { + if (err) { + return reject(err); + } + + const chunkData = new Reader(data); + const chunk = new Chunk(world, x, z, chunkData.readUint8Array(totalByteSize), chunkData.readUint8Array(totalByteSize / 2), chunkData.readUint8Array(totalByteSize / 2), chunkData.readUint8Array(totalByteSize / 2)); + resolve(chunk); + }); + } } }); }); diff --git a/server/blocks/Block.ts b/server/blocks/Block.ts index b3549af..06cf09d 100644 --- a/server/blocks/Block.ts +++ b/server/blocks/Block.ts @@ -17,6 +17,11 @@ export class Block { Block.lightPassage[this.blockId] = value; } + public setLightPassage(value:number) { + this.lightPassage = value; + return this; + } + // Define statics here static readonly stone = new Block(1); static readonly grass = new Block(2); @@ -24,7 +29,7 @@ export class Block { static readonly bedrock = new Block(7); - static readonly waterStill = new Block(9); + static readonly waterStill = new Block(9).setLightPassage(3); static readonly lavaStill = new Block(11); @@ -32,7 +37,9 @@ export class Block { static readonly gravel = new Block(13); static readonly wood = new Block(17); - static readonly leaves = new Block(18); + static readonly leaves = new Block(18).setLightPassage(1); + + static readonly glass = new Block(20).setLightPassage(255); static readonly tallGrass = new Block(31); diff --git a/server/entities/Player.ts b/server/entities/Player.ts index 10c5688..fa69abc 100644 --- a/server/entities/Player.ts +++ b/server/entities/Player.ts @@ -64,7 +64,7 @@ export class Player extends EntityLiving { // Mark any unaccounted chunks for unload for (const coordPair of this.loadedChunks) { - if (!currentLoads.includes(coordPair)) { + if (!currentLoads.includes(coordPair) && this.world.chunkExists(coordPair)) { this.justUnloaded.push(coordPair); const chunkToUnload = this.world.getChunkByCoordPair(coordPair); this.mpClient?.send(new PacketPreChunk(chunkToUnload.x, chunkToUnload.z, false).writeData()); diff --git a/server/generators/Hilly.ts b/server/generators/Hilly.ts index efbcb0a..c33cc62 100644 --- a/server/generators/Hilly.ts +++ b/server/generators/Hilly.ts @@ -23,6 +23,7 @@ export class HillyGenerator implements IGenerator { private caveGenerator2:Noise3D; private caveGenerator3:Noise3D; private caveGenerator4:Noise3D; + private caveGenerator5:Noise3D; private underwaterGravelGenerator:Noise2D; private underwaterSandGenerator:Noise2D; @@ -47,6 +48,7 @@ export class HillyGenerator implements IGenerator { this.caveGenerator2 = this.createGenerator3D(); this.caveGenerator3 = this.createGenerator3D(); this.caveGenerator4 = this.createGenerator3D(); + this.caveGenerator5 = this.createGenerator3D(); this.underwaterGravelGenerator = this.createGenerator2D(); this.underwaterSandGenerator = this.createGenerator2D(); @@ -141,8 +143,9 @@ export class HillyGenerator implements IGenerator { if ( ((this.caveGenerator1((chunk.x * 16 + x) / 16, caveY / 16, (chunk.z * 16 + z) / 16) + this.caveGenerator2((chunk.x * 16 + x) / 8, caveY / 8, (chunk.z * 16 + z) / 8)) / 2) > 0.45 - //this.caveGenerator3((chunk.x * 16 + x) / 256, caveY / 256, (chunk.z * 16 + z) / 256) > 0.6 || - //this.caveGenerator4((chunk.x * 16 + x) / 128, caveY / 128, (chunk.z * 16 + z) / 128) > 0.6 + || this.caveGenerator3((chunk.x * 16 + x) / 16, caveY / 16, (chunk.z * 16 + z) / 16) > 0.6 || + this.caveGenerator4((chunk.x * 16 + x) / 16, caveY / 16, (chunk.z * 16 + z) / 16) > 0.6 || + this.caveGenerator5((chunk.x * 16 + x) / 16, caveY / 16, (chunk.z * 16 + z) / 16) > 0.5 ) { if (caveY <= 3) { chunk.setBlock(Block.lavaStill.blockId, x, caveY, z); diff --git a/server/packets/MapChunk.ts b/server/packets/MapChunk.ts index 5467955..28f73e3 100644 --- a/server/packets/MapChunk.ts +++ b/server/packets/MapChunk.ts @@ -33,29 +33,15 @@ export class PacketMapChunk implements IPacket { public writeData() { return new Promise((resolve, reject) => { - const blocks = new Writer(32768); - const lighting = new Writer(32768); - - let blockMeta = false; - for (let x = 0; x < 16; x++) { - for (let z = 0; z < 16; z++) { - for (let y = 0; y < 128; y++) { - blocks.writeUByte(this.chunk.getBlockId(x, y, z)); - if (blockMeta) { - // Light level 15 for 2 blocks (1111 1111) - lighting.writeUByte(0xff); // TODO: Lighting (Client seems to do it's own (when a block update happens) so it's not top priority) - lighting.writeUByte(0xff); - } - // Hack for nibble stuff - blockMeta = !blockMeta; - } - } + // TODO: Use block and sky nibble array buffers + const fakeLighting = new Writer(16384); + for (let i = 0; i < 16384; i++) { + fakeLighting.writeUByte(0xFF); } - // Write meta and lighting data into block buffer for compression - blocks.writeBuffer(this.chunk.getMetadataBuffer()).writeBuffer(lighting.toBuffer()); + const data = new Writer().writeBuffer(this.chunk.getBlockBuffer()).writeBuffer(this.chunk.getMetadataBuffer()).writeBuffer(fakeLighting.toBuffer()).writeBuffer(fakeLighting.toBuffer());//.writeBuffer(this.chunk.blockLight.toBuffer()).writeBuffer(this.chunk.skyLight.toBuffer()); - deflate(blocks.toBuffer(), (err, data) => { + deflate(data.toBuffer(), (err, data) => { if (err) { return reject(err); }