Pumpkin/pumpkin-world/src/generation/surface/terrain.rs
Alexander Medvedev ac021d5d47 chore: more work
- fixed nether again, not the dead lock or whatever it is but the server crash
- further replace Notify with CancellationToken
- fixed chunk loading on inital spawn
- more stronghold work
2026-01-21 23:08:56 +01:00

271 lines
10 KiB
Rust

use pumpkin_data::{Block, BlockState, chunk::Biome};
use pumpkin_util::{
math::vector3::Vector3,
random::{RandomDeriver, RandomDeriverImpl, RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
block::RawBlockState,
generation::noise::{
WATER_BLOCK, perlin::DoublePerlinNoiseSampler,
router::proto_noise_router::DoublePerlinNoiseBuilder,
},
};
pub struct SurfaceTerrainBuilder {
// Badlands stuff
terracotta_bands: Box<[RawBlockState]>,
terracotta_bands_offset_noise: DoublePerlinNoiseSampler,
badlands_pillar_noise: DoublePerlinNoiseSampler,
badlands_surface_noise: DoublePerlinNoiseSampler,
badlands_pillar_roof_noise: DoublePerlinNoiseSampler,
// Iceberg stuff
iceberg_pillar_noise: DoublePerlinNoiseSampler,
iceberg_pillar_roof_noise: DoublePerlinNoiseSampler,
iceberg_surface_noise: DoublePerlinNoiseSampler,
}
impl SurfaceTerrainBuilder {
pub fn new(noise_builder: &DoublePerlinNoiseBuilder, random_deriver: &RandomDeriver) -> Self {
Self {
terracotta_bands: Self::create_terracotta_bands(
random_deriver.split_string("minecraft:clay_bands"),
),
terracotta_bands_offset_noise: noise_builder
.get_noise_sampler_for_id("clay_bands_offset"),
badlands_pillar_noise: noise_builder.get_noise_sampler_for_id("badlands_pillar"),
badlands_surface_noise: noise_builder.get_noise_sampler_for_id("badlands_surface"),
badlands_pillar_roof_noise: noise_builder
.get_noise_sampler_for_id("badlands_pillar_roof"),
iceberg_pillar_noise: noise_builder.get_noise_sampler_for_id("iceberg_pillar"),
iceberg_pillar_roof_noise: noise_builder
.get_noise_sampler_for_id("iceberg_pillar_roof"),
iceberg_surface_noise: noise_builder.get_noise_sampler_for_id("iceberg_surface"),
}
}
const ORANGE_TERRACOTTA: RawBlockState =
RawBlockState(Block::ORANGE_TERRACOTTA.default_state.id);
const YELLOW_TERRACOTTA: RawBlockState =
RawBlockState(Block::YELLOW_TERRACOTTA.default_state.id);
const BROWN_TERRACOTTA: RawBlockState = RawBlockState(Block::BROWN_TERRACOTTA.default_state.id);
const RED_TERRACOTTA: RawBlockState = RawBlockState(Block::RED_TERRACOTTA.default_state.id);
const WHITE_TERRACOTTA: RawBlockState = RawBlockState(Block::WHITE_TERRACOTTA.default_state.id);
const LIGHT_GRAY_TERRACOTTA: RawBlockState =
RawBlockState(Block::LIGHT_GRAY_TERRACOTTA.default_state.id);
const TERRACOTTA: RawBlockState = RawBlockState(Block::TERRACOTTA.default_state.id);
fn create_terracotta_bands(mut random: RandomGenerator) -> Box<[RawBlockState]> {
let mut block_states = [Self::TERRACOTTA; 192];
let mut i = 0;
while i < block_states.len() {
i += random.next_bounded_i32(5) as usize + 1;
if i >= block_states.len() {
break;
}
block_states[i] = Self::ORANGE_TERRACOTTA;
i += 1;
}
Self::add_terracotta_bands(&mut random, &mut block_states, 1, Self::YELLOW_TERRACOTTA);
Self::add_terracotta_bands(&mut random, &mut block_states, 2, Self::BROWN_TERRACOTTA);
Self::add_terracotta_bands(&mut random, &mut block_states, 1, Self::RED_TERRACOTTA);
let band_count = random.next_inbetween_i32(9, 15);
let mut current_band = 0;
let mut index = 0;
while current_band < band_count && index < block_states.len() {
block_states[index] = Self::WHITE_TERRACOTTA;
if index > 1 && random.next_bool() {
block_states[index - 1] = Self::LIGHT_GRAY_TERRACOTTA;
}
if index + 1 < block_states.len() && random.next_bool() {
block_states[index + 1] = Self::LIGHT_GRAY_TERRACOTTA;
}
index += random.next_bounded_i32(16) as usize + 4;
current_band += 1;
}
Box::new(block_states)
}
fn add_terracotta_bands(
random: &mut RandomGenerator,
terracotta_bands: &mut [RawBlockState],
min_band_size: i32,
state: RawBlockState,
) {
let band_count = random.next_inbetween_i32(6, 15);
for _ in 0..band_count {
let band_width = min_band_size + random.next_bounded_i32(3);
let start_index = random.next_bounded_i32(terracotta_bands.len() as i32);
for m in 0..band_width {
if (start_index + m < terracotta_bands.len() as i32) && (m < band_width) {
terracotta_bands[(start_index + m) as usize] = state;
} else {
break; // Stop if we reach the end of the array
}
}
}
}
pub fn place_badlands_pillar(
&self,
chunk: &mut ProtoChunk,
global_x: i32,
global_z: i32,
surface_y: i32,
) {
let surface_noise =
(self
.badlands_surface_noise
.sample(global_x as f64, 0.0, global_z as f64)
* 8.25)
.abs();
let pillar_noise =
self.badlands_pillar_noise
.sample(global_x as f64 * 0.2, 0.0, global_z as f64 * 0.2)
* 15.0;
let threshold = surface_noise.min(pillar_noise);
if threshold > 0.0 {
let pillar_roof_noise = (self.badlands_pillar_roof_noise.sample(
global_x as f64 * 0.75,
0.0,
global_z as f64 * 0.75,
) * 1.5)
.abs();
let scaled_threshold = threshold * threshold * 2.5;
let transformed_roof = (pillar_roof_noise * 50.0).ceil() + 24.0;
let elevation = 64.0 + scaled_threshold.min(transformed_roof);
let elevation_y = elevation.floor() as i32;
if surface_y <= elevation_y {
for y in (chunk.bottom_y() as i32..=elevation_y).rev() {
let pos = Vector3::new(global_x, y, global_z);
let block_id = chunk.get_block_state(&pos).to_block_id();
if block_id == Block::get_raw_id_from_state_id(chunk.default_block.id) {
break;
}
if block_id == Block::WATER {
return;
}
}
for y in (chunk.bottom_y() as i32..=elevation_y).rev() {
let pos = Vector3::new(global_x, y, global_z);
let block_state = chunk.get_block_state(&pos).to_state();
if !block_state.is_air() {
break;
}
let default_block = &chunk.default_block;
chunk.set_block_state(global_x, y, global_z, default_block);
}
}
}
}
const SNOW_BLOCK: Block = Block::SNOW_BLOCK;
const PACKED_ICE: Block = Block::PACKED_ICE;
#[expect(clippy::too_many_arguments)]
pub fn place_iceberg(
&self,
chunk: &mut ProtoChunk,
biome: &Biome,
x: i32,
z: i32,
estimated_surface_y: i32,
current_top_y: i32,
sea_level: i32,
random_deriver: &RandomDeriver,
) {
let iceburg_surface_noise =
(self.iceberg_surface_noise.sample(x as f64, 0.0, z as f64) * 8.25).abs();
let iceburg_pillar_noise =
self.iceberg_pillar_noise
.sample(x as f64 * 1.28, 0.0, z as f64 * 1.28)
* 15.0;
let threshold = iceburg_surface_noise.min(iceburg_pillar_noise);
if threshold > 1.8 {
let iceburg_pillar_roof_noise =
(self
.iceberg_pillar_roof_noise
.sample(x as f64 * 1.17, 0.0, z as f64 * 1.17)
* 1.5)
.abs();
let scaled_threshold = threshold * threshold * 1.2;
let scaled_roof_noise = (iceburg_pillar_roof_noise * 40.0).ceil() + 14.0;
let mut block_threshold = scaled_threshold.min(scaled_roof_noise);
// TODO: Cache this
let temperature = biome
.weather
.compute_temperature(x as f64, sea_level, z as f64, sea_level);
if temperature > 0.1f32 {
block_threshold -= 2.0;
}
let (top_block, bottom_block) = if block_threshold > 2.0 {
let value = sea_level as f64 - block_threshold - 7.0;
(block_threshold as i32 + sea_level, value as i32)
} else {
(0, 0)
};
let mut rand = random_deriver.split_pos(x, 0, z);
let snow_block_count = 2 + rand.next_bounded_i32(4);
let snow_bottom = sea_level + 18 + rand.next_bounded_i32(10);
let mut snow_blocks = 0;
let top_y = current_top_y.max(top_block + 1);
for y in (estimated_surface_y..=top_y).rev() {
let pos = Vector3::new(x, y, z);
let block_state = chunk.get_block_state(&pos);
if (block_state.to_state().is_air() && y < top_block && rand.next_f64() > 0.01)
|| (block_state.to_block() == &WATER_BLOCK
&& y > bottom_block
&& y < sea_level
&& bottom_block != 0
&& rand.next_f64() > 0.15)
{
if snow_blocks <= snow_block_count && y > snow_bottom {
chunk.set_block_state(x, y, z, Self::SNOW_BLOCK.default_state);
snow_blocks += 1;
} else {
chunk.set_block_state(x, y, z, Self::PACKED_ICE.default_state);
}
}
}
}
}
pub fn get_terracotta_block(&self, x: i32, y: i32, z: i32) -> &'static BlockState {
let offset = (self
.terracotta_bands_offset_noise
.sample(x as f64, 0.0, z as f64)
* 4.0)
.round() as i32;
let offset = y + offset;
self.terracotta_bands[((offset as u64 + self.terracotta_bands.len() as u64)
% self.terracotta_bands.len() as u64) as usize]
.to_state()
}
}