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 {
is_air(GenerationCache::get_block_state(self, local_pos))
}
fn get_sea_level(&self) -> i32 {
self.get_center_chunk().get_sea_level()
}
}
impl Cache {

View File

@ -38,8 +38,9 @@ impl BlockStateProvider {
Self::Simple(provider) => Some(provider.get(pos)),
Self::Weighted(provider) => Some(provider.get(random)),
Self::DualNoise(provider) => Some(provider.get(pos)),
Self::Pillar(provider) => Some(provider.get(pos)),
Self::RandomizedInt(_) | Self::Rule(_) => None,
Self::Pillar(provider) => Some(provider.get_with_random(random, pos)),
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::NoiseProvider(provider) => provider.get(pos),
Self::DualNoise(provider) => provider.get(pos),
Self::Pillar(provider) => provider.get(pos),
Self::RandomizedInt(provider) => {
provider.source.get_state_for_world(world, random, pos)
}
Self::Pillar(provider) => provider.get_with_random(random, pos),
Self::RandomizedInt(provider) => provider.get_state_for_world(world, random, pos),
Self::Rule(provider) => {
for rule in &provider.rules {
if rule.if_true.test_world(world, None, &pos) {
@ -86,7 +85,7 @@ impl BlockStateProvider {
Self::Simple(provider) => provider.get(pos),
Self::Weighted(provider) => provider.get(random),
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::Rule(provider) => provider.get(block_registry, chunk, random, pos),
}
@ -170,6 +169,63 @@ pub struct 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>(
&self,
random: &mut RandomGenerator,
@ -177,8 +233,8 @@ impl RandomizedIntBlockStateProvider {
chunk: &T,
block_registry: &dyn WorldPortalExt,
) -> &'static BlockState {
// TODO
self.source.get(random, pos, chunk, block_registry)
let state = self.source.get(random, pos, chunk, block_registry);
self.apply_property(state, random)
}
}
@ -189,7 +245,33 @@ pub struct PillarBlockStateProvider {
impl PillarBlockStateProvider {
#[must_use]
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
}
}

View File

@ -422,21 +422,45 @@ impl ConfiguredFeature {
Self::BonusChest(_feature) => {
BonusChestFeature::generate(chunk, min_y, height, feature_name, random, pos)
}
Self::DeltaFeature(_feature) => {
DeltaFeatureFeature::generate(chunk, min_y, height, feature_name, random, pos)
Self::DeltaFeature(feature) => {
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::LargeDripstone(feature) => feature.generate(chunk, random, pos),
Self::EndGateway(feature) => feature.generate(chunk, pos),
Self::FillLayer(feature) => {
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) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::HugeFungus(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
Self::HugeFungus(feature) => feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
),
Self::HugeRedMushroom(feature) => {
feature.generate(chunk, min_y, height, feature_name, random, pos)
}
@ -452,7 +476,7 @@ impl ConfiguredFeature {
Self::WeepingVines(feature) => {
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,
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
_feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut i = 0;
if chunk.is_air(&pos.0) {
if block_registry.can_place_at(&Block::BAMBOO, Block::BAMBOO.default_state, chunk, &pos)
let mut placed = 0;
if chunk.is_air(&pos.0)
&& block_registry.can_place_at(&Block::BAMBOO, Block::BAMBOO.default_state, chunk, &pos)
{
let height = random.next_bounded_i32(12) + 5;
if random.next_f32() < self.probability {
let rnd = random.next_bounded_i32(4) + 1;
for x in pos.0.x - rnd..pos.0.x + rnd {
for z in pos.0.z - rnd..pos.0.z + rnd {
let block_below =
BlockPos::new(x, chunk.top_block_height_exclusive(x, z) - 1, z);
let block = GenerationCache::get_block_state(chunk, &block_below.0);
if !block.to_block_id().has_tag(tag::Block::MINECRAFT_DIRT) {
continue;
}
chunk.set_block_state(&block_below.0, Block::PODZOL.default_state);
let r = random.next_bounded_i32(4) + 1;
for xx in pos.0.x - r..=pos.0.x + r {
for zz in pos.0.z - r..=pos.0.z + r {
let xd = xx - pos.0.x;
let zd = zz - pos.0.z;
if xd * xd + zd * zd <= r * r {
let podzol_y = chunk.top_block_height_exclusive(xx, zz) - 1;
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);
}
}
}
}
}
let trunk_props = BambooLikeProperties {
age: 1,
leaves: BambooLeaves::None,
stage: 0,
};
let trunk_state = BlockState::from_id(trunk_props.to_state_id(&Block::BAMBOO));
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);
chunk.set_block_state(&bpos.0, trunk_state);
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(
&bpos.0,
BlockState::from_id(props.to_state_id(&Block::BAMBOO)),
);
props.stage = 0;
let final_large_props = BambooLikeProperties {
age: 1,
leaves: BambooLeaves::Large,
stage: 1,
};
chunk.set_block_state(
&bpos.down().0,
BlockState::from_id(props.to_state_id(&Block::BAMBOO)),
BlockState::from_id(final_large_props.to_state_id(&Block::BAMBOO)),
);
props.leaves = BambooLeaves::Small;
let top_large_props = BambooLikeProperties {
age: 1,
leaves: BambooLeaves::Large,
stage: 0,
};
chunk.set_block_state(
&bpos.down().down().0,
BlockState::from_id(props.to_state_id(&Block::BAMBOO)),
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 += 1;
}
i > 0
placed > 0
}
}

View File

@ -6,7 +6,6 @@ use pumpkin_util::{
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 UNCLUSTERED_REACH: i32 = 8;
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 {
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 {

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

View File

@ -1,5 +1,10 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use pumpkin_data::{Block, BlockDirection};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
HeightMap,
math::{position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
@ -11,28 +16,57 @@ impl BonusChestFeature {
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut block_pos = pos;
while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 {
block_pos = block_pos.down();
let chunk_min_x = (pos.0.x >> 4) << 4;
let chunk_min_z = (pos.0.z >> 4) << 4;
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();
if !chunk.is_air(&chest_pos.0) {
return false;
for &x in &x_poses {
for &z in &z_poses {
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);
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);
for dir in BlockDirection::horizontal() {
let torch_pos = chest_pos.add(&dir.to_offset());
let below_torch = torch_pos.add(&Vector3::new(0, -1, 0));
if !chunk.is_air(&below_torch) && chunk.is_air(&torch_pos) {
chunk.set_block_state(&torch_pos, Block::TORCH.default_state);
}
}
chunk.set_block_state(&chest_pos.0, Block::CHEST.default_state);
return true;
}
}
}
let torch_offsets = [(1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)];
for &(dx, dy, dz) in &torch_offsets {
let t_pos = BlockPos::new(chest_pos.0.x + dx, chest_pos.0.y + dy, chest_pos.0.z + dz);
if chunk.is_air(&t_pos.0) {
chunk.set_block_state(&t_pos.0, Block::TORCH.default_state);
}
}
true
false
}
}

View File

@ -16,53 +16,69 @@ impl CoralClawFeature {
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random);
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) {
return false;
}
let direction = BlockDirection::random_horizontal(random);
let i = random.next_bounded_i32(2) + 2;
// TODO: vanilla iterates the first `i` of Util.toShuffledList([direction,
// direction.getClockWise(), direction.getCounterClockWise()], random) — the
// shuffle consumes RNG draws and the opposite of `direction` is never visited.
let directions = BlockDirection::horizontal_worldgen()
.into_iter()
.take(i as usize);
'block0: for direction2 in directions {
let mut pos = pos;
let j = random.next_bounded_i32(2) + 1;
pos = pos.offset(direction2.to_offset());
let claw_dir =
BlockDirection::from_cardinal_direction(BlockDirection::random_horizontal(random));
let n_branches = random.next_bounded_i32(2) + 2;
let mut possible_directions = [
claw_dir,
claw_dir.rotate_clockwise(),
claw_dir.rotate_counter_clockwise(),
];
for i in (1..possible_directions.len()).rev() {
let j = random.next_bounded_i32(i as i32 + 1) as usize;
possible_directions.swap(i, j);
}
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 {
branch_direction = direction;
random.next_bounded_i32(3) + 2
let (segment_direction, inway_length) = if branch_direction == claw_dir {
(claw_dir, random.next_bounded_i32(3) + 2)
} else {
pos = pos.up();
//let _directions = [direction2, BlockDirection::Up];
branch_direction = direction2; // TODO: make this random
random.next_bounded_i32(3) + 5
mut_pos = mut_pos.up();
let seg_dir = if random.next_bool() {
branch_direction
} else {
BlockDirection::Up
};
(seg_dir, random.next_bounded_i32(3) + 3)
};
for _ in 0..j {
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) {
for _ in 0..sideway_length {
if !CoralFeature::generate_coral_piece(
chunk,
block_registry,
random,
block,
mut_pos,
) {
break;
}
pos = pos.offset(branch_direction.to_offset());
mut_pos = mut_pos.offset(segment_direction.to_offset());
}
pos = pos.offset(branch_direction.to_offset());
pos = pos.up();
mut_pos = mut_pos.offset(segment_direction.opposite().to_offset());
mut_pos = mut_pos.up();
for _l in 0..k {
pos = pos.offset(direction.opposite().to_offset());
if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) {
continue 'block0;
for _ in 0..inway_length {
mut_pos = mut_pos.offset(claw_dir.to_offset());
if !CoralFeature::generate_coral_piece(
chunk,
block_registry,
random,
block,
mut_pos,
) {
break;
}
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 pumpkin_data::tag;
use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
@ -10,47 +10,47 @@ use super::CoralFeature;
pub struct CoralMushroomFeature;
impl CoralMushroomFeature {
#[allow(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
_feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
// First lets get a random coral
let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random);
let i = random.next_bounded_i32(3) + 3;
let j = random.next_bounded_i32(3) + 3;
let k = random.next_bounded_i32(3) + 3;
let l = random.next_bounded_i32(3) + 1;
let height = random.next_bounded_i32(3) + 3;
let width = random.next_bounded_i32(3) + 3;
let length = random.next_bounded_i32(3) + 3;
let sink_value = random.next_bounded_i32(3) + 1;
for m in 0..=j {
for n in 0..=i {
for o in 0..=k {
let mut pos = pos;
pos = pos.offset(Vector3::new(pos.0.x + m, pos.0.y + n, pos.0.z + o));
pos = pos.down_height(l);
for x in 0..=width {
for y in 0..=height {
for z in 0..=length {
let cur_pos = BlockPos::new(pos.0.x + x, pos.0.y + y - sink_value, pos.0.z + z);
let condition_a = (m != 0 && m != j) || (n != 0 && n != i);
let condition_b = (o != 0 && o != k) || (n != 0 && n != i);
let condition_c = (m != 0 && m != j) || (o != 0 && o != k);
let condition_d = m == 0 || m == j || n == 0 || n == i || o == 0 || o == k;
let random_check = random.next_f32() < 0.1f32;
let condition_a = (x != 0 && x != width) || (y != 0 && y != height);
let condition_b = (z != 0 && z != length) || (y != 0 && y != height);
let condition_c = (x != 0 && x != width) || (z != 0 && z != length);
let condition_d =
x == 0 || x == width || y == 0 || y == height || z == 0 || z == length;
if !((condition_a && condition_b && condition_c && condition_d)
&& !random_check
&& CoralFeature::generate_coral_piece(
if condition_a
&& condition_b
&& condition_c
&& condition_d
&& random.next_f32() >= 0.1
{
CoralFeature::generate_coral_piece(
chunk,
block_registry,
random,
block,
pos,
))
{}
cur_pos,
);
}
}
}
}

View File

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

View File

@ -24,9 +24,10 @@ impl CoralFeature {
pos: BlockPos,
) -> bool {
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
{
return false;
@ -41,18 +42,22 @@ impl CoralFeature {
Block::from_state_id(block_to_place_state.id),
block_to_place_state,
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 {
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);
let block_state = BlockState::from_id(state_id);
if block_registry.can_place_at(Block::from_state_id(state_id), block_state, chunk, &pos)
{
chunk.set_block_state(&pos.0, block_state);
if block_registry.can_place_at(
Block::from_state_id(state_id),
block_state,
chunk,
&above_pos,
) {
chunk.set_block_state(&above_pos.0, block_state);
}
}
for dir in BlockDirection::horizontal_worldgen() {
@ -68,7 +73,6 @@ impl CoralFeature {
continue;
};
let original_props = &properties.to_props();
// Set the right Axis
let props: Vec<(&str, &str)> = original_props
.iter()
.map(|(key, value)| {

View File

@ -1,12 +1,21 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl};
use pumpkin_data::{BlockDirection, BlockId, BlockState};
use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos},
random::{RandomGenerator, RandomImpl},
};
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 {
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
_min_y: i8,
_height: u16,
@ -14,32 +23,81 @@ impl DeltaFeatureFeature {
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let size = random.next_bounded_i32(3) + 2;
let mut generated = false;
for dx in -size..=size {
for dy in -size..=size {
for dz in -size..=size {
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 =
GenerationCache::get_block_state(chunk, &target.0).to_block_id();
if block_id == Block::NETHERRACK.id
|| block_id == Block::LAVA.id
|| block_id == Block::AIR.id
{
let replacement = if random.next_f32() < 0.6 {
Block::BASALT
let mut any_placed = false;
let spawn_rim = random.next_f64() < 0.9;
let rim_x = if spawn_rim {
self.rim_size.get(random)
} else {
Block::BLACKSTONE
0
};
chunk.set_block_state(&target.0, replacement.default_state);
generated = true;
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 pos_iter in BlockPos::iterate_outwards(pos, radius_x, 0, radius_z) {
if pos_iter.manhattan_distance(pos) > radius_limit {
break;
}
if Self::is_clear(chunk, pos_iter, self.contents) {
if has_rim {
any_placed = true;
chunk.set_block_state(&pos_iter.0, self.rim);
}
let pos_offset = pos_iter.add(rim_x, 0, rim_z);
if Self::is_clear(chunk, pos_offset, self.contents) {
any_placed = true;
chunk.set_block_state(&pos_offset.0, self.contents);
}
}
}
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;
}
}
generated
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_nbt::compound::NbtCompound;
use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
random::RandomGenerator,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::{noise::WATER_BLOCK, proto_chunk::GenerationCache};
@ -19,8 +20,8 @@ impl DesertWellFeature {
chunk: &mut T,
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
_random: &mut RandomGenerator,
_feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
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 {
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 {
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
}
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 pumpkin_data::Block;
use pumpkin_data::BlockId;
use pumpkin_data::block_properties::{
PointedDripstoneLikeProperties, SpeleothemThickness, VerticalDirection,
};
use pumpkin_data::tag;
use pumpkin_data::{Block, BlockDirection, BlockId, BlockState};
use pumpkin_util::math::position::BlockPos;
pub mod cluster;
@ -20,3 +22,55 @@ pub(super) fn gen_dripstone<T: GenerationCache>(chunk: &mut T, pos: BlockPos) ->
}
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,
) -> bool {
if let Some(dir) = Self::get_direction(chunk, pos, random) {
let pos = pos.offset(dir.opposite().to_offset());
self.gen_dripstone_blocks(chunk, pos, random);
// TODO
let root_pos = pos.offset(dir.opposite().to_offset());
self.gen_dripstone_blocks(chunk, root_pos, random);
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;
}
false

View File

@ -44,7 +44,6 @@ impl EndPodiumFeature {
let mut oy = origin.0.y;
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 {
return false;
}
@ -76,7 +75,7 @@ impl EndPodiumFeature {
if y < oy {
if closer_than_2_5 {
chunk.set_block_state(&target, Block::BEDROCK.default_state);
} else if self.active {
} else {
chunk.set_block_state(&target, Block::END_STONE.default_state);
}
} else if y > oy {

View File

@ -182,7 +182,6 @@ impl EndSpikeFeature {
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(
&pumpkin_util::math::vector3::Vector3::new(
spike.center_x,
@ -200,12 +199,10 @@ impl EndSpikeFeature {
Block::FIRE.default_state,
);
// Iron-bar cage for guarded spikes: 5x5 walls + open top frame at dy=3.
if spike.guarded {
for dy in 0i32..=3 {
for dx 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 z_wall_present = dz.abs() == 2;
let on_top = dy == 3;
@ -213,15 +210,14 @@ impl EndSpikeFeature {
continue;
}
// Connectivity rules
let x_edge = x_wall_present || on_top;
let z_edge = z_wall_present || on_top;
let mut props = OakFenceLikeProperties::default(&Block::IRON_BARS);
props.north = x_edge && dz != 2;
props.south = x_edge && dz != -2;
props.west = z_edge && dx != 2;
props.east = z_edge && dx != -2;
props.north = x_edge && dz != -2;
props.south = x_edge && dz != 2;
props.west = z_edge && dx != -2;
props.east = z_edge && dx != 2;
let bar_state = BlockState::from_id(props.to_state_id(&Block::IRON_BARS));
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 trunk_provider: BlockStateProvider,
pub log_length: IntProvider,
pub stump_decorators: Vec<TreeDecorator>,
pub log_decorators: Vec<TreeDecorator>,
}
impl FallenTreeFeature {
pub const fn generate(
_chunk: &mut ProtoChunk,
#[allow(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, // This placed feature
_random: &mut RandomGenerator,
_pos: BlockPos,
_feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> 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) {
// // chunk.set_block_state(
// // pos.0.x,
// // pos.0.y,
// // pos.0.z,
// // self.trunk_provider.get(random, pos, chunk),
// // );
// }
fn set_ground_height_for_fallen_log_start_pos<T: GenerationCache>(
chunk: &T,
log_start_pos: &mut BlockPos,
) {
*log_start_pos = log_start_pos.up();
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 crate::generation::proto_chunk::GenerationCache;
pub struct FillLayerFeature;
pub struct FillLayerFeature {
pub height: i32,
pub state: &'static BlockState,
}
impl FillLayerFeature {
#[allow(clippy::unused_self)]
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
_min_y: i8,
min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
for dx in -1..=1 {
for dz in -1..=1 {
let target = BlockPos::new(pos.0.x + dx, pos.0.y, pos.0.z + dz);
chunk.set_block_state(&target.0, Block::SNOW.default_state);
let y = min_y as i32 + self.height;
for dx in 0..16 {
for dz in 0..16 {
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

View File

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

View File

@ -1,56 +1,246 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl};
use pumpkin_data::{Block, BlockState};
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 {
#[allow(clippy::unused_self)]
#[allow(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 {
let height = random.next_bounded_i32(4) + 6;
let is_warped = random.next_bool();
let stem_state = if is_warped {
Block::WARPED_STEM.default_state
} 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 below = pos.down();
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;
if below_block != valid_base_id {
return false;
}
let cap_y = pos.0.y + height - 2;
for dy in 0..=3 {
let radius = if dy == 0 || dy == 3 { 1 } else { 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);
let block_state = if (dx == 0 || dz == 0) && dy == 1 && random.next_f32() < 0.3
{
Block::SHROOMLIGHT.default_state
} else {
wart_state
};
chunk.set_block_state(&cap_pos.0, block_state);
}
let mut total_height = random.next_inbetween_i32(4, 13);
if random.next_bounded_i32(12) == 0 {
total_height *= 2;
}
if !self.planted && pos.0.y + total_height + 1 >= chunk.top_y() as i32 {
return false;
}
let is_huge = !self.planted && random.next_f32() < 0.06;
chunk.set_block_state(&pos.0, Block::AIR.default_state);
self.place_stem(chunk, block_registry, random, pos, total_height, is_huge);
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);
}
}
}
}
}
}
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};
const SEA_LEVEL: i32 = 63; // TODO: use getSeaLevel() instead of hardcoding
pub struct IcebergFeature {
pub main_block: BlockStateCodec,
}
@ -25,7 +23,7 @@ impl IcebergFeature {
random: &mut RandomGenerator,
pos: BlockPos,
) -> 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 snow_on_top = random.next_f64() > 0.7;

View File

@ -1,10 +1,11 @@
use crate::generation::proto_chunk::GenerationCache;
use pumpkin_data::{Block, BlockState};
use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::KelpLikeProperties, tag};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
pub struct KelpFeature;
impl KelpFeature {
@ -19,67 +20,66 @@ impl KelpFeature {
) -> bool {
let mut placed = 0;
// Start on the ocean floor
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);
// Must be in 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()
== Block::WATER
&& GenerationCache::get_block_state(
chunk,
&BlockPos::new(kelp_pos.0.x, kelp_pos.0.y + 1, kelp_pos.0.z).0,
)
.to_block_id()
&& GenerationCache::get_block_state(chunk, &above.0).to_block_id()
== Block::WATER
&& can_survive
{
// If this is the last iteration place the kelp head with age
if h == height_rand {
let age = random.next_bounded_i32(4) + 20;
// Clamp in case it goes past available states
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(&kelp_pos.0, state);
if h == height {
let age = (random.next_bounded_i32(4) + 20) as u8;
let mut props = KelpLikeProperties::default(&Block::KELP);
props.age = age;
let state_id = props.to_state_id(&Block::KELP);
chunk.set_block_state(&kelp_pos.0, BlockState::from_id(state_id));
placed += 1;
} else {
// Place kelp plant body
let state_id = Block::KELP_PLANT.default_state.id;
let state = BlockState::from_id(state_id);
chunk.set_block_state(&kelp_pos.0, state);
chunk.set_block_state(&kelp_pos.0, Block::KELP_PLANT.default_state);
}
} else if h > 0 {
// Can't place further but we have already placed at least one segment, try to put head below
let below = BlockPos::new(kelp_pos.0.x, kelp_pos.0.y - 1, kelp_pos.0.z);
// Check head block can survive and that below-below is not a kelp head
if GenerationCache::get_block_state(chunk, &below.0).to_block_id()
== Block::WATER
&& GenerationCache::get_block_state(
chunk,
&BlockPos::new(below.0.x, below.0.y - 1, below.0.z).0,
)
.to_block_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);
let below = kelp_pos.down();
let below_below = below.down();
let below_below_id =
GenerationCache::get_block_state(chunk, &below_below.0).to_block_id();
if Self::can_survive(chunk, below) && below_below_id != Block::KELP.id {
let age = (random.next_bounded_i32(4) + 20) as u8;
let mut props = KelpLikeProperties::default(&Block::KELP);
props.age = age;
let state_id = props.to_state_id(&Block::KELP);
chunk.set_block_state(&below.0, BlockState::from_id(state_id));
placed += 1;
}
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
}
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;
/// The three mob types that can appear in a dungeon spawner.
///
/// Skeleton 25%, Zombie 50%, Spider 25%.
const DUNGEON_MOBS: [&str; 4] = [
"minecraft:skeleton",
"minecraft:zombie",
@ -20,7 +17,6 @@ const DUNGEON_MOBS: [&str; 4] = [
pub struct DungeonFeature;
impl DungeonFeature {
/// Generate a monster room (dungeon) at `pos`.
#[expect(clippy::too_many_lines)]
pub fn generate<T: GenerationCache>(
chunk: &mut T,
@ -46,13 +42,12 @@ impl DungeonFeature {
let solid = !chunk.is_air(&check_pos);
if dy == -1 && !solid {
return false; // Floor has a gap
return false;
}
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;
if on_wall
&& dy == 0
@ -94,18 +89,15 @@ impl DungeonFeature {
chunk.set_block_state(&wall_pos, Block::CAVE_AIR.default_state);
} else if cur_solid && !is_chest {
if dy == -1 && random.next_bounded_i32(4) != 0 {
// 75% mossy cobblestone
chunk.set_block_state(
&wall_pos,
Block::MOSSY_COBBLESTONE.default_state,
);
} else {
// 25% cobblestone (or 100% if it's not the floor)
chunk.set_block_state(&wall_pos, Block::COBBLESTONE.default_state);
}
}
} else {
// Clear interior, but preserve chests and spawners
let state = GenerationCache::get_block_state(chunk, &wall_pos);
let is_chest = state.to_block() == &Block::CHEST;
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..3 {
let cx = pos.0.x + random.next_bounded_i32(xr * 2 + 1) - xr;
@ -129,20 +120,17 @@ impl DungeonFeature {
continue;
}
// Count solid horizontal neighbours
let wall_count = BlockDirection::horizontal()
.iter()
.filter(|d| !chunk.is_air(&chest_pos.add(&d.to_offset())))
.count();
if wall_count == 1 {
// Orient the chest toward its single solid neighbor.
let facing_dir = BlockDirection::horizontal()
.iter()
.find(|d| !chunk.is_air(&chest_pos.add(&d.to_offset())))
.copied();
// Build the chest state: face away from the wall.
let chest_state = facing_dir.map_or(Block::CHEST.default_state, |dir| {
use pumpkin_data::block_properties::ChestLikeProperties;
let mut props = ChestLikeProperties::default(&Block::CHEST);
@ -153,7 +141,6 @@ impl DungeonFeature {
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 mut chest_nbt = NbtCompound::new();
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];
chunk.set_block_state(&pos.0, Block::SPAWNER.default_state);

View File

@ -1,9 +1,20 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::GlowLichenLikeProperties};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
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 {
#[allow(clippy::unused_self)]
@ -13,27 +24,133 @@ impl MultifaceGrowthFeature {
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut generated = false;
let directions = [
(1, 0, 0),
(-1, 0, 0),
(0, 1, 0),
(0, -1, 0),
(0, 0, 1),
(0, 0, -1),
];
for &(dx, dy, dz) in &directions {
let neighbor = BlockPos::new(pos.0.x + dx, pos.0.y + dy, pos.0.z + dz);
let state = GenerationCache::get_block_state(chunk, &neighbor.0);
if state.to_block_id() != Block::AIR.id && chunk.is_air(&pos.0) {
chunk.set_block_state(&pos.0, Block::GLOW_LICHEN.default_state);
generated = true;
if !Self::is_air_or_water(chunk, pos) {
return false;
}
let shuffled_dirs = self.get_shuffled_directions(random);
if self.place_growth_if_possible(chunk, pos, random, &shuffled_dirs) {
return true;
}
for search_direction in shuffled_dirs {
let placement_directions =
self.get_shuffled_directions_except(random, search_direction.opposite());
let mut cur_pos = pos;
for _ in 0..self.search_range {
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;
}
}
generated
if self.place_growth_if_possible(chunk, cur_pos, random, &placement_directions) {
return true;
}
}
}
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,14 +50,13 @@ impl OreFeature {
let q = 2 * (g.ceil() as i32 + i);
let r = 2 * (2 + i);
for _ in n..=(n + q) {
for _ in p..=(p + q) {
if o > chunk.ocean_floor_height_exclusive(pos.0.x, pos.0.z) {
continue;
}
for xprobe in n..=(n + q) {
for zprobe in p..=(p + q) {
if o <= chunk.ocean_floor_height_exclusive(xprobe, zprobe) {
return self.generate_vein_part(chunk, random, d, e, h, j, l, m, n, o, p, q, r);
}
}
}
false
}

View File

@ -1,22 +1,29 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator};
use crate::generation::feature::features::ore::OreTarget;
use crate::generation::proto_chunk::GenerationCache;
pub struct ReplaceSingleBlockFeature;
pub struct ReplaceSingleBlockFeature {
pub targets: Vec<OreTarget>,
}
impl ReplaceSingleBlockFeature {
#[allow(clippy::unused_self)]
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature,
_random: &mut RandomGenerator,
random: &mut RandomGenerator,
pos: BlockPos,
) -> 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
}
}

View File

@ -1,3 +1,4 @@
use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
@ -36,52 +37,156 @@ impl RootSystemFeature {
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
if !self.allowed_tree_position.test(block_registry, chunk, &pos) {
if !chunk.is_air(&pos.0) {
return false;
}
if !self.can_place_tree(chunk, pos) {
return false;
}
if !self.feature.generate(
let mut working_pos = pos;
if self.place_dirt_and_tree(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
&mut working_pos,
pos,
) {
return false;
}
self.place_roots(chunk, block_registry, random, pos);
}
true
}
fn can_place_tree<T: GenerationCache>(&self, chunk: &T, pos: BlockPos) -> bool {
let mut mutable_pos = pos;
fn space_for_tree<T: GenerationCache>(&self, chunk: &T, pos: BlockPos) -> bool {
let mut column_up = pos;
for i in 1..=self.required_vertical_space_for_tree {
mutable_pos = mutable_pos.add(0, 1, 0);
if !self.is_allowed_tree_space(chunk, mutable_pos, i) {
column_up = column_up.up();
let state = GenerationCache::get_block_state(chunk, &column_up.0);
if !self.is_allowed_tree_space(state.to_block_id(), i) {
return false;
}
}
true
}
fn is_allowed_tree_space<T: GenerationCache>(
fn is_allowed_tree_space(
&self,
chunk: &T,
pos: BlockPos,
vertical_space: i32,
block_id: pumpkin_data::BlockId,
blocks_above_origin: i32,
) -> bool {
if chunk.is_air(&pos.0) {
if block_id == Block::AIR.id {
return true;
}
// TODO: check for water.
vertical_space <= self.allowed_vertical_water_for_tree
let blocks_above_ground = blocks_above_origin + 1;
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>(
@ -91,92 +196,29 @@ impl RootSystemFeature {
random: &mut RandomGenerator,
pos: BlockPos,
) {
self.place_rooted_dirt(chunk, block_registry, random, pos);
self.place_hanging_roots(chunk, block_registry, random, pos);
}
let root_radius = self.hanging_root_radius.max(1);
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 {
let mutable_pos = pos.add(
random.next_bounded_i32(self.hanging_root_radius.max(1))
- random.next_bounded_i32(self.hanging_root_radius.max(1)),
random.next_bounded_i32(self.hanging_roots_vertical_span.max(1))
- 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)),
let working_pos = pos.add(
random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius),
random.next_bounded_i32(vertical_span) - random.next_bounded_i32(vertical_span),
random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius),
);
if chunk.is_air(&mutable_pos.0) {
let state = self.hanging_root_state_provider.get_with_context(
block_registry,
chunk,
if chunk.is_air(&working_pos.0) {
let above = working_pos.up();
let above_state = GenerationCache::get_block_state(chunk, &above.0);
if above_state.to_state().is_side_solid(BlockDirection::Down) {
chunk.set_block_state(
&working_pos.0,
self.hanging_root_state_provider.get(
random,
mutable_pos,
working_pos,
chunk,
block_registry,
),
);
// In vanilla, it checks if it can survive and if the block above is sturdy.
// For now, let's just check if the block above is NOT air.
let above = mutable_pos.add(0, 1, 0);
if !chunk.is_air(&above.0) {
chunk.set_block_state(&mutable_pos.0, state);
}
}
}

View File

@ -27,12 +27,11 @@ impl ScatteredOreFeature {
pos: BlockPos,
) -> bool {
let count = random.next_bounded_i32(self.size + 1);
let mut placed = false;
for _ in 0..count {
let offset_x = self.get_random_offset(random);
let offset_y = self.get_random_offset(random);
let offset_z = self.get_random_offset(random);
for i in 0..count {
let max_dist = i.min(7);
let offset_x = Self::get_random_offset(random, max_dist);
let offset_y = Self::get_random_offset(random, max_dist);
let offset_z = Self::get_random_offset(random, max_dist);
let target_pos = pos.add(offset_x, offset_y, offset_z);
@ -52,18 +51,17 @@ impl ScatteredOreFeature {
&target_pos,
) {
chunk.set_block_state(&target_pos.0, target.state);
placed = true;
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 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, BlockState, block_properties::SeaPickleLikeProperties};
use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::SeaPickleLikeProperties};
use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos},
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
pub struct SeaPickleFeature {
pub count: IntProvider,
}
@ -15,28 +16,37 @@ impl SeaPickleFeature {
chunk: &mut T,
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
_feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut times = 0;
let mut placed = 0;
let count = self.count.get(random);
for _ in 0..count {
let x = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let z = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z);
if GenerationCache::get_block_state(chunk, &pos.0).to_block_id() != Block::WATER {
continue;
}
let pickle_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
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 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
let state_id = props.to_state_id(&Block::SEA_PICKLE);
chunk.set_block_state(&pickle_pos.0, BlockState::from_id(state_id));
placed += 1;
}
}
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::{
Block, BlockState,
Block, BlockDirection, BlockState,
block_properties::{DoubleBlockHalf, TallSeagrassLikeProperties},
tag,
};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
pub struct SeagrassFeature {
pub probability: f32,
}
@ -18,34 +20,51 @@ impl SeagrassFeature {
chunk: &mut T,
_min_y: i8,
_height: u16,
_feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature
_feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut placed_any = false;
let x = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let z = random.next_bounded_i32(8) - random.next_bounded_i32(8);
let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z);
let top_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 tall {
let tall_pos = top_pos.up();
if GenerationCache::get_block_state(chunk, &tall_pos.0).to_block_id()
== Block::WATER
let grass_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z);
if GenerationCache::get_block_state(chunk, &grass_pos.0).to_block_id() == Block::WATER
&& Self::can_survive(chunk, grass_pos)
{
let mut props = TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS);
props.half = DoubleBlockHalf::Upper;
chunk.set_block_state(&top_pos.0, Block::TALL_SEAGRASS.default_state);
chunk.set_block_state(
&tall_pos.0,
BlockState::from_id(props.to_state_id(&Block::TALL_SEAGRASS)),
);
let is_tall = random.next_f64() < self.probability as f64;
if is_tall {
let above = grass_pos.up();
if GenerationCache::get_block_state(chunk, &above.0).to_block_id() == Block::WATER {
let mut upper_props =
TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS);
upper_props.half = DoubleBlockHalf::Upper;
let upper_state =
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 {
chunk.set_block_state(&top_pos.0, Block::SEAGRASS.default_state);
}
return true;
}
false
chunk.set_block_state(&grass_pos.0, Block::SEAGRASS.default_state);
placed_any = true;
}
}
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;
}
// TODO: check things..
chunk.set_block_state(&pos.0, state);
// TODO: schedule tick when needed
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,
log_positions: &[BlockPos],
) {
// TODO: shuffle
for pos in log_positions {
// TODO: random
let pos = pos.offset(self.directions[0].to_offset());
if random.next_f32() > self.probability
|| !GenerationCache::get_block_state(chunk, &pos.0)
.to_state()
.is_air()
{
continue;
if self.directions.is_empty() || log_positions.is_empty() {
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 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(
&pos.0,
self.block_provider.get(random, pos, chunk, block_registry),
&placement_pos.0,
self.block_provider
.get(random, placement_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 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);
}
Self::LeaveVine(decorator) => decorator.generate(chunk, random, foliage_positions),
Self::PaleMoss(_decorator) => {}
Self::CreakingHeart(_decorator) => {}
Self::Cocoa(_decorator) => {}
Self::Beehive(_decorator) => {}
Self::AlterGround(_decorator) => {}
Self::PaleMoss(decorator) => {
decorator.generate(chunk, random, log_positions, foliage_positions);
}
Self::CreakingHeart(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) => {
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) => {
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,
) {
let state = GenerationCache::get_block_state(chunk, &pos.0);
let up_pos = pos.up();
let up_state = GenerationCache::get_block_state(chunk, &up_pos.0);
// TODO
if (up_state.to_state().is_air() || up_state.to_block_id() == Block::VINE)
&& state.to_state().is_full_cube()
&& !state.to_block_id().has_tag(MINECRAFT_LEAVES)
// TODO: using heightmap seems not to work
let ground_pos = pos.down();
let ground_state = GenerationCache::get_block_state(chunk, &ground_pos.0);
if (state.to_state().is_air() || state.to_block_id() == Block::VINE)
&& ground_state.to_state().is_full_cube()
&& !ground_state.to_block_id().has_tag(MINECRAFT_LEAVES)
{
chunk.set_block_state(
&up_pos.0,
&pos.0,
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::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
@ -22,34 +22,31 @@ impl TwistingVinesFeature {
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
if Self::is_invalid_location(chunk, &pos) {
if Self::is_invalid_placement_location(chunk, pos) {
return false;
}
let mut placed = false;
for _ in 0..self.spread_width * self.spread_width {
let offset_x = random.next_bounded_i32(self.spread_width)
- random.next_bounded_i32(self.spread_width);
let offset_y = random.next_bounded_i32(self.spread_height)
- 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 offset_x = random.next_inbetween_i32(-self.spread_width, self.spread_width);
let offset_y = random.next_inbetween_i32(-self.spread_height, self.spread_height);
let offset_z = random.next_inbetween_i32(-self.spread_width, 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)
&& !Self::is_invalid_location(chunk, &mutable_pos)
if Self::find_first_air_block_above_ground(chunk, &mut place_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 {
height *= 2;
vine_height *= 2;
}
if random.next_bounded_i32(10) == 0 {
height = 1;
if random.next_bounded_i32(5) == 0 {
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;
}
}
@ -57,75 +54,59 @@ impl TwistingVinesFeature {
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,
random: &mut RandomGenerator,
pos: &BlockPos,
height: i32,
mut place_pos: BlockPos,
total_height: i32,
min_age: i32,
max_age: i32,
) {
let mut current_pos = *pos;
for i in 0..height {
if !GenerationCache::get_block_state(chunk, &current_pos.0)
.to_state()
.is_air()
{
for height in 1..=total_height {
if chunk.is_air(&place_pos.0) {
if height == total_height || !chunk.is_air(&place_pos.up().0) {
let age = random.next_inbetween_i32(min_age, max_age);
let mut props = KelpLikeProperties::default(&Block::TWISTING_VINES);
props.age = age as u8;
let state_id = props.to_state_id(&Block::TWISTING_VINES);
chunk.set_block_state(&place_pos.0, BlockState::from_id(state_id));
break;
}
if i == height - 1
|| !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(&place_pos.0, Block::TWISTING_VINES_PLANT.default_state);
}
chunk.set_block_state(&current_pos.0, Block::TWISTING_VINES_PLANT.default_state);
current_pos = current_pos.up();
place_pos = place_pos.up();
}
}
fn is_invalid_location<T: GenerationCache>(chunk: &T, pos: &BlockPos) -> bool {
if !GenerationCache::get_block_state(chunk, &pos.0)
.to_state()
.is_air()
{
fn is_invalid_placement_location<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
if !chunk.is_air(&pos.0) {
return true;
}
let block_below = GenerationCache::get_block_state(chunk, &pos.down().0).to_state();
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
}
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
let below = pos.down();
let below_id = GenerationCache::get_block_state(chunk, &below.0).to_block_id();
below_id != Block::NETHERRACK.id
&& below_id != Block::WARPED_NYLIUM.id
&& below_id != Block::WARPED_WART_BLOCK.id
}
}

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 crate::generation::proto_chunk::GenerationCache;
@ -19,17 +19,19 @@ impl VinesFeature {
if !chunk.is_air(&pos.0) {
return false;
}
for dir in BlockDirection::all() {
// TODO
if dir == BlockDirection::Down
|| !GenerationCache::get_block_state(chunk, &pos.offset(dir.to_offset()).0)
.to_state()
.is_full_cube()
{
if dir == BlockDirection::Down {
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.east = dir == BlockDirection::East;
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)));
return true;
}
false
}
}

View File

@ -1,12 +1,14 @@
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;
pub struct VoidStartPlatformFeature;
impl VoidStartPlatformFeature {
#[allow(clippy::unused_self)]
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
@ -16,12 +18,33 @@ impl VoidStartPlatformFeature {
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
for dx in -1..=1 {
for dz in -1..=1 {
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);
let chunk_x = pos.0.x >> 4;
let chunk_z = pos.0.z >> 4;
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
}
}

View File

@ -1,5 +1,8 @@
use pumpkin_data::Block;
use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl};
use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::KelpLikeProperties};
use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
@ -16,21 +19,109 @@ impl WeepingVinesFeature {
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let ceiling_pos = pos.up();
let ceiling_block = GenerationCache::get_block_state(chunk, &ceiling_pos.0).to_block_id();
if ceiling_block == Block::AIR.id {
if !chunk.is_air(&pos.0) {
return false;
}
let length = random.next_bounded_i32(4) + 3;
for i in 0..length {
let target = BlockPos::new(pos.0.x, pos.0.y - i, pos.0.z);
if chunk.is_air(&target.0) {
chunk.set_block_state(&target.0, Block::WEEPING_VINES.default_state);
} else {
let above = 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 {
return false;
}
Self::place_roof_nether_wart(chunk, random, pos);
Self::place_roof_weeping_vines(chunk, random, pos);
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;
}
}
true
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_z: i32,
) -> Option<&crate::generation::blender::blending_data::BlendingData>;
fn get_sea_level(&self) -> i32 {
63
}
}
const AIR_BLOCK: Block = Block::AIR;
@ -184,6 +187,11 @@ impl TerrainCache {
}
impl ProtoChunk {
#[must_use]
pub const fn get_sea_level(&self) -> i32 {
63
}
#[cfg(test)]
pub(crate) fn has_structure(&self, key: StructureKeys) -> bool {
self.structure_starts.contains_key(&key)
@ -1717,4 +1725,7 @@ impl GenerationCache for ProtoChunk {
) -> Option<&crate::generation::blender::blending_data::BlendingData> {
None
}
fn get_sea_level(&self) -> i32 {
Self::get_sea_level(self)
}
}

View File

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

View File

@ -1,25 +1,22 @@
use std::sync::Arc;
use pumpkin_data::{Block, BlockDirection, BlockId};
use pumpkin_data::Block;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
HeightMap,
math::{block_box::BlockBox, position::BlockPos},
BlockDirection, HeightMap,
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand},
};
use crate::{
ProtoChunk,
generation::{
positions::chunk_pos::{get_center_x, get_center_z},
structure::{
generation::structure::{
piece::StructurePieceType,
structures::{
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
StructurePiecesCollector, StructurePosition, WorldPortalExt,
},
},
},
};
pub struct BuriedTreasureGenerator;
@ -29,13 +26,12 @@ impl StructureGenerator for BuriedTreasureGenerator {
&self,
context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> {
let x = get_center_x(context.chunk_x);
let z = get_center_z(context.chunk_z);
let x = (context.chunk_x << 4) + 9;
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();
collector.add_piece(Box::new(BuriedTreasurePiece {
piece: StructurePiece::new(StructurePieceType::BuriedTreasure, bounding_box, 0),
}));
@ -55,88 +51,6 @@ impl StructurePieceBase for BuriedTreasurePiece {
fn as_any(&self) -> &dyn std::any::Any {
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 {
&self.piece
@ -145,10 +59,88 @@ impl StructurePieceBase for BuriedTreasurePiece {
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&mut self.piece
}
fn place(
&mut self,
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);
}
}
}
impl BuriedTreasurePiece {
fn is_liquid(id: BlockId) -> bool {
id == BlockId::WATER || id == BlockId::LAVA
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
}
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
.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)]
@ -119,11 +129,12 @@ impl DesertPyramidPiece {
x1: i32,
y1: i32,
z1: i32,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) {
for y in y0..=y1 {
for x in x0..=x1 {
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,
x1: i32,
z1: i32,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) {
for x in x0..=x1 {
for z in z0..=z1 {
self.place_collapsed_roof_piece(chunk, bb, random, x, y0, z);
}
}
// TODO: Pick a random collapsed roof position for sus sand placement in
// the structure-level afterPlace pass when brushable support exists.
let rx = x0 + random.next_bounded_i32(x1 - x0 + 1);
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(
@ -205,8 +219,36 @@ impl DesertPyramidPiece {
}
fn add_cellar(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) {
self.add_cellar_stairs(chunk, bb, random);
self.add_cellar_room(chunk, bb, random);
let mut potential_positions = Vec::new();
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(
@ -214,6 +256,7 @@ impl DesertPyramidPiece {
chunk: &mut ProtoChunk,
bb: &BlockBox,
random: &mut RandomGenerator,
potential_positions: &mut Vec<pumpkin_util::math::vector3::Vector3<i32>>,
) {
let west_stairs = Self::sandstone_stairs(HorizontalFacing::West);
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 variant = random.next_bool();
self.piece.add_block(chunk, sand, x - 4, y + 4, z + 4, bb);
self.piece.add_block(chunk, sand, x - 3, y + 4, z + 4, bb);
self.piece.add_block(chunk, sand, x - 2, y + 4, z + 4, bb);
self.piece.add_block(chunk, sand, x - 1, y + 4, z + 4, bb);
self.piece.add_block(chunk, sand, x, y + 4, z + 4, bb);
self.piece.add_block(chunk, sand, x - 2, y + 3, z + 4, bb);
self.place_sand(chunk, bb, x - 4, y + 4, z + 4, potential_positions);
self.place_sand(chunk, bb, x - 3, y + 4, z + 4, potential_positions);
self.place_sand(chunk, bb, x - 2, y + 4, z + 4, potential_positions);
self.place_sand(chunk, bb, x - 1, y + 4, z + 4, potential_positions);
self.place_sand(chunk, bb, x, y + 4, z + 4, potential_positions);
self.place_sand(chunk, bb, x - 2, y + 3, z + 4, potential_positions);
self.piece.add_block(
chunk,
if variant { sand } else { sandstone },
@ -247,13 +290,19 @@ impl DesertPyramidPiece {
z + 4,
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, sand, x, y + 1, z + 4, bb);
self.place_sand(chunk, bb, x, y + 1, z + 4, potential_positions);
}
#[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 cut = Block::CUT_SANDSTONE.default_state;
let chiseled = Block::CHISELED_SANDSTONE.default_state;
@ -416,8 +465,28 @@ impl DesertPyramidPiece {
cut,
);
self.place_sand_box(chunk, bb, x - 2, y + 1, z - 2, x + 2, y + 3, z + 2);
self.place_collapsed_roof(chunk, bb, random, x - 2, y + 4, z - 2, x + 2, z + 2);
self.place_sand_box(
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, 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 + 3, y, z, bb);
self.place_sand(chunk, bb, x + 3, y + 1, z);
self.place_sand(chunk, bb, x + 3, y + 2, z);
self.place_sand(chunk, bb, x + 3, y + 1, z, potential_positions);
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, chiseled, x + 4, y + 2, 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 + 2, z);
self.place_sand(chunk, bb, x - 3, y + 1, z, potential_positions);
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, chiseled, x - 4, y + 2, z, 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 + 2, z + 3);
self.place_sand(chunk, bb, x, y + 1, z + 3, potential_positions);
self.place_sand(chunk, bb, x, y + 2, z + 3, potential_positions);
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 + 2, z - 3);
self.place_sand(chunk, bb, x, y + 1, z - 3, potential_positions);
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, 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::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::RandomImpl;
use pumpkin_util::random::{RandomDeriverImpl, RandomImpl};
use serde::Deserialize;
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() {
structure

View File

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

View File

@ -74,7 +74,6 @@ pub trait StructurePieceBase: Send + Sync {
&self,
_start: &StructurePiece,
_random: &mut RandomGenerator,
// TODO: this is only for Stronghold and should not be here
_weights: &mut Vec<crate::generation::structure::structures::stronghold::PieceWeight>,
_last_piece_type: &mut Option<StrongholdPieceType>,
_has_portal_room: &mut bool,
@ -388,15 +387,16 @@ impl StructurePiece {
}
}
#[must_use]
pub const fn is_under_sea_level(
&self,
chunk: &mut ProtoChunk,
chunk: &ProtoChunk,
x: i32,
y: i32,
z: i32,
box_limit: &BlockBox,
) -> 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) {
return false;
@ -503,6 +503,43 @@ impl StructurePiece {
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 {
@ -625,6 +662,24 @@ impl StructurePiecesCollector {
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 {
if let Some(bbox) = self.cached_box {
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, Block::LAVA.default_state, 6, 5, 6, &bb);
// Schedule lava fluid tick
// TODO
// let lava_pos = p.offset_pos(6, 5, 6);
// if bb.contains_pos(&lava_pos) {
// chunk.schedule_fluid_tick(&lava_pos, 0);
// }
let lava_pos = p.offset_pos(6, 5, 6);
if bb.contains_pos(&lava_pos) {
chunk.schedule_fluid_tick(
lava_pos.x,
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.move_inside_heights(&mut random, 48, 70);
Some(StructurePosition {
start_pos: BlockPos::new(
section_coords::section_to_block(context.chunk_x),

View File

@ -1,43 +1,28 @@
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::{
math::{block_box::BlockBox, position::BlockPos},
random::{RandomGenerator, RandomImpl},
HeightMap,
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand},
};
use crate::{
ProtoChunk,
generation::{
positions::chunk_pos::{get_center_x, get_center_z},
structure::{
generation::structure::{
piece::StructurePieceType,
structures::{
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] = &[
"underwater_ruin/warm_1",
"underwater_ruin/warm_2",
@ -47,8 +32,67 @@ const WARM_RUINS: &[&str] = &[
"underwater_ruin/warm_6",
"underwater_ruin/warm_7",
"underwater_ruin/warm_8",
];
const BIG_WARM_RUINS: &[&str] = &[
"underwater_ruin/big_warm_4",
"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 {
@ -60,81 +104,566 @@ impl StructureGenerator for OceanRuinGenerator {
&self,
mut context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> {
let chunk_center_x = get_center_x(context.chunk_x);
let chunk_center_z = get_center_z(context.chunk_z);
let start_x = context.chunk_x << 4;
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 = Rotation::from_index(rotation_idx);
let pool = if self.is_warm { WARM_RUINS } else { COLD_RUINS };
let template_idx = context.random.next_bounded_i32(pool.len() as i32) as usize;
let template_name = pool[template_idx];
let template = get_template(template_name)?;
let size = template.size;
let bounding_box = BlockBox::new(
chunk_center_x - size.x / 2,
context.min_y,
chunk_center_z - size.z / 2,
chunk_center_x + size.x / 2,
256,
chunk_center_z + size.z / 2,
let mut collector = StructurePiecesCollector::default();
add_pieces(
&mut collector,
position,
rotation,
&mut context.random,
self.is_warm,
0.3,
0.7,
);
let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(OceanRuinPiece {
piece: StructurePiece::new(StructurePieceType::OceanTemple, bounding_box, 0),
template,
rotation,
}));
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()),
})
}
}
pub struct OceanRuinPiece {
piece: StructurePiece,
template: Arc<StructureTemplate>,
fn add_pieces(
collector: &mut StructurePiecesCollector,
position: Vector3<i32>,
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 {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece
}
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
&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(
&mut self,
chunk: &mut ProtoChunk,
_block_registry: &dyn WorldPortalExt,
_random: &mut RandomGenerator,
random: &mut RandomGenerator,
_seed: i64,
chunk_box: &BlockBox,
) {
let origin = self.piece.bounding_box.min;
let sample_y = chunk.get_top_y(&pumpkin_util::HeightMap::OceanFloorWg, origin.x, origin.z);
let target_y = sample_y - 2; // slightly buried on ocean floor
let mut final_origin = origin;
final_origin.y = target_y;
place_template(
chunk,
&self.template,
final_origin,
(0, 0),
let height = chunk.get_top_y(
&HeightMap::OceanFloorWg,
self.template_position.x,
self.template_position.z,
);
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,
true, // skip air
true, // apply waterlogging
&[],
Some(chunk_box),
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<'_>,
) -> Option<StructurePosition> {
let mut collector = StructurePiecesCollector::default();
let mut random = context.random;
let mut tries: i64 = 0;
loop {
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 last_piece_type: Option<StrongholdPieceType> = None;
let mut has_portal_room = false;

View File

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

View File

@ -1,6 +1,10 @@
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::{
BlockDirection,
math::{block_box::BlockBox, position::BlockPos},
@ -11,13 +15,13 @@ use serde::Deserialize;
use crate::{
ProtoChunk,
generation::{
positions::chunk_pos::{get_center_x, get_center_z},
positions::chunk_pos::{start_block_x, start_block_z},
structure::{
piece::StructurePieceType,
shiftable_piece::ShiftableStructurePiece,
structures::{
StructureGenerator, StructureGeneratorContext, StructurePieceBase,
StructurePiecesCollector, StructurePosition,
StructurePiecesCollector, StructurePosition, WorldPortalExt,
},
},
},
@ -31,12 +35,11 @@ impl StructureGenerator for SwampHutGenerator {
&self,
mut context: StructureGeneratorContext<'_>,
) -> Option<StructurePosition> {
let x = get_center_x(context.chunk_x);
let z = get_center_z(context.chunk_z);
let x = start_block_x(context.chunk_x);
let z = start_block_z(context.chunk_z);
let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(SwampHutPiece {
shiftable_structure_piece: ShiftableStructurePiece::new(
let facing = BlockDirection::get_random_horizontal_direction(&mut context.random);
let mut shiftable_piece = ShiftableStructurePiece::new(
StructurePieceType::SwampHut,
x,
64,
@ -44,8 +47,15 @@ impl StructureGenerator for SwampHutGenerator {
7,
7,
9,
BlockDirection::get_random_horizontal_direction(&mut context.random).get_axis(),
),
facing.get_axis(),
);
shiftable_piece.piece.set_facing(Some(facing));
let mut collector = StructurePiecesCollector::default();
collector.add_piece(Box::new(SwampHutPiece {
shiftable_structure_piece: shiftable_piece,
spawned_witch: false,
spawned_cat: false,
}));
Some(StructurePosition {
@ -57,17 +67,72 @@ impl StructureGenerator for SwampHutGenerator {
pub struct SwampHutPiece {
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 {
fn as_any(&self) -> &dyn std::any::Any {
self
}
#[allow(clippy::too_many_lines)]
fn place(
&mut self,
chunk: &mut ProtoChunk,
_block_registry: &dyn crate::world::WorldPortalExt,
block_registry: &dyn WorldPortalExt,
_random: &mut RandomGenerator,
_seed: i64,
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, 5, 2, 1, &box_limit);
// let stairs_n = Block::SPRUCE_STAIRS.default_state.with("facing", "north");
// let stairs_e = Block::SPRUCE_STAIRS.default_state.with("facing", "east");
// let stairs_w = Block::SPRUCE_STAIRS.default_state.with("facing", "west");
// let stairs_s = Block::SPRUCE_STAIRS.default_state.with("facing", "south");
let stairs_n = Self::spruce_stairs(HorizontalFacing::North, StairsShape::Straight);
let stairs_e = Self::spruce_stairs(HorizontalFacing::East, StairsShape::Straight);
let stairs_w = Self::spruce_stairs(HorizontalFacing::West, StairsShape::Straight);
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(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.fill_with_outline(
chunk, &box_limit, false, 0, 4, 1, 6, 4, 1, stairs_n, stairs_n,
);
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.add_block(chunk, &stairs_n.with("shape", "outer_left"), 6, 4, 1, &box_limit);
// p.add_block(chunk, &stairs_s.with("shape", "outer_left"), 0, 4, 8, &box_limit);
// p.add_block(chunk, &stairs_s.with("shape", "outer_right"), 6, 4, 8, &box_limit);
p.place_block(
chunk,
block_registry,
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 j in [1, 5] {
@ -231,15 +336,10 @@ impl StructurePieceBase for SwampHutPiece {
}
}
// if !self.has_witch {
// let world_coords = p.get_world_coords(2, 2, 5);
// 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);
self.spawn_witch(chunk, &box_limit);
self.spawn_cat(chunk, &box_limit);
}
fn get_structure_piece(&self) -> &super::StructurePiece {
&self.shiftable_structure_piece.piece
}

View File

@ -232,7 +232,7 @@ impl PosRuleTest {
let dist = dx + dy + dz;
let chance =
calculate_linear_chance(dist, *min_dist, *max_dist, *min_chance, *max_chance);
rng.next_f32() < chance
rng.next_f32() <= chance
}
Self::AxisAlignedLinearPos {
axis,
@ -248,7 +248,7 @@ impl PosRuleTest {
};
let 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(
state: &'static BlockState,
mossiness: f32,
@ -537,45 +550,50 @@ fn process_block_age(
block_id,
BlockId::STONE_BRICKS
| BlockId::STONE
| BlockId::CRACKED_STONE_BRICKS
| BlockId::CHISELED_STONE_BRICKS
| BlockId::INFESTED_STONE
| BlockId::INFESTED_STONE_BRICKS
| BlockId::INFESTED_CRACKED_STONE_BRICKS
| BlockId::INFESTED_CHISELED_STONE_BRICKS
) {
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 {
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
|| block_id == BlockId::INFESTED_MOSSY_STONE_BRICKS
{
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness {
return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_STAIRS);
} else if check_block_has_tag(block_id, "stairs") {
if rng.next_f32() < 0.5 {
let non_mossy = [
Block::STONE_SLAB.default_state,
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];
}
} else if block_id == BlockId::STONE_BRICK_SLAB {
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness {
return non_mossy[idx];
}
} else if check_block_has_tag(block_id, "slabs") {
if rng.next_f32() < mossiness {
return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_SLAB);
}
} else if block_id == BlockId::STONE_BRICK_WALL {
if rng.next_f32() < 0.5 && rng.next_f32() < mossiness {
} else if check_block_has_tag(block_id, "walls") {
if rng.next_f32() < mossiness {
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 {
return Block::CRYING_OBSIDIAN.default_state;
}

View File

@ -372,9 +372,21 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
}
"minecraft:fallen_tree" => {
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! {
ConfiguredFeature::FallenTree(FallenTreeFeature {
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" => {
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" => {
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) }
}
"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" => {
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" => {
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" => {
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" => {
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" => {
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" => {
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" => {
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;
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" => {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { TreeDecorator::Beehive(BeehiveTreeDecorator { probability: #prob }) }
}
"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" => {
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" => {
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" => {
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" => {
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" => {
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 => {
let msg = format!("unknown tree decorator: {other}");