feat(chunk-gen): add carver

This commit is contained in:
Alexander Medvedev 2026-05-01 12:26:49 +02:00
parent a58ccdd33d
commit 7fe705c7eb
15 changed files with 1359 additions and 197 deletions

View File

@ -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<String, Value> =
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<i32>,
}
pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset,
pub max_inclusive: YOffset,
pub inner: Option<std::num::NonZeroU32>,
}
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 }) }
}
}

View File

@ -13,6 +13,7 @@ mod attributes;
mod biome; mod biome;
mod bitsets; mod bitsets;
mod block; mod block;
mod carver;
mod chunk_gen_settings; mod chunk_gen_settings;
mod chunk_status; mod chunk_status;
mod composter_increase_chance; mod composter_increase_chance;
@ -122,6 +123,7 @@ pub fn main() {
configured_feature::build, configured_feature::build,
"configured_features_generated.rs", "configured_features_generated.rs",
), ),
(carver::build, "carver.rs"),
]; ];
build_functions.extend(remap::build()); build_functions.extend(remap::build());

View File

@ -41,6 +41,7 @@ default = [
"potion", "potion",
"recipe_remainder", "recipe_remainder",
"chunk_gen_settings", "chunk_gen_settings",
"carver",
] ]
item = [] item = []
@ -76,6 +77,7 @@ structures = []
potion = ["potion_brewing"] potion = ["potion_brewing"]
recipe_remainder = [] recipe_remainder = []
chunk_gen_settings = [] chunk_gen_settings = []
carver = []
noise_parameter = [] noise_parameter = []
biome = [] biome = []

View File

@ -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<i32>,
}
pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset,
pub max_inclusive: YOffset,
pub inner: Option<std::num::NonZeroU32>,
}
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),
}),
};

View File

@ -289,3 +289,8 @@ pub use blocks::Block;
#[rustfmt::skip] #[rustfmt::skip]
#[path = "generated/chunk_gen_settings.rs"] #[path = "generated/chunk_gen_settings.rs"]
pub mod chunk_gen_settings; pub mod chunk_gen_settings;
#[cfg(feature = "carver")]
#[rustfmt::skip]
#[path = "generated/carver.rs"]
pub mod carver;

View File

@ -14,7 +14,7 @@ pub struct ChunkHolder {
pub occupied: NodeKey, pub occupied: NodeKey,
pub occupied_by: EdgeKey, pub occupied_by: EdgeKey,
pub public: bool, pub public: bool,
pub tasks: [NodeKey; 10], pub tasks: [NodeKey; StagedChunkEnum::COUNT],
} }
impl Default for ChunkHolder { impl Default for ChunkHolder {
@ -27,7 +27,7 @@ impl Default for ChunkHolder {
occupied: NodeKey::null(), occupied: NodeKey::null(),
occupied_by: EdgeKey::null(), occupied_by: EdgeKey::null(),
public: false, public: false,
tasks: [NodeKey::null(); 10], tasks: [NodeKey::null(); StagedChunkEnum::COUNT],
} }
} }
} }

View File

@ -129,7 +129,7 @@ pub struct ChunkLoading {
impl ChunkLoading { impl ChunkLoading {
// pub const FULL_CHUNK_LEVEL: i8 = 33; // pub const FULL_CHUNK_LEVEL: i8 = 33;
pub const FULL_CHUNK_LEVEL: i8 = 43; 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 { fn debug_check_error(&self) -> bool {
let mut temp = ChunkLevel::default(); let mut temp = ChunkLevel::default();
for (ticket_pos, levels) in &self.ticket { for (ticket_pos, levels) in &self.ticket {

View File

@ -25,8 +25,10 @@ pub enum StagedChunkEnum {
StructureReferences, StructureReferences,
/// Chunk with terrain noise generated, ready for surface building /// Chunk with terrain noise generated, ready for surface building
Noise, Noise,
/// Chunk with surface built, ready for features and structures /// Chunk with surface built, ready for carvers
Surface, // SURFACE CARVERS Surface,
/// Chunk with carvers applied, ready for features and structures
Carvers,
/// Chunk with features and structures, ready for lighting /// Chunk with features and structures, ready for lighting
Features, // FEATURES SPAWN Features, // FEATURES SPAWN
/// Chunk with lighting calculated, ready for finalization /// Chunk with lighting calculated, ready for finalization
@ -44,9 +46,10 @@ impl From<u8> for StagedChunkEnum {
4 => Self::StructureReferences, 4 => Self::StructureReferences,
5 => Self::Noise, 5 => Self::Noise,
6 => Self::Surface, 6 => Self::Surface,
7 => Self::Features, 7 => Self::Carvers,
8 => Self::Lighting, 8 => Self::Features,
9 => Self::Full, 9 => Self::Lighting,
10 => Self::Full,
_ => panic!(), _ => panic!(),
} }
} }
@ -61,7 +64,7 @@ impl From<ChunkStatus> for StagedChunkEnum {
ChunkStatus::Biomes => Self::Biomes, ChunkStatus::Biomes => Self::Biomes,
ChunkStatus::Noise => Self::Noise, ChunkStatus::Noise => Self::Noise,
ChunkStatus::Surface => Self::Surface, ChunkStatus::Surface => Self::Surface,
ChunkStatus::Carvers => Self::Surface, ChunkStatus::Carvers => Self::Carvers,
ChunkStatus::Features => Self::Features, ChunkStatus::Features => Self::Features,
ChunkStatus::Spawn => Self::Features, ChunkStatus::Spawn => Self::Features,
ChunkStatus::InitializeLight => Self::Lighting, ChunkStatus::InitializeLight => Self::Lighting,
@ -80,6 +83,7 @@ impl From<StagedChunkEnum> for ChunkStatus {
StagedChunkEnum::Biomes => Self::Biomes, StagedChunkEnum::Biomes => Self::Biomes,
StagedChunkEnum::Noise => Self::Noise, StagedChunkEnum::Noise => Self::Noise,
StagedChunkEnum::Surface => Self::Surface, StagedChunkEnum::Surface => Self::Surface,
StagedChunkEnum::Carvers => Self::Carvers,
StagedChunkEnum::Features => Self::Features, StagedChunkEnum::Features => Self::Features,
StagedChunkEnum::Lighting => Self::Light, StagedChunkEnum::Lighting => Self::Light,
StagedChunkEnum::Full => Self::Full, StagedChunkEnum::Full => Self::Full,
@ -98,17 +102,24 @@ impl StagedChunkEnum {
} else if level <= 45 { } else if level <= 45 {
Self::Features Self::Features
} else if level <= 46 { } else if level <= 46 {
Self::Carvers
} else if level <= 47 {
Self::Surface Self::Surface
} else { } else {
Self::None 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 COUNT: usize = Self::Full as usize + 1;
pub const FULL_DEPENDENCIES: &'static [Self] = pub const FULL_DEPENDENCIES: &'static [Self] = &[
&[Self::Full, Self::Lighting, Self::Features, Self::Surface]; Self::Full,
pub const FULL_RADIUS: i32 = 3; Self::Lighting,
Self::Features,
Self::Carvers,
Self::Surface,
];
pub const FULL_RADIUS: i32 = 4;
#[must_use] #[must_use]
pub const fn get_direct_radius(self) -> i32 { pub const fn get_direct_radius(self) -> i32 {
// self exclude // self exclude
@ -119,6 +130,7 @@ impl StagedChunkEnum {
Self::Biomes => 0, Self::Biomes => 0,
Self::Noise => 0, Self::Noise => 0,
Self::Surface => 0, Self::Surface => 0,
Self::Carvers => 0,
Self::Features => 1, Self::Features => 1,
Self::Lighting => 1, Self::Lighting => 1,
Self::Full => 1, Self::Full => 1,
@ -135,6 +147,7 @@ impl StagedChunkEnum {
Self::Biomes => 0, Self::Biomes => 0,
Self::Noise => 0, Self::Noise => 0,
Self::Surface => 0, Self::Surface => 0,
Self::Carvers => 0,
Self::Features => 1, Self::Features => 1,
Self::Lighting => 1, Self::Lighting => 1,
Self::Full => 0, Self::Full => 0,
@ -161,7 +174,8 @@ impl StagedChunkEnum {
], ],
Self::Noise => &[Self::StructureReferences], Self::Noise => &[Self::StructureReferences],
Self::Surface => &[Self::Noise], 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::Lighting => &[Self::Features, Self::Features],
Self::Full => &[Self::Lighting, Self::Lighting], Self::Full => &[Self::Lighting, Self::Lighting],
_ => panic!(), _ => panic!(),
@ -179,7 +193,7 @@ impl Chunk {
pub fn get_stage_id(&self) -> u8 { pub fn get_stage_id(&self) -> u8 {
match self { match self {
Self::Proto(data) => data.stage_id(), 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 { pub fn get_proto_chunk_mut(&mut self) -> &mut ProtoChunk {

View File

@ -367,6 +367,9 @@ impl Cache {
StagedChunkEnum::Surface => self.chunks[mid] StagedChunkEnum::Surface => self.chunks[mid]
.get_proto_chunk_mut() .get_proto_chunk_mut()
.step_to_surface(generator), .step_to_surface(generator),
StagedChunkEnum::Carvers => self.chunks[mid]
.get_proto_chunk_mut()
.step_to_carvers(generator),
StagedChunkEnum::Features => { StagedChunkEnum::Features => {
ProtoChunk::generate_features_and_structure( ProtoChunk::generate_features_and_structure(
self, self,

View File

@ -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<i32>,
carver_chunk_pos: &Vector2<i32>,
) {
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<f32> {
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
}
}

View File

@ -1,166 +1,423 @@
// use pumpkin_macros::block_state; use super::Carver;
// use pumpkin_util::{ use crate::ProtoChunk;
// math::{float_provider::FloatProvider, vector2::Vector2, vector3::Vector3}, use pumpkin_data::block_state::BlockState;
// random::RandomGenerator, use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig, HeightProvider};
// }; use pumpkin_util::math::vector2::Vector2;
// use serde::Deserialize; use pumpkin_util::random::{RandomGenerator, RandomImpl};
use std::f32::consts::PI;
// use crate::{ pub struct CaveCarver;
// 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,
// },
// };
// use super::mask::CarvingMask; impl Carver for CaveCarver {
fn carve(
&self,
config: &CarverConfig,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
_chunk_pos: &Vector2<i32>,
carver_chunk_pos: &Vector2<i32>,
) {
let (is_nether, cave_config) = match config.additional {
CarverAdditionalConfig::Cave(ref c) => (false, c),
CarverAdditionalConfig::NetherCave(ref c) => (true, c),
_ => return,
};
// #[derive(Deserialize)] let min_y = chunk.bottom_y() as i32;
// pub struct CaveCraver { let height = chunk.height();
// 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;
// impl CaveCraver { let max_distance = (4 * 2 - 1) << 4;
// pub fn should_carve(&self, random: &mut RandomGenerator) -> bool {
// random.next_f32() <= self.probability
// }
// pub fn carve( let bound = if is_nether { 10 } else { 15 };
// &self, let c1 = random.next_bounded_i32(bound);
// random: &mut RandomGenerator, let c2 = random.next_bounded_i32(c1 + 1);
// chunk_pos: &Vector2<i32>, let cave_count = random.next_bounded_i32(c2 + 1);
// 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);
// }
// }
// }
// fn carve_cave(&self, chunk_pos: &Vector2<i32>, width: f64, height: f64) { for _ in 0..cave_count {
// todo!(); let x = (carver_chunk_pos.x << 4) + random.next_bounded_i32(16);
// // let width = 1.5 + 1.5707964f64.sin() * width; let y = get_height(&config.y, random, min_y as i8, height) as f64;
// // let height = width * height; let z = (carver_chunk_pos.y << 4) + random.next_bounded_i32(16);
// // Self::carve_region(chunk_pos, width, height)
// }
// fn carve_region( let horizontal_radius_multiplier =
// min_y: i8, cave_config.horizontal_radius_multiplier.get(random) as f64;
// chunk_height: u16, let vertical_radius_multiplier =
// chunk_pos: &Vector2<i32>, cave_config.vertical_radius_multiplier.get(random) as f64;
// x: f64, let floor_level = cave_config.floor_level.get(random) as 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 chunk_center_x = (start_x + 8) as f64; let mut tunnels = 1;
// let chunk_center_z = (start_z + 8) as f64; 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; for _ in 0..tunnels {
// if (x - chunk_center_x).abs() > max_width || (z - chunk_center_z).abs() > max_width { let horizontal_rotation = random.next_f32() * PI * 2.0;
// return; let vertical_rotation = (random.next_f32() - 0.5) / 4.0;
// } let thickness = self.get_thickness(random, is_nether);
// let x_start = (x - width).floor() as i32 - start_x - 1.max(0); let distance = max_distance - random.next_bounded_i32(max_distance / 4);
// let max_x = (x - width).floor() as i32 - start_x - 1.min(15);
// let z_start = (z - width).floor() as i32 - start_z - 1.max(0); self.create_tunnel(
// let max_z = (z - width).floor() as i32 - start_z - 1.min(15); 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 = impl CaveCarver {
// (y + height).floor() as i32 + 1.min(min_y as i32 + chunk_height as i32 - 1 - 7); 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 { #[allow(clippy::too_many_arguments)]
// let x_offset = chunk_pos::start_block_x(chunk_pos) + current_x; fn create_tunnel(
// let x_offwidth = (x_offset as f64 + 0.5 - x) / width; &self,
// for current_z in max_z..z_start { config: &CarverConfig,
// let z_offset = chunk_pos::start_block_z(chunk_pos) + current_z; chunk: &mut ProtoChunk,
// let z_offwidth = (z_offset as f64 + 0.5 - z) / width; 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 { for current_step in step..dist {
// continue; let horizontal_radius =
// } 1.5 + (PI * current_step as f32 / dist as f32).sin() * thickness;
// for current_y in init_height..end_height { let vertical_radius = horizontal_radius as f64 * y_scale;
// let y_offwidth = (current_y as f64 - 0.5 - y) / height; let cos_x = vertical_rotation.cos();
// if Self::is_pos_excluded( x += (horizontal_rotation.cos() * cos_x) as f64;
// Vector3::new(x_offwidth, y_offwidth, z_offwidth), y += vertical_rotation.sin() as f64;
// floor_level, z += (horizontal_rotation.sin() * cos_x) as f64;
// ) {
// continue;
// }
// mask.set(current_x, current_y, current_z);
// }
// }
// }
// }
// fn carve_at_point(&self, chunk: &ProtoChunk, pos: Vector3<i32>, min_y: i8, height: u16) { vertical_rotation *= if steep { 0.92 } else { 0.7 };
// let state = chunk.get_block_state(&pos); 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 { if current_step == split_point && thickness > 1.0 {
// // TODO self.create_tunnel(
// // } config,
// let state = self.get_state(pos, min_y, height); 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<i32>, min_y: i8, height: u16) -> Option<BlockState> { if random.next_bounded_i32(4) != 0 {
// if pos.y <= self.lava_level.get_y(min_y, height) as i32 { if !self.can_reach(chunk.x, chunk.z, x, z, current_step, dist, thickness) {
// return Some(block_state!("lava")); return;
// } }
// None
// }
// fn is_pos_excluded(scaled: Vector3<f64>, floor_y: f64) -> bool { self.carve_ellipsoid(
// if scaled.y <= floor_y { chunk,
// return true; config,
// } x,
// scaled.x * scaled.x + scaled.y * scaled.y + scaled.z * scaled.z >= 1.0 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 { #[allow(clippy::too_many_arguments)]
// let mut width = random.next_f32() * 2.0 + random.next_f32(); fn can_reach(
// if random.next_bounded_i32(10) == 0 { &self,
// width *= random.next_f32() * random.next_f32() * 3.0 + 1.0; chunk_x: i32,
// } chunk_z: i32,
// width 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)
}
}
}

View File

@ -1,29 +1,41 @@
// use pumpkin_util::math::vector3::Vector3; #[derive(Clone)]
pub struct CarvingMask {
min_y: i32,
mask: Vec<u64>,
}
// pub struct CarvingMask { impl CarvingMask {
// min_y: i8, #[must_use]
// mask: Vec<Vector3<i32>>, 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 { fn get_bit_index(&self, x: i32, y: i32, z: i32) -> usize {
// pub fn new(height: i16, min_y: i8) -> Self { // x: 0..16, z: 0..16, y: min_y..min_y+height
// Self { let rel_x = x & 15;
// min_y, let rel_z = z & 15;
// mask: vec![Vector3::new(0, 0, 0); 256 * height as usize], 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 { pub fn set(&mut self, x: i32, y: i32, z: i32) {
// (offset_x & 0xF | (offset_z & 0xF) << 4 | (y - self.min_y as i32) << 8) as usize 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) { #[must_use]
// let index = self.get_index(offset_x, y, offset_z); pub fn get(&self, x: i32, y: i32, z: i32) -> bool {
// self.mask[index] = Vector3::new(offset_x, y, offset_z) let bit_index = self.get_bit_index(x, y, z);
// } let u64_idx = bit_index / 64;
let bit_in_u64 = bit_index % 64;
// pub fn get(&mut self, offset_x: i32, y: i32, offset_z: i32) -> bool { (self.mask[u64_idx] >> bit_in_u64) & 1 != 0
// let index = self.get_index(offset_x, y, offset_z); }
// self.mask.get(index).is_some() }
// }
// }

View File

@ -1,2 +1,97 @@
mod cave; pub mod canyon;
mod mask; 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<i32>,
carver_chunk_pos: &Vector2<i32>,
);
}
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
}

View File

@ -168,6 +168,7 @@ pub struct ProtoChunk {
bottom_y: i8, bottom_y: i8,
pub stage: StagedChunkEnum, pub stage: StagedChunkEnum,
pub light: ChunkLight, pub light: ChunkLight,
pub carving_mask: crate::generation::carver::mask::CarvingMask,
/// Block entities pending creation when the chunk is finalized. /// Block entities pending creation when the chunk is finalized.
/// These are created from structure templates during world generation. /// These are created from structure templates during world generation.
pub pending_block_entities: Vec<NbtCompound>, pub pending_block_entities: Vec<NbtCompound>,
@ -246,6 +247,10 @@ impl ProtoChunk {
.map(|_| LightContainer::new_empty(0)) .map(|_| LightContainer::new_empty(0))
.collect(), .collect(),
}, },
carving_mask: crate::generation::carver::mask::CarvingMask::new(
height as i32,
dimension.min_y,
),
pending_block_entities: Vec::new(), pending_block_entities: Vec::new(),
} }
} }
@ -630,6 +635,14 @@ impl ProtoChunk {
self.stage = StagedChunkEnum::Surface; 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( pub fn populate_biomes(
&mut self, &mut self,
generator: &super::generator::VanillaGenerator, generator: &super::generator::VanillaGenerator,

View File

@ -94,6 +94,7 @@ pub struct BedrockClient {
//input_sequence_number: AtomicU32, //input_sequence_number: AtomicU32,
received_sequences: Mutex<HashSet<u32>>, received_sequences: Mutex<HashSet<u32>>,
pending_acks: Mutex<Vec<u32>>, pending_acks: Mutex<Vec<u32>>,
#[allow(clippy::type_complexity)]
unacked_outgoing_frames: Mutex<HashMap<u32, (u8, Vec<u8>, std::time::Instant)>>, unacked_outgoing_frames: Mutex<HashMap<u32, (u8, Vec<u8>, std::time::Instant)>>,
expected_order_index: Mutex<HashMap<u8, u32>>, expected_order_index: Mutex<HashMap<u8, u32>>,
highest_sequence_index: Mutex<HashMap<u8, u32>>, highest_sequence_index: Mutex<HashMap<u8, u32>>,
@ -191,8 +192,7 @@ impl BedrockClient {
.await .await
.write_packet(&packet_data, client.address, &client.socket) .write_packet(&packet_data, client.address, &client.socket)
.await .await
{ && !client.close_token.is_cancelled() {
if !client.close_token.is_cancelled() {
warn!("Failed to send packet to client: {err}",); warn!("Failed to send packet to client: {err}",);
client.close_token.cancel(); client.close_token.cancel();
break; break;
@ -200,7 +200,6 @@ impl BedrockClient {
} }
} }
} }
}
}); });
} }
@ -489,8 +488,8 @@ impl BedrockClient {
debug!("Received NACK for sequences: {:?}", nack.sequences); debug!("Received NACK for sequences: {:?}", nack.sequences);
let unacked = self.unacked_outgoing_frames.lock().await; let unacked = self.unacked_outgoing_frames.lock().await;
for seq in &nack.sequences { for seq in &nack.sequences {
if let Some((_id, data, _timestamp)) = unacked.get(seq) { if let Some((_id, data, _timestamp)) = unacked.get(seq)
if let Err(err) = self && let Err(err) = self
.network_writer .network_writer
.lock() .lock()
.await .await
@ -501,7 +500,6 @@ impl BedrockClient {
} }
} }
} }
}
async fn handle_frame_set( async fn handle_frame_set(
self: &Arc<Self>, self: &Arc<Self>,