INFINITE TERRAIN!!!!

This commit is contained in:
Holly Stubbs 2023-04-09 04:19:10 +01:00
parent 2503664723
commit 5de6e74323
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
21 changed files with 648 additions and 64 deletions

View file

@ -1,5 +1,6 @@
{ {
"port": 25565, "port": 25565,
"onlineMode": false, "onlineMode": false,
"maxPlayers": 20 "maxPlayers": 20,
"seed": "really janky"
} }

View file

@ -1,5 +1,6 @@
export interface Config { export interface Config {
port: number, port: number,
onlineMode: boolean, onlineMode: boolean,
maxPlayers: number maxPlayers: number,
seed: number|string,
} }

195
external/OpenSimplex2D.ts vendored Normal file
View file

@ -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,
];

8
external/shuffle_seed.ts vendored Normal file
View file

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

View file

@ -1,11 +1,14 @@
import { FunkyArray } from "../funkyArray";
import { Block } from "./blocks/Block"; import { Block } from "./blocks/Block";
import { Player } from "./entities/Player";
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; private readonly world:World;
private readonly x:number; public readonly x:number;
private readonly z:number; public readonly z:number;
public readonly playersInChunk:FunkyArray<number, Player>;
private blocks:Uint8Array; private blocks:Uint8Array;
@ -17,6 +20,8 @@ export class Chunk {
this.world = world; this.world = world;
this.x = x; this.x = x;
this.z = z; this.z = z;
this.playersInChunk = new FunkyArray<number, Player>();
this.blocks = new Uint8Array(16 * 16 * this.MAX_HEIGHT); this.blocks = new Uint8Array(16 * 16 * this.MAX_HEIGHT);
this.world.generator.generate(this); this.world.generator.generate(this);

View file

@ -1,16 +1,64 @@
import { Socket } from "net"; import { Socket } from "net";
import { IEntity } from "./entities/IEntity"; import { Reader, Writer } from "../bufferStuff";
import { 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 { export class MPClient {
private readonly socket:Socket; 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.socket = socket;
this.entity = entity; 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) { send(buffer:Buffer|Writer) {
if (buffer instanceof Writer) { if (buffer instanceof Writer) {
this.socket.write(buffer.toBuffer()); this.socket.write(buffer.toBuffer());

View file

@ -17,6 +17,7 @@ import { Chunk } from "./Chunk";
import { PacketMapChunk } from "./packets/MapChunk"; import { PacketMapChunk } from "./packets/MapChunk";
import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook"; import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook";
import { PacketPreChunk } from "./packets/PreChunk"; import { PacketPreChunk } from "./packets/PreChunk";
import { PacketChat } from "./packets/Chat";
export class MinecraftServer { export class MinecraftServer {
private static readonly PROTOCOL_VERSION = 14; private static readonly PROTOCOL_VERSION = 14;
@ -29,25 +30,56 @@ export class MinecraftServer {
private server:Server; private server:Server;
private serverClock:NodeJS.Timer; private serverClock:NodeJS.Timer;
private tickCounter:number = 0; private tickCounter:number = 0;
private clients:FunkyArray<number, MPClient>; private clients:FunkyArray<string, MPClient>;
private worlds:FunkyArray<number, World>; private worlds:FunkyArray<number, World>;
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) { public constructor(config:Config) {
this.config = config; this.config = config;
this.clients = new FunkyArray<number, MPClient>(); this.clients = new FunkyArray<string, MPClient>();
// Convert seed if needed
const worldSeed = typeof(this.config.seed) === "string" ? this.hashCode(this.config.seed) : this.config.seed;
this.worlds = new FunkyArray<number, World>(); this.worlds = new FunkyArray<number, World>();
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(() => { this.serverClock = setInterval(() => {
// Every 1 sec // Every 1 sec
if (this.tickCounter % MinecraftServer.TICK_RATE === 0) { if (this.tickCounter % MinecraftServer.TICK_RATE === 0) {
if (this.clients.length !== 0) { if (this.clients.length !== 0) {
const timePacket = new PacketTimeUpdate(this.tickCounter).writeData();
this.clients.forEach(client => { this.clients.forEach(client => {
client.send(this.keepalivePacket); client.send(this.keepalivePacket);
client.send(timePacket);
}); });
} }
} }
@ -70,11 +102,26 @@ export class MinecraftServer {
} }
onConnection(socket:Socket) { 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 => { socket.on("data", chunk => {
const reader = new Reader(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(); const packetId = reader.readUByte();
//console.log(packetId);
switch (packetId) { switch (packetId) {
// Handle timeouts at some point, idk. // Handle timeouts at some point, idk.
case Packets.KeepAlive: case Packets.KeepAlive:
@ -95,20 +142,17 @@ export class MinecraftServer {
if (world instanceof World) { if (world instanceof World) {
const clientEntity = new Player(this, world, loginPacket.username); const clientEntity = new Player(this, world, loginPacket.username);
world.addEntity(clientEntity); 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 PacketSpawnPosition(8, 64, 8).writeData());
socket.write(new PacketPreChunk(0, 0, true).writeData()); socket.write(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).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);
} else { } else {
socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData()); socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData());
} }

View file

@ -1,7 +1,10 @@
import { Console } from "../console";
import { FunkyArray } from "../funkyArray"; import { FunkyArray } from "../funkyArray";
import { Chunk } from "./Chunk"; import { Chunk } from "./Chunk";
import { IEntity } from "./entities/IEntity"; import { IEntity } from "./entities/IEntity";
import { Player } from "./entities/Player";
import { FlatGenerator } from "./generators/Flat"; import { FlatGenerator } from "./generators/Flat";
import { HillyGenerator } from "./generators/Hilly";
import { IGenerator } from "./generators/IGenerator"; import { IGenerator } from "./generators/IGenerator";
export class World { export class World {
@ -10,30 +13,78 @@ export class World {
public generator:IGenerator; public generator:IGenerator;
public constructor() { public constructor(seed:number) {
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.generator = new FlatGenerator(); this.generator = new HillyGenerator(seed);
this.chunks.set(Chunk.CreateCoordPair(0, 0), new Chunk(this, 0, 0));
} }
public addEntity(entity:IEntity) { public addEntity(entity:IEntity) {
this.entites.set(entity.entityId, entity); this.entites.set(entity.entityId, entity);
} }
public removeEntity(entity:IEntity|number) { // TODO: getChunkByCoordPair failed in here during removeEntity, figure out why.
if (typeof(entity) === "number") { public removeEntity(entity:IEntity) {
return this.entites.remove(entity); 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) { public getChunk(x:number, z:number, generate:boolean = true) {
return this.chunks.get(Chunk.CreateCoordPair(x, z)); 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) { 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<number>();
}
}
entity.onTick();
})
} }
} }

View file

@ -12,4 +12,15 @@ export class Block {
static bedrock = new Block(7); static bedrock = new Block(7);
static waterStill = new Block(9);
static wood = new Block(17);
static leaves = new Block(18);
} }

View file

@ -20,4 +20,10 @@ 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;
} }
onTick() {
this.lastX = this.x;
this.lastY = this.y;
this.lastZ = this.z;
}
} }

View file

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

View file

@ -1,3 +1,10 @@
export interface IEntity { export interface IEntity {
entityId:number entityId:number,
x:number,
y:number,
z:number,
lastX:number,
lastY:number,
lastZ:number,
onTick:() => void
} }

View file

@ -1,18 +1,81 @@
import { FunkyArray } from "../../funkyArray";
import { Chunk } from "../Chunk";
import { MPClient } from "../MPClient";
import { MinecraftServer } from "../MinecraftServer"; import { MinecraftServer } from "../MinecraftServer";
import { World } from "../World"; import { World } from "../World";
import { PacketMapChunk } from "../packets/MapChunk";
import { EntityLiving } from "./EntityLiving";
import { Entity } from "./Entity"; 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; public username:string;
private server:MinecraftServer; private server:MinecraftServer;
private firstUpdate:boolean;
public loadedChunks:Array<number>;
public justUnloaded:Array<number>;
public mpClient?:MPClient;
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;
this.firstUpdate = true;
this.loadedChunks = new Array<number>();
this.justUnloaded = new Array<number>();
this.username = username; this.username = username;
this.x = 8; this.x = 8;
this.y = 64; this.y = 64;
this.z = 8; 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();
}
} }

View file

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

View file

@ -6,8 +6,12 @@ export class PacketChat implements IPacket {
public packetId = Packets.Chat; public packetId = Packets.Chat;
public message:string; public message:string;
public constructor(message:string) { public constructor(message?:string) {
if (typeof(message) === "string") {
this.message = message; this.message = message;
} else {
this.message = "";
}
} }
public readData(reader:Reader) { public readData(reader:Reader) {

View file

@ -61,7 +61,7 @@ export class PacketMapChunk implements IPacket {
return reject(err); 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());
}); });
}); });
} }

View file

@ -6,8 +6,12 @@ export class PacketPlayer implements IPacket {
public packetId = Packets.Player; public packetId = Packets.Player;
public onGround:boolean; public onGround:boolean;
public constructor(onGround:boolean = false) { public constructor(onGround?:boolean) {
if (typeof(onGround) === "boolean") {
this.onGround = onGround; this.onGround = onGround;
} else {
this.onGround = false;
}
} }
public readData(reader:Reader) { public readData(reader:Reader) {

View file

@ -8,10 +8,16 @@ export class PacketPlayerLook implements IPacket {
public pitch:number; public pitch:number;
public onGround:boolean; public onGround:boolean;
public constructor(yaw:number, pitch:number, onGround:boolean = false) { public constructor(yaw?:number, pitch?:number, onGround?:boolean) {
if (typeof(yaw) === "number" && typeof(pitch) === "number" && typeof(onGround) === "boolean") {
this.yaw = yaw; this.yaw = yaw;
this.pitch = pitch; this.pitch = pitch;
this.onGround = onGround; this.onGround = onGround;
} else {
this.yaw = Number.MIN_VALUE;
this.pitch = Number.MIN_VALUE;
this.onGround = false;
}
} }
public readData(reader:Reader) { public readData(reader:Reader) {

View file

@ -10,12 +10,20 @@ export class PacketPlayerPosition implements IPacket {
public z:number; public z:number;
public onGround:boolean; public onGround:boolean;
public constructor(x:number, y:number, stance:number, z:number, onGround:boolean = false) { 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.x = x;
this.y = y; this.y = y;
this.stance = stance; this.stance = stance;
this.z = z; this.z = z;
this.onGround = onGround; 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) { public readData(reader:Reader) {

View file

@ -12,7 +12,8 @@ export class PacketPlayerPositionLook implements IPacket {
public pitch:number; public pitch:number;
public onGround:boolean; public onGround:boolean;
public constructor(x:number, y:number, stance:number, z:number, yaw:number, pitch:number, onGround:boolean = false) { 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.x = x;
this.y = y; this.y = y;
this.stance = stance; this.stance = stance;
@ -20,6 +21,15 @@ export class PacketPlayerPositionLook implements IPacket {
this.yaw = yaw; this.yaw = yaw;
this.pitch = pitch; this.pitch = pitch;
this.onGround = onGround; 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) { public readData(reader:Reader) {

View file

@ -4,14 +4,14 @@ import { IPacket } from "./IPacket";
export class PacketTimeUpdate implements IPacket { export class PacketTimeUpdate implements IPacket {
public packetId = Packets.TimeUpdate; public packetId = Packets.TimeUpdate;
public time:number; public time:bigint;
public constructor(time:number) { public constructor(time:bigint) {
this.time = time; this.time = time;
} }
public readData(reader:Reader) { public readData(reader:Reader) {
this.time = Number(reader.readLong()); this.time = reader.readLong();
return this; return this;
} }