diff --git a/server/Chunk.ts b/server/Chunk.ts index 42bb13e..b5bc3fd 100644 --- a/server/Chunk.ts +++ b/server/Chunk.ts @@ -1,11 +1,12 @@ import { FunkyArray } from "../funkyArray"; import { NibbleArray } from "../nibbleArray"; import { Player } from "./entities/Player"; +import { QueuedBlockUpdate } from "./queuedUpdateTypes/BlockUpdate"; import { World } from "./World"; export class Chunk { private readonly MAX_HEIGHT:number = 128; - private readonly world:World; + public readonly world:World; public readonly x:number; public readonly z:number; public readonly playersInChunk:FunkyArray; @@ -39,8 +40,22 @@ export class Chunk { } } + public calculateLighting() { + + } + + public queueBlockUpdateForOuterChunkBlock(blockId:number, metadata:number, x:number, y:number, z:number) { + const cPair = Chunk.CreateCoordPair(this.x + (x >> 4), this.z + (z >> 4)); + if (this.world.chunks.keys.includes(cPair)) { + this.world.queuedUpdates.push(new QueuedBlockUpdate(cPair, x & 0xf, y, z & 0xf, blockId, metadata)); + } else { + this.world.queuedChunkBlocks.push(new QueuedBlockUpdate(cPair, x & 0xf, y, z & 0xf, blockId, metadata)); + } + } + public setBlock(blockId:number, x:number, y:number, z:number) { if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) { + this.queueBlockUpdateForOuterChunkBlock(blockId, 0, x, y, z); return; } @@ -49,6 +64,7 @@ export class Chunk { public setBlockWithMetadata(blockId:number, metadata:number, x:number, y:number, z:number) { if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) { + this.queueBlockUpdateForOuterChunkBlock(blockId, metadata, x, y, z); return; } x = x << 11 | z << 7 | y; diff --git a/server/MPClient.ts b/server/MPClient.ts index 4b0f614..b4e91b5 100644 --- a/server/MPClient.ts +++ b/server/MPClient.ts @@ -135,7 +135,7 @@ export class MPClient { // Started digging } else if (packet.status === 2) { if (this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) != 0) { - this.entity.world.setBlock(0, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, true); + this.entity.world.setBlockWithMetadata(0, 0, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, true); } } } diff --git a/server/World.ts b/server/World.ts index 67e8e47..9bd00ac 100644 --- a/server/World.ts +++ b/server/World.ts @@ -7,6 +7,8 @@ import { Player } from "./entities/Player"; import { HillyGenerator } from "./generators/Hilly"; import { IGenerator } from "./generators/IGenerator"; import { PacketBlockChange } from "./packets/BlockChange"; +import { QueuedBlockUpdate } from "./queuedUpdateTypes/BlockUpdate"; +import { IQueuedUpdate } from "./queuedUpdateTypes/IQueuedUpdate"; export class World { public static ENTITY_MAX_SEND_DISTANCE = 50; @@ -17,6 +19,8 @@ export class World { public entites:FunkyArray; public players:FunkyArray; + public queuedChunkBlocks:Array; + public queuedUpdates:Array; public generator:IGenerator; public constructor(saveManager:WorldSaveManager, seed:number) { @@ -25,6 +29,8 @@ export class World { this.chunks = new FunkyArray(); this.entites = new FunkyArray(); this.players = new FunkyArray(); + this.queuedChunkBlocks = new Array(); + this.queuedUpdates = new Array(); this.generator = new HillyGenerator(seed); } @@ -148,6 +154,14 @@ export class World { }); } + public sendToNearbyAllNearbyClients(sentFrom:IEntity, buffer:Buffer) { + this.players.forEach(player => { + if (Math.abs(sentFrom.distanceTo(player)) < World.ENTITY_MAX_SEND_DISTANCE) { + player.mpClient?.send(buffer); + } + }); + } + public async unloadChunk(coordPair:number) { const chunk = this.getChunkByCoordPair(coordPair); if (!chunk.savingToDisk) { @@ -167,6 +181,25 @@ export class World { } public tick() { + if (this.queuedUpdates.length > 0) { + for (let i = this.queuedUpdates.length - 1; i >= 0; i--) { + const update = this.queuedUpdates[i]; + if (update instanceof QueuedBlockUpdate) { + if (this.chunks.keys.includes(update.coordPair)) { + this.queuedUpdates.splice(i, 1); + const thatChunk = this.getChunkByCoordPair(update.coordPair); + thatChunk.setBlockWithMetadata(update.blockId, update.metadata, update.x, update.y, update.z); + if (thatChunk.playersInChunk.length > 0) { + const blockUpdate = new PacketBlockChange((thatChunk.x << 4) + update.x, update.y, (thatChunk.z << 4) + update.z, update.blockId, update.metadata).writeData() + thatChunk.playersInChunk.forEach(player => { + player.mpClient?.send(blockUpdate); + }); + } + } + } + } + } + this.entites.forEach(entity => { entity.onTick(); diff --git a/server/WorldSaveManager.ts b/server/WorldSaveManager.ts index f898019..afe6dc2 100644 --- a/server/WorldSaveManager.ts +++ b/server/WorldSaveManager.ts @@ -86,6 +86,9 @@ export class WorldSaveManager { } public writeChunkToDisk(chunk:Chunk) { + /*return new Promise((resolve, reject) => { + resolve(true); + });*/ return new Promise((resolve, reject) => { const saveType = SaveCompressionType[this.config.saveCompression]; const chunkFileWriter = new Writer(10); diff --git a/server/blocks/Block.ts b/server/blocks/Block.ts index a3998ec..d28bb96 100644 --- a/server/blocks/Block.ts +++ b/server/blocks/Block.ts @@ -1,10 +1,22 @@ export class Block { public readonly blockId:number; + public static readonly blocks:Array = new Array(); + public static readonly lightPassage:Array = new Array(); public constructor(blockId:number) { + Block.blocks[blockId] = this; + Block.lightPassage[blockId] = 0; this.blockId = blockId; } + public get lightPassage() { + return Block.lightPassage[this.blockId]; + } + + public set lightPassage(value:number) { + Block.lightPassage[this.blockId] = value; + } + // Define statics here static readonly stone = new Block(1); static readonly grass = new Block(2); @@ -20,5 +32,7 @@ export class Block { static readonly wood = new Block(17); static readonly leaves = new Block(18); + static readonly tallGrass = new Block(31); + static readonly clay = new Block(82); } \ No newline at end of file diff --git a/server/entities/Entity.ts b/server/entities/Entity.ts index 56ae40b..4ec1fc6 100644 --- a/server/entities/Entity.ts +++ b/server/entities/Entity.ts @@ -17,6 +17,8 @@ export class Entity implements IEntity { public lastY:number; public lastZ:number; + public health:number; + public fire:number; public crouching:boolean; @@ -31,6 +33,8 @@ export class Entity implements IEntity { this.world = world; this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0; this.crouching = this.lastCrouchState = this.lastFireState = false; + + this.health = 20; } updateMetadata() { @@ -47,7 +51,7 @@ export class Entity implements IEntity { metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, Number(this.fire > 0) + Number(this.crouching) * 2)); } - this.world.sendToNearbyClients(this, new PacketEntityMetadata(this.entityId, metadata.writeBuffer()).writeData()); + this.world.sendToNearbyAllNearbyClients(this, new PacketEntityMetadata(this.entityId, metadata.writeBuffer()).writeData()); this.lastCrouchState = this.crouching; this.lastFireState = this.fire > 0; @@ -62,12 +66,18 @@ export class Entity implements IEntity { return Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2) + Math.pow(dZ, 2)); } + damageFrom(damage:number, entity?:IEntity) { + if (entity === undefined) { + this.health -= damage; + } + } + onTick() { this.updateMetadata(); if (this.fire > 0) { if (this.fire % 20 === 0) { - + this.damageFrom(1); } this.fire--; diff --git a/server/entities/EntityLiving.ts b/server/entities/EntityLiving.ts index 125e83f..8e27441 100644 --- a/server/entities/EntityLiving.ts +++ b/server/entities/EntityLiving.ts @@ -11,6 +11,7 @@ export class EntityLiving extends Entity { public pitch:number; public lastPitch:number; public onGround:boolean; + public fallDistance:number; public absX:number; public absY:number; @@ -26,8 +27,8 @@ export class EntityLiving extends Entity { public constructor(world:World) { super(world); - this.yaw = this.lastYaw = this.pitch = this.lastPitch = this.absX = this.absY = this.absZ = this.absYaw = this.absPitch = this.lastAbsX = this.lastAbsY = this.lastAbsZ = this.lastAbsYaw = this.lastAbsPitch = 0; - this.onGround = false; + this.fallDistance = this.yaw = this.lastYaw = this.pitch = this.lastPitch = this.absX = this.absY = this.absZ = this.absYaw = this.absPitch = this.lastAbsX = this.lastAbsY = this.lastAbsZ = this.lastAbsYaw = this.lastAbsPitch = 0; + this.onGround = true; } private constrainRot(rot:number) { @@ -73,6 +74,11 @@ export class EntityLiving extends Entity { onTick() { super.onTick(); this.sendPositionUpdate(); + + if (!this.onGround) { + this.fallDistance + } + this.lastYaw = this.yaw; this.lastPitch = this.lastPitch; } diff --git a/server/entities/Player.ts b/server/entities/Player.ts index c2e46d7..10c5688 100644 --- a/server/entities/Player.ts +++ b/server/entities/Player.ts @@ -5,6 +5,7 @@ import { World } from "../World"; import { PacketMapChunk } from "../packets/MapChunk"; import { EntityLiving } from "./EntityLiving"; import { PacketPreChunk } from "../packets/PreChunk"; +import { PacketUpdateHealth } from "../packets/UpdateHealth"; export class Player extends EntityLiving { public username:string; @@ -14,6 +15,8 @@ export class Player extends EntityLiving { public justUnloaded:Array; public mpClient?:MPClient; + private lastHealth:number; + public constructor(server:MinecraftServer, world:World, username:string) { super(world); this.server = server; @@ -25,6 +28,8 @@ export class Player extends EntityLiving { this.x = 8; this.y = 64; this.z = 8; + + this.lastHealth = this.health; } private async updatePlayerChunks() { @@ -33,7 +38,7 @@ export class Player extends EntityLiving { if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.firstUpdate) { if (this.firstUpdate) { this.firstUpdate = false; - // TODO: Make this based on the player's coords + // TODO: Make this based on the player's initial coords this.mpClient?.send(new PacketPreChunk(0, 0, true).writeData()); const chunk = await this.world.getChunkSafe(0, 0); const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData()); @@ -74,6 +79,11 @@ export class Player extends EntityLiving { public onTick() { this.updatePlayerChunks(); + if (this.health != this.lastHealth) { + this.lastHealth = this.health; + this.mpClient?.send(new PacketUpdateHealth(this.health).writeData()); + } + super.onTick(); } } \ No newline at end of file diff --git a/server/generators/Hilly.ts b/server/generators/Hilly.ts index 0cd2a2e..b904dd8 100644 --- a/server/generators/Hilly.ts +++ b/server/generators/Hilly.ts @@ -2,6 +2,7 @@ import { Block } from "../blocks/Block"; import { Chunk } from "../Chunk"; import { IGenerator } from "./IGenerator"; import { Noise2D, makeNoise2D } from "../../external/OpenSimplex2D"; +import { QueuedBlockUpdate } from "../queuedUpdateTypes/BlockUpdate"; export class HillyGenerator implements IGenerator { private seed:number; @@ -60,6 +61,8 @@ export class HillyGenerator implements IGenerator { public generate(chunk:Chunk) { const treeRNG = this.mulberry32(this.seed + chunk.x + chunk.z); + const grassRNG = this.mulberry32(this.seed + chunk.x + chunk.z); + let colY = 0, colDirtMin = 0, colWaterY = 0, orgColY = 0; for (let x = 0; x < 16; x++) { for (let z = 0; z < 16; z++) { @@ -77,7 +80,7 @@ export class HillyGenerator implements IGenerator { ) / 9); colDirtMin = colY - 2; const sandNoise = this.underwaterSandGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16); - if (colY === 59 && sandNoise > 0.5) { + if (colY <= 60 && sandNoise > 0.5) { chunk.setBlock(Block.sand.blockId, x, colY, z); } else { chunk.setBlock(Block.grass.blockId, x, colY, z); @@ -110,6 +113,22 @@ export class HillyGenerator implements IGenerator { chunk.setBlock(Block.waterStill.blockId, x, colWaterY, z); } + const queuedChunkBlocks = chunk.world.queuedChunkBlocks; + if (queuedChunkBlocks.length > 0) { + const thisCoordPair = Chunk.CreateCoordPair(chunk.x, chunk.z); + for (let i = queuedChunkBlocks.length - 1; i >= 0; i--) { + const blockUpdate = queuedChunkBlocks[i]; + if (blockUpdate instanceof QueuedBlockUpdate && blockUpdate.coordPair === thisCoordPair) { + queuedChunkBlocks.splice(i, 1); + chunk.setBlockWithMetadata(blockUpdate.blockId, blockUpdate.metadata, blockUpdate.x, blockUpdate.y, blockUpdate.z); + } + } + } + + if (grassRNG() > 0.9 && chunk.getBlockId(x, orgColY, z) === Block.grass.blockId) { + chunk.setBlockWithMetadata(Block.tallGrass.blockId, 1, x, orgColY + 1, z); + } + // TODO: Move trees to it's own generator if (chunk.getBlockId(x, orgColY + 1, z) !== Block.waterStill.blockId && chunk.getBlockId(x, orgColY, z) === Block.grass.blockId && treeRNG() > 0.995) { const treeType = treeRNG() >= 0.5; diff --git a/server/queuedUpdateTypes/BlockUpdate.ts b/server/queuedUpdateTypes/BlockUpdate.ts new file mode 100644 index 0000000..9343595 --- /dev/null +++ b/server/queuedUpdateTypes/BlockUpdate.ts @@ -0,0 +1,24 @@ +import { Chunk } from "../Chunk"; +import { IQueuedUpdate } from "./IQueuedUpdate"; + +export class QueuedBlockUpdate implements IQueuedUpdate { + public coordPair:number; + public x:number; + public y:number; + public z:number; + public blockId:number; + public metadata:number; + + public constructor(coordPair:number, x:number, y:number, z:number, blockId:number, metadata?:number) { + this.coordPair = coordPair; + this.x = x; + this.y = y; + this.z = z; + this.blockId = blockId; + if (typeof(metadata) === "number") { + this.metadata = metadata; + } else { + this.metadata = 0; + } + } +} \ No newline at end of file diff --git a/server/queuedUpdateTypes/IQueuedUpdate.ts b/server/queuedUpdateTypes/IQueuedUpdate.ts new file mode 100644 index 0000000..b60adcf --- /dev/null +++ b/server/queuedUpdateTypes/IQueuedUpdate.ts @@ -0,0 +1 @@ +export interface IQueuedUpdate {} \ No newline at end of file