diff --git a/pumpkin-codegen/src/carver.rs b/pumpkin-codegen/src/carver.rs new file mode 100644 index 000000000..15442d3b9 --- /dev/null +++ b/pumpkin-codegen/src/carver.rs @@ -0,0 +1,277 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use serde_json::Value; +use std::collections::BTreeMap; +use std::fs; + +pub fn build() -> TokenStream { + let carvers: BTreeMap = + serde_json::from_str(&fs::read_to_string("../assets/carver.json").unwrap()) + .expect("Failed to parse carver.json"); + + let mut carver_instances = Vec::new(); + + for (name, data) in carvers { + let variant_name = format_ident!("{}", name.to_uppercase()); + let config = &data["config"]; + let carver_type = data["type"].as_str().unwrap_or(""); + + let prob = config["probability"].as_f64().unwrap_or(0.0) as f32; + let y = value_to_height_provider(&config["y"]); + let y_scale = value_to_float_provider(&config["yScale"]); + let lava_level = value_to_y_offset(&config["lava_level"]); + + let replaceable_str = config["replaceable"].as_str().unwrap_or(""); + let replaceable = if replaceable_str.starts_with('#') { + let tag_name = replaceable_str[1..] + .to_uppercase() + .replace(':', "_") + .replace('.', "_") + .replace('-', "_"); + let tag_ident = format_ident!("{}", tag_name); + quote! { crate::tag::Block::#tag_ident } + } else { + // Handle non-tag replaceables if any + quote! { crate::tag::Block::MINECRAFT_OVERWORLD_CARVER_REPLACEABLES } + }; + + let additional = match carver_type { + "minecraft:cave" => { + let horizontal_radius_multiplier = + value_to_float_provider(&config["horizontal_radius_multiplier"]); + let vertical_radius_multiplier = + value_to_float_provider(&config["vertical_radius_multiplier"]); + let floor_level = value_to_float_provider(&config["floor_level"]); + quote! { + CarverAdditionalConfig::Cave(CaveCarverConfig { + horizontal_radius_multiplier: #horizontal_radius_multiplier, + vertical_radius_multiplier: #vertical_radius_multiplier, + floor_level: #floor_level, + }) + } + } + "minecraft:nether_cave" => { + let horizontal_radius_multiplier = + value_to_float_provider(&config["horizontal_radius_multiplier"]); + let vertical_radius_multiplier = + value_to_float_provider(&config["vertical_radius_multiplier"]); + let floor_level = value_to_float_provider(&config["floor_level"]); + quote! { + CarverAdditionalConfig::NetherCave(CaveCarverConfig { + horizontal_radius_multiplier: #horizontal_radius_multiplier, + vertical_radius_multiplier: #vertical_radius_multiplier, + floor_level: #floor_level, + }) + } + } + "minecraft:canyon" => { + let vertical_rotation = value_to_float_provider(&config["vertical_rotation"]); + let shape = &config["shape"]; + let distance_factor = value_to_float_provider(&shape["distance_factor"]); + let thickness = value_to_float_provider(&shape["thickness"]); + let width_smoothness = shape["width_smoothness"].as_i64().unwrap_or(0) as i32; + let horizontal_radius_factor = + value_to_float_provider(&shape["horizontal_radius_factor"]); + let vertical_radius_default_factor = + shape["vertical_radius_default_factor"].as_f64().unwrap_or(0.0) as f32; + let vertical_radius_center_factor = + shape["vertical_radius_center_factor"].as_f64().unwrap_or(0.0) as f32; + + quote! { + CarverAdditionalConfig::Canyon(CanyonCarverConfig { + vertical_rotation: #vertical_rotation, + shape: CanyonShapeConfig { + distance_factor: #distance_factor, + thickness: #thickness, + width_smoothness: #width_smoothness, + horizontal_radius_factor: #horizontal_radius_factor, + vertical_radius_default_factor: #vertical_radius_default_factor, + vertical_radius_center_factor: #vertical_radius_center_factor, + } + }) + } + } + _ => quote! { CarverAdditionalConfig::Cave(CaveCarverConfig::default()) }, + }; + + carver_instances.push(quote! { + pub const #variant_name: CarverConfig = CarverConfig { + probability: #prob, + y: #y, + y_scale: #y_scale, + lava_level: #lava_level, + replaceable: #replaceable, + additional: #additional, + }; + }); + } + + quote! { + use pumpkin_util::math::float_provider::{FloatProvider, NormalFloatProvider, ConstantFloatProvider, UniformFloatProvider, TrapezoidFloatProvider, ClampedNormalFloatProvider}; + use pumpkin_util::y_offset::{YOffset, Absolute, AboveBottom, BelowTop}; + + pub enum HeightProvider { + Uniform(UniformHeightProvider), + Trapezoid(TrapezoidHeightProvider), + VeryBiasedToBottom(VeryBiasedToBottomHeightProvider), + } + + pub struct UniformHeightProvider { + pub min_inclusive: YOffset, + pub max_inclusive: YOffset, + } + + pub struct TrapezoidHeightProvider { + pub min_inclusive: YOffset, + pub max_inclusive: YOffset, + pub plateau: Option, + } + + pub struct VeryBiasedToBottomHeightProvider { + pub min_inclusive: YOffset, + pub max_inclusive: YOffset, + pub inner: Option, + } + + pub struct CaveCarverConfig { + pub horizontal_radius_multiplier: FloatProvider, + pub vertical_radius_multiplier: FloatProvider, + pub floor_level: FloatProvider, + } + + impl CaveCarverConfig { + pub const fn default() -> Self { + Self { + horizontal_radius_multiplier: FloatProvider::Constant(1.0), + vertical_radius_multiplier: FloatProvider::Constant(1.0), + floor_level: FloatProvider::Constant(-0.7), + } + } + } + + pub struct CanyonShapeConfig { + pub distance_factor: FloatProvider, + pub thickness: FloatProvider, + pub width_smoothness: i32, + pub horizontal_radius_factor: FloatProvider, + pub vertical_radius_default_factor: f32, + pub vertical_radius_center_factor: f32, + } + + pub struct CanyonCarverConfig { + pub vertical_rotation: FloatProvider, + pub shape: CanyonShapeConfig, + } + + pub enum CarverAdditionalConfig { + Cave(CaveCarverConfig), + NetherCave(CaveCarverConfig), + Canyon(CanyonCarverConfig), + } + + pub struct CarverConfig { + pub probability: f32, + pub y: HeightProvider, + pub y_scale: FloatProvider, + pub lava_level: YOffset, + pub replaceable: crate::tag::Tag, + pub additional: CarverAdditionalConfig, + } + + use super::*; + #(#carver_instances)* + } +} + +fn value_to_float_provider(v: &Value) -> TokenStream { + if v.is_number() { + let f = v.as_f64().unwrap_or(0.0) as f32; + quote! { FloatProvider::Constant(#f) } + } else { + // This is complex because we need to match the structure of FloatProvider in pumpkin-util + // For now, let's handle the common case of uniform + let type_str = v["type"].as_str().unwrap_or(""); + match type_str { + "minecraft:uniform" => { + let min = v["min_inclusive"].as_f64().unwrap_or(0.0) as f32; + let max = v["max_exclusive"].as_f64().unwrap_or(0.0) as f32; + quote! { FloatProvider::Object(NormalFloatProvider::Uniform(UniformFloatProvider::new(#min, #max))) } + } + "minecraft:constant" => { + let val = v["value"].as_f64().unwrap_or(0.0) as f32; + quote! { FloatProvider::Constant(#val) } + } + "minecraft:trapezoid" => { + let min = v["min"].as_f64().unwrap_or(0.0) as f32; + let max = v["max"].as_f64().unwrap_or(0.0) as f32; + let plateau = v["plateau"].as_f64().unwrap_or(0.0) as f32; + quote! { FloatProvider::Object(NormalFloatProvider::Trapezoid(TrapezoidFloatProvider::new(#min, #max, #plateau))) } + } + _ => { + // Fallback to constant 0 if unknown + quote! { FloatProvider::Constant(0.0) } + } + } + } +} + +fn value_to_height_provider(v: &Value) -> TokenStream { + let type_str = v["type"].as_str().unwrap_or(""); + match type_str { + "minecraft:uniform" => { + let min = value_to_y_offset(&v["min_inclusive"]); + let max = value_to_y_offset(&v["max_inclusive"]); + quote! { + HeightProvider::Uniform(UniformHeightProvider { + min_inclusive: #min, + max_inclusive: #max, + }) + } + } + "minecraft:trapezoid" => { + let min = value_to_y_offset(&v["min_inclusive"]); + let max = value_to_y_offset(&v["max_inclusive"]); + let plateau = if v["plateau"].is_number() { + let p = v["plateau"].as_i64().unwrap_or(0) as i32; + quote! { Some(#p) } + } else { + quote! { None } + }; + quote! { + HeightProvider::Trapezoid(TrapezoidHeightProvider { + min_inclusive: #min, + max_inclusive: #max, + plateau: #plateau, + }) + } + } + _ => { + let zero = value_to_y_offset(&Value::Null); + quote! { + HeightProvider::Uniform(UniformHeightProvider { + min_inclusive: #zero, + max_inclusive: #zero, + }) + } + } + } +} + +fn value_to_y_offset(v: &Value) -> TokenStream { + if v.is_object() { + if let Some(abs) = v.get("absolute") { + let val = abs.as_i64().unwrap_or(0) as i16; + quote! { YOffset::Absolute(Absolute { absolute: #val }) } + } else if let Some(ab) = v.get("above_bottom") { + let val = ab.as_i64().unwrap_or(0) as i8; + quote! { YOffset::AboveBottom(AboveBottom { above_bottom: #val }) } + } else if let Some(bt) = v.get("below_top") { + let val = bt.as_i64().unwrap_or(0) as i8; + quote! { YOffset::BelowTop(BelowTop { below_top: #val }) } + } else { + quote! { YOffset::Absolute(Absolute { absolute: 0 }) } + } + } else { + quote! { YOffset::Absolute(Absolute { absolute: 0 }) } + } +} diff --git a/pumpkin-codegen/src/main.rs b/pumpkin-codegen/src/main.rs index d1bbb2bfc..2f835bbae 100644 --- a/pumpkin-codegen/src/main.rs +++ b/pumpkin-codegen/src/main.rs @@ -13,6 +13,7 @@ mod attributes; mod biome; mod bitsets; mod block; +mod carver; mod chunk_gen_settings; mod chunk_status; mod composter_increase_chance; @@ -122,6 +123,7 @@ pub fn main() { configured_feature::build, "configured_features_generated.rs", ), + (carver::build, "carver.rs"), ]; build_functions.extend(remap::build()); diff --git a/pumpkin-data/Cargo.toml b/pumpkin-data/Cargo.toml index 9218989ec..4442392fb 100644 --- a/pumpkin-data/Cargo.toml +++ b/pumpkin-data/Cargo.toml @@ -41,6 +41,7 @@ default = [ "potion", "recipe_remainder", "chunk_gen_settings", + "carver", ] item = [] @@ -76,6 +77,7 @@ structures = [] potion = ["potion_brewing"] recipe_remainder = [] chunk_gen_settings = [] +carver = [] noise_parameter = [] biome = [] diff --git a/pumpkin-data/src/generated/carver.rs b/pumpkin-data/src/generated/carver.rs new file mode 100644 index 000000000..7333947a1 --- /dev/null +++ b/pumpkin-data/src/generated/carver.rs @@ -0,0 +1,155 @@ +/* This file is generated. Do not edit manually. */ +use pumpkin_util::math::float_provider::{ + ClampedNormalFloatProvider, ConstantFloatProvider, FloatProvider, NormalFloatProvider, + TrapezoidFloatProvider, UniformFloatProvider, +}; +use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset}; +pub enum HeightProvider { + Uniform(UniformHeightProvider), + Trapezoid(TrapezoidHeightProvider), + VeryBiasedToBottom(VeryBiasedToBottomHeightProvider), +} +pub struct UniformHeightProvider { + pub min_inclusive: YOffset, + pub max_inclusive: YOffset, +} +pub struct TrapezoidHeightProvider { + pub min_inclusive: YOffset, + pub max_inclusive: YOffset, + pub plateau: Option, +} +pub struct VeryBiasedToBottomHeightProvider { + pub min_inclusive: YOffset, + pub max_inclusive: YOffset, + pub inner: Option, +} +pub struct CaveCarverConfig { + pub horizontal_radius_multiplier: FloatProvider, + pub vertical_radius_multiplier: FloatProvider, + pub floor_level: FloatProvider, +} +impl CaveCarverConfig { + pub const fn default() -> Self { + Self { + horizontal_radius_multiplier: FloatProvider::Constant(1.0), + vertical_radius_multiplier: FloatProvider::Constant(1.0), + floor_level: FloatProvider::Constant(-0.7), + } + } +} +pub struct CanyonShapeConfig { + pub distance_factor: FloatProvider, + pub thickness: FloatProvider, + pub width_smoothness: i32, + pub horizontal_radius_factor: FloatProvider, + pub vertical_radius_default_factor: f32, + pub vertical_radius_center_factor: f32, +} +pub struct CanyonCarverConfig { + pub vertical_rotation: FloatProvider, + pub shape: CanyonShapeConfig, +} +pub enum CarverAdditionalConfig { + Cave(CaveCarverConfig), + NetherCave(CaveCarverConfig), + Canyon(CanyonCarverConfig), +} +pub struct CarverConfig { + pub probability: f32, + pub y: HeightProvider, + pub y_scale: FloatProvider, + pub lava_level: YOffset, + pub replaceable: crate::tag::Tag, + pub additional: CarverAdditionalConfig, +} + use super::*; + pub const CANYON: CarverConfig = CarverConfig { + probability: 0.01f32, + y: HeightProvider::Uniform(UniformHeightProvider { + min_inclusive: YOffset::Absolute(Absolute { absolute: 10i16 }), + max_inclusive: YOffset::Absolute(Absolute { absolute: 67i16 }), + }), + y_scale: FloatProvider::Constant(3f32), + lava_level: YOffset::AboveBottom(AboveBottom { above_bottom: 8i8 }), + replaceable: crate::tag::Block::MINECRAFT_OVERWORLD_CARVER_REPLACEABLES, + additional: CarverAdditionalConfig::Canyon(CanyonCarverConfig { + vertical_rotation: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(-0.125f32, 0.125f32), + )), + shape: CanyonShapeConfig { + distance_factor: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(0.75f32, 1f32), + )), + thickness: FloatProvider::Object(NormalFloatProvider::Trapezoid( + TrapezoidFloatProvider::new(0f32, 6f32, 2f32), + )), + width_smoothness: 3i32, + horizontal_radius_factor: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(0.75f32, 1f32), + )), + vertical_radius_default_factor: 1f32, + vertical_radius_center_factor: 0f32, + }, + }), + }; + pub const CAVE: CarverConfig = CarverConfig { + probability: 0.15f32, + y: HeightProvider::Uniform(UniformHeightProvider { + min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 8i8 }), + max_inclusive: YOffset::Absolute(Absolute { absolute: 180i16 }), + }), + y_scale: FloatProvider::Object(NormalFloatProvider::Uniform(UniformFloatProvider::new( + 0.1f32, 0.9f32, + ))), + lava_level: YOffset::AboveBottom(AboveBottom { above_bottom: 8i8 }), + replaceable: crate::tag::Block::MINECRAFT_OVERWORLD_CARVER_REPLACEABLES, + additional: CarverAdditionalConfig::Cave(CaveCarverConfig { + horizontal_radius_multiplier: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(0.7f32, 1.4f32), + )), + vertical_radius_multiplier: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(0.8f32, 1.3f32), + )), + floor_level: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(-1f32, -0.4f32), + )), + }), + }; + pub const CAVE_EXTRA_UNDERGROUND: CarverConfig = CarverConfig { + probability: 0.07f32, + y: HeightProvider::Uniform(UniformHeightProvider { + min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 8i8 }), + max_inclusive: YOffset::Absolute(Absolute { absolute: 47i16 }), + }), + y_scale: FloatProvider::Object(NormalFloatProvider::Uniform(UniformFloatProvider::new( + 0.1f32, 0.9f32, + ))), + lava_level: YOffset::AboveBottom(AboveBottom { above_bottom: 8i8 }), + replaceable: crate::tag::Block::MINECRAFT_OVERWORLD_CARVER_REPLACEABLES, + additional: CarverAdditionalConfig::Cave(CaveCarverConfig { + horizontal_radius_multiplier: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(0.7f32, 1.4f32), + )), + vertical_radius_multiplier: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(0.8f32, 1.3f32), + )), + floor_level: FloatProvider::Object(NormalFloatProvider::Uniform( + UniformFloatProvider::new(-1f32, -0.4f32), + )), + }), + }; + pub const NETHER_CAVE: CarverConfig = CarverConfig { + probability: 0.2f32, + y: HeightProvider::Uniform(UniformHeightProvider { + min_inclusive: YOffset::Absolute(Absolute { absolute: 0i16 }), + max_inclusive: YOffset::BelowTop(BelowTop { below_top: 1i8 }), + }), + y_scale: FloatProvider::Constant(0.5f32), + lava_level: YOffset::AboveBottom(AboveBottom { above_bottom: 10i8 }), + replaceable: crate::tag::Block::MINECRAFT_NETHER_CARVER_REPLACEABLES, + additional: CarverAdditionalConfig::NetherCave(CaveCarverConfig { + horizontal_radius_multiplier: FloatProvider::Constant(1f32), + vertical_radius_multiplier: FloatProvider::Constant(1f32), + floor_level: FloatProvider::Constant(-0.7f32), + }), + }; \ No newline at end of file diff --git a/pumpkin-data/src/lib.rs b/pumpkin-data/src/lib.rs index 5cf0d045e..f7174aec8 100644 --- a/pumpkin-data/src/lib.rs +++ b/pumpkin-data/src/lib.rs @@ -289,3 +289,8 @@ pub use blocks::Block; #[rustfmt::skip] #[path = "generated/chunk_gen_settings.rs"] pub mod chunk_gen_settings; + +#[cfg(feature = "carver")] +#[rustfmt::skip] +#[path = "generated/carver.rs"] +pub mod carver; diff --git a/pumpkin-world/src/chunk_system/chunk_holder.rs b/pumpkin-world/src/chunk_system/chunk_holder.rs index 5fa3965da..95f61b104 100644 --- a/pumpkin-world/src/chunk_system/chunk_holder.rs +++ b/pumpkin-world/src/chunk_system/chunk_holder.rs @@ -14,7 +14,7 @@ pub struct ChunkHolder { pub occupied: NodeKey, pub occupied_by: EdgeKey, pub public: bool, - pub tasks: [NodeKey; 10], + pub tasks: [NodeKey; StagedChunkEnum::COUNT], } impl Default for ChunkHolder { @@ -27,7 +27,7 @@ impl Default for ChunkHolder { occupied: NodeKey::null(), occupied_by: EdgeKey::null(), public: false, - tasks: [NodeKey::null(); 10], + tasks: [NodeKey::null(); StagedChunkEnum::COUNT], } } } diff --git a/pumpkin-world/src/chunk_system/chunk_loading.rs b/pumpkin-world/src/chunk_system/chunk_loading.rs index 9183ec2e9..9150c86d4 100644 --- a/pumpkin-world/src/chunk_system/chunk_loading.rs +++ b/pumpkin-world/src/chunk_system/chunk_loading.rs @@ -129,7 +129,7 @@ pub struct ChunkLoading { impl ChunkLoading { // pub const FULL_CHUNK_LEVEL: i8 = 33; pub const FULL_CHUNK_LEVEL: i8 = 43; - pub const MAX_LEVEL: i8 = 47; // level 47 will be unloaded. + pub const MAX_LEVEL: i8 = 48; // level 48 will be unloaded. fn debug_check_error(&self) -> bool { let mut temp = ChunkLevel::default(); for (ticket_pos, levels) in &self.ticket { diff --git a/pumpkin-world/src/chunk_system/chunk_state.rs b/pumpkin-world/src/chunk_system/chunk_state.rs index 6a1f8a587..71be9aa08 100644 --- a/pumpkin-world/src/chunk_system/chunk_state.rs +++ b/pumpkin-world/src/chunk_system/chunk_state.rs @@ -25,8 +25,10 @@ pub enum StagedChunkEnum { StructureReferences, /// Chunk with terrain noise generated, ready for surface building Noise, - /// Chunk with surface built, ready for features and structures - Surface, // SURFACE CARVERS + /// Chunk with surface built, ready for carvers + Surface, + /// Chunk with carvers applied, ready for features and structures + Carvers, /// Chunk with features and structures, ready for lighting Features, // FEATURES SPAWN /// Chunk with lighting calculated, ready for finalization @@ -44,9 +46,10 @@ impl From for StagedChunkEnum { 4 => Self::StructureReferences, 5 => Self::Noise, 6 => Self::Surface, - 7 => Self::Features, - 8 => Self::Lighting, - 9 => Self::Full, + 7 => Self::Carvers, + 8 => Self::Features, + 9 => Self::Lighting, + 10 => Self::Full, _ => panic!(), } } @@ -61,7 +64,7 @@ impl From for StagedChunkEnum { ChunkStatus::Biomes => Self::Biomes, ChunkStatus::Noise => Self::Noise, ChunkStatus::Surface => Self::Surface, - ChunkStatus::Carvers => Self::Surface, + ChunkStatus::Carvers => Self::Carvers, ChunkStatus::Features => Self::Features, ChunkStatus::Spawn => Self::Features, ChunkStatus::InitializeLight => Self::Lighting, @@ -80,6 +83,7 @@ impl From for ChunkStatus { StagedChunkEnum::Biomes => Self::Biomes, StagedChunkEnum::Noise => Self::Noise, StagedChunkEnum::Surface => Self::Surface, + StagedChunkEnum::Carvers => Self::Carvers, StagedChunkEnum::Features => Self::Features, StagedChunkEnum::Lighting => Self::Light, StagedChunkEnum::Full => Self::Full, @@ -98,17 +102,24 @@ impl StagedChunkEnum { } else if level <= 45 { Self::Features } else if level <= 46 { + Self::Carvers + } else if level <= 47 { Self::Surface } else { Self::None } } - /// Total number of state values (0 = None … 9 = Full). + /// Total number of state values (0 = None … 10 = Full). pub const COUNT: usize = Self::Full as usize + 1; - pub const FULL_DEPENDENCIES: &'static [Self] = - &[Self::Full, Self::Lighting, Self::Features, Self::Surface]; - pub const FULL_RADIUS: i32 = 3; + pub const FULL_DEPENDENCIES: &'static [Self] = &[ + Self::Full, + Self::Lighting, + Self::Features, + Self::Carvers, + Self::Surface, + ]; + pub const FULL_RADIUS: i32 = 4; #[must_use] pub const fn get_direct_radius(self) -> i32 { // self exclude @@ -119,6 +130,7 @@ impl StagedChunkEnum { Self::Biomes => 0, Self::Noise => 0, Self::Surface => 0, + Self::Carvers => 0, Self::Features => 1, Self::Lighting => 1, Self::Full => 1, @@ -135,6 +147,7 @@ impl StagedChunkEnum { Self::Biomes => 0, Self::Noise => 0, Self::Surface => 0, + Self::Carvers => 0, Self::Features => 1, Self::Lighting => 1, Self::Full => 0, @@ -161,7 +174,8 @@ impl StagedChunkEnum { ], Self::Noise => &[Self::StructureReferences], Self::Surface => &[Self::Noise], - Self::Features => &[Self::Surface, Self::Surface], + Self::Carvers => &[Self::Surface], + Self::Features => &[Self::Carvers, Self::Carvers], Self::Lighting => &[Self::Features, Self::Features], Self::Full => &[Self::Lighting, Self::Lighting], _ => panic!(), @@ -179,7 +193,7 @@ impl Chunk { pub fn get_stage_id(&self) -> u8 { match self { Self::Proto(data) => data.stage_id(), - Self::Level(_) => 9, + Self::Level(_) => StagedChunkEnum::Full as u8, } } pub fn get_proto_chunk_mut(&mut self) -> &mut ProtoChunk { diff --git a/pumpkin-world/src/chunk_system/generation_cache.rs b/pumpkin-world/src/chunk_system/generation_cache.rs index 8c1df464b..65487c0ad 100644 --- a/pumpkin-world/src/chunk_system/generation_cache.rs +++ b/pumpkin-world/src/chunk_system/generation_cache.rs @@ -367,6 +367,9 @@ impl Cache { StagedChunkEnum::Surface => self.chunks[mid] .get_proto_chunk_mut() .step_to_surface(generator), + StagedChunkEnum::Carvers => self.chunks[mid] + .get_proto_chunk_mut() + .step_to_carvers(generator), StagedChunkEnum::Features => { ProtoChunk::generate_features_and_structure( self, diff --git a/pumpkin-world/src/generation/carver/canyon.rs b/pumpkin-world/src/generation/carver/canyon.rs new file mode 100644 index 000000000..7dea1e03e --- /dev/null +++ b/pumpkin-world/src/generation/carver/canyon.rs @@ -0,0 +1,329 @@ +use super::Carver; +use super::cave::get_height; +use crate::ProtoChunk; +use pumpkin_data::block_state::BlockState; +use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig}; +use pumpkin_util::math::vector2::Vector2; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use std::f32::consts::PI; + +pub struct CanyonCarver; + +impl Carver for CanyonCarver { + fn carve( + &self, + config: &CarverConfig, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _chunk_pos: &Vector2, + carver_chunk_pos: &Vector2, + ) { + let CarverAdditionalConfig::Canyon(ref canyon_config) = config.additional else { + return; + }; + + let min_y = chunk.bottom_y() as i32; + let height = chunk.height(); + + let max_distance = (4 * 2 - 1) * 16; + + let x = (carver_chunk_pos.x << 4) + random.next_bounded_i32(16); + let y = get_height(&config.y, random, min_y as i8, height); + let z = (carver_chunk_pos.y << 4) + random.next_bounded_i32(16); + + let horizontal_rotation = random.next_f32() * PI * 2.0; + let vertical_rotation = canyon_config.vertical_rotation.get(random); + let y_scale = config.y_scale.get(random) as f64; + let thickness = canyon_config.shape.thickness.get(random); + let distance = + (max_distance as f32 * canyon_config.shape.distance_factor.get(random)) as i32; + + self.do_carve( + config, + chunk, + random.next_i64(), + x as f64, + y as f64, + z as f64, + thickness, + horizontal_rotation, + vertical_rotation, + 0, + distance, + y_scale, + ); + } +} + +impl CanyonCarver { + #[allow(clippy::too_many_arguments)] + fn do_carve( + &self, + config: &CarverConfig, + chunk: &mut ProtoChunk, + tunnel_seed: i64, + mut x: f64, + mut y: f64, + mut z: f64, + thickness: f32, + mut horizontal_rotation: f32, + mut vertical_rotation: f32, + step: i32, + distance: i32, + y_scale: f64, + ) { + let mut random = RandomGenerator::Xoroshiro( + pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(tunnel_seed as u64), + ); + let width_factor_per_height = + self.init_width_factors(chunk.height() as usize, config, &mut random); + let mut y_rota = 0.0f32; + let mut x_rota = 0.0f32; + + let CarverAdditionalConfig::Canyon(ref canyon_config) = config.additional else { + return; + }; + + for current_step in step..distance { + let mut horizontal_radius = + (1.5 + (current_step as f32 * PI / distance as f32).sin() * thickness) as f64; + let mut vertical_radius = horizontal_radius * y_scale; + horizontal_radius *= canyon_config + .shape + .horizontal_radius_factor + .get(&mut random) as f64; + vertical_radius = self.update_vertical_radius( + config, + &mut random, + vertical_radius, + distance as f32, + current_step as f32, + ); + + let xc = vertical_rotation.cos(); + let xs = vertical_rotation.sin(); + x += (horizontal_rotation.cos() * xc) as f64; + y += xs as f64; + z += (horizontal_rotation.sin() * xc) as f64; + + vertical_rotation *= 0.7; + vertical_rotation += x_rota * 0.05; + horizontal_rotation += y_rota * 0.05; + x_rota *= 0.8; + y_rota *= 0.5; + x_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 2.0; + 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) { + return; + } + + self.carve_ellipsoid( + chunk, + config, + x, + y, + z, + horizontal_radius, + vertical_radius, + &width_factor_per_height, + ); + } + } + } + + fn init_width_factors( + &self, + depth: usize, + config: &CarverConfig, + random: &mut RandomGenerator, + ) -> Vec { + let CarverAdditionalConfig::Canyon(ref canyon_config) = config.additional else { + return vec![1.0; depth]; + }; + let mut width_factor_per_height = vec![0.0; depth]; + let mut width_factor = 1.0f32; + + for (y_index, item) in width_factor_per_height.iter_mut().enumerate() { + if y_index == 0 || random.next_bounded_i32(canyon_config.shape.width_smoothness) == 0 { + width_factor = 1.0 + random.next_f32() * random.next_f32(); + } + *item = width_factor * width_factor; + } + width_factor_per_height + } + + fn update_vertical_radius( + &self, + config: &CarverConfig, + random: &mut RandomGenerator, + vertical_radius: f64, + distance: f32, + current_step: f32, + ) -> f64 { + let CarverAdditionalConfig::Canyon(ref canyon_config) = config.additional else { + return vertical_radius; + }; + let vertical_multiplier = 1.0 - (0.5 - current_step / distance).abs() * 2.0; + let factor = canyon_config.shape.vertical_radius_default_factor + + canyon_config.shape.vertical_radius_center_factor * vertical_multiplier; + factor as f64 * vertical_radius * (random.next_inbetween_i32(75, 100) as f64 / 100.0) + } + + #[allow(clippy::too_many_arguments)] + fn can_reach( + &self, + chunk_x: i32, + chunk_z: i32, + x: f64, + z: f64, + step: i32, + distance: i32, + thickness: f32, + ) -> bool { + let chunk_middle_x = (chunk_x << 4) + 8; + let chunk_middle_z = (chunk_z << 4) + 8; + let dx = x - chunk_middle_x as f64; + let dz = z - chunk_middle_z as f64; + let remaining = (distance - step) as f64; + let rr = (thickness + 2.0 + 16.0) as f64; + dx * dx + dz * dz - remaining * remaining <= rr * rr + } + + #[allow(clippy::too_many_arguments)] + fn carve_ellipsoid( + &self, + chunk: &mut ProtoChunk, + config: &CarverConfig, + x: f64, + y: f64, + z: f64, + horizontal_radius: f64, + 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 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 min_x_index = ((x - horizontal_radius).floor() as i32 - chunk_min_x - 1).max(0); + let max_x_index = ((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 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 min_z_index = ((z - horizontal_radius).floor() as i32 - chunk_min_z - 1).max(0); + let max_z_index = ((z + horizontal_radius).floor() as i32 - chunk_min_z).min(15); + + for x_index in min_x_index..=max_x_index { + let world_x = chunk_min_x + x_index; + let xd = (world_x as f64 + 0.5 - x) / horizontal_radius; + + for z_index in min_z_index..=max_z_index { + let world_z = chunk_min_z + z_index; + 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..=max_y).rev() { + let yd = (world_y as f64 - 0.5 - y) / vertical_radius; + + if !self.should_skip( + width_factor_per_height, + xd, + yd, + zd, + world_y, + chunk.bottom_y() as i32, + ) && !chunk.carving_mask.get(world_x, world_y, world_z) + { + chunk.carving_mask.set(world_x, world_y, world_z); + has_grass |= self + .carve_block(chunk, config, world_x, world_y, world_z, has_grass); + } + } + } + } + } + } + + fn should_skip( + &self, + width_factor_per_height: &[f32], + xd: f64, + yd: f64, + zd: f64, + y: i32, + min_gen_y: i32, + ) -> bool { + let y_index = (y - min_gen_y) as usize; + if y_index == 0 { + return true; + } + (xd * xd + zd * zd) * width_factor_per_height[y_index - 1] as f64 + yd * yd / 6.0 >= 1.0 + } + + fn carve_block( + &self, + chunk: &mut ProtoChunk, + config: &CarverConfig, + x: i32, + y: i32, + z: i32, + mut has_grass: bool, + ) -> 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 = pumpkin_data::Block::from_state_id(state_id); + + if block.id == pumpkin_data::Block::WATER.id || block.id == pumpkin_data::Block::LAVA.id { + return false; + } + + if block.id == pumpkin_data::Block::GRASS_BLOCK.id + || block.id == pumpkin_data::Block::MYCELIUM.id + { + has_grass = true; + } + + if config.replaceable.1.contains(&block.id) { + let air = BlockState::from_id(pumpkin_data::Block::AIR.default_state.id); + let lava = BlockState::from_id(pumpkin_data::Block::LAVA.default_state.id); + + 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); + } + + if has_grass { + let down_y = y - 1; + let local_down_y = down_y - chunk.bottom_y() as i32; + if (0..chunk.height() as i32).contains(&local_down_y) { + let down_state_id = chunk.get_block_state_raw(x & 15, local_down_y, z & 15); + if pumpkin_data::Block::from_state_id(down_state_id).id + == pumpkin_data::Block::DIRT.id + { + // dirt replacement skipped + } + } + } + return true; + } + false + } +} diff --git a/pumpkin-world/src/generation/carver/cave.rs b/pumpkin-world/src/generation/carver/cave.rs index 630a2310e..0e7eeff3b 100644 --- a/pumpkin-world/src/generation/carver/cave.rs +++ b/pumpkin-world/src/generation/carver/cave.rs @@ -1,166 +1,423 @@ -// use pumpkin_macros::block_state; -// use pumpkin_util::{ -// math::{float_provider::FloatProvider, vector2::Vector2, vector3::Vector3}, -// random::RandomGenerator, -// }; -// use serde::Deserialize; +use super::Carver; +use crate::ProtoChunk; +use pumpkin_data::block_state::BlockState; +use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig, HeightProvider}; +use pumpkin_util::math::vector2::Vector2; +use pumpkin_util::random::{RandomGenerator, RandomImpl}; +use std::f32::consts::PI; -// use crate::{ -// ProtoChunk, -// block::{BlockState, registry::get_block}, -// generation::{ -// aquifer_sampler::{AquiferSamplerImpl, WorldAquiferSampler}, -// height_limit::HeightLimitView, -// height_provider::HeightProvider, -// positions::chunk_pos, -// section_coords, -// y_offset::YOffset, -// }, -// }; +pub struct CaveCarver; -// use super::mask::CarvingMask; +impl Carver for CaveCarver { + fn carve( + &self, + config: &CarverConfig, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _chunk_pos: &Vector2, + carver_chunk_pos: &Vector2, + ) { + let (is_nether, cave_config) = match config.additional { + CarverAdditionalConfig::Cave(ref c) => (false, c), + CarverAdditionalConfig::NetherCave(ref c) => (true, c), + _ => return, + }; -// #[derive(Deserialize)] -// pub struct CaveCraver { -// vertical_radius_multiplier: FloatProvider, -// horizontal_radius_multiplier: FloatProvider, -// floor_level: FloatProvider, -// y: HeightProvider, -// #[serde(rename = "yScale")] -// y_scale: FloatProvider, -// lava_level: YOffset, -// probability: f32, -// } -// const BRANCH_FACTOR: i32 = 4; -// const MAX_CAVE_COUNT: i32 = 15; + let min_y = chunk.bottom_y() as i32; + let height = chunk.height(); -// impl CaveCraver { -// pub fn should_carve(&self, random: &mut RandomGenerator) -> bool { -// random.next_f32() <= self.probability -// } + let max_distance = (4 * 2 - 1) << 4; -// pub fn carve( -// &self, -// random: &mut RandomGenerator, -// chunk_pos: &Vector2, -// min_y: i8, -// height: u16, -// ) { -// todo!(); -// let block_coord = section_coords::section_to_block(BRANCH_FACTOR * 2 - 1); -// let first_rnd = random.next_bounded_i32(MAX_CAVE_COUNT); -// let sec_rnd = random.next_bounded_i32(first_rnd + 1); -// let third_rnd = random.next_bounded_i32(sec_rnd + 1); -// let range = third_rnd; -// for _ in 0..range { -// let x = chunk_pos::start_block_x(chunk_pos) + random.next_bounded_i32(16); // offset -// let y = self.y.get(random, min_y, height); -// let z = chunk_pos::start_block_z(chunk_pos) + random.next_bounded_i32(16); // offset -// let vertical = self.vertical_radius_multiplier.get(); -// let horizontal = self.horizontal_radius_multiplier.get(); -// let floor = self.floor_level.get(); -// let mut pitch; -// let mut tries = 0; -// if random.next_bounded_i32(4) == 0 { -// let scale = self.y_scale.get(); -// pitch = 1.0 + random.next_f32() * 6.0; -// tries += random.next_bounded_i32(4); -// } -// } -// } + let bound = if is_nether { 10 } else { 15 }; + let c1 = random.next_bounded_i32(bound); + let c2 = random.next_bounded_i32(c1 + 1); + let cave_count = random.next_bounded_i32(c2 + 1); -// fn carve_cave(&self, chunk_pos: &Vector2, width: f64, height: f64) { -// todo!(); -// // let width = 1.5 + 1.5707964f64.sin() * width; -// // let height = width * height; -// // Self::carve_region(chunk_pos, width, height) -// } + for _ in 0..cave_count { + let x = (carver_chunk_pos.x << 4) + random.next_bounded_i32(16); + let y = get_height(&config.y, random, min_y as i8, height) as f64; + let z = (carver_chunk_pos.y << 4) + random.next_bounded_i32(16); -// fn carve_region( -// min_y: i8, -// chunk_height: u16, -// chunk_pos: &Vector2, -// x: f64, -// y: f64, -// z: f64, -// width: f64, -// height: f64, -// mask: &mut CarvingMask, -// floor_level: f64, -// ) { -// let start_x = chunk_pos::start_block_x(chunk_pos); -// let start_z = chunk_pos::start_block_z(chunk_pos); + let horizontal_radius_multiplier = + cave_config.horizontal_radius_multiplier.get(random) as f64; + let vertical_radius_multiplier = + cave_config.vertical_radius_multiplier.get(random) as f64; + let floor_level = cave_config.floor_level.get(random) as f64; -// let chunk_center_x = (start_x + 8) as f64; -// let chunk_center_z = (start_z + 8) as f64; + let mut tunnels = 1; + if random.next_bounded_i32(4) == 0 { + let y_scale = config.y_scale.get(random) as f64; + let thickness = 1.0 + random.next_f32() * 6.0; + self.create_room( + chunk, + x as f64, + y, + z as f64, + thickness, + y_scale, + config, + floor_level, + is_nether, + ); + tunnels += random.next_bounded_i32(4); + } -// let max_width = 16.0 + width * 2.0; -// if (x - chunk_center_x).abs() > max_width || (z - chunk_center_z).abs() > max_width { -// return; -// } -// let x_start = (x - width).floor() as i32 - start_x - 1.max(0); -// let max_x = (x - width).floor() as i32 - start_x - 1.min(15); + for _ in 0..tunnels { + let horizontal_rotation = random.next_f32() * PI * 2.0; + let vertical_rotation = (random.next_f32() - 0.5) / 4.0; + let thickness = self.get_thickness(random, is_nether); + let distance = max_distance - random.next_bounded_i32(max_distance / 4); -// let z_start = (z - width).floor() as i32 - start_z - 1.max(0); -// let max_z = (z - width).floor() as i32 - start_z - 1.min(15); + self.create_tunnel( + config, + chunk, + random.next_i64(), + x as f64, + y, + z as f64, + horizontal_radius_multiplier, + vertical_radius_multiplier, + thickness, + horizontal_rotation, + vertical_rotation, + 0, + distance, + if is_nether { 5.0 } else { 1.0 }, // this.getYScale() + floor_level, + is_nether, + ); + } + } + } +} -// let init_height = -// (y + height).floor() as i32 + 1.min(min_y as i32 + chunk_height as i32 - 1 - 7); +impl CaveCarver { + fn get_thickness(&self, random: &mut RandomGenerator, is_nether: bool) -> f32 { + if is_nether { + (random.next_f32() * 2.0 + random.next_f32()) * 2.0 + } else { + let mut thickness = random.next_f32() * 2.0 + random.next_f32(); + if random.next_bounded_i32(10) == 0 { + thickness *= random.next_f32() * random.next_f32() * 3.0 + 1.0; + } + thickness + } + } -// let end_height = (y - height).floor() as i32 - 1.max(min_y as i32 + 1); + #[allow(clippy::too_many_arguments)] + fn create_room( + &self, + chunk: &mut ProtoChunk, + x: f64, + y: f64, + z: f64, + thickness: f32, + y_scale: f64, + config: &CarverConfig, + 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; + self.carve_ellipsoid( + chunk, + config, + x + 1.0, + y, + z, + horizontal_radius as f64, + vertical_radius, + floor_level, + is_nether, + ); + } -// for current_x in max_x..x_start { -// let x_offset = chunk_pos::start_block_x(chunk_pos) + current_x; -// let x_offwidth = (x_offset as f64 + 0.5 - x) / width; -// for current_z in max_z..z_start { -// let z_offset = chunk_pos::start_block_z(chunk_pos) + current_z; -// let z_offwidth = (z_offset as f64 + 0.5 - z) / width; + #[allow(clippy::too_many_arguments)] + fn create_tunnel( + &self, + config: &CarverConfig, + chunk: &mut ProtoChunk, + tunnel_seed: i64, + mut x: f64, + mut y: f64, + mut z: f64, + horizontal_radius_multiplier: f64, + vertical_radius_multiplier: f64, + thickness: f32, + mut horizontal_rotation: f32, + mut vertical_rotation: f32, + step: i32, + dist: i32, + y_scale: f64, + floor_level: f64, + is_nether: bool, + ) { + let mut random = RandomGenerator::Xoroshiro( + pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(tunnel_seed as u64), + ); + let split_point = random.next_bounded_i32(dist / 2) + dist / 4; + let steep = random.next_bounded_i32(6) == 0; + let mut y_rota = 0.0f32; + let mut x_rota = 0.0f32; -// if x_offwidth * x_offwidth + z_offwidth * z_offwidth >= 1.0 { -// continue; -// } -// for current_y in init_height..end_height { -// let y_offwidth = (current_y as f64 - 0.5 - y) / height; -// if Self::is_pos_excluded( -// Vector3::new(x_offwidth, y_offwidth, z_offwidth), -// floor_level, -// ) { -// continue; -// } -// mask.set(current_x, current_y, current_z); -// } -// } -// } -// } + for current_step in step..dist { + 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; -// fn carve_at_point(&self, chunk: &ProtoChunk, pos: Vector3, min_y: i8, height: u16) { -// let state = chunk.get_block_state(&pos); + vertical_rotation *= if steep { 0.92 } else { 0.7 }; + vertical_rotation += x_rota * 0.1; + horizontal_rotation += y_rota * 0.1; + x_rota *= 0.9; + y_rota *= 0.75; + x_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 2.0; + y_rota += (random.next_f32() - random.next_f32()) * random.next_f32() * 4.0; -// // if state.block_id == block_state!("grass_block").block_id || state.block_id == block_state!("mycelium").block_id { -// // TODO -// // } -// let state = self.get_state(pos, min_y, height); -// } + if current_step == split_point && thickness > 1.0 { + self.create_tunnel( + config, + chunk, + random.next_i64(), + x, + y, + z, + horizontal_radius_multiplier, + vertical_radius_multiplier, + random.next_f32() * 0.5 + 0.5, + horizontal_rotation - (PI / 2.0), + vertical_rotation / 3.0, + current_step, + dist, + 1.0, + floor_level, + is_nether, + ); + self.create_tunnel( + config, + chunk, + random.next_i64(), + x, + y, + z, + horizontal_radius_multiplier, + vertical_radius_multiplier, + random.next_f32() * 0.5 + 0.5, + horizontal_rotation + (PI / 2.0), + vertical_rotation / 3.0, + current_step, + dist, + 1.0, + floor_level, + is_nether, + ); + return; + } -// fn get_state(&self, pos: Vector3, min_y: i8, height: u16) -> Option { -// if pos.y <= self.lava_level.get_y(min_y, height) as i32 { -// return Some(block_state!("lava")); -// } -// None -// } + if random.next_bounded_i32(4) != 0 { + if !self.can_reach(chunk.x, chunk.z, x, z, current_step, dist, thickness) { + return; + } -// fn is_pos_excluded(scaled: Vector3, floor_y: f64) -> bool { -// if scaled.y <= floor_y { -// return true; -// } -// scaled.x * scaled.x + scaled.y * scaled.y + scaled.z * scaled.z >= 1.0 -// } + self.carve_ellipsoid( + chunk, + config, + x, + y, + z, + horizontal_radius as f64 * horizontal_radius_multiplier, + vertical_radius * vertical_radius_multiplier, + floor_level, + is_nether, + ); + } + } + } -// fn get_tunnel_width(random: &mut RandomGenerator) -> f32 { -// let mut width = random.next_f32() * 2.0 + random.next_f32(); -// if random.next_bounded_i32(10) == 0 { -// width *= random.next_f32() * random.next_f32() * 3.0 + 1.0; -// } -// width -// } -// } + #[allow(clippy::too_many_arguments)] + fn can_reach( + &self, + chunk_x: i32, + chunk_z: i32, + x: f64, + z: f64, + step: i32, + distance: i32, + thickness: f32, + ) -> bool { + let chunk_middle_x = (chunk_x << 4) + 8; + let chunk_middle_z = (chunk_z << 4) + 8; + let dx = x - chunk_middle_x as f64; + let dz = z - chunk_middle_z as f64; + let remaining = (distance - step) as f64; + let rr = (thickness + 2.0 + 16.0) as f64; + dx * dx + dz * dz - remaining * remaining <= rr * rr + } + + #[allow(clippy::too_many_arguments)] + fn carve_ellipsoid( + &self, + chunk: &mut ProtoChunk, + config: &CarverConfig, + x: f64, + y: f64, + z: f64, + horizontal_radius: f64, + vertical_radius: f64, + 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 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 min_x_index = ((x - horizontal_radius).floor() as i32 - chunk_min_x - 1).max(0); + let max_x_index = ((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 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 min_z_index = ((z - horizontal_radius).floor() as i32 - chunk_min_z - 1).max(0); + let max_z_index = ((z + horizontal_radius).floor() as i32 - chunk_min_z).min(15); + + for x_index in min_x_index..=max_x_index { + let world_x = chunk_min_x + x_index; + let xd = (world_x as f64 + 0.5 - x) / horizontal_radius; + + for z_index in min_z_index..=max_z_index { + let world_z = chunk_min_z + z_index; + 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..=max_y).rev() { + 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) + { + chunk.carving_mask.set(world_x, world_y, world_z); + has_grass |= self.carve_block( + chunk, config, world_x, world_y, world_z, is_nether, has_grass, + ); + } + } + } + } + } + } + + fn should_skip(&self, xd: f64, yd: f64, zd: f64, floor_level: f64) -> bool { + if yd <= floor_level { + true + } else { + xd * xd + yd * yd + zd * zd >= 1.0 + } + } + + #[allow(clippy::too_many_arguments)] + fn carve_block( + &self, + chunk: &mut ProtoChunk, + config: &CarverConfig, + x: i32, + y: i32, + z: i32, + is_nether: bool, + mut has_grass: bool, + ) -> 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 = pumpkin_data::Block::from_state_id(state_id); + + if block.id == pumpkin_data::Block::WATER.id || block.id == pumpkin_data::Block::LAVA.id { + return false; + } + + if block.id == pumpkin_data::Block::GRASS_BLOCK.id + || block.id == pumpkin_data::Block::MYCELIUM.id + { + has_grass = true; + } + + // Only carve if it's replaceable + if config.replaceable.1.contains(&block.id) { + // Replace with air or lava + let air = BlockState::from_id(pumpkin_data::Block::AIR.default_state.id); + let lava = BlockState::from_id(pumpkin_data::Block::LAVA.default_state.id); + + let lava_y = if is_nether { + chunk.bottom_y() as i32 + 31 + } else { + 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); + } + + if has_grass { + let down_y = y - 1; + let local_down_y = down_y - chunk.bottom_y() as i32; + if (0..chunk.height() as i32).contains(&local_down_y) { + let down_state_id = chunk.get_block_state_raw(x & 15, local_down_y, z & 15); + if pumpkin_data::Block::from_state_id(down_state_id).id + == pumpkin_data::Block::DIRT.id + { + // dirt replacement skipped + } + } + } + + return true; + } + false + } +} + +pub fn get_height(p: &HeightProvider, random: &mut RandomGenerator, min_y: i8, height: u16) -> i32 { + match p { + HeightProvider::Uniform(p) => { + let min = p.min_inclusive.get_y(min_y as i16, height); + let max = p.max_inclusive.get_y(min_y as i16, height); + random.next_inbetween_i32(min, max) + } + HeightProvider::Trapezoid(p) => { + let i = p.min_inclusive.get_y(min_y as i16, height); + let j = p.max_inclusive.get_y(min_y as i16, height); + let plateau = p.plateau.unwrap_or(0); + let k = j - i; + if plateau >= k { + random.next_inbetween_i32(i, j) + } else { + let l = (k - plateau) / 2; + let m = k - l; + i + random.next_inbetween_i32(0, m) + random.next_inbetween_i32(0, l) + } + } + HeightProvider::VeryBiasedToBottom(p) => { + let min = p.min_inclusive.get_y(min_y as i16, height); + let max = p.max_inclusive.get_y(min_y as i16, height); + let inner = p.inner.map_or(1, |n| n.get()) as i32; + let min_rnd = random.next_inbetween_i32(min + inner, max); + let max_rnd = random.next_inbetween_i32(min, min_rnd - 1); + random.next_inbetween_i32(min, max_rnd - 1 + inner) + } + } +} diff --git a/pumpkin-world/src/generation/carver/mask.rs b/pumpkin-world/src/generation/carver/mask.rs index f51e1d97d..4170c3dbb 100644 --- a/pumpkin-world/src/generation/carver/mask.rs +++ b/pumpkin-world/src/generation/carver/mask.rs @@ -1,29 +1,41 @@ -// use pumpkin_util::math::vector3::Vector3; +#[derive(Clone)] +pub struct CarvingMask { + min_y: i32, + mask: Vec, +} -// pub struct CarvingMask { -// min_y: i8, -// mask: Vec>, -// } +impl CarvingMask { + #[must_use] + pub fn new(height: i32, min_y: i32) -> Self { + // Each u64 stores 64 bits. Total bits needed: 16 * 16 * height. + let total_bits = 16 * 16 * height; + let num_u64s = (total_bits as usize).div_ceil(64); + Self { + min_y, + mask: vec![0; num_u64s], + } + } -// impl CarvingMask { -// pub fn new(height: i16, min_y: i8) -> Self { -// Self { -// min_y, -// mask: vec![Vector3::new(0, 0, 0); 256 * height as usize], -// } -// } + fn get_bit_index(&self, x: i32, y: i32, z: i32) -> usize { + // x: 0..16, z: 0..16, y: min_y..min_y+height + let rel_x = x & 15; + let rel_z = z & 15; + let rel_y = y - self.min_y; + (rel_x | (rel_z << 4) | (rel_y << 8)) as usize + } -// fn get_index(&self, offset_x: i32, y: i32, offset_z: i32) -> usize { -// (offset_x & 0xF | (offset_z & 0xF) << 4 | (y - self.min_y as i32) << 8) as usize -// } + pub fn set(&mut self, x: i32, y: i32, z: i32) { + let bit_index = self.get_bit_index(x, y, z); + let u64_idx = bit_index / 64; + let bit_in_u64 = bit_index % 64; + self.mask[u64_idx] |= 1 << bit_in_u64; + } -// pub fn set(&mut self, offset_x: i32, y: i32, offset_z: i32) { -// let index = self.get_index(offset_x, y, offset_z); -// self.mask[index] = Vector3::new(offset_x, y, offset_z) -// } - -// pub fn get(&mut self, offset_x: i32, y: i32, offset_z: i32) -> bool { -// let index = self.get_index(offset_x, y, offset_z); -// self.mask.get(index).is_some() -// } -// } + #[must_use] + pub fn get(&self, x: i32, y: i32, z: i32) -> bool { + let bit_index = self.get_bit_index(x, y, z); + let u64_idx = bit_index / 64; + let bit_in_u64 = bit_index % 64; + (self.mask[u64_idx] >> bit_in_u64) & 1 != 0 + } +} diff --git a/pumpkin-world/src/generation/carver/mod.rs b/pumpkin-world/src/generation/carver/mod.rs index 6da8150c0..f75068893 100644 --- a/pumpkin-world/src/generation/carver/mod.rs +++ b/pumpkin-world/src/generation/carver/mod.rs @@ -1,2 +1,97 @@ -mod cave; -mod mask; +pub mod canyon; +pub mod cave; +pub mod mask; + +use crate::ProtoChunk; +use crate::generation::generator::VanillaGenerator; +use pumpkin_data::carver::{CANYON, CAVE, CAVE_EXTRA_UNDERGROUND, NETHER_CAVE}; +use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig}; +use pumpkin_util::math::vector2::Vector2; +use pumpkin_util::random::{RandomGenerator, RandomImpl, get_carver_seed}; + +pub trait Carver { + fn carve( + &self, + config: &CarverConfig, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + chunk_pos: &Vector2, + carver_chunk_pos: &Vector2, + ); +} + +pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) { + let mut random = RandomGenerator::Xoroshiro( + pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(generator.random_config.seed), + ); + + // Vanilla applyCarvers uses a range of 8 chunks (17x17 area) + let radius = 8; + let chunk_x = chunk.x; + 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 = if generator.dimension == pumpkin_data::dimension::Dimension::OVERWORLD { + &overworld_carvers[..] + } else if generator.dimension == pumpkin_data::dimension::Dimension::THE_NETHER { + &nether_carvers[..] + } else { + &[] + }; + + let cave_carver = cave::CaveCarver; + let canyon_carver = canyon::CanyonCarver; + + for dx in -radius..=radius { + for dz in -radius..=radius { + let carver_x = chunk_x + dx; + let carver_z = chunk_z + dz; + let carver_chunk_pos = Vector2::new(carver_x, carver_z); + + // 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( + &mut random, + generator.random_config.seed + index as u64, + carver_x, + carver_z, + ); + let mut carver_random = RandomGenerator::Xoroshiro( + pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(seed), + ); + + 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, + ); + } + CarverAdditionalConfig::Canyon(_) => { + canyon_carver.carve( + config, + chunk, + &mut carver_random, + &chunk_pos, + &carver_chunk_pos, + ); + } + } + } + } + } + } +} + +fn should_carve(config: &CarverConfig, random: &mut RandomGenerator) -> bool { + random.next_f32() <= config.probability +} diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index 3513be80f..ffe102635 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -168,6 +168,7 @@ pub struct ProtoChunk { bottom_y: i8, pub stage: StagedChunkEnum, pub light: ChunkLight, + pub carving_mask: crate::generation::carver::mask::CarvingMask, /// Block entities pending creation when the chunk is finalized. /// These are created from structure templates during world generation. pub pending_block_entities: Vec, @@ -246,6 +247,10 @@ impl ProtoChunk { .map(|_| LightContainer::new_empty(0)) .collect(), }, + carving_mask: crate::generation::carver::mask::CarvingMask::new( + height as i32, + dimension.min_y, + ), pending_block_entities: Vec::new(), } } @@ -630,6 +635,14 @@ impl ProtoChunk { self.stage = StagedChunkEnum::Surface; } + pub fn step_to_carvers(&mut self, generator: &super::generator::VanillaGenerator) { + debug_assert_eq!(self.stage, StagedChunkEnum::Surface); + + super::carver::carve(self, generator); + + self.stage = StagedChunkEnum::Carvers; + } + pub fn populate_biomes( &mut self, generator: &super::generator::VanillaGenerator, diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index 7feeb322e..77614254e 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -94,6 +94,7 @@ pub struct BedrockClient { //input_sequence_number: AtomicU32, received_sequences: Mutex>, pending_acks: Mutex>, + #[allow(clippy::type_complexity)] unacked_outgoing_frames: Mutex, std::time::Instant)>>, expected_order_index: Mutex>, highest_sequence_index: Mutex>, @@ -191,13 +192,11 @@ impl BedrockClient { .await .write_packet(&packet_data, client.address, &client.socket) .await - { - if !client.close_token.is_cancelled() { + && !client.close_token.is_cancelled() { warn!("Failed to send packet to client: {err}",); client.close_token.cancel(); break; } - } } } } @@ -489,16 +488,15 @@ impl BedrockClient { debug!("Received NACK for sequences: {:?}", nack.sequences); let unacked = self.unacked_outgoing_frames.lock().await; for seq in &nack.sequences { - if let Some((_id, data, _timestamp)) = unacked.get(seq) { - if let Err(err) = self + if let Some((_id, data, _timestamp)) = unacked.get(seq) + && let Err(err) = self .network_writer .lock() .await .write_packet(data, self.address, &self.socket) .await - { - warn!("Failed to resend packet for sequence {}: {}", seq, err); - } + { + warn!("Failed to resend packet for sequence {}: {}", seq, err); } } }