mc-beta-server/server/World.ts

237 lines
7.4 KiB
TypeScript
Raw Normal View History

2023-04-08 20:52:47 +01:00
import { FunkyArray } from "../funkyArray";
import { Chunk } from "./Chunk";
2023-04-11 07:47:56 +01:00
import { WorldSaveManager } from "./WorldSaveManager";
2023-04-08 20:52:47 +01:00
import { IEntity } from "./entities/IEntity";
2023-04-09 04:19:10 +01:00
import { Player } from "./entities/Player";
2023-04-09 04:47:23 +01:00
//import { FlatGenerator } from "./generators/Flat";
2023-04-09 04:19:10 +01:00
import { HillyGenerator } from "./generators/Hilly";
2023-04-08 20:52:47 +01:00
import { IGenerator } from "./generators/IGenerator";
2023-04-10 21:52:30 +01:00
import { PacketBlockChange } from "./packets/BlockChange";
2023-04-13 23:52:13 +01:00
import { QueuedBlockUpdate } from "./queuedUpdateTypes/BlockUpdate";
import { IQueuedUpdate } from "./queuedUpdateTypes/IQueuedUpdate";
2023-04-08 20:52:47 +01:00
export class World {
2023-04-10 14:42:14 +01:00
public static ENTITY_MAX_SEND_DISTANCE = 50;
2023-06-19 18:29:16 +01:00
private static READ_CHUNKS_FROM_DISK = false;
2023-04-10 14:42:14 +01:00
2023-04-11 07:47:56 +01:00
private readonly saveManager;
2023-04-08 20:52:47 +01:00
public chunks:FunkyArray<number, Chunk>;
public entites:FunkyArray<number, IEntity>;
2023-04-10 14:42:14 +01:00
public players:FunkyArray<number, Player>;
2023-04-08 20:52:47 +01:00
2023-04-13 23:52:13 +01:00
public queuedChunkBlocks:Array<IQueuedUpdate>;
public queuedUpdates:Array<IQueuedUpdate>;
2023-04-08 20:52:47 +01:00
public generator:IGenerator;
2023-04-11 07:47:56 +01:00
public constructor(saveManager:WorldSaveManager, seed:number) {
this.saveManager = saveManager;
2023-04-08 20:52:47 +01:00
this.chunks = new FunkyArray<number, Chunk>();
this.entites = new FunkyArray<number, IEntity>();
2023-04-10 14:42:14 +01:00
this.players = new FunkyArray<number, Player>();
2023-04-13 23:52:13 +01:00
this.queuedChunkBlocks = new Array<IQueuedUpdate>();
this.queuedUpdates = new Array<IQueuedUpdate>();
2023-04-09 04:19:10 +01:00
this.generator = new HillyGenerator(seed);
2023-04-08 20:52:47 +01:00
}
public addEntity(entity:IEntity) {
this.entites.set(entity.entityId, entity);
2023-04-10 14:42:14 +01:00
if (entity instanceof Player) {
this.players.set(entity.entityId, entity);
}
2023-04-08 20:52:47 +01:00
}
2023-04-09 04:19:10 +01:00
// TODO: getChunkByCoordPair failed in here during removeEntity, figure out why.
public removeEntity(entity:IEntity) {
if (entity instanceof Player) {
2023-04-09 04:47:23 +01:00
for (const coordPair of entity.loadedChunks) {
2023-05-02 08:50:49 +01:00
if (this.chunkExists(coordPair)) {
const chunk = this.getChunkByCoordPair(coordPair);
chunk.playersInChunk.remove(entity.entityId);
2023-04-09 04:19:10 +01:00
2023-05-02 08:50:49 +01:00
if (!chunk.forceLoaded && chunk.playersInChunk.length === 0) {
this.unloadChunk(coordPair);
}
2023-04-09 04:19:10 +01:00
}
}
2023-04-10 14:42:14 +01:00
this.players.remove(entity.entityId);
2023-04-09 04:19:10 +01:00
}
this.entites.remove(entity.entityId);
// TODO: Inform clients about entity removal
}
2023-05-02 08:50:49 +01:00
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) {
2023-04-09 04:19:10 +01:00
const coordPair = Chunk.CreateCoordPair(x, z);
const existingChunk = this.chunks.get(coordPair);
if (!(existingChunk instanceof Chunk)) {
2023-04-11 07:47:56 +01:00
throw new Error(`BADLOOKUP: Chunk [${x}, ${z}] does not exist.`);
}
return existingChunk;
}
public getChunkSafe(x:number, z:number) {
2023-06-26 09:53:45 +01:00
return new Promise<Chunk>((resolve) => {
2023-04-11 07:47:56 +01:00
const coordPair = Chunk.CreateCoordPair(x, z);
const existingChunk = this.chunks.get(coordPair);
if (!(existingChunk instanceof Chunk)) {
2023-06-19 18:29:16 +01:00
if (World.READ_CHUNKS_FROM_DISK && this.saveManager.chunksOnDisk.includes(coordPair)) {
2023-04-11 07:47:56 +01:00
return this.saveManager.readChunkFromDisk(this, x, z)
.then(chunk => {
//console.log("Loaded " + x + "," + z + " from disk");
resolve(this.chunks.set(coordPair, chunk));
});
} else {
resolve(this.chunks.set(coordPair, new Chunk(this, x, z, true)));
2023-06-19 18:29:16 +01:00
if (World.READ_CHUNKS_FROM_DISK) {
this.saveManager.writeChunkToDisk(this.getChunk(x, z));
}
return;
2023-04-11 07:47:56 +01:00
}
2023-04-09 04:19:10 +01:00
}
2023-04-11 07:47:56 +01:00
resolve(existingChunk);
});
}
public getChunkByCoordPair(coordPair:number) {
const existingChunk = this.chunks.get(coordPair);
if (!(existingChunk instanceof Chunk)) {
throw new Error(`BADLOOKUP: Chunk ${coordPair} does not exist.`);
2023-04-08 20:52:47 +01:00
}
2023-04-09 04:19:10 +01:00
return existingChunk;
2023-04-08 20:52:47 +01:00
}
2023-04-10 21:52:30 +01:00
public getBlockId(x:number, y:number, z:number) {
const chunkX = x >> 4,
chunkZ = z >> 4;
return this.getChunk(chunkX, chunkZ).getBlockId(x & 0xf, y, z & 0xf);
}
public getBlockMetadata(x:number, y:number, z:number) {
const chunkX = x >> 4,
chunkZ = z >> 4;
return this.getChunk(chunkX, chunkZ).getBlockMetadata(x & 0xf, y, z & 0xf);
2023-04-10 21:52:30 +01:00
}
public setBlock(blockId:number, x:number, y:number, z:number, doBlockUpdate?:boolean) {
const chunkX = x >> 4,
chunkZ = z >> 4;
const chunk = this.getChunk(chunkX, chunkZ);
chunk.setBlockWithMetadata(blockId, 0, x & 0xf, y, z & 0xf);
if (doBlockUpdate) {
const blockUpdatePacket = new PacketBlockChange(x, y, z, blockId, 0).writeData();
// Send block update to all players that have this chunk loaded
chunk.playersInChunk.forEach(player => {
player.mpClient?.send(blockUpdatePacket);
});
}
}
public setBlockWithMetadata(blockId:number, metadata:number, x:number, y:number, z:number, doBlockUpdate?:boolean) {
const chunkX = x >> 4,
chunkZ = z >> 4;
const chunk = this.getChunk(chunkX, chunkZ);
chunk.setBlockWithMetadata(blockId, metadata, x & 0xf, y, z & 0xf);
2023-04-10 21:52:30 +01:00
if (doBlockUpdate) {
const blockUpdatePacket = new PacketBlockChange(x, y, z, blockId, metadata).writeData(); // TODO: Handle metadata
2023-04-10 21:52:30 +01:00
// Send block update to all players that have this chunk loaded
chunk.playersInChunk.forEach(player => {
player.mpClient?.send(blockUpdatePacket);
});
}
}
2023-04-10 14:42:14 +01:00
public sendToNearbyClients(sentFrom:IEntity, buffer:Buffer) {
this.players.forEach(player => {
if (sentFrom.entityId !== player.entityId && Math.abs(sentFrom.distanceTo(player)) < World.ENTITY_MAX_SEND_DISTANCE) {
player.mpClient?.send(buffer);
}
});
}
2023-04-13 23:52:13 +01:00
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);
}
});
}
2023-04-11 07:47:56 +01:00
public async unloadChunk(coordPair:number) {
const chunk = this.getChunkByCoordPair(coordPair);
if (!chunk.savingToDisk) {
chunk.savingToDisk = true;
2023-04-09 04:19:10 +01:00
2023-04-11 07:47:56 +01:00
await this.saveManager.writeChunkToDisk(chunk);
2023-04-09 04:19:10 +01:00
2023-04-11 07:47:56 +01:00
if (chunk.playersInChunk.length === 0) {
this.chunks.remove(coordPair);
return;
}
// A player loaded the chunk while we were, flushing to disk.
// Keep it loaded.
chunk.savingToDisk = false;
}
2023-04-08 20:52:47 +01:00
}
2023-04-09 04:47:23 +01:00
public tick() {
2023-04-13 23:52:13 +01:00
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);
});
}
}
}
}
}
2023-04-09 04:19:10 +01:00
this.entites.forEach(entity => {
2023-04-10 14:42:14 +01:00
entity.onTick();
2023-04-09 04:19:10 +01:00
if (entity instanceof Player) {
if (entity.justUnloaded.length > 0) {
2023-04-09 04:47:23 +01:00
for (const coordPair of entity.justUnloaded) {
2023-05-02 08:50:49 +01:00
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);
}
2023-04-09 04:19:10 +01:00
}
}
entity.justUnloaded = new Array<number>();
}
}
})
2023-04-08 20:52:47 +01:00
}
}