diff --git a/pumpkin-world/src/generation/feature/configured_features.rs b/pumpkin-world/src/generation/feature/configured_features.rs index 3f712e229..2e8582adc 100644 --- a/pumpkin-world/src/generation/feature/configured_features.rs +++ b/pumpkin-world/src/generation/feature/configured_features.rs @@ -235,6 +235,24 @@ impl ConfiguredFeature { Self::CoralClaw(feature) => { feature.generate(chunk, min_y, height, feature_name, random, pos) } + Self::EndPlatform(feature) => feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ), + Self::EndSpike(feature) => feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ), Self::SpringFeature(feature) => feature.generate(block_registry, chunk, random, pos), Self::SimpleBlock(feature) => feature.generate(block_registry, chunk, random, pos), Self::Flower(feature) => { diff --git a/pumpkin-world/src/generation/feature/features/end_platform.rs b/pumpkin-world/src/generation/feature/features/end_platform.rs index e41e14808..f60b61288 100644 --- a/pumpkin-world/src/generation/feature/features/end_platform.rs +++ b/pumpkin-world/src/generation/feature/features/end_platform.rs @@ -1,6 +1,40 @@ +use pumpkin_data::{Block, block_properties::get_state_by_state_id}; +use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use serde::Deserialize; +use crate::{ProtoChunk, world::BlockRegistryExt}; + #[derive(Deserialize)] -pub struct EndPlatformFeature { - // TODO +pub struct EndPlatformFeature; + +impl EndPlatformFeature { + #[expect(clippy::too_many_arguments)] + pub fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + _block_registry: &dyn BlockRegistryExt, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + _random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + for _ in -2..2 { + for _ in -2..2 { + for t in -1..3 { + let block = if t == -1 { + Block::OBSIDIAN.default_state_id + } else { + Block::AIR.default_state_id + }; + let state = get_state_by_state_id(block).unwrap(); + if chunk.get_block_state(&pos.0).state_id == state.id { + continue; + } + chunk.set_block_state(&pos.0, &state); + } + } + } + true + } } diff --git a/pumpkin-world/src/generation/feature/features/end_spike.rs b/pumpkin-world/src/generation/feature/features/end_spike.rs index f1f01c9d7..855bec465 100644 --- a/pumpkin-world/src/generation/feature/features/end_spike.rs +++ b/pumpkin-world/src/generation/feature/features/end_spike.rs @@ -1,6 +1,117 @@ +use pumpkin_data::{Block, block_properties::get_state_by_state_id}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; use serde::Deserialize; +use crate::{ + ProtoChunk, + generation::{height_limit::HeightLimitView, section_coords}, + world::BlockRegistryExt, +}; + #[derive(Deserialize)] pub struct EndSpikeFeature { - // TODO + crystal_invulnerable: bool, + spikes: Vec, +} + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +struct Spike { + center_x: i32, + center_z: i32, + radius: i32, + height: i32, + guarded: bool, +} + +impl Spike { + pub fn is_in_chunk(&self, pos: &BlockPos) -> bool { + section_coords::block_to_section(pos.0.x) == section_coords::block_to_section(self.center_x) + && section_coords::block_to_section(pos.0.z) + == section_coords::block_to_section(self.center_z) + } +} + +impl EndSpikeFeature { + #[expect(clippy::too_many_arguments)] + pub fn generate( + &self, + chunk: &mut ProtoChunk<'_>, + _block_registry: &dyn BlockRegistryExt, + _min_y: i8, + _height: u16, + _feature: &str, // This placed feature + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + let mut spikes = self.spikes.clone(); + if spikes.is_empty() { + for i in 0..10 { + let angle = 2.0 * (-std::f64::consts::PI + 0.3141592653589793 * i as f64); + let center_x = (42.0 * angle.cos()).floor() as i32; + let center_z = (42.0 * angle.sin()).floor() as i32; + + let l = random.next_bounded_i32(10); // TODO + let radius = 2 + l / 3; + let height = 76 + l * 3; + let guarded = l == 1 || l == 2; + + spikes.push(Spike { + center_x, + center_z, + radius, + height, + guarded, + }); + } + } + for spike in spikes { + if !spike.is_in_chunk(&pos) { + continue; + } + Self::gen_spike(&spike, chunk); + } + + true + } + + fn gen_spike(spike: &Spike, chunk: &mut ProtoChunk<'_>) { + let radius = spike.radius; + for pos in BlockPos::iterate( + BlockPos::new( + spike.center_x - radius, + chunk.bottom_y() as i32, + spike.center_z - radius, + ), + BlockPos::new( + spike.center_x + radius, + chunk.height() as i32 + 10, + spike.center_z + radius, + ), + ) { + if pos + .0 + .squared_distance_to(spike.center_x, pos.0.y, spike.center_z) + <= (radius * radius + 1) + && pos.0.y < spike.height + { + chunk.set_block_state( + &pos.0, + &get_state_by_state_id(Block::OBSIDIAN.default_state_id).unwrap(), + ); + continue; + } + if pos.0.y <= 65 { + continue; + } + chunk.set_block_state( + &pos.0, + &get_state_by_state_id(Block::AIR.default_state_id).unwrap(), + ); + } + // TODO + } }