add Chunk features (#685)

* inital chunk features

* add more things

great title i know

* add some features

still no result, but we are getting there...

* make it parse

* add simple BlockPredicate

* add simple random selector

* new things

* change stuff

* base tree impl

* change things

* add more heightmaps

* fix master merge

* steps towards proper random

* fix upper level block placement logic

* Make it compile

* fix block dir serde name

* Added Tree Foliage

* more Tree work

* Implement would_survive check

I don't say its good, but it works :D

* Fixed biome placement modifier (#836)

* Implement all tree foliages

* Fix grass, flowers, seagrass, seapickles...

* Add Ores

* add Bamboo, Vine, Spring feature

Also more fixes

* Corals

* Add Tree decoration & more

Many many fixes

* fix vines

* fixed bamboo

* fix some clippy warns

* fix trees outside of chunk

* make it compile again

upsi

* fix clippy warns

* fix InsideWorldBoundsBlockPredicate

* set dirt under tress trunk

dirt, not grass

* more trees work

---------

Co-authored-by: kralverde <github@email.kralverde.dev>
Co-authored-by: Laurent Stéphenne <laurent@guibi.dev>
This commit is contained in:
Alexander Medvedev
2025-06-05 17:03:52 +02:00
committed by GitHub
parent 00df0d7ee7
commit abccd3f700
196 changed files with 8281 additions and 1006 deletions

493
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@ futures = "0.3"
rayon = "1.10"
crossbeam = "0.8"
uuid = { version = "1.17", features = ["serde", "v3", "v4"] }
uuid = { version = "1.16", features = ["serde", "v3", "v4"] }
derive_more = { version = "2.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@@ -275,15 +275,20 @@ impl ToTokens for BlockPropertyStruct {
}
}
#[inline]
fn handles_block_id(block_id: u16) -> bool where Self: Sized {
[#(#block_ids),*].contains(&block_id)
}
fn to_state_id(&self, block: &Block) -> u16 {
if ![#(#block_ids),*].contains(&block.id) {
if !Self::handles_block_id(block.id) {
panic!("{} is not a valid block for {}", &block.name, #struct_name);
}
block.states[0].id + self.to_index()
}
fn from_state_id(state_id: u16, block: &Block) -> Self {
if ![#(#block_ids),*].contains(&block.id) {
if !Self::handles_block_id(block.id) {
panic!("{} is not a valid block for {}", &block.name, #struct_name);
}
if state_id >= block.states[0].id && state_id <= block.states.last().unwrap().id {
@@ -295,7 +300,7 @@ impl ToTokens for BlockPropertyStruct {
}
fn default(block: &Block) -> Self {
if ![#(#block_ids),*].contains(&block.id) {
if !Self::handles_block_id(block.id) {
panic!("{} is not a valid block for {}", &block.name, #struct_name);
}
Self::from_state_id(block.default_state_id, block)
@@ -307,7 +312,6 @@ impl ToTokens for BlockPropertyStruct {
#(#to_props_values)*
props
}
fn from_props(props: Vec<(&str, &str)>, block: &Block) -> Self {
if ![#(#block_ids),*].contains(&block.id) {
panic!("{} is not a valid block for {}", &block.name, #struct_name);
@@ -1316,6 +1320,9 @@ pub(crate) fn build() -> TokenStream {
// Convert an index back to properties.
fn from_index(index: u16) -> Self where Self: Sized;
// Check if a block uses this property
fn handles_block_id(block_id: u16) -> bool where Self: Sized;
// Convert properties to a state id.
fn to_state_id(&self, block: &Block) -> u16;
// Convert a state id back to properties.

View File

@@ -1,20 +1,13 @@
use std::collections::HashMap;
use proc_macro2::TokenStream;
use pumpkin_util::DoublePerlinNoiseParametersCodec;
use quote::{format_ident, quote};
use serde::Deserialize;
#[derive(Deserialize)]
pub struct DoublePerlinNoiseParameters {
#[serde(rename = "firstOctave")]
first_octave: i32,
amplitudes: Vec<f64>,
}
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/noise_parameters.json");
let json: HashMap<String, DoublePerlinNoiseParameters> =
let json: HashMap<String, DoublePerlinNoiseParametersCodec> =
serde_json::from_str(include_str!("../../assets/noise_parameters.json"))
.expect("Failed to parse noise_parameters.json");
let mut variants = TokenStream::new();

View File

@@ -1,8 +1,13 @@
use crate::block_properties::{Axis, Facing, HorizontalFacing};
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::{
math::vector3::Vector3,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
#[repr(u8)]
#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq)]
#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BlockDirection {
Down = 0,
Up,
@@ -54,6 +59,10 @@ impl BlockDirection {
}
}
pub fn random(random: &mut RandomGenerator) -> Self {
Self::all()[random.next_bounded_i32(Self::all().len() as i32 - 1) as usize]
}
pub fn by_index(index: usize) -> Option<Self> {
Self::all().get(index % Self::all().len()).cloned()
}

View File

@@ -1,5 +1,11 @@
use std::io::Write;
use crate::{
ClientPacket,
codec::identifier::Identifier,
ser::{NetworkWriteExt, WritingError},
};
use pumpkin_data::{
block_properties::get_block,
fluid::Fluid,
@@ -8,12 +14,6 @@ use pumpkin_data::{
};
use pumpkin_macros::packet;
use crate::{
ClientPacket,
codec::identifier::Identifier,
ser::{NetworkWriteExt, WritingError},
};
#[packet(CONFIG_UPDATE_TAGS)]
pub struct CUpdateTags<'a> {
tags: &'a [pumpkin_data::tag::RegistryKey],

View File

@@ -107,6 +107,13 @@ impl<T> Index<usize> for MutableSplitSlice<'_, T> {
}
}
#[derive(Deserialize, Clone)]
pub struct DoublePerlinNoiseParametersCodec {
#[serde(rename = "firstOctave")]
pub first_octave: i32,
pub amplitudes: Vec<f64>,
}
impl<T> IndexMut<usize> for MutableSplitSlice<'_, T> {
#[allow(clippy::comparison_chain)]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {

View File

@@ -3,12 +3,23 @@ use quote::{ToTokens, quote};
use serde::Deserialize;
use syn::LitInt;
use crate::random::{RandomGenerator, RandomImpl};
use super::pool::{Pool, Weighted};
#[derive(Deserialize, Clone, Debug)]
#[serde(tag = "type")]
pub enum NormalIntProvider {
#[serde(rename = "minecraft:uniform")]
Uniform(UniformIntProvider),
// TODO: Add more...
#[serde(rename = "minecraft:weighted_list")]
WeightedList(WeightedListIntProvider),
#[serde(rename = "minecraft:clamped")]
Clamped(ClampedIntProvider),
#[serde(rename = "minecraft:clamped_normal")]
ClampedNormal(ClampedNormalIntProvider),
#[serde(rename = "minecraft:biased_to_bottom")]
BiasedToBottom(BiasedToBottomIntProvider), // TODO: Add more...
}
impl ToTokens for NormalIntProvider {
@@ -19,6 +30,10 @@ impl ToTokens for NormalIntProvider {
NormalIntProvider::Uniform(#uniform)
});
}
NormalIntProvider::WeightedList(_) => todo!(),
NormalIntProvider::Clamped(_) => todo!(),
NormalIntProvider::BiasedToBottom(_biased_to_bottom_int_provider) => todo!(),
NormalIntProvider::ClampedNormal(_clamped_int_provider) => todo!(),
}
}
}
@@ -50,15 +65,23 @@ impl IntProvider {
match self {
IntProvider::Object(int_provider) => match int_provider {
NormalIntProvider::Uniform(uniform) => uniform.get_min(),
NormalIntProvider::WeightedList(provider) => provider.get_min(),
NormalIntProvider::Clamped(provider) => provider.get_min(),
NormalIntProvider::BiasedToBottom(provider) => provider.get_min(),
NormalIntProvider::ClampedNormal(provider) => provider.get_min(),
},
IntProvider::Constant(i) => *i,
}
}
pub fn get(&self) -> i32 {
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
match self {
IntProvider::Object(int_provider) => match int_provider {
NormalIntProvider::Uniform(uniform) => uniform.get(),
NormalIntProvider::Uniform(uniform) => uniform.get(random),
NormalIntProvider::WeightedList(provider) => provider.get(random),
NormalIntProvider::Clamped(provider) => provider.get(random),
NormalIntProvider::BiasedToBottom(provider) => provider.get(random),
NormalIntProvider::ClampedNormal(provider) => provider.get(random),
},
IntProvider::Constant(i) => *i,
}
@@ -68,12 +91,108 @@ impl IntProvider {
match self {
IntProvider::Object(int_provider) => match int_provider {
NormalIntProvider::Uniform(uniform) => uniform.get_max(),
NormalIntProvider::WeightedList(provider) => provider.get_max(),
NormalIntProvider::Clamped(provider) => provider.get_max(),
NormalIntProvider::BiasedToBottom(provider) => provider.get_max(),
NormalIntProvider::ClampedNormal(provider) => provider.get_max(),
},
IntProvider::Constant(i) => *i,
}
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct ClampedNormalIntProvider {
mean: f32,
deviation: f32,
min_inclusive: i32,
max_inclusive: i32,
}
impl ClampedNormalIntProvider {
pub fn get_min(&self) -> i32 {
self.min_inclusive
}
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
(self.mean + random.next_gaussian() as f32 * self.deviation)
.clamp(self.min_inclusive as f32, self.max_inclusive as f32) as i32
}
pub fn get_max(&self) -> i32 {
self.max_inclusive
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct BiasedToBottomIntProvider {
min_inclusive: i32,
max_inclusive: i32,
}
impl BiasedToBottomIntProvider {
pub fn get_min(&self) -> i32 {
self.min_inclusive
}
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
// TODO: not sure if this is called first this matches vanilla
let first_gen = random.next_bounded_i32(self.max_inclusive - self.min_inclusive + 1) + 1;
self.min_inclusive + random.next_bounded_i32(first_gen)
}
pub fn get_max(&self) -> i32 {
self.max_inclusive
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct ClampedIntProvider {
source: Box<IntProvider>,
min_inclusive: i32,
max_inclusive: i32,
}
impl ClampedIntProvider {
pub fn get_min(&self) -> i32 {
self.min_inclusive.max(self.source.get_min())
}
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
self.source
.get(random)
.clamp(self.min_inclusive, self.max_inclusive)
}
pub fn get_max(&self) -> i32 {
self.max_inclusive.min(self.source.get_max())
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct WeightedListIntProvider {
distribution: Vec<Weighted<IntProvider>>,
}
impl WeightedListIntProvider {
pub fn get_min(&self) -> i32 {
let mut min = i32::MAX;
for dist in &self.distribution {
let dmin = dist.data.get_min();
min = min.min(dmin);
}
min
}
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
if let Some(int) = Pool.get(&self.distribution, random) {
return int.get(random);
}
0
}
pub fn get_max(&self) -> i32 {
let mut max = i32::MIN;
for dist in &self.distribution {
let dmax = dist.data.get_max();
max = max.max(dmax);
}
max
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct UniformIntProvider {
pub min_inclusive: i32,
@@ -95,8 +214,8 @@ impl UniformIntProvider {
pub fn get_min(&self) -> i32 {
self.min_inclusive
}
pub fn get(&self) -> i32 {
rand::random_range(self.min_inclusive..self.max_inclusive)
pub fn get(&self, random: &mut RandomGenerator) -> i32 {
random.next_inbetween_i32(self.min_inclusive, self.max_inclusive)
}
pub fn get_max(&self) -> i32 {
self.max_inclusive

View File

@@ -4,6 +4,7 @@ pub mod boundingbox;
pub mod experience;
pub mod float_provider;
pub mod int_provider;
pub mod pool;
pub mod position;
pub mod vector2;
pub mod vector3;

View File

@@ -0,0 +1,55 @@
use serde::Deserialize;
use crate::random::{RandomGenerator, RandomImpl};
#[derive(Deserialize, Clone, Debug)]
pub struct Pool;
impl Pool {
pub fn get<E: Clone>(
&self,
distribution: &[Weighted<E>],
random: &mut RandomGenerator,
) -> Option<E> {
let mut total_weight = 0;
for dist in distribution {
total_weight += dist.weight;
}
let index = random.next_bounded_i32(total_weight);
if total_weight < 64 {
return Some(FlattenedContent::get(index, distribution, total_weight));
} else {
// WrappedContent
for dist in distribution {
if index - dist.weight >= 0 {
continue;
}
return Some(dist.data.clone());
}
}
None
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct Weighted<E> {
pub data: E,
pub weight: i32,
}
struct FlattenedContent;
impl FlattenedContent {
pub fn get<E: Clone>(index: i32, entries: &[Weighted<E>], total_weight: i32) -> E {
let mut final_entries = Vec::with_capacity(total_weight as usize);
let mut cur_index = 0;
for entry in entries {
let weight = entry.weight;
for i in cur_index..cur_index + weight {
final_entries.insert(i as usize, entry.data.clone());
}
cur_index += weight;
}
final_entries[index as usize].clone()
}
}

View File

@@ -156,6 +156,10 @@ impl BlockPos {
BlockPos(self.0 + offset)
}
pub fn add(&self, x: i32, y: i32, z: i32) -> Self {
BlockPos::new(self.0.x + x, self.0.y + y, self.0.z + z)
}
pub fn offset_dir(&self, offset: Vector3<i32>, direction: i32) -> Self {
BlockPos(Vector3::new(
self.0.x + offset.x * direction,
@@ -168,9 +172,17 @@ impl BlockPos {
self.offset(Vector3::new(0, 1, 0))
}
pub fn up_height(&self, height: i32) -> Self {
self.offset(Vector3::new(0, height, 0))
}
pub fn down(&self) -> Self {
self.offset(Vector3::new(0, -1, 0))
}
pub fn down_height(&self, height: i32) -> Self {
self.offset(Vector3::new(0, -height, 0))
}
}
impl Serialize for BlockPos {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>

View File

@@ -239,6 +239,39 @@ impl Math for i32 {}
impl Math for i64 {}
impl Math for u8 {}
impl<'de> serde::Deserialize<'de> for Vector3<i32> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Vector3Visitor;
impl<'de> serde::de::Visitor<'de> for Vector3Visitor {
type Value = Vector3<i32>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid Vector<i32>")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
if let Some(x) = seq.next_element::<i32>()? {
if let Some(y) = seq.next_element::<i32>()? {
if let Some(z) = seq.next_element::<i32>()? {
return Ok(Vector3::new(x, y, z));
}
}
}
Err(serde::de::Error::custom("Failed to read Vector<i32>"))
}
}
deserializer.deserialize_seq(Vector3Visitor)
}
}
impl<'de> serde::Deserialize<'de> for Vector3<f32> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where

View File

@@ -3,7 +3,7 @@ use super::{
hash_block_pos,
};
use crate::math::java_string_hash;
use crate::{math::java_string_hash, population_seed_fn};
pub struct LegacyRand {
seed: u64,
@@ -11,6 +11,8 @@ pub struct LegacyRand {
}
impl LegacyRand {
population_seed_fn!();
pub fn from_seed(seed: u64) -> Self {
LegacyRand {
seed: (seed ^ 0x5DEECE66D) & 0xFFFFFFFFFFFF,

View File

@@ -44,6 +44,30 @@ pub enum RandomDeriver {
Legacy(LegacySplitter),
}
// TODO: Write unit test for this
#[macro_export]
macro_rules! population_seed_fn {
() => {
pub fn get_population_seed(world_seed: u64, block_x: i32, block_z: i32) -> u64 {
let mut rand = Self::from_seed(world_seed);
let l = rand.next_i64() | 1;
let m = rand.next_i64() | 1;
let base = (block_x as i64)
.wrapping_mul(l)
.wrapping_add((block_z as i64).wrapping_mul(m));
(base as u64) ^ world_seed
}
};
}
// TODO: Write unit test for this
#[inline]
pub fn get_decorator_seed(population_seed: u64, index: usize, step: usize) -> u64 {
population_seed
.wrapping_add(index as u64)
.wrapping_add(10_000u64.wrapping_mul(step as u64))
}
#[enum_dispatch]
pub trait RandomImpl {
fn split(&mut self) -> Self;

View File

@@ -1,3 +1,5 @@
use crate::population_seed_fn;
use super::{
RandomDeriver, RandomDeriverImpl, RandomGenerator, RandomImpl, gaussian::GaussianGenerator,
hash_block_pos,
@@ -10,6 +12,8 @@ pub struct Xoroshiro {
}
impl Xoroshiro {
population_seed_fn!();
pub fn from_seed(seed: u64) -> Self {
let (lo, hi) = Self::mix_u64(seed);
let lo = mix_stafford_13(lo);

View File

@@ -1,8 +1,16 @@
use std::{fs, path::PathBuf, sync::Arc};
use async_trait::async_trait;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use pumpkin_util::math::vector2::Vector2;
use pumpkin_world::{chunk::ChunkData, dimension::Dimension, global_path, level::Level};
use pumpkin_data::BlockDirection;
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use pumpkin_world::{
chunk::ChunkData,
dimension::Dimension,
global_path,
level::Level,
world::{BlockAccessor, BlockRegistryExt},
};
use tokio::{runtime::Runtime, sync::RwLock};
async fn test_reads(level: &Arc<Level>, positions: Vec<Vector2<i32>>) {
@@ -69,6 +77,21 @@ const MAX_CHUNK: i32 = 16;
// How many chunks to use on parallel tests
//const CHUNKS_ON_PARALLEL: usize = 32;
struct BlockRegistry;
#[async_trait]
impl BlockRegistryExt for BlockRegistry {
async fn can_place_at(
&self,
_block: &pumpkin_data::Block,
_block_accessor: &dyn BlockAccessor,
_block_pos: &BlockPos,
_face: BlockDirection,
) -> bool {
true
}
}
fn initialize_level(
async_handler: &Runtime,
root_dir: PathBuf,
@@ -78,10 +101,12 @@ fn initialize_level(
let mut chunks = Vec::new();
async_handler.block_on(async {
let (send, mut recv) = tokio::sync::mpsc::unbounded_channel();
let block_registry = Arc::new(BlockRegistry);
// Our data dir is empty, so we're generating new chunks here
let level_to_save = Arc::new(Level::from_root_folder(
root_dir.clone(),
block_registry,
123,
Dimension::Overworld,
));
@@ -193,6 +218,8 @@ fn bench_chunk_io(c: &mut Criterion) {
n_chunks,
chunks.len()
);
let block_registry = Arc::new(BlockRegistry);
write_group.bench_with_input(
BenchmarkId::new("Single", n_chunks),
&chunks,
@@ -200,6 +227,7 @@ fn bench_chunk_io(c: &mut Criterion) {
b.to_async(&async_handler).iter(async || {
let level = Arc::new(Level::from_root_folder(
root_dir.clone(),
block_registry.clone(),
123,
Dimension::Overworld,
));
@@ -222,6 +250,7 @@ fn bench_chunk_io(c: &mut Criterion) {
n_chunks,
positions.len()
);
let block_registry = Arc::new(BlockRegistry);
read_group.bench_with_input(
BenchmarkId::new("Single", n_chunks),
@@ -230,6 +259,7 @@ fn bench_chunk_io(c: &mut Criterion) {
b.to_async(&async_handler).iter(async || {
let level = Arc::new(Level::from_root_folder(
root_dir.clone(),
block_registry.clone(),
123,
Dimension::Overworld,
));

View File

@@ -1,14 +1,46 @@
pub mod entities;
pub mod state;
use serde::Deserialize;
use std::collections::HashMap;
use pumpkin_data::{
BlockState,
block_properties::{get_block, get_state_by_state_id},
};
use serde::{Deserialize, Serialize};
pub use state::RawBlockState;
#[derive(Deserialize)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct BlockStateCodec {
/// Block name
pub name: String,
// TODO: properties...
/// Key-value pairs of properties
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<HashMap<String, String>>,
}
impl BlockStateCodec {
pub fn get_state(&self) -> Option<BlockState> {
let block = get_block(self.name.as_str());
if let Some(block) = block {
let mut state_id = block.default_state_id;
if let Some(properties) = &self.properties {
let mut properties_vec: Vec<(&str, &str)> = Vec::with_capacity(properties.len());
for (key, value) in properties {
properties_vec.push((key, value));
}
let block_properties = block.from_properties(properties_vec).unwrap();
state_id = block_properties.to_state_id(&block);
}
return get_state_by_state_id(state_id);
}
None
}
}
#[cfg(test)]

View File

@@ -1,6 +1,6 @@
use pumpkin_data::block_properties::{get_block, get_block_by_state_id, get_state_by_state_id};
use crate::{BlockStateId, chunk::format::PaletteBlockEntry};
use crate::BlockStateId;
/// Instead of using a memory heavy normal BlockState This is used for internal representation in chunks to save memory
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -19,27 +19,6 @@ impl RawBlockState {
})
}
pub fn from_palette(palette: &PaletteBlockEntry) -> Option<Self> {
let block = get_block(palette.name.as_str());
if let Some(block) = block {
let mut state_id = block.default_state_id;
if let Some(properties) = &palette.properties {
let mut properties_vec: Vec<(&str, &str)> = Vec::with_capacity(properties.len());
for (key, value) in properties {
properties_vec.push((key, value));
}
let block_properties = block.from_properties(properties_vec).unwrap();
state_id = block_properties.to_state_id(&block);
}
return Some(Self { state_id });
}
None
}
pub fn to_state(&self) -> pumpkin_data::BlockState {
get_state_by_state_id(self.state_id).unwrap()
}

View File

@@ -901,7 +901,10 @@ pub async fn chunk_to_bytes(chunk_data: &ChunkData) -> Result<Vec<u8>, ChunkSeri
#[cfg(test)]
mod tests {
use async_trait::async_trait;
use pumpkin_config::{AdvancedConfiguration, advanced_config, override_config_for_testing};
use pumpkin_data::BlockDirection;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2;
use std::fs;
use std::path::PathBuf;
@@ -914,7 +917,23 @@ mod tests {
use crate::chunk::io::{ChunkIO, LoadedData};
use crate::dimension::Dimension;
use crate::generation::{Seed, get_world_gen};
use crate::level::{LevelFolder, SyncChunk};
use crate::level::{Level, LevelFolder, SyncChunk};
use crate::world::{BlockAccessor, BlockRegistryExt};
struct BlockRegistry;
#[async_trait]
impl BlockRegistryExt for BlockRegistry {
async fn can_place_at(
&self,
_block: &pumpkin_data::Block,
_block_accessor: &dyn BlockAccessor,
_block_pos: &BlockPos,
_face: BlockDirection,
) -> bool {
true
}
}
async fn get_chunks(
saver: &ChunkFileManager<AnvilChunkFile>,
@@ -992,13 +1011,22 @@ mod tests {
};
fs::create_dir(&level_folder.region_folder).expect("couldn't create region folder");
let chunk_saver = ChunkFileManager::<AnvilChunkFile>::default();
let block_registry = Arc::new(BlockRegistry);
// Generate chunks
let mut chunks = vec![];
let level = Arc::new(Level::from_root_folder(
temp_dir.path().to_path_buf(),
block_registry.clone(),
0,
Dimension::Overworld,
));
for x in -5..5 {
for y in -5..5 {
let position = Vector2::new(x, y);
let chunk = generator.generate_chunk(&position);
let chunk = generator
.generate_chunk(&level, block_registry.as_ref(), &position)
.await;
chunks.push((position, Arc::new(RwLock::new(chunk))));
}
}
@@ -1260,13 +1288,22 @@ mod tests {
};
fs::create_dir(&level_folder.region_folder).expect("couldn't create region folder");
let chunk_saver = ChunkFileManager::<AnvilChunkFile>::default();
let block_registry = Arc::new(BlockRegistry);
// Generate chunks
let mut chunks = vec![];
let level = Arc::new(Level::from_root_folder(
temp_dir.path().to_path_buf(),
block_registry.clone(),
0,
Dimension::Overworld,
));
for x in -5..5 {
for y in -5..5 {
let position = Vector2::new(x, y);
let chunk = generator.generate_chunk(&position);
let chunk = generator
.generate_chunk(&level, block_registry.as_ref(), &position)
.await;
chunks.push((position, Arc::new(RwLock::new(chunk))));
}
}

View File

@@ -365,7 +365,10 @@ impl ChunkSerializer for LinearFile {
#[cfg(test)]
mod tests {
use async_trait::async_trait;
use core::panic;
use pumpkin_data::BlockDirection;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector2::Vector2;
use std::fs;
use std::path::PathBuf;
@@ -378,7 +381,23 @@ mod tests {
use crate::chunk::io::{ChunkIO, LoadedData};
use crate::dimension::Dimension;
use crate::generation::{Seed, get_world_gen};
use crate::level::LevelFolder;
use crate::level::{Level, LevelFolder};
use crate::world::{BlockAccessor, BlockRegistryExt};
struct BlockRegistry;
#[async_trait]
impl BlockRegistryExt for BlockRegistry {
async fn can_place_at(
&self,
_block: &pumpkin_data::Block,
_block_accessor: &dyn BlockAccessor,
_block_pos: &BlockPos,
_face: BlockDirection,
) -> bool {
true
}
}
#[tokio::test(flavor = "multi_thread")]
async fn not_existing() {
@@ -419,13 +438,21 @@ mod tests {
};
fs::create_dir(&level_folder.region_folder).expect("couldn't create region folder");
let chunk_saver = ChunkFileManager::<LinearFile>::default();
let block_registry = Arc::new(BlockRegistry);
// Generate chunks
let mut chunks = vec![];
let level = Arc::new(Level::from_root_folder(
temp_dir.path().to_path_buf(),
block_registry.clone(),
0,
Dimension::Overworld,
));
for x in -5..5 {
for y in -5..5 {
let position = Vector2::new(x, y);
let chunk = generator.generate_chunk(&position);
let chunk = generator
.generate_chunk(&level, block_registry.as_ref(), &position)
.await;
chunks.push((position, Arc::new(RwLock::new(chunk))));
}
}

View File

@@ -7,6 +7,8 @@ use crate::{block::entities::block_entity_from_nbt, generation::section_coords};
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use serde::{Deserialize, Serialize};
use crate::block::BlockStateCodec;
use super::{
ChunkData, ChunkHeightmaps, ChunkLight, ChunkParsingError, ChunkSections, ScheduledTick,
SubChunk, TickPriority,
@@ -216,7 +218,7 @@ pub struct ChunkSectionBlockStates {
skip_serializing_if = "Option::is_none"
)]
pub(crate) data: Option<Box<[i64]>>,
pub(crate) palette: Vec<PaletteBlockEntry>,
pub(crate) palette: Vec<BlockStateCodec>,
}
#[derive(Debug, Clone)]
@@ -293,16 +295,6 @@ impl Default for LightContainer {
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct PaletteBlockEntry {
/// Block name
pub name: String,
/// Key-value pairs of properties
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<HashMap<String, String>>,
}
#[derive(Serialize, Deserialize, Debug)]
struct SerializedScheduledTick {
#[serde(rename = "x")]

View File

@@ -224,7 +224,7 @@ impl ChunkSections {
block_state: BlockStateId,
) {
let y = y - self.min_y;
debug_assert!(y >= 0);
debug_assert!(y > 0);
let relative_y = y as usize;
self.set_relative_block(relative_x, relative_y, relative_z, block_state);

View File

@@ -7,11 +7,9 @@ use std::{
use pumpkin_data::{Block, block_properties::get_state_by_state_id, chunk::Biome};
use pumpkin_util::encompassing_bits;
use crate::block::RawBlockState;
use crate::block::BlockStateCodec;
use super::format::{
ChunkSectionBiomes, ChunkSectionBlockStates, PaletteBiomeEntry, PaletteBlockEntry,
};
use super::format::{ChunkSectionBiomes, ChunkSectionBlockStates, PaletteBiomeEntry};
/// 3d array indexed by y,z,x
type AbstractCube<T, const DIM: usize> = [[[T; DIM]; DIM]; DIM];
@@ -401,8 +399,8 @@ impl BlockPalette {
.palette
.into_iter()
.map(|entry| {
if let Some(block_state) = RawBlockState::from_palette(&entry) {
block_state.state_id
if let Some(block_state) = entry.get_state() {
block_state.id
} else {
log::warn!(
"Could not find valid block state for {}. Defaulting...",
@@ -436,10 +434,10 @@ impl BlockPalette {
}
}
fn block_state_id_to_palette_entry(registry_id: u16) -> PaletteBlockEntry {
fn block_state_id_to_palette_entry(registry_id: u16) -> BlockStateCodec {
let block = Block::from_state_id(registry_id).unwrap();
PaletteBlockEntry {
BlockStateCodec {
name: block.name.into(),
properties: {
if let Some(properties) = block.properties(registry_id) {

View File

@@ -1,8 +1,8 @@
use std::path::PathBuf;
use std::{path::PathBuf, sync::Arc};
use serde::Deserialize;
use crate::level::Level;
use crate::{level::Level, world::BlockRegistryExt};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
@@ -13,12 +13,17 @@ pub enum Dimension {
}
impl Dimension {
pub fn into_level(&self, mut base_directory: PathBuf, seed: i64) -> Level {
pub fn into_level(
&self,
mut base_directory: PathBuf,
block_registry: Arc<dyn BlockRegistryExt>,
seed: i64,
) -> Level {
match self {
Dimension::Overworld => {}
Dimension::Nether => base_directory.push("DIM-1"),
Dimension::End => base_directory.push("DIM1"),
}
Level::from_root_folder(base_directory, seed, *self)
Level::from_root_folder(base_directory, block_registry, seed, *self)
}
}

View File

@@ -0,0 +1,277 @@
use itertools::Itertools;
use pumpkin_data::{
Block, BlockDirection, BlockState, block_properties::get_block_by_state_id, tag::Tagable,
};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use serde::Deserialize;
use crate::{
ProtoChunk, block::BlockStateCodec, generation::height_limit::HeightLimitView,
world::BlockRegistryExt,
};
#[derive(Deserialize)]
pub struct EmptyTODOStruct {}
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum BlockPredicate {
#[serde(rename = "minecraft:matching_blocks")]
MatchingBlocks(MatchingBlocksBlockPredicate),
#[serde(rename = "minecraft:matching_block_tag")]
MatchingBlockTag(MatchingBlockTagPredicate),
#[serde(rename = "minecraft:matching_fluids")]
MatchingFluids(EmptyTODOStruct),
#[serde(rename = "minecraft:has_sturdy_face")]
HasSturdyFace(HasSturdyFacePredicate),
#[serde(rename = "minecraft:solid")]
Solid(SolidBlockPredicate),
#[serde(rename = "minecraft:replaceable")]
Replaceable(ReplaceableBlockPredicate),
#[serde(rename = "minecraft:would_survive")]
WouldSurvive(WouldSurviveBlockPredicate),
#[serde(rename = "minecraft:inside_world_bounds")]
InsideWorldBounds(InsideWorldBoundsBlockPredicate),
#[serde(rename = "minecraft:any_of")]
AnyOf(AnyOfBlockPredicate),
#[serde(rename = "minecraft:all_of")]
AllOf(AllOfBlockPredicate),
#[serde(rename = "minecraft:not")]
Not(NotBlockPredicate),
#[serde(rename = "minecraft:true")]
AlwaysTrue,
/// Not used
#[serde(rename = "minecraft:unobstructed")]
Unobstructed(EmptyTODOStruct),
}
impl BlockPredicate {
pub async fn test(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &ProtoChunk<'_>,
pos: &BlockPos,
) -> bool {
match self {
BlockPredicate::MatchingBlocks(predicate) => predicate.test(chunk, pos),
BlockPredicate::MatchingBlockTag(predicate) => predicate.test(chunk, pos),
BlockPredicate::MatchingFluids(_predicate) => false,
BlockPredicate::HasSturdyFace(predicate) => predicate.test(chunk, pos),
BlockPredicate::Solid(predicate) => predicate.test(chunk, pos),
BlockPredicate::Replaceable(predicate) => predicate.test(chunk, pos),
BlockPredicate::WouldSurvive(predicate) => {
predicate.test(block_registry, chunk, pos).await
}
BlockPredicate::InsideWorldBounds(predicate) => predicate.test(chunk, pos),
BlockPredicate::AnyOf(predicate) => predicate.test(block_registry, chunk, pos).await,
BlockPredicate::AllOf(predicate) => predicate.test(block_registry, chunk, pos).await,
BlockPredicate::Not(predicate) => predicate.test(block_registry, chunk, pos).await,
BlockPredicate::AlwaysTrue => true,
BlockPredicate::Unobstructed(_predicate) => false,
}
}
}
#[derive(Deserialize)]
pub struct MatchingBlocksBlockPredicate {
#[serde(flatten)]
offset: OffsetBlocksBlockPredicate,
blocks: MatchingBlocksWrapper,
}
impl MatchingBlocksBlockPredicate {
pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool {
let block = self.offset.get_block(chunk, pos);
match &self.blocks {
MatchingBlocksWrapper::Single(single_block) => {
single_block.replace("minecraft:", "") == block.name
}
MatchingBlocksWrapper::Multiple(blocks) => blocks
.iter()
.map(|s| s.replace("minecraft:", ""))
.contains(block.name),
}
}
}
#[derive(Deserialize)]
pub struct InsideWorldBoundsBlockPredicate {
offset: Vector3<i32>,
}
impl InsideWorldBoundsBlockPredicate {
pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool {
let pos = pos.offset(self.offset);
!chunk.out_of_height(pos.0.y as i16)
}
}
#[derive(Deserialize)]
pub struct MatchingBlockTagPredicate {
#[serde(flatten)]
offset: OffsetBlocksBlockPredicate,
tag: String,
}
impl MatchingBlockTagPredicate {
pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool {
let block = self.offset.get_block(chunk, pos);
block.is_tagged_with(&self.tag).unwrap()
}
}
#[derive(Deserialize)]
pub struct HasSturdyFacePredicate {
#[serde(flatten)]
offset: OffsetBlocksBlockPredicate,
direction: BlockDirection,
}
impl HasSturdyFacePredicate {
pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool {
let state = self.offset.get_state(chunk, pos);
state.is_side_solid(self.direction)
}
}
#[derive(Deserialize)]
pub struct AnyOfBlockPredicate {
predicates: Vec<BlockPredicate>,
}
impl AnyOfBlockPredicate {
pub async fn test(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &ProtoChunk<'_>,
pos: &BlockPos,
) -> bool {
for predicate in &self.predicates {
if !Box::pin(predicate.test(block_registry, chunk, pos)).await {
continue;
}
return true;
}
false
}
}
#[derive(Deserialize)]
pub struct AllOfBlockPredicate {
predicates: Vec<BlockPredicate>,
}
impl AllOfBlockPredicate {
pub async fn test(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &ProtoChunk<'_>,
pos: &BlockPos,
) -> bool {
for predicate in &self.predicates {
if Box::pin(predicate.test(block_registry, chunk, pos)).await {
continue;
}
return false;
}
true
}
}
#[derive(Deserialize)]
pub struct NotBlockPredicate {
predicate: Box<BlockPredicate>,
}
impl NotBlockPredicate {
pub async fn test(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &ProtoChunk<'_>,
pos: &BlockPos,
) -> bool {
!Box::pin(self.predicate.test(block_registry, chunk, pos)).await
}
}
#[derive(Deserialize)]
pub struct SolidBlockPredicate {
#[serde(flatten)]
offset: OffsetBlocksBlockPredicate,
}
impl SolidBlockPredicate {
pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool {
let state = self.offset.get_state(chunk, pos);
state.is_solid()
}
}
#[derive(Deserialize)]
pub struct WouldSurviveBlockPredicate {
#[serde(flatten)]
offset: OffsetBlocksBlockPredicate,
state: BlockStateCodec,
}
impl WouldSurviveBlockPredicate {
pub async fn test(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &ProtoChunk<'_>,
pos: &BlockPos,
) -> bool {
let state = self.state.get_state().unwrap();
let pos = self.offset.get(pos);
return block_registry
.can_place_at(
&get_block_by_state_id(state.id).unwrap(),
chunk,
&pos,
BlockDirection::Up,
)
.await;
}
}
#[derive(Deserialize)]
pub struct ReplaceableBlockPredicate {
#[serde(flatten)]
offset: OffsetBlocksBlockPredicate,
}
impl ReplaceableBlockPredicate {
pub fn test(&self, chunk: &ProtoChunk, pos: &BlockPos) -> bool {
let state = self.offset.get_state(chunk, pos);
state.replaceable()
}
}
#[derive(Deserialize)]
pub struct OffsetBlocksBlockPredicate {
offset: Option<Vector3<i32>>,
}
impl OffsetBlocksBlockPredicate {
pub fn get(&self, pos: &BlockPos) -> BlockPos {
if let Some(offset) = self.offset {
return pos.offset(offset);
}
*pos
}
pub fn get_block(&self, chunk: &ProtoChunk, pos: &BlockPos) -> Block {
let pos = self.get(pos);
chunk.get_block_state(&pos.0).to_block()
}
pub fn get_state(&self, chunk: &ProtoChunk, pos: &BlockPos) -> BlockState {
let pos = self.get(pos);
chunk.get_block_state(&pos.0).to_state()
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum MatchingBlocksWrapper {
Single(String),
Multiple(Vec<String>),
}

View File

@@ -0,0 +1,228 @@
use pumpkin_data::{BlockState, chunk::DoublePerlinNoiseParameters};
use pumpkin_util::{
DoublePerlinNoiseParametersCodec,
math::{
clamped_map,
int_provider::IntProvider,
pool::{Pool, Weighted},
position::BlockPos,
vector3::Vector3,
},
random::{RandomGenerator, RandomImpl, legacy_rand::LegacyRand},
};
use serde::Deserialize;
use crate::block::BlockStateCodec;
use super::noise::perlin::DoublePerlinNoiseSampler;
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum BlockStateProvider {
#[serde(rename = "minecraft:simple_state_provider")]
Simple(SimpleStateProvider),
#[serde(rename = "minecraft:weighted_state_provider")]
Weighted(WeightedBlockStateProvider),
#[serde(rename = "minecraft:noise_threshold_provider")]
NoiseThreshold(NoiseThresholdBlockStateProvider),
#[serde(rename = "minecraft:noise_provider")]
NoiseProvider(NoiseBlockStateProvider),
#[serde(rename = "minecraft:dual_noise_provider")]
DualNoise(DualNoiseBlockStateProvider),
#[serde(rename = "minecraft:rotated_block_provider")]
Pillar(PillarBlockStateProvider),
#[serde(rename = "minecraft:randomized_int_state_provider")]
RandomizedInt(RandomizedIntBlockStateProvider),
}
impl BlockStateProvider {
pub fn get(&self, random: &mut RandomGenerator, pos: BlockPos) -> BlockState {
match self {
BlockStateProvider::NoiseThreshold(provider) => provider.get(random, pos),
BlockStateProvider::NoiseProvider(provider) => provider.get(pos),
BlockStateProvider::Simple(provider) => provider.get(pos),
BlockStateProvider::Weighted(provider) => provider.get(random),
BlockStateProvider::DualNoise(provider) => provider.get(pos),
BlockStateProvider::Pillar(provider) => provider.get(pos),
BlockStateProvider::RandomizedInt(provider) => provider.get(random, pos),
}
}
}
#[derive(Deserialize)]
pub struct RandomizedIntBlockStateProvider {
source: Box<BlockStateProvider>,
property: String,
values: IntProvider,
}
impl RandomizedIntBlockStateProvider {
pub fn get(&self, random: &mut RandomGenerator, pos: BlockPos) -> BlockState {
// TODO
self.source.get(random, pos)
}
}
#[derive(Deserialize)]
pub struct PillarBlockStateProvider {
state: BlockStateCodec,
}
impl PillarBlockStateProvider {
pub fn get(&self, _pos: BlockPos) -> BlockState {
// TODO: random axis
self.state.get_state().unwrap()
}
}
#[derive(Deserialize)]
pub struct DualNoiseBlockStateProvider {
#[serde(flatten)]
base: NoiseBlockStateProvider,
variety: [u32; 2],
slow_noise: DoublePerlinNoiseParametersCodec,
slow_scale: f32,
}
impl DualNoiseBlockStateProvider {
pub fn get(&self, pos: BlockPos) -> BlockState {
let noise = perlin_codec_to_static(self.slow_noise.clone());
let sampler = DoublePerlinNoiseSampler::new(
&mut RandomGenerator::Legacy(LegacyRand::from_seed(self.base.base.seed as u64)),
&noise,
false,
);
let slow_noise = self.get_slow_noise(&pos, &sampler);
let mapped = clamped_map(
slow_noise,
-1.0,
1.0,
self.variety[0] as f64,
self.variety[1] as f64 + 1.0,
) as i32;
let mut list = Vec::with_capacity(mapped as usize);
for i in 0..mapped {
let value = self.get_slow_noise(
&BlockPos(pos.0.add(&Vector3::new(i * 54545, 0, i * 34234))),
&sampler,
);
list.push(self.base.get_state_by_value(&self.base.states, value));
}
let value = self.base.base.get_noise(pos);
self.base
.get_state_by_value(&list, value)
.get_state()
.unwrap()
}
fn get_slow_noise(&self, pos: &BlockPos, sampler: &DoublePerlinNoiseSampler) -> f64 {
sampler.sample(
pos.0.x as f64 * self.slow_scale as f64,
pos.0.y as f64 * self.slow_scale as f64,
pos.0.z as f64 * self.slow_scale as f64,
)
}
}
#[derive(Deserialize)]
pub struct WeightedBlockStateProvider {
entries: Vec<Weighted<BlockStateCodec>>,
}
impl WeightedBlockStateProvider {
pub fn get(&self, random: &mut RandomGenerator) -> BlockState {
Pool.get(&self.entries, random)
.unwrap()
.get_state()
.unwrap()
}
}
#[derive(Deserialize)]
pub struct SimpleStateProvider {
state: BlockStateCodec,
}
impl SimpleStateProvider {
pub fn get(&self, _pos: BlockPos) -> BlockState {
self.state.get_state().unwrap()
}
}
#[derive(Deserialize)]
pub struct NoiseBlockStateProviderBase {
seed: i64,
noise: DoublePerlinNoiseParametersCodec,
scale: f32,
}
fn perlin_codec_to_static(noise: DoublePerlinNoiseParametersCodec) -> DoublePerlinNoiseParameters {
let amplitudes_static: &'static [f64] = noise.amplitudes.leak();
DoublePerlinNoiseParameters::new(noise.first_octave, amplitudes_static, "none")
}
impl NoiseBlockStateProviderBase {
pub fn get_noise(&self, pos: BlockPos) -> f64 {
let noise = perlin_codec_to_static(self.noise.clone());
let sampler = DoublePerlinNoiseSampler::new(
&mut RandomGenerator::Legacy(LegacyRand::from_seed(self.seed as u64)),
&noise,
false,
);
sampler.sample(
pos.0.x as f64 * self.scale as f64,
pos.0.y as f64 * self.scale as f64,
pos.0.z as f64 * self.scale as f64,
)
}
}
#[derive(Deserialize)]
pub struct NoiseBlockStateProvider {
#[serde(flatten)]
base: NoiseBlockStateProviderBase,
states: Vec<BlockStateCodec>,
}
impl NoiseBlockStateProvider {
pub fn get(&self, pos: BlockPos) -> BlockState {
let value = self.base.get_noise(pos);
self.get_state_by_value(&self.states, value)
.get_state()
.unwrap()
}
fn get_state_by_value(&self, states: &[BlockStateCodec], value: f64) -> BlockStateCodec {
let val = ((1.0 + value) / 2.0).clamp(0.0, 0.9999);
states[(val * states.len() as f64) as usize].clone()
}
}
#[derive(Deserialize)]
pub struct NoiseThresholdBlockStateProvider {
#[serde(flatten)]
base: NoiseBlockStateProviderBase,
threshold: f32,
high_chance: f32,
default_state: BlockStateCodec,
low_states: Vec<BlockStateCodec>,
high_states: Vec<BlockStateCodec>,
}
impl NoiseThresholdBlockStateProvider {
pub fn get(&self, random: &mut RandomGenerator, pos: BlockPos) -> BlockState {
let value = self.base.get_noise(pos);
if value < self.threshold as f64 {
return self.low_states[random.next_bounded_i32(self.low_states.len() as i32) as usize]
.get_state()
.unwrap();
}
if random.next_f32() < self.high_chance {
return self.high_states
[random.next_bounded_i32(self.high_states.len() as i32) as usize]
.get_state()
.unwrap();
}
self.default_state.get_state().unwrap()
}
}

View File

@@ -0,0 +1,385 @@
use std::{
collections::HashMap,
sync::{Arc, LazyLock},
};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use serde::Deserialize;
use crate::{ProtoChunk, level::Level, world::BlockRegistryExt};
use super::features::{
bamboo::BambooFeature,
basalt_columns::BasaltColumnsFeature,
basalt_pillar::BasaltPillarFeature,
block_column::BlockColumnFeature,
block_pile::BlockPileFeature,
blue_ice::BlueIceFeature,
bonus_chest::BonusChestFeature,
chorus_plant::ChorusPlantFeature,
coral::{
coral_claw::CoralClawFeature, coral_mushroom::CoralMushroomFeature,
coral_tree::CoralTreeFeature,
},
delta_feature::DeltaFeatureFeature,
desert_well::DesertWellFeature,
disk::DiskFeature,
drip_stone::{
cluster::DripstoneClusterFeature, large::LargeDripstoneFeature,
small::SmallDripstoneFeature,
},
end_gateway::EndGatewayFeature,
end_island::EndIslandFeature,
end_platform::EndPlatformFeature,
end_spike::EndSpikeFeature,
fallen_tree::FallenTreeFeature,
fill_layer::FillLayerFeature,
forest_rock::ForestRockFeature,
fossil::FossilFeature,
freeze_top_layer::FreezeTopLayerFeature,
geode::GeodeFeature,
glowstone_blob::GlowstoneBlobFeature,
huge_brown_mushroom::HugeBrownMushroomFeature,
huge_fungus::HugeFungusFeature,
huge_red_mushroom::HugeRedMushroomFeature,
ice_spike::IceSpikeFeature,
iceberg::IcebergFeature,
kelp::KelpFeature,
lake::LakeFeature,
monster_room::DungeonFeature,
multiface_growth::MultifaceGrowthFeature,
nether_forest_vegetation::NetherForestVegetationFeature,
netherrack_replace_blobs::ReplaceBlobsFeature,
ore::OreFeature,
random_boolean_selector::RandomBooleanFeature,
random_patch::RandomPatchFeature,
random_selector::RandomFeature,
replace_single_block::ReplaceSingleBlockFeature,
root_system::RootSystemFeature,
scattered_ore::ScatteredOreFeature,
sculk_patch::SculkPatchFeature,
sea_pickle::SeaPickleFeature,
seagrass::SeagrassFeature,
simple_block::SimpleBlockFeature,
simple_random_selector::SimpleRandomFeature,
spring_feature::SpringFeatureFeature,
tree::TreeFeature,
twisting_vines::TwistingVinesFeature,
underwater_magma::UnderwaterMagmaFeature,
vegetation_patch::VegetationPatchFeature,
vines::VinesFeature,
void_start_platform::VoidStartPlatformFeature,
waterlogged_vegetation_patch::WaterloggedVegetationPatchFeature,
weeping_vines::WeepingVinesFeature,
};
pub static CONFIGURED_FEATURES: LazyLock<HashMap<String, ConfiguredFeature>> =
LazyLock::new(|| {
serde_json::from_str(include_str!("../../../../assets/configured_features.json"))
.expect("Could not parse configured_features.json registry.")
});
// Yes this may look ugly and you wonder why this is hard coded, but its makes sense to hardcode since we have to add logic for these in code
#[derive(Deserialize)]
#[serde(tag = "type", content = "config")]
pub enum ConfiguredFeature {
#[serde(rename = "minecraft:no_op")]
NoOp,
#[serde(rename = "minecraft:tree")]
Tree(Box<TreeFeature>),
#[serde(rename = "minecraft:fallen_tree")]
FallenTree(FallenTreeFeature),
#[serde(rename = "minecraft:flower")]
Flower(RandomPatchFeature),
#[serde(rename = "minecraft:no_bonemeal_flower")]
NoBonemealFlower(RandomPatchFeature),
#[serde(rename = "minecraft:random_patch")]
RandomPatch(RandomPatchFeature),
#[serde(rename = "minecraft:block_pile")]
BlockPile(BlockPileFeature),
#[serde(rename = "minecraft:spring_feature")]
SpringFeature(SpringFeatureFeature),
#[serde(rename = "minecraft:chorus_plant")]
ChorusPlant(ChorusPlantFeature),
#[serde(rename = "minecraft:replace_single_block")]
ReplaceSingleBlock(ReplaceSingleBlockFeature),
#[serde(rename = "minecraft:void_start_platform")]
VoidStartPlatform(VoidStartPlatformFeature),
#[serde(rename = "minecraft:desert_well")]
DesertWell(DesertWellFeature),
#[serde(rename = "minecraft:fossil")]
Fossil(FossilFeature),
#[serde(rename = "minecraft:huge_red_mushroom")]
HugeRedMushroom(HugeRedMushroomFeature),
#[serde(rename = "minecraft:huge_brown_mushroom")]
HugeBrownMushroom(HugeBrownMushroomFeature),
#[serde(rename = "minecraft:ice_spike")]
IceSpike(IceSpikeFeature),
#[serde(rename = "minecraft:glowstone_blob")]
GlowstoneBlob(GlowstoneBlobFeature),
#[serde(rename = "minecraft:freeze_top_layer")]
FreezeTopLayer(FreezeTopLayerFeature),
#[serde(rename = "minecraft:vines")]
Vines(VinesFeature),
#[serde(rename = "minecraft:block_column")]
BlockColumn(BlockColumnFeature),
#[serde(rename = "minecraft:vegetation_patch")]
VegetationPatch(VegetationPatchFeature),
#[serde(rename = "minecraft:waterlogged_vegetation_patch")]
WaterloggedVegetationPatch(WaterloggedVegetationPatchFeature),
#[serde(rename = "minecraft:root_system")]
RootSystem(RootSystemFeature),
#[serde(rename = "minecraft:multiface_growth")]
MultifaceGrowth(MultifaceGrowthFeature),
#[serde(rename = "minecraft:underwater_magma")]
UnderwaterMagma(UnderwaterMagmaFeature),
#[serde(rename = "minecraft:monster_room")]
MonsterRoom(DungeonFeature),
#[serde(rename = "minecraft:blue_ice")]
BlueIce(BlueIceFeature),
#[serde(rename = "minecraft:iceberg")]
Iceberg(IcebergFeature),
#[serde(rename = "minecraft:forest_rock")]
ForestRock(ForestRockFeature),
#[serde(rename = "minecraft:disk")]
Disk(DiskFeature),
#[serde(rename = "minecraft:lake")]
Lake(LakeFeature),
#[serde(rename = "minecraft:ore")]
Ore(OreFeature),
#[serde(rename = "minecraft:end_platform")]
EndPlatform(EndPlatformFeature),
#[serde(rename = "minecraft:end_spike")]
EndSpike(EndSpikeFeature),
#[serde(rename = "minecraft:end_island")]
EndIsland(EndIslandFeature),
#[serde(rename = "minecraft:end_gateway")]
EndGateway(EndGatewayFeature),
#[serde(rename = "minecraft:seagrass")]
Seagrass(SeagrassFeature),
#[serde(rename = "minecraft:kelp")]
Kelp(KelpFeature),
#[serde(rename = "minecraft:coral_tree")]
CoralTree(CoralTreeFeature),
#[serde(rename = "minecraft:coral_mushroom")]
CoralMushroom(CoralMushroomFeature),
#[serde(rename = "minecraft:coral_claw")]
CoralClaw(CoralClawFeature),
#[serde(rename = "minecraft:sea_pickle")]
SeaPickle(SeaPickleFeature),
#[serde(rename = "minecraft:simple_block")]
SimpleBlock(SimpleBlockFeature),
#[serde(rename = "minecraft:bamboo")]
Bamboo(BambooFeature),
#[serde(rename = "minecraft:huge_fungus")]
HugeFungus(HugeFungusFeature),
#[serde(rename = "minecraft:nether_forest_vegetation")]
NetherForestVegetation(NetherForestVegetationFeature),
#[serde(rename = "minecraft:weeping_vines")]
WeepingVines(WeepingVinesFeature),
#[serde(rename = "minecraft:twisting_vines")]
TwistingVines(TwistingVinesFeature),
#[serde(rename = "minecraft:basalt_columns")]
BasaltColumns(BasaltColumnsFeature),
#[serde(rename = "minecraft:delta_feature")]
DeltaFeature(DeltaFeatureFeature),
#[serde(rename = "minecraft:netherrack_replace_blobs")]
NetherrackReplaceBlobs(ReplaceBlobsFeature),
#[serde(rename = "minecraft:fill_layer")]
FillLayer(FillLayerFeature),
#[serde(rename = "minecraft:bonus_chest")]
BonusChest(BonusChestFeature),
#[serde(rename = "minecraft:basalt_pillar")]
BasaltPillar(BasaltPillarFeature),
#[serde(rename = "minecraft:scattered_ore")]
ScatteredOre(ScatteredOreFeature),
#[serde(rename = "minecraft:random_selector")]
RandomSelector(RandomFeature),
#[serde(rename = "minecraft:simple_random_selector")]
SimpleRandomSelector(SimpleRandomFeature),
#[serde(rename = "minecraft:random_boolean_selector")]
RandomBooleanSelector(RandomBooleanFeature),
#[serde(rename = "minecraft:geode")]
Geode(GeodeFeature),
#[serde(rename = "minecraft:dripstone_cluster")]
DripstoneCluster(DripstoneClusterFeature),
#[serde(rename = "minecraft:large_dripstone")]
LargeDripstone(LargeDripstoneFeature),
#[serde(rename = "minecraft:pointed_dripstone")]
PointedDripstone(SmallDripstoneFeature),
#[serde(rename = "minecraft:sculk_patch")]
SculkPatch(SculkPatchFeature),
}
impl ConfiguredFeature {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
block_registry: &dyn BlockRegistryExt,
min_y: i8,
height: u16,
feature_name: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
match self {
Self::PointedDripstone(feature) => feature.generate(chunk, random, pos),
Self::CoralMushroom(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::CoralTree(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::CoralClaw(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::SpringFeature(feature) => feature.generate(block_registry, chunk, random, pos),
Self::SimpleBlock(feature) => feature.generate(block_registry, chunk, random, pos),
Self::Flower(feature) => {
feature
.generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::NoBonemealFlower(feature) => {
feature
.generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::DesertWell(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::Bamboo(feature) => {
feature
.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::BlockColumn(feature) => {
feature
.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::RandomPatch(feature) => {
feature
.generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::RandomBooleanSelector(feature) => {
feature
.generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::Tree(feature) => {
feature
.generate(chunk, level, min_y, height, feature_name, random, pos)
.await
}
Self::RandomSelector(feature) => {
feature
.generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::SimpleRandomSelector(feature) => {
feature
.generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
)
.await
}
Self::Vines(feature) => feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
),
Self::Seagrass(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::SeaPickle(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::Ore(feature) => feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
),
_ => false, // TODO
}
}
}

View File

@@ -0,0 +1,98 @@
use pumpkin_data::{
Block, BlockDirection,
block_properties::{
BambooLeaves, BambooLikeProperties, BlockProperties, Integer0To1, get_state_by_state_id,
},
tag::Tagable,
};
use pumpkin_util::{
math::{position::BlockPos, vector2::Vector2},
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{ProtoChunk, world::BlockRegistryExt};
#[derive(Deserialize)]
pub struct BambooFeature {
probability: f32,
}
impl BambooFeature {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
block_registry: &dyn BlockRegistryExt,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut i = 0;
if chunk.is_air(&pos.0) {
if block_registry
.can_place_at(&Block::BAMBOO, chunk, &pos, BlockDirection::Up)
.await
{
let height = random.next_bounded_i32(12) + 5;
if random.next_f32() < self.probability {
let rnd = random.next_bounded_i32(4) + 1;
for x in pos.0.x - rnd..pos.0.x + rnd {
for z in pos.0.z - rnd..pos.0.z + rnd {
let block_below = BlockPos::new(
x,
chunk.top_block_height_exclusive(&Vector2::new(x, z)) as i32 - 1,
z,
);
let block = chunk.get_block_state(&block_below.0);
if !block.to_block().is_tagged_with("minecraft:dirt").unwrap() {
continue;
}
chunk.set_block_state(
&block_below.0,
&get_state_by_state_id(Block::PODZOL.id).unwrap(),
);
}
}
}
let mut bpos = pos;
let bamboo = get_state_by_state_id(Block::BAMBOO.default_state_id).unwrap();
for _ in 0..height {
if chunk.is_air(&bpos.0) {
chunk.set_block_state(&bpos.0, &bamboo);
bpos = bpos.up();
} else {
break;
}
}
// Top block
if bpos.0.y - pos.0.y >= 3 {
let mut props = BambooLikeProperties::default(&Block::BAMBOO);
props.leaves = BambooLeaves::Large;
props.stage = Integer0To1::L1;
chunk.set_block_state(
&bpos.0,
&get_state_by_state_id(props.to_state_id(&Block::BAMBOO)).unwrap(),
);
props.stage = Integer0To1::L0;
chunk.set_block_state(
&bpos.down().0,
&get_state_by_state_id(props.to_state_id(&Block::BAMBOO)).unwrap(),
);
props.leaves = BambooLeaves::Small;
chunk.set_block_state(
&bpos.down().down().0,
&get_state_by_state_id(props.to_state_id(&Block::BAMBOO)).unwrap(),
);
}
}
i += 1;
}
i > 0
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct BasaltColumnsFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct BasaltPillarFeature {
// TODO
}

View File

@@ -0,0 +1,114 @@
use pumpkin_data::BlockDirection;
use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos},
random::RandomGenerator,
};
use serde::Deserialize;
use crate::{
ProtoChunk,
generation::{block_predicate::BlockPredicate, block_state_provider::BlockStateProvider},
world::BlockRegistryExt,
};
#[derive(Deserialize)]
pub struct BlockColumnFeature {
layers: Vec<Layer>,
direction: BlockDirection,
allowed_placement: BlockPredicate,
prioritize_tip: bool,
}
#[derive(Deserialize)]
struct Layer {
height: IntProvider,
provider: BlockStateProvider,
}
impl BlockColumnFeature {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
block_registry: &dyn BlockRegistryExt,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let i = self.layers.len();
let mut is = vec![0; i];
let mut j = 0;
for (k, item) in is.iter_mut().enumerate().take(i) {
*item = (self.layers[k].height).get(random);
j += *item;
}
if j == 0 {
return false;
}
let mut mutable = pos;
let mut mutable2 = mutable.offset(self.direction.to_offset());
let mut l = 0;
while l < j {
if !Box::pin(
self.allowed_placement
.test(block_registry, chunk, &mutable2),
)
.await
{
Self::adjust_layer_heights(&mut is, j, l, self.prioritize_tip);
break;
}
mutable2 = mutable2.offset(self.direction.to_offset());
l += 1;
}
for (l, m) in is.iter().enumerate().take(i) {
if *m == 0 {
continue;
}
let layer = &self.layers[l];
for _n in 0..*m {
let state = layer.provider.get(random, mutable);
chunk.set_block_state(&mutable.0, &state);
mutable = mutable.offset(self.direction.to_offset());
}
}
true
}
fn adjust_layer_heights(
layer_heights: &mut [i32],
expected_height: i32,
actual_height: i32,
prioritize_tip: bool,
) {
let mut i = expected_height - actual_height;
let j = if prioritize_tip { 1 } else { -1 };
let k = if prioritize_tip {
0
} else {
layer_heights.len() as i32 - 1
};
let l = if prioritize_tip {
layer_heights.len() as i32
} else {
-1
};
let mut m = k;
while m != l && i > 0 {
let n = layer_heights[m as usize];
let o = std::cmp::min(n, i);
layer_heights[m as usize] -= o;
i -= o;
m += j;
}
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct BlockPileFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct BlueIceFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct BonusChestFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ChorusPlantFeature {
// TODO
}

View File

@@ -0,0 +1,78 @@
use pumpkin_data::BlockDirection;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::ProtoChunk;
use super::CoralFeature;
#[derive(Deserialize)]
pub struct CoralClawFeature;
impl CoralClawFeature {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry("minecraft:coral_blocks", random);
if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) {
return false;
}
let i = random.next_bounded_i32(2) + 2;
let direction = BlockDirection::horizontal()
[random.next_bounded_i32(BlockDirection::horizontal().len() as i32 - 1) as usize];
// TODO: Shuffle
let directions: Vec<_> = BlockDirection::horizontal()
.into_iter()
.take(i as usize)
.collect();
'block0: for direction2 in directions {
let mut pos = pos;
let j = random.next_bounded_i32(2) + 1;
pos = pos.offset(direction2.to_offset());
let direction3;
let k;
if direction2 == direction {
direction3 = direction;
k = random.next_bounded_i32(3) + 2;
} else {
pos = pos.up();
let _directions = [direction2, BlockDirection::Up];
direction3 = direction2; // TODO: make this random
k = random.next_bounded_i32(3) + 5;
}
for _ in 0..j {
if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) {
break;
}
pos = pos.offset(direction3.to_offset());
}
pos = pos.offset(direction3.to_offset());
pos = pos.up();
for _l in 0..k {
pos = pos.offset(direction.opposite().to_offset());
if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) {
continue 'block0;
}
if random.next_f32() < 0.25 {
pos = pos.up();
}
}
}
true
}
}

View File

@@ -0,0 +1,56 @@
use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::ProtoChunk;
use super::CoralFeature;
#[derive(Deserialize)]
pub struct CoralMushroomFeature;
impl CoralMushroomFeature {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry("minecraft:coral_blocks", random);
let i = random.next_bounded_i32(3) + 3;
let j = random.next_bounded_i32(3) + 3;
let k = random.next_bounded_i32(3) + 3;
let l = random.next_bounded_i32(3) + 1;
for m in 0..=j {
for n in 0..=i {
for o in 0..=k {
let mut pos = pos;
pos = pos.offset(Vector3::new(pos.0.x + m, pos.0.y + n, pos.0.z + o));
pos = pos.down_height(l);
let condition_a = (m != 0 && m != j) || (n != 0 && n != i);
let condition_b = (o != 0 && o != k) || (n != 0 && n != i);
let condition_c = (m != 0 && m != j) || (o != 0 && o != k);
let condition_d = m == 0 || m == j || n == 0 || n == i || o == 0 || o == k;
let random_check = random.next_f32() < 0.1f32;
if !((condition_a && condition_b && condition_c && condition_d)
&& !random_check
&& CoralFeature::generate_coral_piece(chunk, random, &block, pos))
{
continue;
}
}
}
}
true
}
}

View File

@@ -0,0 +1,61 @@
use pumpkin_data::BlockDirection;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::ProtoChunk;
use super::CoralFeature;
#[derive(Deserialize)]
pub struct CoralTreeFeature;
impl CoralTreeFeature {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry("minecraft:coral_blocks", random);
let mut pos = pos;
let i = random.next_bounded_i32(3) + 1;
for _ in 0..i {
if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) {
return true;
}
pos = pos.up();
}
let i = random.next_bounded_i32(3) + 2;
// TODO: Shuffle
let directions: Vec<_> = BlockDirection::horizontal()
.into_iter()
.take(i as usize)
.collect();
for dir in directions {
pos = pos.offset(dir.to_offset());
let times = random.next_bounded_i32(5) + 2;
let mut m = 0;
for n in 0..times {
if !CoralFeature::generate_coral_piece(chunk, random, &block, pos) {
break;
}
pos = pos.up();
m += 1;
if n != 0 && (m < 2 || random.next_f32() >= 0.25) {
continue;
}
pos = pos.offset(dir.to_offset());
m = 0;
}
}
true
}
}

View File

@@ -0,0 +1,100 @@
use pumpkin_data::{
Block, BlockDirection, BlockState,
block_properties::{
BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties, get_block,
get_state_by_state_id,
},
tag::{RegistryKey, Tagable, get_tag_values},
};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::ProtoChunk;
pub mod coral_claw;
pub mod coral_mushroom;
pub mod coral_tree;
pub struct CoralFeature;
impl CoralFeature {
pub fn generate_coral_piece(
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
state: &BlockState,
pos: BlockPos,
) -> bool {
let block = chunk.get_block_state(&pos.0).to_block();
let above_block = chunk.get_block_state(&pos.up().0).to_block();
if block != Block::WATER && !block.is_tagged_with("minecraft:corals").unwrap()
|| above_block != Block::WATER
{
return false;
}
chunk.set_block_state(&pos.0, state);
if random.next_f32() < 0.25 {
chunk.set_block_state(
&pos.0,
&Self::get_random_tag_entry("minecraft:corals", random),
);
} else if random.next_f32() < 0.05 {
let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE);
props.pickles = Integer1To4::from_index(random.next_bounded_i32(4) as u16); // TODO: vanilla adds + 1, but this can crash
chunk.set_block_state(
&pos.0,
&get_state_by_state_id(props.to_state_id(&Block::SEA_PICKLE)).unwrap(),
);
}
for dir in BlockDirection::horizontal() {
let dir_pos = pos.offset(dir.to_offset());
if random.next_f32() >= 0.2
|| chunk.get_block_state(&dir_pos.0).to_block() != Block::WATER
{
continue;
}
let wall_coral = Self::get_random_tag_entry_block("minecraft:wall_corals", random);
let original_props = &wall_coral
.properties(wall_coral.default_state_id)
.unwrap()
.to_props();
let facing = dir.to_facing();
// Set the right Axis
let props_vec: Vec<(&str, &str)> = original_props
.iter()
.map(|(key, value)| {
if key == "facing" {
(key.as_str(), facing.to_value())
} else {
(key.as_str(), value.as_str())
}
})
.collect();
chunk.set_block_state(
&dir_pos.0,
&get_state_by_state_id(
wall_coral
.from_properties(props_vec)
.unwrap()
.to_state_id(&wall_coral),
)
.unwrap(),
);
}
true
}
pub fn get_random_tag_entry(tag: &str, random: &mut RandomGenerator) -> BlockState {
let block = Self::get_random_tag_entry_block(tag, random);
get_state_by_state_id(block.default_state_id).unwrap()
}
pub fn get_random_tag_entry_block(tag: &str, random: &mut RandomGenerator) -> Block {
let values = get_tag_values(RegistryKey::Block, tag).unwrap();
let value = values[random.next_bounded_i32(values.len() as i32) as usize];
get_block(value).unwrap()
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct DeltaFeatureFeature {
// TODO
}

View File

@@ -0,0 +1,152 @@
use pumpkin_data::BlockDirection;
use pumpkin_macros::default_block_state;
use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
random::RandomGenerator,
};
use serde::Deserialize;
use crate::{
ProtoChunk,
block::RawBlockState,
generation::{chunk_noise::WATER_BLOCK, height_limit::HeightLimitView},
};
// TODO: remove .to_state()
#[derive(Deserialize)]
pub struct DesertWellFeature;
impl DesertWellFeature {
const CAN_GENERATE: RawBlockState = default_block_state!("sand");
const SAND: RawBlockState = default_block_state!("sand");
const SLAB: RawBlockState = default_block_state!("sandstone_slab");
const WALL: RawBlockState = default_block_state!("sandstone");
pub fn generate(
&self,
chunk: &mut ProtoChunk,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut block_pos = pos.up();
while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 {
block_pos = block_pos.down();
}
let block = chunk.get_block_state(&pos.0).to_block();
const CAN_GENERATE: RawBlockState = default_block_state!("sand");
if CAN_GENERATE.to_block().id != block.id {
return false;
}
for i in -2..=2 {
for j2 in -2..=2 {
if !chunk.is_air(&block_pos.0.add(&Vector3::new(i, -1, j2)))
|| !chunk.is_air(&block_pos.0.add(&Vector3::new(i, -2, j2)))
{
continue;
}
return false;
}
}
for i in -2..=0 {
for j2 in -2..=2 {
for k in -2..=2 {
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(j2, i, k)),
&Self::WALL.to_state(),
);
}
}
}
chunk.set_block_state(&block_pos.0, &WATER_BLOCK.to_state());
for direction in BlockDirection::horizontal().iter() {
chunk.set_block_state(
&block_pos.0.add(&direction.to_offset()),
&WATER_BLOCK.to_state(),
);
}
let block_pos2 = &block_pos.0.add(&Vector3::new(0, -1, 0));
chunk.set_block_state(block_pos2, &Self::SAND.to_state());
for direction2 in BlockDirection::horizontal().iter() {
chunk.set_block_state(
&block_pos2.add(&direction2.to_offset()),
&Self::SAND.to_state(),
);
}
for j in -2..=2 {
for k in -2..=2 {
if j != -2 && j != 2 && k != -2 && k != 2 {
continue;
}
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(j, 1, k)),
&Self::WALL.to_state(),
);
}
}
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(2, 1, 0)),
&Self::SLAB.to_state(),
);
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(-2, 1, 0)),
&Self::SLAB.to_state(),
);
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(0, 1, 2)),
&Self::SLAB.to_state(),
);
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(0, 1, -2)),
&Self::SLAB.to_state(),
);
for j in -1..=1 {
for k in -1..=1 {
if j == 0 && k == 0 {
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(j, 4, k)),
&Self::WALL.to_state(),
);
continue;
}
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(j, 4, k)),
&Self::SLAB.to_state(),
);
}
}
for j in 1..=3 {
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(-1, j, -1)),
&Self::WALL.to_state(),
);
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(-1, j, 1)),
&Self::WALL.to_state(),
);
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(1, j, -1)),
&Self::WALL.to_state(),
);
chunk.set_block_state(
&block_pos.0.add(&Vector3::new(1, j, 1)),
&Self::WALL.to_state(),
);
}
true
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct DiskFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct DripstoneClusterFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct LargeDripstoneFeature {
// TODO
}

View File

@@ -0,0 +1,30 @@
use pumpkin_data::{Block, block_properties::get_state_by_state_id, tag::Tagable};
use pumpkin_util::math::position::BlockPos;
use crate::ProtoChunk;
pub mod cluster;
pub mod large;
pub mod small;
pub(super) fn can_replace(block: &Block) -> bool {
block == &Block::DRIPSTONE_BLOCK
|| block
.is_tagged_with("minecraft:dripstone_replaceable_blocks")
.unwrap()
}
pub(super) fn gen_dripstone(chunk: &mut ProtoChunk, pos: BlockPos) -> bool {
let block = chunk.get_block_state(&pos.0).to_block();
if block
.is_tagged_with("minecraft:dripstone_replaceable_blocks")
.unwrap()
{
chunk.set_block_state(
&pos.0,
&get_state_by_state_id(Block::DRIPSTONE_BLOCK.default_state_id).unwrap(),
);
return true;
}
false
}

View File

@@ -0,0 +1,82 @@
use pumpkin_data::BlockDirection;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::ProtoChunk;
#[derive(Deserialize)]
pub struct SmallDripstoneFeature {
chance_of_taller_dripstone: f32,
chance_of_directional_spread: f32,
chance_of_spread_radius2: f32,
chance_of_spread_radius3: f32,
}
impl SmallDripstoneFeature {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
if let Some(dir) = Self::get_direction(chunk, pos, random) {
let pos = pos.offset(dir.opposite().to_offset());
self.gen_dripstone_blocks(chunk, pos, random);
// TODO
return true;
}
false
}
fn get_direction(
chunk: &mut ProtoChunk,
pos: BlockPos,
random: &mut RandomGenerator,
) -> Option<BlockDirection> {
let up = super::can_replace(&chunk.get_block_state(&pos.up().0).to_block());
let down: bool = super::can_replace(&chunk.get_block_state(&pos.down().0).to_block());
if up && down {
return if random.next_bool() {
Some(BlockDirection::Down)
} else {
Some(BlockDirection::Up)
};
}
if up {
return Some(BlockDirection::Down);
}
if down {
return Some(BlockDirection::Up);
}
None
}
fn gen_dripstone_blocks(
&self,
chunk: &mut ProtoChunk,
pos: BlockPos,
random: &mut RandomGenerator,
) {
super::gen_dripstone(chunk, pos);
for dir in BlockDirection::horizontal() {
if random.next_f32() > self.chance_of_directional_spread {
continue;
}
let pos = pos.offset(dir.to_offset());
super::gen_dripstone(chunk, pos);
if random.next_f32() > self.chance_of_spread_radius2 {
continue;
}
let pos = pos.offset(BlockDirection::random(random).to_offset());
super::gen_dripstone(chunk, pos);
if random.next_f32() > self.chance_of_spread_radius3 {
continue;
}
let pos = pos.offset(BlockDirection::random(random).to_offset());
super::gen_dripstone(chunk, pos);
}
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct EndGatewayFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct EndIslandFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct EndPlatformFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct EndSpikeFeature {
// TODO
}

View File

@@ -0,0 +1,27 @@
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use serde::Deserialize;
use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider};
#[derive(Deserialize)]
pub struct FallenTreeFeature {
trunk_provider: BlockStateProvider,
}
impl FallenTreeFeature {
pub fn generate(
&self,
_chunk: &mut ProtoChunk,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
_random: &mut RandomGenerator,
_pos: BlockPos,
) -> bool {
false
}
fn gen_stump(&self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, pos: BlockPos) {
chunk.set_block_state(&pos.0, &self.trunk_provider.get(random, pos));
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct FillLayerFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ForestRockFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct FossilFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct FreezeTopLayerFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct GeodeFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct GlowstoneBlobFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct HugeBrownMushroomFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct HugeFungusFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct HugeRedMushroomFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct IceSpikeFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct IcebergFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct KelpFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct LakeFeature {
// TODO
}

View File

@@ -0,0 +1,56 @@
pub mod bamboo;
pub mod basalt_columns;
pub mod basalt_pillar;
pub mod block_column;
pub mod block_pile;
pub mod blue_ice;
pub mod bonus_chest;
pub mod chorus_plant;
pub mod coral;
pub mod delta_feature;
pub mod desert_well;
pub mod disk;
pub mod drip_stone;
pub mod end_gateway;
pub mod end_island;
pub mod end_platform;
pub mod end_spike;
pub mod fallen_tree;
pub mod fill_layer;
pub mod forest_rock;
pub mod fossil;
pub mod freeze_top_layer;
pub mod geode;
pub mod glowstone_blob;
pub mod huge_brown_mushroom;
pub mod huge_fungus;
pub mod huge_red_mushroom;
pub mod ice_spike;
pub mod iceberg;
pub mod kelp;
pub mod lake;
pub mod monster_room;
pub mod multiface_growth;
pub mod nether_forest_vegetation;
pub mod netherrack_replace_blobs;
pub mod ore;
pub mod random_boolean_selector;
pub mod random_patch;
pub mod random_selector;
pub mod replace_single_block;
pub mod root_system;
pub mod scattered_ore;
pub mod sculk_patch;
pub mod sea_pickle;
pub mod seagrass;
pub mod simple_block;
pub mod simple_random_selector;
pub mod spring_feature;
pub mod tree;
pub mod twisting_vines;
pub mod underwater_magma;
pub mod vegetation_patch;
pub mod vines;
pub mod void_start_platform;
pub mod waterlogged_vegetation_patch;
pub mod weeping_vines;

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct DungeonFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct MultifaceGrowthFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct NetherForestVegetationFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ReplaceBlobsFeature {
// TODO
}

View File

@@ -0,0 +1,252 @@
use core::f32;
use std::collections::HashSet;
use pumpkin_data::{BlockDirection, BlockState};
use pumpkin_util::{
math::{lerp, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{
ProtoChunk,
block::BlockStateCodec,
generation::{height_limit::HeightLimitView, rule::RuleTest},
world::BlockRegistryExt,
};
#[derive(Deserialize)]
pub struct OreFeature {
size: i32,
discard_chance_on_air_exposure: f32,
targets: Vec<OreTarget>,
}
#[derive(Deserialize)]
struct OreTarget {
pub target: RuleTest,
pub state: BlockStateCodec,
}
impl OreFeature {
#[expect(clippy::too_many_arguments)]
pub fn generate(
&self,
chunk: &mut ProtoChunk,
_block_registry: &dyn BlockRegistryExt,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let f = random.next_f32() * f32::consts::PI;
let g = self.size as f32 / 8.0f32;
let i = ((self.size as f32 / 16.0f32 * 2.0 + 1.0) / 2.0).ceil() as i32;
let d = pos.0.x as f64 + f.sin() as f64 * g as f64;
let e = pos.0.x as f64 - f.sin() as f64 * g as f64;
let h = pos.0.z as f64 + f.cos() as f64 * g as f64; // Use f.cos() for Math.cos(f)
let j = pos.0.z as f64 - f.cos() as f64 * g as f64;
let _k = 2; // This variable 'k' from Java seems unused
let l = pos.0.y as f64 + random.next_bounded_i32(3) as f64 - 2.0;
let m = pos.0.y as f64 + random.next_bounded_i32(3) as f64 - 2.0;
let n = pos.0.x - g.ceil() as i32 - i;
let o = pos.0.y - 2 - i;
let p = pos.0.z - g.ceil() as i32 - i;
let q = 2 * (g.ceil() as i32 + i);
let r = 2 * (2 + i);
for _ in n..=(n + q) {
for _ in p..=(p + q) {
if o > chunk.ocean_floor_height_exclusive(&pos.0.to_vec2_i32()) as i32 {
continue;
}
return self.generate_vein_part(chunk, random, d, e, h, j, l, m, n, o, p, q, r);
}
}
false
}
#[expect(clippy::too_many_arguments)]
fn generate_vein_part(
&self,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
start_x: f64,
end_x: f64,
start_z: f64,
end_z: f64,
start_y: f64,
end_y: f64,
x_bound: i32,
y_bound: i32,
z_bound: i32,
horizontal_size: i32,
vertical_size: i32,
) -> bool {
let mut placed_blocks_count = 0;
let mut bit_set = HashSet::new();
let mut mutable_pos = BlockPos::new(0, 0, 0);
let j = self.size;
let mut ds = vec![0.0; (j * 4) as usize];
for k in 0..j {
let f = k as f32 / j as f32;
let d = lerp(f as f64, start_x, end_x);
let e = lerp(f as f64, start_y, end_y);
let g = lerp(f as f64, start_z, end_z);
let h = random.next_f64() * j as f64 / 16.0;
let l = (((f32::consts::PI * f).sin() + 1.0) * h as f32 + 1.0) / 2.0;
ds[k as usize * 4] = d;
ds[k as usize * 4 + 1] = e;
ds[k as usize * 4 + 2] = g;
ds[k as usize * 4 + 3] = l as f64;
}
for k in 0..(j - 1) {
if ds[k as usize * 4 + 3] <= 0.0 {
continue;
}
for m in (k + 1)..j {
if ds[m as usize * 4 + 3] <= 0.0 {
continue;
}
let h_val = ds[k as usize * 4 + 3] - ds[m as usize * 4 + 3];
let d_val = ds[k as usize * 4] - ds[m as usize * 4];
let e_val = ds[k as usize * 4 + 1] - ds[m as usize * 4 + 1];
let g_val = ds[k as usize * 4 + 2] - ds[m as usize * 4 + 2];
if h_val * h_val > d_val * d_val + e_val * e_val + g_val * g_val {
if h_val > 0.0 {
ds[m as usize * 4 + 3] = -1.0;
continue;
}
ds[k as usize * 4 + 3] = -1.0;
}
}
}
for m_idx in 0..j {
let d_val = ds[m_idx as usize * 4 + 3];
if d_val < 0.0 {
continue;
}
let e_val = ds[m_idx as usize * 4];
let g_val = ds[m_idx as usize * 4 + 1];
let h_val = ds[m_idx as usize * 4 + 2];
let n_bound = ((e_val - d_val).floor() as i32).max(x_bound);
let o_bound = ((g_val - d_val).floor() as i32).max(y_bound);
let p_bound = ((h_val - d_val).floor() as i32).max(z_bound);
let q_bound = ((e_val + d_val).floor() as i32).max(n_bound);
let r_bound = ((g_val + d_val).floor() as i32).max(o_bound);
let s_bound = ((h_val + d_val).floor() as i32).max(p_bound);
for t_val in n_bound..=q_bound {
let u_val = (t_val as f64 + 0.5 - e_val) / d_val;
if u_val * u_val >= 1.0 {
continue;
}
for v_val in o_bound..=r_bound {
let w_val = (v_val as f64 + 0.5 - g_val) / d_val;
if u_val * u_val + w_val * w_val >= 1.0 {
continue;
}
for aa_val in p_bound..=s_bound {
let ab_val = (aa_val as f64 + 0.5 - h_val) / d_val;
if u_val * u_val + w_val * w_val + ab_val * ab_val >= 1.0 {
continue;
}
if chunk.out_of_height(v_val as i16) {
continue;
}
let ac = (t_val - x_bound)
+ (v_val - y_bound) * horizontal_size
+ (aa_val - z_bound) * horizontal_size * vertical_size;
if bit_set.contains(&ac) {
continue;
}
bit_set.insert(ac);
mutable_pos.0.x = t_val;
mutable_pos.0.y = v_val;
mutable_pos.0.z = aa_val;
// if !world.is_valid_for_set_block(&mutable_pos) {
// continue;
// }
let ad = t_val;
let ae = v_val;
let af = aa_val;
let block_state = chunk.get_block_state(&Vector3::new(ad, ae, af));
for target in &self.targets {
if self.should_place(
chunk,
block_state.to_state(),
random,
target,
&mut mutable_pos,
) {
chunk.set_block_state(
&Vector3::new(ad, ae, af),
&target.state.get_state().unwrap(),
);
placed_blocks_count += 1;
break; // Equivalent to 'continue block11;'
}
}
}
}
}
}
placed_blocks_count > 0
}
fn should_place(
&self,
chunk: &mut ProtoChunk,
state: BlockState,
random: &mut RandomGenerator,
target: &OreTarget,
pos: &mut BlockPos,
) -> bool {
if !target.target.test(&state, random) {
return false;
}
if Self::should_not_discard(random, self.discard_chance_on_air_exposure) {
return true;
}
!Self::is_exposed_to_air(chunk, pos)
}
fn should_not_discard(random: &mut RandomGenerator, chance: f32) -> bool {
if chance <= 0.0f32 {
return true;
}
if chance >= 1.0f32 {
return false;
}
random.next_f32() >= chance
}
fn is_exposed_to_air(chunk: &mut ProtoChunk, pos: &BlockPos) -> bool {
for dir in BlockDirection::all() {
if chunk
.get_block_state(&pos.offset(dir.to_offset()).0)
.to_state()
.is_air()
{
return true;
}
}
false
}
}

View File

@@ -0,0 +1,51 @@
use std::sync::Arc;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{
ProtoChunk, generation::feature::placed_features::PlacedFeatureWrapper, level::Level,
world::BlockRegistryExt,
};
#[derive(Deserialize)]
pub struct RandomBooleanFeature {
feature_true: Box<PlacedFeatureWrapper>,
feature_false: Box<PlacedFeatureWrapper>,
}
impl RandomBooleanFeature {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
block_registry: &dyn BlockRegistryExt,
min_y: i8,
height: u16,
feature_name: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let val = random.next_bool();
let feature = if val {
&self.feature_true
} else {
&self.feature_false
};
Box::pin(feature.get().generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
))
.await
}
}

View File

@@ -0,0 +1,62 @@
use std::sync::Arc;
use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{
ProtoChunk, generation::feature::placed_features::PlacedFeature, level::Level,
world::BlockRegistryExt,
};
#[derive(Deserialize)]
pub struct RandomPatchFeature {
tries: u8,
xz_spread: u8,
y_spread: u8,
feature: Box<PlacedFeature>,
}
impl RandomPatchFeature {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
block_registry: &dyn BlockRegistryExt,
min_y: i8,
height: u16,
feature: &str,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut i = 0;
let xz = self.xz_spread as i32 + 1;
let y = self.y_spread as i32 + 1;
for _ in 0..self.tries {
let pos = Vector3::new(
pos.0.x + random.next_bounded_i32(xz) - random.next_bounded_i32(xz),
pos.0.y + random.next_bounded_i32(y) - random.next_bounded_i32(y),
pos.0.z + random.next_bounded_i32(xz) - random.next_bounded_i32(xz),
);
if !Box::pin(self.feature.generate(
chunk,
level,
block_registry,
min_y,
height,
feature,
random,
BlockPos(pos),
))
.await
{
continue;
}
i += 1;
}
i > 0
}
}

View File

@@ -0,0 +1,67 @@
use std::sync::Arc;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{
ProtoChunk, generation::feature::placed_features::PlacedFeatureWrapper, level::Level,
world::BlockRegistryExt,
};
#[derive(Deserialize)]
pub struct RandomFeature {
features: Vec<RandomFeatureEntry>,
default: Box<PlacedFeatureWrapper>,
}
#[derive(Deserialize)]
struct RandomFeatureEntry {
feature: PlacedFeatureWrapper,
chance: f32,
}
impl RandomFeature {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
block_registry: &dyn BlockRegistryExt,
min_y: i8,
height: u16,
feature_name: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
for feature in &self.features {
if random.next_f32() >= feature.chance {
continue;
}
return Box::pin(feature.feature.get().generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
))
.await;
}
Box::pin(self.default.get().generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
))
.await
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ReplaceSingleBlockFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct RootSystemFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ScatteredOreFeature {
// TODO
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct SculkPatchFeature {
// TODO
}

View File

@@ -0,0 +1,51 @@
use pumpkin_data::{
Block,
block_properties::{
BlockProperties, EnumVariants, Integer1To4, SeaPickleLikeProperties, get_state_by_state_id,
},
};
use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos, vector2::Vector2},
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::ProtoChunk;
#[derive(Deserialize)]
pub struct SeaPickleFeature {
count: IntProvider,
}
impl SeaPickleFeature {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut times = 0;
let count = self.count.get(random);
for _ in 0..count {
let x = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let z = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let y =
chunk.ocean_floor_height_exclusive(&Vector2::new(pos.0.x + x, pos.0.z + z)) as i32;
if chunk.get_block_state(&pos.0).to_block() != Block::WATER {
continue;
}
let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE);
props.pickles = Integer1To4::from_index(random.next_bounded_i32(4) as u16); // TODO: vanilla adds + 1, but this can crash
let pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
chunk.set_block_state(
&pos.0,
&get_state_by_state_id(props.to_state_id(&Block::SEA_PICKLE)).unwrap(),
);
times += 1;
}
times > 0
}
}

View File

@@ -0,0 +1,60 @@
use pumpkin_data::{
Block,
block_properties::{
BlockProperties, DoubleBlockHalf, TallSeagrassLikeProperties, get_state_by_state_id,
},
};
use pumpkin_util::{
math::{position::BlockPos, vector2::Vector2},
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::ProtoChunk;
#[derive(Deserialize)]
pub struct SeagrassFeature {
probability: f32,
}
impl SeagrassFeature {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
_min_y: i8,
_height: u16,
_feature: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let x = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let z = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let y = chunk.ocean_floor_height_exclusive(&Vector2::new(pos.0.x + x, pos.0.z + z)) as i32;
let top_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
if chunk.get_block_state(&top_pos.0).to_block() == Block::WATER {
let tall = random.next_f64() < self.probability as f64;
if tall {
let tall_pos = top_pos.up();
if chunk.get_block_state(&tall_pos.0).to_block() == Block::WATER {
let mut props = TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS);
props.half = DoubleBlockHalf::Upper;
chunk.set_block_state(
&top_pos.0,
&get_state_by_state_id(Block::TALL_SEAGRASS.default_state_id).unwrap(),
);
chunk.set_block_state(
&tall_pos.0,
&get_state_by_state_id(props.to_state_id(&Block::TALL_SEAGRASS)).unwrap(),
);
}
} else {
chunk.set_block_state(
&top_pos.0,
&get_state_by_state_id(Block::SEAGRASS.default_state_id).unwrap(),
);
}
return true;
}
false
}
}

View File

@@ -0,0 +1,41 @@
use pumpkin_data::{BlockDirection, block_properties::get_block_by_state_id};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use serde::Deserialize;
use crate::{
ProtoChunk,
generation::block_state_provider::BlockStateProvider,
world::{BlockAccessor, BlockRegistryExt},
};
#[derive(Deserialize)]
pub struct SimpleBlockFeature {
to_place: BlockStateProvider,
schedule_tick: Option<bool>,
}
impl SimpleBlockFeature {
pub fn generate(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let state = self.to_place.get(random, pos);
let block = get_block_by_state_id(state.id).unwrap();
let block_accessor: &dyn BlockAccessor = chunk;
if !futures::executor::block_on(async move {
block_registry
.can_place_at(&block, block_accessor, &pos, BlockDirection::Up)
.await
}) {
return false;
}
// TODO: check things..
chunk.set_block_state(&pos.0, &state);
// TODO: schedule tick when needed
true
}
}

View File

@@ -0,0 +1,46 @@
use std::sync::Arc;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{
ProtoChunk, generation::feature::placed_features::PlacedFeature, level::Level,
world::BlockRegistryExt,
};
#[derive(Deserialize)]
pub struct SimpleRandomFeature {
features: Vec<PlacedFeature>,
}
impl SimpleRandomFeature {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
block_registry: &dyn BlockRegistryExt,
min_y: i8,
height: u16,
feature_name: &str, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let i = random.next_bounded_i32(self.features.len() as i32);
let feature = &self.features[i as usize];
Box::pin(feature.generate(
chunk,
level,
block_registry,
min_y,
height,
feature_name,
random,
pos,
))
.await
}
}

View File

@@ -0,0 +1,130 @@
use pumpkin_data::BlockDirection;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use serde::Deserialize;
use crate::{ProtoChunk, block::BlockStateCodec, world::BlockRegistryExt};
#[derive(Deserialize)]
pub struct SpringFeatureFeature {
state: BlockStateCodec,
requires_block_below: bool,
rock_count: i32,
hole_count: i32,
valid_blocks: BlockWrapper,
}
#[derive(Deserialize, Clone)]
#[serde(untagged)]
enum BlockWrapper {
Single(String),
Multi(Vec<String>),
}
impl SpringFeatureFeature {
pub fn generate(
&self,
_block_registry: &dyn BlockRegistryExt,
chunk: &mut ProtoChunk,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
// i don't think this is the most efficient way, but it works
let valid_blocks = match self.valid_blocks.clone() {
BlockWrapper::Single(item) => vec![item],
BlockWrapper::Multi(items) => items,
};
if !valid_blocks.contains(
&chunk
.get_block_state(&pos.up().0)
.to_block()
.name
.to_string(),
) {
return false;
}
if self.requires_block_below
&& !valid_blocks.contains(
&chunk
.get_block_state(&pos.offset(BlockDirection::Down.to_offset()).0)
.to_block()
.name
.to_string(),
)
{
return false;
}
let state = chunk.get_block_state(&pos.0);
if !state.to_state().is_air() && !valid_blocks.contains(&state.to_block().name.to_string())
{
return false;
}
let mut valid = 0;
if valid_blocks.contains(
&chunk
.get_block_state(&pos.offset(BlockDirection::West.to_offset()).0)
.to_block()
.name
.to_string(),
) {
valid += 1;
}
if valid_blocks.contains(
&chunk
.get_block_state(&pos.offset(BlockDirection::East.to_offset()).0)
.to_block()
.name
.to_string(),
) {
valid += 1;
}
if valid_blocks.contains(
&chunk
.get_block_state(&pos.offset(BlockDirection::North.to_offset()).0)
.to_block()
.name
.to_string(),
) {
valid += 1;
}
if valid_blocks.contains(
&chunk
.get_block_state(&pos.offset(BlockDirection::South.to_offset()).0)
.to_block()
.name
.to_string(),
) {
valid += 1;
}
if valid_blocks.contains(
&chunk
.get_block_state(&pos.offset(BlockDirection::Down.to_offset()).0)
.to_block()
.name
.to_string(),
) {
valid += 1;
}
let mut air = 0;
if chunk.is_air(&pos.offset(BlockDirection::West.to_offset()).0) {
air += 1;
}
if chunk.is_air(&pos.offset(BlockDirection::East.to_offset()).0) {
air += 1;
}
if chunk.is_air(&pos.offset(BlockDirection::North.to_offset()).0) {
air += 1;
}
if chunk.is_air(&pos.offset(BlockDirection::South.to_offset()).0) {
air += 1;
}
if chunk.is_air(&pos.offset(BlockDirection::Down.to_offset()).0) {
air += 1;
}
if valid == self.rock_count && air == self.hole_count {
chunk.set_block_state(&pos.0, &self.state.get_state().unwrap());
return true;
}
false
}
}

View File

@@ -0,0 +1,4 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct AlterGroundTreeDecorator {}

View File

@@ -0,0 +1,4 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct AttachedToLeavesTreeDecorator {}

View File

@@ -0,0 +1,37 @@
use pumpkin_data::BlockDirection;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider};
#[derive(Deserialize)]
pub struct AttachedToLogsTreeDecorator {
probability: f32,
block_provider: BlockStateProvider,
directions: Vec<BlockDirection>,
}
impl AttachedToLogsTreeDecorator {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
_root_positions: Vec<BlockPos>,
log_positions: Vec<BlockPos>,
) {
// TODO: shuffle
for pos in log_positions {
// TODO: random
let pos = pos.offset(self.directions[0].to_offset());
if random.next_f32() > self.probability
|| !chunk.get_block_state(&pos.0).to_state().is_air()
{
continue;
}
chunk.set_block_state(&pos.0, &self.block_provider.get(random, pos));
}
}
}

View File

@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct BeehiveTreeDecorator {
probability: f32,
}

View File

@@ -0,0 +1,4 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct CocoaTreeDecorator {}

View File

@@ -0,0 +1,4 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct CreakingHeartTreeDecorator {}

View File

@@ -0,0 +1,4 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct LeavesVineTreeDecorator {}

View File

@@ -0,0 +1,96 @@
use alter_ground::AlterGroundTreeDecorator;
use attached_to_leaves::AttachedToLeavesTreeDecorator;
use attached_to_logs::AttachedToLogsTreeDecorator;
use beehive::BeehiveTreeDecorator;
use cocoa::CocoaTreeDecorator;
use creaking_heart::CreakingHeartTreeDecorator;
use leave_vine::LeavesVineTreeDecorator;
use pale_moss::PaleMossTreeDecorator;
use place_on_ground::PlaceOnGroundTreeDecorator;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use serde::Deserialize;
use trunk_vine::TrunkVineTreeDecorator;
use crate::ProtoChunk;
mod alter_ground;
mod attached_to_leaves;
mod attached_to_logs;
mod beehive;
mod cocoa;
mod creaking_heart;
mod leave_vine;
mod pale_moss;
mod place_on_ground;
mod trunk_vine;
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum TreeDecorator {
#[serde(rename = "minecraft:trunk_vine")]
TrunkVine(TrunkVineTreeDecorator),
#[serde(rename = "minecraft:leave_vine")]
LeaveVine(LeavesVineTreeDecorator),
#[serde(rename = "minecraft:pale_moss")]
PaleMoss(PaleMossTreeDecorator),
#[serde(rename = "minecraft:creaking_heart")]
CreakingHeart(CreakingHeartTreeDecorator),
#[serde(rename = "minecraft:cocoa")]
Cocoa(CocoaTreeDecorator),
#[serde(rename = "minecraft:beehive")]
Beehive(BeehiveTreeDecorator),
#[serde(rename = "minecraft:alter_ground")]
AlterGround(AlterGroundTreeDecorator),
#[serde(rename = "minecraft:attached_to_leaves")]
AttachedToLeaves(AttachedToLeavesTreeDecorator),
#[serde(rename = "minecraft:place_on_ground")]
PlaceOnGround(PlaceOnGroundTreeDecorator),
#[serde(rename = "minecraft:attached_to_logs")]
AttachedToLogs(AttachedToLogsTreeDecorator),
}
impl TreeDecorator {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
root_positions: Vec<BlockPos>,
log_positions: Vec<BlockPos>,
) {
match self {
TreeDecorator::TrunkVine(decorator) => decorator.generate(chunk, random, log_positions),
TreeDecorator::LeaveVine(_decorator) => {}
TreeDecorator::PaleMoss(_decorator) => {}
TreeDecorator::CreakingHeart(_decorator) => {}
TreeDecorator::Cocoa(_decorator) => {}
TreeDecorator::Beehive(_decorator) => {}
TreeDecorator::AlterGround(_decorator) => {}
TreeDecorator::PlaceOnGround(decorator) => {
decorator.generate(chunk, random, root_positions, log_positions)
}
TreeDecorator::AttachedToLeaves(_decorator) => {}
TreeDecorator::AttachedToLogs(decorator) => {
decorator.generate(chunk, random, root_positions, log_positions)
}
}
}
pub(super) fn get_leaf_litter_positions(
root_positions: Vec<BlockPos>,
log_positions: Vec<BlockPos>,
) -> Vec<BlockPos> {
let mut list = Vec::new();
if root_positions.is_empty() {
list.extend_from_slice(&log_positions);
} else if !log_positions.is_empty()
&& root_positions.first().unwrap().0.y == log_positions.first().unwrap().0.y
{
list.extend_from_slice(&log_positions);
list.extend_from_slice(&root_positions);
} else {
list.extend_from_slice(&root_positions);
}
list
}
}

View File

@@ -0,0 +1,4 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct PaleMossTreeDecorator {}

View File

@@ -0,0 +1,83 @@
use pumpkin_data::Block;
use pumpkin_util::{
math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider};
use super::TreeDecorator;
#[derive(Deserialize)]
pub struct PlaceOnGroundTreeDecorator {
tries: i32,
radius: i32,
height: i32,
block_state_provider: BlockStateProvider,
}
impl PlaceOnGroundTreeDecorator {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
root_positions: Vec<BlockPos>,
log_positions: Vec<BlockPos>,
) {
let list = TreeDecorator::get_leaf_litter_positions(root_positions, log_positions);
if list.is_empty() {
return;
}
let pos = list.first().unwrap();
let i = pos.0.y;
let mut j = pos.0.x;
let mut k = pos.0.x;
let mut l = pos.0.z;
let mut m = pos.0.z;
for block_pos_2 in list {
if block_pos_2.0.y != i {
continue;
}
j = j.min(block_pos_2.0.x);
k = k.max(block_pos_2.0.x);
l = l.min(block_pos_2.0.z);
m = m.max(block_pos_2.0.z);
}
let block_box = BoundingBox::new(
Vector3::new(j as f64, i as f64, l as f64),
Vector3::new(k as f64, i as f64, m as f64),
)
.expand(self.radius as f64, self.height as f64, self.radius as f64);
for _n in 0..self.tries {
let pos = BlockPos::new(
random.next_inbetween_i32(block_box.min.x as i32, block_box.max.x as i32),
random.next_inbetween_i32(block_box.min.y as i32, block_box.max.y as i32),
random.next_inbetween_i32(block_box.min.z as i32, block_box.max.z as i32),
);
self.generate_decoration(chunk, pos, random);
}
}
fn generate_decoration(
&self,
chunk: &mut ProtoChunk,
pos: BlockPos,
random: &mut RandomGenerator,
) {
let state = chunk.get_block_state(&pos.0);
let pos = pos.up();
let up_state = chunk.get_block_state(&pos.0);
// TODO
if (up_state.to_state().is_air() || up_state.to_block() == Block::VINE)
&& state.to_state().is_full_cube()
{
chunk.set_block_state(&pos.0, &self.block_state_provider.get(random, pos));
}
}
}

View File

@@ -0,0 +1,69 @@
use pumpkin_data::{
Block, BlockDirection,
block_properties::{BlockProperties, VineLikeProperties, get_state_by_state_id},
};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::ProtoChunk;
#[derive(Deserialize)]
pub struct TrunkVineTreeDecorator;
impl TrunkVineTreeDecorator {
pub fn generate(
&self,
chunk: &mut ProtoChunk,
random: &mut RandomGenerator,
log_positions: Vec<BlockPos>,
) {
for pos in log_positions {
if random.next_bounded_i32(3) > 0
&& chunk.is_air(&pos.offset(BlockDirection::West.to_offset()).0)
{
let mut vine = VineLikeProperties::default(&Block::VINE);
vine.east = true;
chunk.set_block_state(
&pos.offset(BlockDirection::West.to_offset()).0,
&get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(),
);
}
if random.next_bounded_i32(3) > 0
&& chunk.is_air(&pos.offset(BlockDirection::East.to_offset()).0)
{
let mut vine = VineLikeProperties::default(&Block::VINE);
vine.west = true;
chunk.set_block_state(
&pos.offset(BlockDirection::West.to_offset()).0,
&get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(),
);
}
if random.next_bounded_i32(3) > 0
&& chunk.is_air(&pos.offset(BlockDirection::North.to_offset()).0)
{
let mut vine = VineLikeProperties::default(&Block::VINE);
vine.south = true;
chunk.set_block_state(
&pos.offset(BlockDirection::West.to_offset()).0,
&get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(),
);
}
if random.next_bounded_i32(3) > 0
&& chunk.is_air(&pos.offset(BlockDirection::South.to_offset()).0)
{
let mut vine = VineLikeProperties::default(&Block::VINE);
vine.north = true;
chunk.set_block_state(
&pos.offset(BlockDirection::West.to_offset()).0,
&get_state_by_state_id(vine.to_state_id(&Block::VINE)).unwrap(),
);
}
}
}
}

View File

@@ -0,0 +1,85 @@
use std::sync::Arc;
use pumpkin_data::BlockState;
use pumpkin_util::random::RandomGenerator;
use serde::Deserialize;
use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level};
use super::{FoliagePlacer, LeaveValidator};
#[derive(Deserialize)]
pub struct AcaciaFoliagePlacer;
impl AcaciaFoliagePlacer {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
random: &mut RandomGenerator,
node: &TreeNode,
foliage_height: i32,
radius: i32,
_offset: i32,
foliage_provider: &BlockState,
) {
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
node.center,
radius + node.foliage_radius,
-1,
node.giant_trunk,
foliage_provider,
)
.await;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
node.center,
radius - 1,
-foliage_height,
node.giant_trunk,
foliage_provider,
)
.await;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
node.center,
radius + node.foliage_radius - 1,
0,
node.giant_trunk,
foliage_provider,
)
.await;
}
pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
0
}
}
impl LeaveValidator for AcaciaFoliagePlacer {
fn is_invalid_for_leaves(
&self,
_random: &mut pumpkin_util::random::RandomGenerator,
dx: i32,
y: i32,
dz: i32,
radius: i32,
_giant_trunk: bool,
) -> bool {
if y == 0 {
return (dx > 1 || dz > 1) && dx != 0 && dz != 0;
}
dx == radius && dz == radius && radius > 0
}
}

View File

@@ -0,0 +1,63 @@
use std::sync::Arc;
use pumpkin_data::BlockState;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use serde::Deserialize;
use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level};
use super::{FoliagePlacer, LeaveValidator};
#[derive(Deserialize)]
pub struct BlobFoliagePlacer {
height: i32,
}
impl BlobFoliagePlacer {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
random: &mut RandomGenerator,
node: &TreeNode,
foliage_height: i32,
radius: i32,
offset: i32,
foliage_provider: &BlockState,
) {
for y in (offset - foliage_height..=offset).rev() {
let radius = (radius + node.foliage_radius - 1 - y / 2).max(0);
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
node.center,
radius,
y,
node.giant_trunk,
foliage_provider,
)
.await;
}
}
pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
self.height
}
}
impl LeaveValidator for BlobFoliagePlacer {
fn is_invalid_for_leaves(
&self,
random: &mut pumpkin_util::random::RandomGenerator,
dx: i32,
y: i32,
dz: i32,
radius: i32,
_giant_trunk: bool,
) -> bool {
dx == radius && dz == radius && (random.next_bounded_i32(2) == 0 || y == 0)
}
}

View File

@@ -0,0 +1,63 @@
use std::sync::Arc;
use pumpkin_data::BlockState;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use serde::Deserialize;
use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level};
use super::{FoliagePlacer, LeaveValidator};
#[derive(Deserialize)]
pub struct BushFoliagePlacer {
height: i32,
}
impl BushFoliagePlacer {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
random: &mut RandomGenerator,
node: &TreeNode,
foliage_height: i32,
radius: i32,
offset: i32,
foliage_provider: &BlockState,
) {
for y in (offset - foliage_height..=offset).rev() {
let radius = radius + node.foliage_radius - 1 - y;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
node.center,
radius,
y,
node.giant_trunk,
foliage_provider,
)
.await;
}
}
pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
self.height
}
}
impl LeaveValidator for BushFoliagePlacer {
fn is_invalid_for_leaves(
&self,
random: &mut pumpkin_util::random::RandomGenerator,
dx: i32,
_y: i32,
dz: i32,
radius: i32,
_giant_trunk: bool,
) -> bool {
dx == radius && dz == radius && random.next_bounded_i32(2) == 0
}
}

View File

@@ -0,0 +1,131 @@
use std::sync::Arc;
use pumpkin_data::BlockState;
use pumpkin_util::{
math::int_provider::IntProvider,
random::{RandomGenerator, RandomImpl},
};
use serde::Deserialize;
use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level};
use super::{FoliagePlacer, LeaveValidator};
#[derive(Deserialize)]
pub struct CherryFoliagePlacer {
height: IntProvider,
wide_bottom_layer_hole_chance: f32,
corner_hole_chance: f32,
hanging_leaves_chance: f32,
hanging_leaves_extension_chance: f32,
}
impl CherryFoliagePlacer {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
random: &mut RandomGenerator,
node: &TreeNode,
foliage_height: i32,
radius: i32,
offset: i32,
foliage_provider: &BlockState,
) {
let pos = node.center.up_height(offset);
let radius = radius + node.foliage_radius - 1;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius - 2,
foliage_height - 3,
node.giant_trunk,
foliage_provider,
)
.await;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius - 1,
foliage_height - 4,
node.giant_trunk,
foliage_provider,
)
.await;
for y in foliage_height - 5..0 {
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius,
y,
node.giant_trunk,
foliage_provider,
)
.await;
}
// TODO: generateSquareWithHangingLeaves
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius,
-1,
node.giant_trunk,
foliage_provider,
)
.await;
// TODO: generateSquareWithHangingLeaves
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius - 1,
-2,
node.giant_trunk,
foliage_provider,
)
.await;
}
pub fn get_random_height(&self, random: &mut RandomGenerator) -> i32 {
self.height.get(random)
}
}
impl LeaveValidator for CherryFoliagePlacer {
fn is_invalid_for_leaves(
&self,
random: &mut pumpkin_util::random::RandomGenerator,
dx: i32,
y: i32,
dz: i32,
radius: i32,
_giant_trunk: bool,
) -> bool {
if y == -1
&& (dx == radius || dz == radius)
&& random.next_f32() < self.wide_bottom_layer_hole_chance
{
return true;
}
let in_radius = dx == radius && dz == radius;
if radius > 2 {
return in_radius
|| dx + dz > radius * 2 - 2 && random.next_f32() < self.corner_hole_chance;
}
in_radius && random.next_f32() < self.corner_hole_chance
}
}

View File

@@ -0,0 +1,159 @@
use std::sync::Arc;
use pumpkin_data::BlockState;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use serde::Deserialize;
use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level};
use super::{FoliagePlacer, LeaveValidator};
#[derive(Deserialize)]
pub struct DarkOakFoliagePlacer;
impl DarkOakFoliagePlacer {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
random: &mut RandomGenerator,
node: &TreeNode,
_foliage_height: i32,
radius: i32,
offset: i32,
foliage_provider: &BlockState,
) {
let pos = node.center.up_height(offset);
let is_giant = node.giant_trunk;
if is_giant {
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius + 2,
-1,
node.giant_trunk,
foliage_provider,
)
.await;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius + 3,
0,
node.giant_trunk,
foliage_provider,
)
.await;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius + 2,
1,
node.giant_trunk,
foliage_provider,
)
.await;
if random.next_bool() {
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius,
2,
node.giant_trunk,
foliage_provider,
)
.await;
}
} else {
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius + 2,
-1,
node.giant_trunk,
foliage_provider,
)
.await;
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
pos,
radius + 1,
0,
node.giant_trunk,
foliage_provider,
)
.await;
}
}
pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
4
}
}
impl LeaveValidator for DarkOakFoliagePlacer {
fn is_position_invalid(
&self,
random: &mut RandomGenerator,
dx: i32,
y: i32,
dz: i32,
radius: i32,
giant_trunk: bool,
) -> bool {
dbg!("aa");
if !(y != 0 || !giant_trunk || dx != -radius && dx < radius || dz != -radius && dz < radius)
{
return true;
}
// This is default
let x = if giant_trunk {
dx.abs().min((dx - 1).abs())
} else {
dx.abs()
};
let z = if giant_trunk {
dz.abs().min((dz - 1).abs())
} else {
dz.abs()
};
self.is_invalid_for_leaves(random, x, y, z, radius, giant_trunk)
}
fn is_invalid_for_leaves(
&self,
_random: &mut pumpkin_util::random::RandomGenerator,
dx: i32,
y: i32,
dz: i32,
radius: i32,
giant_trunk: bool,
) -> bool {
if y == -1 && !giant_trunk {
return dx == radius && dz == radius;
}
if y == 1 {
return dx + dz > radius * 2 - 2;
}
false
}
}

View File

@@ -0,0 +1,68 @@
use std::sync::Arc;
use pumpkin_data::BlockState;
use pumpkin_util::{math::square, random::RandomGenerator};
use serde::Deserialize;
use crate::{ProtoChunk, generation::feature::features::tree::TreeNode, level::Level};
use super::{FoliagePlacer, LeaveValidator};
#[derive(Deserialize)]
pub struct LargeOakFoliagePlacer {
height: i32,
}
impl LargeOakFoliagePlacer {
#[expect(clippy::too_many_arguments)]
pub async fn generate(
&self,
chunk: &mut ProtoChunk<'_>,
level: &Arc<Level>,
random: &mut RandomGenerator,
node: &TreeNode,
foliage_height: i32,
radius: i32,
offset: i32,
foliage_provider: &BlockState,
) {
for y in (offset - foliage_height..=offset).rev() {
let radius = radius
+ if y == offset || y == offset - foliage_height {
0
} else {
1
};
FoliagePlacer::generate_square(
self,
chunk,
level,
random,
node.center,
radius,
y,
node.giant_trunk,
foliage_provider,
)
.await;
}
}
pub fn get_random_height(&self, _random: &mut RandomGenerator) -> i32 {
self.height
}
}
impl LeaveValidator for LargeOakFoliagePlacer {
fn is_invalid_for_leaves(
&self,
_random: &mut pumpkin_util::random::RandomGenerator,
dx: i32,
_y: i32,
dz: i32,
radius: i32,
_giant_trunk: bool,
) -> bool {
square(dx as f32 + 0.5) + square(dz as f32 + 0.5) > (radius * radius) as f32
}
}

Some files were not shown because too many files have changed in this diff Show More