mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: aquifer sample and top material in cave carver (#2242)
* sin/cos util * 4 nieghbor check * formating * air helper * carver wrapper * fmt * clippy * clipp2 * remove local y * out of bound guard * fluid tick * surface rule * wire up * math correct * start fix * bring back xoroshiro oops * scheduler guard * simplify test * clean test * comment * crash fix * clippy * clippy * i hate you codex * sin cos * im stupid f32
This commit is contained in:
@@ -29,7 +29,6 @@ use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
@@ -2347,8 +2346,9 @@ impl DataComponentImpl for ContainerImpl {
|
||||
default_impl!(Container);
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(clippy::disallowed_types)]
|
||||
pub struct BlockStateImpl {
|
||||
pub properties: HashMap<String, String>,
|
||||
pub properties: std::collections::HashMap<String, String>,
|
||||
}
|
||||
impl PartialEq for BlockStateImpl {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
@@ -2367,9 +2367,10 @@ impl std::hash::Hash for BlockStateImpl {
|
||||
}
|
||||
}
|
||||
impl BlockStateImpl {
|
||||
#[allow(clippy::disallowed_types)]
|
||||
fn read_data(data: &NbtTag) -> Option<Self> {
|
||||
let compound = data.extract_compound()?;
|
||||
let mut properties = HashMap::new();
|
||||
let mut properties = std::collections::HashMap::new();
|
||||
for (key, val) in compound.child_tags.iter() {
|
||||
if let Some(s) = val.extract_string() {
|
||||
properties.insert(key.to_string(), s.to_string());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use num_traits::{Float, One, PrimInt, Zero};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub mod atomic_f32;
|
||||
pub mod bit_storage;
|
||||
@@ -15,6 +16,25 @@ pub mod vector2;
|
||||
pub mod vector3;
|
||||
pub mod vertical_surface_type;
|
||||
|
||||
const SIN_SCALE: f32 = 10430.378; // 65536 / 2P
|
||||
const SIN_MASK: i32 = 65535;
|
||||
|
||||
static SIN: LazyLock<[f32; 65536]> = LazyLock::new(|| {
|
||||
std::array::from_fn(|i| (f64::from(i as u32) / 10430.378350470453).sin() as f32)
|
||||
});
|
||||
|
||||
/// Returns the vanilla sine table value for an angle in radians.
|
||||
#[must_use]
|
||||
pub fn sin(value: f32) -> f32 {
|
||||
SIN[((value * SIN_SCALE) as i32 & SIN_MASK) as usize]
|
||||
}
|
||||
|
||||
/// Returns the vanilla cosine table value for an angle in radians.
|
||||
#[must_use]
|
||||
pub fn cos(value: f32) -> f32 {
|
||||
SIN[((value * SIN_SCALE + 16384.0) as i32 & SIN_MASK) as usize]
|
||||
}
|
||||
|
||||
/// Wraps an angle in degrees to the range [-180, 180).
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -16,7 +16,7 @@ fn main() {
|
||||
);
|
||||
let mut pool_code = String::from(
|
||||
"
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(clippy::match_same_arms)]
|
||||
#[must_use]
|
||||
pub fn get_pool_elements(pool_id: &str) -> Option<&'static [&'static str]> {\n match pool_id {\n",
|
||||
|
||||
@@ -309,7 +309,7 @@ impl Chunk {
|
||||
z: proto_chunk.z,
|
||||
dirty: AtomicBool::new(true),
|
||||
block_ticks: ChunkTickScheduler::default(),
|
||||
fluid_ticks: ChunkTickScheduler::default(),
|
||||
fluid_ticks: ChunkTickScheduler::from_iter(proto_chunk.fluid_ticks),
|
||||
pending_block_entities: Mutex::new(pending_block_entities),
|
||||
status: proto_chunk.stage.into(),
|
||||
blending_data: proto_chunk.blending_data,
|
||||
|
||||
@@ -65,8 +65,14 @@ impl GenerationCache for Cache {
|
||||
let dx = chunk_x - self.x;
|
||||
let dz = chunk_z - self.z;
|
||||
|
||||
(dx >= 0 && dx < self.size && dz >= 0 && dz < self.size)
|
||||
.then(|| self.chunks[(dx * self.size + dz) as usize].get_proto_chunk_mut())
|
||||
if dx < 0 || dx >= self.size || dz < 0 || dz >= self.size {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &mut self.chunks[(dx * self.size + dz) as usize] {
|
||||
Chunk::Proto(chunk) => Some(chunk),
|
||||
Chunk::Level(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk> {
|
||||
@@ -349,6 +355,11 @@ impl Cache {
|
||||
lighting_config: &LightingEngineConfig,
|
||||
) {
|
||||
let mid = ((self.size * self.size) >> 1) as usize;
|
||||
match &self.chunks[mid] {
|
||||
Chunk::Level(_) => return,
|
||||
Chunk::Proto(chunk) if chunk.stage >= stage => return,
|
||||
Chunk::Proto(_) => {}
|
||||
}
|
||||
match stage {
|
||||
StagedChunkEnum::Empty => panic!("empty stage"),
|
||||
StagedChunkEnum::StructureStart => {
|
||||
|
||||
@@ -244,6 +244,49 @@ impl GenerationSchedule {
|
||||
self.queue = BinaryHeap::from(tasks);
|
||||
}
|
||||
|
||||
/// TODO: will remove at some point
|
||||
pub(crate) fn restore_ready_tasks(
|
||||
graph: &mut DAG,
|
||||
queue: &mut BinaryHeap<TaskHeapNode>,
|
||||
chunk_map: &HashMap<ChunkPos, ChunkHolder>,
|
||||
last_level: &ChunkLevel,
|
||||
last_high_priority: &[ChunkPos],
|
||||
waiting_for_chunks: &HashSetType<NodeKey>,
|
||||
) -> usize {
|
||||
debug_assert!(queue.is_empty());
|
||||
|
||||
let mut ready = Vec::new();
|
||||
for (key, node) in &mut graph.nodes {
|
||||
node.in_queue = false;
|
||||
if node.stage == StagedChunkEnum::None
|
||||
|| node.in_degree != 0
|
||||
|| waiting_for_chunks.contains(&key)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(holder) = chunk_map.get(&node.pos) else {
|
||||
continue;
|
||||
};
|
||||
if holder.current_stage >= node.stage || holder.tasks[node.stage as usize] != key {
|
||||
continue;
|
||||
}
|
||||
ready.push((key, node.pos, node.stage));
|
||||
}
|
||||
|
||||
for (key, pos, stage) in &ready {
|
||||
let Some(node) = graph.nodes.get_mut(*key) else {
|
||||
continue;
|
||||
};
|
||||
node.in_queue = true;
|
||||
queue.push(TaskHeapNode(
|
||||
Self::calc_priority(last_level, last_high_priority, *pos, *stage),
|
||||
*key,
|
||||
));
|
||||
}
|
||||
|
||||
ready.len()
|
||||
}
|
||||
|
||||
/// Ensure that the dependency chain for `req_stage` exists on `holder` (for chunk at
|
||||
/// `chunk_pos`) and wire it to depend on `dependency_task`.
|
||||
///
|
||||
@@ -720,6 +763,16 @@ impl GenerationSchedule {
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_satisfied_tasks(&mut self, holder: &mut ChunkHolder, stage: StagedChunkEnum) {
|
||||
for task_idx in (holder.current_stage as usize + 1)..=(stage as usize) {
|
||||
if !holder.tasks[task_idx].is_null() {
|
||||
self.waiting_for_chunks.remove(&holder.tasks[task_idx]);
|
||||
self.drop_node(holder.tasks[task_idx]);
|
||||
holder.tasks[task_idx] = NodeKey::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn receive_chunk(&mut self, pos: ChunkPos, data: RecvChunk) {
|
||||
match data {
|
||||
@@ -733,11 +786,9 @@ impl GenerationSchedule {
|
||||
}
|
||||
debug_assert_eq!(holder.current_stage, StagedChunkEnum::None);
|
||||
|
||||
for i in (holder.current_stage as usize + 1)..=(chunk.get_stage_id() as usize) {
|
||||
self.drop_node(holder.tasks[i]);
|
||||
holder.tasks[i] = NodeKey::null();
|
||||
}
|
||||
holder.current_stage = StagedChunkEnum::from(chunk.get_stage_id());
|
||||
let stage = StagedChunkEnum::from(chunk.get_stage_id());
|
||||
self.drop_satisfied_tasks(&mut holder, stage);
|
||||
holder.current_stage = stage;
|
||||
debug_assert!(self.graph.nodes.contains_key(holder.occupied));
|
||||
self.drop_node(holder.occupied);
|
||||
holder.occupied = NodeKey::null();
|
||||
@@ -784,6 +835,7 @@ impl GenerationSchedule {
|
||||
match chunk {
|
||||
Chunk::Level(chunk) => {
|
||||
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
|
||||
let stage = StagedChunkEnum::Full;
|
||||
if new_pos == pos {
|
||||
if holder.current_stage != StagedChunkEnum::Spawn {
|
||||
warn!(
|
||||
@@ -794,12 +846,11 @@ impl GenerationSchedule {
|
||||
);
|
||||
holder.current_stage = StagedChunkEnum::Spawn;
|
||||
}
|
||||
self.drop_node(holder.tasks[StagedChunkEnum::Full as usize]);
|
||||
holder.tasks[StagedChunkEnum::Full as usize] = NodeKey::null();
|
||||
self.drop_satisfied_tasks(&mut holder, stage);
|
||||
if self.graph.nodes.contains_key(holder.occupied) {
|
||||
self.drop_node(holder.occupied);
|
||||
}
|
||||
holder.current_stage = StagedChunkEnum::Full;
|
||||
holder.current_stage = stage;
|
||||
|
||||
let was_public = holder.public;
|
||||
self.apply_lighting_override(&chunk);
|
||||
@@ -836,7 +887,8 @@ impl GenerationSchedule {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
holder.current_stage = StagedChunkEnum::Full;
|
||||
self.drop_satisfied_tasks(&mut holder, stage);
|
||||
holder.current_stage = stage;
|
||||
holder.chunk = Some(Chunk::Level(chunk));
|
||||
}
|
||||
|
||||
@@ -861,26 +913,18 @@ impl GenerationSchedule {
|
||||
Chunk::Proto(chunk) => {
|
||||
let mut holder = self.chunk_map.remove(&new_pos).unwrap();
|
||||
|
||||
let stage = chunk.stage_id();
|
||||
for task_idx in (holder.current_stage as usize + 1)
|
||||
..=(stage as usize).min(holder.tasks.len() - 1)
|
||||
{
|
||||
if !holder.tasks[task_idx].is_null() {
|
||||
self.waiting_for_chunks.remove(&holder.tasks[task_idx]);
|
||||
self.drop_node(holder.tasks[task_idx]);
|
||||
holder.tasks[task_idx] = NodeKey::null();
|
||||
}
|
||||
}
|
||||
let stage = StagedChunkEnum::from(chunk.stage_id());
|
||||
self.drop_satisfied_tasks(&mut holder, stage);
|
||||
|
||||
if new_pos == pos {
|
||||
debug_assert_ne!(holder.current_stage, StagedChunkEnum::None);
|
||||
if self.graph.nodes.contains_key(holder.occupied) {
|
||||
self.drop_node(holder.occupied);
|
||||
}
|
||||
holder.current_stage = StagedChunkEnum::from(stage);
|
||||
holder.current_stage = stage;
|
||||
} else {
|
||||
if holder.current_stage < StagedChunkEnum::from(stage) {
|
||||
holder.current_stage = StagedChunkEnum::from(stage);
|
||||
if holder.current_stage < stage {
|
||||
holder.current_stage = stage;
|
||||
}
|
||||
if !holder.occupied.is_null()
|
||||
&& self.graph.nodes.contains_key(holder.occupied)
|
||||
@@ -1269,6 +1313,18 @@ impl GenerationSchedule {
|
||||
}
|
||||
} else {
|
||||
// No tasks in flight, wait indefinitely for LevelChannel changes
|
||||
let restored = Self::restore_ready_tasks(
|
||||
&mut self.graph,
|
||||
&mut self.queue,
|
||||
&self.chunk_map,
|
||||
&self.last_level,
|
||||
&self.last_high_priority,
|
||||
&self.waiting_for_chunks,
|
||||
);
|
||||
if restored > 0 {
|
||||
warn!("Restored {restored} stranded ready chunk tasks to generation queue");
|
||||
continue;
|
||||
}
|
||||
debug_assert!(self.debug_check());
|
||||
debug_assert_eq!(self.running_task_count, 0);
|
||||
self.resort_work(self.send_level.wait_and_get(level));
|
||||
|
||||
@@ -325,7 +325,7 @@ impl BiomeSupplier for BlenderBiomeSupplier<'_> {
|
||||
fn biome(&self, x: i32, y: i32, z: i32, sampler: &mut MultiNoiseSampler<'_>) -> &'static Biome {
|
||||
self.blender
|
||||
.blend_biome(x, y, z, &self.shift_noise)
|
||||
.map_or_else(|| self.base.biome(x, y, z, sampler), |blended| blended)
|
||||
.unwrap_or_else(|| self.base.biome(x, y, z, sampler))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use super::Carver;
|
||||
use super::cave::get_height;
|
||||
use crate::ProtoChunk;
|
||||
use pumpkin_data::block_state::BlockState;
|
||||
use super::{CarveRun, Carver, overworld_carve_state, place_carved_block};
|
||||
use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig};
|
||||
use pumpkin_data::{Block, BlockId};
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::random::{RandomGenerator, RandomImpl};
|
||||
use std::f32::consts::PI;
|
||||
@@ -14,7 +11,7 @@ impl Carver for CanyonCarver {
|
||||
fn carve(
|
||||
&self,
|
||||
config: &CarverConfig,
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
random: &mut RandomGenerator,
|
||||
_chunk_pos: &Vector2<i32>,
|
||||
carver_chunk_pos: &Vector2<i32>,
|
||||
@@ -24,8 +21,8 @@ impl Carver for CanyonCarver {
|
||||
return;
|
||||
};
|
||||
|
||||
let min_y = chunk.bottom_y() as i32;
|
||||
let height = chunk.height();
|
||||
let min_y = run.chunk.bottom_y() as i32;
|
||||
let height = run.chunk.height();
|
||||
|
||||
let max_distance = (4 * 2 - 1) * 16;
|
||||
|
||||
@@ -42,7 +39,7 @@ impl Carver for CanyonCarver {
|
||||
|
||||
Self::do_carve(
|
||||
config,
|
||||
chunk,
|
||||
run,
|
||||
random.next_i64(),
|
||||
x as f64,
|
||||
y as f64,
|
||||
@@ -62,7 +59,7 @@ impl CanyonCarver {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn do_carve(
|
||||
config: &CarverConfig,
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
tunnel_seed: i64,
|
||||
mut x: f64,
|
||||
mut y: f64,
|
||||
@@ -75,17 +72,9 @@ impl CanyonCarver {
|
||||
y_scale: f64,
|
||||
legacy_random_source: bool,
|
||||
) {
|
||||
let mut random = if legacy_random_source {
|
||||
RandomGenerator::Legacy(pumpkin_util::random::legacy_rand::LegacyRand::from_seed(
|
||||
tunnel_seed as u64,
|
||||
))
|
||||
} else {
|
||||
RandomGenerator::Xoroshiro(pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(
|
||||
tunnel_seed as u64,
|
||||
))
|
||||
};
|
||||
let mut random = super::new_carver_random(tunnel_seed as u64, legacy_random_source);
|
||||
let width_factor_per_height =
|
||||
Self::init_width_factors(chunk.height() as usize, config, &mut random);
|
||||
Self::init_width_factors(run.chunk.height() as usize, config, &mut random);
|
||||
let mut y_rota = 0.0f32;
|
||||
let mut x_rota = 0.0f32;
|
||||
|
||||
@@ -94,8 +83,9 @@ impl CanyonCarver {
|
||||
};
|
||||
|
||||
for current_step in step..distance {
|
||||
let progress = current_step as f32 * PI / distance as f32;
|
||||
let mut horizontal_radius =
|
||||
(1.5 + (current_step as f32 * PI / distance as f32).sin() * thickness) as f64;
|
||||
1.5 + f64::from(pumpkin_util::math::sin(progress) * thickness);
|
||||
let mut vertical_radius = horizontal_radius * y_scale;
|
||||
horizontal_radius *= canyon_config
|
||||
.shape
|
||||
@@ -109,11 +99,11 @@ impl CanyonCarver {
|
||||
current_step as f32,
|
||||
);
|
||||
|
||||
let xc = vertical_rotation.cos();
|
||||
let xs = vertical_rotation.sin();
|
||||
x += (horizontal_rotation.cos() * xc) as f64;
|
||||
let xc = pumpkin_util::math::cos(vertical_rotation);
|
||||
let xs = pumpkin_util::math::sin(vertical_rotation);
|
||||
x += f64::from(pumpkin_util::math::cos(horizontal_rotation) * xc);
|
||||
y += xs as f64;
|
||||
z += (horizontal_rotation.sin() * xc) as f64;
|
||||
z += f64::from(pumpkin_util::math::sin(horizontal_rotation) * xc);
|
||||
|
||||
vertical_rotation *= 0.7;
|
||||
vertical_rotation += x_rota * 0.05;
|
||||
@@ -124,12 +114,20 @@ impl CanyonCarver {
|
||||
y_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 4.0;
|
||||
|
||||
if random.next_bounded_i32(4) != 0 {
|
||||
if !Self::can_reach(chunk.x, chunk.z, x, z, current_step, distance, thickness) {
|
||||
if !Self::can_reach(
|
||||
run.chunk.x,
|
||||
run.chunk.z,
|
||||
x,
|
||||
z,
|
||||
current_step,
|
||||
distance,
|
||||
thickness,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Self::carve_ellipsoid(
|
||||
chunk,
|
||||
run,
|
||||
config,
|
||||
x,
|
||||
y,
|
||||
@@ -199,7 +197,7 @@ impl CanyonCarver {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn carve_ellipsoid(
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
config: &CarverConfig,
|
||||
x: f64,
|
||||
y: f64,
|
||||
@@ -208,24 +206,25 @@ impl CanyonCarver {
|
||||
vertical_radius: f64,
|
||||
width_factor_per_height: &[f32],
|
||||
) {
|
||||
let center_x = (chunk.x << 4) as f64 + 8.0;
|
||||
let center_z = (chunk.z << 4) as f64 + 8.0;
|
||||
let center_x = (run.chunk.x << 4) as f64 + 8.0;
|
||||
let center_z = (run.chunk.z << 4) as f64 + 8.0;
|
||||
let max_delta = 16.0 + horizontal_radius * 2.0;
|
||||
|
||||
if (x - center_x).abs() > max_delta || (z - center_z).abs() > max_delta {
|
||||
return;
|
||||
}
|
||||
|
||||
let chunk_min_x = chunk.x << 4;
|
||||
let chunk_min_z = chunk.z << 4;
|
||||
let chunk_min_x = run.chunk.x << 4;
|
||||
let chunk_min_z = run.chunk.z << 4;
|
||||
|
||||
let x_index_min = ((x - horizontal_radius).floor() as i32 - chunk_min_x - 1).max(0);
|
||||
let x_index_max = ((x + horizontal_radius).floor() as i32 - chunk_min_x).min(15);
|
||||
|
||||
let min_y = ((y - vertical_radius).floor() as i32 - 1).max(chunk.bottom_y() as i32 + 1);
|
||||
let min_y = ((y - vertical_radius).floor() as i32 - 1).max(run.chunk.bottom_y() as i32 + 1);
|
||||
let protected_blocks_on_top = 7;
|
||||
let max_y = ((y + vertical_radius).floor() as i32 + 1)
|
||||
.min(chunk.bottom_y() as i32 + chunk.height() as i32 - 1 - protected_blocks_on_top);
|
||||
let max_y = ((y + vertical_radius).floor() as i32 + 1).min(
|
||||
run.chunk.bottom_y() as i32 + run.chunk.height() as i32 - 1 - protected_blocks_on_top,
|
||||
);
|
||||
|
||||
let z_index_min = ((z - horizontal_radius).floor() as i32 - chunk_min_z - 1).max(0);
|
||||
let z_index_max = ((z + horizontal_radius).floor() as i32 - chunk_min_z).min(15);
|
||||
@@ -239,6 +238,8 @@ impl CanyonCarver {
|
||||
let zd = (world_z as f64 + 0.5 - z) / horizontal_radius;
|
||||
|
||||
if xd * xd + zd * zd < 1.0 {
|
||||
let mut has_grass = false;
|
||||
|
||||
for world_y in (min_y + 1..=max_y).rev() {
|
||||
let yd = (world_y as f64 - 0.5 - y) / vertical_radius;
|
||||
|
||||
@@ -248,11 +249,18 @@ impl CanyonCarver {
|
||||
yd,
|
||||
zd,
|
||||
world_y,
|
||||
chunk.bottom_y() as i32,
|
||||
) && !chunk.carving_mask.get(world_x, world_y, world_z)
|
||||
run.chunk.bottom_y() as i32,
|
||||
) && !run.chunk.carving_mask.get(world_x, world_y, world_z)
|
||||
{
|
||||
chunk.carving_mask.set(world_x, world_y, world_z);
|
||||
Self::carve_block(chunk, config, world_x, world_y, world_z);
|
||||
run.chunk.carving_mask.set(world_x, world_y, world_z);
|
||||
Self::carve_block(
|
||||
run,
|
||||
config,
|
||||
world_x,
|
||||
world_y,
|
||||
world_z,
|
||||
&mut has_grass,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,28 +283,39 @@ impl CanyonCarver {
|
||||
(xd * xd + zd * zd) * width_factor_per_height[y_index - 1] as f64 + yd * yd / 6.0 >= 1.0
|
||||
}
|
||||
|
||||
fn carve_block(chunk: &mut ProtoChunk, config: &CarverConfig, x: i32, y: i32, z: i32) -> bool {
|
||||
let local_y = y - chunk.bottom_y() as i32;
|
||||
let state_id = chunk.get_block_state_raw(x & 15, local_y, z & 15);
|
||||
let block = Block::from_state_id(state_id);
|
||||
fn carve_block(
|
||||
run: &mut CarveRun,
|
||||
config: &CarverConfig,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
has_grass: &mut bool,
|
||||
) -> bool {
|
||||
let local_y = y - run.chunk.bottom_y() as i32;
|
||||
let state_id = run.chunk.get_block_state_raw(x & 15, local_y, z & 15);
|
||||
let block = pumpkin_data::Block::from_state_id(state_id);
|
||||
|
||||
if block.id == BlockId::WATER || block.id == BlockId::LAVA {
|
||||
return false;
|
||||
if block.id == pumpkin_data::Block::GRASS_BLOCK.id
|
||||
|| block.id == pumpkin_data::Block::MYCELIUM.id
|
||||
{
|
||||
*has_grass = true;
|
||||
}
|
||||
|
||||
if block.id.has_tag(config.replaceable) {
|
||||
let air = BlockState::from_id(Block::AIR.default_state.id);
|
||||
let lava = BlockState::from_id(Block::LAVA.default_state.id);
|
||||
let Some((state, should_schedule_fluid_update)) =
|
||||
overworld_carve_state(run, config, x, y, z)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let lava_y = config
|
||||
.lava_level
|
||||
.get_y(chunk.bottom_y() as i16, chunk.height());
|
||||
|
||||
if y <= lava_y {
|
||||
chunk.set_block_state(x & 15, local_y, z & 15, lava);
|
||||
} else {
|
||||
chunk.set_block_state(x & 15, local_y, z & 15, air);
|
||||
}
|
||||
place_carved_block(
|
||||
run,
|
||||
pumpkin_util::math::vector3::Vector3::new(x, y, z),
|
||||
state,
|
||||
should_schedule_fluid_update,
|
||||
*has_grass,
|
||||
true,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use super::Carver;
|
||||
use crate::ProtoChunk;
|
||||
use super::{CarveRun, Carver, overworld_carve_state, place_carved_block};
|
||||
use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig, HeightProvider};
|
||||
use pumpkin_data::{Block, BlockId};
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::random::{RandomGenerator, RandomImpl};
|
||||
@@ -13,7 +11,7 @@ impl Carver for CaveCarver {
|
||||
fn carve(
|
||||
&self,
|
||||
config: &CarverConfig,
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
random: &mut RandomGenerator,
|
||||
_chunk_pos: &Vector2<i32>,
|
||||
carver_chunk_pos: &Vector2<i32>,
|
||||
@@ -25,8 +23,8 @@ impl Carver for CaveCarver {
|
||||
CarverAdditionalConfig::Canyon(_) => return,
|
||||
};
|
||||
|
||||
let min_y = chunk.bottom_y() as i32;
|
||||
let height = chunk.height();
|
||||
let min_y = run.chunk.bottom_y() as i32;
|
||||
let height = run.chunk.height();
|
||||
|
||||
let max_distance = (4 * 2 - 1) << 4;
|
||||
|
||||
@@ -51,7 +49,7 @@ impl Carver for CaveCarver {
|
||||
let y_scale = config.y_scale.get(random) as f64;
|
||||
let thickness = 1.0 + random.next_f32() * 6.0;
|
||||
Self::create_room(
|
||||
chunk,
|
||||
run,
|
||||
x as f64,
|
||||
y,
|
||||
z as f64,
|
||||
@@ -72,7 +70,7 @@ impl Carver for CaveCarver {
|
||||
|
||||
Self::create_tunnel(
|
||||
config,
|
||||
chunk,
|
||||
run,
|
||||
random.next_i64(),
|
||||
x as f64,
|
||||
y,
|
||||
@@ -109,7 +107,7 @@ impl CaveCarver {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn create_room(
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
@@ -119,15 +117,16 @@ impl CaveCarver {
|
||||
floor_level: f64,
|
||||
is_nether: bool,
|
||||
) {
|
||||
let horizontal_radius = 1.5 + (PI / 2.0).sin() * thickness;
|
||||
let vertical_radius = horizontal_radius as f64 * y_scale;
|
||||
let horizontal_radius =
|
||||
1.5 + f64::from(pumpkin_util::math::sin(std::f32::consts::FRAC_PI_2) * thickness);
|
||||
let vertical_radius = horizontal_radius * y_scale;
|
||||
Self::carve_ellipsoid(
|
||||
chunk,
|
||||
run,
|
||||
config,
|
||||
x + 1.0,
|
||||
y,
|
||||
z,
|
||||
horizontal_radius as f64,
|
||||
horizontal_radius,
|
||||
vertical_radius,
|
||||
floor_level,
|
||||
is_nether,
|
||||
@@ -137,7 +136,7 @@ impl CaveCarver {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn create_tunnel(
|
||||
config: &CarverConfig,
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
tunnel_seed: i64,
|
||||
mut x: f64,
|
||||
mut y: f64,
|
||||
@@ -154,28 +153,21 @@ impl CaveCarver {
|
||||
is_nether: bool,
|
||||
legacy_random_source: bool,
|
||||
) {
|
||||
let mut random = if legacy_random_source {
|
||||
RandomGenerator::Legacy(pumpkin_util::random::legacy_rand::LegacyRand::from_seed(
|
||||
tunnel_seed as u64,
|
||||
))
|
||||
} else {
|
||||
RandomGenerator::Xoroshiro(pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(
|
||||
tunnel_seed as u64,
|
||||
))
|
||||
};
|
||||
let mut random = super::new_carver_random(tunnel_seed as u64, legacy_random_source);
|
||||
let split_point = random.next_bounded_i32(dist / 2) + dist / 4;
|
||||
let is_steep = random.next_bounded_i32(6) == 0;
|
||||
let mut y_rota = 0.0f32;
|
||||
let mut x_rota = 0.0f32;
|
||||
|
||||
for current_step in step..dist {
|
||||
let progress_arg = PI * current_step as f32 / dist as f32;
|
||||
let horizontal_radius =
|
||||
1.5 + (PI * current_step as f32 / dist as f32).sin() * thickness;
|
||||
let vertical_radius = horizontal_radius as f64 * y_scale;
|
||||
let cos_x = vertical_rotation.cos();
|
||||
x += (horizontal_rotation.cos() * cos_x) as f64;
|
||||
y += vertical_rotation.sin() as f64;
|
||||
z += (horizontal_rotation.sin() * cos_x) as f64;
|
||||
1.5 + f64::from(pumpkin_util::math::sin(progress_arg) * thickness);
|
||||
let vertical_radius = horizontal_radius * y_scale;
|
||||
let cos_x = pumpkin_util::math::cos(vertical_rotation);
|
||||
x += f64::from(pumpkin_util::math::cos(horizontal_rotation) * cos_x);
|
||||
y += f64::from(pumpkin_util::math::sin(vertical_rotation));
|
||||
z += f64::from(pumpkin_util::math::sin(horizontal_rotation) * cos_x);
|
||||
|
||||
vertical_rotation *= if is_steep { 0.92 } else { 0.7 };
|
||||
vertical_rotation += x_rota * 0.1;
|
||||
@@ -188,7 +180,7 @@ impl CaveCarver {
|
||||
if current_step == split_point && thickness > 1.0 {
|
||||
Self::create_tunnel(
|
||||
config,
|
||||
chunk,
|
||||
run,
|
||||
random.next_i64(),
|
||||
x,
|
||||
y,
|
||||
@@ -207,7 +199,7 @@ impl CaveCarver {
|
||||
);
|
||||
Self::create_tunnel(
|
||||
config,
|
||||
chunk,
|
||||
run,
|
||||
random.next_i64(),
|
||||
x,
|
||||
y,
|
||||
@@ -228,12 +220,20 @@ impl CaveCarver {
|
||||
}
|
||||
|
||||
if random.next_bounded_i32(4) != 0 {
|
||||
if !Self::can_reach(chunk.x, chunk.z, x, z, current_step, dist, thickness) {
|
||||
if !Self::can_reach(
|
||||
run.chunk.x,
|
||||
run.chunk.z,
|
||||
x,
|
||||
z,
|
||||
current_step,
|
||||
dist,
|
||||
thickness,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
Self::carve_ellipsoid(
|
||||
chunk,
|
||||
run,
|
||||
config,
|
||||
x,
|
||||
y,
|
||||
@@ -247,7 +247,7 @@ impl CaveCarver {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[must_use]
|
||||
fn can_reach(
|
||||
chunk_x: i32,
|
||||
chunk_z: i32,
|
||||
@@ -268,7 +268,7 @@ impl CaveCarver {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn carve_ellipsoid(
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
config: &CarverConfig,
|
||||
x: f64,
|
||||
y: f64,
|
||||
@@ -278,24 +278,25 @@ impl CaveCarver {
|
||||
floor_level: f64,
|
||||
is_nether: bool,
|
||||
) {
|
||||
let center_x = (chunk.x << 4) as f64 + 8.0;
|
||||
let center_z = (chunk.z << 4) as f64 + 8.0;
|
||||
let center_x = (run.chunk.x << 4) as f64 + 8.0;
|
||||
let center_z = (run.chunk.z << 4) as f64 + 8.0;
|
||||
let max_delta = 16.0 + horizontal_radius * 2.0;
|
||||
|
||||
if (x - center_x).abs() > max_delta || (z - center_z).abs() > max_delta {
|
||||
return;
|
||||
}
|
||||
|
||||
let chunk_min_x = chunk.x << 4;
|
||||
let chunk_min_z = chunk.z << 4;
|
||||
let chunk_min_x = run.chunk.x << 4;
|
||||
let chunk_min_z = run.chunk.z << 4;
|
||||
|
||||
let x_index_min = ((x - horizontal_radius).floor() as i32 - chunk_min_x - 1).max(0);
|
||||
let x_index_max = ((x + horizontal_radius).floor() as i32 - chunk_min_x).min(15);
|
||||
|
||||
let min_y = ((y - vertical_radius).floor() as i32 - 1).max(chunk.bottom_y() as i32 + 1);
|
||||
let min_y = ((y - vertical_radius).floor() as i32 - 1).max(run.chunk.bottom_y() as i32 + 1);
|
||||
let protected_blocks_on_top = 7;
|
||||
let max_y = ((y + vertical_radius).floor() as i32 + 1)
|
||||
.min(chunk.bottom_y() as i32 + chunk.height() as i32 - 1 - protected_blocks_on_top);
|
||||
let max_y = ((y + vertical_radius).floor() as i32 + 1).min(
|
||||
run.chunk.bottom_y() as i32 + run.chunk.height() as i32 - 1 - protected_blocks_on_top,
|
||||
);
|
||||
|
||||
let z_index_min = ((z - horizontal_radius).floor() as i32 - chunk_min_z - 1).max(0);
|
||||
let z_index_max = ((z + horizontal_radius).floor() as i32 - chunk_min_z).min(15);
|
||||
@@ -315,11 +316,11 @@ impl CaveCarver {
|
||||
let yd = (world_y as f64 - 0.5 - y) / vertical_radius;
|
||||
|
||||
if !Self::should_skip(xd, yd, zd, floor_level)
|
||||
&& !chunk.carving_mask.get(world_x, world_y, world_z)
|
||||
&& !run.chunk.carving_mask.get(world_x, world_y, world_z)
|
||||
{
|
||||
chunk.carving_mask.set(world_x, world_y, world_z);
|
||||
run.chunk.carving_mask.set(world_x, world_y, world_z);
|
||||
Self::carve_block(
|
||||
chunk,
|
||||
run,
|
||||
config,
|
||||
world_x,
|
||||
world_y,
|
||||
@@ -344,7 +345,7 @@ impl CaveCarver {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn carve_block(
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
config: &CarverConfig,
|
||||
x: i32,
|
||||
y: i32,
|
||||
@@ -352,65 +353,43 @@ impl CaveCarver {
|
||||
is_nether: bool,
|
||||
has_grass: &mut bool,
|
||||
) -> bool {
|
||||
let local_y = y - chunk.bottom_y() as i32;
|
||||
let state = chunk.get_block_state(&Vector3::new(x, y, z));
|
||||
let block = state.to_block_id();
|
||||
let state = run.chunk.get_block_state(&Vector3::new(x, y, z));
|
||||
let block = state.to_block();
|
||||
|
||||
if block == BlockId::GRASS_BLOCK || block == BlockId::MYCELIUM {
|
||||
if block.id == pumpkin_data::Block::GRASS_BLOCK.id
|
||||
|| block.id == pumpkin_data::Block::MYCELIUM.id
|
||||
{
|
||||
*has_grass = true;
|
||||
}
|
||||
|
||||
if !block.has_tag(config.replaceable) {
|
||||
if !block.id.has_tag(config.replaceable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let carve_state = {
|
||||
let lava_y = if is_nether {
|
||||
chunk.bottom_y() as i32 + 31
|
||||
let (state, should_schedule_fluid_update) = if is_nether {
|
||||
let state = if y <= run.chunk.bottom_y() as i32 + 31 {
|
||||
run.ids.lava
|
||||
} else {
|
||||
config
|
||||
.lava_level
|
||||
.get_y(chunk.bottom_y() as i16, chunk.height())
|
||||
run.ids.cave_air
|
||||
};
|
||||
|
||||
if y <= lava_y {
|
||||
Some(Block::LAVA.default_state)
|
||||
} else {
|
||||
// TODO: Aquifer logic goes here.
|
||||
// BlockState state = aquifer.computeSubstance(...)
|
||||
// return state (or debug barrier if null)
|
||||
if block == BlockId::WATER || block == BlockId::LAVA {
|
||||
None
|
||||
} else {
|
||||
Some(Block::AIR.default_state)
|
||||
}
|
||||
}
|
||||
(state, false)
|
||||
} else {
|
||||
let Some(state) = overworld_carve_state(run, config, x, y, z) else {
|
||||
return false;
|
||||
};
|
||||
state
|
||||
};
|
||||
|
||||
if let Some(state) = carve_state {
|
||||
chunk.set_block_state(x, local_y, z, state);
|
||||
place_carved_block(
|
||||
run,
|
||||
Vector3::new(x, y, z),
|
||||
state,
|
||||
should_schedule_fluid_update,
|
||||
*has_grass,
|
||||
!is_nether,
|
||||
);
|
||||
|
||||
// TODO: Fluid scheduling
|
||||
// if aquifer.should_schedule_fluid_update() && !state.fluid_state().is_empty() {
|
||||
// chunk.mark_pos_for_postprocessing(x, y, z);
|
||||
// }
|
||||
|
||||
// TODO: fix this (grass block survival logic)
|
||||
// if *has_grass {
|
||||
// let below_state_id = chunk.get_block_state_raw(x, local_y - 1, z);
|
||||
// let below_block = pumpkin_data::Block::from_state_id(below_state_id);
|
||||
|
||||
// if below_block.id == pumpkin_data::Block::DIRT.id {
|
||||
// let top_material =
|
||||
// pumpkin_data::Block::GRASS_BLOCK.default_state;
|
||||
// chunk.set_block_state(x, local_y - 1, z, top_material);
|
||||
// }
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,3 +423,102 @@ pub fn get_height(p: &HeightProvider, random: &mut RandomGenerator, min_y: i8, h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pumpkin_data::carver::CAVE;
|
||||
use pumpkin_data::{Block, BlockStateId, dimension::Dimension};
|
||||
|
||||
type Run<'a, 'b> = super::super::CarveRun<'a, 'b>;
|
||||
|
||||
#[test]
|
||||
fn carves_at_world_y() {
|
||||
super::super::with_carve_run(Dimension::OVERWORLD, |run| {
|
||||
let expected = super::super::overworld_carve_state(run, &CAVE, 5, 20, 6)
|
||||
.expect("test position should carve")
|
||||
.0
|
||||
.id;
|
||||
assert_world_y(run, 5, 20, 6, expected);
|
||||
assert_world_y(run, 7, -58, 8, Block::LAVA.default_state.id);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_aquifer_state() {
|
||||
super::super::with_carve_run(Dimension::OVERWORLD, |run| {
|
||||
let (x, y, z, expected_state) =
|
||||
find_aquifer_carve_state(run, |state, _| state.id != Block::AIR.default_state.id)
|
||||
.expect("expected non-air aquifer carve state in test chunk");
|
||||
carve_at(run, x, y, z, Block::WATER.default_state, expected_state.id);
|
||||
assert_ne!(expected_state.id, Block::AIR.default_state.id);
|
||||
|
||||
let (x, y, z, expected_state) =
|
||||
find_aquifer_carve_state(run, |state, schedule| state.is_liquid() && schedule)
|
||||
.expect("expected scheduled aquifer fluid in test chunk");
|
||||
let old_tick_count = run.chunk.fluid_ticks.len();
|
||||
|
||||
carve_at(run, x, y, z, Block::STONE.default_state, expected_state.id);
|
||||
assert_eq!(run.chunk.fluid_ticks.len(), old_tick_count + 1);
|
||||
let pos = run.chunk.fluid_ticks.last().unwrap().position.0;
|
||||
assert_eq!((pos.x, pos.y, pos.z), (x, y, z));
|
||||
});
|
||||
}
|
||||
|
||||
fn assert_world_y(run: &mut Run, x: i32, y: i32, z: i32, expected: BlockStateId) {
|
||||
let old_wrong_y = y - run.chunk.bottom_y() as i32;
|
||||
let stone = Block::STONE.default_state;
|
||||
|
||||
run.chunk.set_block_state(x, y, z, stone);
|
||||
run.chunk.set_block_state(x, old_wrong_y, z, stone);
|
||||
carve_at(run, x, y, z, stone, expected);
|
||||
assert_eq!(block_id(run, x, old_wrong_y, z), stone.id);
|
||||
}
|
||||
|
||||
fn carve_at(
|
||||
run: &mut Run,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
initial_state: &'static pumpkin_data::BlockState,
|
||||
expected: BlockStateId,
|
||||
) {
|
||||
let mut has_grass = false;
|
||||
|
||||
run.chunk.set_block_state(x, y, z, initial_state);
|
||||
let carved = CaveCarver::carve_block(run, &CAVE, x, y, z, false, &mut has_grass);
|
||||
assert!(carved);
|
||||
assert_eq!(block_id(run, x, y, z), expected);
|
||||
}
|
||||
|
||||
fn block_id(run: &Run, x: i32, y: i32, z: i32) -> BlockStateId {
|
||||
run.chunk.get_block_state(&Vector3::new(x, y, z))
|
||||
}
|
||||
|
||||
fn find_aquifer_carve_state(
|
||||
run: &mut Run,
|
||||
predicate: impl Fn(&'static pumpkin_data::BlockState, bool) -> bool,
|
||||
) -> Option<(i32, i32, i32, &'static pumpkin_data::BlockState)> {
|
||||
let lava_y = CAVE
|
||||
.lava_level
|
||||
.get_y(run.chunk.bottom_y() as i16, run.chunk.height());
|
||||
|
||||
for y in (lava_y + 1)..=63 {
|
||||
for x in 0..16 {
|
||||
for z in 0..16 {
|
||||
let Some((state, should_schedule)) =
|
||||
super::super::overworld_carve_state(run, &CAVE, x, y, z)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if predicate(state, should_schedule) {
|
||||
return Some((x, y, z, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,115 @@ pub mod cave;
|
||||
pub mod mask;
|
||||
|
||||
use crate::ProtoChunk;
|
||||
use crate::generation::GlobalRandomConfig;
|
||||
use crate::generation::generator::VanillaGenerator;
|
||||
use crate::generation::noise::aquifer_sampler::CarverAquiferSampler;
|
||||
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
|
||||
use crate::generation::noise::router::surface_height_sampler::{
|
||||
SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions,
|
||||
};
|
||||
use crate::generation::surface::rule::try_apply_material_rule;
|
||||
use crate::generation::surface::terrain::SurfaceTerrainBuilder;
|
||||
use crate::generation::surface::{MaterialRuleContext, steep_material_condition};
|
||||
use pumpkin_data::block_state::BlockState;
|
||||
use pumpkin_data::carver::{CANYON, CAVE, CAVE_EXTRA_UNDERGROUND, NETHER_CAVE};
|
||||
use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig};
|
||||
use pumpkin_data::chunk_gen_settings::MaterialRule;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::fluid::Fluid;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::random::{RandomGenerator, RandomImpl, get_carver_seed};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::random::{RandomGenerator, RandomImpl};
|
||||
|
||||
const OVERWORLD_CARVERS: [&CarverConfig; 3] = [&CAVE, &CAVE_EXTRA_UNDERGROUND, &CANYON];
|
||||
const NETHER_CARVERS: [&CarverConfig; 1] = [&NETHER_CAVE];
|
||||
|
||||
pub struct CarverBlockIds {
|
||||
pub air: &'static BlockState,
|
||||
pub cave_air: &'static BlockState,
|
||||
pub lava: &'static BlockState,
|
||||
pub dirt: &'static BlockState,
|
||||
pub grass_block: &'static BlockState,
|
||||
pub mycelium: &'static BlockState,
|
||||
}
|
||||
|
||||
impl Default for CarverBlockIds {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CarverBlockIds {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
air: pumpkin_data::Block::AIR.default_state,
|
||||
cave_air: pumpkin_data::Block::CAVE_AIR.default_state,
|
||||
lava: pumpkin_data::Block::LAVA.default_state,
|
||||
dirt: pumpkin_data::Block::DIRT.default_state,
|
||||
grass_block: pumpkin_data::Block::GRASS_BLOCK.default_state,
|
||||
mycelium: pumpkin_data::Block::MYCELIUM.default_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CarvingContext<'a> {
|
||||
pub min_y: i8,
|
||||
pub height: u16,
|
||||
pub random_config: &'a GlobalRandomConfig,
|
||||
pub surface_noise: &'a DoublePerlinNoiseSampler,
|
||||
pub secondary_noise: &'a DoublePerlinNoiseSampler,
|
||||
pub terrain_builder: &'a SurfaceTerrainBuilder,
|
||||
pub sea_level: i32,
|
||||
pub surface_rule: &'a MaterialRule,
|
||||
pub surface_height_sampler: SurfaceHeightEstimateSampler<'a>,
|
||||
pub carver_aquifer: Option<CarverAquiferSampler<'a>>,
|
||||
}
|
||||
|
||||
pub struct CarveRun<'a, 'b> {
|
||||
pub ctx: &'a mut CarvingContext<'b>,
|
||||
pub chunk: &'a mut ProtoChunk,
|
||||
pub ids: CarverBlockIds,
|
||||
}
|
||||
|
||||
impl CarvingContext<'_> {
|
||||
pub fn top_material(
|
||||
&mut self,
|
||||
chunk: &mut ProtoChunk,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
under_fluid: bool,
|
||||
steep: bool,
|
||||
) -> Option<&'static BlockState> {
|
||||
let mut context = MaterialRuleContext::new(
|
||||
self.min_y,
|
||||
self.height,
|
||||
&self.random_config.base_random_deriver,
|
||||
self.terrain_builder,
|
||||
self.surface_noise,
|
||||
self.secondary_noise,
|
||||
self.sea_level,
|
||||
);
|
||||
context.init_horizontal(x, z);
|
||||
context.biome = chunk.get_terrain_gen_biome(x, y, z);
|
||||
context.set_steep_material_condition(steep);
|
||||
context.init_vertical(1, 1, y, if under_fluid { y + 1 } else { i32::MIN });
|
||||
|
||||
try_apply_material_rule(
|
||||
self.surface_rule,
|
||||
chunk,
|
||||
&mut context,
|
||||
&mut self.surface_height_sampler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Carver {
|
||||
fn carve(
|
||||
&self,
|
||||
config: &CarverConfig,
|
||||
chunk: &mut ProtoChunk,
|
||||
run: &mut CarveRun,
|
||||
random: &mut RandomGenerator,
|
||||
chunk_pos: &Vector2<i32>,
|
||||
carver_chunk_pos: &Vector2<i32>,
|
||||
@@ -28,18 +126,58 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
|
||||
let chunk_z = chunk.z;
|
||||
let chunk_pos = Vector2::new(chunk_x, chunk_z);
|
||||
|
||||
let overworld_carvers = [&CAVE, &CAVE_EXTRA_UNDERGROUND, &CANYON];
|
||||
let nether_carvers = [&NETHER_CAVE];
|
||||
let carvers_to_use = carvers_for_dimension(&generator.dimension);
|
||||
|
||||
let carvers_to_use = if generator.dimension == pumpkin_data::dimension::Dimension::OVERWORLD {
|
||||
&overworld_carvers[..]
|
||||
} else if generator.dimension == pumpkin_data::dimension::Dimension::THE_NETHER {
|
||||
&nether_carvers[..]
|
||||
} else {
|
||||
&[]
|
||||
let start_x = crate::generation::positions::chunk_pos::start_block_x(chunk_x);
|
||||
let start_z = crate::generation::positions::chunk_pos::start_block_z(chunk_z);
|
||||
let generation_shape = &generator.settings.shape;
|
||||
let horizontal_cell_count = 16 / generation_shape.horizontal_cell_block_count();
|
||||
|
||||
let horizontal_biome_end = crate::generation::biome_coords::from_block(
|
||||
horizontal_cell_count as i32 * generation_shape.horizontal_cell_block_count() as i32,
|
||||
);
|
||||
let surface_config = SurfaceHeightSamplerBuilderOptions::new(
|
||||
crate::generation::biome_coords::from_block(start_x),
|
||||
crate::generation::biome_coords::from_block(start_z),
|
||||
horizontal_biome_end as usize,
|
||||
generation_shape.min_y as i32,
|
||||
generation_shape.max_y() as i32,
|
||||
generation_shape.vertical_cell_block_count() as usize,
|
||||
);
|
||||
let surface_height_sampler = SurfaceHeightEstimateSampler::generate(
|
||||
&generator.base_router.surface_estimator,
|
||||
&surface_config,
|
||||
);
|
||||
let carver_aquifer = generator.settings.aquifers_enabled.then(|| {
|
||||
CarverAquiferSampler::new(
|
||||
chunk_x,
|
||||
chunk_z,
|
||||
&generator.base_router,
|
||||
&generator.random_config,
|
||||
generator.settings,
|
||||
)
|
||||
});
|
||||
|
||||
let mut context = CarvingContext {
|
||||
min_y: generator.dimension.min_y as i8,
|
||||
height: generator.dimension.logical_height as u16,
|
||||
random_config: &generator.random_config,
|
||||
surface_noise: &generator.terrain_cache.surface_noise,
|
||||
secondary_noise: &generator.terrain_cache.secondary_noise,
|
||||
terrain_builder: &generator.terrain_cache.terrain_builder,
|
||||
sea_level: generator.settings.sea_level,
|
||||
surface_rule: &generator.settings.surface_rule,
|
||||
surface_height_sampler,
|
||||
carver_aquifer,
|
||||
};
|
||||
|
||||
//let _cave_carver = cave::CaveCarver;
|
||||
let mut run = CarveRun {
|
||||
ctx: &mut context,
|
||||
chunk,
|
||||
ids: CarverBlockIds::new(),
|
||||
};
|
||||
|
||||
let cave_carver = cave::CaveCarver;
|
||||
let canyon_carver = canyon::CanyonCarver;
|
||||
|
||||
for dx in -radius..=radius {
|
||||
@@ -51,37 +189,30 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
|
||||
// In vanilla, carvers are per-biome. Here we use the hardcoded list but
|
||||
// maintain the random seed logic.
|
||||
for (index, &config) in carvers_to_use.iter().enumerate() {
|
||||
let seed = get_carver_seed(
|
||||
let seed = get_large_feature_seed(
|
||||
generator.random_config.seed + index as u64,
|
||||
carver_x,
|
||||
carver_z,
|
||||
);
|
||||
let mut carver_random = if generator.settings.legacy_random_source {
|
||||
RandomGenerator::Legacy(
|
||||
pumpkin_util::random::legacy_rand::LegacyRand::from_seed(seed),
|
||||
)
|
||||
} else {
|
||||
RandomGenerator::Xoroshiro(
|
||||
pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(seed),
|
||||
)
|
||||
};
|
||||
let mut carver_random =
|
||||
new_carver_random(seed, generator.settings.legacy_random_source);
|
||||
|
||||
if should_carve(config, &mut carver_random) {
|
||||
match config.additional {
|
||||
CarverAdditionalConfig::Cave(_) | CarverAdditionalConfig::NetherCave(_) => {
|
||||
// cave_carver.carve(
|
||||
// config,
|
||||
// chunk,
|
||||
// &mut carver_random,
|
||||
// &chunk_pos,
|
||||
// &carver_chunk_pos,
|
||||
// generator.settings.legacy_random_source,
|
||||
// );
|
||||
cave_carver.carve(
|
||||
config,
|
||||
&mut run,
|
||||
&mut carver_random,
|
||||
&chunk_pos,
|
||||
&carver_chunk_pos,
|
||||
generator.settings.legacy_random_source,
|
||||
);
|
||||
}
|
||||
CarverAdditionalConfig::Canyon(_) => {
|
||||
canyon_carver.carve(
|
||||
config,
|
||||
chunk,
|
||||
&mut run,
|
||||
&mut carver_random,
|
||||
&chunk_pos,
|
||||
&carver_chunk_pos,
|
||||
@@ -98,3 +229,343 @@ pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) {
|
||||
fn should_carve(config: &CarverConfig, random: &mut RandomGenerator) -> bool {
|
||||
random.next_f32() <= config.probability
|
||||
}
|
||||
|
||||
fn get_large_feature_seed(seed: u64, chunk_x: i32, chunk_z: i32) -> u64 {
|
||||
let mut random = pumpkin_util::random::legacy_rand::LegacyRand::from_seed(seed);
|
||||
let x_scale = random.next_i64();
|
||||
let z_scale = random.next_i64();
|
||||
let seed = seed as i64;
|
||||
let result =
|
||||
(chunk_x as i64).wrapping_mul(x_scale) ^ (chunk_z as i64).wrapping_mul(z_scale) ^ seed;
|
||||
result as u64
|
||||
}
|
||||
|
||||
const fn new_carver_random(seed: u64, non_vanilla_random: bool) -> RandomGenerator {
|
||||
if non_vanilla_random {
|
||||
RandomGenerator::Xoroshiro(pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(
|
||||
seed,
|
||||
))
|
||||
} else {
|
||||
RandomGenerator::Legacy(pumpkin_util::random::legacy_rand::LegacyRand::from_seed(
|
||||
seed,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn carvers_for_dimension(dimension: &Dimension) -> &'static [&'static CarverConfig] {
|
||||
if dimension == &Dimension::OVERWORLD {
|
||||
&OVERWORLD_CARVERS
|
||||
} else if dimension == &Dimension::THE_NETHER {
|
||||
&NETHER_CARVERS
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
fn carve_top_material(
|
||||
run: &mut CarveRun,
|
||||
x: i32,
|
||||
carved_y: i32,
|
||||
z: i32,
|
||||
carved_state: &'static BlockState,
|
||||
has_grass: bool,
|
||||
overworld: bool,
|
||||
) {
|
||||
if !overworld || !has_grass {
|
||||
return;
|
||||
}
|
||||
|
||||
let below_y = carved_y - 1;
|
||||
let below_state = run.chunk.get_block_state(&Vector3::new(x, below_y, z));
|
||||
if below_state != run.ids.dirt.id {
|
||||
return;
|
||||
}
|
||||
|
||||
let steep = steep_material_condition(run.chunk, x, z);
|
||||
if let Some(top_material) =
|
||||
run.ctx
|
||||
.top_material(run.chunk, x, below_y, z, carved_state.is_liquid(), steep)
|
||||
{
|
||||
run.chunk.set_block_state(x, below_y, z, top_material);
|
||||
|
||||
schedule_fluid_tick_for_state(run.chunk, x, below_y, z, top_material);
|
||||
}
|
||||
}
|
||||
|
||||
fn overworld_carve_state(
|
||||
run: &mut CarveRun,
|
||||
config: &CarverConfig,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
) -> Option<(&'static BlockState, bool)> {
|
||||
let lava_y = config
|
||||
.lava_level
|
||||
.get_y(run.chunk.bottom_y() as i16, run.chunk.height());
|
||||
|
||||
if y <= lava_y {
|
||||
return Some((run.ids.lava, false));
|
||||
}
|
||||
|
||||
let Some(aquifer) = run.ctx.carver_aquifer.as_mut() else {
|
||||
return Some((run.ids.air, false));
|
||||
};
|
||||
|
||||
let result = aquifer.compute(&Vector3::new(x, y, z), 0.0);
|
||||
result
|
||||
.state
|
||||
.map(|state| (state, result.should_schedule_fluid_update))
|
||||
}
|
||||
|
||||
fn place_carved_block(
|
||||
run: &mut CarveRun,
|
||||
pos: Vector3<i32>,
|
||||
state: &'static BlockState,
|
||||
should_schedule_fluid_update: bool,
|
||||
has_grass: bool,
|
||||
overworld: bool,
|
||||
) {
|
||||
run.chunk.set_block_state(pos.x, pos.y, pos.z, state);
|
||||
|
||||
if overworld && should_schedule_fluid_update && state.is_liquid() {
|
||||
schedule_fluid_tick_for_state(run.chunk, pos.x, pos.y, pos.z, state);
|
||||
}
|
||||
|
||||
carve_top_material(run, pos.x, pos.y, pos.z, state, has_grass, overworld);
|
||||
}
|
||||
|
||||
fn schedule_fluid_tick_for_state(
|
||||
chunk: &mut ProtoChunk,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
state: &'static BlockState,
|
||||
) {
|
||||
if state.id == pumpkin_data::Block::WATER.default_state.id {
|
||||
chunk.schedule_fluid_tick(x, y, z, &Fluid::WATER);
|
||||
} else if state.id == pumpkin_data::Block::LAVA.default_state.id {
|
||||
chunk.schedule_fluid_tick(x, y, z, &Fluid::LAVA);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn with_carve_run<F>(dimension: Dimension, test: F)
|
||||
where
|
||||
F: FnOnce(&mut CarveRun<'_, '_>),
|
||||
{
|
||||
with_carve_run_options(dimension, None, true, test);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn with_carve_run_options<F>(
|
||||
dimension: Dimension,
|
||||
surface_rule: Option<&MaterialRule>,
|
||||
use_carver_aquifer: bool,
|
||||
test: F,
|
||||
) where
|
||||
F: FnOnce(&mut CarveRun<'_, '_>),
|
||||
{
|
||||
use crate::generation::generator::{GeneratorInit, VanillaGenerator};
|
||||
use pumpkin_util::world_seed::Seed;
|
||||
|
||||
let generator = VanillaGenerator::new(Seed(42), dimension);
|
||||
let mut chunk = ProtoChunk::new(0, 0, &generator);
|
||||
|
||||
let start_x = crate::generation::positions::chunk_pos::start_block_x(chunk.x);
|
||||
let start_z = crate::generation::positions::chunk_pos::start_block_z(chunk.z);
|
||||
let generation_shape = &generator.settings.shape;
|
||||
let horizontal_cell_count = 16 / generation_shape.horizontal_cell_block_count();
|
||||
let horizontal_biome_end = crate::generation::biome_coords::from_block(
|
||||
horizontal_cell_count as i32 * generation_shape.horizontal_cell_block_count() as i32,
|
||||
);
|
||||
let surface_config = SurfaceHeightSamplerBuilderOptions::new(
|
||||
crate::generation::biome_coords::from_block(start_x),
|
||||
crate::generation::biome_coords::from_block(start_z),
|
||||
horizontal_biome_end as usize,
|
||||
generation_shape.min_y as i32,
|
||||
generation_shape.max_y() as i32,
|
||||
generation_shape.vertical_cell_block_count() as usize,
|
||||
);
|
||||
let surface_height_sampler = SurfaceHeightEstimateSampler::generate(
|
||||
&generator.base_router.surface_estimator,
|
||||
&surface_config,
|
||||
);
|
||||
let carver_aquifer = use_carver_aquifer.then(|| {
|
||||
CarverAquiferSampler::new(
|
||||
chunk.x,
|
||||
chunk.z,
|
||||
&generator.base_router,
|
||||
&generator.random_config,
|
||||
generator.settings,
|
||||
)
|
||||
});
|
||||
let mut context = CarvingContext {
|
||||
min_y: generator.dimension.min_y as i8,
|
||||
height: generator.dimension.logical_height as u16,
|
||||
random_config: &generator.random_config,
|
||||
surface_noise: &generator.terrain_cache.surface_noise,
|
||||
secondary_noise: &generator.terrain_cache.secondary_noise,
|
||||
terrain_builder: &generator.terrain_cache.terrain_builder,
|
||||
sea_level: generator.settings.sea_level,
|
||||
surface_rule: surface_rule.unwrap_or(&generator.settings.surface_rule),
|
||||
surface_height_sampler,
|
||||
carver_aquifer,
|
||||
};
|
||||
let mut run = CarveRun {
|
||||
ctx: &mut context,
|
||||
chunk: &mut chunk,
|
||||
ids: CarverBlockIds::new(),
|
||||
};
|
||||
|
||||
test(&mut run);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::chunk_gen_settings::{
|
||||
BlockMaterialRule, ConditionMaterialRule, MaterialCondition, SequenceMaterialRule,
|
||||
WaterMaterialCondition,
|
||||
};
|
||||
|
||||
static PODZOL_RULE: MaterialRule = MaterialRule::Block(BlockMaterialRule {
|
||||
result_state: Block::PODZOL.default_state,
|
||||
});
|
||||
static GRASS_RULE: MaterialRule = MaterialRule::Block(BlockMaterialRule {
|
||||
result_state: Block::GRASS_BLOCK.default_state,
|
||||
});
|
||||
static WATER_SENSITIVE_RULES: [MaterialRule; 2] = [
|
||||
MaterialRule::Condition(ConditionMaterialRule {
|
||||
if_true: MaterialCondition::Water(WaterMaterialCondition {
|
||||
offset: 0,
|
||||
surface_depth_multiplier: 0,
|
||||
add_stone_depth: false,
|
||||
}),
|
||||
then_run: &GRASS_RULE,
|
||||
}),
|
||||
MaterialRule::Block(BlockMaterialRule {
|
||||
result_state: Block::DIRT.default_state,
|
||||
}),
|
||||
];
|
||||
static WATER_SENSITIVE_RULE: MaterialRule = MaterialRule::Sequence(SequenceMaterialRule {
|
||||
sequence: &WATER_SENSITIVE_RULES,
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn overworld_has_aquifer() {
|
||||
with_carve_run(Dimension::OVERWORLD, |run| {
|
||||
assert!(run.ctx.carver_aquifer.is_some());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restores_surface() {
|
||||
with_carve_run_options(Dimension::OVERWORLD, Some(&PODZOL_RULE), false, |run| {
|
||||
let x = 4;
|
||||
let y = 70;
|
||||
let z = 5;
|
||||
run.chunk
|
||||
.set_block_state(x, y - 1, z, Block::DIRT.default_state);
|
||||
|
||||
carve_top_material(run, x, y, z, Block::AIR.default_state, true, true);
|
||||
|
||||
assert_eq!(
|
||||
run.chunk.get_block_state(&Vector3::new(x, y - 1, z)),
|
||||
Block::PODZOL.default_state.id,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_surface_restore() {
|
||||
with_carve_run_options(Dimension::OVERWORLD, Some(&PODZOL_RULE), false, |run| {
|
||||
let x = 4;
|
||||
let y = 70;
|
||||
let z = 5;
|
||||
|
||||
run.chunk
|
||||
.set_block_state(x, y - 1, z, Block::DIRT.default_state);
|
||||
carve_top_material(run, x, y, z, Block::AIR.default_state, false, true);
|
||||
assert_eq!(
|
||||
run.chunk.get_block_state(&Vector3::new(x, y - 1, z)),
|
||||
Block::DIRT.default_state.id,
|
||||
);
|
||||
|
||||
run.chunk
|
||||
.set_block_state(x, y - 1, z, Block::STONE.default_state);
|
||||
carve_top_material(run, x, y, z, Block::AIR.default_state, true, true);
|
||||
assert_eq!(
|
||||
run.chunk.get_block_state(&Vector3::new(x, y - 1, z)),
|
||||
Block::STONE.default_state.id,
|
||||
);
|
||||
|
||||
run.chunk
|
||||
.set_block_state(x, y - 1, z, Block::DIRT.default_state);
|
||||
carve_top_material(run, x, y, z, Block::AIR.default_state, true, false);
|
||||
assert_eq!(
|
||||
run.chunk.get_block_state(&Vector3::new(x, y - 1, z)),
|
||||
Block::DIRT.default_state.id,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn passes_fluid_to_rule() {
|
||||
with_carve_run_options(
|
||||
Dimension::OVERWORLD,
|
||||
Some(&WATER_SENSITIVE_RULE),
|
||||
false,
|
||||
|run| {
|
||||
let x = 6;
|
||||
let y = 70;
|
||||
let z = 7;
|
||||
|
||||
let dry = run
|
||||
.ctx
|
||||
.top_material(run.chunk, x, y - 1, z, false, false)
|
||||
.unwrap();
|
||||
let under_fluid = run
|
||||
.ctx
|
||||
.top_material(run.chunk, x, y - 1, z, true, false)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(dry.id, Block::GRASS_BLOCK.default_state.id);
|
||||
assert_eq!(under_fluid.id, Block::DIRT.default_state.id);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn steep_matches_vanilla() {
|
||||
with_carve_run(Dimension::OVERWORLD, |run| {
|
||||
let x = 5;
|
||||
let z = 5;
|
||||
|
||||
run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA];
|
||||
set_surface_height(run.chunk, x, z - 1, 60);
|
||||
set_surface_height(run.chunk, x, z + 1, 64);
|
||||
assert!(steep_material_condition(run.chunk, x, z));
|
||||
|
||||
run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA];
|
||||
set_surface_height(run.chunk, x, z - 1, 64);
|
||||
set_surface_height(run.chunk, x, z + 1, 60);
|
||||
assert!(!steep_material_condition(run.chunk, x, z));
|
||||
|
||||
run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA];
|
||||
set_surface_height(run.chunk, x - 1, z, 64);
|
||||
set_surface_height(run.chunk, x + 1, z, 60);
|
||||
assert!(steep_material_condition(run.chunk, x, z));
|
||||
|
||||
run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA];
|
||||
set_surface_height(run.chunk, x - 1, z, 60);
|
||||
set_surface_height(run.chunk, x + 1, z, 64);
|
||||
assert!(!steep_material_condition(run.chunk, x, z));
|
||||
});
|
||||
}
|
||||
|
||||
fn set_surface_height(chunk: &mut ProtoChunk, x: i32, z: i32, height: i16) {
|
||||
let index = (x & 15) as usize * 16 + (z & 15) as usize;
|
||||
chunk.flat_surface_height_map[index] = height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn get_world_gen(seed: Seed, dimension: Dimension) -> Box<VanillaGenerator>
|
||||
pub struct GlobalRandomConfig {
|
||||
pub seed: u64,
|
||||
pub legacy_random_source: bool,
|
||||
base_random_deriver: XoroshiroSplitter,
|
||||
pub base_random_deriver: XoroshiroSplitter,
|
||||
aquifer_random_deriver: XoroshiroSplitter,
|
||||
pub ore_random_deriver: XoroshiroSplitter,
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use pumpkin_data::{Block, BlockState};
|
||||
use pumpkin_data::{Block, BlockState, chunk_gen_settings::GenerationSettings};
|
||||
use pumpkin_util::{
|
||||
math::{clamped_map, floor_div, vector3::Vector3},
|
||||
random::{RandomImpl, xoroshiro128::XoroshiroSplitter},
|
||||
};
|
||||
|
||||
use crate::generation::{
|
||||
GlobalRandomConfig, biome_coords,
|
||||
noise::{
|
||||
LAVA_BLOCK, WATER_BLOCK,
|
||||
CHUNK_DIM, LAVA_BLOCK, WATER_BLOCK,
|
||||
router::{
|
||||
chunk_density_function::ChunkNoiseFunctionSampleOptions,
|
||||
chunk_density_function::{
|
||||
ChunkNoiseFunctionBuilderOptions, ChunkNoiseFunctionSampleOptions, SampleAction,
|
||||
},
|
||||
chunk_noise_router::ChunkNoiseRouter,
|
||||
proto_noise_router::ProtoNoiseRouters,
|
||||
surface_height_sampler::SurfaceHeightEstimateSampler,
|
||||
},
|
||||
},
|
||||
@@ -55,6 +59,107 @@ pub enum AquiferSampler {
|
||||
Aquifer(WorldAquiferSampler),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct CarverAquiferResult {
|
||||
pub state: Option<&'static BlockState>,
|
||||
pub should_schedule_fluid_update: bool,
|
||||
}
|
||||
|
||||
pub struct CarverAquiferSampler<'a> {
|
||||
aquifer: WorldAquiferSampler,
|
||||
router: ChunkNoiseRouter<'a>,
|
||||
height_estimator: SurfaceHeightEstimateSampler<'a>,
|
||||
sample_options: ChunkNoiseFunctionSampleOptions,
|
||||
}
|
||||
|
||||
impl<'a> CarverAquiferSampler<'a> {
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
chunk_x: i32,
|
||||
chunk_z: i32,
|
||||
base_router: &'a ProtoNoiseRouters,
|
||||
random_config: &GlobalRandomConfig,
|
||||
settings: &GenerationSettings,
|
||||
) -> Self {
|
||||
let shape = &settings.shape;
|
||||
let horizontal_cell_count = CHUNK_DIM / shape.horizontal_cell_block_count();
|
||||
let start_x = chunk_pos::start_block_x(chunk_x);
|
||||
let start_z = chunk_pos::start_block_z(chunk_z);
|
||||
let horizontal_biome_end = biome_coords::from_block(
|
||||
horizontal_cell_count as i32 * shape.horizontal_cell_block_count() as i32,
|
||||
);
|
||||
let builder_options = ChunkNoiseFunctionBuilderOptions::new(
|
||||
shape.horizontal_cell_block_count() as usize,
|
||||
shape.vertical_cell_block_count() as usize,
|
||||
floor_div(
|
||||
shape.height as usize,
|
||||
shape.vertical_cell_block_count() as usize,
|
||||
),
|
||||
horizontal_cell_count as usize,
|
||||
biome_coords::from_block(start_x),
|
||||
biome_coords::from_block(start_z),
|
||||
horizontal_biome_end as usize,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
None,
|
||||
);
|
||||
let surface_config =
|
||||
super::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions::new(
|
||||
biome_coords::from_block(start_x),
|
||||
biome_coords::from_block(start_z),
|
||||
horizontal_biome_end as usize,
|
||||
shape.min_y as i32,
|
||||
shape.max_y() as i32,
|
||||
shape.vertical_cell_block_count() as usize,
|
||||
);
|
||||
let fluid_level = StandardChunkFluidLevelSampler::new(
|
||||
FluidLevel::new(
|
||||
settings.sea_level,
|
||||
Block::from_state_id(settings.default_fluid.id),
|
||||
),
|
||||
FluidLevel::new(-54, &Block::LAVA),
|
||||
);
|
||||
|
||||
Self {
|
||||
aquifer: WorldAquiferSampler::new(
|
||||
chunk_x,
|
||||
chunk_z,
|
||||
&random_config.aquifer_random_deriver,
|
||||
shape.min_y,
|
||||
shape.height,
|
||||
fluid_level,
|
||||
),
|
||||
router: ChunkNoiseRouter::generate(&base_router.noise, &builder_options),
|
||||
height_estimator: SurfaceHeightEstimateSampler::generate(
|
||||
&base_router.surface_estimator,
|
||||
&surface_config,
|
||||
),
|
||||
sample_options: ChunkNoiseFunctionSampleOptions::new(
|
||||
false,
|
||||
SampleAction::SkipCellCaches,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute(&mut self, pos: &Vector3<i32>, density: f64) -> CarverAquiferResult {
|
||||
let (state, should_schedule_fluid_update) = self.aquifer.apply_internal(
|
||||
&mut self.router,
|
||||
pos,
|
||||
&self.sample_options,
|
||||
&mut self.height_estimator,
|
||||
density,
|
||||
);
|
||||
|
||||
CarverAquiferResult {
|
||||
state,
|
||||
should_schedule_fluid_update,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! packed_position_index {
|
||||
($local_x:expr,$local_y:expr,$local_z:expr,$dim_y:expr,$dim_z:expr) => {
|
||||
($local_x * $dim_z + $local_z) * $dim_y + $local_y
|
||||
@@ -158,28 +263,35 @@ impl WorldAquiferSampler {
|
||||
}
|
||||
}
|
||||
|
||||
const fn packed_position_index(&self, x: i32, y: i32, z: i32) -> usize {
|
||||
let local_x = (x - self.start_x) as usize;
|
||||
let local_y = (y - self.start_y) as usize;
|
||||
let local_z = (z - self.start_z) as usize;
|
||||
fn checked_packed_position_index(&self, x: i32, y: i32, z: i32) -> Option<usize> {
|
||||
let local_x = usize::try_from(x - self.start_x).ok()?;
|
||||
let local_y = usize::try_from(y - self.start_y).ok()?;
|
||||
let local_z = usize::try_from(z - self.start_z).ok()?;
|
||||
|
||||
packed_position_index!(local_x, local_y, local_z, self.size_y, self.size_z)
|
||||
if local_y >= self.size_y || local_z >= self.size_z {
|
||||
return None;
|
||||
}
|
||||
|
||||
let index = packed_position_index!(local_x, local_y, local_z, self.size_y, self.size_z);
|
||||
(index < self.packed_positions.len()).then_some(index)
|
||||
}
|
||||
|
||||
fn random_positions_for_pos(&self, x: i32, y: i32, z: i32) -> [i64; 12] {
|
||||
fn random_positions_for_pos(&self, x: i32, y: i32, z: i32) -> Option<[i64; 12]> {
|
||||
let sy = self.size_y;
|
||||
let syz = self.size_y * self.size_z;
|
||||
|
||||
let i00 = self.packed_position_index(x, y - 1, z);
|
||||
let i00 = self.checked_packed_position_index(x, y - 1, z)?;
|
||||
let i01 = i00 + sy;
|
||||
let i10 = i00 + syz;
|
||||
let i11 = i10 + sy;
|
||||
|
||||
assert!(i11 + 2 < self.packed_positions.len(), "Index out of bounds");
|
||||
if i11 + 2 >= self.packed_positions.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let p = &self.packed_positions;
|
||||
|
||||
[
|
||||
Some([
|
||||
p[i11 + 2],
|
||||
p[i10 + 2],
|
||||
p[i01 + 2],
|
||||
@@ -192,7 +304,7 @@ impl WorldAquiferSampler {
|
||||
p[i10],
|
||||
p[i01],
|
||||
p[i00],
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -250,7 +362,7 @@ impl WorldAquiferSampler {
|
||||
router: &mut ChunkNoiseRouter,
|
||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||
) -> &FluidLevel {
|
||||
) -> FluidLevel {
|
||||
let x = block_pos::unpack_x(packed_pos);
|
||||
let y = block_pos::unpack_y(packed_pos);
|
||||
let z = block_pos::unpack_z(packed_pos);
|
||||
@@ -259,9 +371,20 @@ impl WorldAquiferSampler {
|
||||
let local_y = local_y!(y);
|
||||
let local_z = local_xz!(z);
|
||||
|
||||
let index = self.packed_position_index(local_x, local_y, local_z);
|
||||
let Some(index) = self.checked_packed_position_index(local_x, local_y, local_z) else {
|
||||
return Self::get_fluid_level(
|
||||
&self.fluid_level_sampler,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
router,
|
||||
height_estimator,
|
||||
sample_options,
|
||||
);
|
||||
};
|
||||
|
||||
if let Some(ref level) = self.levels[index] {
|
||||
return level;
|
||||
return level.clone();
|
||||
}
|
||||
|
||||
let sampled = Self::get_fluid_level(
|
||||
@@ -274,7 +397,8 @@ impl WorldAquiferSampler {
|
||||
sample_options,
|
||||
);
|
||||
|
||||
self.levels[index].insert(sampled)
|
||||
self.levels[index] = Some(sampled.clone());
|
||||
sampled
|
||||
}
|
||||
|
||||
fn get_fluid_level(
|
||||
@@ -450,9 +574,9 @@ impl WorldAquiferSampler {
|
||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||
density: f64,
|
||||
) -> Option<&'static BlockState> {
|
||||
) -> (Option<&'static BlockState>, bool) {
|
||||
if density > 0f64 {
|
||||
return None;
|
||||
return (None, false);
|
||||
}
|
||||
|
||||
let sample_x = pos.x;
|
||||
@@ -463,37 +587,32 @@ impl WorldAquiferSampler {
|
||||
.fluid_level_sampler
|
||||
.get_fluid_level(sample_x, sample_y, sample_z);
|
||||
if fluid_level.get_block(sample_y) == &LAVA_BLOCK {
|
||||
return Some(LAVA_BLOCK.default_state);
|
||||
return (Some(LAVA_BLOCK.default_state), false);
|
||||
}
|
||||
|
||||
let scaled_x = local_xz!(sample_x - 5);
|
||||
let scaled_y = local_y!(sample_y + 1);
|
||||
let scaled_z = local_xz!(sample_z - 5);
|
||||
|
||||
// Inline random_positions_for_pos: read directly from packed_positions with a
|
||||
// single bounds check instead of stack-allocating and copying a [i64; 12].
|
||||
let sy = self.size_y;
|
||||
let syz = sy * self.size_z;
|
||||
let i00 = self.packed_position_index(scaled_x, scaled_y - 1, scaled_z);
|
||||
let i01 = i00 + sy;
|
||||
let i10 = i00 + syz;
|
||||
let i11 = i10 + sy;
|
||||
let Some(random_positions) = self.random_positions_for_pos(scaled_x, scaled_y, scaled_z)
|
||||
else {
|
||||
return (Some(fluid_level.get_block(sample_y).default_state), false);
|
||||
};
|
||||
|
||||
let p = &self.packed_positions;
|
||||
// i11 + 2 is the largest index we ever access; all others are strictly smaller.
|
||||
assert!(i11 + 2 < p.len(), "Index out of bounds");
|
||||
let mut nearest = [(0i64, i32::MAX); 4];
|
||||
|
||||
let mut nearest = [(0i64, i32::MAX); 3];
|
||||
|
||||
// SAFETY: every index passed to this macro is <= i11 + 2, checked by the assert above.
|
||||
macro_rules! process {
|
||||
($idx:expr) => {{
|
||||
let packed = unsafe { *p.get_unchecked($idx) };
|
||||
($packed:expr) => {{
|
||||
let packed = $packed;
|
||||
let dx = block_pos::unpack_x(packed) - sample_x;
|
||||
let dy = block_pos::unpack_y(packed) - sample_y;
|
||||
let dz = block_pos::unpack_z(packed) - sample_z;
|
||||
let h = dx * dx + dy * dy + dz * dz;
|
||||
if nearest[3].1 > h {
|
||||
nearest[3] = (packed, h);
|
||||
}
|
||||
if nearest[2].1 > h {
|
||||
nearest[3] = nearest[2];
|
||||
nearest[2] = (packed, h);
|
||||
}
|
||||
if nearest[1].1 > h {
|
||||
@@ -507,34 +626,26 @@ impl WorldAquiferSampler {
|
||||
}};
|
||||
}
|
||||
|
||||
// Same insertion order as the original array literal — sort behaviour is preserved.
|
||||
process!(i11 + 2);
|
||||
process!(i10 + 2);
|
||||
process!(i01 + 2);
|
||||
process!(i00 + 2);
|
||||
process!(i11 + 1);
|
||||
process!(i10 + 1);
|
||||
process!(i01 + 1);
|
||||
process!(i00 + 1);
|
||||
process!(i11);
|
||||
process!(i10);
|
||||
process!(i01);
|
||||
process!(i00);
|
||||
// Same insertion order as the original array literal; sort behaviour is preserved.
|
||||
for packed in random_positions {
|
||||
process!(packed);
|
||||
}
|
||||
|
||||
// Precompute all three pairwise distances before fetching any water levels so we
|
||||
// can skip the third get_water_level call entirely when neither f nor g_dist > 0.
|
||||
let d = Self::max_distance(nearest[0].1, nearest[1].1);
|
||||
let f = Self::max_distance(nearest[0].1, nearest[2].1);
|
||||
let g_dist = Self::max_distance(nearest[1].1, nearest[2].1);
|
||||
|
||||
let fluid_level2 = self
|
||||
.get_water_level(nearest[0].0, router, height_estimator, sample_options)
|
||||
.clone();
|
||||
let fluid_level2 =
|
||||
self.get_water_level(nearest[0].0, router, height_estimator, sample_options);
|
||||
let block_state = fluid_level2.get_block(sample_y);
|
||||
let sim12 = Self::max_distance(nearest[0].1, nearest[1].1);
|
||||
|
||||
if d <= 0f64 {
|
||||
// TODO: Handle fluid tick
|
||||
return Some(block_state.default_state);
|
||||
if sim12 <= 0f64 {
|
||||
let should_schedule = if sim12 >= -0.12f64 {
|
||||
// FLOWING_UPDATE_SIMILARITY
|
||||
let fluid_level3 =
|
||||
self.get_water_level(nearest[1].0, router, height_estimator, sample_options);
|
||||
fluid_level2.block != fluid_level3.block || fluid_level2.max_y != fluid_level3.max_y
|
||||
} else {
|
||||
false
|
||||
};
|
||||
return (Some(block_state.default_state), should_schedule);
|
||||
}
|
||||
|
||||
if block_state == &WATER_BLOCK
|
||||
@@ -544,67 +655,83 @@ impl WorldAquiferSampler {
|
||||
.get_block(sample_y - 1)
|
||||
== &LAVA_BLOCK
|
||||
{
|
||||
return Some(block_state.default_state);
|
||||
return (Some(block_state.default_state), true);
|
||||
}
|
||||
|
||||
let mut barrier_sample = None;
|
||||
let fluid_level3 = self
|
||||
.get_water_level(nearest[1].0, router, height_estimator, sample_options)
|
||||
.clone();
|
||||
let e = d * Self::calculate_density(
|
||||
&mut barrier_sample,
|
||||
pos,
|
||||
router,
|
||||
sample_options,
|
||||
&fluid_level2,
|
||||
&fluid_level3,
|
||||
);
|
||||
let fluid_level3 =
|
||||
self.get_water_level(nearest[1].0, router, height_estimator, sample_options);
|
||||
let barrier12 = sim12
|
||||
* Self::calculate_density(
|
||||
&mut barrier_sample,
|
||||
pos,
|
||||
router,
|
||||
sample_options,
|
||||
&fluid_level2,
|
||||
&fluid_level3,
|
||||
);
|
||||
|
||||
if density + e > 0f64 {
|
||||
return None;
|
||||
if density + barrier12 > 0f64 {
|
||||
return (None, false);
|
||||
}
|
||||
|
||||
// Only pay for the cache/noise lookup when at least one distance weight is positive;
|
||||
// when both are <= 0 the third centre cannot affect the result.
|
||||
if f > 0f64 || g_dist > 0f64 {
|
||||
let fluid_level4 =
|
||||
self.get_water_level(nearest[2].0, router, height_estimator, sample_options);
|
||||
|
||||
if f > 0f64 {
|
||||
let contrib = d
|
||||
* f
|
||||
* Self::calculate_density(
|
||||
&mut barrier_sample,
|
||||
pos,
|
||||
router,
|
||||
sample_options,
|
||||
&fluid_level2,
|
||||
fluid_level4,
|
||||
);
|
||||
if density + contrib > 0f64 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if g_dist > 0f64 {
|
||||
let contrib = d
|
||||
* g_dist
|
||||
* Self::calculate_density(
|
||||
&mut barrier_sample,
|
||||
pos,
|
||||
router,
|
||||
sample_options,
|
||||
&fluid_level3,
|
||||
fluid_level4,
|
||||
);
|
||||
if density + contrib > 0f64 {
|
||||
return None;
|
||||
}
|
||||
let fluid_level4 =
|
||||
self.get_water_level(nearest[2].0, router, height_estimator, sample_options);
|
||||
let sim13 = Self::max_distance(nearest[0].1, nearest[2].1);
|
||||
if sim13 > 0f64 {
|
||||
let barrier13 = sim12
|
||||
* sim13
|
||||
* Self::calculate_density(
|
||||
&mut barrier_sample,
|
||||
pos,
|
||||
router,
|
||||
sample_options,
|
||||
&fluid_level2,
|
||||
&fluid_level4,
|
||||
);
|
||||
if density + barrier13 > 0f64 {
|
||||
return (None, false);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle fluid tick
|
||||
Some(block_state.default_state)
|
||||
let sim23 = Self::max_distance(nearest[1].1, nearest[2].1);
|
||||
if sim23 > 0f64 {
|
||||
let barrier23 = sim12
|
||||
* sim23
|
||||
* Self::calculate_density(
|
||||
&mut barrier_sample,
|
||||
pos,
|
||||
router,
|
||||
sample_options,
|
||||
&fluid_level3,
|
||||
&fluid_level4,
|
||||
);
|
||||
if density + barrier23 > 0f64 {
|
||||
return (None, false);
|
||||
}
|
||||
}
|
||||
|
||||
let may_flow12 =
|
||||
fluid_level2.block != fluid_level3.block || fluid_level2.max_y != fluid_level3.max_y;
|
||||
let may_flow23 = sim23 >= -0.12f64
|
||||
&& (fluid_level3.block != fluid_level4.block
|
||||
|| fluid_level3.max_y != fluid_level4.max_y);
|
||||
let may_flow13 = sim13 >= -0.12f64
|
||||
&& (fluid_level2.block != fluid_level4.block
|
||||
|| fluid_level2.max_y != fluid_level4.max_y);
|
||||
|
||||
let should_schedule = if may_flow12 || may_flow23 || may_flow13 {
|
||||
true
|
||||
} else {
|
||||
let fluid_level5 =
|
||||
self.get_water_level(nearest[3].0, router, height_estimator, sample_options);
|
||||
sim13 >= -0.12f64
|
||||
&& Self::max_distance(nearest[0].1, nearest[3].1) >= -0.12f64
|
||||
&& (fluid_level2.block != fluid_level5.block
|
||||
|| fluid_level2.max_y != fluid_level5.max_y)
|
||||
};
|
||||
|
||||
(Some(block_state.default_state), should_schedule)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,7 +743,7 @@ impl AquiferSamplerImpl for WorldAquiferSampler {
|
||||
pos: &Vector3<i32>,
|
||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||
) -> Option<&'static BlockState> {
|
||||
) -> (Option<&'static BlockState>, bool) {
|
||||
let density = router.final_density(pos, sample_options);
|
||||
self.apply_internal(router, pos, sample_options, height_estimator, density)
|
||||
}
|
||||
@@ -640,17 +767,19 @@ impl AquiferSamplerImpl for SeaLevelAquiferSampler {
|
||||
pos: &Vector3<i32>,
|
||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||
_height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||
) -> Option<&'static BlockState> {
|
||||
) -> (Option<&'static BlockState>, bool) {
|
||||
let sample = router.final_density(pos, sample_options);
|
||||
//log::debug!("Aquifer sample {:?}: {}", &pos, sample);
|
||||
if sample > 0f64 {
|
||||
None
|
||||
(None, false)
|
||||
} else {
|
||||
Some(
|
||||
self.level_sampler
|
||||
.get_fluid_level(pos.x, pos.y, pos.z)
|
||||
.get_block(pos.y)
|
||||
.default_state,
|
||||
(
|
||||
Some(
|
||||
self.level_sampler
|
||||
.get_fluid_level(pos.x, pos.y, pos.z)
|
||||
.get_block(pos.y)
|
||||
.default_state,
|
||||
),
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -664,7 +793,7 @@ pub trait AquiferSamplerImpl {
|
||||
pos: &Vector3<i32>,
|
||||
sample_options: &ChunkNoiseFunctionSampleOptions,
|
||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||
) -> Option<&'static BlockState>;
|
||||
) -> (Option<&'static BlockState>, bool);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -694,7 +823,7 @@ mod random_positions_and_hypot {
|
||||
proto_chunk::StandardChunkFluidLevelSampler,
|
||||
};
|
||||
|
||||
use super::{AquiferSampler, FluidLevel, WorldAquiferSampler};
|
||||
use super::{AquiferSampler, CarverAquiferSampler, FluidLevel, WorldAquiferSampler};
|
||||
|
||||
const SEED: u64 = 0;
|
||||
static RANDOM_CONFIG: LazyLock<GlobalRandomConfig> =
|
||||
@@ -773,6 +902,57 @@ mod random_positions_and_hypot {
|
||||
(aquifer, noise.router, height_estimator, options)
|
||||
}
|
||||
|
||||
fn create_carver_aquifer() -> CarverAquiferSampler<'static> {
|
||||
let settings = GenerationSettings::from_dimension(&Dimension::OVERWORLD);
|
||||
CarverAquiferSampler::new(7, 4, &PROTO_ROUTER, &RANDOM_CONFIG, settings)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carver_aquifer_returns_stable_output() {
|
||||
let pos = Vector3::new(112, 0, 64);
|
||||
let mut first = create_carver_aquifer();
|
||||
let mut second = create_carver_aquifer();
|
||||
|
||||
assert_eq!(first.compute(&pos, -1.0), second.compute(&pos, -1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carver_aquifer_handles_chunk_edges() {
|
||||
let mut aquifer = create_carver_aquifer();
|
||||
let positions = [
|
||||
Vector3::new(112, -64, 64),
|
||||
Vector3::new(127, -64, 79),
|
||||
Vector3::new(112, 319, 79),
|
||||
Vector3::new(127, 319, 64),
|
||||
];
|
||||
|
||||
for pos in positions {
|
||||
let _ = aquifer.compute(&pos, -1.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carver_aquifer_reports_fluid_schedule_signal() {
|
||||
let mut aquifer = create_carver_aquifer();
|
||||
let mut found_schedule = false;
|
||||
|
||||
'positions: for y in -64..=63 {
|
||||
for x in 112..=127 {
|
||||
for z in 64..=79 {
|
||||
if aquifer
|
||||
.compute(&Vector3::new(x, y, z), -1.0)
|
||||
.should_schedule_fluid_update
|
||||
{
|
||||
found_schedule = true;
|
||||
break 'positions;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(found_schedule);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[expect(clippy::too_many_lines, clippy::large_stack_arrays)]
|
||||
fn get_fluid_block_state() {
|
||||
@@ -2420,7 +2600,9 @@ mod random_positions_and_hypot {
|
||||
for ((x, y, z, sample), result) in values {
|
||||
let pos = Vector3::new(x, y, z);
|
||||
assert_eq!(
|
||||
aquifer.apply_internal(&mut router, &pos, &env, &mut height_estimator, sample),
|
||||
aquifer
|
||||
.apply_internal(&mut router, &pos, &env, &mut height_estimator, sample)
|
||||
.0,
|
||||
result.map(pumpkin_data::BlockStateId::to_state)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,11 @@ impl BlockStateSampler {
|
||||
height_estimator: &mut SurfaceHeightEstimateSampler,
|
||||
) -> Option<&'static BlockState> {
|
||||
match self {
|
||||
Self::Aquifer(aquifer) => aquifer.apply(router, pos, sample_options, height_estimator),
|
||||
Self::Aquifer(aquifer) => {
|
||||
aquifer
|
||||
.apply(router, pos, sample_options, height_estimator)
|
||||
.0
|
||||
}
|
||||
Self::Ore(ore) => ore.sample(router, ore_random_deriver, pos, sample_options),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ use crate::{
|
||||
use pumpkin_data::tag::get_tag_ids;
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
|
||||
use crate::tick::{ScheduledTick, TickPriority};
|
||||
|
||||
enum ActiveSupplier {
|
||||
Overworld(MultiNoiseBiomeSupplier),
|
||||
Nether(MultiNoiseBiomeSupplier),
|
||||
@@ -142,6 +144,7 @@ pub struct ProtoChunk {
|
||||
pub carving_mask: crate::generation::carver::mask::CarvingMask,
|
||||
pub blending_data: Option<crate::generation::blender::blending_data::BlendingData>,
|
||||
pub pending_block_entities: Vec<NbtCompound>,
|
||||
pub fluid_ticks: Vec<ScheduledTick<&'static Fluid>>,
|
||||
}
|
||||
|
||||
pub struct TerrainCache {
|
||||
@@ -215,6 +218,7 @@ impl ProtoChunk {
|
||||
),
|
||||
blending_data: None,
|
||||
pending_block_entities: Vec::new(),
|
||||
fluid_ticks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +332,15 @@ impl ProtoChunk {
|
||||
std::mem::take(&mut self.pending_block_entities)
|
||||
}
|
||||
|
||||
pub fn schedule_fluid_tick(&mut self, x: i32, y: i32, z: i32, fluid: &'static Fluid) {
|
||||
self.fluid_ticks.push(ScheduledTick {
|
||||
delay: 0,
|
||||
priority: TickPriority::Normal,
|
||||
position: BlockPos::new(x, y, z),
|
||||
value: fluid,
|
||||
});
|
||||
}
|
||||
|
||||
fn maybe_update_surface_height_map(&mut self, index: usize, y: i16) {
|
||||
let current_height = self.flat_surface_height_map[index];
|
||||
self.flat_surface_height_map[index] = current_height.max(y) as _;
|
||||
@@ -834,6 +847,9 @@ impl ProtoChunk {
|
||||
|
||||
pub fn spawn_mobs<T: GenerationCache>(cache: &mut T, block_registry: &dyn WorldPortalExt) {
|
||||
let chunk = cache.get_center_chunk();
|
||||
if chunk.stage >= StagedChunkEnum::Spawn {
|
||||
return;
|
||||
}
|
||||
debug_assert_eq!(chunk.stage, StagedChunkEnum::Lighting);
|
||||
|
||||
let biome = chunk.get_terrain_gen_biome(
|
||||
|
||||
@@ -52,6 +52,7 @@ pub struct MaterialRuleContext<'a> {
|
||||
pub stone_depth_above: i32,
|
||||
pub terrain_builder: &'a SurfaceTerrainBuilder,
|
||||
pub sea_level: i32,
|
||||
steep_material_condition: Option<bool>,
|
||||
}
|
||||
|
||||
impl<'a> MaterialRuleContext<'a> {
|
||||
@@ -88,6 +89,7 @@ impl<'a> MaterialRuleContext<'a> {
|
||||
stone_depth_below: 0,
|
||||
stone_depth_above: 0,
|
||||
sea_level,
|
||||
steep_material_condition: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,9 +135,12 @@ impl<'a> MaterialRuleContext<'a> {
|
||||
}
|
||||
self.secondary_depth
|
||||
}
|
||||
|
||||
pub const fn set_steep_material_condition(&mut self, steep: bool) {
|
||||
self.steep_material_condition = Some(steep);
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
pub fn test_condition(
|
||||
condition: &MaterialCondition,
|
||||
chunk: &mut ProtoChunk,
|
||||
@@ -161,28 +166,9 @@ pub fn test_condition(
|
||||
);
|
||||
temperature < 0.15f32
|
||||
}
|
||||
MaterialCondition::Steep => {
|
||||
let local_x = context.block_pos_x & 15;
|
||||
let local_z = context.block_pos_z & 15;
|
||||
|
||||
let local_z_sub = 0.max(local_z - 1);
|
||||
let local_z_add = 15.min(local_z + 1);
|
||||
|
||||
let sub_height = chunk.top_block_height_exclusive(local_x, local_z_sub);
|
||||
let add_height = chunk.top_block_height_exclusive(local_x, local_z_add);
|
||||
|
||||
if add_height >= sub_height + 4 {
|
||||
true
|
||||
} else {
|
||||
let local_x_sub = 0.max(local_x - 1);
|
||||
let local_x_add = 15.min(local_x + 1);
|
||||
|
||||
let sub_height = chunk.top_block_height_exclusive(local_x_sub, local_z);
|
||||
let add_height = chunk.top_block_height_exclusive(local_x_add, local_z);
|
||||
|
||||
sub_height >= add_height + 4
|
||||
}
|
||||
}
|
||||
MaterialCondition::Steep => context.steep_material_condition.unwrap_or_else(|| {
|
||||
steep_material_condition(chunk, context.block_pos_x, context.block_pos_z)
|
||||
}),
|
||||
MaterialCondition::Not(not) => {
|
||||
test_not_material(not, chunk, context, surface_height_estimate_sampler)
|
||||
}
|
||||
@@ -194,6 +180,30 @@ pub fn test_condition(
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn steep_material_condition(chunk: &ProtoChunk, block_x: i32, block_z: i32) -> bool {
|
||||
let local_x = block_x & 15;
|
||||
let local_z = block_z & 15;
|
||||
|
||||
let local_z_sub = 0.max(local_z - 1);
|
||||
let local_z_add = 15.min(local_z + 1);
|
||||
|
||||
let sub_height = chunk.top_block_height_exclusive(local_x, local_z_sub);
|
||||
let add_height = chunk.top_block_height_exclusive(local_x, local_z_add);
|
||||
|
||||
if add_height >= sub_height + 4 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let local_x_sub = 0.max(local_x - 1);
|
||||
let local_x_add = 15.min(local_x + 1);
|
||||
|
||||
let sub_height = chunk.top_block_height_exclusive(local_x_sub, local_z);
|
||||
let add_height = chunk.top_block_height_exclusive(local_x_add, local_z);
|
||||
|
||||
sub_height >= add_height + 4
|
||||
}
|
||||
|
||||
pub struct HoleMaterialCondition;
|
||||
|
||||
impl HoleMaterialCondition {
|
||||
|
||||
@@ -139,7 +139,6 @@ mod test {
|
||||
use pumpkin_data::game_rules::GameRuleRegistry;
|
||||
use pumpkin_nbt::{deserializer::from_bytes, serializer::to_bytes};
|
||||
use pumpkin_util::{Difficulty, world_seed::Seed};
|
||||
use std::assert_matches;
|
||||
use std::{
|
||||
fs,
|
||||
io::{Cursor, Read},
|
||||
@@ -283,6 +282,9 @@ mod test {
|
||||
.unwrap();
|
||||
|
||||
let result = AnvilLevelInfo.read_world_info(temp_dir.path());
|
||||
assert_matches!(result, Err(WorldInfoError::UnsupportedDataVersion(_)));
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(WorldInfoError::UnsupportedDataVersion(_))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2579,11 +2579,6 @@ impl World {
|
||||
client_suggestions::send_c_commands_packet(player, server, &command_dispatcher).await;
|
||||
};
|
||||
|
||||
// Spawn in initial chunks
|
||||
// This is made before the player teleport so that the player doesn't glitch out when spawning
|
||||
chunker::update_position(player).await;
|
||||
|
||||
// Teleport
|
||||
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
|
||||
let position = player.position();
|
||||
let yaw = player.get_entity().yaw.load(); //info.spawn_angle;
|
||||
@@ -2605,13 +2600,26 @@ impl World {
|
||||
(position, info.spawn_yaw, info.spawn_pitch)
|
||||
};
|
||||
|
||||
let velocity = player.get_entity().velocity.load();
|
||||
// Load chunks around the real spawn position before teleporting the client there.
|
||||
player.living_entity.entity.set_pos(position);
|
||||
player.living_entity.entity.set_rotation(yaw, pitch);
|
||||
player.living_entity.entity.last_pos.store(position);
|
||||
chunker::update_position(player).await;
|
||||
|
||||
let center_chunk = player.living_entity.entity.chunk_pos.load();
|
||||
let chunk = self
|
||||
.level
|
||||
.get_or_fetch_chunk(center_chunk, std::clone::Clone::clone)
|
||||
.await;
|
||||
client.send_packet_now(&CChunkBatchStart).await;
|
||||
client.send_packet_now(&CChunkData(&chunk)).await;
|
||||
client.send_packet_now(&CChunkBatchEnd::new(1u16)).await;
|
||||
|
||||
let velocity = player.living_entity.entity.velocity.load();
|
||||
|
||||
debug!("Sending player teleport to {}", player.gameprofile.name);
|
||||
player.request_teleport(position, yaw, pitch).await;
|
||||
|
||||
player.get_entity().last_pos.store(position);
|
||||
|
||||
let gameprofile = &player.gameprofile;
|
||||
let bedrock_player_list = CPlayerList {
|
||||
action: CPlayerList::ACTION_ADD,
|
||||
|
||||
Reference in New Issue
Block a user