mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(chunk-gen) add scattered ore, twisting vines, blue ice
This commit is contained in:
@@ -226,7 +226,9 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
|
||||
if type_str == "minecraft:scattered_ore" {
|
||||
quote! {
|
||||
ConfiguredFeature::ScatteredOre(crate::generation::feature::features::scattered_ore::ScatteredOreFeature {
|
||||
// TODO
|
||||
size: #size,
|
||||
discard_chance_on_air_exposure: #discard,
|
||||
targets: vec![#(#targets),*],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -701,7 +703,16 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
|
||||
quote! { ConfiguredFeature::WeepingVines(crate::generation::feature::features::weeping_vines::WeepingVinesFeature {}) }
|
||||
}
|
||||
"minecraft:twisting_vines" => {
|
||||
quote! { ConfiguredFeature::TwistingVines(crate::generation::feature::features::twisting_vines::TwistingVinesFeature {}) }
|
||||
let spread_width = config["spread_width"].as_i64().unwrap_or(0) as i32;
|
||||
let spread_height = config["spread_height"].as_i64().unwrap_or(0) as i32;
|
||||
let max_height = config["max_height"].as_i64().unwrap_or(0) as i32;
|
||||
quote! {
|
||||
ConfiguredFeature::TwistingVines(crate::generation::feature::features::twisting_vines::TwistingVinesFeature {
|
||||
spread_width: #spread_width,
|
||||
spread_height: #spread_height,
|
||||
max_height: #max_height,
|
||||
})
|
||||
}
|
||||
}
|
||||
"minecraft:delta_feature" => {
|
||||
quote! { ConfiguredFeature::DeltaFeature(crate::generation::feature::features::delta_feature::DeltaFeatureFeature {}) }
|
||||
|
||||
@@ -2738,13 +2738,31 @@ fn build_configured_features() -> std::collections::HashMap<String, ConfiguredFe
|
||||
map.insert(
|
||||
"ore_ancient_debris_large".to_string(),
|
||||
ConfiguredFeature::ScatteredOre(
|
||||
crate::generation::feature::features::scattered_ore::ScatteredOreFeature {},
|
||||
crate::generation::feature::features::scattered_ore::ScatteredOreFeature {
|
||||
size: 3i32,
|
||||
discard_chance_on_air_exposure: 1f32,
|
||||
targets: vec![OreTarget {
|
||||
target: RuleTest::TagMatch(TagMatchRuleTest {
|
||||
tag: pumpkin_data::tag::Block::MINECRAFT_BASE_STONE_NETHER,
|
||||
}),
|
||||
state: pumpkin_data::Block::ANCIENT_DEBRIS.default_state,
|
||||
}],
|
||||
},
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
"ore_ancient_debris_small".to_string(),
|
||||
ConfiguredFeature::ScatteredOre(
|
||||
crate::generation::feature::features::scattered_ore::ScatteredOreFeature {},
|
||||
crate::generation::feature::features::scattered_ore::ScatteredOreFeature {
|
||||
size: 2i32,
|
||||
discard_chance_on_air_exposure: 1f32,
|
||||
targets: vec![OreTarget {
|
||||
target: RuleTest::TagMatch(TagMatchRuleTest {
|
||||
tag: pumpkin_data::tag::Block::MINECRAFT_BASE_STONE_NETHER,
|
||||
}),
|
||||
state: pumpkin_data::Block::ANCIENT_DEBRIS.default_state,
|
||||
}],
|
||||
},
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
@@ -4234,13 +4252,21 @@ fn build_configured_features() -> std::collections::HashMap<String, ConfiguredFe
|
||||
map.insert(
|
||||
"twisting_vines".to_string(),
|
||||
ConfiguredFeature::TwistingVines(
|
||||
crate::generation::feature::features::twisting_vines::TwistingVinesFeature {},
|
||||
crate::generation::feature::features::twisting_vines::TwistingVinesFeature {
|
||||
spread_width: 8i32,
|
||||
spread_height: 4i32,
|
||||
max_height: 8i32,
|
||||
},
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
"twisting_vines_bonemeal".to_string(),
|
||||
ConfiguredFeature::TwistingVines(
|
||||
crate::generation::feature::features::twisting_vines::TwistingVinesFeature {},
|
||||
crate::generation::feature::features::twisting_vines::TwistingVinesFeature {
|
||||
spread_width: 3i32,
|
||||
spread_height: 1i32,
|
||||
max_height: 2i32,
|
||||
},
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
|
||||
@@ -317,6 +317,9 @@ impl ConfiguredFeature {
|
||||
Self::Seagrass(feature) => {
|
||||
feature.generate(chunk, min_y, height, feature_name, random, pos)
|
||||
}
|
||||
Self::TwistingVines(feature) => {
|
||||
feature.generate(chunk, min_y, height, feature_name, random, pos)
|
||||
}
|
||||
Self::UnderwaterMagma(feature) => {
|
||||
feature.generate(chunk, min_y, height, feature_name, random, pos)
|
||||
}
|
||||
@@ -344,9 +347,19 @@ impl ConfiguredFeature {
|
||||
random,
|
||||
pos,
|
||||
),
|
||||
Self::ScatteredOre(feature) => feature.generate(
|
||||
chunk,
|
||||
block_registry,
|
||||
min_y,
|
||||
height,
|
||||
feature_name,
|
||||
random,
|
||||
pos,
|
||||
),
|
||||
Self::MonsterRoom(feature) => {
|
||||
feature.generate(chunk, min_y, height, feature_name, random, pos)
|
||||
}
|
||||
Self::BlueIce(feature) => feature.generate(chunk, random, pos),
|
||||
Self::GlowstoneBlob(feature) => {
|
||||
feature.generate(chunk, min_y, height, feature_name, random, pos)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,95 @@
|
||||
pub struct BlueIceFeature {
|
||||
// TODO
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_util::{
|
||||
math::position::BlockPos,
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
};
|
||||
|
||||
use crate::generation::proto_chunk::GenerationCache;
|
||||
|
||||
const SEA_LEVEL: i32 = 63; // TODO: use getSeaLevel() instead of hardcoding
|
||||
|
||||
pub struct BlueIceFeature {}
|
||||
|
||||
impl BlueIceFeature {
|
||||
pub fn generate<T: GenerationCache>(
|
||||
&self,
|
||||
chunk: &mut T,
|
||||
random: &mut RandomGenerator,
|
||||
pos: BlockPos,
|
||||
) -> bool {
|
||||
if pos.0.y >= SEA_LEVEL - 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let block = GenerationCache::get_block_state(chunk, &pos.0).to_state();
|
||||
let block_below = GenerationCache::get_block_state(chunk, &pos.down().0).to_state();
|
||||
|
||||
if block != Block::WATER.default_state && block_below != Block::WATER.default_state {
|
||||
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
|
||||
{
|
||||
has_ice_neighbor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !has_ice_neighbor {
|
||||
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);
|
||||
|
||||
if chunk.out_of_height(target_pos.0.y as i16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let target_state = GenerationCache::get_block_state(chunk, &target_pos.0).to_state();
|
||||
if target_state.is_air()
|
||||
|| target_state == Block::WATER.default_state
|
||||
|| target_state == Block::ICE.default_state
|
||||
|| target_state == Block::PACKED_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
|
||||
{
|
||||
has_blue_ice_neighbor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if has_blue_ice_neighbor {
|
||||
chunk.set_block_state(&target_pos.0, Block::BLUE_ICE.default_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,14 @@ impl OreFeature {
|
||||
let block_state = GenerationCache::get_block_state(chunk, &pos_vec);
|
||||
|
||||
for target in &self.targets {
|
||||
if self.should_place(chunk, block_state, random, target, &mutable_pos) {
|
||||
if Self::should_place(
|
||||
self.discard_chance_on_air_exposure,
|
||||
chunk,
|
||||
block_state,
|
||||
random,
|
||||
target,
|
||||
&mutable_pos,
|
||||
) {
|
||||
chunk.set_block_state(&pos_vec, target.state);
|
||||
placed_blocks_count += 1;
|
||||
break; // Equivalent to 'continue block11;'
|
||||
@@ -197,8 +204,8 @@ impl OreFeature {
|
||||
placed_blocks_count > 0
|
||||
}
|
||||
|
||||
fn should_place<T: GenerationCache>(
|
||||
&self,
|
||||
pub fn should_place<T: GenerationCache>(
|
||||
discard_chance: f32,
|
||||
chunk: &T,
|
||||
state: RawBlockState,
|
||||
random: &mut RandomGenerator,
|
||||
@@ -208,13 +215,13 @@ impl OreFeature {
|
||||
if !target.target.test(state, random) {
|
||||
return false;
|
||||
}
|
||||
if Self::should_not_discard(random, self.discard_chance_on_air_exposure) {
|
||||
if Self::should_not_discard(random, discard_chance) {
|
||||
return true;
|
||||
}
|
||||
!Self::is_exposed_to_air(chunk, pos)
|
||||
}
|
||||
|
||||
fn should_not_discard(random: &mut RandomGenerator, chance: f32) -> bool {
|
||||
pub fn should_not_discard(random: &mut RandomGenerator, chance: f32) -> bool {
|
||||
if chance <= 0.0f32 {
|
||||
return true;
|
||||
}
|
||||
@@ -224,7 +231,7 @@ impl OreFeature {
|
||||
random.next_f32() >= chance
|
||||
}
|
||||
|
||||
fn is_exposed_to_air<T: GenerationCache>(chunk: &T, pos: &BlockPos) -> bool {
|
||||
pub fn is_exposed_to_air<T: GenerationCache>(chunk: &T, pos: &BlockPos) -> bool {
|
||||
for dir in BlockDirection::all() {
|
||||
if GenerationCache::get_block_state(chunk, &pos.offset(dir.to_offset()).0)
|
||||
.to_state()
|
||||
|
||||
@@ -1,3 +1,69 @@
|
||||
use pumpkin_util::{
|
||||
math::position::BlockPos,
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
};
|
||||
|
||||
use crate::generation::proto_chunk::GenerationCache;
|
||||
use crate::world::BlockRegistryExt;
|
||||
|
||||
use super::ore::{OreFeature, OreTarget};
|
||||
|
||||
pub struct ScatteredOreFeature {
|
||||
// TODO
|
||||
pub size: i32,
|
||||
pub discard_chance_on_air_exposure: f32,
|
||||
pub targets: Vec<OreTarget>,
|
||||
}
|
||||
|
||||
impl ScatteredOreFeature {
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn generate<T: GenerationCache>(
|
||||
&self,
|
||||
chunk: &mut T,
|
||||
_block_registry: &dyn BlockRegistryExt,
|
||||
_min_y: i8,
|
||||
_height: u16,
|
||||
_feature: &str,
|
||||
random: &mut RandomGenerator,
|
||||
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);
|
||||
|
||||
let target_pos = pos.add(offset_x, offset_y, offset_z);
|
||||
|
||||
if chunk.out_of_height(target_pos.0.y as i16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let block_state = GenerationCache::get_block_state(chunk, &target_pos.0);
|
||||
|
||||
for target in &self.targets {
|
||||
if OreFeature::should_place(
|
||||
self.discard_chance_on_air_exposure,
|
||||
chunk,
|
||||
block_state,
|
||||
random,
|
||||
target,
|
||||
&target_pos,
|
||||
) {
|
||||
chunk.set_block_state(&target_pos.0, target.state);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
placed
|
||||
}
|
||||
|
||||
fn get_random_offset(&self, random: &mut RandomGenerator) -> i32 {
|
||||
let f1 = random.next_f32();
|
||||
let f2 = random.next_f32();
|
||||
((f1 - f2) * self.size as f32).round() as i32
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,133 @@
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_util::{
|
||||
math::position::BlockPos,
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
};
|
||||
|
||||
use crate::generation::proto_chunk::GenerationCache;
|
||||
|
||||
pub struct TwistingVinesFeature {
|
||||
// TODO
|
||||
pub spread_width: i32,
|
||||
pub spread_height: i32,
|
||||
pub max_height: i32,
|
||||
}
|
||||
|
||||
impl TwistingVinesFeature {
|
||||
pub fn generate<T: GenerationCache>(
|
||||
&self,
|
||||
chunk: &mut T,
|
||||
_min_y: i8,
|
||||
_height: u16,
|
||||
_feature_name: &str,
|
||||
random: &mut RandomGenerator,
|
||||
pos: BlockPos,
|
||||
) -> bool {
|
||||
if self.is_invalid_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 mut mutable_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)
|
||||
{
|
||||
let mut height = random.next_bounded_i32(self.max_height) + 1;
|
||||
if random.next_bounded_i32(6) == 0 {
|
||||
height *= 2;
|
||||
}
|
||||
if random.next_bounded_i32(10) == 0 {
|
||||
height = 1;
|
||||
}
|
||||
|
||||
self.generate_column(chunk, random, &mutable_pos, height);
|
||||
placed = true;
|
||||
}
|
||||
}
|
||||
|
||||
placed
|
||||
}
|
||||
|
||||
fn generate_column<T: GenerationCache>(
|
||||
&self,
|
||||
chunk: &mut T,
|
||||
random: &mut RandomGenerator,
|
||||
pos: &BlockPos,
|
||||
height: i32,
|
||||
) {
|
||||
let mut current_pos = *pos;
|
||||
for i in 0..height {
|
||||
if !GenerationCache::get_block_state(chunk, ¤t_pos.0)
|
||||
.to_state()
|
||||
.is_air()
|
||||
{
|
||||
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;
|
||||
} else {
|
||||
chunk.set_block_state(¤t_pos.0, Block::TWISTING_VINES_PLANT.default_state);
|
||||
}
|
||||
current_pos = current_pos.up();
|
||||
}
|
||||
}
|
||||
|
||||
fn is_invalid_location<T: GenerationCache>(&self, chunk: &T, pos: &BlockPos) -> bool {
|
||||
if !GenerationCache::get_block_state(chunk, &pos.0)
|
||||
.to_state()
|
||||
.is_air()
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user