WIP: Lighting

This commit is contained in:
Holly Stubbs 2023-05-02 08:50:49 +01:00
parent ad1c402d84
commit 2aedd81baa
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
8 changed files with 145 additions and 42 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -45,6 +45,7 @@ export class World {
public removeEntity(entity:IEntity) {
if (entity instanceof Player) {
for (const coordPair of entity.loadedChunks) {
if (this.chunkExists(coordPair)) {
const chunk = this.getChunkByCoordPair(coordPair);
chunk.playersInChunk.remove(entity.entityId);
@ -52,6 +53,7 @@ export class World {
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,12 +216,15 @@ export class World {
if (entity instanceof Player) {
if (entity.justUnloaded.length > 0) {
for (const coordPair of entity.justUnloaded) {
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);
}
}
}
entity.justUnloaded = new Array<number>();
}

View file

@ -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);
});
}
}
});
});

View file

@ -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);

View file

@ -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());

View file

@ -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);

View file

@ -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);
}