diff --git a/pumpkin-codegen/src/configured_feature.rs b/pumpkin-codegen/src/configured_feature.rs index f7cca2ae9..9586b8078 100644 --- a/pumpkin-codegen/src/configured_feature.rs +++ b/pumpkin-codegen/src/configured_feature.rs @@ -670,7 +670,24 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream { quote! { ConfiguredFeature::LargeDripstone(crate::generation::feature::features::drip_stone::large::LargeDripstoneFeature {}) } } "minecraft:sculk_patch" => { - quote! { ConfiguredFeature::SculkPatch(crate::generation::feature::features::sculk_patch::SculkPatchFeature {}) } + let charge_count = config["charge_count"].as_u64().unwrap() as i32; + let amount_per_charge = config["amount_per_charge"].as_u64().unwrap() as i32; + let spread_attempts = config["spread_attempts"].as_u64().unwrap() as i32; + let growth_rounds = config["growth_rounds"].as_u64().unwrap() as i32; + let spread_rounds = config["spread_rounds"].as_u64().unwrap() as i32; + let extra_rare_growths = value_to_int_provider(&config["extra_rare_growths"]); + let catalyst_chance = config["catalyst_chance"].as_f64().unwrap() as f32; + quote! { + ConfiguredFeature::SculkPatch(crate::generation::feature::features::sculk_patch::SculkPatchFeature { + charge_count: #charge_count, + amount_per_charge: #amount_per_charge, + spread_attempts: #spread_attempts, + growth_rounds: #growth_rounds, + spread_rounds: #spread_rounds, + extra_rare_growths: #extra_rare_growths, + catalyst_chance: #catalyst_chance, + }) + } } "minecraft:block_pile" => { quote! { ConfiguredFeature::BlockPile(crate::generation::feature::features::block_pile::BlockPileFeature {}) } diff --git a/pumpkin-data/src/generated/configured_features_generated.rs b/pumpkin-data/src/generated/configured_features_generated.rs index 082bd4211..1e98ade52 100644 --- a/pumpkin-data/src/generated/configured_features_generated.rs +++ b/pumpkin-data/src/generated/configured_features_generated.rs @@ -91,7 +91,7 @@ fn build_configured_features() -> std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap std::collections::HashMap { feature.generate(chunk, min_y, height, feature_name, random, pos) } + Self::SculkPatch(feature) => feature.generate(block_registry, chunk, random, pos), _ => false, // TODO } } diff --git a/pumpkin-world/src/generation/feature/features/sculk_patch.rs b/pumpkin-world/src/generation/feature/features/sculk_patch.rs index eae752c34..d2b27ae35 100644 --- a/pumpkin-world/src/generation/feature/features/sculk_patch.rs +++ b/pumpkin-world/src/generation/feature/features/sculk_patch.rs @@ -1,3 +1,183 @@ +use pumpkin_data::Block; +use pumpkin_data::tag::Block::MINECRAFT_SCULK_REPLACEABLE_WORLD_GEN; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::generation::proto_chunk::GenerationCache; +use crate::world::BlockRegistryExt; + pub struct SculkPatchFeature { - // TODO + pub charge_count: i32, + pub amount_per_charge: i32, + pub spread_attempts: i32, + pub growth_rounds: i32, + pub spread_rounds: i32, + pub extra_rare_growths: IntProvider, + pub catalyst_chance: f32, +} + +impl SculkPatchFeature { + pub fn generate( + &self, + _block_registry: &dyn BlockRegistryExt, + chunk: &mut T, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + if !self.can_spread_from(chunk, pos) { + return false; + } + + let mut spreader = SculkSpreader::new(); + let total_rounds = self.spread_rounds + self.growth_rounds; + + for round in 0..total_rounds { + for _ in 0..self.charge_count { + spreader.add_cursor(pos, self.amount_per_charge); + } + + let spread_veins = round < self.spread_rounds; + + for _ in 0..self.spread_attempts { + spreader.update_cursors(chunk, random, spread_veins); + } + + spreader.clear(); + } + + let below = pos.down(); + if random.next_f32() <= self.catalyst_chance + && GenerationCache::get_block_state(chunk, &below.0) + .to_state() + .is_solid() + { + chunk.set_block_state(&pos.0, Block::SCULK_CATALYST.default_state); + } + + let extra_growths = self.extra_rare_growths.get(random); + for _ in 0..extra_growths { + let candidate = pos.offset(Vector3::new( + random.next_bounded_i32(5) - 2, + 0, + random.next_bounded_i32(5) - 2, + )); + let state = GenerationCache::get_block_state(chunk, &candidate.0).to_state(); + let below_candidate = candidate.down(); + let below_state = + GenerationCache::get_block_state(chunk, &below_candidate.0).to_state(); + + if state.is_air() && below_state.is_side_solid(pumpkin_data::BlockDirection::Up) { + // TODO: set sculk shrieker with can_summon = true if possible + chunk.set_block_state(&candidate.0, Block::SCULK_SHRIEKER.default_state); + } + } + + true + } + + fn can_spread_from(&self, chunk: &T, pos: BlockPos) -> bool { + let state = GenerationCache::get_block_state(chunk, &pos.0); + let block_id = state.to_block_id(); + if is_sculk_behaviour(block_id) { + true + } else { + if !chunk.is_air(&pos.0) && block_id != Block::WATER.id { + return false; + } + // Check if any neighbor is solid + for neighbor in [ + pos.offset(Vector3::new(1, 0, 0)), + pos.offset(Vector3::new(-1, 0, 0)), + pos.offset(Vector3::new(0, 1, 0)), + pos.offset(Vector3::new(0, -1, 0)), + pos.offset(Vector3::new(0, 0, 1)), + pos.offset(Vector3::new(0, 0, -1)), + ] { + if GenerationCache::get_block_state(chunk, &neighbor.0) + .to_state() + .is_solid() + { + return true; + } + } + false + } + } +} + +fn is_sculk_behaviour(block_id: u16) -> bool { + block_id == Block::SCULK.id + || block_id == Block::SCULK_VEIN.id + || block_id == Block::SCULK_CATALYST.id + || block_id == Block::SCULK_SHRIEKER.id + || block_id == Block::SCULK_SENSOR.id + || block_id == Block::CALIBRATED_SCULK_SENSOR.id +} + +fn is_sculk_replaceable(block_id: u16) -> bool { + MINECRAFT_SCULK_REPLACEABLE_WORLD_GEN.1.contains(&block_id) +} + +struct Cursor { + pos: BlockPos, + charge: i32, +} + +struct SculkSpreader { + cursors: Vec, +} + +impl SculkSpreader { + fn new() -> Self { + Self { + cursors: Vec::new(), + } + } + + fn add_cursor(&mut self, pos: BlockPos, charge: i32) { + self.cursors.push(Cursor { pos, charge }); + } + + fn clear(&mut self) { + self.cursors.clear(); + } + + fn update_cursors( + &mut self, + chunk: &mut T, + random: &mut RandomGenerator, + _spread_veins: bool, + ) { + let mut next_cursors = Vec::new(); + for mut cursor in self.cursors.drain(..) { + if cursor.charge <= 0 { + continue; + } + + // In world gen, it picks one of 26 neighbors + let dx = random.next_bounded_i32(3) - 1; + let dy = random.next_bounded_i32(3) - 1; + let dz = random.next_bounded_i32(3) - 1; + let target_pos = cursor.pos.offset(Vector3::new(dx, dy, dz)); + + let target_state = GenerationCache::get_block_state(chunk, &target_pos.0); + let target_block_id = target_state.to_block_id(); + + if is_sculk_replaceable(target_block_id) { + chunk.set_block_state(&target_pos.0, Block::SCULK.default_state); + cursor.pos = target_pos; + cursor.charge -= 1; + } else if target_block_id == Block::SCULK.id { + cursor.pos = target_pos; + cursor.charge -= 1; + } + + if cursor.charge > 0 { + next_cursors.push(cursor); + } + } + self.cursors = next_cursors; + } }