forked from EXTERNAL/bareiron
overhaul worldgen
This commit is contained in:
@@ -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
|
||||
|
@@ -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) {
|
||||
|
@@ -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];
|
||||
|
||||
|
147
src/worldgen.c
147
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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,12 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 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);
|
||||
|
Reference in New Issue
Block a user