Compare commits

...

9 Commits

Author SHA1 Message Date
chelsea
afbf333622 Add subtle island terrain relief 2026-05-03 11:58:59 -05:00
chelsea
acd5eaa52f Add dry sand patches near beaches 2026-05-03 11:53:07 -05:00
chelsea
84a0435312 Place wall stairs above walkway surface 2026-05-03 11:39:35 -05:00
chelsea
46f1d24e9d Add stair ramps to border wall walks 2026-05-03 11:32:22 -05:00
chelsea
aed76c3b1b Add dead sequoias and align tower walks 2026-05-03 01:22:38 -05:00
chelsea
95bd106a3b Fix generated map integrity and wall access 2026-05-02 23:48:35 -05:00
chelsea
127e706bcb Fix wall rails and water biomes 2026-05-02 23:23:29 -05:00
chelsea
335b35d416 Add plant and aquatic variety 2026-05-02 23:18:00 -05:00
chelsea
d0f3c9a048 Address border wall issue reports 2026-05-02 21:54:12 -05:00
4 changed files with 327 additions and 68 deletions

Binary file not shown.

View File

@@ -61,7 +61,17 @@
BLOCK_STONE_BRICK_STAIRS_E = 48,
BLOCK_STONE_BRICK_STAIRS_W = 49,
BLOCK_STONE_BRICK_STAIRS_N = 50,
BLOCK_STONE_BRICK_STAIRS_S = 51
BLOCK_STONE_BRICK_STAIRS_S = 51,
BLOCK_DANDELION = 52,
BLOCK_AZURE_BLUET = 53,
BLOCK_OXEYE_DAISY = 54,
BLOCK_CORNFLOWER = 55,
BLOCK_LILY_OF_THE_VALLEY = 56,
BLOCK_FERN = 57,
BLOCK_CLAY = 58,
BLOCK_SEAGRASS = 59,
BLOCK_IRON_BARS_NS = 60,
BLOCK_IRON_BARS_EW = 61
};
struct trail_segment;

View File

@@ -100,6 +100,8 @@ static const kv_pair PROPS_LADDER_N[] = {{"facing", "north"}, {"waterlogged", "f
static const kv_pair PROPS_LADDER_S[] = {{"facing", "south"}, {"waterlogged", "false"}};
static const kv_pair PROPS_LADDER_W[] = {{"facing", "west"}, {"waterlogged", "false"}};
static const kv_pair PROPS_LADDER_E[] = {{"facing", "east"}, {"waterlogged", "false"}};
static const kv_pair PROPS_IRON_BARS_NS[] = {{"east", "false"}, {"north", "true"}, {"south", "true"}, {"waterlogged", "false"}, {"west", "false"}};
static const kv_pair PROPS_IRON_BARS_EW[] = {{"east", "true"}, {"north", "false"}, {"south", "false"}, {"waterlogged", "false"}, {"west", "true"}};
static const size_t BLOCK_STATE_CAP = 256;
static const int32_t DATA_VERSION = 2730; /* Java Edition 1.17.1; includes copper ore. */
static const block_state BLOCK_STATE_TABLE[] = {
@@ -154,7 +156,17 @@ static const block_state BLOCK_STATE_TABLE[] = {
[BLOCK_STONE_BRICK_STAIRS_E] = {"minecraft:stone_brick_stairs", PROPS_STAIRS_E, 4},
[BLOCK_STONE_BRICK_STAIRS_W] = {"minecraft:stone_brick_stairs", PROPS_STAIRS_W, 4},
[BLOCK_STONE_BRICK_STAIRS_N] = {"minecraft:stone_brick_stairs", PROPS_STAIRS_N, 4},
[BLOCK_STONE_BRICK_STAIRS_S] = {"minecraft:stone_brick_stairs", PROPS_STAIRS_S, 4}
[BLOCK_STONE_BRICK_STAIRS_S] = {"minecraft:stone_brick_stairs", PROPS_STAIRS_S, 4},
[BLOCK_DANDELION] = {"minecraft:dandelion", NULL, 0},
[BLOCK_AZURE_BLUET] = {"minecraft:azure_bluet", NULL, 0},
[BLOCK_OXEYE_DAISY] = {"minecraft:oxeye_daisy", NULL, 0},
[BLOCK_CORNFLOWER] = {"minecraft:cornflower", NULL, 0},
[BLOCK_LILY_OF_THE_VALLEY] = {"minecraft:lily_of_the_valley", NULL, 0},
[BLOCK_FERN] = {"minecraft:fern", NULL, 0},
[BLOCK_CLAY] = {"minecraft:clay", NULL, 0},
[BLOCK_SEAGRASS] = {"minecraft:seagrass", NULL, 0},
[BLOCK_IRON_BARS_NS] = {"minecraft:iron_bars", PROPS_IRON_BARS_NS, 5},
[BLOCK_IRON_BARS_EW] = {"minecraft:iron_bars", PROPS_IRON_BARS_EW, 5}
};
static const block_state *get_block_state(uint16_t id) {
@@ -308,18 +320,18 @@ static int64_t to_signed64(uint64_t v) {
static void pack_bits(const uint16_t *indices, size_t count, int bits_per_value, int64_t *out_longs, size_t out_count) {
memset(out_longs, 0, out_count * sizeof(int64_t));
int values_per_long = 64 / bits_per_value;
if (values_per_long < 1) values_per_long = 1;
for (size_t idx = 0; idx < count; ++idx) {
uint64_t value = indices[idx];
size_t bit_index = idx * (size_t)bits_per_value;
size_t long_id = bit_index / 64;
size_t offset = bit_index % 64;
size_t long_id = idx / (size_t)values_per_long;
size_t offset = (idx % (size_t)values_per_long) * (size_t)bits_per_value;
if (long_id >= out_count) continue;
uint64_t *target = (uint64_t *)&out_longs[long_id];
*target |= value << offset;
int spill = (int)(offset + bits_per_value - 64);
if (spill > 0 && long_id + 1 < out_count) {
uint64_t *next = (uint64_t *)&out_longs[long_id + 1];
*next |= value >> (bits_per_value - spill);
}
for (size_t i = 0; i < out_count; ++i) {
out_longs[i] = to_signed64((uint64_t)out_longs[i]);
}
}
@@ -345,6 +357,13 @@ static void pack_heightmap(const chunk_data *chunk, int64_t *out_longs, size_t o
}
}
static int column_contains_water(const chunk_data *chunk, int x, int z) {
for (int y = 1; y < CHUNK_HEIGHT; ++y) {
if (chunk->blocks[y][x][z] == BLOCK_WATER) return 1;
}
return 0;
}
static int section_has_blocks(const chunk_data *chunk, int section_y) {
int y_base = section_y * 16;
for (int y = 0; y < 16; ++y) {
@@ -406,7 +425,9 @@ static void write_section(buf *b, const chunk_data *chunk, int section_y) {
int needed = (int)ceil(log2((double)palette_len));
if (needed > bits) bits = needed;
}
size_t packed_count = ((size_t)idx * (size_t)bits + 63) / 64;
size_t values_per_long = (size_t)(64 / bits);
if (values_per_long < 1) values_per_long = 1;
size_t packed_count = ((size_t)idx + values_per_long - 1) / values_per_long;
int64_t *packed = (int64_t *)calloc(packed_count, sizeof(int64_t));
if (!packed) return;
pack_bits(block_indices, idx, bits, packed, packed_count);
@@ -436,7 +457,11 @@ static void write_section(buf *b, const chunk_data *chunk, int section_y) {
static void build_chunk_nbt(const chunk_data *chunk, buf *out) {
int32_t biomes[256];
for (int i = 0; i < 256; ++i) biomes[i] = 1; // Plains biome
for (int z = 0; z < CHUNK_SIZE; ++z) {
for (int x = 0; x < CHUNK_SIZE; ++x) {
biomes[z * CHUNK_SIZE + x] = column_contains_water(chunk, x, z) ? 0 : 1; // Ocean or plains
}
}
int64_t heightmap[37];
pack_heightmap(chunk, heightmap, 37);

View File

@@ -131,12 +131,14 @@ static inline double smoothstep01(double v) {
}
static int ground_slope(worldgen_ctx *ctx, int x, int z);
static int is_dry_beach_sand_patch(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z);
static uint16_t select_surface_block(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z);
static void generate_chunk_trails(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_redwood_floor(worldgen_ctx *ctx, int chunk_x, int chunk_z,
column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *chunk);
static void generate_chunk_grass(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_flowers(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_aquatic_life(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out);
static void ensure_trail_prepass(worldgen_ctx *ctx, int chunk_x, int chunk_z);
@@ -181,6 +183,13 @@ static uint32_t hash_coords3(int x, int y, int z, uint32_t seed) {
return h ^ (h >> 16);
}
static int floor_div_int(int value, int divisor) {
int q = value / divisor;
int r = value % divisor;
if (r != 0 && ((r < 0) != (divisor < 0))) q--;
return q;
}
static double land_value(worldgen_ctx *ctx, int x, int z) {
column_data col = get_column_data(ctx, x, z);
double value = 0.0;
@@ -684,6 +693,19 @@ static int raw_column_height(worldgen_ctx *ctx, int x, int z) {
height = height * (1.0 - plains_blend) + plains_target * plains_blend;
}
double above_water = height - (double)ctx->sea_level;
if (above_water > -1.5 && above_water < 24.0) {
double island_mask = smoothstep01((above_water + 1.5) / 6.0) * (1.0 - smoothstep01((above_water - 12.0) / 12.0));
island_mask *= 1.0 - build_flats * 0.7;
double rolling = simplex_noise2(&ctx->noise, (warp2_x + 76000) * 0.0045, (warp2_z - 76000) * 0.0045) * 3.2;
rolling += simplex_noise2(&ctx->noise, (warp2_x - 83000) * 0.013, (warp2_z + 83000) * 0.013) * 1.4;
double mound = simplex_noise2(&ctx->noise, (warp2_x + 91000) * 0.021, (warp2_z - 91000) * 0.021) * 0.5 + 0.5;
rolling += mound * mound * 2.0;
double coastal_mask = (1.0 - smoothstep01((above_water - 1.0) / 5.0)) * smoothstep01((above_water + 1.0) / 3.0);
double dune = fabs(simplex_noise2(&ctx->noise, (warp2_x - 47000) * 0.028, (warp2_z + 47000) * 0.028)) * 2.2;
height += (rolling * island_mask) + (dune * coastal_mask * (1.0 - build_flats * 0.85));
}
return (int)height;
}
@@ -929,11 +951,51 @@ static int generate_coal(worldgen_ctx *ctx, int x, int y, int z, int column_heig
// ---------------------------------------------------------------------------
// Terrain block generation
// ---------------------------------------------------------------------------
static int is_dry_beach_sand_patch(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z) {
if (data->has_water && data->water_surface >= data->height) return 0;
if (ground_slope(ctx, world_x, world_z) > 2) return 0;
int nearest_water_dist2 = 9999;
int shore_level = ctx->sea_level;
for (int dx = -7; dx <= 7; ++dx) {
for (int dz = -7; dz <= 7; ++dz) {
int dist2 = dx * dx + dz * dz;
if (dist2 == 0 || dist2 > 49) continue;
column_data neighbor = get_column_data(ctx, world_x + dx, world_z + dz);
if (!neighbor.has_water || neighbor.water_surface < neighbor.height) continue;
if (data->height < neighbor.water_surface - 1 || data->height > neighbor.water_surface + 4) continue;
if (dist2 < nearest_water_dist2) {
nearest_water_dist2 = dist2;
shore_level = neighbor.water_surface;
}
}
}
if (nearest_water_dist2 == 9999) return 0;
if (data->height > shore_level + 4) return 0;
double broad = simplex_noise2(&ctx->noise, (world_x + 72000) * 0.045, (world_z - 72000) * 0.045) * 0.5 + 0.5;
double ragged = simplex_noise2(&ctx->noise, (world_x - 81000) * 0.16, (world_z + 81000) * 0.16) * 0.5 + 0.5;
double patch = broad * 0.7 + ragged * 0.3;
double near_bonus = clamp01((18.0 - (double)nearest_water_dist2) / 18.0) * 0.18;
double height_penalty = clamp01((double)(data->height - shore_level) / 4.0) * 0.16;
return patch + near_bonus - height_penalty > 0.56;
}
static uint16_t select_surface_block(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z) {
if (data->has_water && data->water_surface >= data->height) {
int slope = ground_slope(ctx, world_x, world_z);
if (slope <= 1) return BLOCK_SAND;
if (slope <= 3) return BLOCK_GRAVEL;
double shelf = simplex_noise2(&ctx->noise, (world_x + 45000) * 0.025, (world_z - 45000) * 0.025) * 0.5 + 0.5;
double basin = simplex_noise2(&ctx->noise, (world_x - 23000) * 0.007, (world_z + 23000) * 0.007) * 0.5 + 0.5;
int depth = data->water_surface - data->height;
if (slope <= 1) {
if (depth >= 3 && basin > 0.74) return BLOCK_CLAY;
if (shelf > 0.76) return BLOCK_GRAVEL;
return BLOCK_SAND;
}
if (slope <= 3) {
if (depth >= 4 && basin > 0.82) return BLOCK_CLAY;
return (shelf > 0.44) ? BLOCK_GRAVEL : BLOCK_SAND;
}
return BLOCK_STONE;
}
int snow_line = ctx->snow_line;
@@ -946,6 +1008,9 @@ static uint16_t select_surface_block(worldgen_ctx *ctx, const column_data *data,
double noise = simplex_noise2(&ctx->noise, world_x * 0.02, world_z * 0.02) * 0.5 + 0.5;
if (noise < t) return BLOCK_SNOW;
}
if (is_dry_beach_sand_patch(ctx, data, world_x, world_z)) {
return BLOCK_SAND;
}
if (data->biome == BIOME_REDWOOD_FOREST) {
int slope = ground_slope(ctx, world_x, world_z);
if (slope >= 5) return BLOCK_STONE;
@@ -1050,6 +1115,7 @@ static void place_leaf_circle(int cx, int cy, int cz, int radius, int leaf_block
int r2 = radius * radius;
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (dx == 0 && dz == 0) continue;
if (dx * dx + dz * dz > r2) continue;
if (hole_prob > 0.0 && rng_next_f64(rng) < hole_prob) continue;
block_list_push(out, cx + dx, cy, cz + dz, (uint16_t)leaf_block);
@@ -1579,6 +1645,33 @@ static void build_redwood_titan(worldgen_ctx *ctx, int x, int y, int z, int heig
{1, 0}, {-1, 0}, {0, 1}, {0, -1},
{1, 1}, {1, -1}, {-1, 1}, {-1, -1}
};
if (rng_next_f64(rng) < 0.10) {
int snag_height = core_height - rng_range_inclusive(rng, 4, 10);
if (snag_height < 22) snag_height = 22;
int taper_start = snag_height - 5;
for (int dy = 0; dy < snag_height; ++dy) {
int radius = (dy >= taper_start) ? 0 : 1;
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (radius == 1 && abs(dx) == 1 && abs(dz) == 1 && dy > 10 && (dy % 3) == 0) continue;
block_list_push(out, x + dx, y + dy, z + dz, (uint16_t)arch->log_block);
}
}
if (dy > 8 && dy < snag_height - 4 && (dy % 7) == 0) {
int dir = (dy / 7 + rng_range_inclusive(rng, 0, 3)) & 7;
int branch_log = redwood_axis_log(arch->log_block, dirs8[dir][0], dirs8[dir][1]);
int len = 2 + rng_range_inclusive(rng, 0, 2);
for (int step = 2; step <= len + 1; ++step) {
block_list_push(out, x + dirs8[dir][0] * step, y + dy, z + dirs8[dir][1] * step, (uint16_t)branch_log);
}
}
}
for (int i = 0; i < 6; ++i) {
int root_len = 3 + rng_range_inclusive(rng, 0, 2);
place_redwood_root(ctx, x, y, z, dirs8[i][0], dirs8[i][1], root_len, arch->log_block, rng, out);
}
return;
}
int spire = 3 + rng_range_inclusive(rng, 0, 2);
int crown_start = y + core_height / 2 + rng_range_inclusive(rng, 0, 2);
@@ -1863,7 +1956,7 @@ static void generate_chunk_redwood_floor(worldgen_ctx *ctx, int chunk_x, int chu
double litter = simplex_noise2(&ctx->noise, (wx + 8000) * 0.03, (wz - 8000) * 0.03) * 0.5 + 0.5;
double fern_chance = clamp01(0.25 + rain * 0.5 + (1.0 - litter) * 0.25);
if (chunk->blocks[cd.height + 1][dx][dz] == BLOCK_AIR && rng_next_f64(&rng) < fern_chance) {
chunk->blocks[cd.height + 1][dx][dz] = BLOCK_TALL_GRASS;
chunk->blocks[cd.height + 1][dx][dz] = (rain > 0.55 || rng_next_f64(&rng) < 0.45) ? BLOCK_FERN : BLOCK_TALL_GRASS;
}
if (rng_next_f64(&rng) < 0.0035) {
try_place_redwood_fallen_log(ctx, chunk_x, chunk_z, chunk, columns, dx, dz, &rng);
@@ -1894,7 +1987,7 @@ static void generate_chunk_grass(worldgen_ctx *ctx, int chunk_x, int chunk_z, co
uint32_t h = hash_coords(world_x, world_z, (uint32_t)(ctx->world_seed ^ 0x5F3759DFu));
double roll = (double)(h & 0xFFFF) / 65535.0;
if (roll > chance) continue;
out->blocks[cd.height + 1][dx][dz] = BLOCK_TALL_GRASS;
out->blocks[cd.height + 1][dx][dz] = (humidity > 0.72 && rain > 0.45) ? BLOCK_FERN : BLOCK_TALL_GRASS;
if (roll < chance * 0.4) {
const int offsets[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int i = 0; i < 4; ++i) {
@@ -1905,7 +1998,7 @@ static void generate_chunk_grass(worldgen_ctx *ctx, int chunk_x, int chunk_z, co
if (nd.height <= 0 || nd.height >= CHUNK_HEIGHT - 1) continue;
if (out->blocks[nd.height][nx][nz] != BLOCK_GRASS) continue;
if (out->blocks[nd.height + 1][nx][nz] != BLOCK_AIR) continue;
out->blocks[nd.height + 1][nx][nz] = BLOCK_TALL_GRASS;
out->blocks[nd.height + 1][nx][nz] = ((h >> (i + 3)) & 1u) ? BLOCK_TALL_GRASS : BLOCK_FERN;
}
}
}
@@ -1935,7 +2028,17 @@ static void generate_chunk_flowers(worldgen_ctx *ctx, int chunk_x, int chunk_z,
uint32_t h = hash_coords(world_x + 30000, world_z - 30000, (uint32_t)(ctx->world_seed ^ 0xA511E9B5u));
double roll = (double)(h & 0xFFFF) / 65535.0;
if (roll > chance) continue;
out->blocks[cd.height + 1][dx][dz] = BLOCK_WILDFLOWER;
uint16_t flower = BLOCK_WILDFLOWER;
uint32_t flower_hash = hash_coords(world_x - 17000, world_z + 17000, (uint32_t)(ctx->world_seed ^ 0xB10F00Du));
switch ((flower_hash >> 8) % 6) {
case 0: flower = BLOCK_DANDELION; break;
case 1: flower = BLOCK_AZURE_BLUET; break;
case 2: flower = BLOCK_OXEYE_DAISY; break;
case 3: flower = BLOCK_CORNFLOWER; break;
case 4: flower = BLOCK_LILY_OF_THE_VALLEY; break;
default: flower = BLOCK_WILDFLOWER; break;
}
out->blocks[cd.height + 1][dx][dz] = flower;
static const int offsets[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int i = 0; i < 4; ++i) {
int nx = dx + offsets[i][0];
@@ -1947,7 +2050,40 @@ static void generate_chunk_flowers(worldgen_ctx *ctx, int chunk_x, int chunk_z,
if (out->blocks[nd.height + 1][nx][nz] != BLOCK_AIR) continue;
uint32_t nh = hash_coords(world_x + offsets[i][0] + 60000, world_z + offsets[i][1] - 60000, (uint32_t)(ctx->world_seed ^ 0xF00DBAAu));
if ((nh & 0xFFFF) > 24000) continue;
out->blocks[nd.height + 1][nx][nz] = BLOCK_WILDFLOWER;
uint16_t patch_flower = flower;
if ((nh & 7u) == 0u) patch_flower = BLOCK_DANDELION;
out->blocks[nd.height + 1][nx][nz] = patch_flower;
}
}
}
}
static void generate_chunk_aquatic_life(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out) {
for (int dx = 0; dx < CHUNK_SIZE; ++dx) {
for (int dz = 0; dz < CHUNK_SIZE; ++dz) {
column_data cd = columns[dx][dz];
if (!cd.has_water || cd.water_surface <= cd.height) continue;
if (cd.height <= 0 || cd.height >= CHUNK_HEIGHT - 2) continue;
int depth = cd.water_surface - cd.height;
if (depth < 1 || depth > 9) continue;
int world_x = chunk_x * CHUNK_SIZE + dx;
int world_z = chunk_z * CHUNK_SIZE + dz;
uint16_t floor = out->blocks[cd.height][dx][dz];
if (floor != BLOCK_SAND && floor != BLOCK_GRAVEL && floor != BLOCK_CLAY && floor != BLOCK_DIRT) continue;
if (out->blocks[cd.height + 1][dx][dz] != BLOCK_WATER) continue;
double bed = simplex_noise2(&ctx->noise, (world_x + 70000) * 0.035, (world_z - 70000) * 0.035) * 0.5 + 0.5;
double patch = simplex_noise2(&ctx->noise, (world_x - 15000) * 0.011, (world_z + 15000) * 0.011) * 0.5 + 0.5;
double chance = 0.08 + patch * 0.24;
if (floor == BLOCK_CLAY) chance += 0.10;
if (depth <= 3) chance += 0.12;
if (ground_slope(ctx, world_x, world_z) > 2) chance *= 0.35;
uint32_t h = hash_coords(world_x, world_z, (uint32_t)(ctx->world_seed ^ 0x5EA6A55u));
double roll = (double)(h & 0xFFFF) / 65535.0;
if (roll > clamp01(chance)) continue;
out->blocks[cd.height + 1][dx][dz] = BLOCK_SEAGRASS;
if (depth >= 3 && bed > 0.78 && cd.height + 2 < CHUNK_HEIGHT &&
out->blocks[cd.height + 2][dx][dz] == BLOCK_WATER) {
out->blocks[cd.height + 2][dx][dz] = BLOCK_SEAGRASS;
}
}
}
@@ -3554,6 +3690,99 @@ static void generate_chunk_trails(worldgen_ctx *ctx, int chunk_x, int chunk_z, c
}
}
static int border_wall_anchor_base(worldgen_ctx *ctx, int side, int anchor_along, int wall_center) {
int center_x = anchor_along;
int center_z = anchor_along;
switch (side) {
case 0:
center_x = ctx->wall_min_x + wall_center;
break;
case 1:
center_x = ctx->wall_max_x - wall_center;
break;
case 2:
center_z = ctx->wall_min_z + wall_center;
break;
case 3:
center_z = ctx->wall_max_z - wall_center;
break;
}
int smoothed_base = 0;
int weight_sum = 0;
int highest_sample_base = 0;
const int sample_offsets[5] = {-32, -16, 0, 16, 32};
const int sample_weights[5] = {1, 2, 4, 2, 1};
for (int i = 0; i < 5; ++i) {
int sx = center_x;
int sz = center_z;
if (side < 2) {
sz += sample_offsets[i];
} else {
sx += sample_offsets[i];
}
if (sx < ctx->wall_min_x) sx = ctx->wall_min_x;
if (sx > ctx->wall_max_x) sx = ctx->wall_max_x;
if (sz < ctx->wall_min_z) sz = ctx->wall_min_z;
if (sz > ctx->wall_max_z) sz = ctx->wall_max_z;
column_data sample = get_column_data(ctx, sx, sz);
int sample_base = sample.height;
if (sample.has_water && sample.water_surface > sample_base) sample_base = sample.water_surface;
if (i == 0 || sample_base > highest_sample_base) highest_sample_base = sample_base;
smoothed_base += sample_base * sample_weights[i];
weight_sum += sample_weights[i];
}
smoothed_base /= weight_sum;
if (highest_sample_base > smoothed_base + 8) {
smoothed_base += (highest_sample_base - smoothed_base - 8 + 1) / 2;
}
if (smoothed_base < 1) smoothed_base = 1;
if (smoothed_base > CHUNK_HEIGHT - 2) smoothed_base = CHUNK_HEIGHT - 2;
return smoothed_base;
}
static int border_wall_ramp_base(worldgen_ctx *ctx, int side, int along, int wall_center, int tower_spacing) {
int anchor = floor_div_int(along + tower_spacing / 2, tower_spacing) * tower_spacing;
int current_base = border_wall_anchor_base(ctx, side, anchor, wall_center);
int next_anchor = anchor + tower_spacing;
int next_boundary = anchor + tower_spacing / 2;
int next_base = border_wall_anchor_base(ctx, side, next_anchor, wall_center);
int next_delta = next_base - current_base;
if (next_delta != 0) {
int next_span = clamp_int(abs(next_delta), 6, tower_spacing - 8);
int next_start = next_boundary - next_span / 2;
int next_end = next_start + next_span;
if (along >= next_start && along <= next_end) {
int progress = along - next_start;
return current_base + (next_delta * progress + (next_delta > 0 ? next_span / 2 : -next_span / 2)) / next_span;
}
}
int prev_anchor = anchor - tower_spacing;
int prev_boundary = anchor - tower_spacing / 2;
int prev_base = border_wall_anchor_base(ctx, side, prev_anchor, wall_center);
int prev_delta = current_base - prev_base;
if (prev_delta != 0) {
int prev_span = clamp_int(abs(prev_delta), 6, tower_spacing - 8);
int prev_start = prev_boundary - prev_span / 2;
int prev_end = prev_start + prev_span;
if (along >= prev_start && along <= prev_end) {
int progress = along - prev_start;
return prev_base + (prev_delta * progress + (prev_delta > 0 ? prev_span / 2 : -prev_span / 2)) / prev_span;
}
}
return current_base;
}
static uint16_t border_wall_ramp_stair_block(int side, int slope) {
if (side < 2) {
return (slope > 0) ? BLOCK_STONE_BRICK_STAIRS_S : BLOCK_STONE_BRICK_STAIRS_N;
}
return (slope > 0) ? BLOCK_STONE_BRICK_STAIRS_E : BLOCK_STONE_BRICK_STAIRS_W;
}
static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out) {
if (!ctx->enable_wall) return;
if (ctx->wall_min_x > ctx->wall_max_x || ctx->wall_min_z > ctx->wall_max_z) return;
@@ -3611,14 +3840,8 @@ static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk
if (ladder_offset < 0) ladder_offset = -ladder_offset;
int near_corner = (dist_min_x < tower_depth || dist_max_x < tower_depth) &&
(dist_min_z < tower_depth || dist_max_z < tower_depth);
int ladder_module = !near_corner && ladder_offset <= 1;
int ladder_face = !near_corner && ladder_offset == 0 && side_dist == wall_end + 1;
int ladder_trim = ladder_module && side_dist == wall_end + 1 && ladder_offset == 1;
int in_tower = side_dist >= tower_outer && side_dist <= tower_inner &&
(tower_offset <= tower_half_width || near_corner);
int in_wall = (side_dist >= wall_start && side_dist <= wall_end) || in_tower || ladder_face || ladder_trim;
int in_exterior_apron = side_dist < wall_start && !in_wall;
if (!in_wall && !in_exterior_apron) continue;
column_data data = get_column_data(ctx, world_x, world_z);
int visible_base = data.height;
@@ -3627,6 +3850,14 @@ static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk
}
if (visible_base >= CHUNK_HEIGHT - 1) continue;
int dry_ladder_site = !(data.has_water && data.height < data.water_surface);
int ladder_module = !near_corner && dry_ladder_site && ladder_offset <= 1;
int ladder_face = ladder_module && ladder_offset == 0 && side_dist == wall_end + 1;
int ladder_trim = ladder_module && side_dist == wall_end + 1 && ladder_offset == 1;
int in_wall = (side_dist >= wall_start && side_dist <= wall_end) || in_tower || ladder_face || ladder_trim;
int in_exterior_apron = side_dist < wall_start && !in_wall;
if (!in_wall && !in_exterior_apron) continue;
if (in_exterior_apron) {
for (int y = data.height + 1; y < CHUNK_HEIGHT; ++y) {
out->blocks[y][dx][dz] = BLOCK_AIR;
@@ -3643,44 +3874,15 @@ static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk
continue;
}
int center_x = world_x;
int center_z = world_z;
switch (side) {
case 0: center_x = ctx->wall_min_x + wall_center; break;
case 1: center_x = ctx->wall_max_x - wall_center; break;
case 2: center_z = ctx->wall_min_z + wall_center; break;
case 3: center_z = ctx->wall_max_z - wall_center; break;
int smoothed_base = border_wall_ramp_base(ctx, side, along, wall_center, tower_spacing);
int prev_base = border_wall_ramp_base(ctx, side, along - 1, wall_center, tower_spacing);
int next_base = border_wall_ramp_base(ctx, side, along + 1, wall_center, tower_spacing);
int stair_slope = 0;
if (next_base > smoothed_base) {
stair_slope = 1;
} else if (prev_base > smoothed_base) {
stair_slope = -1;
}
int smoothed_base = 0;
int weight_sum = 0;
const int sample_offsets[5] = {-32, -16, 0, 16, 32};
const int sample_weights[5] = {1, 2, 4, 2, 1};
for (int i = 0; i < 5; ++i) {
int sx = center_x;
int sz = center_z;
if (side < 2) {
sz += sample_offsets[i];
} else {
sx += sample_offsets[i];
}
if (sx < ctx->wall_min_x) sx = ctx->wall_min_x;
if (sx > ctx->wall_max_x) sx = ctx->wall_max_x;
if (sz < ctx->wall_min_z) sz = ctx->wall_min_z;
if (sz > ctx->wall_max_z) sz = ctx->wall_max_z;
column_data sample = get_column_data(ctx, sx, sz);
int sample_base = sample.height;
if (sample.has_water && sample.water_surface > sample_base) sample_base = sample.water_surface;
smoothed_base += sample_base * sample_weights[i];
weight_sum += sample_weights[i];
}
smoothed_base /= weight_sum;
if (visible_base > smoothed_base + 8) {
smoothed_base += (visible_base - smoothed_base - 8 + 3) / 4;
} else if (visible_base < smoothed_base - 16) {
smoothed_base -= (smoothed_base - visible_base - 16 + 4) / 5;
}
if (smoothed_base < 1) smoothed_base = 1;
if (smoothed_base > CHUNK_HEIGHT - 2) smoothed_base = CHUNK_HEIGHT - 2;
int height = in_tower ? tower_height : wall_height;
int top = smoothed_base + height;
@@ -3703,7 +3905,10 @@ static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk
uint16_t block = BLOCK_SMOOTH_STONE;
int in_center_corridor = side_dist >= wall_center - 1 && side_dist <= wall_center + 1 &&
rel_y >= 2 && rel_y <= 4;
if (in_center_corridor) {
int in_walltop_tower_passage = in_tower &&
side_dist >= wall_center - 1 && side_dist <= wall_center + 1 &&
rel_y >= wall_height + 1 && rel_y <= wall_height + 3;
if (in_center_corridor || in_walltop_tower_passage) {
continue;
}
if (rel_y < 0) {
@@ -3715,7 +3920,10 @@ static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk
if (side_dist >= wall_center - 1 && side_dist <= wall_center + 1 && rel_y == height) {
block = BLOCK_STONE_BRICKS;
}
if (ladder_face && rel_y >= 2 && rel_y <= height - 1) {
if (ladder_face && rel_y >= 2 && rel_y <= height) {
if (rel_y == height) {
continue;
}
block = ladder_block;
} else if (ladder_trim && rel_y >= 2 && rel_y <= height - 1 && rel_y % 3 != 1) {
block = (rel_y % 6 == 0) ? BLOCK_BLACKSTONE : BLOCK_STONE_BRICKS;
@@ -3730,12 +3938,19 @@ static void generate_chunk_border_wall(worldgen_ctx *ctx, int chunk_x, int chunk
}
set_block_with_height(out, dx, dz, y, block);
}
if (top + 1 < CHUNK_HEIGHT && (side_dist == wall_start || side_dist == wall_end || in_tower) &&
phase % 4 < 2) {
if (top + 1 < CHUNK_HEIGHT) {
int rail_opening = ladder_module && side_dist == wall_end && ladder_offset <= 1;
if (side_dist >= wall_center - 1 && side_dist <= wall_center + 1 && stair_slope != 0) {
set_block_with_height(out, dx, dz, top + 1, border_wall_ramp_stair_block(side, stair_slope));
} else if (!in_tower && !rail_opening && (side_dist == wall_start || side_dist == wall_end)) {
uint16_t rail_block = (side < 2) ? BLOCK_IRON_BARS_NS : BLOCK_IRON_BARS_EW;
set_block_with_height(out, dx, dz, top + 1, rail_block);
} else if (in_tower && phase % 4 < 2) {
set_block_with_height(out, dx, dz, top + 1, BLOCK_STONE_BRICKS);
}
}
}
}
}
// ---------------------------------------------------------------------------
@@ -3805,7 +4020,15 @@ void worldgen_generate_chunk(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_
out->blocks[y][dx][dz] = BLOCK_DIRT;
}
if (cd.height >= 0 && cd.height < CHUNK_HEIGHT) {
out->blocks[cd.height][dx][dz] = select_surface_block(ctx, &cd, world_x, world_z);
uint16_t surface = select_surface_block(ctx, &cd, world_x, world_z);
if (surface == BLOCK_SAND) {
int sand_bottom = cd.height - 2;
if (sand_bottom < dirt_start) sand_bottom = dirt_start;
for (int y = sand_bottom; y < cd.height; ++y) {
if (y > 0 && y < CHUNK_HEIGHT) out->blocks[y][dx][dz] = BLOCK_SAND;
}
}
out->blocks[cd.height][dx][dz] = surface;
}
if (cd.has_water) {
int water_top = cd.water_surface;
@@ -3824,6 +4047,7 @@ void worldgen_generate_chunk(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_
generate_chunk_redwood_floor(ctx, chunk_x, chunk_z, columns, out);
generate_chunk_grass(ctx, chunk_x, chunk_z, columns, out);
generate_chunk_flowers(ctx, chunk_x, chunk_z, columns, out);
generate_chunk_aquatic_life(ctx, chunk_x, chunk_z, columns, out);
// Tree overlay
block_list trees;