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-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-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
|
|
|
|
|
|
|
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-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-04-09 04:19:10 +01:00
|
|
|
const chunk = this.getChunkByCoordPair(coordPair);
|
|
|
|
chunk.playersInChunk.remove(entity.entityId);
|
|
|
|
|
2023-04-11 07:47:56 +01:00
|
|
|
if (!chunk.forceLoaded && chunk.playersInChunk.length === 0) {
|
2023-04-09 04:19:10 +01:00
|
|
|
this.unloadChunk(coordPair);
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
public getChunk(x:number, z:number, generate:boolean = true) {
|
|
|
|
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) {
|
|
|
|
return new Promise<Chunk>((resolve, reject) => {
|
|
|
|
const coordPair = Chunk.CreateCoordPair(x, z);
|
|
|
|
const existingChunk = this.chunks.get(coordPair);
|
|
|
|
if (!(existingChunk instanceof Chunk)) {
|
|
|
|
if (this.saveManager.chunksOnDisk.includes(coordPair)) {
|
|
|
|
return this.saveManager.readChunkFromDisk(this, x, z)
|
|
|
|
.then(chunk => {
|
|
|
|
//console.log("Loaded " + x + "," + z + " from disk");
|
|
|
|
resolve(this.chunks.set(coordPair, chunk));
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return resolve(this.chunks.set(coordPair, new Chunk(this, x, z, true)));
|
|
|
|
}
|
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 - chunkX << 4, y, z - chunkZ << 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
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.setBlock(blockId, x - chunkX << 4, y, z - chunkZ << 4);
|
|
|
|
|
|
|
|
const blockUpdatePacket = new PacketBlockChange(x, y, z, blockId, 0).writeData(); // TODO: Handle metadata
|
|
|
|
if (doBlockUpdate) {
|
|
|
|
// 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-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-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-04-09 04:19:10 +01:00
|
|
|
const chunkToUnload = this.getChunkByCoordPair(coordPair);
|
|
|
|
chunkToUnload.playersInChunk.remove(entity.entityId);
|
2023-04-11 07:47:56 +01:00
|
|
|
if (!chunkToUnload.forceLoaded && chunkToUnload.playersInChunk.length === 0) {
|
2023-04-09 04:19:10 +01:00
|
|
|
this.unloadChunk(coordPair);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
entity.justUnloaded = new Array<number>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2023-04-08 20:52:47 +01:00
|
|
|
}
|
|
|
|
}
|