WIP: Lighting
This commit is contained in:
parent
ad1c402d84
commit
2aedd81baa
|
@ -48,6 +48,10 @@ export class FunkyArray<T, TT> {
|
|||
return this.items.get(key);
|
||||
}
|
||||
|
||||
public has(key:T) : boolean {
|
||||
return this.itemKeys.includes(key);
|
||||
}
|
||||
|
||||
public get keys() : Array<T> {
|
||||
return this.itemKeys;
|
||||
}
|
||||
|
|
|
@ -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<number, Player>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,13 +86,11 @@ export class WorldSaveManager {
|
|||
}
|
||||
|
||||
public writeChunkToDisk(chunk:Chunk) {
|
||||
/*return new Promise<boolean>((resolve, reject) => {
|
||||
resolve(true);
|
||||
});*/
|
||||
return new Promise<boolean>((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<Chunk>((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);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -33,29 +33,15 @@ export class PacketMapChunk implements IPacket {
|
|||
|
||||
public writeData() {
|
||||
return new Promise<Buffer>((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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue