cross chunk block placement
This commit is contained in:
parent
75d36b49db
commit
686a694432
11 changed files with 144 additions and 8 deletions
|
@ -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<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) {
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<number, IEntity>;
|
||||
public players:FunkyArray<number, Player>;
|
||||
|
||||
public queuedChunkBlocks:Array<IQueuedUpdate>;
|
||||
public queuedUpdates:Array<IQueuedUpdate>;
|
||||
public generator:IGenerator;
|
||||
|
||||
public constructor(saveManager:WorldSaveManager, seed:number) {
|
||||
|
@ -25,6 +29,8 @@ export class World {
|
|||
this.chunks = new FunkyArray<number, Chunk>();
|
||||
this.entites = new FunkyArray<number, IEntity>();
|
||||
this.players = new FunkyArray<number, Player>();
|
||||
this.queuedChunkBlocks = new Array<IQueuedUpdate>();
|
||||
this.queuedUpdates = new Array<IQueuedUpdate>();
|
||||
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();
|
||||
|
||||
|
|
|
@ -86,6 +86,9 @@ export class WorldSaveManager {
|
|||
}
|
||||
|
||||
public writeChunkToDisk(chunk:Chunk) {
|
||||
/*return new Promise<boolean>((resolve, reject) => {
|
||||
resolve(true);
|
||||
});*/
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const saveType = SaveCompressionType[this.config.saveCompression];
|
||||
const chunkFileWriter = new Writer(10);
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
export class Block {
|
||||
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) {
|
||||
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);
|
||||
}
|
|
@ -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--;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<number>;
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
24
server/queuedUpdateTypes/BlockUpdate.ts
Normal file
24
server/queuedUpdateTypes/BlockUpdate.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
1
server/queuedUpdateTypes/IQueuedUpdate.ts
Normal file
1
server/queuedUpdateTypes/IQueuedUpdate.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export interface IQueuedUpdate {}
|
Loading…
Reference in a new issue