feat: more chunk gen work
This commit is contained in:
parent
3a3ba2c873
commit
015091de12
File diff suppressed because one or more lines are too long
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)| {
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
if self.place_growth_if_possible(chunk, cur_pos, random, &placement_directions) {
|
||||
return true;
|
||||
}
|
||||
generated
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn place_growth_if_possible<T: GenerationCache>(
|
||||
&self,
|
||||
chunk: &mut T,
|
||||
pos: BlockPos,
|
||||
_random: &mut RandomGenerator,
|
||||
placement_directions: &[BlockDirection],
|
||||
) -> bool {
|
||||
for &direction in placement_directions {
|
||||
let neighbor_pos = pos.offset(direction.to_offset());
|
||||
let neighbor_id =
|
||||
GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_block_id();
|
||||
|
||||
if self.can_be_placed_on.contains(&neighbor_id) {
|
||||
let is_water = GenerationCache::get_block_state(chunk, &pos.0).to_block_id()
|
||||
== Block::WATER.id;
|
||||
let block = Block::from_id(self.place_block);
|
||||
|
||||
let mut props = GlowLichenLikeProperties {
|
||||
down: direction == BlockDirection::Down,
|
||||
up: direction == BlockDirection::Up,
|
||||
north: direction == BlockDirection::North,
|
||||
south: direction == BlockDirection::South,
|
||||
west: direction == BlockDirection::West,
|
||||
east: direction == BlockDirection::East,
|
||||
waterlogged: is_water,
|
||||
};
|
||||
|
||||
let cur_state = GenerationCache::get_block_state(chunk, &pos.0);
|
||||
if cur_state.to_block_id() == self.place_block
|
||||
&& (self.place_block == Block::GLOW_LICHEN.id
|
||||
|| self.place_block == Block::SCULK_VEIN.id)
|
||||
{
|
||||
let cur_props = GlowLichenLikeProperties::from_state_id(cur_state);
|
||||
props.down |= cur_props.down;
|
||||
props.up |= cur_props.up;
|
||||
props.north |= cur_props.north;
|
||||
props.south |= cur_props.south;
|
||||
props.west |= cur_props.west;
|
||||
props.east |= cur_props.east;
|
||||
}
|
||||
|
||||
let state_id = props.to_state_id(block);
|
||||
chunk.set_block_state(&pos.0, BlockState::from_id(state_id));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn get_valid_directions(&self) -> Vec<BlockDirection> {
|
||||
let mut dirs = Vec::with_capacity(6);
|
||||
if self.can_place_on_ceiling {
|
||||
dirs.push(BlockDirection::Up);
|
||||
}
|
||||
if self.can_place_on_floor {
|
||||
dirs.push(BlockDirection::Down);
|
||||
}
|
||||
if self.can_place_on_wall {
|
||||
dirs.extend(
|
||||
BlockDirection::horizontal_worldgen().map(BlockDirection::from_cardinal_direction),
|
||||
);
|
||||
}
|
||||
dirs
|
||||
}
|
||||
|
||||
fn get_shuffled_directions(&self, random: &mut RandomGenerator) -> Vec<BlockDirection> {
|
||||
let mut dirs = self.get_valid_directions();
|
||||
for i in (1..dirs.len()).rev() {
|
||||
let j = random.next_bounded_i32((i + 1) as i32) as usize;
|
||||
dirs.swap(i, j);
|
||||
}
|
||||
dirs
|
||||
}
|
||||
|
||||
fn get_shuffled_directions_except(
|
||||
&self,
|
||||
random: &mut RandomGenerator,
|
||||
exclude: BlockDirection,
|
||||
) -> Vec<BlockDirection> {
|
||||
let mut dirs: Vec<BlockDirection> = self
|
||||
.get_valid_directions()
|
||||
.into_iter()
|
||||
.filter(|&d| d != exclude)
|
||||
.collect();
|
||||
for i in (1..dirs.len()).rev() {
|
||||
let j = random.next_bounded_i32((i + 1) as i32) as usize;
|
||||
dirs.swap(i, j);
|
||||
}
|
||||
dirs
|
||||
}
|
||||
|
||||
fn is_air_or_water<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
|
||||
let id = GenerationCache::get_block_state(chunk, &pos.0).to_block_id();
|
||||
id == Block::AIR.id || id == Block::WATER.id
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
let state_id = props.to_state_id(&Block::SEA_PICKLE);
|
||||
chunk.set_block_state(&pickle_pos.0, BlockState::from_id(state_id));
|
||||
placed += 1;
|
||||
}
|
||||
times > 0
|
||||
}
|
||||
|
||||
placed > 0
|
||||
}
|
||||
|
||||
fn can_survive<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
|
||||
let below = pos.down();
|
||||
GenerationCache::get_block_state(chunk, &below.0)
|
||||
.to_state()
|
||||
.is_side_solid(BlockDirection::Up)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
chunk.set_block_state(&grass_pos.0, Block::SEAGRASS.default_state);
|
||||
placed_any = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
|
||||
placed_any
|
||||
}
|
||||
|
||||
fn can_survive<T: GenerationCache>(chunk: &T, pos: BlockPos) -> bool {
|
||||
let below = pos.down();
|
||||
let below_state = GenerationCache::get_block_state(chunk, &below.0);
|
||||
let below_id = below_state.to_block_id();
|
||||
|
||||
if below_id.has_tag(tag::Block::MINECRAFT_CANNOT_SUPPORT_SEAGRASS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
below_state.to_state().is_side_solid(BlockDirection::Up)
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,9 +27,7 @@ impl SimpleBlockFeature {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: check things..
|
||||
chunk.set_block_state(&pos.0, state);
|
||||
// TODO: schedule tick when needed
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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, ¤t_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, ¤t_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(¤t_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(¤t_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, ¤t.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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
impl BuriedTreasurePiece {
|
||||
fn is_liquid(id: BlockId) -> bool {
|
||||
id == BlockId::WATER || id == BlockId::LAVA
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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}");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user