From 7a3e2b5fa2f70df5f79aad8d2a259622933aca9c Mon Sep 17 00:00:00 2001 From: p2r3 Date: Sat, 16 Aug 2025 16:15:33 +0300 Subject: [PATCH] overhaul worldgen --- src/packets.c | 17 +++++- src/tools.c | 7 +++ src/tools.h | 1 + src/worldgen.c | 147 +++++++++++++++++++++++++++++++++---------------- src/worldgen.h | 5 ++ 5 files changed, 126 insertions(+), 51 deletions(-) diff --git a/src/packets.c b/src/packets.c index 2deb04f..479ef0e 100644 --- a/src/packets.c +++ b/src/packets.c @@ -284,7 +284,7 @@ int sc_setCenterChunk (int client_fd, int x, int y) { // S->C Chunk Data and Update Light int sc_chunkDataAndUpdateLight (int client_fd, int _x, int _z) { - const int chunk_data_size = (4101 + sizeVarInt(256) + sizeof(network_block_palette)) * 20 + 6 * 4; + const int chunk_data_size = (4101 + sizeVarInt(256) + sizeof(network_block_palette)) * 20 + 6 * 12; writeVarInt(client_fd, 17 + sizeVarInt(chunk_data_size) + chunk_data_size); writeByte(client_fd, 0x27); @@ -310,8 +310,8 @@ int sc_chunkDataAndUpdateLight (int client_fd, int _x, int _z) { wdt_reset(); // send chunk sections - for (int i = 4; i < 24; i ++) { - y = i * 16 - 64; + for (int i = 0; i < 20; i ++) { + y = i * 16; writeUint16(client_fd, 4096); // block count writeByte(client_fd, 8); // bits per entry writeVarInt(client_fd, 256); // block palette length @@ -327,6 +327,17 @@ int sc_chunkDataAndUpdateLight (int client_fd, int _x, int _z) { wdt_reset(); } + // send 8 chunk sections (up to Y=192) with no blocks + for (int i = 0; i < 8; i ++) { + writeUint16(client_fd, 4096); // block count + writeByte(client_fd, 0); // block bits + writeVarInt(client_fd, 0); // block palette (air) + writeByte(client_fd, 0); // biome bits + writeByte(client_fd, 0); // biome palette + } + // reset watchdog and yield + wdt_reset(); + writeVarInt(client_fd, 0); // omit block entities // light data diff --git a/src/tools.c b/src/tools.c index b9456cc..4d36ea0 100644 --- a/src/tools.c +++ b/src/tools.c @@ -116,6 +116,13 @@ uint32_t fast_rand () { return rng_seed; } +uint64_t splitmix64 (uint64_t state) { + uint64_t z = state + 0x9e3779b97f4a7c15; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); +} + int client_states[MAX_PLAYERS * 2]; void setClientState (int client_fd, int new_state) { diff --git a/src/tools.h b/src/tools.h index db1046e..eeb3bfa 100644 --- a/src/tools.h +++ b/src/tools.h @@ -26,6 +26,7 @@ double readDouble (int client_fd); void readString (int client_fd); uint32_t fast_rand (); +uint64_t splitmix64 (uint64_t state); extern int client_states[MAX_PLAYERS * 2]; diff --git a/src/worldgen.c b/src/worldgen.c index aec03a0..c7e9c9c 100644 --- a/src/worldgen.c +++ b/src/worldgen.c @@ -9,29 +9,6 @@ #include "registries.h" #include "worldgen.h" -uint32_t getHash (const void *data, size_t len) { - const uint8_t *bytes = data; - uint32_t hash = 2166136261u; - for (size_t i = 0; i < len; i ++) { - hash ^= bytes[i]; - hash *= 16777619u; - } - return hash; -} - -int sapling (short x, short y, short z) { - - uint8_t buf[10]; - memcpy(buf, &x, 2); - memcpy(buf + 2, &y, 2); - memcpy(buf + 4, &z, 2); - memcpy(buf + 6, &world_seed, 4); - - if (getHash(buf, sizeof(buf)) % 20 == 0) return B_oak_sapling; - return B_air; - -} - uint32_t getChunkHash (short x, short z) { uint8_t buf[8]; @@ -39,14 +16,26 @@ uint32_t getChunkHash (short x, short z) { memcpy(buf + 2, &z, 2); memcpy(buf + 4, &world_seed, 4); - return getHash(buf, sizeof(buf)); + return splitmix64(*((uint64_t *)buf)); } int getCornerHeight (uint32_t hash) { - int height = 60 + hash % 8 + (hash >> 8) % 5; - if (height < 64) height -= (hash >> 16) % 5; + // Use parts of the hash as random values for the height variation. + // We stack multiple different numbers to stabilize the distribution + // while allowing for occasional variances. + int height = terrain_base_height + ( + (hash & 3) + + (hash >> 4 & 3) + + (hash >> 8 & 3) + + (hash >> 12 & 3) + ); + + // If height dips below sea level, push it down further + // This selectively makes bodies of water larger and deeper + if (height < 64) height -= (hash >> 24) & 7; + return height; } @@ -73,54 +62,58 @@ int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash) { } -uint8_t getTerrainAt (int x, int y, int z) { +typedef struct { + short x; + short z; + uint32_t hash; +} ChunkAnchor; + +uint8_t getTerrainAt (int x, int y, int z, ChunkAnchor anchor) { if (y > 80) return B_air; - int _x = x / chunk_size; - int _z = z / chunk_size; int rx = x % chunk_size; int rz = z % chunk_size; + if (rx < 0) rx += chunk_size; + if (rz < 0) rz += chunk_size; - if (rx < 0) { rx += chunk_size; _x -= 1; } - if (rz < 0) { rz += chunk_size; _z -= 1; } - - uint32_t chunk_hash = getChunkHash(_x, _z); - int height = getHeightAt(rx, rz, _x, _z, chunk_hash); + int height = getHeightAt(rx, rz, anchor.x, anchor.z, anchor.hash); if (y >= 64 && y >= height) { - uint8_t tree_position = chunk_hash % (chunk_size * chunk_size); + uint8_t tree_position = anchor.hash % (chunk_size * chunk_size); short tree_x = tree_position % chunk_size; if (tree_x < 3 || tree_x > chunk_size - 3) goto skip_tree; - tree_x += _x * chunk_size; - short tree_z = tree_position / chunk_size; if (tree_z < 3 || tree_z > chunk_size - 3) goto skip_tree; - tree_z += _z * chunk_size; + + uint8_t tree_short = (anchor.hash >> (tree_x + tree_z)) & 1; + + tree_x += anchor.x * chunk_size; + tree_z += anchor.z * chunk_size; uint8_t tree_y = getHeightAt( tree_x < 0 ? tree_x % chunk_size + chunk_size : tree_x % chunk_size, tree_z < 0 ? tree_z % chunk_size + chunk_size : tree_z % chunk_size, - _x, _z, chunk_hash + anchor.x, anchor.z, anchor.hash ) + 1; if (tree_y < 64) goto skip_tree; if (x == tree_x && z == tree_z) { if (y == tree_y - 1) return B_dirt; - if (y >= tree_y && y < tree_y + 6) return B_oak_log; + if (y >= tree_y && y < tree_y - tree_short + 6) return B_oak_log; } uint8_t dx = x > tree_x ? x - tree_x : tree_x - x; uint8_t dz = z > tree_z ? z - tree_z : tree_z - z; - if (dx < 3 && dz < 3 && y > tree_y + 2 && y < tree_y + 5) { - if (y == tree_y + 4 && dx == 2 && dz == 2 && (chunk_hash >> (x + z + y)) & 1) return B_air; + if (dx < 3 && dz < 3 && y > tree_y - tree_short + 2 && y < tree_y - tree_short + 5) { + if (y == tree_y - tree_short + 4 && dx == 2 && dz == 2) return B_air; return B_oak_leaves; } - if (dx < 2 && dz < 2 && y >= tree_y + 5 && y <= tree_y + 6) { - if (y == tree_y + 6 && dx == 1 && dz == 1 && (chunk_hash >> (x + z + y)) & 1) return B_air; + if (dx < 2 && dz < 2 && y >= tree_y - tree_short + 5 && y <= tree_y - tree_short + 6) { + if (y == tree_y - tree_short + 6 && dx == 1 && dz == 1) return B_air; return B_oak_leaves; } @@ -130,11 +123,44 @@ uint8_t getTerrainAt (int x, int y, int z) { } skip_tree: - if (y >= 63 && y == height) return B_grass_block; - if (y < height - 3) return B_stone; + // For surface-level terrain, generate grass blocks + if (y == height && height >= 63) return B_grass_block; + // Starting at 4 blocks below terrain level, generate minerals and caves + if (y <= height - 4) { + // Caves use the same shape as surface terrain, just mirrored + int8_t gap = height - terrain_base_height; + if (y < cave_base_depth + gap && y > cave_base_depth - gap) return B_air; + + // The chunk-relative X and Z coordinates are used in a bit shift on the hash + // The sum of these is then used to get the Y coordinate of the ore in this column + // This way, each column is guaranteed to have exactly one ore candidate + uint8_t ore_x_component = (anchor.hash >> rx) & 31; + uint8_t ore_z_component = (anchor.hash >> (rz + 16)) & 31; + uint8_t ore_y = ore_x_component + ore_z_component; + + if (y == ore_y) { + // Since the ore Y coordinate is effectely a random number in range [0;64], + // we use it in another bit shift to get a pseudo-random number for the column + uint8_t ore_probability = (anchor.hash >> ore_y) & 127; + // Ore placement is determined by Y level and "probability" + if (y < 15 && ore_probability < 15) return B_diamond_ore; + if (y < 30) { + if (ore_probability < 5) return B_gold_ore; + if (ore_probability < 20) return B_redstone_ore; + } + if (y < 54 && ore_probability < 50) return B_iron_ore; + if (ore_probability < 60) return B_coal_ore; + } + + // For everything else, fall back to stone + return B_stone; + } + // Under water and in the space between stone and grass, generate dirt if (y <= height) return B_dirt; + // If all else failed, but we're below sea level, generate water if (y < 64) return B_water; + // For everything else, fall back to air return B_air; } @@ -144,14 +170,38 @@ uint8_t getBlockAt (int x, int y, int z) { uint8_t block_change = getBlockChange(x, y, z); if (block_change != 0xFF) return block_change; - return getTerrainAt(x, y, z); + ChunkAnchor anchor = { + x / chunk_size, + z / chunk_size + }; + if (x % chunk_size < 0) anchor.x --; + if (z % chunk_size < 0) anchor.z --; + anchor.hash = getChunkHash(anchor.x, anchor.z); + + return getTerrainAt(x, y, z, anchor); } uint8_t chunk_section[4096]; +ChunkAnchor chunk_anchors[256 / (chunk_size * chunk_size)]; void buildChunkSection (int cx, int cy, int cz) { + // Precompute the hashes and anchors for each minichunk + int anchor_index = 0; + for (int i = cz; i < cz + 16; i += chunk_size) { + for (int j = cx; j < cx + 16; j += chunk_size) { + + ChunkAnchor *anchor = chunk_anchors + anchor_index; + + anchor->x = j / chunk_size; + anchor->z = i / chunk_size; + anchor->hash = getChunkHash(anchor->x, anchor->z); + + anchor_index ++; + } + } + // Generate 4096 blocks in one buffer to reduce overhead for (int j = 0; j < 4096; j += 8) { // These values don't change in the lower array, @@ -161,10 +211,11 @@ void buildChunkSection (int cx, int cy, int cz) { // The client expects "big-endian longs", which in our // case means reversing the order in which we store/send // each 8 block sequence. + anchor_index = (j % 16) / chunk_size + (j / 16 % 16) / chunk_size * 2; for (int offset = 7; offset >= 0; offset--) { int k = j + offset; int x = k % 16 + cx; - chunk_section[j + 7 - offset] = getTerrainAt(x, y, z); + chunk_section[j + 7 - offset] = getTerrainAt(x, y, z, chunk_anchors[anchor_index]); } } diff --git a/src/worldgen.h b/src/worldgen.h index 648c678..23ec2e5 100644 --- a/src/worldgen.h +++ b/src/worldgen.h @@ -3,7 +3,12 @@ #include +// For best performance, chunk_size should be a power of 2 #define chunk_size 8 +// Terrain low point - should start a bit below sea level for rivers/lakes +#define terrain_base_height 60 +// Center point of cave generation +#define cave_base_depth 24 uint32_t getChunkHash (short x, short z); int getHeightAt (int rx, int rz, int _x, int _z, uint32_t chunk_hash);