cross chunk block placement

This commit is contained in:
Holly Stubbs 2023-04-13 23:52:13 +01:00
parent 75d36b49db
commit 686a694432
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
11 changed files with 144 additions and 8 deletions

View file

@ -1,11 +1,12 @@
import { FunkyArray } from "../funkyArray"; import { FunkyArray } from "../funkyArray";
import { NibbleArray } from "../nibbleArray"; import { NibbleArray } from "../nibbleArray";
import { Player } from "./entities/Player"; import { Player } from "./entities/Player";
import { QueuedBlockUpdate } from "./queuedUpdateTypes/BlockUpdate";
import { World } from "./World"; import { World } from "./World";
export class Chunk { export class Chunk {
private readonly MAX_HEIGHT:number = 128; private readonly MAX_HEIGHT:number = 128;
private readonly world:World; public readonly world:World;
public readonly x:number; public readonly x:number;
public readonly z:number; public readonly z:number;
public readonly playersInChunk:FunkyArray<number, Player>; public readonly playersInChunk:FunkyArray<number, Player>;
@ -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) { public setBlock(blockId:number, x:number, y:number, z:number) {
if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) { if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) {
this.queueBlockUpdateForOuterChunkBlock(blockId, 0, x, y, z);
return; return;
} }
@ -49,6 +64,7 @@ export class Chunk {
public setBlockWithMetadata(blockId:number, metadata:number, x:number, y:number, z:number) { 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) { if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) {
this.queueBlockUpdateForOuterChunkBlock(blockId, metadata, x, y, z);
return; return;
} }
x = x << 11 | z << 7 | y; x = x << 11 | z << 7 | y;

View file

@ -135,7 +135,7 @@ export class MPClient {
// Started digging // Started digging
} else if (packet.status === 2) { } else if (packet.status === 2) {
if (this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) != 0) { 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);
} }
} }
} }

View file

@ -7,6 +7,8 @@ import { Player } from "./entities/Player";
import { HillyGenerator } from "./generators/Hilly"; import { HillyGenerator } from "./generators/Hilly";
import { IGenerator } from "./generators/IGenerator"; import { IGenerator } from "./generators/IGenerator";
import { PacketBlockChange } from "./packets/BlockChange"; import { PacketBlockChange } from "./packets/BlockChange";
import { QueuedBlockUpdate } from "./queuedUpdateTypes/BlockUpdate";
import { IQueuedUpdate } from "./queuedUpdateTypes/IQueuedUpdate";
export class World { export class World {
public static ENTITY_MAX_SEND_DISTANCE = 50; public static ENTITY_MAX_SEND_DISTANCE = 50;
@ -17,6 +19,8 @@ export class World {
public entites:FunkyArray<number, IEntity>; public entites:FunkyArray<number, IEntity>;
public players:FunkyArray<number, Player>; public players:FunkyArray<number, Player>;
public queuedChunkBlocks:Array<IQueuedUpdate>;
public queuedUpdates:Array<IQueuedUpdate>;
public generator:IGenerator; public generator:IGenerator;
public constructor(saveManager:WorldSaveManager, seed:number) { public constructor(saveManager:WorldSaveManager, seed:number) {
@ -25,6 +29,8 @@ export class World {
this.chunks = new FunkyArray<number, Chunk>(); this.chunks = new FunkyArray<number, Chunk>();
this.entites = new FunkyArray<number, IEntity>(); this.entites = new FunkyArray<number, IEntity>();
this.players = new FunkyArray<number, Player>(); this.players = new FunkyArray<number, Player>();
this.queuedChunkBlocks = new Array<IQueuedUpdate>();
this.queuedUpdates = new Array<IQueuedUpdate>();
this.generator = new HillyGenerator(seed); 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) { public async unloadChunk(coordPair:number) {
const chunk = this.getChunkByCoordPair(coordPair); const chunk = this.getChunkByCoordPair(coordPair);
if (!chunk.savingToDisk) { if (!chunk.savingToDisk) {
@ -167,6 +181,25 @@ export class World {
} }
public tick() { 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 => { this.entites.forEach(entity => {
entity.onTick(); entity.onTick();

View file

@ -86,6 +86,9 @@ export class WorldSaveManager {
} }
public writeChunkToDisk(chunk:Chunk) { public writeChunkToDisk(chunk:Chunk) {
/*return new Promise<boolean>((resolve, reject) => {
resolve(true);
});*/
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
const saveType = SaveCompressionType[this.config.saveCompression]; const saveType = SaveCompressionType[this.config.saveCompression];
const chunkFileWriter = new Writer(10); const chunkFileWriter = new Writer(10);

View file

@ -1,10 +1,22 @@
export class Block { export class Block {
public readonly blockId:number; public readonly blockId:number;
public static readonly blocks:Array<Block> = new Array<Block>();
public static readonly lightPassage:Array<number> = new Array<number>();
public constructor(blockId:number) { public constructor(blockId:number) {
Block.blocks[blockId] = this;
Block.lightPassage[blockId] = 0;
this.blockId = blockId; this.blockId = blockId;
} }
public get lightPassage() {
return Block.lightPassage[this.blockId];
}
public set lightPassage(value:number) {
Block.lightPassage[this.blockId] = value;
}
// Define statics here // Define statics here
static readonly stone = new Block(1); static readonly stone = new Block(1);
static readonly grass = new Block(2); static readonly grass = new Block(2);
@ -20,5 +32,7 @@ export class Block {
static readonly wood = new Block(17); static readonly wood = new Block(17);
static readonly leaves = new Block(18); static readonly leaves = new Block(18);
static readonly tallGrass = new Block(31);
static readonly clay = new Block(82); static readonly clay = new Block(82);
} }

View file

@ -17,6 +17,8 @@ export class Entity implements IEntity {
public lastY:number; public lastY:number;
public lastZ:number; public lastZ:number;
public health:number;
public fire:number; public fire:number;
public crouching:boolean; public crouching:boolean;
@ -31,6 +33,8 @@ export class Entity implements IEntity {
this.world = world; this.world = world;
this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0; this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0;
this.crouching = this.lastCrouchState = this.lastFireState = false; this.crouching = this.lastCrouchState = this.lastFireState = false;
this.health = 20;
} }
updateMetadata() { 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)); 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.lastCrouchState = this.crouching;
this.lastFireState = this.fire > 0; 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)); 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() { onTick() {
this.updateMetadata(); this.updateMetadata();
if (this.fire > 0) { if (this.fire > 0) {
if (this.fire % 20 === 0) { if (this.fire % 20 === 0) {
this.damageFrom(1);
} }
this.fire--; this.fire--;

View file

@ -11,6 +11,7 @@ export class EntityLiving extends Entity {
public pitch:number; public pitch:number;
public lastPitch:number; public lastPitch:number;
public onGround:boolean; public onGround:boolean;
public fallDistance:number;
public absX:number; public absX:number;
public absY:number; public absY:number;
@ -26,8 +27,8 @@ export class EntityLiving extends Entity {
public constructor(world:World) { public constructor(world:World) {
super(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.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 = false; this.onGround = true;
} }
private constrainRot(rot:number) { private constrainRot(rot:number) {
@ -73,6 +74,11 @@ export class EntityLiving extends Entity {
onTick() { onTick() {
super.onTick(); super.onTick();
this.sendPositionUpdate(); this.sendPositionUpdate();
if (!this.onGround) {
this.fallDistance
}
this.lastYaw = this.yaw; this.lastYaw = this.yaw;
this.lastPitch = this.lastPitch; this.lastPitch = this.lastPitch;
} }

View file

@ -5,6 +5,7 @@ import { World } from "../World";
import { PacketMapChunk } from "../packets/MapChunk"; import { PacketMapChunk } from "../packets/MapChunk";
import { EntityLiving } from "./EntityLiving"; import { EntityLiving } from "./EntityLiving";
import { PacketPreChunk } from "../packets/PreChunk"; import { PacketPreChunk } from "../packets/PreChunk";
import { PacketUpdateHealth } from "../packets/UpdateHealth";
export class Player extends EntityLiving { export class Player extends EntityLiving {
public username:string; public username:string;
@ -14,6 +15,8 @@ export class Player extends EntityLiving {
public justUnloaded:Array<number>; public justUnloaded:Array<number>;
public mpClient?:MPClient; public mpClient?:MPClient;
private lastHealth:number;
public constructor(server:MinecraftServer, world:World, username:string) { public constructor(server:MinecraftServer, world:World, username:string) {
super(world); super(world);
this.server = server; this.server = server;
@ -25,6 +28,8 @@ export class Player extends EntityLiving {
this.x = 8; this.x = 8;
this.y = 64; this.y = 64;
this.z = 8; this.z = 8;
this.lastHealth = this.health;
} }
private async updatePlayerChunks() { private async updatePlayerChunks() {
@ -33,7 +38,7 @@ export class Player extends EntityLiving {
if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.firstUpdate) { if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.firstUpdate) {
if (this.firstUpdate) { if (this.firstUpdate) {
this.firstUpdate = false; 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()); this.mpClient?.send(new PacketPreChunk(0, 0, true).writeData());
const chunk = await this.world.getChunkSafe(0, 0); const chunk = await this.world.getChunkSafe(0, 0);
const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData()); const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData());
@ -74,6 +79,11 @@ export class Player extends EntityLiving {
public onTick() { public onTick() {
this.updatePlayerChunks(); this.updatePlayerChunks();
if (this.health != this.lastHealth) {
this.lastHealth = this.health;
this.mpClient?.send(new PacketUpdateHealth(this.health).writeData());
}
super.onTick(); super.onTick();
} }
} }

View file

@ -2,6 +2,7 @@ import { Block } from "../blocks/Block";
import { Chunk } from "../Chunk"; import { Chunk } from "../Chunk";
import { IGenerator } from "./IGenerator"; import { IGenerator } from "./IGenerator";
import { Noise2D, makeNoise2D } from "../../external/OpenSimplex2D"; import { Noise2D, makeNoise2D } from "../../external/OpenSimplex2D";
import { QueuedBlockUpdate } from "../queuedUpdateTypes/BlockUpdate";
export class HillyGenerator implements IGenerator { export class HillyGenerator implements IGenerator {
private seed:number; private seed:number;
@ -60,6 +61,8 @@ export class HillyGenerator implements IGenerator {
public generate(chunk:Chunk) { public generate(chunk:Chunk) {
const treeRNG = this.mulberry32(this.seed + chunk.x + chunk.z); 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; let colY = 0, colDirtMin = 0, colWaterY = 0, orgColY = 0;
for (let x = 0; x < 16; x++) { for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) { for (let z = 0; z < 16; z++) {
@ -77,7 +80,7 @@ export class HillyGenerator implements IGenerator {
) / 9); ) / 9);
colDirtMin = colY - 2; colDirtMin = colY - 2;
const sandNoise = this.underwaterSandGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16); 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); chunk.setBlock(Block.sand.blockId, x, colY, z);
} else { } else {
chunk.setBlock(Block.grass.blockId, x, colY, z); 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); 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 // 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) { 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; const treeType = treeRNG() >= 0.5;

View file

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

View file

@ -0,0 +1 @@
export interface IQueuedUpdate {}