feat: more chunk gen work

This commit is contained in:
Alexander Medvedev 2026-09-02 17:36:22 +02:00
parent 3a3ba2c873
commit 015091de12
63 changed files with 5582 additions and 1122 deletions

File diff suppressed because one or more lines are too long

View File

@ -454,6 +454,10 @@ impl GenerationCache for Cache {
fn is_air(&self, local_pos: &Vector3<i32>) -> bool { fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
is_air(GenerationCache::get_block_state(self, local_pos)) is_air(GenerationCache::get_block_state(self, local_pos))
} }
fn get_sea_level(&self) -> i32 {
self.get_center_chunk().get_sea_level()
}
} }
impl Cache { impl Cache {

View File

@ -38,8 +38,9 @@ impl BlockStateProvider {
Self::Simple(provider) => Some(provider.get(pos)), Self::Simple(provider) => Some(provider.get(pos)),
Self::Weighted(provider) => Some(provider.get(random)), Self::Weighted(provider) => Some(provider.get(random)),
Self::DualNoise(provider) => Some(provider.get(pos)), Self::DualNoise(provider) => Some(provider.get(pos)),
Self::Pillar(provider) => Some(provider.get(pos)), Self::Pillar(provider) => Some(provider.get_with_random(random, pos)),
Self::RandomizedInt(_) | Self::Rule(_) => None, Self::RandomizedInt(provider) => provider.get_for_bonemeal(random, pos),
Self::Rule(_) => None,
} }
} }
@ -55,10 +56,8 @@ impl BlockStateProvider {
Self::NoiseThreshold(provider) => provider.get(random, pos), Self::NoiseThreshold(provider) => provider.get(random, pos),
Self::NoiseProvider(provider) => provider.get(pos), Self::NoiseProvider(provider) => provider.get(pos),
Self::DualNoise(provider) => provider.get(pos), Self::DualNoise(provider) => provider.get(pos),
Self::Pillar(provider) => provider.get(pos), Self::Pillar(provider) => provider.get_with_random(random, pos),
Self::RandomizedInt(provider) => { Self::RandomizedInt(provider) => provider.get_state_for_world(world, random, pos),
provider.source.get_state_for_world(world, random, pos)
}
Self::Rule(provider) => { Self::Rule(provider) => {
for rule in &provider.rules { for rule in &provider.rules {
if rule.if_true.test_world(world, None, &pos) { if rule.if_true.test_world(world, None, &pos) {
@ -86,7 +85,7 @@ impl BlockStateProvider {
Self::Simple(provider) => provider.get(pos), Self::Simple(provider) => provider.get(pos),
Self::Weighted(provider) => provider.get(random), Self::Weighted(provider) => provider.get(random),
Self::DualNoise(provider) => provider.get(pos), Self::DualNoise(provider) => provider.get(pos),
Self::Pillar(provider) => provider.get(pos), Self::Pillar(provider) => provider.get_with_random(random, pos),
Self::RandomizedInt(provider) => provider.get(random, pos, chunk, block_registry), Self::RandomizedInt(provider) => provider.get(random, pos, chunk, block_registry),
Self::Rule(provider) => provider.get(block_registry, chunk, random, pos), Self::Rule(provider) => provider.get(block_registry, chunk, random, pos),
} }
@ -170,6 +169,63 @@ pub struct RandomizedIntBlockStateProvider {
} }
impl RandomizedIntBlockStateProvider { impl RandomizedIntBlockStateProvider {
fn apply_property(
&self,
base_state: &'static BlockState,
random: &mut RandomGenerator,
) -> &'static BlockState {
let value = self.values.get(random);
let val_str = value.to_string();
let block = Block::from_id(base_state.id.to_block_id());
block.properties(base_state.id).map_or_else(
|| {
let props = [(self.property.as_str(), val_str.as_str())];
let new_state_id = block.from_properties(&props).to_state_id(block);
BlockState::from_id(new_state_id)
},
|props_source| {
let mut props: Vec<(&str, &str)> = props_source
.to_props()
.iter()
.map(|(k, v)| (*k, *v))
.collect();
let mut found = false;
for (k, v) in &mut props {
if *k == self.property {
*v = &val_str;
found = true;
break;
}
}
if !found {
props.push((&self.property, &val_str));
}
let new_state_id = block.from_properties(&props).to_state_id(block);
BlockState::from_id(new_state_id)
},
)
}
pub fn get_for_bonemeal(
&self,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Option<&'static BlockState> {
self.source
.get_for_bonemeal(random, pos)
.map(|state| self.apply_property(state, random))
}
pub fn get_state_for_world(
&self,
world: &dyn BlockAccessor,
random: &mut RandomGenerator,
pos: BlockPos,
) -> &'static BlockState {
let state = self.source.get_state_for_world(world, random, pos);
self.apply_property(state, random)
}
pub fn get<T: GenerationCache>( pub fn get<T: GenerationCache>(
&self, &self,
random: &mut RandomGenerator, random: &mut RandomGenerator,
@ -177,8 +233,8 @@ impl RandomizedIntBlockStateProvider {
chunk: &T, chunk: &T,
block_registry: &dyn WorldPortalExt, block_registry: &dyn WorldPortalExt,
) -> &'static BlockState { ) -> &'static BlockState {
// TODO let state = self.source.get(random, pos, chunk, block_registry);
self.source.get(random, pos, chunk, block_registry) self.apply_property(state, random)
} }
} }
@ -189,7 +245,33 @@ pub struct PillarBlockStateProvider {
impl PillarBlockStateProvider { impl PillarBlockStateProvider {
#[must_use] #[must_use]
pub const fn get(&self, _pos: BlockPos) -> &'static BlockState { pub const fn get(&self, _pos: BlockPos) -> &'static BlockState {
// TODO: random axis self.state
}
#[must_use]
pub fn get_with_random(
&self,
random: &mut RandomGenerator,
_pos: BlockPos,
) -> &'static BlockState {
let axis_str = match random.next_bounded_i32(3) {
0 => "x",
1 => "y",
_ => "z",
};
let block = Block::from_id(self.state.id.to_block_id());
if let Some(props_source) = block.properties(self.state.id) {
let prop_list = props_source.to_props();
if prop_list.iter().any(|(k, _)| *k == "axis") {
let new_props: Vec<(&str, &str)> = prop_list
.iter()
.map(|(k, v)| (*k, if *k == "axis" { axis_str } else { *v }))
.collect();
let new_props_obj = block.from_properties(new_props.as_slice());
let new_state_id = new_props_obj.to_state_id(block);
return BlockState::from_id(new_state_id);
}
}
self.state self.state
} }
} }

View File

@ -422,21 +422,45 @@ impl ConfiguredFeature {
Self::BonusChest(_feature) => { Self::BonusChest(_feature) => {
BonusChestFeature::generate(chunk, min_y, height, feature_name, random, pos) BonusChestFeature::generate(chunk, min_y, height, feature_name, random, pos)
} }
Self::DeltaFeature(_feature) => { Self::DeltaFeature(feature) => {
DeltaFeatureFeature::generate(chunk, min_y, height, feature_name, random, pos) feature.generate(chunk, min_y, height, feature_name, random, pos)
} }
Self::BlockPile(feature) => feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
),
Self::DripstoneCluster(feature) => feature.generate(chunk, pos), Self::DripstoneCluster(feature) => feature.generate(chunk, pos),
Self::LargeDripstone(feature) => feature.generate(chunk, random, pos), Self::LargeDripstone(feature) => feature.generate(chunk, random, pos),
Self::EndGateway(feature) => feature.generate(chunk, pos), Self::EndGateway(feature) => feature.generate(chunk, pos),
Self::FillLayer(feature) => { Self::FillLayer(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos) feature.generate(chunk, min_y, height, feature_name, random, pos)
} }
Self::FallenTree(feature) => feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
),
Self::HugeBrownMushroom(feature) => { Self::HugeBrownMushroom(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos) feature.generate(chunk, min_y, height, feature_name, random, pos)
} }
Self::HugeFungus(feature) => { Self::HugeFungus(feature) => feature.generate(
feature.generate(chunk, min_y, height, feature_name, random, pos) chunk,
} block_registry,
min_y,
height,
feature_name,
random,
pos,
),
Self::HugeRedMushroom(feature) => { Self::HugeRedMushroom(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos) feature.generate(chunk, min_y, height, feature_name, random, pos)
} }
@ -452,7 +476,7 @@ impl ConfiguredFeature {
Self::WeepingVines(feature) => { Self::WeepingVines(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos) feature.generate(chunk, min_y, height, feature_name, random, pos)
} }
_ => false, Self::NoOp => false,
} }
} }
} }

View File

@ -23,65 +23,88 @@ impl BambooFeature {
block_registry: &dyn WorldPortalExt, block_registry: &dyn WorldPortalExt,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let mut i = 0; let mut placed = 0;
if chunk.is_air(&pos.0) { if chunk.is_air(&pos.0)
if block_registry.can_place_at(&Block::BAMBOO, Block::BAMBOO.default_state, chunk, &pos) && block_registry.can_place_at(&Block::BAMBOO, Block::BAMBOO.default_state, chunk, &pos)
{ {
let height = random.next_bounded_i32(12) + 5; let height = random.next_bounded_i32(12) + 5;
if random.next_f32() < self.probability { if random.next_f32() < self.probability {
let rnd = random.next_bounded_i32(4) + 1; let r = random.next_bounded_i32(4) + 1;
for x in pos.0.x - rnd..pos.0.x + rnd { for xx in pos.0.x - r..=pos.0.x + r {
for z in pos.0.z - rnd..pos.0.z + rnd { for zz in pos.0.z - r..=pos.0.z + r {
let block_below = let xd = xx - pos.0.x;
BlockPos::new(x, chunk.top_block_height_exclusive(x, z) - 1, z); let zd = zz - pos.0.z;
let block = GenerationCache::get_block_state(chunk, &block_below.0); if xd * xd + zd * zd <= r * r {
if !block.to_block_id().has_tag(tag::Block::MINECRAFT_DIRT) { let podzol_y = chunk.top_block_height_exclusive(xx, zz) - 1;
continue; let podzol_pos = BlockPos::new(xx, podzol_y, zz);
let block_id = GenerationCache::get_block_state(chunk, &podzol_pos.0)
.to_block_id();
if block_id
.has_tag(tag::Block::MINECRAFT_BENEATH_BAMBOO_PODZOL_REPLACEABLE)
{
chunk.set_block_state(&podzol_pos.0, Block::PODZOL.default_state);
} }
chunk.set_block_state(&block_below.0, Block::PODZOL.default_state);
} }
} }
} }
let mut bpos = pos; }
let bamboo = Block::BAMBOO.default_state;
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 = 1;
chunk.set_block_state( let trunk_props = BambooLikeProperties {
&bpos.0, age: 1,
BlockState::from_id(props.to_state_id(&Block::BAMBOO)), leaves: BambooLeaves::None,
); stage: 0,
props.stage = 0; };
let trunk_state = BlockState::from_id(trunk_props.to_state_id(&Block::BAMBOO));
chunk.set_block_state( let mut bpos = pos;
&bpos.down().0, for _ in 0..height {
BlockState::from_id(props.to_state_id(&Block::BAMBOO)), if chunk.is_air(&bpos.0) {
); chunk.set_block_state(&bpos.0, trunk_state);
props.leaves = BambooLeaves::Small; bpos = bpos.up();
} else {
chunk.set_block_state( break;
&bpos.down().down().0,
BlockState::from_id(props.to_state_id(&Block::BAMBOO)),
);
} }
} }
i += 1;
if bpos.0.y - pos.0.y >= 3 {
let final_large_props = BambooLikeProperties {
age: 1,
leaves: BambooLeaves::Large,
stage: 1,
};
chunk.set_block_state(
&bpos.down().0,
BlockState::from_id(final_large_props.to_state_id(&Block::BAMBOO)),
);
let top_large_props = BambooLikeProperties {
age: 1,
leaves: BambooLeaves::Large,
stage: 0,
};
chunk.set_block_state(
&bpos.down().down().0,
BlockState::from_id(top_large_props.to_state_id(&Block::BAMBOO)),
);
let top_small_props = BambooLikeProperties {
age: 1,
leaves: BambooLeaves::Small,
stage: 0,
};
chunk.set_block_state(
&bpos.down().down().down().0,
BlockState::from_id(top_small_props.to_state_id(&Block::BAMBOO)),
);
}
placed += 1;
} }
i > 0
placed > 0
} }
} }

View File

@ -6,7 +6,6 @@ use pumpkin_util::{
use crate::{generation::proto_chunk::GenerationCache, world::WorldPortalExt}; use crate::{generation::proto_chunk::GenerationCache, world::WorldPortalExt};
const LAVA_SEA_LEVEL: i32 = 32; // TODO: use getSeaLevel() instead of hardcoding this
const CLUSTERED_REACH: i32 = 5; const CLUSTERED_REACH: i32 = 5;
const UNCLUSTERED_REACH: i32 = 8; const UNCLUSTERED_REACH: i32 = 8;
const CLUSTERED_SIZE: i32 = 50; const CLUSTERED_SIZE: i32 = 50;
@ -143,7 +142,7 @@ fn find_air<T: GenerationCache>(chunk: &T, pos: BlockPos, mut limit: i32) -> Opt
fn is_air_or_lava_ocean<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool { fn is_air_or_lava_ocean<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
let state = GenerationCache::get_block_state(chunk, &pos.0); let state = GenerationCache::get_block_state(chunk, &pos.0);
is_air(state) || (state.to_block_id() == Block::LAVA.id && pos.0.y <= LAVA_SEA_LEVEL) is_air(state) || (state.to_block_id() == Block::LAVA.id && pos.0.y <= chunk.get_sea_level())
} }
const fn is_cannot_place_on(id: BlockId) -> bool { const fn is_cannot_place_on(id: BlockId) -> bool {

View File

@ -1 +1,75 @@
pub struct BlockPileFeature; use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::block_state_provider::BlockStateProvider;
use crate::generation::proto_chunk::GenerationCache;
use crate::world::WorldPortalExt;
pub struct BlockPileFeature {
pub state_provider: BlockStateProvider,
}
impl BlockPileFeature {
#[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
if pos.0.y < min_y as i32 + 5 {
return false;
}
let xr = 2 + random.next_bounded_i32(2);
let zr = 2 + random.next_bounded_i32(2);
for block_pos in BlockPos::iterate(pos.add(-xr, 0, -zr), pos.add(xr, 1, zr)) {
let xd = pos.0.x - block_pos.0.x;
let zd = pos.0.z - block_pos.0.z;
if (xd * xd + zd * zd) as f32 <= random.next_f32() * 10.0 - random.next_f32() * 6.0
|| random.next_f32() < 0.031
{
self.try_place_block(chunk, block_registry, block_pos, random);
}
}
true
}
fn may_place_on<T: GenerationCache>(
chunk: &T,
block_pos: BlockPos,
random: &mut RandomGenerator,
) -> bool {
let below = block_pos.down();
let below_state = GenerationCache::get_block_state(chunk, &below.0).to_state();
if below_state.id == Block::DIRT_PATH.default_state.id {
random.next_bool()
} else {
below_state.is_side_solid(BlockDirection::Up)
}
}
fn try_place_block<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
block_pos: BlockPos,
random: &mut RandomGenerator,
) {
if chunk.is_air(&block_pos.0) && Self::may_place_on(chunk, block_pos, random) {
let state = self
.state_provider
.get(random, block_pos, chunk, block_registry);
chunk.set_block_state(&block_pos.0, state);
}
}
}

View File

@ -1,4 +1,4 @@
use pumpkin_data::Block; use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
@ -6,8 +6,6 @@ use pumpkin_util::{
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
const SEA_LEVEL: i32 = 63; // TODO: use getSeaLevel() instead of hardcoding
pub struct BlueIceFeature; pub struct BlueIceFeature;
impl BlueIceFeature { impl BlueIceFeature {
@ -16,7 +14,7 @@ impl BlueIceFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
if pos.0.y >= SEA_LEVEL - 1 { if pos.0.y > chunk.get_sea_level() - 1 {
return false; return false;
} }
@ -27,64 +25,57 @@ impl BlueIceFeature {
return false; return false;
} }
let mut has_ice_neighbor = false; let mut found_packed_ice = false;
for neighbor_pos in [ for dir in BlockDirection::all() {
pos.up(), if dir != BlockDirection::Down {
pos.down(), let neighbor_pos = pos.offset(dir.to_offset());
pos.north(), if GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_block_id()
pos.south(), == Block::PACKED_ICE.id
pos.east(), {
pos.west(), found_packed_ice = true;
] { break;
if GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_state() }
== Block::ICE.default_state
{
has_ice_neighbor = true;
break;
} }
} }
if !has_ice_neighbor { if !found_packed_ice {
return false; return false;
} }
chunk.set_block_state(&pos.0, Block::BLUE_ICE.default_state); chunk.set_block_state(&pos.0, Block::BLUE_ICE.default_state);
for _ in 0..200 { for _ in 0..200 {
let offset_x = random.next_bounded_i32(8) - random.next_bounded_i32(8); let y_off = random.next_bounded_i32(5) - random.next_bounded_i32(6);
let offset_y = random.next_bounded_i32(8) - random.next_bounded_i32(8); let mut xz_diff = 3;
let offset_z = random.next_bounded_i32(8) - random.next_bounded_i32(8); if y_off < 2 {
let target_pos = pos.add(offset_x, offset_y, offset_z); xz_diff += y_off / 2;
if chunk.out_of_height(target_pos.0.y as i16) {
continue;
} }
let target_state = GenerationCache::get_block_state(chunk, &target_pos.0).to_state(); if xz_diff >= 1 {
if target_state.is_air() let place_pos = pos.add(
|| target_state == Block::WATER.default_state random.next_bounded_i32(xz_diff) - random.next_bounded_i32(xz_diff),
|| target_state == Block::ICE.default_state y_off,
|| target_state == Block::PACKED_ICE.default_state random.next_bounded_i32(xz_diff) - random.next_bounded_i32(xz_diff),
{ );
let mut has_blue_ice_neighbor = false; if chunk.out_of_height(place_pos.0.y as i16) {
for neighbor_pos in [ continue;
target_pos.up(),
target_pos.down(),
target_pos.north(),
target_pos.south(),
target_pos.east(),
target_pos.west(),
] {
if GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_state()
== Block::BLUE_ICE.default_state
{
has_blue_ice_neighbor = true;
break;
}
} }
if has_blue_ice_neighbor { let place_state = GenerationCache::get_block_state(chunk, &place_pos.0).to_state();
chunk.set_block_state(&target_pos.0, Block::BLUE_ICE.default_state); if place_state.is_air()
|| place_state == Block::WATER.default_state
|| place_state == Block::PACKED_ICE.default_state
|| place_state == Block::ICE.default_state
{
for dir in BlockDirection::all() {
let rel_pos = place_pos.offset(dir.to_offset());
if GenerationCache::get_block_state(chunk, &rel_pos.0).to_block_id()
== Block::BLUE_ICE.id
{
chunk.set_block_state(&place_pos.0, Block::BLUE_ICE.default_state);
break;
}
}
} }
} }
} }

View File

@ -1,5 +1,10 @@
use pumpkin_data::Block; use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
HeightMap,
math::{position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
@ -11,28 +16,57 @@ impl BonusChestFeature {
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, _feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let mut block_pos = pos; let chunk_min_x = (pos.0.x >> 4) << 4;
while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 { let chunk_min_z = (pos.0.z >> 4) << 4;
block_pos = block_pos.down();
let mut x_poses: Vec<i32> = (chunk_min_x..=chunk_min_x + 15).collect();
let mut z_poses: Vec<i32> = (chunk_min_z..=chunk_min_z + 15).collect();
for i in (1..x_poses.len()).rev() {
let j = random.next_bounded_i32(i as i32 + 1) as usize;
x_poses.swap(i, j);
}
for i in (1..z_poses.len()).rev() {
let j = random.next_bounded_i32(i as i32 + 1) as usize;
z_poses.swap(i, j);
} }
let chest_pos = block_pos.up(); for &x in &x_poses {
if !chunk.is_air(&chest_pos.0) { for &z in &z_poses {
return false; let y = chunk.get_top_y(&HeightMap::MotionBlockingNoLeaves, x, z);
} let chest_pos = Vector3::new(x, y, z);
if chunk.is_air(&chest_pos) {
chunk.set_block_state(&chest_pos, Block::CHEST.default_state);
chunk.set_block_state(&chest_pos.0, Block::CHEST.default_state); let loot_seed = random.next_i64();
let mut chest_nbt = NbtCompound::new();
chest_nbt.put_string("id", "minecraft:chest".to_string());
chest_nbt.put_int("x", chest_pos.x);
chest_nbt.put_int("y", chest_pos.y);
chest_nbt.put_int("z", chest_pos.z);
chest_nbt.put_string(
"LootTable",
"minecraft:chests/spawn_bonus_chest".to_string(),
);
chest_nbt.put_long("LootTableSeed", loot_seed);
chunk.add_block_entity(&chest_pos, chest_nbt);
let torch_offsets = [(1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)]; for dir in BlockDirection::horizontal() {
for &(dx, dy, dz) in &torch_offsets { let torch_pos = chest_pos.add(&dir.to_offset());
let t_pos = BlockPos::new(chest_pos.0.x + dx, chest_pos.0.y + dy, chest_pos.0.z + dz); let below_torch = torch_pos.add(&Vector3::new(0, -1, 0));
if chunk.is_air(&t_pos.0) { if !chunk.is_air(&below_torch) && chunk.is_air(&torch_pos) {
chunk.set_block_state(&t_pos.0, Block::TORCH.default_state); chunk.set_block_state(&torch_pos, Block::TORCH.default_state);
}
}
return true;
}
} }
} }
true
false
} }
} }

View File

@ -16,53 +16,69 @@ impl CoralClawFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random); let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random);
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) {
return false; return false;
} }
let direction = BlockDirection::random_horizontal(random); let claw_dir =
let i = random.next_bounded_i32(2) + 2; BlockDirection::from_cardinal_direction(BlockDirection::random_horizontal(random));
// TODO: vanilla iterates the first `i` of Util.toShuffledList([direction, let n_branches = random.next_bounded_i32(2) + 2;
// direction.getClockWise(), direction.getCounterClockWise()], random) — the let mut possible_directions = [
// shuffle consumes RNG draws and the opposite of `direction` is never visited. claw_dir,
let directions = BlockDirection::horizontal_worldgen() claw_dir.rotate_clockwise(),
.into_iter() claw_dir.rotate_counter_clockwise(),
.take(i as usize); ];
'block0: for direction2 in directions { for i in (1..possible_directions.len()).rev() {
let mut pos = pos; let j = random.next_bounded_i32(i as i32 + 1) as usize;
let j = random.next_bounded_i32(2) + 1; possible_directions.swap(i, j);
pos = pos.offset(direction2.to_offset()); }
let branch_direction; for &branch_direction in possible_directions.iter().take(n_branches as usize) {
let mut mut_pos = pos;
let sideway_length = random.next_bounded_i32(2) + 1;
mut_pos = mut_pos.offset(branch_direction.to_offset());
let k = if direction2 == direction { let (segment_direction, inway_length) = if branch_direction == claw_dir {
branch_direction = direction; (claw_dir, random.next_bounded_i32(3) + 2)
random.next_bounded_i32(3) + 2
} else { } else {
pos = pos.up(); mut_pos = mut_pos.up();
//let _directions = [direction2, BlockDirection::Up]; let seg_dir = if random.next_bool() {
branch_direction = direction2; // TODO: make this random branch_direction
random.next_bounded_i32(3) + 5 } else {
BlockDirection::Up
};
(seg_dir, random.next_bounded_i32(3) + 3)
}; };
for _ in 0..j { for _ in 0..sideway_length {
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { if !CoralFeature::generate_coral_piece(
chunk,
block_registry,
random,
block,
mut_pos,
) {
break; break;
} }
pos = pos.offset(branch_direction.to_offset()); mut_pos = mut_pos.offset(segment_direction.to_offset());
} }
pos = pos.offset(branch_direction.to_offset()); mut_pos = mut_pos.offset(segment_direction.opposite().to_offset());
pos = pos.up(); mut_pos = mut_pos.up();
for _l in 0..k { for _ in 0..inway_length {
pos = pos.offset(direction.opposite().to_offset()); mut_pos = mut_pos.offset(claw_dir.to_offset());
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { if !CoralFeature::generate_coral_piece(
continue 'block0; chunk,
block_registry,
random,
block,
mut_pos,
) {
break;
} }
if random.next_f32() < 0.25 { if random.next_f32() < 0.25 {
pos = pos.up(); mut_pos = mut_pos.up();
} }
} }
} }

View File

@ -1,7 +1,7 @@
use crate::{generation::proto_chunk::GenerationCache, world::WorldPortalExt}; use crate::{generation::proto_chunk::GenerationCache, world::WorldPortalExt};
use pumpkin_data::tag; use pumpkin_data::tag;
use pumpkin_util::{ use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3}, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
}; };
@ -10,47 +10,47 @@ use super::CoralFeature;
pub struct CoralMushroomFeature; pub struct CoralMushroomFeature;
impl CoralMushroomFeature { impl CoralMushroomFeature {
#[allow(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
chunk: &mut T, chunk: &mut T,
block_registry: &dyn WorldPortalExt, block_registry: &dyn WorldPortalExt,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random); let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random);
let i = random.next_bounded_i32(3) + 3; let height = random.next_bounded_i32(3) + 3;
let j = random.next_bounded_i32(3) + 3; let width = random.next_bounded_i32(3) + 3;
let k = random.next_bounded_i32(3) + 3; let length = random.next_bounded_i32(3) + 3;
let l = random.next_bounded_i32(3) + 1; let sink_value = random.next_bounded_i32(3) + 1;
for m in 0..=j { for x in 0..=width {
for n in 0..=i { for y in 0..=height {
for o in 0..=k { for z in 0..=length {
let mut pos = pos; let cur_pos = BlockPos::new(pos.0.x + x, pos.0.y + y - sink_value, pos.0.z + z);
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_a = (x != 0 && x != width) || (y != 0 && y != height);
let condition_b = (o != 0 && o != k) || (n != 0 && n != i); let condition_b = (z != 0 && z != length) || (y != 0 && y != height);
let condition_c = (m != 0 && m != j) || (o != 0 && o != k); let condition_c = (x != 0 && x != width) || (z != 0 && z != length);
let condition_d = m == 0 || m == j || n == 0 || n == i || o == 0 || o == k; let condition_d =
let random_check = random.next_f32() < 0.1f32; x == 0 || x == width || y == 0 || y == height || z == 0 || z == length;
if !((condition_a && condition_b && condition_c && condition_d) if condition_a
&& !random_check && condition_b
&& CoralFeature::generate_coral_piece( && condition_c
&& condition_d
&& random.next_f32() >= 0.1
{
CoralFeature::generate_coral_piece(
chunk, chunk,
block_registry, block_registry,
random, random,
block, block,
pos, cur_pos,
)) );
{} }
} }
} }
} }

View File

@ -10,49 +10,59 @@ use super::CoralFeature;
pub struct CoralTreeFeature; pub struct CoralTreeFeature;
impl CoralTreeFeature { impl CoralTreeFeature {
#[allow(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
chunk: &mut T, chunk: &mut T,
block_registry: &dyn WorldPortalExt, block_registry: &dyn WorldPortalExt,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random); let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random);
let mut pos = pos; let mut cur_pos = pos;
let i = random.next_bounded_i32(3) + 1; let trunk_height = random.next_bounded_i32(3) + 1;
for _ in 0..i { for _ in 0..trunk_height {
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, cur_pos) {
return true; return true;
} }
pos = pos.up(); cur_pos = cur_pos.up();
} }
let i = random.next_bounded_i32(3) + 2;
// TODO: vanilla takes the first `i` of Plane.HORIZONTAL.shuffledCopy(random) — a let trunk_top_pos = cur_pos;
// Fisher–Yates that must run over the horizontal_worldgen() [N, E, S, W] base let n_branches = random.next_bounded_i32(3) + 2;
// order and consumes RNG draws.
let directions = BlockDirection::horizontal_worldgen() let mut directions: Vec<BlockDirection> = BlockDirection::horizontal_worldgen()
.into_iter() .iter()
.take(i as usize); .copied()
for dir in directions { .map(BlockDirection::from_cardinal_direction)
pos = pos.offset(dir.to_offset()); .collect();
let times = random.next_bounded_i32(5) + 2; for i in (1..directions.len()).rev() {
let mut m = 0; let j = random.next_bounded_i32(i as i32 + 1) as usize;
for n in 0..times { directions.swap(i, j);
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { }
for &branch_direction in directions.iter().take(n_branches as usize) {
let mut branch_pos = trunk_top_pos.offset(branch_direction.to_offset());
let branch_height = random.next_bounded_i32(5) + 2;
let mut segment_length = 0;
for j in 0..branch_height {
if !CoralFeature::generate_coral_piece(
chunk,
block_registry,
random,
block,
branch_pos,
) {
break; break;
} }
pos = pos.up(); segment_length += 1;
m += 1; branch_pos = branch_pos.up();
if n != 0 && (m < 2 || random.next_f32() >= 0.25) { if j == 0 || (segment_length >= 2 && random.next_f32() < 0.25) {
continue; branch_pos = branch_pos.offset(branch_direction.to_offset());
segment_length = 0;
} }
pos = pos.offset(dir.to_offset());
m = 0;
} }
} }
true true

View File

@ -24,9 +24,10 @@ impl CoralFeature {
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id(); let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id();
let above_block = GenerationCache::get_block_state(chunk, &pos.up().0).to_block_id(); let above_pos = pos.up();
let above_block = GenerationCache::get_block_state(chunk, &above_pos.0).to_block_id();
if block != BlockId::WATER && !block.has_tag(tag::Block::MINECRAFT_CORALS) if (block != BlockId::WATER && !block.has_tag(tag::Block::MINECRAFT_CORALS))
|| above_block != BlockId::WATER || above_block != BlockId::WATER
{ {
return false; return false;
@ -41,18 +42,22 @@ impl CoralFeature {
Block::from_state_id(block_to_place_state.id), Block::from_state_id(block_to_place_state.id),
block_to_place_state, block_to_place_state,
chunk, chunk,
&pos, &above_pos,
) { ) {
chunk.set_block_state(&pos.0, block_to_place_state); chunk.set_block_state(&above_pos.0, block_to_place_state);
} }
} else if random.next_f32() < 0.05 { } else if random.next_f32() < 0.05 {
let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE);
props.pickles = (random.next_bounded_i32(4) as u8) + 1; props.pickles = (random.next_bounded_i32(4) as u8) + 1;
let state_id = props.to_state_id(&Block::SEA_PICKLE); let state_id = props.to_state_id(&Block::SEA_PICKLE);
let block_state = BlockState::from_id(state_id); let block_state = BlockState::from_id(state_id);
if block_registry.can_place_at(Block::from_state_id(state_id), block_state, chunk, &pos) if block_registry.can_place_at(
{ Block::from_state_id(state_id),
chunk.set_block_state(&pos.0, block_state); block_state,
chunk,
&above_pos,
) {
chunk.set_block_state(&above_pos.0, block_state);
} }
} }
for dir in BlockDirection::horizontal_worldgen() { for dir in BlockDirection::horizontal_worldgen() {
@ -68,7 +73,6 @@ impl CoralFeature {
continue; continue;
}; };
let original_props = &properties.to_props(); let original_props = &properties.to_props();
// Set the right Axis
let props: Vec<(&str, &str)> = original_props let props: Vec<(&str, &str)> = original_props
.iter() .iter()
.map(|(key, value)| { .map(|(key, value)| {

View File

@ -1,12 +1,21 @@
use pumpkin_data::Block; use pumpkin_data::{BlockDirection, BlockId, BlockState};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl}; use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos},
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
pub struct DeltaFeatureFeature; pub struct DeltaFeatureFeature {
pub contents: &'static BlockState,
pub rim: &'static BlockState,
pub size: IntProvider,
pub rim_size: IntProvider,
}
impl DeltaFeatureFeature { impl DeltaFeatureFeature {
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T, chunk: &mut T,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
@ -14,32 +23,81 @@ impl DeltaFeatureFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let size = random.next_bounded_i32(3) + 2; let mut any_placed = false;
let mut generated = false; let spawn_rim = random.next_f64() < 0.9;
let rim_x = if spawn_rim {
self.rim_size.get(random)
} else {
0
};
let rim_z = if spawn_rim {
self.rim_size.get(random)
} else {
0
};
let has_rim = spawn_rim && rim_x != 0 && rim_z != 0;
let radius_x = self.size.get(random);
let radius_z = self.size.get(random);
let radius_limit = radius_x.max(radius_z);
for dx in -size..=size { for pos_iter in BlockPos::iterate_outwards(pos, radius_x, 0, radius_z) {
for dy in -size..=size { if pos_iter.manhattan_distance(pos) > radius_limit {
for dz in -size..=size { break;
if dx * dx + dy * dy + dz * dz <= size * size { }
let target = BlockPos::new(pos.0.x + dx, pos.0.y + dy, pos.0.z + dz);
let block_id = if Self::is_clear(chunk, pos_iter, self.contents) {
GenerationCache::get_block_state(chunk, &target.0).to_block_id(); if has_rim {
if block_id == Block::NETHERRACK.id any_placed = true;
|| block_id == Block::LAVA.id chunk.set_block_state(&pos_iter.0, self.rim);
|| block_id == Block::AIR.id }
{
let replacement = if random.next_f32() < 0.6 { let pos_offset = pos_iter.add(rim_x, 0, rim_z);
Block::BASALT if Self::is_clear(chunk, pos_offset, self.contents) {
} else { any_placed = true;
Block::BLACKSTONE chunk.set_block_state(&pos_offset.0, self.contents);
};
chunk.set_block_state(&target.0, replacement.default_state);
generated = true;
}
}
} }
} }
} }
generated
any_placed
}
fn is_clear<T: GenerationCache>(
chunk: &T,
pos: BlockPos,
contents: &'static BlockState,
) -> bool {
let state = GenerationCache::get_block_state(chunk, &pos.0);
if state.to_block_id() == contents.id.to_block_id() {
return false;
}
if is_cannot_replace(state.to_block_id()) {
return false;
}
for dir in BlockDirection::all() {
let neighbor_state =
GenerationCache::get_block_state(chunk, &pos.offset(dir.to_offset()).0);
let is_air = neighbor_state.to_state().is_air();
if (is_air && dir != BlockDirection::Up) || (!is_air && dir == BlockDirection::Up) {
return false;
}
}
true
} }
} }
const fn is_cannot_replace(id: BlockId) -> bool {
matches!(
id,
BlockId::BEDROCK
| BlockId::NETHER_BRICKS
| BlockId::NETHER_BRICK_FENCE
| BlockId::NETHER_BRICK_STAIRS
| BlockId::NETHER_WART
| BlockId::CHEST
| BlockId::SPAWNER
)
}

View File

@ -1,7 +1,8 @@
use pumpkin_data::{Block, BlockDirection}; use pumpkin_data::{Block, BlockDirection};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{ use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3}, math::{position::BlockPos, vector3::Vector3},
random::RandomGenerator, random::{RandomGenerator, RandomImpl},
}; };
use crate::generation::{noise::WATER_BLOCK, proto_chunk::GenerationCache}; use crate::generation::{noise::WATER_BLOCK, proto_chunk::GenerationCache};
@ -19,8 +20,8 @@ impl DesertWellFeature {
chunk: &mut T, chunk: &mut T,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature _feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
const CAN_GENERATE: Block = Block::SAND; const CAN_GENERATE: Block = Block::SAND;
@ -29,7 +30,7 @@ impl DesertWellFeature {
while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 { while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 {
block_pos = block_pos.down(); block_pos = block_pos.down();
} }
let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id(); let block = GenerationCache::get_block_state(chunk, &block_pos.0).to_block_id();
if CAN_GENERATE.id != block { if CAN_GENERATE.id != block {
return false; return false;
} }
@ -139,6 +140,33 @@ impl DesertWellFeature {
); );
} }
let water_center = block_pos.0;
let water_positions = [
water_center,
water_center.add(&Vector3::new(1, 0, 0)),
water_center.add(&Vector3::new(0, 0, 1)),
water_center.add(&Vector3::new(-1, 0, 0)),
water_center.add(&Vector3::new(0, 0, -1)),
];
let pos1 =
water_positions[random.next_bounded_i32(5) as usize].add(&Vector3::new(0, -1, 0));
let pos2 =
water_positions[random.next_bounded_i32(5) as usize].add(&Vector3::new(0, -2, 0));
Self::place_sus_sand(chunk, &pos1);
Self::place_sus_sand(chunk, &pos2);
true true
} }
fn place_sus_sand<T: GenerationCache>(chunk: &mut T, pos: &Vector3<i32>) {
chunk.set_block_state(pos, Block::SUSPICIOUS_SAND.default_state);
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:brushable_block".to_string());
nbt.put_int("x", pos.x);
nbt.put_int("y", pos.y);
nbt.put_int("z", pos.z);
nbt.put_string("LootTable", "minecraft:archaeology/desert_well".to_string());
nbt.put_long("LootTableSeed", BlockPos(*pos).as_long());
chunk.add_block_entity(pos, nbt);
}
} }

View File

@ -1,7 +1,9 @@
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use pumpkin_data::Block; use pumpkin_data::block_properties::{
use pumpkin_data::BlockId; PointedDripstoneLikeProperties, SpeleothemThickness, VerticalDirection,
};
use pumpkin_data::tag; use pumpkin_data::tag;
use pumpkin_data::{Block, BlockDirection, BlockId, BlockState};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
pub mod cluster; pub mod cluster;
@ -20,3 +22,55 @@ pub(super) fn gen_dripstone<T: GenerationCache>(chunk: &mut T, pos: BlockPos) ->
} }
false false
} }
pub(super) fn is_empty_or_water<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id();
block == BlockId::AIR
|| block == BlockId::CAVE_AIR
|| block == BlockId::VOID_AIR
|| block == BlockId::WATER
}
pub(super) fn grow_pointed_dripstone<T: GenerationCache>(
chunk: &mut T,
start_pos: BlockPos,
tip_direction: BlockDirection,
height: i32,
merged_tip: bool,
) {
let mut cur = start_pos;
let vert_dir = match tip_direction {
BlockDirection::Down => VerticalDirection::Down,
_ => VerticalDirection::Up,
};
let mut thicknesses = Vec::with_capacity(height as usize);
if height >= 3 {
thicknesses.push(SpeleothemThickness::Base);
for _ in 0..(height - 3) {
thicknesses.push(SpeleothemThickness::Middle);
}
}
if height >= 2 {
thicknesses.push(SpeleothemThickness::Frustum);
}
if height >= 1 {
thicknesses.push(if merged_tip {
SpeleothemThickness::TipMerge
} else {
SpeleothemThickness::Tip
});
}
for thickness in thicknesses {
let is_water =
GenerationCache::get_block_state(chunk, &cur.0).to_block_id() == BlockId::WATER;
let mut props = PointedDripstoneLikeProperties::default(&Block::POINTED_DRIPSTONE);
props.thickness = thickness;
props.vertical_direction = vert_dir;
props.waterlogged = is_water;
let state_id = props.to_state_id(&Block::POINTED_DRIPSTONE);
chunk.set_block_state(&cur.0, BlockState::from_id(state_id));
cur = cur.offset(tip_direction.to_offset());
}
}

View File

@ -20,9 +20,17 @@ impl SmallDripstoneFeature {
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
if let Some(dir) = Self::get_direction(chunk, pos, random) { if let Some(dir) = Self::get_direction(chunk, pos, random) {
let pos = pos.offset(dir.opposite().to_offset()); let root_pos = pos.offset(dir.opposite().to_offset());
self.gen_dripstone_blocks(chunk, pos, random); self.gen_dripstone_blocks(chunk, root_pos, random);
// TODO let next_pos = pos.offset(dir.to_offset());
let height = if random.next_f32() < self.taller_dripstone
&& super::is_empty_or_water(chunk, next_pos)
{
2
} else {
1
};
super::grow_pointed_dripstone(chunk, pos, dir, height, false);
return true; return true;
} }
false false

View File

@ -44,7 +44,6 @@ impl EndPodiumFeature {
let mut oy = origin.0.y; let mut oy = origin.0.y;
let oz = origin.0.z; let oz = origin.0.z;
// Podium spans [ox - 4, ox + 4] and [oz - 4, oz + 4]
if ox + 4 < min_x || ox - 4 > max_x || oz + 4 < min_z || oz - 4 > max_z { if ox + 4 < min_x || ox - 4 > max_x || oz + 4 < min_z || oz - 4 > max_z {
return false; return false;
} }
@ -76,7 +75,7 @@ impl EndPodiumFeature {
if y < oy { if y < oy {
if closer_than_2_5 { if closer_than_2_5 {
chunk.set_block_state(&target, Block::BEDROCK.default_state); chunk.set_block_state(&target, Block::BEDROCK.default_state);
} else if self.active { } else {
chunk.set_block_state(&target, Block::END_STONE.default_state); chunk.set_block_state(&target, Block::END_STONE.default_state);
} }
} else if y > oy { } else if y > oy {

View File

@ -182,7 +182,6 @@ impl EndSpikeFeature {
chunk.set_block_state(&pos.0, Block::AIR.default_state); chunk.set_block_state(&pos.0, Block::AIR.default_state);
} }
// Bedrock cap serves as the crystal base, fire sits on top of it
chunk.set_block_state( chunk.set_block_state(
&pumpkin_util::math::vector3::Vector3::new( &pumpkin_util::math::vector3::Vector3::new(
spike.center_x, spike.center_x,
@ -200,12 +199,10 @@ impl EndSpikeFeature {
Block::FIRE.default_state, Block::FIRE.default_state,
); );
// Iron-bar cage for guarded spikes: 5x5 walls + open top frame at dy=3.
if spike.guarded { if spike.guarded {
for dy in 0i32..=3 { for dy in 0i32..=3 {
for dx in -2i32..=2 { for dx in -2i32..=2 {
for dz in -2i32..=2 { for dz in -2i32..=2 {
// Only place on perimeter walls and the top frame
let x_wall_present = dx.abs() == 2; let x_wall_present = dx.abs() == 2;
let z_wall_present = dz.abs() == 2; let z_wall_present = dz.abs() == 2;
let on_top = dy == 3; let on_top = dy == 3;
@ -213,15 +210,14 @@ impl EndSpikeFeature {
continue; continue;
} }
// Connectivity rules
let x_edge = x_wall_present || on_top; let x_edge = x_wall_present || on_top;
let z_edge = z_wall_present || on_top; let z_edge = z_wall_present || on_top;
let mut props = OakFenceLikeProperties::default(&Block::IRON_BARS); let mut props = OakFenceLikeProperties::default(&Block::IRON_BARS);
props.north = x_edge && dz != 2; props.north = x_edge && dz != -2;
props.south = x_edge && dz != -2; props.south = x_edge && dz != 2;
props.west = z_edge && dx != 2; props.west = z_edge && dx != -2;
props.east = z_edge && dx != -2; props.east = z_edge && dx != 2;
let bar_state = BlockState::from_id(props.to_state_id(&Block::IRON_BARS)); let bar_state = BlockState::from_id(props.to_state_id(&Block::IRON_BARS));
chunk.set_block_state( chunk.set_block_state(

View File

@ -1,29 +1,204 @@
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_data::{Block, BlockDirection, BlockState};
use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos},
random::{RandomGenerator, RandomImpl},
};
use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider}; use super::tree::{TreeFeature, decorator::TreeDecorator};
use crate::{
generation::{block_state_provider::BlockStateProvider, proto_chunk::GenerationCache},
world::WorldPortalExt,
};
pub struct FallenTreeFeature { pub struct FallenTreeFeature {
pub trunk_provider: BlockStateProvider, pub trunk_provider: BlockStateProvider,
pub log_length: IntProvider,
pub stump_decorators: Vec<TreeDecorator>,
pub log_decorators: Vec<TreeDecorator>,
} }
impl FallenTreeFeature { impl FallenTreeFeature {
pub const fn generate( #[allow(clippy::too_many_arguments)]
_chunk: &mut ProtoChunk, pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature _feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator, random: &mut RandomGenerator,
_pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
false self.place_stump(chunk, block_registry, random, pos);
let direction =
BlockDirection::from_cardinal_direction(BlockDirection::random_horizontal(random));
let log_length = self.log_length.get(random) - 2;
let mut log_start_pos =
pos.offset(direction.to_offset() * (2 + random.next_bounded_i32(2)));
Self::set_ground_height_for_fallen_log_start_pos(chunk, &mut log_start_pos);
if Self::can_place_entire_fallen_log(chunk, log_length, log_start_pos, direction) {
self.place_fallen_log(
chunk,
block_registry,
random,
log_length,
log_start_pos,
direction,
);
}
true
} }
// fn gen_stump(&self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, pos: BlockPos) { fn set_ground_height_for_fallen_log_start_pos<T: GenerationCache>(
// // chunk.set_block_state( chunk: &T,
// // pos.0.x, log_start_pos: &mut BlockPos,
// // pos.0.y, ) {
// // pos.0.z, *log_start_pos = log_start_pos.up();
// // self.trunk_provider.get(random, pos, chunk), for _ in 0..6 {
// // ); if Self::may_place_on(chunk, *log_start_pos) {
// } return;
}
*log_start_pos = log_start_pos.down();
}
}
fn place_stump<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
stump_pos: BlockPos,
) {
let stump = self.place_log_block(chunk, block_registry, random, stump_pos, None);
Self::decorate_logs(
chunk,
block_registry,
random,
&[stump],
&self.stump_decorators,
);
}
fn can_place_entire_fallen_log<T: GenerationCache>(
chunk: &T,
log_length: i32,
mut log_start_pos: BlockPos,
direction: BlockDirection,
) -> bool {
let mut gap_in_ground = 0;
let offset = direction.to_offset();
for _ in 0..log_length {
let state = GenerationCache::get_block_state(chunk, &log_start_pos.0);
if !TreeFeature::can_replace(state.to_state(), state.to_block_id()) {
return false;
}
if Self::is_over_solid_ground(chunk, log_start_pos) {
gap_in_ground = 0;
} else {
gap_in_ground += 1;
if gap_in_ground > 2 {
return false;
}
}
log_start_pos = log_start_pos.offset(offset);
}
true
}
fn place_fallen_log<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
log_length: i32,
mut log_start_pos: BlockPos,
direction: BlockDirection,
) {
let mut fallen_log = Vec::with_capacity(log_length.max(0) as usize);
let offset = direction.to_offset();
for _ in 0..log_length {
let placed = self.place_log_block(
chunk,
block_registry,
random,
log_start_pos,
Some(direction),
);
fallen_log.push(placed);
log_start_pos = log_start_pos.offset(offset);
}
Self::decorate_logs(
chunk,
block_registry,
random,
&fallen_log,
&self.log_decorators,
);
}
fn may_place_on<T: GenerationCache>(chunk: &T, block_pos: BlockPos) -> bool {
let state = GenerationCache::get_block_state(chunk, &block_pos.0);
TreeFeature::can_replace(state.to_state(), state.to_block_id())
&& Self::is_over_solid_ground(chunk, block_pos)
}
fn is_over_solid_ground<T: GenerationCache>(chunk: &T, block_pos: BlockPos) -> bool {
let below = block_pos.down();
GenerationCache::get_block_state(chunk, &below.0)
.to_state()
.is_side_solid(BlockDirection::Up)
}
fn place_log_block<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
block_pos: BlockPos,
direction: Option<BlockDirection>,
) -> BlockPos {
let mut state = self
.trunk_provider
.get(random, block_pos, chunk, block_registry);
if let Some(dir) = direction {
let block = Block::from_state_id(state.id);
let axis_str = match dir {
BlockDirection::East | BlockDirection::West => "x",
BlockDirection::North | BlockDirection::South => "z",
_ => "y",
};
if let Some(props) = block.properties(state.id) {
let prop_list = props.to_props();
if prop_list.iter().any(|(k, _)| *k == "axis") {
let new_props: Vec<(&str, &str)> = prop_list
.iter()
.map(|(k, v)| (*k, if *k == "axis" { axis_str } else { *v }))
.collect();
let new_props_obj = block.from_properties(new_props.as_slice());
let new_state_id = new_props_obj.to_state_id(block);
state = BlockState::from_id(new_state_id);
}
}
}
chunk.set_block_state(&block_pos.0, state);
block_pos
}
fn decorate_logs<T: GenerationCache>(
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
logs: &[BlockPos],
decorators: &[TreeDecorator],
) {
for decorator in decorators {
decorator.generate(chunk, block_registry, random, &[], logs, &[]);
}
}
} }

View File

@ -1,25 +1,30 @@
use pumpkin_data::Block; use pumpkin_data::BlockState;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
pub struct FillLayerFeature; pub struct FillLayerFeature {
pub height: i32,
pub state: &'static BlockState,
}
impl FillLayerFeature { impl FillLayerFeature {
#[allow(clippy::unused_self)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
chunk: &mut T, chunk: &mut T,
_min_y: i8, min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, _feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator, _random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
for dx in -1..=1 { let y = min_y as i32 + self.height;
for dz in -1..=1 { for dx in 0..16 {
let target = BlockPos::new(pos.0.x + dx, pos.0.y, pos.0.z + dz); for dz in 0..16 {
chunk.set_block_state(&target.0, Block::SNOW.default_state); let target = BlockPos::new(pos.0.x + dx, y, pos.0.z + dz);
if chunk.is_air(&target.0) {
chunk.set_block_state(&target.0, self.state);
}
} }
} }
true true

View File

@ -4,8 +4,6 @@ use pumpkin_data::{Block, BlockState};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::random::RandomGenerator; use pumpkin_util::random::RandomGenerator;
const SEA_LEVEL: i32 = 63; // TODO: getSeaLevel() from the worldgen context instead of hardcoding
pub struct FreezeTopLayerFeature; pub struct FreezeTopLayerFeature;
impl FreezeTopLayerFeature { impl FreezeTopLayerFeature {
@ -42,9 +40,10 @@ impl FreezeTopLayerFeature {
} }
// Snow check // Snow check
let top_temp = biome let top_temp =
.weather biome
.compute_temperature(x as f64, y, z as f64, SEA_LEVEL); .weather
.compute_temperature(x as f64, y, z as f64, chunk.get_sea_level());
if top_temp < 0.15 { if top_temp < 0.15 {
let top_raw = GenerationCache::get_block_state(chunk, &top_vec); let top_raw = GenerationCache::get_block_state(chunk, &top_vec);

View File

@ -1,56 +1,246 @@
use pumpkin_data::Block; use pumpkin_data::{Block, BlockState};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl}; use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache; use super::weeping_vines::WeepingVinesFeature;
use crate::{
generation::{block_predicate::BlockPredicate, proto_chunk::GenerationCache},
world::WorldPortalExt,
};
pub struct HugeFungusFeature; pub struct HugeFungusFeature {
pub valid_base_block: &'static BlockState,
pub stem_state: &'static BlockState,
pub hat_state: &'static BlockState,
pub decor_state: &'static BlockState,
pub replaceable_blocks: BlockPredicate,
pub planted: bool,
}
impl HugeFungusFeature { impl HugeFungusFeature {
#[allow(clippy::unused_self)] #[allow(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
chunk: &mut T, chunk: &mut T,
block_registry: &dyn WorldPortalExt,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let height = random.next_bounded_i32(4) + 6; let below = pos.down();
let is_warped = random.next_bool(); let below_block = GenerationCache::get_block_state(chunk, &below.0).to_block_id();
let valid_base_id = Block::from_state_id(self.valid_base_block.id).id;
let stem_state = if is_warped { if below_block != valid_base_id {
Block::WARPED_STEM.default_state return false;
} else {
Block::CRIMSON_STEM.default_state
};
let wart_state = if is_warped {
Block::WARPED_WART_BLOCK.default_state
} else {
Block::NETHER_WART_BLOCK.default_state
};
for i in 0..height {
let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z);
chunk.set_block_state(&stem_pos.0, stem_state);
} }
let cap_y = pos.0.y + height - 2; let mut total_height = random.next_inbetween_i32(4, 13);
for dy in 0..=3 { if random.next_bounded_i32(12) == 0 {
let radius = if dy == 0 || dy == 3 { 1 } else { 2 }; total_height *= 2;
for dx in -radius..=radius { }
for dz in -radius..=radius {
let cap_pos = BlockPos::new(pos.0.x + dx, cap_y + dy, pos.0.z + dz); if !self.planted && pos.0.y + total_height + 1 >= chunk.top_y() as i32 {
let block_state = if (dx == 0 || dz == 0) && dy == 1 && random.next_f32() < 0.3 return false;
{ }
Block::SHROOMLIGHT.default_state
} else { let is_huge = !self.planted && random.next_f32() < 0.06;
wart_state chunk.set_block_state(&pos.0, Block::AIR.default_state);
}; self.place_stem(chunk, block_registry, random, pos, total_height, is_huge);
chunk.set_block_state(&cap_pos.0, block_state); self.place_hat(chunk, block_registry, random, pos, total_height, is_huge);
true
}
fn is_replaceable<T: GenerationCache>(
&self,
chunk: &T,
block_registry: &dyn WorldPortalExt,
pos: BlockPos,
check_non_replaceable_plants: bool,
) -> bool {
if chunk.is_air(&pos.0)
|| GenerationCache::get_block_state(chunk, &pos.0)
.to_state()
.is_air()
{
true
} else if check_non_replaceable_plants {
self.replaceable_blocks.test(block_registry, chunk, &pos)
} else {
false
}
}
fn place_stem<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
total_height: i32,
is_huge: bool,
) {
let stem_radius = i32::from(is_huge);
for dx in -stem_radius..=stem_radius {
for dz in -stem_radius..=stem_radius {
let corner_of_huge_stem =
is_huge && dx.abs() == stem_radius && dz.abs() == stem_radius;
for dy in 0..total_height {
let block_pos = pos.add(dx, dy, dz);
if self.is_replaceable(chunk, block_registry, block_pos, true) {
if self.planted {
chunk.set_block_state(&block_pos.0, self.stem_state);
} else if corner_of_huge_stem {
if random.next_f32() < 0.1 {
chunk.set_block_state(&block_pos.0, self.stem_state);
}
} else {
chunk.set_block_state(&block_pos.0, self.stem_state);
}
}
} }
} }
} }
true }
fn place_hat<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
total_height: i32,
is_huge: bool,
) {
let place_vines = Block::from_state_id(self.hat_state.id).id == Block::NETHER_WART_BLOCK.id;
let hat_height = (random.next_bounded_i32(1 + total_height / 3) + 5).min(total_height);
let hat_start_y = total_height - hat_height;
for dy in hat_start_y..=total_height {
let mut radius = if hat_height > 8 && dy < hat_start_y + 4 {
3
} else if dy < total_height - random.next_bounded_i32(3) {
2
} else {
1
};
if is_huge {
radius += 1;
}
for dx in -radius..=radius {
for dz in -radius..=radius {
let is_edge_x = dx == -radius || dx == radius;
let is_edge_z = dz == -radius || dz == radius;
let inside = !is_edge_x && !is_edge_z && dy != total_height;
let corner = is_edge_x && is_edge_z;
let is_hat_bottom = dy < hat_start_y + 3;
let block_pos = pos.add(dx, dy, dz);
if self.is_replaceable(chunk, block_registry, block_pos, false) {
if is_hat_bottom {
if !inside {
self.place_hat_drop_block(chunk, random, block_pos, place_vines);
}
} else if inside {
self.place_hat_block(
chunk,
random,
block_pos,
0.1,
0.2,
if place_vines { 0.1 } else { 0.0 },
);
} else if corner {
self.place_hat_block(
chunk,
random,
block_pos,
0.01,
0.7,
if place_vines { 0.083 } else { 0.0 },
);
} else {
self.place_hat_block(
chunk,
random,
block_pos,
5.0e-4,
0.98,
if place_vines { 0.07 } else { 0.0 },
);
}
}
}
}
}
}
fn place_hat_block<T: GenerationCache>(
&self,
chunk: &mut T,
random: &mut RandomGenerator,
block_pos: BlockPos,
decor_prob: f32,
hat_prob: f32,
vines_prob: f32,
) {
if random.next_f32() < decor_prob {
chunk.set_block_state(&block_pos.0, self.decor_state);
} else if random.next_f32() < hat_prob {
chunk.set_block_state(&block_pos.0, self.hat_state);
if random.next_f32() < vines_prob {
Self::try_place_weeping_vines(chunk, random, block_pos);
}
}
}
fn place_hat_drop_block<T: GenerationCache>(
&self,
chunk: &mut T,
random: &mut RandomGenerator,
block_pos: BlockPos,
place_vines: bool,
) {
let below = block_pos.down();
let below_id = GenerationCache::get_block_state(chunk, &below.0).to_block_id();
let hat_id = Block::from_state_id(self.hat_state.id).id;
if below_id == hat_id {
chunk.set_block_state(&block_pos.0, self.hat_state);
} else if random.next_f32() < 0.15 {
chunk.set_block_state(&block_pos.0, self.hat_state);
if place_vines && random.next_bounded_i32(11) == 0 {
Self::try_place_weeping_vines(chunk, random, block_pos);
}
}
}
fn try_place_weeping_vines<T: GenerationCache>(
chunk: &mut T,
random: &mut RandomGenerator,
hat_block_pos: BlockPos,
) {
let place_pos = hat_block_pos.down();
if chunk.is_air(&place_pos.0) {
let mut goal_vine_height = random.next_inbetween_i32(1, 5);
if random.next_bounded_i32(7) == 0 {
goal_vine_height *= 2;
}
WeepingVinesFeature::place_weeping_vines_column(
chunk,
random,
place_pos,
goal_vine_height,
23,
25,
);
}
} }
} }

View File

@ -8,8 +8,6 @@ use pumpkin_util::{
use crate::{block::BlockStateCodec, generation::proto_chunk::GenerationCache}; use crate::{block::BlockStateCodec, generation::proto_chunk::GenerationCache};
const SEA_LEVEL: i32 = 63; // TODO: use getSeaLevel() instead of hardcoding
pub struct IcebergFeature { pub struct IcebergFeature {
pub main_block: BlockStateCodec, pub main_block: BlockStateCodec,
} }
@ -25,7 +23,7 @@ impl IcebergFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let origin = Vector3::new(pos.0.x, SEA_LEVEL, pos.0.z); let origin = Vector3::new(pos.0.x, chunk.get_sea_level(), pos.0.z);
let main_block = self.main_block.get_state(); let main_block = self.main_block.get_state();
let snow_on_top = random.next_f64() > 0.7; let snow_on_top = random.next_f64() > 0.7;

View File

@ -1,10 +1,11 @@
use crate::generation::proto_chunk::GenerationCache; use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::KelpLikeProperties, tag};
use pumpkin_data::{Block, BlockState};
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
}; };
use crate::generation::proto_chunk::GenerationCache;
pub struct KelpFeature; pub struct KelpFeature;
impl KelpFeature { impl KelpFeature {
@ -19,67 +20,66 @@ impl KelpFeature {
) -> bool { ) -> bool {
let mut placed = 0; let mut placed = 0;
// Start on the ocean floor
let y = chunk.ocean_floor_height_exclusive(pos.0.x, pos.0.z); let y = chunk.ocean_floor_height_exclusive(pos.0.x, pos.0.z);
let mut kelp_pos = BlockPos::new(pos.0.x, y, pos.0.z); let mut kelp_pos = BlockPos::new(pos.0.x, y, pos.0.z);
// Must be in water
if GenerationCache::get_block_state(chunk, &kelp_pos.0).to_block_id() == Block::WATER { if GenerationCache::get_block_state(chunk, &kelp_pos.0).to_block_id() == Block::WATER {
let height_rand = 1 + random.next_bounded_i32(10); let height = 1 + random.next_bounded_i32(10);
for h in 0..=height {
let above = kelp_pos.up();
let can_survive = Self::can_survive(chunk, kelp_pos);
// Iterate from base up to height_rand
for h in 0..=height_rand {
// Check there is water at this position and one above
if GenerationCache::get_block_state(chunk, &kelp_pos.0).to_block_id() if GenerationCache::get_block_state(chunk, &kelp_pos.0).to_block_id()
== Block::WATER == Block::WATER
&& GenerationCache::get_block_state( && GenerationCache::get_block_state(chunk, &above.0).to_block_id()
chunk,
&BlockPos::new(kelp_pos.0.x, kelp_pos.0.y + 1, kelp_pos.0.z).0,
)
.to_block_id()
== Block::WATER == Block::WATER
&& can_survive
{ {
// If this is the last iteration place the kelp head with age if h == height {
if h == height_rand { let age = (random.next_bounded_i32(4) + 20) as u8;
let age = random.next_bounded_i32(4) + 20; let mut props = KelpLikeProperties::default(&Block::KELP);
// Clamp in case it goes past available states props.age = age;
let age = age.min((Block::KELP.states.len() - 1) as i32) as usize; let state_id = props.to_state_id(&Block::KELP);
let state_id = Block::KELP.states[age].id; chunk.set_block_state(&kelp_pos.0, BlockState::from_id(state_id));
let state = BlockState::from_id(state_id);
chunk.set_block_state(&kelp_pos.0, state);
placed += 1; placed += 1;
} else { } else {
// Place kelp plant body chunk.set_block_state(&kelp_pos.0, Block::KELP_PLANT.default_state);
let state_id = Block::KELP_PLANT.default_state.id;
let state = BlockState::from_id(state_id);
chunk.set_block_state(&kelp_pos.0, state);
} }
} else if h > 0 { } else if h > 0 {
// Can't place further but we have already placed at least one segment, try to put head below let below = kelp_pos.down();
let below = BlockPos::new(kelp_pos.0.x, kelp_pos.0.y - 1, kelp_pos.0.z); let below_below = below.down();
// Check head block can survive and that below-below is not a kelp head let below_below_id =
if GenerationCache::get_block_state(chunk, &below.0).to_block_id() GenerationCache::get_block_state(chunk, &below_below.0).to_block_id();
== Block::WATER if Self::can_survive(chunk, below) && below_below_id != Block::KELP.id {
&& GenerationCache::get_block_state( let age = (random.next_bounded_i32(4) + 20) as u8;
chunk, let mut props = KelpLikeProperties::default(&Block::KELP);
&BlockPos::new(below.0.x, below.0.y - 1, below.0.z).0, props.age = age;
) let state_id = props.to_state_id(&Block::KELP);
.to_block_id() chunk.set_block_state(&below.0, BlockState::from_id(state_id));
!= Block::KELP
{
let age = random.next_bounded_i32(4) + 20;
let age = age.min((Block::KELP.states.len() - 1) as i32) as usize;
let state_id = Block::KELP.states[age].id;
let state = BlockState::from_id(state_id);
chunk.set_block_state(&below.0, state);
placed += 1; placed += 1;
} }
break; break;
} }
kelp_pos = BlockPos::new(kelp_pos.0.x, kelp_pos.0.y + 1, kelp_pos.0.z);
kelp_pos = kelp_pos.up();
} }
} }
placed > 0 placed > 0
} }
fn can_survive<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
let below = pos.down();
let below_state = GenerationCache::get_block_state(chunk, &below.0);
let below_id = below_state.to_block_id();
if below_id.has_tag(tag::Block::MINECRAFT_CANNOT_SUPPORT_KELP) {
return false;
}
below_id == Block::KELP.id
|| below_id == Block::KELP_PLANT.id
|| below_state.to_state().is_side_solid(BlockDirection::Up)
}
} }

View File

@ -7,9 +7,6 @@ use pumpkin_util::{
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
/// The three mob types that can appear in a dungeon spawner.
///
/// Skeleton 25%, Zombie 50%, Spider 25%.
const DUNGEON_MOBS: [&str; 4] = [ const DUNGEON_MOBS: [&str; 4] = [
"minecraft:skeleton", "minecraft:skeleton",
"minecraft:zombie", "minecraft:zombie",
@ -20,7 +17,6 @@ const DUNGEON_MOBS: [&str; 4] = [
pub struct DungeonFeature; pub struct DungeonFeature;
impl DungeonFeature { impl DungeonFeature {
/// Generate a monster room (dungeon) at `pos`.
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
chunk: &mut T, chunk: &mut T,
@ -46,13 +42,12 @@ impl DungeonFeature {
let solid = !chunk.is_air(&check_pos); let solid = !chunk.is_air(&check_pos);
if dy == -1 && !solid { if dy == -1 && !solid {
return false; // Floor has a gap return false;
} }
if dy == 4 && !solid { if dy == 4 && !solid {
return false; // Ceiling has a gap return false;
} }
// Wall openings
let on_wall = dx == min_x || dx == max_x || dz == min_z || dz == max_z; let on_wall = dx == min_x || dx == max_x || dz == min_z || dz == max_z;
if on_wall if on_wall
&& dy == 0 && dy == 0
@ -94,18 +89,15 @@ impl DungeonFeature {
chunk.set_block_state(&wall_pos, Block::CAVE_AIR.default_state); chunk.set_block_state(&wall_pos, Block::CAVE_AIR.default_state);
} else if cur_solid && !is_chest { } else if cur_solid && !is_chest {
if dy == -1 && random.next_bounded_i32(4) != 0 { if dy == -1 && random.next_bounded_i32(4) != 0 {
// 75% mossy cobblestone
chunk.set_block_state( chunk.set_block_state(
&wall_pos, &wall_pos,
Block::MOSSY_COBBLESTONE.default_state, Block::MOSSY_COBBLESTONE.default_state,
); );
} else { } else {
// 25% cobblestone (or 100% if it's not the floor)
chunk.set_block_state(&wall_pos, Block::COBBLESTONE.default_state); chunk.set_block_state(&wall_pos, Block::COBBLESTONE.default_state);
} }
} }
} else { } else {
// Clear interior, but preserve chests and spawners
let state = GenerationCache::get_block_state(chunk, &wall_pos); let state = GenerationCache::get_block_state(chunk, &wall_pos);
let is_chest = state.to_block() == &Block::CHEST; let is_chest = state.to_block() == &Block::CHEST;
let is_spawner = state.to_block() == &Block::SPAWNER; let is_spawner = state.to_block() == &Block::SPAWNER;
@ -117,7 +109,6 @@ impl DungeonFeature {
} }
} }
// Chest placement attempts (rooms have 1-2 chests)
for _ in 0..2 { for _ in 0..2 {
for _ in 0..3 { for _ in 0..3 {
let cx = pos.0.x + random.next_bounded_i32(xr * 2 + 1) - xr; let cx = pos.0.x + random.next_bounded_i32(xr * 2 + 1) - xr;
@ -129,20 +120,17 @@ impl DungeonFeature {
continue; continue;
} }
// Count solid horizontal neighbours
let wall_count = BlockDirection::horizontal() let wall_count = BlockDirection::horizontal()
.iter() .iter()
.filter(|d| !chunk.is_air(&chest_pos.add(&d.to_offset()))) .filter(|d| !chunk.is_air(&chest_pos.add(&d.to_offset())))
.count(); .count();
if wall_count == 1 { if wall_count == 1 {
// Orient the chest toward its single solid neighbor.
let facing_dir = BlockDirection::horizontal() let facing_dir = BlockDirection::horizontal()
.iter() .iter()
.find(|d| !chunk.is_air(&chest_pos.add(&d.to_offset()))) .find(|d| !chunk.is_air(&chest_pos.add(&d.to_offset())))
.copied(); .copied();
// Build the chest state: face away from the wall.
let chest_state = facing_dir.map_or(Block::CHEST.default_state, |dir| { let chest_state = facing_dir.map_or(Block::CHEST.default_state, |dir| {
use pumpkin_data::block_properties::ChestLikeProperties; use pumpkin_data::block_properties::ChestLikeProperties;
let mut props = ChestLikeProperties::default(&Block::CHEST); let mut props = ChestLikeProperties::default(&Block::CHEST);
@ -153,7 +141,6 @@ impl DungeonFeature {
chunk.set_block_state(&chest_pos, chest_state); chunk.set_block_state(&chest_pos, chest_state);
// Write the block-entity NBT with a deferred loot table.
let loot_seed = random.next_i64(); let loot_seed = random.next_i64();
let mut chest_nbt = NbtCompound::new(); let mut chest_nbt = NbtCompound::new();
chest_nbt.put_string("id", "minecraft:chest".to_string()); chest_nbt.put_string("id", "minecraft:chest".to_string());
@ -170,7 +157,6 @@ impl DungeonFeature {
} }
} }
// TODO: set spawner entity type
let mob = DUNGEON_MOBS[random.next_bounded_i32(DUNGEON_MOBS.len() as i32) as usize]; let mob = DUNGEON_MOBS[random.next_bounded_i32(DUNGEON_MOBS.len() as i32) as usize];
chunk.set_block_state(&pos.0, Block::SPAWNER.default_state); chunk.set_block_state(&pos.0, Block::SPAWNER.default_state);

View File

@ -1,9 +1,20 @@
use pumpkin_data::Block; use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::GlowLichenLikeProperties};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
pub struct MultifaceGrowthFeature; pub struct MultifaceGrowthFeature {
pub place_block: pumpkin_data::BlockId,
pub search_range: i32,
pub can_place_on_floor: bool,
pub can_place_on_ceiling: bool,
pub can_place_on_wall: bool,
pub chance_of_spreading: f32,
pub can_be_placed_on: Vec<pumpkin_data::BlockId>,
}
impl MultifaceGrowthFeature { impl MultifaceGrowthFeature {
#[allow(clippy::unused_self)] #[allow(clippy::unused_self)]
@ -13,27 +24,133 @@ impl MultifaceGrowthFeature {
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, _feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let mut generated = false; if !Self::is_air_or_water(chunk, pos) {
let directions = [ return false;
(1, 0, 0), }
(-1, 0, 0),
(0, 1, 0), let shuffled_dirs = self.get_shuffled_directions(random);
(0, -1, 0), if self.place_growth_if_possible(chunk, pos, random, &shuffled_dirs) {
(0, 0, 1), return true;
(0, 0, -1), }
];
for &(dx, dy, dz) in &directions { for search_direction in shuffled_dirs {
let neighbor = BlockPos::new(pos.0.x + dx, pos.0.y + dy, pos.0.z + dz); let placement_directions =
let state = GenerationCache::get_block_state(chunk, &neighbor.0); self.get_shuffled_directions_except(random, search_direction.opposite());
if state.to_block_id() != Block::AIR.id && chunk.is_air(&pos.0) { let mut cur_pos = pos;
chunk.set_block_state(&pos.0, Block::GLOW_LICHEN.default_state);
generated = true; for _ in 0..self.search_range {
break; cur_pos = cur_pos.offset(search_direction.to_offset());
let block_id = GenerationCache::get_block_state(chunk, &cur_pos.0).to_block_id();
if !Self::is_air_or_water(chunk, cur_pos) && block_id != self.place_block {
break;
}
if self.place_growth_if_possible(chunk, cur_pos, random, &placement_directions) {
return true;
}
} }
} }
generated
false
}
fn place_growth_if_possible<T: GenerationCache>(
&self,
chunk: &mut T,
pos: BlockPos,
_random: &mut RandomGenerator,
placement_directions: &[BlockDirection],
) -> bool {
for &direction in placement_directions {
let neighbor_pos = pos.offset(direction.to_offset());
let neighbor_id =
GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_block_id();
if self.can_be_placed_on.contains(&neighbor_id) {
let is_water = GenerationCache::get_block_state(chunk, &pos.0).to_block_id()
== Block::WATER.id;
let block = Block::from_id(self.place_block);
let mut props = GlowLichenLikeProperties {
down: direction == BlockDirection::Down,
up: direction == BlockDirection::Up,
north: direction == BlockDirection::North,
south: direction == BlockDirection::South,
west: direction == BlockDirection::West,
east: direction == BlockDirection::East,
waterlogged: is_water,
};
let cur_state = GenerationCache::get_block_state(chunk, &pos.0);
if cur_state.to_block_id() == self.place_block
&& (self.place_block == Block::GLOW_LICHEN.id
|| self.place_block == Block::SCULK_VEIN.id)
{
let cur_props = GlowLichenLikeProperties::from_state_id(cur_state);
props.down |= cur_props.down;
props.up |= cur_props.up;
props.north |= cur_props.north;
props.south |= cur_props.south;
props.west |= cur_props.west;
props.east |= cur_props.east;
}
let state_id = props.to_state_id(block);
chunk.set_block_state(&pos.0, BlockState::from_id(state_id));
return true;
}
}
false
}
fn get_valid_directions(&self) -> Vec<BlockDirection> {
let mut dirs = Vec::with_capacity(6);
if self.can_place_on_ceiling {
dirs.push(BlockDirection::Up);
}
if self.can_place_on_floor {
dirs.push(BlockDirection::Down);
}
if self.can_place_on_wall {
dirs.extend(
BlockDirection::horizontal_worldgen().map(BlockDirection::from_cardinal_direction),
);
}
dirs
}
fn get_shuffled_directions(&self, random: &mut RandomGenerator) -> Vec<BlockDirection> {
let mut dirs = self.get_valid_directions();
for i in (1..dirs.len()).rev() {
let j = random.next_bounded_i32((i + 1) as i32) as usize;
dirs.swap(i, j);
}
dirs
}
fn get_shuffled_directions_except(
&self,
random: &mut RandomGenerator,
exclude: BlockDirection,
) -> Vec<BlockDirection> {
let mut dirs: Vec<BlockDirection> = self
.get_valid_directions()
.into_iter()
.filter(|&d| d != exclude)
.collect();
for i in (1..dirs.len()).rev() {
let j = random.next_bounded_i32((i + 1) as i32) as usize;
dirs.swap(i, j);
}
dirs
}
fn is_air_or_water<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
let id = GenerationCache::get_block_state(chunk, &pos.0).to_block_id();
id == Block::AIR.id || id == Block::WATER.id
} }
} }

View File

@ -50,12 +50,11 @@ impl OreFeature {
let q = 2 * (g.ceil() as i32 + i); let q = 2 * (g.ceil() as i32 + i);
let r = 2 * (2 + i); let r = 2 * (2 + i);
for _ in n..=(n + q) { for xprobe in n..=(n + q) {
for _ in p..=(p + q) { for zprobe in p..=(p + q) {
if o > chunk.ocean_floor_height_exclusive(pos.0.x, pos.0.z) { if o <= chunk.ocean_floor_height_exclusive(xprobe, zprobe) {
continue; return self.generate_vein_part(chunk, random, d, e, h, j, l, m, n, o, p, q, r);
} }
return self.generate_vein_part(chunk, random, d, e, h, j, l, m, n, o, p, q, r);
} }
} }
false false

View File

@ -1,22 +1,29 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use crate::generation::feature::features::ore::OreTarget;
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
pub struct ReplaceSingleBlockFeature; pub struct ReplaceSingleBlockFeature {
pub targets: Vec<OreTarget>,
}
impl ReplaceSingleBlockFeature { impl ReplaceSingleBlockFeature {
#[allow(clippy::unused_self)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
chunk: &mut T, chunk: &mut T,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, _feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
chunk.set_block_state(&pos.0, Block::SAND.default_state); let block_state = GenerationCache::get_block_state(chunk, &pos.0);
for target in &self.targets {
if target.target.test(block_state, random) {
chunk.set_block_state(&pos.0, target.state);
break;
}
}
true true
} }
} }

View File

@ -1,3 +1,4 @@
use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
@ -36,52 +37,156 @@ impl RootSystemFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
if !self.allowed_tree_position.test(block_registry, chunk, &pos) { if !chunk.is_air(&pos.0) {
return false; return false;
} }
if !self.can_place_tree(chunk, pos) { let mut working_pos = pos;
return false; if self.place_dirt_and_tree(
}
if !self.feature.generate(
chunk, chunk,
block_registry, block_registry,
min_y, min_y,
height, height,
feature_name, feature_name,
random, random,
&mut working_pos,
pos, pos,
) { ) {
return false; self.place_roots(chunk, block_registry, random, pos);
} }
self.place_roots(chunk, block_registry, random, pos);
true true
} }
fn can_place_tree<T: GenerationCache>(&self, chunk: &T, pos: BlockPos) -> bool { fn space_for_tree<T: GenerationCache>(&self, chunk: &T, pos: BlockPos) -> bool {
let mut mutable_pos = pos; let mut column_up = pos;
for i in 1..=self.required_vertical_space_for_tree { for i in 1..=self.required_vertical_space_for_tree {
mutable_pos = mutable_pos.add(0, 1, 0); column_up = column_up.up();
if !self.is_allowed_tree_space(chunk, mutable_pos, i) { let state = GenerationCache::get_block_state(chunk, &column_up.0);
if !self.is_allowed_tree_space(state.to_block_id(), i) {
return false; return false;
} }
} }
true true
} }
fn is_allowed_tree_space<T: GenerationCache>( fn is_allowed_tree_space(
&self, &self,
chunk: &T, block_id: pumpkin_data::BlockId,
pos: BlockPos, blocks_above_origin: i32,
vertical_space: i32,
) -> bool { ) -> bool {
if chunk.is_air(&pos.0) { if block_id == Block::AIR.id {
return true; return true;
} }
// TODO: check for water. let blocks_above_ground = blocks_above_origin + 1;
vertical_space <= self.allowed_vertical_water_for_tree blocks_above_ground <= self.allowed_vertical_water_for_tree && block_id == Block::WATER.id
}
#[allow(clippy::too_many_arguments)]
fn place_dirt_and_tree<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
min_y: i8,
height: u16,
feature_name: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
working_pos: &mut BlockPos,
pos: BlockPos,
) -> bool {
for y in 0..self.root_column_max_height {
*working_pos = working_pos.up();
if chunk.top_block_height_exclusive(working_pos.0.x, working_pos.0.z) < working_pos.0.y
{
return false;
}
if self
.allowed_tree_position
.test(block_registry, chunk, working_pos)
&& self.space_for_tree(chunk, *working_pos)
{
let below_pos = working_pos.down();
let below_state = GenerationCache::get_block_state(chunk, &below_pos.0);
if below_state.to_block_id() == Block::LAVA.id
|| !below_state.to_state().is_side_solid(BlockDirection::Up)
{
return false;
}
if self.feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
*working_pos,
) {
self.place_dirt(chunk, block_registry, random, pos, pos.0.y + y);
return true;
}
}
}
false
}
fn place_dirt<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
origin: BlockPos,
target_height: i32,
) {
let origin_x = origin.0.x;
let origin_z = origin.0.z;
for y in origin.0.y..target_height {
self.place_rooted_dirt(
chunk,
block_registry,
random,
origin_x,
origin_z,
BlockPos::new(origin_x, y, origin_z),
);
}
}
fn place_rooted_dirt<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
origin_x: i32,
origin_z: i32,
mut working_pos: BlockPos,
) {
let root_radius = self.root_radius.max(1);
for _ in 0..self.root_placement_attempts {
working_pos = working_pos.add(
random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius),
0,
random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius),
);
if self
.root_replaceable
.test(block_registry, chunk, &working_pos)
{
chunk.set_block_state(
&working_pos.0,
self.root_state_provider
.get(random, working_pos, chunk, block_registry),
);
}
working_pos.0.x = origin_x;
working_pos.0.z = origin_z;
}
} }
fn place_roots<T: GenerationCache>( fn place_roots<T: GenerationCache>(
@ -91,92 +196,29 @@ impl RootSystemFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) { ) {
self.place_rooted_dirt(chunk, block_registry, random, pos); let root_radius = self.hanging_root_radius.max(1);
self.place_hanging_roots(chunk, block_registry, random, pos); let vertical_span = self.hanging_roots_vertical_span.max(1);
}
fn place_rooted_dirt<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
) {
for _ in 0..self.root_placement_attempts {
let mut mutable_pos = pos.add(
random.next_bounded_i32(self.root_radius.max(1))
- random.next_bounded_i32(self.root_radius.max(1)),
0,
random.next_bounded_i32(self.root_radius.max(1))
- random.next_bounded_i32(self.root_radius.max(1)),
);
if self
.root_replaceable
.test(block_registry, chunk, &mutable_pos)
{
chunk.set_block_state(
&mutable_pos.0,
self.root_state_provider.get_with_context(
block_registry,
chunk,
random,
mutable_pos,
),
);
}
for _ in 0..self.root_column_max_height {
mutable_pos = mutable_pos.add(0, -1, 0);
if chunk.out_of_height(mutable_pos.0.y as i16)
|| !self
.root_replaceable
.test(block_registry, chunk, &mutable_pos)
{
break;
}
chunk.set_block_state(
&mutable_pos.0,
self.root_state_provider.get_with_context(
block_registry,
chunk,
random,
mutable_pos,
),
);
}
}
}
fn place_hanging_roots<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
) {
for _ in 0..self.hanging_root_placement_attempts { for _ in 0..self.hanging_root_placement_attempts {
let mutable_pos = pos.add( let working_pos = pos.add(
random.next_bounded_i32(self.hanging_root_radius.max(1)) random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius),
- random.next_bounded_i32(self.hanging_root_radius.max(1)), random.next_bounded_i32(vertical_span) - random.next_bounded_i32(vertical_span),
random.next_bounded_i32(self.hanging_roots_vertical_span.max(1)) random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius),
- random.next_bounded_i32(self.hanging_roots_vertical_span.max(1)),
random.next_bounded_i32(self.hanging_root_radius.max(1))
- random.next_bounded_i32(self.hanging_root_radius.max(1)),
); );
if chunk.is_air(&mutable_pos.0) { if chunk.is_air(&working_pos.0) {
let state = self.hanging_root_state_provider.get_with_context( let above = working_pos.up();
block_registry, let above_state = GenerationCache::get_block_state(chunk, &above.0);
chunk, if above_state.to_state().is_side_solid(BlockDirection::Down) {
random, chunk.set_block_state(
mutable_pos, &working_pos.0,
); self.hanging_root_state_provider.get(
// In vanilla, it checks if it can survive and if the block above is sturdy. random,
// For now, let's just check if the block above is NOT air. working_pos,
let above = mutable_pos.add(0, 1, 0); chunk,
if !chunk.is_air(&above.0) { block_registry,
chunk.set_block_state(&mutable_pos.0, state); ),
);
} }
} }
} }

View File

@ -27,12 +27,11 @@ impl ScatteredOreFeature {
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let count = random.next_bounded_i32(self.size + 1); let count = random.next_bounded_i32(self.size + 1);
let mut placed = false; for i in 0..count {
let max_dist = i.min(7);
for _ in 0..count { let offset_x = Self::get_random_offset(random, max_dist);
let offset_x = self.get_random_offset(random); let offset_y = Self::get_random_offset(random, max_dist);
let offset_y = self.get_random_offset(random); let offset_z = Self::get_random_offset(random, max_dist);
let offset_z = self.get_random_offset(random);
let target_pos = pos.add(offset_x, offset_y, offset_z); let target_pos = pos.add(offset_x, offset_y, offset_z);
@ -52,18 +51,17 @@ impl ScatteredOreFeature {
&target_pos, &target_pos,
) { ) {
chunk.set_block_state(&target_pos.0, target.state); chunk.set_block_state(&target_pos.0, target.state);
placed = true;
break; break;
} }
} }
} }
placed true
} }
fn get_random_offset(&self, random: &mut RandomGenerator) -> i32 { fn get_random_offset(random: &mut RandomGenerator, max_dist: i32) -> i32 {
let f1 = random.next_f32(); let f1 = random.next_f32();
let f2 = random.next_f32(); let f2 = random.next_f32();
((f1 - f2) * self.size as f32).round() as i32 ((f1 - f2) * max_dist as f32).round() as i32
} }
} }

View File

@ -1,10 +1,11 @@
use crate::generation::proto_chunk::GenerationCache; use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::SeaPickleLikeProperties};
use pumpkin_data::{Block, BlockState, block_properties::SeaPickleLikeProperties};
use pumpkin_util::{ use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos}, math::{int_provider::IntProvider, position::BlockPos},
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
}; };
use crate::generation::proto_chunk::GenerationCache;
pub struct SeaPickleFeature { pub struct SeaPickleFeature {
pub count: IntProvider, pub count: IntProvider,
} }
@ -15,28 +16,37 @@ impl SeaPickleFeature {
chunk: &mut T, chunk: &mut T,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let mut times = 0; let mut placed = 0;
let count = self.count.get(random); let count = self.count.get(random);
for _ in 0..count { for _ in 0..count {
let x = random.next_bounded_i32(8) - random.next_bounded_i32(8); 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 z = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z); let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z);
if GenerationCache::get_block_state(chunk, &pos.0).to_block_id() != Block::WATER { let pickle_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
continue;
if GenerationCache::get_block_state(chunk, &pickle_pos.0).to_block_id() == Block::WATER
&& Self::can_survive(chunk, pickle_pos)
{
let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE);
props.pickles = (random.next_bounded_i32(4) as u8) + 1;
let state_id = props.to_state_id(&Block::SEA_PICKLE);
chunk.set_block_state(&pickle_pos.0, BlockState::from_id(state_id));
placed += 1;
} }
let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE);
props.pickles = (random.next_bounded_i32(4) as u8) + 1;
let pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
chunk.set_block_state(
&pos.0,
BlockState::from_id(props.to_state_id(&Block::SEA_PICKLE)),
);
times += 1;
} }
times > 0
placed > 0
}
fn can_survive<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
let below = pos.down();
GenerationCache::get_block_state(chunk, &below.0)
.to_state()
.is_side_solid(BlockDirection::Up)
} }
} }

View File

@ -1,13 +1,15 @@
use crate::generation::proto_chunk::GenerationCache;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockState, Block, BlockDirection, BlockState,
block_properties::{DoubleBlockHalf, TallSeagrassLikeProperties}, block_properties::{DoubleBlockHalf, TallSeagrassLikeProperties},
tag,
}; };
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
}; };
use crate::generation::proto_chunk::GenerationCache;
pub struct SeagrassFeature { pub struct SeagrassFeature {
pub probability: f32, pub probability: f32,
} }
@ -18,34 +20,51 @@ impl SeagrassFeature {
chunk: &mut T, chunk: &mut T,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature _feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let mut placed_any = false;
let x = random.next_bounded_i32(8) - random.next_bounded_i32(8); 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 z = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z); let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z);
let top_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); let grass_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
if GenerationCache::get_block_state(chunk, &top_pos.0).to_block_id() == Block::WATER {
let tall = random.next_f64() < self.probability as f64; if GenerationCache::get_block_state(chunk, &grass_pos.0).to_block_id() == Block::WATER
if tall { && Self::can_survive(chunk, grass_pos)
let tall_pos = top_pos.up(); {
if GenerationCache::get_block_state(chunk, &tall_pos.0).to_block_id() let is_tall = random.next_f64() < self.probability as f64;
== Block::WATER if is_tall {
{ let above = grass_pos.up();
let mut props = TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS); if GenerationCache::get_block_state(chunk, &above.0).to_block_id() == Block::WATER {
props.half = DoubleBlockHalf::Upper; let mut upper_props =
chunk.set_block_state(&top_pos.0, Block::TALL_SEAGRASS.default_state); TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS);
chunk.set_block_state( upper_props.half = DoubleBlockHalf::Upper;
&tall_pos.0, let upper_state =
BlockState::from_id(props.to_state_id(&Block::TALL_SEAGRASS)), BlockState::from_id(upper_props.to_state_id(&Block::TALL_SEAGRASS));
);
chunk.set_block_state(&grass_pos.0, Block::TALL_SEAGRASS.default_state);
chunk.set_block_state(&above.0, upper_state);
placed_any = true;
} }
} else { } else {
chunk.set_block_state(&top_pos.0, Block::SEAGRASS.default_state); chunk.set_block_state(&grass_pos.0, Block::SEAGRASS.default_state);
placed_any = true;
} }
return true;
} }
false
placed_any
}
fn can_survive<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
let below = pos.down();
let below_state = GenerationCache::get_block_state(chunk, &below.0);
let below_id = below_state.to_block_id();
if below_id.has_tag(tag::Block::MINECRAFT_CANNOT_SUPPORT_SEAGRASS) {
return false;
}
below_state.to_state().is_side_solid(BlockDirection::Up)
} }
} }

View File

@ -27,9 +27,7 @@ impl SimpleBlockFeature {
return false; return false;
} }
// TODO: check things..
chunk.set_block_state(&pos.0, state); chunk.set_block_state(&pos.0, state);
// TODO: schedule tick when needed
true true
} }
} }

View File

@ -1 +1,92 @@
pub struct AlterGroundTreeDecorator; use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use super::TreeDecorator;
use crate::{
generation::{block_state_provider::BlockStateProvider, proto_chunk::GenerationCache},
world::WorldPortalExt,
};
pub struct AlterGroundTreeDecorator {
pub provider: BlockStateProvider,
}
impl AlterGroundTreeDecorator {
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
root_positions: &[BlockPos],
log_positions: &[BlockPos],
) {
let list = TreeDecorator::get_leaf_litter_positions(root_positions, log_positions);
let Some(first) = list.first() else {
return;
};
let min_y = list.iter().map(|p| p.0.y).min().unwrap_or(first.0.y);
for pos in list.iter().filter(|p| p.0.y == min_y) {
self.place_circle(chunk, block_registry, random, pos.west().north());
self.place_circle(chunk, block_registry, random, pos.east().east().north());
self.place_circle(chunk, block_registry, random, pos.west().south().south());
self.place_circle(
chunk,
block_registry,
random,
pos.east().east().south().south(),
);
for _ in 0..5 {
let placement = random.next_bounded_i32(64);
let xx = placement % 8;
let zz = placement / 8;
if xx == 0 || xx == 7 || zz == 0 || zz == 7 {
self.place_circle(chunk, block_registry, random, pos.add(-3 + xx, 0, -3 + zz));
}
}
}
}
fn place_circle<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
) {
for xx in -2..=2i32 {
for zz in -2..=2i32 {
if xx.abs() != 2 || zz.abs() != 2 {
self.place_block_at(chunk, block_registry, random, pos.add(xx, 0, zz));
}
}
}
}
fn place_block_at<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
) {
for dy in (-3..=2).rev() {
let cursor = pos.up_height(dy);
if let Some(replace_with) =
self.provider
.get_optional(block_registry, chunk, random, cursor)
{
chunk.set_block_state(&cursor.0, replace_with);
break;
}
if !chunk.is_air(&cursor.0) && dy < 0 {
break;
}
}
}
}

View File

@ -1 +1,84 @@
pub struct AttachedToLeavesTreeDecorator; use std::collections::HashSet;
use pumpkin_data::BlockDirection;
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::{
generation::{block_state_provider::BlockStateProvider, proto_chunk::GenerationCache},
world::WorldPortalExt,
};
pub struct AttachedToLeavesTreeDecorator {
pub probability: f32,
pub exclusion_radius_xz: i32,
pub exclusion_radius_y: i32,
pub block_provider: BlockStateProvider,
pub required_empty_blocks: i32,
pub directions: Vec<BlockDirection>,
}
impl AttachedToLeavesTreeDecorator {
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
foliage_positions: &[BlockPos],
) {
if self.directions.is_empty() || foliage_positions.is_empty() {
return;
}
let mut blacklist: HashSet<BlockPos> = HashSet::new();
let mut shuffled = foliage_positions.to_vec();
for i in (1..shuffled.len()).rev() {
let j = random.next_bounded_i32((i + 1) as i32) as usize;
shuffled.swap(i, j);
}
for leaf_pos in shuffled {
let dir_idx = random.next_bounded_i32(self.directions.len() as i32) as usize;
let direction = self.directions[dir_idx];
let placement_pos = leaf_pos.offset(direction.to_offset());
if !blacklist.contains(&placement_pos)
&& random.next_f32() < self.probability
&& self.has_required_empty_blocks(chunk, leaf_pos, direction)
{
for dx in -self.exclusion_radius_xz..=self.exclusion_radius_xz {
for dy in -self.exclusion_radius_y..=self.exclusion_radius_y {
for dz in -self.exclusion_radius_xz..=self.exclusion_radius_xz {
blacklist.insert(placement_pos.add(dx, dy, dz));
}
}
}
chunk.set_block_state(
&placement_pos.0,
self.block_provider
.get(random, placement_pos, chunk, block_registry),
);
}
}
}
fn has_required_empty_blocks<T: GenerationCache>(
&self,
chunk: &T,
leaf_pos: BlockPos,
direction: BlockDirection,
) -> bool {
let offset = direction.to_offset();
for i in 1..=self.required_empty_blocks {
let check_pos = leaf_pos.offset(offset * i);
if !chunk.is_air(&check_pos.0) {
return false;
}
}
true
}
}

View File

@ -21,21 +21,28 @@ impl AttachedToLogsTreeDecorator {
random: &mut RandomGenerator, random: &mut RandomGenerator,
log_positions: &[BlockPos], log_positions: &[BlockPos],
) { ) {
// TODO: shuffle if self.directions.is_empty() || log_positions.is_empty() {
for pos in log_positions { return;
// TODO: random }
let pos = pos.offset(self.directions[0].to_offset());
if random.next_f32() > self.probability let mut shuffled = log_positions.to_vec();
|| !GenerationCache::get_block_state(chunk, &pos.0) for i in (1..shuffled.len()).rev() {
.to_state() let j = random.next_bounded_i32((i + 1) as i32) as usize;
.is_air() shuffled.swap(i, j);
{ }
continue;
for log_pos in shuffled {
let dir_idx = random.next_bounded_i32(self.directions.len() as i32) as usize;
let direction = self.directions[dir_idx];
let placement_pos = log_pos.offset(direction.to_offset());
if random.next_f32() <= self.probability && chunk.is_air(&placement_pos.0) {
chunk.set_block_state(
&placement_pos.0,
self.block_provider
.get(random, placement_pos, chunk, block_registry),
);
} }
chunk.set_block_state(
&pos.0,
self.block_provider.get(random, pos, chunk, block_registry),
);
} }
} }
} }

View File

@ -1,3 +1,70 @@
use pumpkin_data::{
Block, BlockDirection, BlockState,
block_properties::{BeehiveProperties, HorizontalFacing},
};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
pub struct BeehiveTreeDecorator { pub struct BeehiveTreeDecorator {
pub probability: f32, pub probability: f32,
} }
impl BeehiveTreeDecorator {
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
random: &mut RandomGenerator,
log_positions: &[BlockPos],
foliage_positions: &[BlockPos],
) {
if log_positions.is_empty() || random.next_f32() >= self.probability {
return;
}
let worldgen_facing = BlockDirection::South;
let spawn_directions = [
BlockDirection::East,
BlockDirection::West,
BlockDirection::South,
];
let hive_y = foliage_positions.first().map_or_else(
|| {
let last_log_y = log_positions.last().map_or(0, |p| p.0.y);
(log_positions[0].0.y + 1 + random.next_bounded_i32(3)).min(last_log_y)
},
|first_leaf| (first_leaf.0.y - 1).max(log_positions[0].0.y + 1),
);
let mut hive_placements: Vec<BlockPos> = Vec::new();
for log_pos in log_positions.iter().filter(|p| p.0.y == hive_y) {
for dir in spawn_directions {
hive_placements.push(log_pos.offset(dir.to_offset()));
}
}
if hive_placements.is_empty() {
return;
}
for i in (1..hive_placements.len()).rev() {
let j = random.next_bounded_i32((i + 1) as i32) as usize;
hive_placements.swap(i, j);
}
for pos in hive_placements {
let in_front = pos.offset(worldgen_facing.to_offset());
if chunk.is_air(&pos.0) && chunk.is_air(&in_front.0) {
let mut props = BeehiveProperties::default(&Block::BEE_NEST);
props.facing = HorizontalFacing::South;
let state_id = props.to_state_id(&Block::BEE_NEST);
chunk.set_block_state(&pos.0, BlockState::from_id(state_id));
break;
}
}
}
}

View File

@ -1 +1,43 @@
pub struct CocoaTreeDecorator; use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::CocoaLikeProperties};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
pub struct CocoaTreeDecorator {
pub probability: f32,
}
impl CocoaTreeDecorator {
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
random: &mut RandomGenerator,
log_positions: &[BlockPos],
) {
if log_positions.is_empty() || random.next_f32() >= self.probability {
return;
}
let tree_y = log_positions[0].0.y;
for pos in log_positions.iter().filter(|p| p.0.y - tree_y <= 2) {
for facing in BlockDirection::horizontal_worldgen() {
if random.next_f32() <= 0.25 {
let dir = BlockDirection::from_cardinal_direction(facing);
let opposite = dir.opposite();
let cocoa_pos = pos.offset(opposite.to_offset());
if chunk.is_air(&cocoa_pos.0) {
let props = CocoaLikeProperties {
age: random.next_bounded_i32(3) as u8,
facing,
};
let state_id = props.to_state_id(&Block::COCOA);
chunk.set_block_state(&cocoa_pos.0, BlockState::from_id(state_id));
}
}
}
}
}
}

View File

@ -1 +1,57 @@
pub struct CreakingHeartTreeDecorator; use pumpkin_data::{
Block, BlockDirection, BlockState,
block_properties::{Axis, CreakingHeartLikeProperties, CreakingHeartState},
tag,
};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
pub struct CreakingHeartTreeDecorator {
pub probability: f32,
}
impl CreakingHeartTreeDecorator {
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
random: &mut RandomGenerator,
log_positions: &[BlockPos],
) {
if log_positions.is_empty() || random.next_f32() >= self.probability {
return;
}
let mut shuffled = log_positions.to_vec();
for i in (1..shuffled.len()).rev() {
let j = random.next_bounded_i32((i + 1) as i32) as usize;
shuffled.swap(i, j);
}
for pos in shuffled {
let mut all_logs = true;
for dir in BlockDirection::all() {
let neighbor = pos.offset(dir.to_offset());
let block_id = GenerationCache::get_block_state(chunk, &neighbor.0).to_block_id();
if !block_id.has_tag(tag::Block::MINECRAFT_LOGS) {
all_logs = false;
break;
}
}
if all_logs {
let props = CreakingHeartLikeProperties {
r#axis: Axis::Y,
r#creaking_heart_state: CreakingHeartState::Dormant,
r#natural: true,
};
let state_id = props.to_state_id(&Block::CREAKING_HEART);
chunk.set_block_state(&pos.0, BlockState::from_id(state_id));
break;
}
}
}
}

View File

@ -53,15 +53,27 @@ impl TreeDecorator {
TrunkVineTreeDecorator::generate(chunk, random, log_positions); TrunkVineTreeDecorator::generate(chunk, random, log_positions);
} }
Self::LeaveVine(decorator) => decorator.generate(chunk, random, foliage_positions), Self::LeaveVine(decorator) => decorator.generate(chunk, random, foliage_positions),
Self::PaleMoss(_decorator) => {} Self::PaleMoss(decorator) => {
Self::CreakingHeart(_decorator) => {} decorator.generate(chunk, random, log_positions, foliage_positions);
Self::Cocoa(_decorator) => {} }
Self::Beehive(_decorator) => {} Self::CreakingHeart(decorator) => {
Self::AlterGround(_decorator) => {} decorator.generate(chunk, random, log_positions);
}
Self::Cocoa(decorator) => {
decorator.generate(chunk, random, log_positions);
}
Self::Beehive(decorator) => {
decorator.generate(chunk, random, log_positions, foliage_positions);
}
Self::AlterGround(decorator) => {
decorator.generate(chunk, block_registry, random, root_positions, log_positions);
}
Self::PlaceOnGround(decorator) => { Self::PlaceOnGround(decorator) => {
decorator.generate(chunk, block_registry, random, root_positions, log_positions); decorator.generate(chunk, block_registry, random, root_positions, log_positions);
} }
Self::AttachedToLeaves(_decorator) => {} Self::AttachedToLeaves(decorator) => {
decorator.generate(chunk, block_registry, random, foliage_positions);
}
Self::AttachedToLogs(decorator) => { Self::AttachedToLogs(decorator) => {
decorator.generate(chunk, block_registry, random, log_positions); decorator.generate(chunk, block_registry, random, log_positions);
} }

View File

@ -1 +1,62 @@
pub struct PaleMossTreeDecorator; use pumpkin_data::{Block, BlockState, block_properties::PaleHangingMossLikeProperties};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
pub struct PaleMossTreeDecorator {
pub leaves_probability: f32,
pub trunk_probability: f32,
pub ground_probability: f32,
}
impl PaleMossTreeDecorator {
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
random: &mut RandomGenerator,
log_positions: &[BlockPos],
foliage_positions: &[BlockPos],
) {
if log_positions.is_empty() {
return;
}
for &pos in log_positions {
if random.next_f32() < self.trunk_probability {
let down = pos.down();
if chunk.is_air(&down.0) {
Self::add_moss_hanger(chunk, random, down);
}
}
}
for &pos in foliage_positions {
if random.next_f32() < self.leaves_probability {
let down = pos.down();
if chunk.is_air(&down.0) {
Self::add_moss_hanger(chunk, random, down);
}
}
}
}
fn add_moss_hanger<T: GenerationCache>(
chunk: &mut T,
random: &mut RandomGenerator,
mut pos: BlockPos,
) {
while chunk.is_air(&pos.down().0) && random.next_f32() >= 0.5 {
let props = PaleHangingMossLikeProperties { tip: false };
let state_id = props.to_state_id(&Block::PALE_HANGING_MOSS);
chunk.set_block_state(&pos.0, BlockState::from_id(state_id));
pos = pos.down();
}
let props = PaleHangingMossLikeProperties { tip: true };
let state_id = props.to_state_id(&Block::PALE_HANGING_MOSS);
chunk.set_block_state(&pos.0, BlockState::from_id(state_id));
}
}

View File

@ -66,18 +66,16 @@ impl PlaceOnGroundTreeDecorator {
random: &mut RandomGenerator, random: &mut RandomGenerator,
) { ) {
let state = GenerationCache::get_block_state(chunk, &pos.0); let state = GenerationCache::get_block_state(chunk, &pos.0);
let up_pos = pos.up(); let ground_pos = pos.down();
let up_state = GenerationCache::get_block_state(chunk, &up_pos.0); let ground_state = GenerationCache::get_block_state(chunk, &ground_pos.0);
// TODO if (state.to_state().is_air() || state.to_block_id() == Block::VINE)
if (up_state.to_state().is_air() || up_state.to_block_id() == Block::VINE) && ground_state.to_state().is_full_cube()
&& state.to_state().is_full_cube() && !ground_state.to_block_id().has_tag(MINECRAFT_LEAVES)
&& !state.to_block_id().has_tag(MINECRAFT_LEAVES)
// TODO: using heightmap seems not to work
{ {
chunk.set_block_state( chunk.set_block_state(
&up_pos.0, &pos.0,
self.block_state_provider self.block_state_provider
.get(random, up_pos, chunk, block_registry), .get(random, pos, chunk, block_registry),
); );
} }
} }

View File

@ -1,4 +1,4 @@
use pumpkin_data::Block; use pumpkin_data::{Block, BlockState, block_properties::KelpLikeProperties};
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
@ -22,34 +22,31 @@ impl TwistingVinesFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
if Self::is_invalid_location(chunk, &pos) { if Self::is_invalid_placement_location(chunk, pos) {
return false; return false;
} }
let mut placed = false; let mut placed = false;
for _ in 0..self.spread_width * self.spread_width { for _ in 0..self.spread_width * self.spread_width {
let offset_x = random.next_bounded_i32(self.spread_width) let offset_x = random.next_inbetween_i32(-self.spread_width, self.spread_width);
- random.next_bounded_i32(self.spread_width); let offset_y = random.next_inbetween_i32(-self.spread_height, self.spread_height);
let offset_y = random.next_bounded_i32(self.spread_height) let offset_z = random.next_inbetween_i32(-self.spread_width, self.spread_width);
- random.next_bounded_i32(self.spread_height);
let offset_z = random.next_bounded_i32(self.spread_width)
- random.next_bounded_i32(self.spread_width);
let mut mutable_pos = pos.add(offset_x, offset_y, offset_z); let mut place_pos = pos.add(offset_x, offset_y, offset_z);
if self.find_target_y(chunk, &mut mutable_pos) if Self::find_first_air_block_above_ground(chunk, &mut place_pos)
&& !Self::is_invalid_location(chunk, &mutable_pos) && !Self::is_invalid_placement_location(chunk, place_pos)
{ {
let mut height = random.next_bounded_i32(self.max_height) + 1; let mut vine_height = random.next_inbetween_i32(1, self.max_height);
if random.next_bounded_i32(6) == 0 { if random.next_bounded_i32(6) == 0 {
height *= 2; vine_height *= 2;
} }
if random.next_bounded_i32(10) == 0 { if random.next_bounded_i32(5) == 0 {
height = 1; vine_height = 1;
} }
Self::generate_column(chunk, random, &mutable_pos, height); Self::place_twisting_vines_column(chunk, random, place_pos, vine_height, 17, 25);
placed = true; placed = true;
} }
} }
@ -57,75 +54,59 @@ impl TwistingVinesFeature {
placed placed
} }
fn generate_column<T: GenerationCache>( fn find_first_air_block_above_ground<T: GenerationCache>(
chunk: &T,
place_pos: &mut BlockPos,
) -> bool {
loop {
*place_pos = place_pos.down();
if place_pos.0.y <= chunk.bottom_y() as i32 {
return false;
}
if !chunk.is_air(&place_pos.0) {
break;
}
}
*place_pos = place_pos.up();
true
}
pub fn place_twisting_vines_column<T: GenerationCache>(
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: &BlockPos, mut place_pos: BlockPos,
height: i32, total_height: i32,
min_age: i32,
max_age: i32,
) { ) {
let mut current_pos = *pos; for height in 1..=total_height {
for i in 0..height { if chunk.is_air(&place_pos.0) {
if !GenerationCache::get_block_state(chunk, &current_pos.0) if height == total_height || !chunk.is_air(&place_pos.up().0) {
.to_state() let age = random.next_inbetween_i32(min_age, max_age);
.is_air() let mut props = KelpLikeProperties::default(&Block::TWISTING_VINES);
{ props.age = age as u8;
break; let state_id = props.to_state_id(&Block::TWISTING_VINES);
chunk.set_block_state(&place_pos.0, BlockState::from_id(state_id));
break;
}
chunk.set_block_state(&place_pos.0, Block::TWISTING_VINES_PLANT.default_state);
} }
if i == height - 1 place_pos = place_pos.up();
|| !GenerationCache::get_block_state(chunk, &current_pos.up().0)
.to_state()
.is_air()
{
// Top part
let _age = 17 + random.next_bounded_i32(25 - 17 + 1);
// We should set the age property here, but Pumpkin's BlockState might not support it easily yet or we just use default
// For now, let's just use the block.
// TODO: Set age property
chunk.set_block_state(&current_pos.0, Block::TWISTING_VINES.default_state);
break;
}
chunk.set_block_state(&current_pos.0, Block::TWISTING_VINES_PLANT.default_state);
current_pos = current_pos.up();
} }
} }
fn is_invalid_location<T: GenerationCache>(chunk: &T, pos: &BlockPos) -> bool { fn is_invalid_placement_location<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
if !GenerationCache::get_block_state(chunk, &pos.0) if !chunk.is_air(&pos.0) {
.to_state()
.is_air()
{
return true; return true;
} }
let block_below = GenerationCache::get_block_state(chunk, &pos.down().0).to_state(); let below = pos.down();
block_below != Block::WARPED_NYLIUM.default_state let below_id = GenerationCache::get_block_state(chunk, &below.0).to_block_id();
&& block_below != Block::WARPED_WART_BLOCK.default_state below_id != Block::NETHERRACK.id
&& block_below != Block::TWISTING_VINES.default_state && below_id != Block::WARPED_NYLIUM.id
&& block_below != Block::TWISTING_VINES_PLANT.default_state && below_id != Block::WARPED_WART_BLOCK.id
}
fn find_target_y<T: GenerationCache>(&self, chunk: &T, pos: &mut BlockPos) -> bool {
// Try to find a valid floor by looking down
let mut current = *pos;
for _ in 0..self.spread_height {
if GenerationCache::get_block_state(chunk, &current.0)
.to_state()
.is_air()
{
let below = current.down();
let block_below = GenerationCache::get_block_state(chunk, &below.0).to_state();
if block_below == Block::WARPED_NYLIUM.default_state
|| block_below == Block::WARPED_WART_BLOCK.default_state
|| block_below == Block::TWISTING_VINES.default_state
|| block_below == Block::TWISTING_VINES_PLANT.default_state
{
*pos = current;
return true;
}
}
current = current.down();
}
false
} }
} }

View File

@ -1,4 +1,4 @@
use pumpkin_data::{Block, BlockDirection, BlockState}; use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::VineLikeProperties};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
@ -19,17 +19,19 @@ impl VinesFeature {
if !chunk.is_air(&pos.0) { if !chunk.is_air(&pos.0) {
return false; return false;
} }
for dir in BlockDirection::all() { for dir in BlockDirection::all() {
// TODO if dir == BlockDirection::Down {
if dir == BlockDirection::Down
|| !GenerationCache::get_block_state(chunk, &pos.offset(dir.to_offset()).0)
.to_state()
.is_full_cube()
{
continue; continue;
} }
let mut vine =
pumpkin_data::block_properties::VineLikeProperties::default(&Block::VINE); let neighbor = pos.offset(dir.to_offset());
let neighbor_state = GenerationCache::get_block_state(chunk, &neighbor.0);
if !neighbor_state.to_state().is_side_solid(dir.opposite()) {
continue;
}
let mut vine = VineLikeProperties::default(&Block::VINE);
vine.north = dir == BlockDirection::North; vine.north = dir == BlockDirection::North;
vine.east = dir == BlockDirection::East; vine.east = dir == BlockDirection::East;
vine.south = dir == BlockDirection::South; vine.south = dir == BlockDirection::South;
@ -38,6 +40,7 @@ impl VinesFeature {
chunk.set_block_state(&pos.0, BlockState::from_id(vine.to_state_id(&Block::VINE))); chunk.set_block_state(&pos.0, BlockState::from_id(vine.to_state_id(&Block::VINE)));
return true; return true;
} }
false false
} }
} }

View File

@ -1,12 +1,14 @@
use pumpkin_data::Block; use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
random::RandomGenerator,
};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
pub struct VoidStartPlatformFeature; pub struct VoidStartPlatformFeature;
impl VoidStartPlatformFeature { impl VoidStartPlatformFeature {
#[allow(clippy::unused_self)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
chunk: &mut T, chunk: &mut T,
@ -16,12 +18,33 @@ impl VoidStartPlatformFeature {
_random: &mut RandomGenerator, _random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
for dx in -1..=1 { let chunk_x = pos.0.x >> 4;
for dz in -1..=1 { let chunk_z = pos.0.z >> 4;
let target = BlockPos::new(pos.0.x + dx, pos.0.y, pos.0.z + dz);
chunk.set_block_state(&target.0, Block::OBSIDIAN.default_state); if chunk_x.abs().max(chunk_z.abs()) > 1 {
return true;
}
let platform_y = pos.0.y + 3;
let min_x = chunk_x * 16;
let max_x = min_x + 15;
let min_z = chunk_z * 16;
let max_z = min_z + 15;
for z in min_z..=max_z {
for x in min_x..=max_x {
if (8 - x).abs().max((8 - z).abs()) <= 16 {
let target = Vector3::new(x, platform_y, z);
let state = if x == 8 && z == 8 {
Block::COBBLESTONE.default_state
} else {
Block::STONE.default_state
};
chunk.set_block_state(&target, state);
}
} }
} }
true true
} }
} }

View File

@ -1,5 +1,8 @@
use pumpkin_data::Block; use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::KelpLikeProperties};
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl}; use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
@ -16,21 +19,109 @@ impl WeepingVinesFeature {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
let ceiling_pos = pos.up(); if !chunk.is_air(&pos.0) {
let ceiling_block = GenerationCache::get_block_state(chunk, &ceiling_pos.0).to_block_id();
if ceiling_block == Block::AIR.id {
return false; return false;
} }
let length = random.next_bounded_i32(4) + 3; let above = pos.up();
for i in 0..length { let above_id = GenerationCache::get_block_state(chunk, &above.0).to_block_id();
let target = BlockPos::new(pos.0.x, pos.0.y - i, pos.0.z); if above_id != Block::NETHERRACK.id && above_id != Block::NETHER_WART_BLOCK.id {
if chunk.is_air(&target.0) { return false;
chunk.set_block_state(&target.0, Block::WEEPING_VINES.default_state);
} else {
break;
}
} }
Self::place_roof_nether_wart(chunk, random, pos);
Self::place_roof_weeping_vines(chunk, random, pos);
true true
} }
fn place_roof_nether_wart<T: GenerationCache>(
chunk: &mut T,
random: &mut RandomGenerator,
origin: BlockPos,
) {
chunk.set_block_state(&origin.0, Block::NETHER_WART_BLOCK.default_state);
for _ in 0..200 {
let place_pos = origin.add(
random.next_bounded_i32(6) - random.next_bounded_i32(6),
random.next_bounded_i32(2) - random.next_bounded_i32(5),
random.next_bounded_i32(6) - random.next_bounded_i32(6),
);
if chunk.is_air(&place_pos.0) {
let mut neighbours = 0;
for dir in BlockDirection::all() {
let neighbour_pos = place_pos.offset(dir.to_offset());
let id =
GenerationCache::get_block_state(chunk, &neighbour_pos.0).to_block_id();
if id == Block::NETHERRACK.id || id == Block::NETHER_WART_BLOCK.id {
neighbours += 1;
}
if neighbours > 1 {
break;
}
}
if neighbours == 1 {
chunk.set_block_state(&place_pos.0, Block::NETHER_WART_BLOCK.default_state);
}
}
}
}
fn place_roof_weeping_vines<T: GenerationCache>(
chunk: &mut T,
random: &mut RandomGenerator,
origin: BlockPos,
) {
for _ in 0..100 {
let place_pos = origin.add(
random.next_bounded_i32(8) - random.next_bounded_i32(8),
random.next_bounded_i32(2) - random.next_bounded_i32(7),
random.next_bounded_i32(8) - random.next_bounded_i32(8),
);
if chunk.is_air(&place_pos.0) {
let above = place_pos.up();
let above_id = GenerationCache::get_block_state(chunk, &above.0).to_block_id();
if above_id == Block::NETHERRACK.id || above_id == Block::NETHER_WART_BLOCK.id {
let mut vine_height = random.next_bounded_i32(8) + 1;
if random.next_bounded_i32(6) == 0 {
vine_height *= 2;
}
if random.next_bounded_i32(5) == 0 {
vine_height = 1;
}
Self::place_weeping_vines_column(chunk, random, place_pos, vine_height, 17, 25);
}
}
}
}
pub fn place_weeping_vines_column<T: GenerationCache>(
chunk: &mut T,
random: &mut RandomGenerator,
mut place_pos: BlockPos,
total_height: i32,
min_age: i32,
max_age: i32,
) {
for height in 0..=total_height {
if chunk.is_air(&place_pos.0) {
if height == total_height || !chunk.is_air(&place_pos.down().0) {
let age = random.next_inbetween_i32(min_age, max_age);
let mut props = KelpLikeProperties::default(&Block::WEEPING_VINES);
props.age = age as u8;
let state_id = props.to_state_id(&Block::WEEPING_VINES);
chunk.set_block_state(&place_pos.0, BlockState::from_id(state_id));
break;
}
chunk.set_block_state(&place_pos.0, Block::WEEPING_VINES_PLANT.default_state);
}
place_pos = place_pos.down();
}
}
} }

View File

@ -96,6 +96,9 @@ pub trait GenerationCache: HeightLimitView + BlockAccessor {
chunk_x: i32, chunk_x: i32,
chunk_z: i32, chunk_z: i32,
) -> Option<&crate::generation::blender::blending_data::BlendingData>; ) -> Option<&crate::generation::blender::blending_data::BlendingData>;
fn get_sea_level(&self) -> i32 {
63
}
} }
const AIR_BLOCK: Block = Block::AIR; const AIR_BLOCK: Block = Block::AIR;
@ -184,6 +187,11 @@ impl TerrainCache {
} }
impl ProtoChunk { impl ProtoChunk {
#[must_use]
pub const fn get_sea_level(&self) -> i32 {
63
}
#[cfg(test)] #[cfg(test)]
pub(crate) fn has_structure(&self, key: StructureKeys) -> bool { pub(crate) fn has_structure(&self, key: StructureKeys) -> bool {
self.structure_starts.contains_key(&key) self.structure_starts.contains_key(&key)
@ -1717,4 +1725,7 @@ impl GenerationCache for ProtoChunk {
) -> Option<&crate::generation::blender::blending_data::BlendingData> { ) -> Option<&crate::generation::blender::blending_data::BlendingData> {
None None
} }
fn get_sea_level(&self) -> i32 {
Self::get_sea_level(self)
}
} }

View File

@ -7,7 +7,6 @@ use pumpkin_util::{
math::floor_div, math::floor_div,
random::{ random::{
RandomGenerator, RandomImpl, get_carver_seed, get_region_seed, legacy_rand::LegacyRand, RandomGenerator, RandomImpl, get_carver_seed, get_region_seed, legacy_rand::LegacyRand,
xoroshiro128::Xoroshiro,
}, },
}; };
use std::f64::consts::PI; use std::f64::consts::PI;
@ -250,8 +249,8 @@ fn should_generate_frequency(
) -> bool { ) -> bool {
match method { match method {
FrequencyReductionMethod::Default => { FrequencyReductionMethod::Default => {
let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, salt); let region_seed = get_region_seed(seed as u64, salt as i32, chunk_x, chunk_z as u32);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed)); let mut random = LegacyRand::from_seed(region_seed);
random.next_f32() < frequency random.next_f32() < frequency
} }
FrequencyReductionMethod::LegacyType1 => { FrequencyReductionMethod::LegacyType1 => {
@ -263,12 +262,12 @@ fn should_generate_frequency(
} }
FrequencyReductionMethod::LegacyType2 => { FrequencyReductionMethod::LegacyType2 => {
let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, 10387320); let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, 10387320);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed)); let mut random = LegacyRand::from_seed(region_seed);
random.next_f32() < frequency random.next_f32() < frequency
} }
FrequencyReductionMethod::LegacyType3 => { FrequencyReductionMethod::LegacyType3 => {
let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z); let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed)); let mut random = LegacyRand::from_seed(carver_seed);
random.next_f64() < f64::from(frequency) random.next_f64() < f64::from(frequency)
} }
} }

View File

@ -1,23 +1,20 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::{Block, BlockDirection, BlockId}; use pumpkin_data::Block;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{ use pumpkin_util::{
HeightMap, BlockDirection, HeightMap,
math::{block_box::BlockBox, position::BlockPos}, math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand}, random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand},
}; };
use crate::{ use crate::{
ProtoChunk, ProtoChunk,
generation::{ generation::structure::{
positions::chunk_pos::{get_center_x, get_center_z}, piece::StructurePieceType,
structure::{ structures::{
piece::StructurePieceType, StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
structures::{ StructurePiecesCollector, StructurePosition, WorldPortalExt,
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
StructurePiecesCollector, StructurePosition, WorldPortalExt,
},
}, },
}, },
}; };
@ -29,13 +26,12 @@ impl StructureGenerator for BuriedTreasureGenerator {
&self, &self,
context: StructureGeneratorContext<'_>, context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let x = get_center_x(context.chunk_x); let x = (context.chunk_x << 4) + 9;
let z = get_center_z(context.chunk_z); let z = (context.chunk_z << 4) + 9;
let bounding_box = BlockBox::new(x, -64, z, x, 320, z); let bounding_box = BlockBox::new(x, 90, z, x, 90, z);
let mut collector = StructurePiecesCollector::default(); let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(BuriedTreasurePiece { collector.add_piece(Box::new(BuriedTreasurePiece {
piece: StructurePiece::new(StructurePieceType::BuriedTreasure, bounding_box, 0), piece: StructurePiece::new(StructurePieceType::BuriedTreasure, bounding_box, 0),
})); }));
@ -55,88 +51,6 @@ impl StructurePieceBase for BuriedTreasurePiece {
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
} }
fn place(
&mut self,
chunk: &mut ProtoChunk,
_block_registry: &dyn WorldPortalExt,
_random: &mut RandomGenerator,
_seed: i64,
_chunk_box: &BlockBox,
) {
let boundingbox = self.bounding_box();
let y = chunk.get_top_y(
&HeightMap::OceanFloorWg,
boundingbox.min.x,
boundingbox.min.z,
);
let mut pos = BlockPos::new(boundingbox.min.x, y, boundingbox.min.z);
let bottom_y = chunk.bottom_y() as i32;
for _ in (bottom_y..=y).rev() {
let state = chunk.get_block_state(&pos.0);
let down_pos = pos.down();
let down_raw_state = chunk.get_block_state(&down_pos.0);
let down_block = down_raw_state.to_block_id();
if down_block == Block::SANDSTONE
|| down_block == Block::STONE
|| down_block == Block::ANDESITE
|| down_block == Block::GRANITE
|| down_block == Block::DIORITE
{
for dir in BlockDirection::all() {
let offset_pos = pos.offset(dir.to_offset());
let dir_state = chunk.get_block_state(&offset_pos.0);
if !dir_state.to_state().is_air() && !Self::is_liquid(dir_state.to_block_id()) {
continue;
}
let down_offset_pos = offset_pos.down();
let down_offset_state = chunk.get_block_state(&down_offset_pos.0);
if (down_offset_state.to_state().is_air()
|| Self::is_liquid(down_offset_state.to_block_id()))
&& dir != BlockDirection::Up
{
chunk.set_block_state(
offset_pos.0.x,
offset_pos.0.y,
offset_pos.0.z,
down_raw_state.to_state(),
);
continue;
}
let state1 =
if state.to_state().is_air() || Self::is_liquid(state.to_block_id()) {
Block::SAND.default_state
} else {
state.to_state()
};
chunk.set_block_state(offset_pos.0.x, offset_pos.0.y, offset_pos.0.z, state1);
}
chunk.set_block_state(pos.0.x, pos.0.y, pos.0.z, Block::CHEST.default_state);
let mut chest_nbt = NbtCompound::new();
chest_nbt.put_string("id", "minecraft:chest".to_string());
chest_nbt.put_int("x", pos.0.x);
chest_nbt.put_int("y", pos.0.y);
chest_nbt.put_int("z", pos.0.z);
chest_nbt.put_string("LootTable", "minecraft:chests/buried_treasure".to_string());
let mut random =
LegacyRand::from_seed(hash_block_pos(pos.0.x, pos.0.y, pos.0.z) as u64);
chest_nbt.put_long("LootTableSeed", random.next_i64());
chunk.add_block_entity(chest_nbt);
return;
}
pos = pos.down();
}
}
fn get_structure_piece(&self) -> &StructurePiece { fn get_structure_piece(&self) -> &StructurePiece {
&self.piece &self.piece
@ -145,10 +59,88 @@ impl StructurePieceBase for BuriedTreasurePiece {
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece &mut self.piece
} }
}
impl BuriedTreasurePiece { fn place(
fn is_liquid(id: BlockId) -> bool { &mut self,
id == BlockId::WATER || id == BlockId::LAVA chunk: &mut ProtoChunk,
_block_registry: &dyn WorldPortalExt,
_random: &mut RandomGenerator,
_seed: i64,
chunk_box: &BlockBox,
) {
let bb = self.bounding_box();
let y = chunk.get_top_y(&HeightMap::OceanFloorWg, bb.min.x, bb.min.z);
let min_y = chunk.bottom_y() as i32;
let mut cur_y = y;
while cur_y > min_y {
let pos = Vector3::new(bb.min.x, cur_y, bb.min.z);
let below_pos = Vector3::new(bb.min.x, cur_y - 1, bb.min.z);
let current_state = chunk.get_block_state(&pos).to_state();
let below_state = chunk.get_block_state(&below_pos).to_state();
let below_block = Block::from_state_id(below_state.id);
if *below_block == Block::SANDSTONE
|| *below_block == Block::STONE
|| *below_block == Block::ANDESITE
|| *below_block == Block::GRANITE
|| *below_block == Block::DIORITE
{
let soft_state = if !current_state.is_air() && !current_state.is_liquid() {
current_state
} else {
Block::SAND.default_state
};
for dir in [
BlockDirection::Down,
BlockDirection::Up,
BlockDirection::North,
BlockDirection::South,
BlockDirection::West,
BlockDirection::East,
] {
let offset = dir.to_vector();
let rel_pos = pos + offset;
let rel_state = chunk.get_block_state(&rel_pos).to_state();
if rel_state.is_air() || rel_state.is_liquid() {
let below_rel_pos = rel_pos - Vector3::new(0, 1, 0);
let below_rel_state = chunk.get_block_state(&below_rel_pos).to_state();
if (below_rel_state.is_air() || below_rel_state.is_liquid())
&& dir != BlockDirection::Up
{
chunk.set_block_state(rel_pos.x, rel_pos.y, rel_pos.z, below_state);
} else {
chunk.set_block_state(rel_pos.x, rel_pos.y, rel_pos.z, soft_state);
}
}
}
self.piece.bounding_box = BlockBox::new(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z);
if chunk_box.contains_pos(&pos) {
chunk.set_block_state(pos.x, pos.y, pos.z, Block::CHEST.default_state);
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:chest".to_string());
nbt.put_int("x", pos.x);
nbt.put_int("y", pos.y);
nbt.put_int("z", pos.z);
nbt.put_string("LootTable", "minecraft:chests/buried_treasure".to_string());
let mut random =
LegacyRand::from_seed(hash_block_pos(pos.x, pos.y, pos.z) as u64);
nbt.put_long("LootTableSeed", random.next_i64());
chunk.add_block_entity(nbt);
}
return;
}
cur_y -= 1;
}
} }
} }

View File

@ -103,9 +103,19 @@ impl DesertPyramidPiece {
true true
} }
fn place_sand(&self, chunk: &mut ProtoChunk, bb: &BlockBox, x: i32, y: i32, z: i32) { fn place_sand(
&self,
chunk: &mut ProtoChunk,
bb: &BlockBox,
x: i32,
y: i32,
z: i32,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) {
self.piece self.piece
.add_block(chunk, Block::SAND.default_state, x, y, z, bb); .add_block(chunk, Block::SAND.default_state, x, y, z, bb);
let world_pos = self.piece.offset_pos(x, y, z);
potential_positions.push(world_pos);
} }
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
@ -119,11 +129,12 @@ impl DesertPyramidPiece {
x1: i32, x1: i32,
y1: i32, y1: i32,
z1: i32, z1: i32,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) { ) {
for y in y0..=y1 { for y in y0..=y1 {
for x in x0..=x1 { for x in x0..=x1 {
for z in z0..=z1 { for z in z0..=z1 {
self.place_sand(chunk, bb, x, y, z); self.place_sand(chunk, bb, x, y, z, potential_positions);
} }
} }
} }
@ -158,14 +169,17 @@ impl DesertPyramidPiece {
z0: i32, z0: i32,
x1: i32, x1: i32,
z1: i32, z1: i32,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) { ) {
for x in x0..=x1 { for x in x0..=x1 {
for z in z0..=z1 { for z in z0..=z1 {
self.place_collapsed_roof_piece(chunk, bb, random, x, y0, z); self.place_collapsed_roof_piece(chunk, bb, random, x, y0, z);
} }
} }
// TODO: Pick a random collapsed roof position for sus sand placement in let rx = x0 + random.next_bounded_i32(x1 - x0 + 1);
// the structure-level afterPlace pass when brushable support exists. let rz = z0 + random.next_bounded_i32(z1 - z0 + 1);
let sus_pos = self.piece.offset_pos(rx, y0, rz);
potential_positions.push(sus_pos);
} }
fn try_place_chest( fn try_place_chest(
@ -205,8 +219,36 @@ impl DesertPyramidPiece {
} }
fn add_cellar(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) { fn add_cellar(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) {
self.add_cellar_stairs(chunk, bb, random); let mut potential_positions = Vec::new();
self.add_cellar_room(chunk, bb, random); self.add_cellar_stairs(chunk, bb, random, &mut potential_positions);
self.add_cellar_room(chunk, bb, random, &mut potential_positions);
for i in (1..potential_positions.len()).rev() {
let j = random.next_bounded_i32(i as i32 + 1) as usize;
potential_positions.swap(i, j);
}
let sus_count =
(random.next_bounded_i32(5) + 4).min(potential_positions.len() as i32) as usize;
for pos in potential_positions.into_iter().take(sus_count) {
if bb.contains_pos(&pos) {
chunk.set_block_state(pos.x, pos.y, pos.z, Block::SUSPICIOUS_SAND.default_state);
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:brushable_block".to_string());
nbt.put_int("x", pos.x);
nbt.put_int("y", pos.y);
nbt.put_int("z", pos.z);
nbt.put_string(
"LootTable",
"minecraft:archaeology/desert_pyramid".to_string(),
);
nbt.put_long(
"LootTableSeed",
BlockPos::new(pos.x, pos.y, pos.z).as_long(),
);
chunk.add_block_entity(nbt);
}
}
} }
fn add_cellar_stairs( fn add_cellar_stairs(
@ -214,6 +256,7 @@ impl DesertPyramidPiece {
chunk: &mut ProtoChunk, chunk: &mut ProtoChunk,
bb: &BlockBox, bb: &BlockBox,
random: &mut RandomGenerator, random: &mut RandomGenerator,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) { ) {
let west_stairs = Self::sandstone_stairs(HorizontalFacing::West); let west_stairs = Self::sandstone_stairs(HorizontalFacing::West);
self.piece.add_block(chunk, west_stairs, 13, -1, 17, bb); self.piece.add_block(chunk, west_stairs, 13, -1, 17, bb);
@ -225,12 +268,12 @@ impl DesertPyramidPiece {
let (x, y, z) = (16, -4, 13); let (x, y, z) = (16, -4, 13);
let variant = random.next_bool(); let variant = random.next_bool();
self.piece.add_block(chunk, sand, x - 4, y + 4, z + 4, bb); self.place_sand(chunk, bb, x - 4, y + 4, z + 4, potential_positions);
self.piece.add_block(chunk, sand, x - 3, y + 4, z + 4, bb); self.place_sand(chunk, bb, x - 3, y + 4, z + 4, potential_positions);
self.piece.add_block(chunk, sand, x - 2, y + 4, z + 4, bb); self.place_sand(chunk, bb, x - 2, y + 4, z + 4, potential_positions);
self.piece.add_block(chunk, sand, x - 1, y + 4, z + 4, bb); self.place_sand(chunk, bb, x - 1, y + 4, z + 4, potential_positions);
self.piece.add_block(chunk, sand, x, y + 4, z + 4, bb); self.place_sand(chunk, bb, x, y + 4, z + 4, potential_positions);
self.piece.add_block(chunk, sand, x - 2, y + 3, z + 4, bb); self.place_sand(chunk, bb, x - 2, y + 3, z + 4, potential_positions);
self.piece.add_block( self.piece.add_block(
chunk, chunk,
if variant { sand } else { sandstone }, if variant { sand } else { sandstone },
@ -247,13 +290,19 @@ impl DesertPyramidPiece {
z + 4, z + 4,
bb, bb,
); );
self.piece.add_block(chunk, sand, x - 1, y + 2, z + 4, bb); self.place_sand(chunk, bb, x - 1, y + 2, z + 4, potential_positions);
self.piece.add_block(chunk, sandstone, x, y + 2, z + 4, bb); self.piece.add_block(chunk, sandstone, x, y + 2, z + 4, bb);
self.piece.add_block(chunk, sand, x, y + 1, z + 4, bb); self.place_sand(chunk, bb, x, y + 1, z + 4, potential_positions);
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
fn add_cellar_room(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) { fn add_cellar_room(
&self,
chunk: &mut ProtoChunk,
bb: &BlockBox,
random: &mut RandomGenerator,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) {
let (x, y, z) = (16, -4, 13); let (x, y, z) = (16, -4, 13);
let cut = Block::CUT_SANDSTONE.default_state; let cut = Block::CUT_SANDSTONE.default_state;
let chiseled = Block::CHISELED_SANDSTONE.default_state; let chiseled = Block::CHISELED_SANDSTONE.default_state;
@ -416,8 +465,28 @@ impl DesertPyramidPiece {
cut, cut,
); );
self.place_sand_box(chunk, bb, x - 2, y + 1, z - 2, x + 2, y + 3, z + 2); self.place_sand_box(
self.place_collapsed_roof(chunk, bb, random, x - 2, y + 4, z - 2, x + 2, z + 2); chunk,
bb,
x - 2,
y + 1,
z - 2,
x + 2,
y + 3,
z + 2,
potential_positions,
);
self.place_collapsed_roof(
chunk,
bb,
random,
x - 2,
y + 4,
z - 2,
x + 2,
z + 2,
potential_positions,
);
self.piece.add_block(chunk, blue, x, y, z, bb); self.piece.add_block(chunk, blue, x, y, z, bb);
self.piece.add_block(chunk, orange, x + 1, y, z - 1, bb); self.piece.add_block(chunk, orange, x + 1, y, z - 1, bb);
self.piece.add_block(chunk, orange, x + 1, y, z + 1, bb); self.piece.add_block(chunk, orange, x + 1, y, z + 1, bb);
@ -429,24 +498,24 @@ impl DesertPyramidPiece {
self.piece.add_block(chunk, orange, x, y, z - 2, bb); self.piece.add_block(chunk, orange, x, y, z - 2, bb);
self.piece.add_block(chunk, orange, x + 3, y, z, bb); self.piece.add_block(chunk, orange, x + 3, y, z, bb);
self.place_sand(chunk, bb, x + 3, y + 1, z); self.place_sand(chunk, bb, x + 3, y + 1, z, potential_positions);
self.place_sand(chunk, bb, x + 3, y + 2, z); self.place_sand(chunk, bb, x + 3, y + 2, z, potential_positions);
self.piece.add_block(chunk, cut, x + 4, y + 1, z, bb); self.piece.add_block(chunk, cut, x + 4, y + 1, z, bb);
self.piece.add_block(chunk, chiseled, x + 4, y + 2, z, bb); self.piece.add_block(chunk, chiseled, x + 4, y + 2, z, bb);
self.piece.add_block(chunk, orange, x - 3, y, z, bb); self.piece.add_block(chunk, orange, x - 3, y, z, bb);
self.place_sand(chunk, bb, x - 3, y + 1, z); self.place_sand(chunk, bb, x - 3, y + 1, z, potential_positions);
self.place_sand(chunk, bb, x - 3, y + 2, z); self.place_sand(chunk, bb, x - 3, y + 2, z, potential_positions);
self.piece.add_block(chunk, cut, x - 4, y + 1, z, bb); self.piece.add_block(chunk, cut, x - 4, y + 1, z, bb);
self.piece.add_block(chunk, chiseled, x - 4, y + 2, z, bb); self.piece.add_block(chunk, chiseled, x - 4, y + 2, z, bb);
self.piece.add_block(chunk, orange, x, y, z + 3, bb); self.piece.add_block(chunk, orange, x, y, z + 3, bb);
self.place_sand(chunk, bb, x, y + 1, z + 3); self.place_sand(chunk, bb, x, y + 1, z + 3, potential_positions);
self.place_sand(chunk, bb, x, y + 2, z + 3); self.place_sand(chunk, bb, x, y + 2, z + 3, potential_positions);
self.piece.add_block(chunk, orange, x, y, z - 3, bb); self.piece.add_block(chunk, orange, x, y, z - 3, bb);
self.place_sand(chunk, bb, x, y + 1, z - 3); self.place_sand(chunk, bb, x, y + 1, z - 3, potential_positions);
self.place_sand(chunk, bb, x, y + 2, z - 3); self.place_sand(chunk, bb, x, y + 2, z - 3, potential_positions);
self.piece.add_block(chunk, cut, x, y + 1, z - 4, bb); self.piece.add_block(chunk, cut, x, y + 1, z - 4, bb);
self.piece.add_block(chunk, chiseled, x, -2, z - 4, bb); self.piece.add_block(chunk, chiseled, x, -2, z - 4, bb);
} }

View File

@ -10,7 +10,7 @@ use crate::generation::structure::template::{
use pumpkin_util::math::block_box::BlockBox; use pumpkin_util::math::block_box::BlockBox;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::RandomImpl; use pumpkin_util::random::{RandomDeriverImpl, RandomImpl};
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
@ -880,7 +880,11 @@ impl StructureGenerator for JigsawGenerator {
&[] &[]
}; };
let pool_alias_lookup = PoolAliasLookup::from_bindings(pool_aliases, &mut context.random); let mut alias_random =
pumpkin_util::random::legacy_rand::LegacyRand::from_seed(context.seed as u64)
.next_splitter()
.split_pos(start_pos.0.x, start_pos.0.y, start_pos.0.z);
let pool_alias_lookup = PoolAliasLookup::from_bindings(pool_aliases, &mut alias_random);
let start_pool = if self.start_pool.is_empty() { let start_pool = if self.start_pool.is_empty() {
structure structure

View File

@ -56,10 +56,10 @@ impl StructureGenerator for JungleTempleGenerator {
collector.add_piece(Box::new(JungleTemplePiece { collector.add_piece(Box::new(JungleTemplePiece {
piece, piece,
height_adjusted: false, height_adjusted: false,
placed_main_chest: true, placed_main_chest: false,
placed_hidden_chest: true, placed_hidden_chest: false,
placed_trap_1: true, placed_trap_1: false,
placed_trap_2: true, placed_trap_2: false,
})); }));
Some(StructurePosition { Some(StructurePosition {
@ -508,18 +508,18 @@ impl StructurePieceBase for JungleTemplePiece {
); );
self.piece self.piece
.add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 3, -3, 1, bb); .add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 3, -3, 1, bb);
/*if !self.placed_trap_1 { if !self.placed_trap_1 {
self.placed_trap_1 = this.createDispenser( self.placed_trap_1 = self.piece.add_dispenser(
chunk, chunk,
bb, bb,
random, random,
3, 3,
-2, -2,
1, 1,
Direction.NORTH, Facing::North,
BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER, "minecraft:chests/jungle_temple_dispenser",
); );
}*/ }
self.piece self.piece
.add_block(chunk, Self::vine_facing(Facing::South), 3, -2, 2, bb); .add_block(chunk, Self::vine_facing(Facing::South), 3, -2, 2, bb);
@ -592,27 +592,34 @@ impl StructurePieceBase for JungleTemplePiece {
.add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 4, bb); .add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 4, bb);
}; };
self.piece.add_block(chunk, redstone_wire_ns, 9, -2, 4, bb); self.piece.add_block(chunk, redstone_wire_ns, 9, -2, 4, bb);
/*if !self.placed_trap_2 { if !self.placed_trap_2 {
self.placed_trap_2 = this.createDispenser( self.placed_trap_2 = self.piece.add_dispenser(
chunk, chunk,
bb, bb,
random, random,
9, 9,
-2, -2,
3, 3,
Direction.WEST, Facing::West,
BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER, "minecraft:chests/jungle_temple_dispenser",
); );
}*/ }
self.piece self.piece
.add_block(chunk, Self::vine_facing(Facing::East), 8, -1, 3, bb); .add_block(chunk, Self::vine_facing(Facing::East), 8, -1, 3, bb);
self.piece self.piece
.add_block(chunk, Self::vine_facing(Facing::East), 8, -2, 3, bb); .add_block(chunk, Self::vine_facing(Facing::East), 8, -2, 3, bb);
/*if !self.placed_main_chest { if !self.placed_main_chest {
self.placed_main_chest = self.placed_main_chest = self.piece.add_chest(
this.createChest(chunk, bb, random, 8, -3, 3, BuiltInLootTables.JUNGLE_TEMPLE); chunk,
}*/ bb,
random,
8,
-3,
3,
"minecraft:chests/jungle_temple",
);
}
self.piece self.piece
.add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 2, bb); .add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 2, bb);
@ -738,17 +745,17 @@ impl StructurePieceBase for JungleTemplePiece {
BlockState::from_id(props.to_state_id(&Block::REPEATER)) BlockState::from_id(props.to_state_id(&Block::REPEATER))
}; };
self.piece.add_block(chunk, repeater_state, 10, -2, 10, bb); self.piece.add_block(chunk, repeater_state, 10, -2, 10, bb);
/*if !self.placed_hidden_chest { if !self.placed_hidden_chest {
self.placed_hidden_chest = this.createChest( self.placed_hidden_chest = self.piece.add_chest(
chunk, chunk,
bb, bb,
random, random,
9, 9,
-3, -3,
10, 10,
BuiltInLootTables.JUNGLE_TEMPLE, "minecraft:chests/jungle_temple",
); );
}*/ }
} }
} }
const fn set_side_dir(props: &mut RedstoneWireLikeProperties, facing: HorizontalFacing) { const fn set_side_dir(props: &mut RedstoneWireLikeProperties, facing: HorizontalFacing) {

View File

@ -74,7 +74,6 @@ pub trait StructurePieceBase: Send + Sync {
&self, &self,
_start: &StructurePiece, _start: &StructurePiece,
_random: &mut RandomGenerator, _random: &mut RandomGenerator,
// TODO: this is only for Stronghold and should not be here
_weights: &mut Vec<crate::generation::structure::structures::stronghold::PieceWeight>, _weights: &mut Vec<crate::generation::structure::structures::stronghold::PieceWeight>,
_last_piece_type: &mut Option<StrongholdPieceType>, _last_piece_type: &mut Option<StrongholdPieceType>,
_has_portal_room: &mut bool, _has_portal_room: &mut bool,
@ -388,15 +387,16 @@ impl StructurePiece {
} }
} }
#[must_use]
pub const fn is_under_sea_level( pub const fn is_under_sea_level(
&self, &self,
chunk: &mut ProtoChunk, chunk: &ProtoChunk,
x: i32, x: i32,
y: i32, y: i32,
z: i32, z: i32,
box_limit: &BlockBox, box_limit: &BlockBox,
) -> bool { ) -> bool {
let block_pos = self.offset_pos(x, y, z); let block_pos = self.offset_pos(x, y + 1, z);
if !box_limit.contains_pos(&block_pos) { if !box_limit.contains_pos(&block_pos) {
return false; return false;
@ -503,6 +503,43 @@ impl StructurePiece {
true true
} }
#[allow(clippy::too_many_arguments)]
pub fn add_dispenser(
&self,
chunk: &mut ProtoChunk,
bb: &BlockBox,
random: &mut RandomGenerator,
x: i32,
y: i32,
z: i32,
facing: pumpkin_data::block_properties::Facing,
loot_table: &str,
) -> bool {
use pumpkin_data::block_properties::DispenserLikeProperties;
use pumpkin_nbt::compound::NbtCompound;
let world_pos = self.offset_pos(x, y, z);
if !bb.contains_pos(&world_pos) {
return false;
}
let mut props = DispenserLikeProperties::default(&Block::DISPENSER);
props.facing = facing;
let state = BlockState::from_id(props.to_state_id(&Block::DISPENSER));
chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state);
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:dispenser".to_string());
nbt.put_int("x", world_pos.x);
nbt.put_int("y", world_pos.y);
nbt.put_int("z", world_pos.z);
nbt.put_string("LootTable", loot_table.to_string());
nbt.put_long("LootTableSeed", random.next_i64());
chunk.add_block_entity(nbt);
true
}
} }
impl StructurePieceBase for StructurePiece { impl StructurePieceBase for StructurePiece {
@ -625,6 +662,24 @@ impl StructurePiecesCollector {
k k
} }
pub fn move_inside_heights(
&mut self,
random: &mut RandomGenerator,
lowest_allowed: i32,
highest_allowed: i32,
) {
let bounding_box = self.get_bounding_box();
let y_span = bounding_box.max.y - bounding_box.min.y + 1;
let height_span = highest_allowed - lowest_allowed + 1 - y_span;
let y0_pos = if height_span > 1 {
lowest_allowed + random.next_bounded_i32(height_span)
} else {
lowest_allowed
};
let dy = y0_pos - bounding_box.min.y;
self.shift(dy);
}
pub fn get_bounding_box(&mut self) -> BlockBox { pub fn get_bounding_box(&mut self) -> BlockBox {
if let Some(bbox) = self.cached_box { if let Some(bbox) = self.cached_box {
return bbox; return bbox;

View File

@ -223,11 +223,14 @@ impl StructurePieceBase for CorridorExitPiece {
p.add_block(chunk, nb, 6, 0, 6, &bb); p.add_block(chunk, nb, 6, 0, 6, &bb);
p.add_block(chunk, Block::LAVA.default_state, 6, 5, 6, &bb); p.add_block(chunk, Block::LAVA.default_state, 6, 5, 6, &bb);
// Schedule lava fluid tick let lava_pos = p.offset_pos(6, 5, 6);
// TODO if bb.contains_pos(&lava_pos) {
// let lava_pos = p.offset_pos(6, 5, 6); chunk.schedule_fluid_tick(
// if bb.contains_pos(&lava_pos) { lava_pos.x,
// chunk.schedule_fluid_tick(&lava_pos, 0); lava_pos.y,
// } lava_pos.z,
&pumpkin_data::fluid::Fluid::LAVA,
);
}
} }
} }

View File

@ -83,6 +83,8 @@ impl StructureGenerator for NetherFortressGenerator {
collector.add_piece(piece); collector.add_piece(piece);
} }
collector.move_inside_heights(&mut random, 48, 70);
Some(StructurePosition { Some(StructurePosition {
start_pos: BlockPos::new( start_pos: BlockPos::new(
section_coords::section_to_block(context.chunk_x), section_coords::section_to_block(context.chunk_x),

View File

@ -1,43 +1,28 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::block_rotation::Rotation; use pumpkin_data::{
Block, BlockState, Mirror, Rotation,
block_properties::{ChestLikeProperties, HorizontalFacing},
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{ use pumpkin_util::{
math::{block_box::BlockBox, position::BlockPos}, HeightMap,
random::{RandomGenerator, RandomImpl}, math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand},
}; };
use crate::{ use crate::{
ProtoChunk, ProtoChunk,
generation::{ generation::structure::{
positions::chunk_pos::{get_center_x, get_center_z}, piece::StructurePieceType,
structure::{ structures::{
piece::StructurePieceType, StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
structures::{ StructurePiecesCollector, StructurePosition, WorldPortalExt,
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
StructurePiecesCollector, StructurePosition, WorldPortalExt,
},
template::{StructureTemplate, get_template, place_template},
}, },
template::{BlockStateResolver, StructurePlaceSettings, StructureTemplate, get_template},
}, },
}; };
const COLD_RUINS: &[&str] = &[
"underwater_ruin/brick_1",
"underwater_ruin/brick_2",
"underwater_ruin/brick_3",
"underwater_ruin/brick_4",
"underwater_ruin/brick_5",
"underwater_ruin/brick_6",
"underwater_ruin/brick_7",
"underwater_ruin/brick_8",
"underwater_ruin/cracked_1",
"underwater_ruin/cracked_2",
"underwater_ruin/cracked_3",
"underwater_ruin/mossy_1",
"underwater_ruin/mossy_2",
"underwater_ruin/mossy_3",
];
const WARM_RUINS: &[&str] = &[ const WARM_RUINS: &[&str] = &[
"underwater_ruin/warm_1", "underwater_ruin/warm_1",
"underwater_ruin/warm_2", "underwater_ruin/warm_2",
@ -47,8 +32,67 @@ const WARM_RUINS: &[&str] = &[
"underwater_ruin/warm_6", "underwater_ruin/warm_6",
"underwater_ruin/warm_7", "underwater_ruin/warm_7",
"underwater_ruin/warm_8", "underwater_ruin/warm_8",
];
const BIG_WARM_RUINS: &[&str] = &[
"underwater_ruin/big_warm_4", "underwater_ruin/big_warm_4",
"underwater_ruin/big_warm_5", "underwater_ruin/big_warm_5",
"underwater_ruin/big_warm_6",
"underwater_ruin/big_warm_7",
];
const RUINS_BRICK: &[&str] = &[
"underwater_ruin/brick_1",
"underwater_ruin/brick_2",
"underwater_ruin/brick_3",
"underwater_ruin/brick_4",
"underwater_ruin/brick_5",
"underwater_ruin/brick_6",
"underwater_ruin/brick_7",
"underwater_ruin/brick_8",
];
const RUINS_CRACKED: &[&str] = &[
"underwater_ruin/cracked_1",
"underwater_ruin/cracked_2",
"underwater_ruin/cracked_3",
"underwater_ruin/cracked_4",
"underwater_ruin/cracked_5",
"underwater_ruin/cracked_6",
"underwater_ruin/cracked_7",
"underwater_ruin/cracked_8",
];
const RUINS_MOSSY: &[&str] = &[
"underwater_ruin/mossy_1",
"underwater_ruin/mossy_2",
"underwater_ruin/mossy_3",
"underwater_ruin/mossy_4",
"underwater_ruin/mossy_5",
"underwater_ruin/mossy_6",
"underwater_ruin/mossy_7",
"underwater_ruin/mossy_8",
];
const BIG_RUINS_BRICK: &[&str] = &[
"underwater_ruin/big_brick_1",
"underwater_ruin/big_brick_2",
"underwater_ruin/big_brick_3",
"underwater_ruin/big_brick_8",
];
const BIG_RUINS_CRACKED: &[&str] = &[
"underwater_ruin/big_cracked_1",
"underwater_ruin/big_cracked_2",
"underwater_ruin/big_cracked_3",
"underwater_ruin/big_cracked_8",
];
const BIG_RUINS_MOSSY: &[&str] = &[
"underwater_ruin/big_mossy_1",
"underwater_ruin/big_mossy_2",
"underwater_ruin/big_mossy_3",
"underwater_ruin/big_mossy_8",
]; ];
pub struct OceanRuinGenerator { pub struct OceanRuinGenerator {
@ -60,81 +104,566 @@ impl StructureGenerator for OceanRuinGenerator {
&self, &self,
mut context: StructureGeneratorContext<'_>, mut context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let chunk_center_x = get_center_x(context.chunk_x); let start_x = context.chunk_x << 4;
let chunk_center_z = get_center_z(context.chunk_z); let start_z = context.chunk_z << 4;
let position = Vector3::new(start_x, 90, start_z);
let rotation_idx = context.random.next_bounded_i32(4) as u8; let rotation_idx = context.random.next_bounded_i32(4) as u8;
let rotation = Rotation::from_index(rotation_idx); let rotation = Rotation::from_index(rotation_idx);
let pool = if self.is_warm { WARM_RUINS } else { COLD_RUINS }; let mut collector = StructurePiecesCollector::default();
let template_idx = context.random.next_bounded_i32(pool.len() as i32) as usize; add_pieces(
let template_name = pool[template_idx]; &mut collector,
let template = get_template(template_name)?; position,
rotation,
let size = template.size; &mut context.random,
let bounding_box = BlockBox::new( self.is_warm,
chunk_center_x - size.x / 2, 0.3,
context.min_y, 0.7,
chunk_center_z - size.z / 2,
chunk_center_x + size.x / 2,
256,
chunk_center_z + size.z / 2,
); );
let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(OceanRuinPiece {
piece: StructurePiece::new(StructurePieceType::OceanTemple, bounding_box, 0),
template,
rotation,
}));
Some(StructurePosition { Some(StructurePosition {
start_pos: BlockPos::new(chunk_center_x, 64, chunk_center_z), start_pos: BlockPos::new(start_x + 8, 64, start_z + 8),
collector: Arc::new(collector.into()), collector: Arc::new(collector.into()),
}) })
} }
} }
pub struct OceanRuinPiece { fn add_pieces(
piece: StructurePiece, collector: &mut StructurePiecesCollector,
template: Arc<StructureTemplate>, position: Vector3<i32>,
rotation: Rotation, rotation: Rotation,
random: &mut RandomGenerator,
is_warm: bool,
large_probability: f32,
cluster_probability: f32,
) {
let is_large = random.next_f32() <= large_probability;
let base_integrity = if is_large { 0.9 } else { 0.8 };
add_piece(
collector,
position,
rotation,
random,
is_warm,
is_large,
base_integrity,
);
if is_large && random.next_f32() <= cluster_probability {
add_cluster_ruins(collector, random, rotation, position, is_warm);
}
}
fn add_cluster_ruins(
collector: &mut StructurePiecesCollector,
random: &mut RandomGenerator,
rotation: Rotation,
p: Vector3<i32>,
is_warm: bool,
) {
let parent_pos = Vector3::new(p.x, 90, p.z);
let parent_corner = StructureTemplate::transform_block_pos(
Vector3::new(15, 0, 15),
Mirror::None,
rotation,
Vector3::new(0, 0, 0),
) + parent_pos;
let parent_bb = BlockBox::new(
parent_pos.x.min(parent_corner.x),
parent_pos.y.min(parent_corner.y),
parent_pos.z.min(parent_corner.z),
parent_pos.x.max(parent_corner.x),
parent_pos.y.max(parent_corner.y),
parent_pos.z.max(parent_corner.z),
);
let parent_bottom_left = Vector3::new(
parent_pos.x.min(parent_corner.x),
parent_pos.y,
parent_pos.z.min(parent_corner.z),
);
let mut all_positions = generate_cluster_positions(random, parent_bottom_left);
let ruins_count = random.next_bounded_i32(5) + 4;
for _ in 0..ruins_count {
if all_positions.is_empty() {
break;
}
let idx = random.next_bounded_i32(all_positions.len() as i32) as usize;
let pos = all_positions.swap_remove(idx);
let next_rotation = Rotation::from_index(random.next_bounded_i32(4) as u8);
let next_corner = StructureTemplate::transform_block_pos(
Vector3::new(5, 0, 6),
Mirror::None,
next_rotation,
Vector3::new(0, 0, 0),
) + pos;
let next_bb = BlockBox::new(
pos.x.min(next_corner.x),
pos.y.min(next_corner.y),
pos.z.min(next_corner.z),
pos.x.max(next_corner.x),
pos.y.max(next_corner.y),
pos.z.max(next_corner.z),
);
if !next_bb.intersects(&parent_bb) {
add_piece(collector, pos, next_rotation, random, is_warm, false, 0.8);
}
}
}
fn generate_cluster_positions(
random: &mut RandomGenerator,
origin: Vector3<i32>,
) -> Vec<Vector3<i32>> {
vec![
origin
+ Vector3::new(
-16 + random.next_bounded_i32(8) + 1,
0,
16 + random.next_bounded_i32(7) + 1,
),
origin
+ Vector3::new(
-16 + random.next_bounded_i32(8) + 1,
0,
random.next_bounded_i32(7) + 1,
),
origin
+ Vector3::new(
-16 + random.next_bounded_i32(8) + 1,
0,
-16 + random.next_bounded_i32(5) + 4,
),
origin
+ Vector3::new(
random.next_bounded_i32(7) + 1,
0,
16 + random.next_bounded_i32(7) + 1,
),
origin
+ Vector3::new(
random.next_bounded_i32(7) + 1,
0,
-16 + random.next_bounded_i32(3) + 4,
),
origin
+ Vector3::new(
16 + random.next_bounded_i32(7) + 1,
0,
16 + random.next_bounded_i32(6) + 3,
),
origin
+ Vector3::new(
16 + random.next_bounded_i32(7) + 1,
0,
random.next_bounded_i32(7) + 1,
),
origin
+ Vector3::new(
16 + random.next_bounded_i32(7) + 1,
0,
-16 + random.next_bounded_i32(5) + 4,
),
]
}
fn add_piece(
collector: &mut StructurePiecesCollector,
position: Vector3<i32>,
rotation: Rotation,
random: &mut RandomGenerator,
is_warm: bool,
is_large: bool,
base_integrity: f32,
) {
if is_warm {
let pool = if is_large { BIG_WARM_RUINS } else { WARM_RUINS };
let template_idx = random.next_bounded_i32(pool.len() as i32) as usize;
let template_name = pool[template_idx];
if let Some(template) = get_template(template_name) {
collector.add_piece(Box::new(OceanRuinPiece::new(
template,
template_name.to_string(),
position,
rotation,
base_integrity,
is_warm,
is_large,
)));
}
} else {
let (bricks, cracked, mossy) = if is_large {
(BIG_RUINS_BRICK, BIG_RUINS_CRACKED, BIG_RUINS_MOSSY)
} else {
(RUINS_BRICK, RUINS_CRACKED, RUINS_MOSSY)
};
let idx = random.next_bounded_i32(bricks.len() as i32) as usize;
if let Some(template) = get_template(bricks[idx]) {
collector.add_piece(Box::new(OceanRuinPiece::new(
template,
bricks[idx].to_string(),
position,
rotation,
base_integrity,
is_warm,
is_large,
)));
}
if let Some(template) = get_template(cracked[idx]) {
collector.add_piece(Box::new(OceanRuinPiece::new(
template,
cracked[idx].to_string(),
position,
rotation,
0.7,
is_warm,
is_large,
)));
}
if let Some(template) = get_template(mossy[idx]) {
collector.add_piece(Box::new(OceanRuinPiece::new(
template,
mossy[idx].to_string(),
position,
rotation,
0.5,
is_warm,
is_large,
)));
}
}
}
pub struct OceanRuinPiece {
pub piece: StructurePiece,
pub template: Arc<StructureTemplate>,
pub template_name: String,
pub template_position: Vector3<i32>,
pub rotation: Rotation,
pub integrity: f32,
pub is_warm: bool,
pub is_large: bool,
}
impl OceanRuinPiece {
#[must_use]
pub fn new(
template: Arc<StructureTemplate>,
template_name: String,
position: Vector3<i32>,
rotation: Rotation,
integrity: f32,
is_warm: bool,
is_large: bool,
) -> Self {
let place_settings = StructurePlaceSettings::new()
.set_rotation(rotation)
.set_mirror(Mirror::None);
let bounding_box = template.get_bounding_box(&place_settings, position);
Self {
piece: StructurePiece::new(StructurePieceType::OceanTemple, bounding_box, 0),
template,
template_name,
template_position: position,
rotation,
integrity,
is_warm,
is_large,
}
}
fn calculate_height(pos: Vector3<i32>, corner: Vector3<i32>, chunk: &ProtoChunk) -> i32 {
let mut new_y = pos.y;
let mut min_y = 512;
let top_y = new_y - 1;
let mut area = 0;
let min_x = pos.x.min(corner.x);
let max_x = pos.x.max(corner.x);
let min_z = pos.z.min(corner.z);
let max_z = pos.z.max(corner.z);
for x in min_x..=max_x {
for z in min_z..=max_z {
let floor_y = chunk.get_top_y(&HeightMap::OceanFloorWg, x, z) - 1;
min_y = min_y.min(floor_y);
if floor_y < top_y - 2 {
area += 1;
}
}
}
let width = (pos.x - corner.x).abs();
if top_y - min_y > 2 && area > width - 2 {
new_y = min_y + 1;
}
new_y
}
} }
impl StructurePieceBase for OceanRuinPiece { impl StructurePieceBase for OceanRuinPiece {
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
} }
fn get_structure_piece(&self) -> &StructurePiece { fn get_structure_piece(&self) -> &StructurePiece {
&self.piece &self.piece
} }
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece &mut self.piece
} }
fn translate(&mut self, x: i32, y: i32, z: i32) {
self.piece.translate(x, y, z);
self.template_position += Vector3::new(x, y, z);
}
#[allow(clippy::too_many_lines)]
fn place( fn place(
&mut self, &mut self,
chunk: &mut ProtoChunk, chunk: &mut ProtoChunk,
_block_registry: &dyn WorldPortalExt, _block_registry: &dyn WorldPortalExt,
_random: &mut RandomGenerator, random: &mut RandomGenerator,
_seed: i64, _seed: i64,
chunk_box: &BlockBox, chunk_box: &BlockBox,
) { ) {
let origin = self.piece.bounding_box.min; let height = chunk.get_top_y(
let sample_y = chunk.get_top_y(&pumpkin_util::HeightMap::OceanFloorWg, origin.x, origin.z); &HeightMap::OceanFloorWg,
let target_y = sample_y - 2; // slightly buried on ocean floor self.template_position.x,
let mut final_origin = origin; self.template_position.z,
final_origin.y = target_y;
place_template(
chunk,
&self.template,
final_origin,
(0, 0),
self.rotation,
true, // skip air
true, // apply waterlogging
&[],
Some(chunk_box),
); );
let mut pos = Vector3::new(self.template_position.x, height, self.template_position.z);
let size = self.template.size;
let corner = StructureTemplate::transform_block_pos(
Vector3::new(size.x - 1, 0, size.z - 1),
Mirror::None,
self.rotation,
Vector3::new(0, 0, 0),
) + pos;
pos.y = Self::calculate_height(pos, corner, chunk);
self.template_position = pos;
let place_settings = StructurePlaceSettings::new()
.set_rotation(self.rotation)
.set_mirror(Mirror::None);
self.piece.bounding_box = self
.template
.get_bounding_box(&place_settings, self.template_position);
let box_limit = self.piece.bounding_box;
let mut suspicious_count = 0;
for block in &self.template.blocks {
let palette_entry = &self.template.palette[block.state as usize];
if palette_entry.name == "minecraft:structure_void"
|| palette_entry.name == "structure_void"
{
continue;
}
let rel_pos = StructureTemplate::transform_block_pos(
block.pos,
Mirror::None,
self.rotation,
Vector3::new(0, 0, 0),
);
let world_pos = self.template_position + rel_pos;
if !box_limit.contains_pos(&world_pos) || !chunk_box.contains_pos(&world_pos) {
continue;
}
if palette_entry.name == "minecraft:structure_block" {
if let Some(nbt) = &block.nbt {
let mode = nbt.get_string("mode").unwrap_or("");
if mode == "DATA" {
let metadata = nbt.get_string("metadata").unwrap_or("");
if metadata == "chest" {
let is_water =
chunk.get_block_state(&world_pos).to_block_id() == Block::WATER.id;
let mut props = ChestLikeProperties::default(&Block::CHEST);
props.facing = HorizontalFacing::North;
props.waterlogged = is_water;
let chest_state = BlockState::from_id(props.to_state_id(&Block::CHEST));
chunk.set_block_state(
world_pos.x,
world_pos.y,
world_pos.z,
chest_state,
);
let mut chest_nbt = NbtCompound::new();
chest_nbt.put_string("id", "minecraft:chest".to_string());
chest_nbt.put_int("x", world_pos.x);
chest_nbt.put_int("y", world_pos.y);
chest_nbt.put_int("z", world_pos.z);
let loot_table = if self.is_large {
"minecraft:chests/underwater_ruin_big"
} else {
"minecraft:chests/underwater_ruin_small"
};
chest_nbt.put_string("LootTable", loot_table.to_string());
let mut rng = LegacyRand::from_seed(hash_block_pos(
world_pos.x,
world_pos.y,
world_pos.z,
)
as u64);
chest_nbt.put_long("LootTableSeed", rng.next_i64());
chunk.add_block_entity(chest_nbt);
} else if metadata == "drowned" {
let replacement = if world_pos.y > chunk.get_sea_level() {
Block::AIR.default_state
} else {
Block::WATER.default_state
};
chunk.set_block_state(
world_pos.x,
world_pos.y,
world_pos.z,
replacement,
);
}
}
}
continue;
}
if self.integrity < 0.999 && random.next_f32() > self.integrity {
continue;
}
if palette_entry.name == "minecraft:air" || palette_entry.name == "air" {
continue;
}
if self.is_warm
&& palette_entry.name == "minecraft:sand"
&& suspicious_count < 5
&& random.next_f32() < 0.45
{
suspicious_count += 1;
chunk.set_block_state(
world_pos.x,
world_pos.y,
world_pos.z,
Block::SUSPICIOUS_SAND.default_state,
);
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:brushable_block".to_string());
nbt.put_int("x", world_pos.x);
nbt.put_int("y", world_pos.y);
nbt.put_int("z", world_pos.z);
nbt.put_string(
"LootTable",
"minecraft:archaeology/ocean_ruin_warm".to_string(),
);
let mut rng =
LegacyRand::from_seed(
hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64
);
nbt.put_long("LootTableSeed", rng.next_i64());
chunk.add_block_entity(nbt);
continue;
} else if !self.is_warm
&& palette_entry.name == "minecraft:gravel"
&& suspicious_count < 5
&& random.next_f32() < 0.45
{
suspicious_count += 1;
chunk.set_block_state(
world_pos.x,
world_pos.y,
world_pos.z,
Block::SUSPICIOUS_GRAVEL.default_state,
);
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:brushable_block".to_string());
nbt.put_int("x", world_pos.x);
nbt.put_int("y", world_pos.y);
nbt.put_int("z", world_pos.z);
nbt.put_string(
"LootTable",
"minecraft:archaeology/ocean_ruin_cold".to_string(),
);
let mut rng =
LegacyRand::from_seed(
hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64
);
nbt.put_long("LootTableSeed", rng.next_i64());
chunk.add_block_entity(nbt);
continue;
}
let Some(mut state) =
BlockStateResolver::resolve(palette_entry, self.rotation, Mirror::None)
else {
continue;
};
if chunk.get_block_state(&world_pos).to_block_id() == Block::WATER.id {
let mut modified_entry = palette_entry.clone();
if let Some((_, waterlogged)) = modified_entry
.properties
.iter_mut()
.find(|(name, _)| name == "waterlogged")
{
*waterlogged = "true".to_string();
if let Some(waterlogged_state) =
BlockStateResolver::resolve(&modified_entry, self.rotation, Mirror::None)
{
state = waterlogged_state;
}
}
}
chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state);
let final_block = Block::from_id(state.id.to_block_id());
let block_entity_id =
crate::generation::structure::template::get_block_entity_id(final_block.name);
if block.nbt.is_some() || block_entity_id.is_some() {
let fallback_id = block_entity_id.unwrap_or(final_block.name);
let mut placed_nbt = NbtCompound::new();
placed_nbt.put_string("id", fallback_id.to_string());
placed_nbt.put_int("x", world_pos.x);
placed_nbt.put_int("y", world_pos.y);
placed_nbt.put_int("z", world_pos.z);
if let Some(template_nbt) = &block.nbt {
for (key, value) in &template_nbt.child_tags {
if key.as_ref() != "x"
&& key.as_ref() != "y"
&& key.as_ref() != "z"
&& key.as_ref() != "id"
{
placed_nbt.child_tags.insert(key.clone(), value.clone());
}
}
}
if placed_nbt.get_string("LootTable").is_some()
&& placed_nbt.get_long("LootTableSeed").is_none()
{
let mut rng =
LegacyRand::from_seed(
hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64,
);
placed_nbt.put_long("LootTableSeed", rng.next_i64());
}
chunk.add_block_entity(placed_nbt);
}
}
} }
} }

View File

@ -63,10 +63,16 @@ impl StructureGenerator for StrongholdGenerator {
context: StructureGeneratorContext<'_>, context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let mut collector = StructurePiecesCollector::default(); let mut collector = StructurePiecesCollector::default();
let mut random = context.random; let mut tries: i64 = 0;
loop { loop {
collector.clear(); collector.clear();
let mut random = crate::generation::structure::structures::create_chunk_random(
context.seed.wrapping_add(tries),
context.chunk_x,
context.chunk_z,
);
tries += 1;
let mut weights = get_initial_weights(); let mut weights = get_initial_weights();
let mut last_piece_type: Option<StrongholdPieceType> = None; let mut last_piece_type: Option<StrongholdPieceType> = None;
let mut has_portal_room = false; let mut has_portal_room = false;

View File

@ -81,7 +81,6 @@ impl StructurePieceBase for PortalRoomPiece {
&self, &self,
_start: &StructurePiece, _start: &StructurePiece,
_random: &mut RandomGenerator, _random: &mut RandomGenerator,
// TODO: this is only for Stronghold and should not be here
_weights: &mut Vec<PieceWeight>, _weights: &mut Vec<PieceWeight>,
_last_piece_type: &mut Option<StrongholdPieceType>, _last_piece_type: &mut Option<StrongholdPieceType>,
has_portal_room: &mut bool, has_portal_room: &mut bool,

View File

@ -1,6 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::Block; use pumpkin_data::{
Block, BlockState,
block_properties::{HorizontalFacing, OakStairsLikeProperties, StairsShape},
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{ use pumpkin_util::{
BlockDirection, BlockDirection,
math::{block_box::BlockBox, position::BlockPos}, math::{block_box::BlockBox, position::BlockPos},
@ -11,13 +15,13 @@ use serde::Deserialize;
use crate::{ use crate::{
ProtoChunk, ProtoChunk,
generation::{ generation::{
positions::chunk_pos::{get_center_x, get_center_z}, positions::chunk_pos::{start_block_x, start_block_z},
structure::{ structure::{
piece::StructurePieceType, piece::StructurePieceType,
shiftable_piece::ShiftableStructurePiece, shiftable_piece::ShiftableStructurePiece,
structures::{ structures::{
StructureGenerator, StructureGeneratorContext, StructurePieceBase, StructureGenerator, StructureGeneratorContext, StructurePieceBase,
StructurePiecesCollector, StructurePosition, StructurePiecesCollector, StructurePosition, WorldPortalExt,
}, },
}, },
}, },
@ -31,21 +35,27 @@ impl StructureGenerator for SwampHutGenerator {
&self, &self,
mut context: StructureGeneratorContext<'_>, mut context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> { ) -> Option<StructurePosition> {
let x = get_center_x(context.chunk_x); let x = start_block_x(context.chunk_x);
let z = get_center_z(context.chunk_z); let z = start_block_z(context.chunk_z);
let facing = BlockDirection::get_random_horizontal_direction(&mut context.random);
let mut shiftable_piece = ShiftableStructurePiece::new(
StructurePieceType::SwampHut,
x,
64,
z,
7,
7,
9,
facing.get_axis(),
);
shiftable_piece.piece.set_facing(Some(facing));
let mut collector = StructurePiecesCollector::default(); let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(SwampHutPiece { collector.add_piece(Box::new(SwampHutPiece {
shiftable_structure_piece: ShiftableStructurePiece::new( shiftable_structure_piece: shiftable_piece,
StructurePieceType::SwampHut, spawned_witch: false,
x, spawned_cat: false,
64,
z,
7,
7,
9,
BlockDirection::get_random_horizontal_direction(&mut context.random).get_axis(),
),
})); }));
Some(StructurePosition { Some(StructurePosition {
@ -57,17 +67,72 @@ impl StructureGenerator for SwampHutGenerator {
pub struct SwampHutPiece { pub struct SwampHutPiece {
shiftable_structure_piece: ShiftableStructurePiece, shiftable_structure_piece: ShiftableStructurePiece,
spawned_witch: bool,
spawned_cat: bool,
}
impl SwampHutPiece {
fn spruce_stairs(facing: HorizontalFacing, shape: StairsShape) -> &'static BlockState {
let mut props = OakStairsLikeProperties::default(&Block::SPRUCE_STAIRS);
props.facing = facing;
props.shape = shape;
BlockState::from_id(props.to_state_id(&Block::SPRUCE_STAIRS))
}
fn spawn_witch(&mut self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
if !self.spawned_witch {
let pos = self.shiftable_structure_piece.piece.offset_pos(2, 2, 5);
if chunk_box.contains_pos(&pos) {
self.spawned_witch = true;
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:witch".to_string());
nbt.put_list(
"Pos",
vec![
(f64::from(pos.x) + 0.5).into(),
f64::from(pos.y).into(),
(f64::from(pos.z) + 0.5).into(),
],
);
nbt.put_bool("PersistenceRequired", true);
chunk.add_structure_entity(nbt);
}
}
}
fn spawn_cat(&mut self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
if !self.spawned_cat {
let pos = self.shiftable_structure_piece.piece.offset_pos(2, 2, 5);
if chunk_box.contains_pos(&pos) {
self.spawned_cat = true;
let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:cat".to_string());
nbt.put_list(
"Pos",
vec![
(f64::from(pos.x) + 0.5).into(),
f64::from(pos.y).into(),
(f64::from(pos.z) + 0.5).into(),
],
);
nbt.put_bool("PersistenceRequired", true);
nbt.put_string("variant", "minecraft:all_black".to_string());
chunk.add_structure_entity(nbt);
}
}
}
} }
impl StructurePieceBase for SwampHutPiece { impl StructurePieceBase for SwampHutPiece {
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {
self self
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn place( fn place(
&mut self, &mut self,
chunk: &mut ProtoChunk, chunk: &mut ProtoChunk,
_block_registry: &dyn crate::world::WorldPortalExt, block_registry: &dyn WorldPortalExt,
_random: &mut RandomGenerator, _random: &mut RandomGenerator,
_seed: i64, _seed: i64,
chunk_box: &BlockBox, chunk_box: &BlockBox,
@ -210,20 +275,60 @@ impl StructurePieceBase for SwampHutPiece {
p.add_block(chunk, oak_fence, 1, 2, 1, &box_limit); p.add_block(chunk, oak_fence, 1, 2, 1, &box_limit);
p.add_block(chunk, oak_fence, 5, 2, 1, &box_limit); p.add_block(chunk, oak_fence, 5, 2, 1, &box_limit);
// let stairs_n = Block::SPRUCE_STAIRS.default_state.with("facing", "north"); let stairs_n = Self::spruce_stairs(HorizontalFacing::North, StairsShape::Straight);
// let stairs_e = Block::SPRUCE_STAIRS.default_state.with("facing", "east"); let stairs_e = Self::spruce_stairs(HorizontalFacing::East, StairsShape::Straight);
// let stairs_w = Block::SPRUCE_STAIRS.default_state.with("facing", "west"); let stairs_w = Self::spruce_stairs(HorizontalFacing::West, StairsShape::Straight);
// let stairs_s = Block::SPRUCE_STAIRS.default_state.with("facing", "south"); let stairs_s = Self::spruce_stairs(HorizontalFacing::South, StairsShape::Straight);
// p.fill_with_outline(chunk, &box_limit, false, 0, 4, 1, 6, 4, 1, &stairs_n, &stairs_n); p.fill_with_outline(
// p.fill_with_outline(chunk, &box_limit, false, 0, 4, 2, 0, 4, 7, &stairs_e, &stairs_e); chunk, &box_limit, false, 0, 4, 1, 6, 4, 1, stairs_n, stairs_n,
// p.fill_with_outline(chunk, &box_limit, false, 6, 4, 2, 6, 4, 7, &stairs_w, &stairs_w); );
// p.fill_with_outline(chunk, &box_limit, false, 0, 4, 8, 6, 4, 8, &stairs_s, &stairs_s); p.fill_with_outline(
chunk, &box_limit, false, 0, 4, 2, 0, 4, 7, stairs_e, stairs_e,
);
p.fill_with_outline(
chunk, &box_limit, false, 6, 4, 2, 6, 4, 7, stairs_w, stairs_w,
);
p.fill_with_outline(
chunk, &box_limit, false, 0, 4, 8, 6, 4, 8, stairs_s, stairs_s,
);
// p.add_block(chunk, &stairs_n.with("shape", "outer_right"), 0, 4, 1, &box_limit); p.place_block(
// p.add_block(chunk, &stairs_n.with("shape", "outer_left"), 6, 4, 1, &box_limit); chunk,
// p.add_block(chunk, &stairs_s.with("shape", "outer_left"), 0, 4, 8, &box_limit); block_registry,
// p.add_block(chunk, &stairs_s.with("shape", "outer_right"), 6, 4, 8, &box_limit); Self::spruce_stairs(HorizontalFacing::North, StairsShape::OuterRight),
0,
4,
1,
&box_limit,
);
p.place_block(
chunk,
block_registry,
Self::spruce_stairs(HorizontalFacing::North, StairsShape::OuterLeft),
6,
4,
1,
&box_limit,
);
p.place_block(
chunk,
block_registry,
Self::spruce_stairs(HorizontalFacing::South, StairsShape::OuterLeft),
0,
4,
8,
&box_limit,
);
p.place_block(
chunk,
block_registry,
Self::spruce_stairs(HorizontalFacing::South, StairsShape::OuterRight),
6,
4,
8,
&box_limit,
);
for i in [2, 7] { for i in [2, 7] {
for j in [1, 5] { for j in [1, 5] {
@ -231,15 +336,10 @@ impl StructurePieceBase for SwampHutPiece {
} }
} }
// if !self.has_witch { self.spawn_witch(chunk, &box_limit);
// let world_coords = p.get_world_coords(2, 2, 5); self.spawn_cat(chunk, &box_limit);
// if box_limit.contains(world_coords.0, world_coords.1, world_coords.2) {
// self.has_witch = true;
// // TODO: chunk.add_entity(Witch, world_coords)
// }
// }
// TODO: self.spawn_cat(chunk, &box_limit);
} }
fn get_structure_piece(&self) -> &super::StructurePiece { fn get_structure_piece(&self) -> &super::StructurePiece {
&self.shiftable_structure_piece.piece &self.shiftable_structure_piece.piece
} }

View File

@ -232,7 +232,7 @@ impl PosRuleTest {
let dist = dx + dy + dz; let dist = dx + dy + dz;
let chance = let chance =
calculate_linear_chance(dist, *min_dist, *max_dist, *min_chance, *max_chance); calculate_linear_chance(dist, *min_dist, *max_dist, *min_chance, *max_chance);
rng.next_f32() < chance rng.next_f32() <= chance
} }
Self::AxisAlignedLinearPos { Self::AxisAlignedLinearPos {
axis, axis,
@ -248,7 +248,7 @@ impl PosRuleTest {
}; };
let chance = let chance =
calculate_linear_chance(dist, *min_dist, *max_dist, *min_chance, *max_chance); calculate_linear_chance(dist, *min_dist, *max_dist, *min_chance, *max_chance);
rng.next_f32() < chance rng.next_f32() <= chance
} }
} }
} }
@ -526,6 +526,19 @@ fn replace_preserving_properties(
}) })
} }
fn get_random_facing_stairs(
rng: &mut impl RandomImpl,
stair_block: &'static Block,
) -> &'static BlockState {
let facings = ["north", "south", "east", "west"];
let halves = ["bottom", "top"];
let facing = facings[rng.next_bounded_i32(4) as usize];
let half = halves[rng.next_bounded_i32(2) as usize];
let props = [("facing", facing), ("half", half)];
let state_id = stair_block.from_properties(&props).to_state_id(stair_block);
BlockState::from_id(state_id)
}
fn process_block_age( fn process_block_age(
state: &'static BlockState, state: &'static BlockState,
mossiness: f32, mossiness: f32,
@ -537,45 +550,50 @@ fn process_block_age(
block_id, block_id,
BlockId::STONE_BRICKS BlockId::STONE_BRICKS
| BlockId::STONE | BlockId::STONE
| BlockId::CRACKED_STONE_BRICKS
| BlockId::CHISELED_STONE_BRICKS | BlockId::CHISELED_STONE_BRICKS
| BlockId::INFESTED_STONE | BlockId::INFESTED_STONE
| BlockId::INFESTED_STONE_BRICKS | BlockId::INFESTED_STONE_BRICKS
| BlockId::INFESTED_CRACKED_STONE_BRICKS
| BlockId::INFESTED_CHISELED_STONE_BRICKS | BlockId::INFESTED_CHISELED_STONE_BRICKS
) { ) {
if rng.next_f32() < 0.5 { if rng.next_f32() < 0.5 {
let non_mossy = [
Block::CRACKED_STONE_BRICKS.default_state,
get_random_facing_stairs(rng, &Block::STONE_BRICK_STAIRS),
];
let mossy = [
Block::MOSSY_STONE_BRICKS.default_state,
get_random_facing_stairs(rng, &Block::MOSSY_STONE_BRICK_STAIRS),
];
let idx = rng.next_bounded_i32(2) as usize;
if rng.next_f32() < mossiness { if rng.next_f32() < mossiness {
return Block::MOSSY_STONE_BRICKS.default_state; return mossy[idx];
} }
return Block::CRACKED_STONE_BRICKS.default_state; return non_mossy[idx];
} }
} else if block_id == BlockId::STONE_BRICK_STAIRS } else if check_block_has_tag(block_id, "stairs") {
|| block_id == BlockId::INFESTED_MOSSY_STONE_BRICKS if rng.next_f32() < 0.5 {
{ let non_mossy = [
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { Block::STONE_SLAB.default_state,
return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_STAIRS); Block::STONE_BRICK_SLAB.default_state,
];
let mossy = [
replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_STAIRS),
Block::MOSSY_STONE_BRICK_SLAB.default_state,
];
let idx = rng.next_bounded_i32(2) as usize;
if rng.next_f32() < mossiness {
return mossy[idx];
}
return non_mossy[idx];
} }
} else if block_id == BlockId::STONE_BRICK_SLAB { } else if check_block_has_tag(block_id, "slabs") {
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { if rng.next_f32() < mossiness {
return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_SLAB); return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_SLAB);
} }
} else if block_id == BlockId::STONE_BRICK_WALL { } else if check_block_has_tag(block_id, "walls") {
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { if rng.next_f32() < mossiness {
return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_WALL); return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_WALL);
} }
} else if block_id == BlockId::COBBLESTONE_STAIRS {
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness {
return replace_preserving_properties(state, &Block::MOSSY_COBBLESTONE_STAIRS);
}
} else if block_id == BlockId::COBBLESTONE_SLAB {
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness {
return replace_preserving_properties(state, &Block::MOSSY_COBBLESTONE_SLAB);
}
} else if block_id == BlockId::COBBLESTONE_WALL {
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness {
return replace_preserving_properties(state, &Block::MOSSY_COBBLESTONE_WALL);
}
} else if block_id == BlockId::OBSIDIAN && rng.next_f32() < 0.15 { } else if block_id == BlockId::OBSIDIAN && rng.next_f32() < 0.15 {
return Block::CRYING_OBSIDIAN.default_state; return Block::CRYING_OBSIDIAN.default_state;
} }

View File

@ -372,9 +372,21 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
} }
"minecraft:fallen_tree" => { "minecraft:fallen_tree" => {
let trunk = value_to_block_state_provider(&config["trunk_provider"]); let trunk = value_to_block_state_provider(&config["trunk_provider"]);
let log_length = value_to_int_provider(&config["log_length"]);
let stump_decorators: Vec<TokenStream> = config["stump_decorators"]
.as_array()
.map(|arr| arr.iter().map(value_to_tree_decorator).collect())
.unwrap_or_default();
let log_decorators: Vec<TokenStream> = config["log_decorators"]
.as_array()
.map(|arr| arr.iter().map(value_to_tree_decorator).collect())
.unwrap_or_default();
quote! { quote! {
ConfiguredFeature::FallenTree(FallenTreeFeature { ConfiguredFeature::FallenTree(FallenTreeFeature {
trunk_provider: #trunk, trunk_provider: #trunk,
log_length: #log_length,
stump_decorators: vec![#(#stump_decorators),*],
log_decorators: vec![#(#log_decorators),*],
}) })
} }
} }
@ -810,7 +822,42 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
} }
} }
"minecraft:multiface_growth" => { "minecraft:multiface_growth" => {
quote! { ConfiguredFeature::MultifaceGrowth(crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature {}) } let block_name = config["block"].as_str().unwrap_or("minecraft:glow_lichen");
let stripped = block_name.strip_prefix("minecraft:").unwrap_or(block_name);
let block_ident =
quote::format_ident!("{}", stripped.to_uppercase().replace([':', '-'], "_"));
let search_range = config["search_range"].as_i64().unwrap_or(10) as i32;
let can_place_on_floor = config["can_place_on_floor"].as_bool().unwrap_or(false);
let can_place_on_ceiling = config["can_place_on_ceiling"].as_bool().unwrap_or(false);
let can_place_on_wall = config["can_place_on_wall"].as_bool().unwrap_or(false);
let chance_of_spreading = config["chance_of_spreading"].as_f64().unwrap_or(0.5) as f32;
let can_be_placed_on: Vec<TokenStream> = config["can_be_placed_on"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str())
.map(|name| {
let stripped = name.strip_prefix("minecraft:").unwrap_or(name);
let ident = quote::format_ident!(
"{}",
stripped.to_uppercase().replace([':', '-'], "_")
);
quote! { pumpkin_data::BlockId::#ident }
})
.collect()
})
.unwrap_or_default();
quote! {
ConfiguredFeature::MultifaceGrowth(crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature {
place_block: pumpkin_data::BlockId::#block_ident,
search_range: #search_range,
can_place_on_floor: #can_place_on_floor,
can_place_on_ceiling: #can_place_on_ceiling,
can_place_on_wall: #can_place_on_wall,
chance_of_spreading: #chance_of_spreading,
can_be_placed_on: vec![#(#can_be_placed_on),*],
})
}
} }
"minecraft:blue_ice" => { "minecraft:blue_ice" => {
quote! { ConfiguredFeature::BlueIce(crate::generation::feature::features::blue_ice::BlueIceFeature {}) } quote! { ConfiguredFeature::BlueIce(crate::generation::feature::features::blue_ice::BlueIceFeature {}) }
@ -842,7 +889,22 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
quote! { ConfiguredFeature::CoralClaw(crate::generation::feature::features::coral::coral_claw::CoralClawFeature) } quote! { ConfiguredFeature::CoralClaw(crate::generation::feature::features::coral::coral_claw::CoralClawFeature) }
} }
"minecraft:huge_fungus" => { "minecraft:huge_fungus" => {
quote! { ConfiguredFeature::HugeFungus(crate::generation::feature::features::huge_fungus::HugeFungusFeature {}) } let valid_base_block = value_to_block_state(&config["valid_base_block"]);
let stem_state = value_to_block_state(&config["stem_state"]);
let hat_state = value_to_block_state(&config["hat_state"]);
let decor_state = value_to_block_state(&config["decor_state"]);
let replaceable_blocks = value_to_block_predicate(&config["replaceable_blocks"]);
let planted = config["planted"].as_bool().unwrap_or(false);
quote! {
ConfiguredFeature::HugeFungus(crate::generation::feature::features::huge_fungus::HugeFungusFeature {
valid_base_block: #valid_base_block,
stem_state: #stem_state,
hat_state: #hat_state,
decor_state: #decor_state,
replaceable_blocks: #replaceable_blocks,
planted: #planted,
})
}
} }
"minecraft:weeping_vines" => { "minecraft:weeping_vines" => {
quote! { ConfiguredFeature::WeepingVines(crate::generation::feature::features::weeping_vines::WeepingVinesFeature {}) } quote! { ConfiguredFeature::WeepingVines(crate::generation::feature::features::weeping_vines::WeepingVinesFeature {}) }
@ -860,10 +922,28 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
} }
} }
"minecraft:delta_feature" => { "minecraft:delta_feature" => {
quote! { ConfiguredFeature::DeltaFeature(crate::generation::feature::features::delta_feature::DeltaFeatureFeature {}) } let contents = value_to_block_state(&config["contents"]);
let rim = value_to_block_state(&config["rim"]);
let size = value_to_int_provider(&config["size"]);
let rim_size = value_to_int_provider(&config["rim_size"]);
quote! {
ConfiguredFeature::DeltaFeature(crate::generation::feature::features::delta_feature::DeltaFeatureFeature {
contents: #contents,
rim: #rim,
size: #size,
rim_size: #rim_size,
})
}
} }
"minecraft:fill_layer" => { "minecraft:fill_layer" => {
quote! { ConfiguredFeature::FillLayer(crate::generation::feature::features::fill_layer::FillLayerFeature {}) } let height = config["height"].as_i64().unwrap_or(0) as i32;
let state = value_to_block_state(&config["state"]);
quote! {
ConfiguredFeature::FillLayer(crate::generation::feature::features::fill_layer::FillLayerFeature {
height: #height,
state: #state,
})
}
} }
"minecraft:bonus_chest" => { "minecraft:bonus_chest" => {
quote! { ConfiguredFeature::BonusChest(crate::generation::feature::features::bonus_chest::BonusChestFeature {}) } quote! { ConfiguredFeature::BonusChest(crate::generation::feature::features::bonus_chest::BonusChestFeature {}) }
@ -898,10 +978,31 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
} }
} }
"minecraft:block_pile" => { "minecraft:block_pile" => {
quote! { ConfiguredFeature::BlockPile(crate::generation::feature::features::block_pile::BlockPileFeature {}) } let provider = value_to_block_state_provider(&config["state_provider"]);
quote! {
ConfiguredFeature::BlockPile(crate::generation::feature::features::block_pile::BlockPileFeature {
state_provider: #provider,
})
}
} }
"minecraft:replace_single_block" => { "minecraft:replace_single_block" => {
quote! { ConfiguredFeature::ReplaceSingleBlock(crate::generation::feature::features::replace_single_block::ReplaceSingleBlockFeature {}) } let targets: Vec<TokenStream> = config["targets"]
.as_array()
.map(|arr| {
arr.iter()
.map(|t| {
let rule = value_to_rule_test(&t["target"]);
let state = value_to_block_state(&t["state"]);
quote! { OreTarget { target: #rule, state: #state } }
})
.collect()
})
.unwrap_or_default();
quote! {
ConfiguredFeature::ReplaceSingleBlock(crate::generation::feature::features::replace_single_block::ReplaceSingleBlockFeature {
targets: vec![#(#targets),*],
})
}
} }
"minecraft:void_start_platform" => { "minecraft:void_start_platform" => {
quote! { ConfiguredFeature::VoidStartPlatform(crate::generation::feature::features::void_start_platform::VoidStartPlatformFeature {}) } quote! { ConfiguredFeature::VoidStartPlatform(crate::generation::feature::features::void_start_platform::VoidStartPlatformFeature {}) }
@ -1506,13 +1607,17 @@ fn value_to_tree_decorator(v: &Value) -> TokenStream {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { TreeDecorator::LeaveVine(LeavesVineTreeDecorator { probability: #prob }) } quote! { TreeDecorator::LeaveVine(LeavesVineTreeDecorator { probability: #prob }) }
} }
"minecraft:cocoa" => quote! { TreeDecorator::Cocoa(CocoaTreeDecorator {}) }, "minecraft:cocoa" => {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { TreeDecorator::Cocoa(CocoaTreeDecorator { probability: #prob }) }
}
"minecraft:beehive" => { "minecraft:beehive" => {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { TreeDecorator::Beehive(BeehiveTreeDecorator { probability: #prob }) } quote! { TreeDecorator::Beehive(BeehiveTreeDecorator { probability: #prob }) }
} }
"minecraft:alter_ground" => { "minecraft:alter_ground" => {
quote! { TreeDecorator::AlterGround(AlterGroundTreeDecorator {}) } let provider = value_to_block_state_provider(&v["provider"]);
quote! { TreeDecorator::AlterGround(AlterGroundTreeDecorator { provider: #provider }) }
} }
"minecraft:attached_to_logs" => { "minecraft:attached_to_logs" => {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
@ -1534,7 +1639,29 @@ fn value_to_tree_decorator(v: &Value) -> TokenStream {
} }
} }
"minecraft:attached_to_leaves" => { "minecraft:attached_to_leaves" => {
quote! { TreeDecorator::AttachedToLeaves(AttachedToLeavesTreeDecorator {}) } let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
let exclusion_radius_xz = v["exclusion_radius_xz"].as_i64().unwrap_or(0) as i32;
let exclusion_radius_y = v["exclusion_radius_y"].as_i64().unwrap_or(0) as i32;
let bp = value_to_block_state_provider(&v["block_provider"]);
let required_empty_blocks = v["required_empty_blocks"].as_i64().unwrap_or(1) as i32;
let dirs: Vec<TokenStream> = v["directions"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|d| d.as_str().map(value_to_block_direction))
.collect()
})
.unwrap_or_default();
quote! {
TreeDecorator::AttachedToLeaves(AttachedToLeavesTreeDecorator {
probability: #prob,
exclusion_radius_xz: #exclusion_radius_xz,
exclusion_radius_y: #exclusion_radius_y,
block_provider: #bp,
required_empty_blocks: #required_empty_blocks,
directions: vec![#(#dirs),*],
})
}
} }
"minecraft:place_on_ground" => { "minecraft:place_on_ground" => {
let tries = v["tries"].as_i64().unwrap_or(1) as i32; let tries = v["tries"].as_i64().unwrap_or(1) as i32;
@ -1551,10 +1678,20 @@ fn value_to_tree_decorator(v: &Value) -> TokenStream {
} }
} }
"minecraft:creaking_heart" => { "minecraft:creaking_heart" => {
quote! { TreeDecorator::CreakingHeart(CreakingHeartTreeDecorator {}) } let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { TreeDecorator::CreakingHeart(CreakingHeartTreeDecorator { probability: #prob }) }
} }
"minecraft:pale_moss" => { "minecraft:pale_moss" => {
quote! { TreeDecorator::PaleMoss(PaleMossTreeDecorator {}) } let leaves_probability = v["leaves_probability"].as_f64().unwrap_or(0.0) as f32;
let trunk_probability = v["trunk_probability"].as_f64().unwrap_or(0.0) as f32;
let ground_probability = v["ground_probability"].as_f64().unwrap_or(0.0) as f32;
quote! {
TreeDecorator::PaleMoss(PaleMossTreeDecorator {
leaves_probability: #leaves_probability,
trunk_probability: #trunk_probability,
ground_probability: #ground_probability,
})
}
} }
other => { other => {
let msg = format!("unknown tree decorator: {other}"); let msg = format!("unknown tree decorator: {other}");