From e21bda620146b1dca09e982f8f411d3d457759c8 Mon Sep 17 00:00:00 2001 From: Holly Date: Mon, 17 Apr 2023 02:05:11 +0100 Subject: [PATCH] Add basic cave generation --- external/OpenSimplex3D.ts | 596 +++++++++++++++++++++++++++++++++++++ server/Chunk.ts | 10 + server/MPClient.ts | 6 + server/WorldSaveManager.ts | 4 +- server/blocks/Block.ts | 5 + server/entities/Item.ts | 13 + server/generators/Hilly.ts | 83 ++++-- 7 files changed, 698 insertions(+), 19 deletions(-) create mode 100644 external/OpenSimplex3D.ts create mode 100644 server/entities/Item.ts diff --git a/external/OpenSimplex3D.ts b/external/OpenSimplex3D.ts new file mode 100644 index 0000000..bee6f5c --- /dev/null +++ b/external/OpenSimplex3D.ts @@ -0,0 +1,596 @@ +// This is free and unencumbered software released into the public domain + +import shuffleSeed from "./shuffle_seed"; + +const NORM_3D = 1.0 / 103.0; +const SQUISH_3D = (Math.sqrt(3 + 1) - 1) / 3; +const STRETCH_3D = (1 / Math.sqrt(3 + 1) - 1) / 3; + +export type Noise3D = (x: number, y: number, z: number) => number; + +interface Contribution3D { + dx: number; + dy: number; + dz: number; + next?: Contribution3D; + xsb: number; + ysb: number; + zsb: number; +} + +function contribution3D( + multiplier: number, + xsb: number, + ysb: number, + zsb: number, +): Contribution3D { + return { + dx: -xsb - multiplier * SQUISH_3D, + dy: -ysb - multiplier * SQUISH_3D, + dz: -zsb - multiplier * SQUISH_3D, + xsb, + ysb, + zsb, + }; +} + +export function makeNoise3D(clientSeed: number): Noise3D { + const contributions: Contribution3D[] = []; + for (let i = 0; i < p3D.length; i += 9) { + const baseSet = base3D[p3D[i]]; + let previous: Contribution3D | null = null; + let current: Contribution3D | null = null; + for (let k = 0; k < baseSet.length; k += 4) { + current = contribution3D( + baseSet[k], + baseSet[k + 1], + baseSet[k + 2], + baseSet[k + 3], + ); + if (previous === null) contributions[i / 9] = current; + else previous.next = current; + previous = current; + } + current!.next = contribution3D( + p3D[i + 1], + p3D[i + 2], + p3D[i + 3], + p3D[i + 4], + ); + current!.next.next = contribution3D( + p3D[i + 5], + p3D[i + 6], + p3D[i + 7], + p3D[i + 8], + ); + } + const lookup: Contribution3D[] = []; + for (let i = 0; i < lookupPairs3D.length; i += 2) { + lookup[lookupPairs3D[i]] = contributions[lookupPairs3D[i + 1]]; + } + + const perm = new Uint8Array(256); + const perm3D = 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]]; + perm3D[i] = (perm[i] % 24) * 3; + source[r[0]] = source[i]; + } + + return (x: number, y: number, z: number): number => { + const stretchOffset = (x + y + z) * STRETCH_3D; + + const xs = x + stretchOffset; + const ys = y + stretchOffset; + const zs = z + stretchOffset; + + const xsb = Math.floor(xs); + const ysb = Math.floor(ys); + const zsb = Math.floor(zs); + + const squishOffset = (xsb + ysb + zsb) * SQUISH_3D; + + const dx0 = x - (xsb + squishOffset); + const dy0 = y - (ysb + squishOffset); + const dz0 = z - (zsb + squishOffset); + + const xins = xs - xsb; + const yins = ys - ysb; + const zins = zs - zsb; + + const inSum = xins + yins + zins; + const hash = (yins - zins + 1) | + ((xins - yins + 1) << 1) | + ((xins - zins + 1) << 2) | + (inSum << 3) | + ((inSum + zins) << 5) | + ((inSum + yins) << 7) | + ((inSum + xins) << 9); + + let value = 0; + + for ( + let c: Contribution3D | undefined = lookup[hash]; + c !== undefined; + c = c.next + ) { + const dx = dx0 + c.dx; + const dy = dy0 + c.dy; + const dz = dz0 + c.dz; + + const attn = 2 - dx * dx - dy * dy - dz * dz; + if (attn > 0) { + const px = xsb + c.xsb; + const py = ysb + c.ysb; + const pz = zsb + c.zsb; + + const indexPartA = perm[px & 0xff]; + const indexPartB = perm[(indexPartA + py) & 0xff]; + const index = perm3D[(indexPartB + pz) & 0xff]; + + const valuePart = gradients3D[index] * dx + + gradients3D[index + 1] * dy + + gradients3D[index + 2] * dz; + + value += attn * attn * attn * attn * valuePart; + } + } + return value * NORM_3D; + }; +} + +const base3D = [ + [0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1], + [2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1, 3, 1, 1, 1], + [1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1], +]; + +const gradients3D = [ + -11, + 4, + 4, + -4, + 11, + 4, + -4, + 4, + 11, + 11, + 4, + 4, + 4, + 11, + 4, + 4, + 4, + 11, + -11, + -4, + 4, + -4, + -11, + 4, + -4, + -4, + 11, + 11, + -4, + 4, + 4, + -11, + 4, + 4, + -4, + 11, + -11, + 4, + -4, + -4, + 11, + -4, + -4, + 4, + -11, + 11, + 4, + -4, + 4, + 11, + -4, + 4, + 4, + -11, + -11, + -4, + -4, + -4, + -11, + -4, + -4, + -4, + -11, + 11, + -4, + -4, + 4, + -11, + -4, + 4, + -4, + -11, +]; + +const lookupPairs3D = [ + 0, + 2, + 1, + 1, + 2, + 2, + 5, + 1, + 6, + 0, + 7, + 0, + 32, + 2, + 34, + 2, + 129, + 1, + 133, + 1, + 160, + 5, + 161, + 5, + 518, + 0, + 519, + 0, + 546, + 4, + 550, + 4, + 645, + 3, + 647, + 3, + 672, + 5, + 673, + 5, + 674, + 4, + 677, + 3, + 678, + 4, + 679, + 3, + 680, + 13, + 681, + 13, + 682, + 12, + 685, + 14, + 686, + 12, + 687, + 14, + 712, + 20, + 714, + 18, + 809, + 21, + 813, + 23, + 840, + 20, + 841, + 21, + 1198, + 19, + 1199, + 22, + 1226, + 18, + 1230, + 19, + 1325, + 23, + 1327, + 22, + 1352, + 15, + 1353, + 17, + 1354, + 15, + 1357, + 17, + 1358, + 16, + 1359, + 16, + 1360, + 11, + 1361, + 10, + 1362, + 11, + 1365, + 10, + 1366, + 9, + 1367, + 9, + 1392, + 11, + 1394, + 11, + 1489, + 10, + 1493, + 10, + 1520, + 8, + 1521, + 8, + 1878, + 9, + 1879, + 9, + 1906, + 7, + 1910, + 7, + 2005, + 6, + 2007, + 6, + 2032, + 8, + 2033, + 8, + 2034, + 7, + 2037, + 6, + 2038, + 7, + 2039, + 6, +]; + +const p3D = [ + 0, + 0, + 1, + -1, + 0, + 0, + 1, + 0, + -1, + 0, + 0, + -1, + 1, + 0, + 0, + 0, + 1, + -1, + 0, + 0, + -1, + 0, + 1, + 0, + 0, + -1, + 1, + 0, + 2, + 1, + 1, + 0, + 1, + 1, + 1, + -1, + 0, + 2, + 1, + 0, + 1, + 1, + 1, + -1, + 1, + 0, + 2, + 0, + 1, + 1, + 1, + -1, + 1, + 1, + 1, + 3, + 2, + 1, + 0, + 3, + 1, + 2, + 0, + 1, + 3, + 2, + 0, + 1, + 3, + 1, + 0, + 2, + 1, + 3, + 0, + 2, + 1, + 3, + 0, + 1, + 2, + 1, + 1, + 1, + 0, + 0, + 2, + 2, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 2, + 0, + 2, + 0, + 1, + 1, + 0, + 0, + 1, + 2, + 0, + 0, + 2, + 2, + 0, + 0, + 0, + 0, + 1, + 1, + -1, + 1, + 2, + 0, + 0, + 0, + 0, + 1, + -1, + 1, + 1, + 2, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + -1, + 2, + 3, + 1, + 1, + 1, + 2, + 0, + 0, + 2, + 2, + 3, + 1, + 1, + 1, + 2, + 2, + 0, + 0, + 2, + 3, + 1, + 1, + 1, + 2, + 0, + 2, + 0, + 2, + 1, + 1, + -1, + 1, + 2, + 0, + 0, + 2, + 2, + 1, + 1, + -1, + 1, + 2, + 2, + 0, + 0, + 2, + 1, + -1, + 1, + 1, + 2, + 0, + 0, + 2, + 2, + 1, + -1, + 1, + 1, + 2, + 0, + 2, + 0, + 2, + 1, + 1, + 1, + -1, + 2, + 2, + 0, + 0, + 2, + 1, + 1, + 1, + -1, + 2, + 0, + 2, + 0, +]; diff --git a/server/Chunk.ts b/server/Chunk.ts index b5bc3fd..66437e8 100644 --- a/server/Chunk.ts +++ b/server/Chunk.ts @@ -40,6 +40,16 @@ export class Chunk { } } + public getTopBlockY(x:number, z:number) { + let castY = this.MAX_HEIGHT; + while (castY-- > 0) { + if (this.getBlockId(x, castY, z) !== 0) { + break; + } + } + return castY; + } + public calculateLighting() { } diff --git a/server/MPClient.ts b/server/MPClient.ts index b4e91b5..c4a7be0 100644 --- a/server/MPClient.ts +++ b/server/MPClient.ts @@ -84,6 +84,12 @@ export class MPClient { const consoleMessage = `[CONSOLE] ${message.slice(1, message.length).join(" ")}`; Console.printChat(consoleMessage); this.mcServer.sendToAllClients(new PacketChat(consoleMessage).writeData()); + } else if (message[0] === "/top") { + // TODO: Figure out why this is broken + packet.message = `Woosh!`; + const topBlock = this.entity.world.getChunk(this.entity.x >> 4, this.entity.z >> 4).getTopBlockY(this.entity.x & 0xf, this.entity.z & 0xf); + console.log(topBlock); + this.send(new PacketPlayerPositionLook(this.entity.x, topBlock, topBlock + 0.62, this.entity.z, this.entity.yaw, this.entity.pitch, false).writeData()); } if (packet.message !== "") { diff --git a/server/WorldSaveManager.ts b/server/WorldSaveManager.ts index 38a5ca4..37ed1dd 100644 --- a/server/WorldSaveManager.ts +++ b/server/WorldSaveManager.ts @@ -86,9 +86,9 @@ export class WorldSaveManager { } public writeChunkToDisk(chunk:Chunk) { - /*return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { resolve(true); - });*/ + }); return new Promise((resolve, reject) => { const saveType = this.config.saveCompression; const chunkFileWriter = new Writer(10); diff --git a/server/blocks/Block.ts b/server/blocks/Block.ts index d28bb96..b3549af 100644 --- a/server/blocks/Block.ts +++ b/server/blocks/Block.ts @@ -26,6 +26,8 @@ export class Block { static readonly waterStill = new Block(9); + static readonly lavaStill = new Block(11); + static readonly sand = new Block(12); static readonly gravel = new Block(13); @@ -34,5 +36,8 @@ export class Block { static readonly tallGrass = new Block(31); + static readonly flowerDandelion = new Block(37); + static readonly flowerRose = new Block(38); + static readonly clay = new Block(82); } \ No newline at end of file diff --git a/server/entities/Item.ts b/server/entities/Item.ts new file mode 100644 index 0000000..6b3567b --- /dev/null +++ b/server/entities/Item.ts @@ -0,0 +1,13 @@ +import { World } from "../World"; +import { ItemStack } from "../containers/ItemStack"; +import { Entity } from "./Entity"; + +export class EntityItem extends Entity { + public age:number; + + public constructor(world:World, x:number, y:number, z:number, itemStack:ItemStack) { + super(world); + this.age = 0; + this.health = 5; + } +} \ No newline at end of file diff --git a/server/generators/Hilly.ts b/server/generators/Hilly.ts index b904dd8..d08e233 100644 --- a/server/generators/Hilly.ts +++ b/server/generators/Hilly.ts @@ -2,6 +2,7 @@ import { Block } from "../blocks/Block"; import { Chunk } from "../Chunk"; import { IGenerator } from "./IGenerator"; import { Noise2D, makeNoise2D } from "../../external/OpenSimplex2D"; +import { Noise3D, makeNoise3D } from "../../external/OpenSimplex3D"; import { QueuedBlockUpdate } from "../queuedUpdateTypes/BlockUpdate"; export class HillyGenerator implements IGenerator { @@ -16,33 +17,53 @@ export class HillyGenerator implements IGenerator { private generator5:Noise2D; private generator6:Noise2D; private oceanGenerator:Noise2D; - private mountainGenerator:Noise2D; + private hillGenerator:Noise2D; + + private caveGenerator1:Noise3D; + private caveGenerator2:Noise3D; + private caveGenerator3:Noise3D; + private caveGenerator4:Noise3D; + private underwaterGravelGenerator:Noise2D; private underwaterSandGenerator:Noise2D; private underwaterClayGenerator:Noise2D; + private flowerGenerator:Noise2D; public constructor(seed:number) { this.seed = seed; this.seedGenerator = this.mulberry32(this.seed); - this.generator = this.createGenerator(); - this.generator1 = this.createGenerator(); - this.generator2 = this.createGenerator(); - this.generator3 = this.createGenerator(); - this.generator4 = this.createGenerator(); - this.generator5 = this.createGenerator(); - this.generator6 = this.createGenerator(); - this.oceanGenerator = this.createGenerator(); - this.mountainGenerator = this.createGenerator(); - this.underwaterGravelGenerator = this.createGenerator(); - this.underwaterSandGenerator = this.createGenerator(); - this.underwaterClayGenerator = this.createGenerator(); + this.generator = this.createGenerator2D(); + this.generator1 = this.createGenerator2D(); + this.generator2 = this.createGenerator2D(); + this.generator3 = this.createGenerator2D(); + this.generator4 = this.createGenerator2D(); + this.generator5 = this.createGenerator2D(); + this.generator6 = this.createGenerator2D(); + this.oceanGenerator = this.createGenerator2D(); + this.hillGenerator = this.createGenerator2D(); + + this.caveGenerator1 = this.createGenerator3D(); + this.caveGenerator2 = this.createGenerator3D(); + this.caveGenerator3 = this.createGenerator3D(); + this.caveGenerator4 = this.createGenerator3D(); + + this.underwaterGravelGenerator = this.createGenerator2D(); + this.underwaterSandGenerator = this.createGenerator2D(); + this.underwaterClayGenerator = this.createGenerator2D(); + this.flowerGenerator = this.createGenerator2D(); + + } - private createGenerator() { + private createGenerator2D() { return makeNoise2D(this.seedGenerator() * Number.MAX_SAFE_INTEGER); } + private createGenerator3D() { + return makeNoise3D(this.seedGenerator() * Number.MAX_SAFE_INTEGER); + } + // This is soooo much faster than using Math.round in here private fastRound(num:number) { return num >= 0.5 ? (num | 0) + 1 : num | 0; @@ -62,6 +83,7 @@ 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); + const flowerRNG = this.mulberry32(this.seed + chunk.x + chunk.z); let colY = 0, colDirtMin = 0, colWaterY = 0, orgColY = 0; for (let x = 0; x < 16; x++) { @@ -76,7 +98,7 @@ export class HillyGenerator implements IGenerator { 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)) + (Math.max(this.hillGenerator((chunk.x * 16 + x) / 128, (chunk.z * 16 + z) / 128), 0) * 50 + Math.min(oceanValue, 0)) ) / 9); colDirtMin = colY - 2; const sandNoise = this.underwaterSandGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16); @@ -86,6 +108,7 @@ export class HillyGenerator implements IGenerator { chunk.setBlock(Block.grass.blockId, x, colY, z); } + let caveY = colY + 1; while (colY-- > 0) { if (colY >= colDirtMin) { chunk.setBlock(Block.dirt.blockId, x, colY, z); @@ -113,6 +136,22 @@ export class HillyGenerator implements IGenerator { chunk.setBlock(Block.waterStill.blockId, x, colWaterY, z); } + + while (caveY-- > 1) { + if ( + this.caveGenerator1((chunk.x * 16 + x) / 16, caveY / 16, (chunk.z * 16 + z) / 16) > 0.45 || + this.caveGenerator2((chunk.x * 16 + x) / 8, caveY / 8, (chunk.z * 16 + z) / 8) > 0.6 || + this.caveGenerator3((chunk.x * 16 + x) / 256, caveY / 256, (chunk.z * 16 + z) / 256) > 0.6 || + this.caveGenerator4((chunk.x * 16 + x) / 128, caveY / 128, (chunk.z * 16 + z) / 128) > 0.6 + ) { + if (caveY <= 3) { + chunk.setBlock(Block.lavaStill.blockId, x, caveY, z); + } else { + chunk.setBlock(0, x, caveY, z); + } + } + } + const queuedChunkBlocks = chunk.world.queuedChunkBlocks; if (queuedChunkBlocks.length > 0) { const thisCoordPair = Chunk.CreateCoordPair(chunk.x, chunk.z); @@ -125,9 +164,19 @@ export class HillyGenerator implements IGenerator { } } - if (grassRNG() > 0.9 && chunk.getBlockId(x, orgColY, z) === Block.grass.blockId) { - chunk.setBlockWithMetadata(Block.tallGrass.blockId, 1, x, orgColY + 1, z); + // Grass and flowers + if (chunk.getBlockId(x, orgColY, z) === Block.grass.blockId) { + if (grassRNG() > 0.9) { + chunk.setBlockWithMetadata(Block.tallGrass.blockId, 1, x, orgColY + 1, z); + } else if (this.flowerGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) > 0.5 && flowerRNG() > 0.9) { + if (flowerRNG() > 0.4) { + chunk.setBlockWithMetadata(Block.flowerRose.blockId, 1, x, orgColY + 1, z); + } else { + chunk.setBlockWithMetadata(Block.flowerDandelion.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) {