129 lines
4.9 KiB
Rust
129 lines
4.9 KiB
Rust
use pumpkin_data::{Block, BlockId, block_properties::is_air, tag};
|
|
use pumpkin_util::{
|
|
math::{position::BlockPos, vector3::Vector3},
|
|
random::{RandomGenerator, RandomImpl},
|
|
};
|
|
|
|
use crate::generation::proto_chunk::GenerationCache;
|
|
|
|
pub struct IceSpikeFeature;
|
|
|
|
impl IceSpikeFeature {
|
|
pub fn generate<T: GenerationCache>(
|
|
chunk: &mut T,
|
|
_min_y: i8,
|
|
_height: u16,
|
|
_feature: pumpkin_data::placed_feature::PlacedFeature,
|
|
random: &mut RandomGenerator,
|
|
pos: BlockPos,
|
|
) -> bool {
|
|
let mut origin = pos.0;
|
|
|
|
// Walk down while block is air, staying above minY + 2
|
|
while chunk.is_air(&origin) && origin.y > chunk.bottom_y() as i32 + 2 {
|
|
origin.y -= 1;
|
|
}
|
|
|
|
// Only place on snow blocks
|
|
let block_id = GenerationCache::get_block_state(chunk, &origin).to_block_id();
|
|
if block_id != Block::SNOW_BLOCK.id {
|
|
return false;
|
|
}
|
|
|
|
origin.y += random.next_bounded_i32(4);
|
|
let height = random.next_bounded_i32(4) + 7;
|
|
let width = height / 4 + random.next_bounded_i32(2);
|
|
|
|
// Occasionally create extra-tall spires
|
|
if width > 1 && random.next_bounded_i32(60) == 0 {
|
|
origin.y += 10 + random.next_bounded_i32(30);
|
|
}
|
|
|
|
for y_off in 0..height {
|
|
let scale = (1.0f32 - y_off as f32 / height as f32) * width as f32;
|
|
let new_width = scale.ceil() as i32;
|
|
|
|
for xo in -new_width..=new_width {
|
|
let dx = xo.abs() as f32 - 0.25f32;
|
|
|
|
for zo in -new_width..=new_width {
|
|
let dz = zo.abs() as f32 - 0.25f32;
|
|
|
|
let in_sphere = (xo == 0 && zo == 0) || (dx * dx + dz * dz <= scale * scale);
|
|
let edge_ok = (xo != -new_width
|
|
&& xo != new_width
|
|
&& zo != -new_width
|
|
&& zo != new_width)
|
|
|| (random.next_f32() <= 0.75f32);
|
|
|
|
if in_sphere && edge_ok {
|
|
// Place packed ice upward
|
|
let place_pos = origin.add(&Vector3::new(xo, y_off, zo));
|
|
let state_id = GenerationCache::get_block_state(chunk, &place_pos);
|
|
let bid = state_id.to_block_id();
|
|
if is_air(state_id)
|
|
|| bid.has_tag(tag::Block::MINECRAFT_DIRT)
|
|
|| bid == BlockId::SNOW_BLOCK
|
|
|| bid == BlockId::ICE
|
|
{
|
|
chunk.set_block_state(&place_pos, Block::PACKED_ICE.default_state);
|
|
}
|
|
|
|
// Mirror downward
|
|
if y_off != 0 && new_width > 1 {
|
|
let neg_pos = origin.add(&Vector3::new(xo, -y_off, zo));
|
|
let state_id2 = GenerationCache::get_block_state(chunk, &neg_pos);
|
|
let bid2 = state_id2.to_block_id();
|
|
if is_air(state_id2)
|
|
|| bid2.has_tag(tag::Block::MINECRAFT_DIRT)
|
|
|| bid2 == BlockId::SNOW_BLOCK
|
|
|| bid2 == BlockId::ICE
|
|
{
|
|
chunk.set_block_state(&neg_pos, Block::PACKED_ICE.default_state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build packed-ice roots downward from the spike base
|
|
let pillar_width = (width - 1).clamp(0, 1);
|
|
|
|
for xo in -pillar_width..=pillar_width {
|
|
for zo in -pillar_width..=pillar_width {
|
|
let mut ice_block = origin.add(&Vector3::new(xo, -1, zo));
|
|
// Corner positions get a short random run; others run 50 blocks
|
|
let mut run_length: i32 = if xo.abs() == 1 && zo.abs() == 1 {
|
|
random.next_bounded_i32(5)
|
|
} else {
|
|
50
|
|
};
|
|
|
|
while ice_block.y > 50 {
|
|
let state_id = GenerationCache::get_block_state(chunk, &ice_block);
|
|
let bid = state_id.to_block_id();
|
|
if !is_air(state_id)
|
|
&& !bid.has_tag(tag::Block::MINECRAFT_DIRT)
|
|
&& bid != BlockId::SNOW_BLOCK
|
|
&& bid != BlockId::ICE
|
|
&& bid != BlockId::PACKED_ICE
|
|
{
|
|
break;
|
|
}
|
|
|
|
chunk.set_block_state(&ice_block, Block::PACKED_ICE.default_state);
|
|
ice_block.y -= 1;
|
|
run_length -= 1;
|
|
if run_length <= 0 {
|
|
ice_block.y -= random.next_bounded_i32(5) + 1;
|
|
run_length = random.next_bounded_i32(5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|