From 5de6e743239610470312b65a26f373072f03da70 Mon Sep 17 00:00:00 2001 From: Holly Date: Sun, 9 Apr 2023 04:19:10 +0100 Subject: [PATCH] INFINITE TERRAIN!!!! --- config.json | 3 +- config.ts | 3 +- external/OpenSimplex2D.ts | 195 +++++++++++++++++++++++++++ external/shuffle_seed.ts | 8 ++ server/Chunk.ts | 9 +- server/MPClient.ts | 56 +++++++- server/MinecraftServer.ts | 80 ++++++++--- server/World.ts | 71 ++++++++-- server/blocks/Block.ts | 11 ++ server/entities/Entity.ts | 6 + server/entities/EntityLiving.ts | 18 +++ server/entities/IEntity.ts | 9 +- server/entities/Player.ts | 65 ++++++++- server/generators/Hilly.ts | 94 +++++++++++++ server/packets/Chat.ts | 8 +- server/packets/MapChunk.ts | 2 +- server/packets/Player.ts | 8 +- server/packets/PlayerLook.ts | 14 +- server/packets/PlayerPosition.ts | 20 ++- server/packets/PlayerPositionLook.ts | 26 ++-- server/packets/TimeUpdate.ts | 6 +- 21 files changed, 648 insertions(+), 64 deletions(-) create mode 100644 external/OpenSimplex2D.ts create mode 100644 external/shuffle_seed.ts create mode 100644 server/entities/EntityLiving.ts create mode 100644 server/generators/Hilly.ts diff --git a/config.json b/config.json index e5c5af3..a88c081 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,6 @@ { "port": 25565, "onlineMode": false, - "maxPlayers": 20 + "maxPlayers": 20, + "seed": "really janky" } \ No newline at end of file diff --git a/config.ts b/config.ts index e09224e..3404424 100644 --- a/config.ts +++ b/config.ts @@ -1,5 +1,6 @@ export interface Config { port: number, onlineMode: boolean, - maxPlayers: number + maxPlayers: number, + seed: number|string, } \ No newline at end of file diff --git a/external/OpenSimplex2D.ts b/external/OpenSimplex2D.ts new file mode 100644 index 0000000..643b07a --- /dev/null +++ b/external/OpenSimplex2D.ts @@ -0,0 +1,195 @@ +// This is free and unencumbered software released into the public domain + +import shuffleSeed from "./shuffle_seed"; + +const NORM_2D = 1.0 / 47.0; +const SQUISH_2D = (Math.sqrt(2 + 1) - 1) / 2; +const STRETCH_2D = (1 / Math.sqrt(2 + 1) - 1) / 2; + +export type Noise2D = (x: number, y: number) => number; + +interface Contribution2D { + dx: number; + dy: number; + next?: Contribution2D; + xsb: number; + ysb: number; +} + +function contribution2D( + multiplier: number, + xsb: number, + ysb: number, +): Contribution2D { + return { + dx: -xsb - multiplier * SQUISH_2D, + dy: -ysb - multiplier * SQUISH_2D, + xsb, + ysb, + }; +} + +export function makeNoise2D(clientSeed: number): Noise2D { + const contributions: Contribution2D[] = []; + for (let i = 0; i < p2D.length; i += 4) { + const baseSet = base2D[p2D[i]]; + let previous: Contribution2D | null = null; + let current: Contribution2D | null = null; + for (let k = 0; k < baseSet.length; k += 3) { + current = contribution2D(baseSet[k], baseSet[k + 1], baseSet[k + 2]); + if (previous === null) contributions[i / 4] = current; + else previous.next = current; + previous = current; + } + current!.next = contribution2D(p2D[i + 1], p2D[i + 2], p2D[i + 3]); + } + const lookup: Contribution2D[] = []; + for (let i = 0; i < lookupPairs2D.length; i += 2) { + lookup[lookupPairs2D[i]] = contributions[lookupPairs2D[i + 1]]; + } + + const perm = new Uint8Array(256); + const perm2D = new Uint8Array(256); + const source = new Uint8Array(256); + for (let i = 0; i < 256; i++) source[i] = i; + let seed = new Uint32Array(1); + seed[0] = clientSeed; + seed = shuffleSeed(shuffleSeed(shuffleSeed(seed))); + for (let i = 255; i >= 0; i--) { + seed = shuffleSeed(seed); + const r = new Uint32Array(1); + r[0] = (seed[0] + 31) % (i + 1); + if (r[0] < 0) r[0] += i + 1; + perm[i] = source[r[0]]; + perm2D[i] = perm[i] & 0x0e; + source[r[0]] = source[i]; + } + + return (x: number, y: number): number => { + const stretchOffset = (x + y) * STRETCH_2D; + + const xs = x + stretchOffset; + const ys = y + stretchOffset; + + const xsb = Math.floor(xs); + const ysb = Math.floor(ys); + + const squishOffset = (xsb + ysb) * SQUISH_2D; + + const dx0 = x - (xsb + squishOffset); + const dy0 = y - (ysb + squishOffset); + + const xins = xs - xsb; + const yins = ys - ysb; + + const inSum = xins + yins; + const hash = (xins - yins + 1) | + (inSum << 1) | + ((inSum + yins) << 2) | + ((inSum + xins) << 4); + + let value = 0; + + for ( + let c: Contribution2D | undefined = lookup[hash]; + c !== undefined; + c = c.next + ) { + const dx = dx0 + c.dx; + const dy = dy0 + c.dy; + + const attn = 2 - dx * dx - dy * dy; + if (attn > 0) { + const px = xsb + c.xsb; + const py = ysb + c.ysb; + + const indexPartA = perm[px & 0xff]; + const index = perm2D[(indexPartA + py) & 0xff]; + + const valuePart = gradients2D[index] * dx + gradients2D[index + 1] * dy; + + value += attn * attn * attn * attn * valuePart; + } + } + + return value * NORM_2D; + }; +} + +const base2D = [ + [1, 1, 0, 1, 0, 1, 0, 0, 0], + [1, 1, 0, 1, 0, 1, 2, 1, 1], +]; + +const gradients2D = [ + 5, + 2, + 2, + 5, + -5, + 2, + -2, + 5, + 5, + -2, + 2, + -5, + -5, + -2, + -2, + -5, +]; + +const lookupPairs2D = [ + 0, + 1, + 1, + 0, + 4, + 1, + 17, + 0, + 20, + 2, + 21, + 2, + 22, + 5, + 23, + 5, + 26, + 4, + 39, + 3, + 42, + 4, + 43, + 3, +]; + +const p2D = [ + 0, + 0, + 1, + -1, + 0, + 0, + -1, + 1, + 0, + 2, + 1, + 1, + 1, + 2, + 2, + 0, + 1, + 2, + 0, + 2, + 1, + 0, + 0, + 0, +]; diff --git a/external/shuffle_seed.ts b/external/shuffle_seed.ts new file mode 100644 index 0000000..3c44d32 --- /dev/null +++ b/external/shuffle_seed.ts @@ -0,0 +1,8 @@ +// This is free and unencumbered software released into the public domain + +export default function shuffleSeed(seed: Uint32Array): Uint32Array { + const newSeed = new Uint32Array(1); + newSeed[0] = seed[0] * 1664525 + 1013904223; + return newSeed; + } + \ No newline at end of file diff --git a/server/Chunk.ts b/server/Chunk.ts index ac037ed..97e19d3 100644 --- a/server/Chunk.ts +++ b/server/Chunk.ts @@ -1,11 +1,14 @@ +import { FunkyArray } from "../funkyArray"; import { Block } from "./blocks/Block"; +import { Player } from "./entities/Player"; import { World } from "./World"; export class Chunk { private readonly MAX_HEIGHT:number = 128; private readonly world:World; - private readonly x:number; - private readonly z:number; + public readonly x:number; + public readonly z:number; + public readonly playersInChunk:FunkyArray; private blocks:Uint8Array; @@ -17,6 +20,8 @@ export class Chunk { this.world = world; this.x = x; this.z = z; + this.playersInChunk = new FunkyArray(); + this.blocks = new Uint8Array(16 * 16 * this.MAX_HEIGHT); this.world.generator.generate(this); diff --git a/server/MPClient.ts b/server/MPClient.ts index cf86872..0576ce8 100644 --- a/server/MPClient.ts +++ b/server/MPClient.ts @@ -1,16 +1,64 @@ import { Socket } from "net"; -import { IEntity } from "./entities/IEntity"; -import { Writer } from "../bufferStuff"; +import { Reader, Writer } from "../bufferStuff"; +import { Packets } from "./enums/Packets"; +import { PacketPlayer } from "./packets/Player"; +import { PacketPlayerPosition } from "./packets/PlayerPosition"; +import { PacketPlayerLook } from "./packets/PlayerLook"; +import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook"; +import { Player } from "./entities/Player"; +import { PacketChat } from "./packets/Chat"; export class MPClient { private readonly socket:Socket; - private readonly entity:IEntity; + public readonly entity:Player; - public constructor(socket:Socket, entity:IEntity) { + public constructor(socket:Socket, entity:Player) { this.socket = socket; this.entity = entity; } + handlePacket(reader:Reader) { + const packetId = reader.readUByte(); + + switch (packetId) { + case Packets.Chat: this.handleChat(new PacketChat().readData(reader)); break; + case Packets.Player: this.handlePacketPlayer(new PacketPlayer().readData(reader)); break; + case Packets.PlayerPosition: this.handlePacketPlayerPosition(new PacketPlayerPosition().readData(reader)); break; + case Packets.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break; + case Packets.PlayerPositionLook: this.handlePacketPlayerPositionLook(new PacketPlayerPositionLook().readData(reader)); break; + } + } + + handleChat(packet:PacketChat) { + const message = packet.message.split(" "); + if (message[0] === "/tp") { + this.send(new PacketPlayerPositionLook(parseFloat(message[1]), parseFloat(message[2]), parseFloat(message[2]) + 0.62, parseFloat(message[3]), 0, 0, false).writeData()); + } + } + + handlePacketPlayer(packet:PacketPlayer) { + // TODO + } + + handlePacketPlayerPosition(packet:PacketPlayerPosition) { + this.entity.x = packet.x; + this.entity.y = packet.y; + this.entity.z = packet.z; + } + + handlePacketPlayerLook(packet:PacketPlayerLook) { + this.entity.yaw = packet.yaw; + this.entity.pitch = packet.pitch; + } + + handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) { + this.entity.x = packet.x; + this.entity.y = packet.y; + this.entity.z = packet.z; + this.entity.yaw = packet.yaw; + this.entity.pitch = packet.pitch; + } + send(buffer:Buffer|Writer) { if (buffer instanceof Writer) { this.socket.write(buffer.toBuffer()); diff --git a/server/MinecraftServer.ts b/server/MinecraftServer.ts index fc080b7..76f8377 100644 --- a/server/MinecraftServer.ts +++ b/server/MinecraftServer.ts @@ -17,6 +17,7 @@ import { Chunk } from "./Chunk"; import { PacketMapChunk } from "./packets/MapChunk"; import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook"; import { PacketPreChunk } from "./packets/PreChunk"; +import { PacketChat } from "./packets/Chat"; export class MinecraftServer { private static readonly PROTOCOL_VERSION = 14; @@ -29,25 +30,56 @@ export class MinecraftServer { private server:Server; private serverClock:NodeJS.Timer; private tickCounter:number = 0; - private clients:FunkyArray; + private clients:FunkyArray; private worlds:FunkyArray; + private overworld:World; + + // https://stackoverflow.com/a/7616484 + // Good enough for the world seed. + private hashCode(string:string) : number { + let hash = 0, i, chr; + if (string.length === 0) { + return hash; + } + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; + } + return hash; + } public constructor(config:Config) { this.config = config; - this.clients = new FunkyArray(); + this.clients = new FunkyArray(); + + // Convert seed if needed + const worldSeed = typeof(this.config.seed) === "string" ? this.hashCode(this.config.seed) : this.config.seed; this.worlds = new FunkyArray(); - this.worlds.set(0, new World()); + this.worlds.set(0, this.overworld = new World(worldSeed)); + + // Generate spawn area (overworld) + const generateStartTime = Date.now(); + Console.printInfo("[Overworld] Generating spawn area..."); + let generatedCount = 0; + for (let x = -3; x < 3; x++) { + for (let z = -3; z < 3; z++) { + this.overworld.getChunk(x, z); + if (generatedCount++ % 5 === 0) { + Console.printInfo(`[Overworld] Generating spawn area... ${Math.floor(generatedCount / 36 * 100)}%`); + } + } + } + Console.printInfo(`Done! Took ${Date.now() - generateStartTime}ms`); this.serverClock = setInterval(() => { // Every 1 sec if (this.tickCounter % MinecraftServer.TICK_RATE === 0) { if (this.clients.length !== 0) { - const timePacket = new PacketTimeUpdate(this.tickCounter).writeData(); this.clients.forEach(client => { client.send(this.keepalivePacket); - client.send(timePacket); }); } } @@ -70,11 +102,26 @@ export class MinecraftServer { } onConnection(socket:Socket) { + let mpClient:MPClient; + + const playerDisconnect = (err:Error) => { + mpClient.entity.world.removeEntity(mpClient.entity); + this.clients.remove(mpClient.entity.username); + this.sendToAllClients(new PacketChat(`\u00a7e${mpClient.entity.username} left the game`).writeData()); + } + socket.on("close", playerDisconnect.bind(this)); + socket.on("error", playerDisconnect.bind(this)); + socket.on("data", chunk => { const reader = new Reader(chunk); + // Let mpClient take over if it exists + if (mpClient instanceof MPClient) { + mpClient.handlePacket(reader); + return; + } + const packetId = reader.readUByte(); - //console.log(packetId); switch (packetId) { // Handle timeouts at some point, idk. case Packets.KeepAlive: @@ -95,20 +142,17 @@ export class MinecraftServer { if (world instanceof World) { const clientEntity = new Player(this, world, loginPacket.username); world.addEntity(clientEntity); - socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, -1).writeData()); + + const client = mpClient = new MPClient(socket, clientEntity); + clientEntity.mpClient = client; + this.clients.set(loginPacket.username, client); + + this.sendToAllClients(new PacketChat(`\u00a7e${loginPacket.username} joined the game`).writeData()); + + socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, 0).writeData()); socket.write(new PacketSpawnPosition(8, 64, 8).writeData()); - socket.write(new PacketPreChunk(0, 0, true).writeData()); - const chunk = world.getChunk(0, 0); - if (chunk instanceof Chunk) { - (async () => { - const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData()); - socket.write(chunkData); - socket.write(new PacketPlayerPositionLook(8, 66, 66.62, 8, 0, 0, false).writeData()); - })(); - } - const client = new MPClient(socket, clientEntity); - this.clients.set(this.totalClients++, client); + socket.write(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData()); } else { socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData()); } diff --git a/server/World.ts b/server/World.ts index 9377c0c..63455ad 100644 --- a/server/World.ts +++ b/server/World.ts @@ -1,7 +1,10 @@ +import { Console } from "../console"; import { FunkyArray } from "../funkyArray"; import { Chunk } from "./Chunk"; import { IEntity } from "./entities/IEntity"; +import { Player } from "./entities/Player"; import { FlatGenerator } from "./generators/Flat"; +import { HillyGenerator } from "./generators/Hilly"; import { IGenerator } from "./generators/IGenerator"; export class World { @@ -10,30 +13,78 @@ export class World { public generator:IGenerator; - public constructor() { + public constructor(seed:number) { this.chunks = new FunkyArray(); this.entites = new FunkyArray(); - this.generator = new FlatGenerator(); - this.chunks.set(Chunk.CreateCoordPair(0, 0), new Chunk(this, 0, 0)); + this.generator = new HillyGenerator(seed); } public addEntity(entity:IEntity) { this.entites.set(entity.entityId, entity); } - public removeEntity(entity:IEntity|number) { - if (typeof(entity) === "number") { - return this.entites.remove(entity); + // TODO: getChunkByCoordPair failed in here during removeEntity, figure out why. + public removeEntity(entity:IEntity) { + if (entity instanceof Player) { + for (let coordPair of entity.loadedChunks) { + const chunk = this.getChunkByCoordPair(coordPair); + chunk.playersInChunk.remove(entity.entityId); + + if (chunk.playersInChunk.length === 0) { + this.unloadChunk(coordPair); + } + } } - return this.entites.remove(entity.entityId); + this.entites.remove(entity.entityId); + // TODO: Inform clients about entity removal } - public getChunk(x:number, z:number) { - return this.chunks.get(Chunk.CreateCoordPair(x, z)); + 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)) { + if (generate) { + return this.chunks.set(coordPair, new Chunk(this, x, z)); + } + + throw new Error(`BADLOOKUP: Chunk [${x}, ${z}] does not exist.`); + } + + return existingChunk; + } + + public getChunkByCoordPair(coordPair:number) { + const existingChunk = this.chunks.get(coordPair); + if (!(existingChunk instanceof Chunk)) { + throw new Error(`BADLOOKUP: Chunk ${coordPair} does not exist.`); + } + + return existingChunk; + } + + public unloadChunk(coordPair:number) { + // TODO: Save to disk + this.chunks.remove(coordPair); } public tick(tickCount:number) { - + this.entites.forEach(entity => { + if (entity instanceof Player) { + if (entity.justUnloaded.length > 0) { + for (let coordPair of entity.justUnloaded) { + const chunkToUnload = this.getChunkByCoordPair(coordPair); + chunkToUnload.playersInChunk.remove(entity.entityId); + if (chunkToUnload.playersInChunk.length === 0) { + this.unloadChunk(coordPair); + } + } + + entity.justUnloaded = new Array(); + } + } + + entity.onTick(); + }) } } \ No newline at end of file diff --git a/server/blocks/Block.ts b/server/blocks/Block.ts index 3884477..918929e 100644 --- a/server/blocks/Block.ts +++ b/server/blocks/Block.ts @@ -12,4 +12,15 @@ export class Block { static bedrock = new Block(7); + + static waterStill = new Block(9); + + + + + + + + static wood = new Block(17); + static leaves = new Block(18); } \ No newline at end of file diff --git a/server/entities/Entity.ts b/server/entities/Entity.ts index 79fcf06..f45c0f3 100644 --- a/server/entities/Entity.ts +++ b/server/entities/Entity.ts @@ -20,4 +20,10 @@ export class Entity implements IEntity { this.world = world; this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0; } + + onTick() { + this.lastX = this.x; + this.lastY = this.y; + this.lastZ = this.z; + } } \ No newline at end of file diff --git a/server/entities/EntityLiving.ts b/server/entities/EntityLiving.ts new file mode 100644 index 0000000..0a8152f --- /dev/null +++ b/server/entities/EntityLiving.ts @@ -0,0 +1,18 @@ +import { World } from "../World"; +import { Entity } from "./Entity"; + +export class EntityLiving extends Entity { + public yaw:number; + public pitch:number; + + public constructor(world:World) { + super(world); + + this.yaw = 0; + this.pitch = 0; + } + + onTick() { + super.onTick(); + } +} \ No newline at end of file diff --git a/server/entities/IEntity.ts b/server/entities/IEntity.ts index 3172fd8..8c46ce2 100644 --- a/server/entities/IEntity.ts +++ b/server/entities/IEntity.ts @@ -1,3 +1,10 @@ export interface IEntity { - entityId:number + entityId:number, + x:number, + y:number, + z:number, + lastX:number, + lastY:number, + lastZ:number, + onTick:() => void } \ No newline at end of file diff --git a/server/entities/Player.ts b/server/entities/Player.ts index d576be7..6edc893 100644 --- a/server/entities/Player.ts +++ b/server/entities/Player.ts @@ -1,18 +1,81 @@ +import { FunkyArray } from "../../funkyArray"; +import { Chunk } from "../Chunk"; +import { MPClient } from "../MPClient"; import { MinecraftServer } from "../MinecraftServer"; import { World } from "../World"; +import { PacketMapChunk } from "../packets/MapChunk"; +import { EntityLiving } from "./EntityLiving"; import { Entity } from "./Entity"; +import { Socket } from "net"; +import { PacketPreChunk } from "../packets/PreChunk"; -export class Player extends Entity { +export class Player extends EntityLiving { public username:string; private server:MinecraftServer; + private firstUpdate:boolean; + public loadedChunks:Array; + public justUnloaded:Array; + public mpClient?:MPClient; public constructor(server:MinecraftServer, world:World, username:string) { super(world); this.server = server; + this.firstUpdate = true; + this.loadedChunks = new Array(); + this.justUnloaded = new Array(); this.username = username; this.x = 8; this.y = 64; this.z = 8; } + + onTick() { + const bitX = this.x >> 4; + const bitZ = this.z >> 4; + if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.firstUpdate) { + if (this.firstUpdate) { + this.firstUpdate = false; + this.mpClient?.send(new PacketPreChunk(0, 0, true).writeData()); + const chunk = this.world.getChunk(0, 0); + (async () => { + const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData()); + this.mpClient?.send(chunkData); + })(); + } + + // Load or keep any chunks we need + const currentLoads = []; + for (let x = bitX - 6; x < bitX + 6; x++) { + for (let z = bitZ - 6; z < bitZ + 6; z++) { + const coordPair = Chunk.CreateCoordPair(x, z); + if (!this.loadedChunks.includes(coordPair)) { + const chunk = this.world.getChunk(x, z); + this.mpClient?.send(new PacketPreChunk(x, z, true).writeData()); + this.loadedChunks.push(coordPair); + chunk.playersInChunk.set(this.entityId, this); + (async () => { + const chunkData = await (new PacketMapChunk(x, 0, z, 15, 127, 15, chunk).writeData()); + this.mpClient?.send(chunkData); + })(); + } + currentLoads.push(coordPair); + } + } + + // Mark any unaccounted chunks for unload + for (let coordPair of this.loadedChunks) { + if (!currentLoads.includes(coordPair)) { + this.justUnloaded.push(coordPair); + const chunkToUnload = this.world.getChunkByCoordPair(coordPair); + this.mpClient?.send(new PacketPreChunk(chunkToUnload.x, chunkToUnload.z, false).writeData()); + } + } + + // Overwrite loaded chunks + this.loadedChunks = currentLoads; + } + + super.onTick(); + } } \ No newline at end of file diff --git a/server/generators/Hilly.ts b/server/generators/Hilly.ts new file mode 100644 index 0000000..e8177b1 --- /dev/null +++ b/server/generators/Hilly.ts @@ -0,0 +1,94 @@ +import { Block } from "../blocks/Block"; +import { Chunk } from "../Chunk"; +import { IGenerator } from "./IGenerator"; +import { Noise2D, makeNoise2D } from "../../external/OpenSimplex2D"; +import shuffle_seed from "../../external/shuffle_seed"; + +export class HillyGenerator implements IGenerator { + private seed:number; + private generator:Noise2D; + private generator1:Noise2D; + private generator2:Noise2D; + private generator3:Noise2D; + private generator4:Noise2D; + private generator5:Noise2D; + private generator6:Noise2D; + private oceanGenerator:Noise2D; + private mountainGenerator:Noise2D; + + public constructor(seed:number) { + this.seed = seed; + + const generatorSeed = this.mulberry32(this.seed); + this.generator = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.generator1 = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.generator2 = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.generator3 = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.generator4 = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.generator5 = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.generator6 = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.oceanGenerator = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + this.mountainGenerator = makeNoise2D(generatorSeed() * Number.MAX_SAFE_INTEGER); + } + + // https://stackoverflow.com/a/47593316 + // This is good enough (and fast enough) for what is needed here. + private mulberry32(a:number) { + return function() { + var t = a += 0x6D2B79F5; + t = Math.imul(t ^ t >>> 15, t | 1); + t ^= t + Math.imul(t ^ t >>> 7, t | 61); + return ((t ^ t >>> 14) >>> 0) / 4294967296; + } + } + + public generate(chunk:Chunk) { + const treeRNG = 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++) { + const oceanValue = this.oceanGenerator((chunk.x * 16 + x) / 128, (chunk.z * 16 + z) / 128) * 100; + orgColY = colWaterY = colY = 60 + ( + this.generator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) * 16 + + this.generator1((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) * 16 + + this.generator2((chunk.x * 16 + x) / 8, (chunk.z * 16 + z) / 8) * 8 + + this.generator3((chunk.x * 16 + x) / 4, (chunk.z * 16 + z) / 4) * 4 + + this.generator4((chunk.x * 16 + x) / 4, (chunk.z * 16 + z) / 4) * 4 + + this.generator5((chunk.x * 16 + x) / 10, (chunk.z * 16 + z) / 10) * 10 + + this.generator6((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) * 16 + + oceanValue + + (Math.max(this.mountainGenerator((chunk.x * 16 + x) / 128, (chunk.z * 16 + z) / 128), 0) * 50 + Math.min(oceanValue, 0)) + ) / 9; + colDirtMin = colY - 2; + chunk.setBlock(Block.grass.blockId, x, colY, z); + + while (colY-- > 0) { + if (colY >= colDirtMin) { + chunk.setBlock(Block.dirt.blockId, x, colY, z); + } else if (colY === 0) { + chunk.setBlock(Block.bedrock.blockId, x, colY, z); + } else { + chunk.setBlock(Block.stone.blockId, x, colY, z); + } + } + + if (colWaterY <= 58) { + chunk.setBlock(Block.dirt.blockId, x, colWaterY, z); + } + while (colWaterY <= 58) { + colWaterY++; + chunk.setBlock(Block.waterStill.blockId, x, colWaterY, z); + } + + if (chunk.getBlockId(x, orgColY + 1, z) !== Block.waterStill.blockId && chunk.getBlockId(x, orgColY, z) === Block.grass.blockId && treeRNG() > 0.995) { + chunk.setBlock(Block.dirt.blockId, x, orgColY, z); + let tY = orgColY + 1; + while (tY < orgColY + 5) { + chunk.setBlock(Block.wood.blockId, x, tY, z); + tY++; + } + } + } + } + } +} \ No newline at end of file diff --git a/server/packets/Chat.ts b/server/packets/Chat.ts index 2be031b..5c7344d 100644 --- a/server/packets/Chat.ts +++ b/server/packets/Chat.ts @@ -6,8 +6,12 @@ export class PacketChat implements IPacket { public packetId = Packets.Chat; public message:string; - public constructor(message:string) { - this.message = message; + public constructor(message?:string) { + if (typeof(message) === "string") { + this.message = message; + } else { + this.message = ""; + } } public readData(reader:Reader) { diff --git a/server/packets/MapChunk.ts b/server/packets/MapChunk.ts index 61800f3..9f482b1 100644 --- a/server/packets/MapChunk.ts +++ b/server/packets/MapChunk.ts @@ -61,7 +61,7 @@ export class PacketMapChunk implements IPacket { return reject(err); } - resolve(new Writer(18).writeUByte(this.packetId).writeInt(this.x).writeShort(this.y).writeInt(this.z).writeUByte(this.sizeX).writeUByte(this.sizeY).writeUByte(this.sizeZ).writeInt(data.length).writeBuffer(data).toBuffer()); + resolve(new Writer(18).writeUByte(this.packetId).writeInt(this.x << 4).writeShort(this.y).writeInt(this.z << 4).writeUByte(this.sizeX).writeUByte(this.sizeY).writeUByte(this.sizeZ).writeInt(data.length).writeBuffer(data).toBuffer()); }); }); } diff --git a/server/packets/Player.ts b/server/packets/Player.ts index 3374169..a2aa928 100644 --- a/server/packets/Player.ts +++ b/server/packets/Player.ts @@ -6,8 +6,12 @@ export class PacketPlayer implements IPacket { public packetId = Packets.Player; public onGround:boolean; - public constructor(onGround:boolean = false) { - this.onGround = onGround; + public constructor(onGround?:boolean) { + if (typeof(onGround) === "boolean") { + this.onGround = onGround; + } else { + this.onGround = false; + } } public readData(reader:Reader) { diff --git a/server/packets/PlayerLook.ts b/server/packets/PlayerLook.ts index 9ab4c22..9577991 100644 --- a/server/packets/PlayerLook.ts +++ b/server/packets/PlayerLook.ts @@ -8,10 +8,16 @@ export class PacketPlayerLook implements IPacket { public pitch:number; public onGround:boolean; - public constructor(yaw:number, pitch:number, onGround:boolean = false) { - this.yaw = yaw; - this.pitch = pitch; - this.onGround = onGround; + public constructor(yaw?:number, pitch?:number, onGround?:boolean) { + if (typeof(yaw) === "number" && typeof(pitch) === "number" && typeof(onGround) === "boolean") { + this.yaw = yaw; + this.pitch = pitch; + this.onGround = onGround; + } else { + this.yaw = Number.MIN_VALUE; + this.pitch = Number.MIN_VALUE; + this.onGround = false; + } } public readData(reader:Reader) { diff --git a/server/packets/PlayerPosition.ts b/server/packets/PlayerPosition.ts index f537800..d83f099 100644 --- a/server/packets/PlayerPosition.ts +++ b/server/packets/PlayerPosition.ts @@ -10,12 +10,20 @@ export class PacketPlayerPosition implements IPacket { public z:number; public onGround:boolean; - public constructor(x:number, y:number, stance:number, z:number, onGround:boolean = false) { - this.x = x; - this.y = y; - this.stance = stance; - this.z = z; - this.onGround = onGround; + public constructor(x?:number, y?:number, stance?:number, z?:number, onGround?:boolean) { + if (typeof(x) === "number" && typeof(y) === "number" && typeof(stance) === "number" && typeof(z) === "number" && typeof(onGround) === "boolean") { + this.x = x; + this.y = y; + this.stance = stance; + this.z = z; + this.onGround = onGround; + } else { + this.x = Number.MIN_VALUE; + this.y = Number.MIN_VALUE; + this.stance = Number.MIN_VALUE; + this.z = Number.MIN_VALUE; + this.onGround = false; + } } public readData(reader:Reader) { diff --git a/server/packets/PlayerPositionLook.ts b/server/packets/PlayerPositionLook.ts index fbb2b08..4f9aae5 100644 --- a/server/packets/PlayerPositionLook.ts +++ b/server/packets/PlayerPositionLook.ts @@ -12,14 +12,24 @@ export class PacketPlayerPositionLook implements IPacket { public pitch:number; public onGround:boolean; - public constructor(x:number, y:number, stance:number, z:number, yaw:number, pitch:number, onGround:boolean = false) { - this.x = x; - this.y = y; - this.stance = stance; - this.z = z; - this.yaw = yaw; - this.pitch = pitch; - this.onGround = onGround; + public constructor(x?:number, y?:number, stance?:number, z?:number, yaw?:number, pitch?:number, onGround?:boolean) { + if (typeof(x) === "number" && typeof(y) === "number" && typeof(stance) === "number" && typeof(z) === "number" && typeof(yaw) === "number" && typeof(pitch) === "number" && typeof(onGround) === "boolean") { + this.x = x; + this.y = y; + this.stance = stance; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + this.onGround = onGround; + } else { + this.x = Number.MIN_VALUE; + this.y = Number.MIN_VALUE; + this.stance = Number.MIN_VALUE; + this.z = Number.MIN_VALUE; + this.yaw = Number.MIN_VALUE; + this.pitch = Number.MIN_VALUE; + this.onGround = false; + } } public readData(reader:Reader) { diff --git a/server/packets/TimeUpdate.ts b/server/packets/TimeUpdate.ts index 832f373..c361dba 100644 --- a/server/packets/TimeUpdate.ts +++ b/server/packets/TimeUpdate.ts @@ -4,14 +4,14 @@ import { IPacket } from "./IPacket"; export class PacketTimeUpdate implements IPacket { public packetId = Packets.TimeUpdate; - public time:number; + public time:bigint; - public constructor(time:number) { + public constructor(time:bigint) { this.time = time; } public readData(reader:Reader) { - this.time = Number(reader.readLong()); + this.time = reader.readLong(); return this; }