Files
Pumpkin/crates/pumpkin-world/src/generation/proto_chunk.rs
MCbabel 69e3ff7c7c fix(world): size proto chunks by the dimension, not by the noise settings (#2803)
`ProtoChunk::new` took its height from the noise settings while
`Chunk::build_level_sections` builds and reads back `dimension.height / 16`
sections. The End and the Nether are the two dimensions taller than their noise
settings (256 vs 128), so their proto chunks held 8 sections for the 16 the
level chunk asks for and `get_block_state_raw` indexed past the end of
`flat_block_map`, panicking on every generation thread. The same mismatch left
light data covering half the column and dropped features placed above y = 128.

A chunk spans its dimension, as in vanilla's `LevelHeightAccessor`; the noise
settings only bound the vertical window the generator writes into. `ProtoChunk`
now carries both, storage sized by the dimension plus a generation window built
with `GenerationShapeConfig::trim_height`, so surface rules, carver height
providers and feature placement keep resolving against y < 128 there.

The panic quoted in the issue (`chunk_density_function.rs`, len 99 index 129)
was the same conflation in `populate_noise` and is already fixed by 3c43f971.
2026-08-07 10:10:00 +02:00

1638 lines
63 KiB
Rust

use crate::generation::structure::placement::GlobalStructureCache;
use std::sync::Arc;
use pumpkin_data::block_properties::is_air;
use pumpkin_data::chunk::DoublePerlinNoiseParameters;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::structures::{
Structure, StructureKeys, StructurePlacementType, StructureSet, WeightedEntry,
};
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::{Block, BlockState, block_properties::blocks_movement, chunk::Biome};
use pumpkin_data::{BlockId, BlockStateId, tag};
use pumpkin_util::random::xoroshiro128::XoroshiroSplitter;
use pumpkin_util::random::{RandomImpl, get_carver_seed};
use pumpkin_util::{
HeightMap,
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
random::{RandomGenerator, get_decorator_seed, xoroshiro128::Xoroshiro},
};
use rustc_hash::FxHashMap;
use super::{
GlobalRandomConfig, biome_coords,
blender::{Blender, BlenderImpl},
feature::placed_features::PLACED_FEATURES,
noise::router::{
multi_noise_sampler::MultiNoiseSampler, proto_noise_router::DoublePerlinNoiseBuilder,
surface_height_sampler::SurfaceHeightEstimateSampler,
},
positions::chunk_pos::{start_block_x, start_block_z},
section_coords,
surface::{MaterialRuleContext, estimate_surface_height, terrain::SurfaceTerrainBuilder},
};
use crate::biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier};
use crate::chunk::format::LightContainer;
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight};
use crate::chunk_system::StagedChunkEnum;
use crate::generation::height_limit::HeightLimitView;
use crate::generation::noise::aquifer_sampler::{FluidLevel, FluidLevelSamplerImpl};
use crate::generation::noise::perlin::DoublePerlinNoiseSampler;
use crate::generation::noise::router::multi_noise_sampler::MultiNoiseSamplerBuilderOptions;
use crate::generation::noise::router::surface_height_sampler::SurfaceHeightSamplerBuilderOptions;
use crate::generation::noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK};
use crate::generation::section_coords::section_to_block;
use crate::generation::structure::lazily_generate_structure;
use crate::generation::structure::placement::should_generate_structure;
use crate::generation::structure::structures::{
StructureGeneratorContext, StructureInstance, create_chunk_random,
};
use crate::generation::structure::try_generate_structure;
use crate::generation::surface::rule::try_apply_material_rule;
use crate::{
chunk::CHUNK_AREA,
generation::{biome, positions::chunk_pos},
world::{BlockAccessor, WorldPortalExt},
};
use pumpkin_data::tag::get_tag_ids;
use pumpkin_nbt::compound::NbtCompound;
use crate::generation::structure::template::BlockPlacer;
use crate::tick::{ScheduledTick, TickPriority};
enum ActiveSupplier {
Overworld(MultiNoiseBiomeSupplier),
Nether(MultiNoiseBiomeSupplier),
End(TheEndBiomeSupplier),
}
pub trait GenerationCache: HeightLimitView + BlockAccessor {
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk;
fn get_center_chunk(&self) -> &ProtoChunk;
fn get_chunk_mut(&mut self, chunk_x: i32, chunk_z: i32) -> Option<&mut ProtoChunk>;
fn get_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk>;
fn try_get_proto_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk>;
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId;
fn get_fluid_and_fluid_state(&self, position: &Vector3<i32>) -> (Fluid, FluidState);
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState);
fn add_block_entity(&mut self, pos: &Vector3<i32>, nbt: NbtCompound);
fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32;
fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32;
fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32;
fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32;
fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32;
fn is_air(&self, local_pos: &Vector3<i32>) -> bool;
fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome;
fn get_blending_data(
&self,
chunk_x: i32,
chunk_z: i32,
) -> Option<&crate::generation::blender::blending_data::BlendingData>;
}
const AIR_BLOCK: Block = Block::AIR;
pub struct StandardChunkFluidLevelSampler {
top_fluid: FluidLevel,
bottom_fluid: FluidLevel,
bottom_y: i32,
}
impl StandardChunkFluidLevelSampler {
#[must_use]
pub fn new(top_fluid: FluidLevel, bottom_fluid: FluidLevel) -> Self {
let bottom_y = top_fluid
.max_y_exclusive()
.min(bottom_fluid.max_y_exclusive());
Self {
top_fluid,
bottom_fluid,
bottom_y,
}
}
}
impl FluidLevelSamplerImpl for StandardChunkFluidLevelSampler {
fn get_fluid_level(&self, _x: i32, y: i32, _z: i32) -> &FluidLevel {
if y < self.bottom_y {
&self.bottom_fluid
} else {
&self.top_fluid
}
}
}
pub struct ProtoChunk {
pub x: i32,
pub z: i32,
pub default_block: &'static BlockState,
biome_mixer_seed: i64,
pub(crate) flat_block_map: Box<[BlockStateId]>,
pub flat_biome_map: Box<[u8]>,
pub flat_surface_height_map: [i16; CHUNK_AREA],
pub flat_ocean_floor_height_map: [i16; CHUNK_AREA],
pub flat_motion_blocking_height_map: [i16; CHUNK_AREA],
pub flat_motion_blocking_no_leaves_height_map: [i16; CHUNK_AREA],
structure_starts: FxHashMap<StructureKeys, StructureInstance>,
height: u16,
bottom_y: i8,
generation_height: u16,
generation_bottom_y: i8,
pub stage: StagedChunkEnum,
pub light: ChunkLight,
pub carving_mask: crate::generation::carver::mask::CarvingMask,
pub blending_data: Option<crate::generation::blender::blending_data::BlendingData>,
pub pending_block_entities: Vec<NbtCompound>,
pending_structure_entities: Vec<NbtCompound>,
pub fluid_ticks: Vec<ScheduledTick<&'static Fluid>>,
}
pub struct TerrainCache {
pub terrain_builder: SurfaceTerrainBuilder,
pub surface_noise: DoublePerlinNoiseSampler,
pub secondary_noise: DoublePerlinNoiseSampler,
}
impl TerrainCache {
#[must_use]
pub fn from_random(random_config: &GlobalRandomConfig) -> Self {
let random = &random_config.base_random_deriver;
let terrain_builder = SurfaceTerrainBuilder::new(random);
let surface_noise = DoublePerlinNoiseBuilder::get_noise_sampler_for_id(
&random_config.base_random_deriver,
&DoublePerlinNoiseParameters::SURFACE,
);
let secondary_noise = DoublePerlinNoiseBuilder::get_noise_sampler_for_id(
&random_config.base_random_deriver,
&DoublePerlinNoiseParameters::SURFACE_SECONDARY,
);
Self {
terrain_builder,
surface_noise,
secondary_noise,
}
}
}
impl ProtoChunk {
#[must_use]
pub fn new(x: i32, z: i32, generator: &super::generator::WorldGenerator) -> Self {
let dimension = generator.dimension();
let height = dimension.height as u16;
let bottom_y = dimension.min_y as i8;
let section_count = (height as usize) / 16;
let (generation_height, generation_bottom_y) = match generator {
super::generator::WorldGenerator::Noise(noise_gen) => {
let shape = noise_gen
.settings
.shape
.trim_height(bottom_y, (dimension.min_y + dimension.height) as u16);
(shape.height, shape.min_y)
}
super::generator::WorldGenerator::Flat(_) => (height, bottom_y),
};
let default_block = match generator {
super::generator::WorldGenerator::Noise(noise_gen) => noise_gen.default_block,
super::generator::WorldGenerator::Flat(_) => Block::AIR.default_state,
};
let biome_mixer_seed = match generator {
super::generator::WorldGenerator::Noise(noise_gen) => noise_gen.biome_mixer_seed,
super::generator::WorldGenerator::Flat(flat_gen) => {
crate::biome::hash_seed(flat_gen.seed)
}
};
let default_heightmap = [i16::MIN; CHUNK_AREA];
Self {
x,
z,
default_block,
biome_mixer_seed,
flat_block_map: vec![BlockStateId::AIR; CHUNK_AREA * height as usize]
.into_boxed_slice(),
flat_biome_map: vec![
Biome::PLAINS.id;
biome_coords::from_block(CHUNK_DIM as i32) as usize
* biome_coords::from_block(CHUNK_DIM as i32) as usize
* biome_coords::from_block(height as i32) as usize
]
.into_boxed_slice(),
flat_surface_height_map: default_heightmap,
flat_ocean_floor_height_map: default_heightmap,
flat_motion_blocking_height_map: default_heightmap,
flat_motion_blocking_no_leaves_height_map: default_heightmap,
structure_starts: FxHashMap::default(),
height,
bottom_y,
generation_height,
generation_bottom_y,
stage: StagedChunkEnum::Empty,
light: ChunkLight {
sky_light: (0..section_count)
.map(|_| LightContainer::new_empty(0))
.collect(),
block_light: (0..section_count)
.map(|_| LightContainer::new_empty(0))
.collect(),
},
carving_mask: crate::generation::carver::mask::CarvingMask::new(
height as i32,
bottom_y as i32,
),
blending_data: None,
pending_block_entities: Vec::new(),
pending_structure_entities: Vec::new(),
fluid_ticks: Vec::new(),
}
}
#[must_use]
pub fn from_chunk_data(
chunk_data: &ChunkData,
generator: &super::generator::WorldGenerator,
) -> Self {
let mut proto_chunk = Self::new(chunk_data.x, chunk_data.z, generator);
proto_chunk.light = chunk_data.light_engine.lock().unwrap().clone();
proto_chunk
.blending_data
.clone_from(&chunk_data.blending_data);
let section_data = &chunk_data.section;
let heightmap_data = chunk_data.heightmap.lock().unwrap();
let block_sections_guard = section_data.block_sections.read().unwrap();
let biome_sections_guard = section_data.biome_sections.read().unwrap();
for (section_idx, block_palette) in block_sections_guard.iter().enumerate() {
let section_base_y = section_idx as i32 * 16;
if section_base_y >= proto_chunk.height() as i32 {
continue;
}
for x in 0..16 {
for y in 0..16 {
for z in 0..16 {
let block_state_id = block_palette.get(x, y, z);
let block_state = BlockState::from_id(block_state_id);
let absolute_y = section_base_y + y as i32 + section_data.min_y;
proto_chunk.set_block_state(x as i32, absolute_y, z as i32, block_state);
}
}
}
if let Some(biome_palette) = biome_sections_guard.get(section_idx) {
for x in 0..4 {
for y in 0..4 {
for z in 0..4 {
let biome_id = biome_palette.get(x, y, z);
let biome_y_idx = (section_idx * 4) + y;
let index = proto_chunk.local_biome_pos_to_biome_index(
x as i32,
biome_y_idx as i32,
z as i32,
);
proto_chunk.flat_biome_map[index] = biome_id;
}
}
}
}
}
drop(block_sections_guard);
drop(biome_sections_guard);
for z in 0..16 {
for x in 0..16 {
let index = Self::local_position_to_height_map_index(x, z);
proto_chunk.flat_motion_blocking_height_map[index] = heightmap_data.get(
ChunkHeightmapType::MotionBlocking,
x,
z,
section_data.min_y,
) as i16;
proto_chunk.flat_motion_blocking_no_leaves_height_map[index] = heightmap_data.get(
ChunkHeightmapType::MotionBlockingNoLeaves,
x,
z,
section_data.min_y,
)
as i16;
proto_chunk.flat_surface_height_map[index] =
heightmap_data.get(ChunkHeightmapType::WorldSurface, x, z, section_data.min_y)
as i16;
}
}
proto_chunk.stage = StagedChunkEnum::from(chunk_data.status);
proto_chunk
}
#[inline]
#[must_use]
pub const fn stage_id(&self) -> u8 {
self.stage as u8
}
#[must_use]
pub const fn height(&self) -> u16 {
self.height
}
#[must_use]
pub const fn bottom_y(&self) -> i8 {
self.bottom_y
}
#[must_use]
pub const fn generation_height(&self) -> u16 {
self.generation_height
}
#[must_use]
pub const fn generation_bottom_y(&self) -> i8 {
self.generation_bottom_y
}
pub fn add_block_entity(&mut self, nbt: NbtCompound) {
self.pending_block_entities.push(nbt);
}
pub fn take_pending_block_entities(&mut self) -> Vec<NbtCompound> {
std::mem::take(&mut self.pending_block_entities)
}
pub fn add_structure_entity(&mut self, nbt: NbtCompound) {
self.pending_structure_entities.push(nbt);
}
fn take_pending_structure_entities(&mut self) -> Vec<NbtCompound> {
std::mem::take(&mut self.pending_structure_entities)
}
pub fn schedule_fluid_tick(&mut self, x: i32, y: i32, z: i32, fluid: &'static Fluid) {
self.fluid_ticks.push(ScheduledTick {
delay: 0,
priority: TickPriority::Normal,
position: BlockPos::new(x, y, z),
value: fluid,
});
}
fn maybe_update_surface_height_map(&mut self, index: usize, y: i16) {
let current_height = self.flat_surface_height_map[index];
self.flat_surface_height_map[index] = current_height.max(y);
}
fn maybe_update_ocean_floor_height_map(&mut self, index: usize, y: i16) {
let current_height = self.flat_ocean_floor_height_map[index];
self.flat_ocean_floor_height_map[index] = current_height.max(y);
}
fn maybe_update_motion_blocking_height_map(&mut self, index: usize, y: i16) {
let current_height = self.flat_motion_blocking_height_map[index];
self.flat_motion_blocking_height_map[index] = current_height.max(y);
}
fn maybe_update_motion_blocking_no_leaves_height_map(&mut self, index: usize, y: i16) {
let current_height = self.flat_motion_blocking_no_leaves_height_map[index];
self.flat_motion_blocking_no_leaves_height_map[index] = current_height.max(y);
}
#[must_use]
pub const fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
match heightmap {
HeightMap::WorldSurfaceWg | HeightMap::WorldSurface => {
self.top_block_height_exclusive(x, z)
}
HeightMap::OceanFloorWg | HeightMap::OceanFloor => {
self.ocean_floor_height_exclusive(x, z)
}
HeightMap::MotionBlocking => self.top_motion_blocking_block_height_exclusive(x, z),
HeightMap::MotionBlockingNoLeaves => {
self.top_motion_blocking_block_no_leaves_height_exclusive(x, z)
}
}
}
#[must_use]
pub const fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let index = Self::local_position_to_height_map_index(x & 15, z & 15);
self.flat_surface_height_map[index] as i32 + 1
}
#[must_use]
pub const fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32 {
let index = Self::local_position_to_height_map_index(x & 15, z & 15);
self.flat_ocean_floor_height_map[index] as i32 + 1
}
#[must_use]
pub const fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
let index = Self::local_position_to_height_map_index(x & 15, z & 15);
self.flat_motion_blocking_height_map[index] as i32 + 1
}
#[must_use]
pub const fn top_motion_blocking_block_no_leaves_height_exclusive(
&self,
x: i32,
z: i32,
) -> i32 {
let index = Self::local_position_to_height_map_index(x & 15, z & 15);
self.flat_motion_blocking_no_leaves_height_map[index] as i32 + 1
}
#[inline]
const fn local_position_to_height_map_index(x: i32, z: i32) -> usize {
x as usize * CHUNK_DIM as usize + z as usize
}
#[inline]
const fn local_pos_to_block_index(&self, x: i32, y: i32, z: i32) -> usize {
self.height() as usize * CHUNK_DIM as usize * x as usize
+ CHUNK_DIM as usize * y as usize
+ z as usize
}
#[inline]
#[must_use]
pub const fn local_biome_pos_to_biome_index(&self, x: i32, y: i32, z: i32) -> usize {
let biome_height = self.height() as usize >> 2;
biome_height * biome_coords::from_block(CHUNK_DIM as i32) as usize * x as usize
+ biome_coords::from_block(CHUNK_DIM as i32) as usize * y as usize
+ z as usize
}
#[inline]
#[must_use]
pub fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
is_air(self.get_block_state(local_pos))
}
#[inline]
#[must_use]
pub fn get_block_state_raw(&self, x: i32, y: i32, z: i32) -> BlockStateId {
let index = self.local_pos_to_block_index(x, y, z);
self.flat_block_map[index]
}
#[inline]
#[must_use]
pub fn get_block_state(&self, local_pos: &Vector3<i32>) -> BlockStateId {
let local_y = local_pos.y - self.bottom_y() as i32;
if local_y < 0 || local_y >= self.height() as i32 {
return Block::VOID_AIR.default_state.id;
}
self.get_block_state_raw(local_pos.x & 15, local_y, local_pos.z & 15)
}
pub fn set_block_state(&mut self, x: i32, y: i32, z: i32, block_state: &BlockState) {
let local_x = x & 15;
let local_y = y - self.bottom_y() as i32;
let local_z = z & 15;
if local_y < 0 || local_y >= self.height() as i32 {
return;
}
if !block_state.is_air() {
let index = Self::local_position_to_height_map_index(local_x, local_z);
let y = y as i16;
self.maybe_update_surface_height_map(index, y);
let block = BlockId::from_state_id(block_state.id);
let blocks_movement = blocks_movement(block_state, block);
if blocks_movement {
self.maybe_update_ocean_floor_height_map(index, y);
}
if blocks_movement || block_state.is_liquid() {
self.maybe_update_motion_blocking_height_map(index, y);
if !block.has_tag(tag::Block::MINECRAFT_LEAVES) {
{
self.maybe_update_motion_blocking_no_leaves_height_map(index, y);
}
}
}
}
let index = self.local_pos_to_block_index(local_x, local_y, local_z);
self.flat_block_map[index] = block_state.id;
}
#[inline]
#[must_use]
pub fn get_biome(&self, x: i32, y: i32, z: i32) -> &'static Biome {
Biome::from_id(self.get_biome_id(x, y, z)).unwrap()
}
#[inline]
#[must_use]
pub fn get_biome_id(&self, x: i32, y: i32, z: i32) -> u8 {
let index = self.local_biome_pos_to_biome_index(
x & 3,
y - biome_coords::from_block(self.bottom_y() as i32),
z & 3,
);
self.flat_biome_map[index]
}
pub fn step_to_biomes(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::Empty);
let start_x = start_block_x(self.x);
let start_z = start_block_z(self.z);
let horizontal_biome_end = biome_coords::from_block(16);
let multi_noise_config =
super::noise::router::multi_noise_sampler::MultiNoiseSamplerBuilderOptions::new(
biome_coords::from_block(start_x),
biome_coords::from_block(start_z),
horizontal_biome_end as usize,
);
let mut multi_noise_sampler =
MultiNoiseSampler::generate(&generator.base_router.multi_noise, &multi_noise_config);
self.populate_biomes(generator, &mut multi_noise_sampler);
self.stage = StagedChunkEnum::Biomes;
}
#[expect(clippy::too_many_lines)]
pub fn step_to_noise(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::StructureReferences);
let settings = generator.settings;
let generation_shape = &settings.shape;
let horizontal_cell_count = CHUNK_DIM / generation_shape.horizontal_cell_block_count();
let start_x = start_block_x(self.x);
let start_z = start_block_z(self.z);
let sampler = StandardChunkFluidLevelSampler::new(
FluidLevel::new(
settings.sea_level,
Block::from_state_id(settings.default_fluid.id),
),
FluidLevel::new(-54, &Block::LAVA),
);
let mut beardifier_structures = Vec::new();
let mut beardifier_junctions = Vec::new();
let mut any_piece_bounding_box: Option<BlockBox> = None;
let chunk_start_x = self.start_block_x();
let chunk_start_z = self.start_block_z();
for (key, instance) in &self.structure_starts {
let structure = pumpkin_data::structures::Structure::get(key);
let terrain_adaptation = match structure.terrain_adaptation {
pumpkin_data::structures::TerrainAdaptation::None => {
crate::generation::noise::router::density_function::beardifier::TerrainAdaptation::None
}
pumpkin_data::structures::TerrainAdaptation::BeardThin => {
crate::generation::noise::router::density_function::beardifier::TerrainAdaptation::BeardThin
}
pumpkin_data::structures::TerrainAdaptation::BeardBox => {
crate::generation::noise::router::density_function::beardifier::TerrainAdaptation::BeardBox
}
pumpkin_data::structures::TerrainAdaptation::Bury => {
crate::generation::noise::router::density_function::beardifier::TerrainAdaptation::Bury
}
pumpkin_data::structures::TerrainAdaptation::Encapsulate => {
crate::generation::noise::router::density_function::beardifier::TerrainAdaptation::Encapsulate
}
};
// Vanilla strictly skips filtering Beardifier parts if adaptation is None early-on
if terrain_adaptation == crate::generation::noise::router::density_function::beardifier::TerrainAdaptation::None {
continue;
}
let collector = match instance {
StructureInstance::Start(pos) => &pos.collector,
StructureInstance::Reference(collector) => collector,
};
let collector = collector.lock().unwrap();
for piece in &collector.pieces {
let bounding_box = piece.get_structure_piece().bounding_box;
// Match `piece.isCloseToChunk(chunkPos, 12)`
// Validates if an expansion 12 blocks out covers the chunk borders
if !bounding_box.intersects_raw_xz(
chunk_start_x - 12,
chunk_start_z - 12,
chunk_start_x + 15 + 12,
chunk_start_z + 15 + 12,
) {
continue;
}
let mut ground_level_delta = 0;
if let Some(jigsaw_piece) = piece.as_any().downcast_ref::<crate::generation::structure::structures::jigsaw::PoolElementStructurePiece>() {
// Java only adds to rigids if projection is RIGID
if jigsaw_piece.projection == crate::generation::structure::structures::jigsaw::JigsawProjection::Rigid {
ground_level_delta = jigsaw_piece.ground_level_delta;
any_piece_bounding_box = any_piece_bounding_box.map_or(Some(bounding_box), |mut b| {
b.encompass(&bounding_box);
Some(b)
});
beardifier_structures.push(
crate::generation::noise::router::density_function::beardifier::BeardifierStructure {
bounding_box,
terrain_adaptation,
ground_level_delta,
}
);
}
for j in &jigsaw_piece.junctions {
let j_x = j.source_x;
let j_z = j.source_z;
// Junction bounds filter (match vanilla proximity checks)
if j_x > chunk_start_x - 12
&& j_z > chunk_start_z - 12
&& j_x < chunk_start_x + 15 + 12
&& j_z < chunk_start_z + 15 + 12
{
beardifier_junctions.push(
crate::generation::noise::router::density_function::beardifier::BeardifierJunction {
x: j_x,
ground_y: j.source_ground_y,
z: j_z,
}
);
let _junction_box = BlockBox::from_pos(BlockPos::new(j_x, j.source_ground_y, j_z));
any_piece_bounding_box = any_piece_bounding_box.map_or(Some(bounding_box), |mut b| {
b.encompass(&bounding_box);
Some(b)
});
}
}
} else {
any_piece_bounding_box = any_piece_bounding_box.map_or(Some(bounding_box), |mut b| {
b.encompass(&bounding_box);
Some(b)
});
beardifier_structures.push(
crate::generation::noise::router::density_function::beardifier::BeardifierStructure {
bounding_box,
terrain_adaptation,
ground_level_delta,
}
);
}
}
}
let affected_box = any_piece_bounding_box.map(|b| b.expand(24, 24, 24));
// Passed the newly mapped beardifier structures & junctions arrays independently!
let mut noise_sampler = ChunkNoiseGenerator::new(
&generator.base_router.noise,
&generator.random_config,
horizontal_cell_count as usize,
start_x,
start_z,
generation_shape,
sampler,
settings.aquifers_enabled,
settings.ore_veins_enabled,
beardifier_structures,
beardifier_junctions,
affected_box,
);
let horizontal_biome_end = biome_coords::from_block(
horizontal_cell_count as i32 * generation_shape.horizontal_cell_block_count() as i32,
);
let surface_config = SurfaceHeightSamplerBuilderOptions::new(
biome_coords::from_block(start_x),
biome_coords::from_block(start_z),
horizontal_biome_end as usize,
generation_shape.min_y as i32,
generation_shape.max_y() as i32,
generation_shape.vertical_cell_block_count() as usize,
);
let mut surface_height_estimate_sampler = SurfaceHeightEstimateSampler::generate(
&generator.base_router.surface_estimator,
&surface_config,
);
self.populate_noise(
generator,
&mut noise_sampler,
&generator.random_config.ore_random_deriver,
&mut surface_height_estimate_sampler,
);
self.stage = StagedChunkEnum::Noise;
}
pub fn step_to_surface(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::Noise);
let start_x = start_block_x(self.x);
let start_z = start_block_z(self.z);
let generation_shape = &generator.settings.shape;
let horizontal_cell_count = CHUNK_DIM / generation_shape.horizontal_cell_block_count();
let horizontal_biome_end = biome_coords::from_block(
horizontal_cell_count as i32 * generation_shape.horizontal_cell_block_count() as i32,
);
let surface_config = SurfaceHeightSamplerBuilderOptions::new(
biome_coords::from_block(start_x),
biome_coords::from_block(start_z),
horizontal_biome_end as usize,
generation_shape.min_y as i32,
generation_shape.max_y() as i32,
generation_shape.vertical_cell_block_count() as usize,
);
let mut surface_height_estimate_sampler = SurfaceHeightEstimateSampler::generate(
&generator.base_router.surface_estimator,
&surface_config,
);
self.build_surface(generator, &mut surface_height_estimate_sampler);
self.stage = StagedChunkEnum::Surface;
}
pub fn step_to_carvers(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::Surface);
super::carver::carve(self, generator);
self.stage = StagedChunkEnum::Carvers;
}
pub fn populate_biomes(
&mut self,
generator: &super::generator::VanillaGenerator,
multi_noise_sampler: &mut MultiNoiseSampler,
) {
let dimension = &generator.dimension;
let active_supplier = if dimension == &Dimension::THE_END {
ActiveSupplier::End(TheEndBiomeSupplier)
} else if dimension == &Dimension::THE_NETHER {
ActiveSupplier::Nether(MultiNoiseBiomeSupplier::NETHER)
} else {
ActiveSupplier::Overworld(MultiNoiseBiomeSupplier::OVERWORLD)
};
let base_supplier: &dyn BiomeSupplier = match &active_supplier {
ActiveSupplier::End(s) => s,
ActiveSupplier::Nether(s) | ActiveSupplier::Overworld(s) => s,
};
let blender = Blender::empty();
let biome_supplier = blender.get_biome_supplier(base_supplier);
let min_y = self.bottom_y();
let bottom_section = section_coords::block_to_section(min_y as i32);
let top_section = section_coords::block_to_section(min_y as i32 + self.height() as i32 - 1);
let start_block_x = start_block_x(self.x);
let start_block_z = start_block_z(self.z);
let start_biome_x = biome_coords::from_block(start_block_x);
let start_biome_z = biome_coords::from_block(start_block_z);
for i in bottom_section..=top_section {
let start_block_y = section_coords::section_to_block(i);
let start_biome_y = biome_coords::from_block(start_block_y);
let biomes_per_section = biome_coords::from_block(CHUNK_DIM as i32);
for x in 0..biomes_per_section {
for y in 0..biomes_per_section {
for z in 0..biomes_per_section {
let biome = biome_supplier.biome(
start_biome_x + x,
start_biome_y + y,
start_biome_z + z,
multi_noise_sampler,
);
let index = self.local_biome_pos_to_biome_index(
x,
start_biome_y + y - biome_coords::from_block(min_y as i32),
z,
);
self.flat_biome_map[index] = biome.id;
}
}
}
}
}
#[expect(clippy::similar_names)]
pub fn populate_noise(
&mut self,
generator: &super::generator::VanillaGenerator,
noise_sampler: &mut ChunkNoiseGenerator,
ore_random_deriver: &XoroshiroSplitter,
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) {
let h_count = noise_sampler.horizontal_cell_block_count() as i32;
let v_count = noise_sampler.vertical_cell_block_count() as i32;
let horizontal_cells = CHUNK_DIM as i32 / h_count;
let minimum_cell_y = noise_sampler.min_y() / v_count as i8;
let cell_height = noise_sampler.height() / v_count as u16;
let delta_y_step = 1.0 / v_count as f64;
let delta_x_z_step = 1.0 / h_count as f64;
noise_sampler.sample_start_density();
for cell_x in 0..horizontal_cells {
noise_sampler.sample_end_density(cell_x);
let sample_start_x = (self.start_cell_x(h_count) + cell_x) * h_count;
let block_x_base = self.start_block_x() + cell_x * h_count;
for cell_z in 0..horizontal_cells {
let sample_start_z = (self.start_cell_z(h_count) + cell_z) * h_count;
let block_z_base = self.start_block_z() + cell_z * h_count;
for cell_y in (0..cell_height).rev() {
noise_sampler.on_sampled_cell_corners(cell_x, cell_y as i32, cell_z);
let sample_start_y = (minimum_cell_y as i32 + cell_y as i32) * v_count;
for local_y in (0..v_count).rev() {
let block_y = sample_start_y + local_y;
noise_sampler.interpolate_y(local_y as f64 * delta_y_step);
for local_x in 0..h_count {
noise_sampler.interpolate_x(local_x as f64 * delta_x_z_step);
let block_x = block_x_base + local_x;
for local_z in 0..h_count {
noise_sampler.interpolate_z(local_z as f64 * delta_x_z_step);
let block_z = block_z_base + local_z;
let block_state = noise_sampler
.sample_block_state(
ore_random_deriver,
sample_start_x,
sample_start_y,
sample_start_z,
local_x,
block_y - sample_start_y,
local_z,
surface_height_estimate_sampler,
)
.unwrap_or(generator.default_block);
self.set_block_state(block_x, block_y, block_z, block_state);
}
}
}
}
}
noise_sampler.swap_buffers();
}
}
pub fn spawn_mobs<T: GenerationCache>(cache: &mut T, block_registry: &dyn WorldPortalExt) {
let chunk = cache.get_center_chunk();
if chunk.stage >= StagedChunkEnum::Spawn {
return;
}
debug_assert_eq!(chunk.stage, StagedChunkEnum::Lighting);
let biome = chunk.get_terrain_gen_biome(
section_to_block(chunk.x),
chunk.bottom_y() as i32 + chunk.height() as i32 - 1,
section_to_block(chunk.z),
);
let x = chunk.x;
let z = chunk.z;
block_registry.spawn_mobs_for_chunk_generation(cache, biome, x, z);
let entities = cache
.get_center_chunk_mut()
.take_pending_structure_entities();
block_registry.spawn_structure_entities(entities);
cache.get_center_chunk_mut().stage = StagedChunkEnum::Spawn;
}
#[must_use]
pub fn get_terrain_gen_biome_id(&self, x: i32, y: i32, z: i32) -> u8 {
let seed_biome_pos = biome::get_biome_blend(
self.bottom_y(),
self.height(),
self.biome_mixer_seed,
x,
y,
z,
);
self.get_biome_id(seed_biome_pos.x, seed_biome_pos.y, seed_biome_pos.z)
}
#[must_use]
pub fn get_terrain_gen_biome(&self, x: i32, y: i32, z: i32) -> &'static Biome {
Biome::from_id(self.get_terrain_gen_biome_id(x, y, z)).unwrap()
}
#[expect(clippy::too_many_lines)]
pub fn build_surface(
&mut self,
generator: &super::generator::VanillaGenerator,
surface_height_estimate_sampler: &mut SurfaceHeightEstimateSampler,
) {
let start_x = chunk_pos::start_block_x(self.x);
let start_z = chunk_pos::start_block_z(self.z);
let min_y = self.bottom_y();
let settings = generator.settings;
let random_config = &generator.random_config;
let terrain_cache = &generator.terrain_cache;
let random = &random_config.base_random_deriver;
let mut context = MaterialRuleContext::new(
self.generation_bottom_y(),
self.generation_height(),
random,
&terrain_cache.terrain_builder,
&terrain_cache.surface_noise,
&terrain_cache.secondary_noise,
settings.sea_level,
);
for local_x in 0..16 {
for local_z in 0..16 {
let x = start_x + local_x;
let z = start_z + local_z;
let mut top_block = self.top_block_height_exclusive(local_x, local_z);
let biome_y = if settings.legacy_random_source {
0
} else {
top_block
};
let this_biome = self.get_terrain_gen_biome_id(x, biome_y, z);
if this_biome == Biome::ERODED_BADLANDS {
terrain_cache
.terrain_builder
.place_badlands_pillar(self, x, z, top_block);
top_block = self.top_block_height_exclusive(local_x, local_z);
}
context.init_horizontal(x, z);
let mut stone_depth_above = 0;
let mut min = i32::MAX;
let mut fluid_height = i32::MIN;
for y in (min_y as i32..top_block).rev() {
let pos = Vector3::new(x, y, z);
let state = self.get_block_state(&pos).to_state();
if state.is_air() {
stone_depth_above = 0;
fluid_height = i32::MIN;
continue;
}
if state.is_liquid() {
if fluid_height == i32::MIN {
fluid_height = y + 1;
}
continue;
}
if min >= y {
let shift = min_y << 4;
min = shift as i32;
for search_y in ((min_y as i32 - 1)..y).rev() {
if search_y < min_y as i32 {
min = search_y + 1;
break;
}
let block_id = self
.get_block_state(&Vector3::new(local_x, search_y, local_z))
.to_block_id();
if !(block_id != AIR_BLOCK
&& block_id != WATER_BLOCK
&& block_id != LAVA_BLOCK)
{
min = search_y + 1;
break;
}
}
}
stone_depth_above += 1;
let stone_depth_below = y - min + 1;
context.init_vertical(stone_depth_above, stone_depth_below, y, fluid_height);
if state.id == self.default_block.id {
context.biome = self.get_terrain_gen_biome(
context.block_pos_x,
context.block_pos_y,
context.block_pos_z,
);
let new_state = try_apply_material_rule(
&settings.surface_rule,
self,
&mut context,
surface_height_estimate_sampler,
);
if let Some(state) = new_state {
self.set_block_state(x, y, z, state);
}
}
}
if this_biome == Biome::FROZEN_OCEAN || this_biome == Biome::DEEP_FROZEN_OCEAN {
let surface_estimate =
estimate_surface_height(&mut context, surface_height_estimate_sampler);
terrain_cache.terrain_builder.place_iceberg(
self,
Biome::from_id(this_biome).unwrap_or(&Biome::PLAINS),
x,
z,
surface_estimate,
top_block,
settings.sea_level,
&random_config.base_random_deriver,
);
}
}
}
}
pub fn generate_features_and_structure<T: GenerationCache>(
cache: &mut T,
block_registry: &dyn WorldPortalExt,
random_config: &GlobalRandomConfig,
) {
let (center_x, center_z, min_y, generation_min_y, generation_height, biomes_in_chunk) = {
let chunk = cache.get_center_chunk();
let mut unique_biomes = Vec::with_capacity(4);
for &biome_id in &chunk.flat_biome_map {
if !unique_biomes.contains(&biome_id) {
unique_biomes.push(biome_id);
}
}
(
chunk.x,
chunk.z,
chunk.bottom_y() as i32,
chunk.generation_bottom_y(),
chunk.generation_height(),
unique_biomes,
)
};
let start_block_x = chunk_pos::start_block_x(center_x);
let start_block_z = chunk_pos::start_block_z(center_z);
let origin_pos = BlockPos::new(start_block_x, min_y, start_block_z);
let population_seed =
Xoroshiro::get_population_seed(random_config.seed, start_block_x, start_block_z);
for step in 0..11 {
Self::generate_structure_step(
cache,
block_registry,
step,
population_seed,
random_config.seed as i64,
);
let mut features_to_run = Vec::new();
for biome_id in &biomes_in_chunk {
if let Some(biome) = Biome::from_id(*biome_id)
&& let Some(features_at_step) = biome.features.get(step)
{
for &feature_id in *features_at_step {
features_to_run.push(feature_id);
}
}
}
features_to_run.sort_unstable();
features_to_run.dedup();
for (p, feature_enum) in features_to_run.into_iter().enumerate() {
if let Some(feature) = PLACED_FEATURES.get(&feature_enum) {
let decorator_seed = get_decorator_seed(population_seed, p as u64, step as u64);
let mut random =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
feature.generate(
cache,
block_registry,
generation_min_y,
generation_height,
feature_enum,
&mut random,
origin_pos,
);
}
}
}
cache.get_center_chunk_mut().stage = StagedChunkEnum::Features;
}
fn generate_structure_step<T: GenerationCache>(
cache: &mut T,
block_registry: &dyn WorldPortalExt,
step: usize,
population_seed: u64,
world_seed: i64,
) {
let mut tasks = Vec::new();
{
let center_chunk = cache.get_center_chunk();
let center_x = center_chunk.x;
let center_z = center_chunk.z;
for (id, instance) in &center_chunk.structure_starts {
let s = Structure::get(id);
if s.step.ordinal() != step {
continue;
}
match instance {
StructureInstance::Start(pos) => tasks.push(pos.collector.clone()),
StructureInstance::Reference(collector) => {
let collector_arc = collector.clone();
if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) {
tasks.push(collector_arc);
}
}
}
}
let radius = 8;
for dx in -radius..=radius {
for dz in -radius..=radius {
if dx == 0 && dz == 0 {
continue;
}
let neighbor_x = center_x + dx;
let neighbor_z = center_z + dz;
if let Some(neighbor) = cache.try_get_proto_chunk(neighbor_x, neighbor_z) {
for (id, instance) in &neighbor.structure_starts {
let s = Structure::get(id);
if s.step.ordinal() != step {
continue;
}
match instance {
StructureInstance::Start(pos) => {
let start_x = chunk_pos::start_block_x(center_x);
let start_z = chunk_pos::start_block_z(center_z);
let end_x = start_x + 15;
let end_z = start_z + 15;
if pos
.get_bounding_box()
.intersects_raw_xz(start_x, start_z, end_x, end_z)
{
let collector_arc = pos.collector.clone();
if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) {
tasks.push(collector_arc);
}
}
}
StructureInstance::Reference(collector) => {
let collector_arc = collector.clone();
if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) {
tasks.push(collector_arc);
}
}
}
}
}
}
}
}
let decorator_seed = get_decorator_seed(population_seed, 0, step as u64);
let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed));
let chunk = cache.get_center_chunk_mut();
for collector_arc in tasks {
let mut collector = collector_arc.lock().unwrap();
collector.generate_in_chunk(chunk, block_registry, &mut random, world_seed);
}
}
#[must_use]
pub fn get_allowed_biomes(set: &StructureSet) -> Vec<u16> {
let mut allowed_biomes = Vec::new();
for entry in set.structures {
let structure = Structure::get(&entry.structure);
if let Some(biomes) = get_tag_ids(
RegistryKey::WorldgenBiome,
structure
.biomes
.strip_prefix('#')
.unwrap_or(structure.biomes),
) {
allowed_biomes.extend_from_slice(biomes);
}
}
allowed_biomes
}
pub fn set_structure_starts(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::Biomes);
let random_config = &generator.random_config;
let settings = generator.settings;
let global_cache = &generator.global_structure_cache;
let calculator = &generator.structure_calculator;
let seed = random_config.seed;
let mut height_sampler =
crate::generation::structure::height_sampler::NoiseHeightSampler::new(
generator,
self.start_block_x(),
self.start_block_z(),
);
for (i, set) in StructureSet::ALL.iter().enumerate() {
let allowed_biomes = &generator.structure_allowed_biomes[&i];
if !should_generate_structure(
&set.placement,
calculator,
self.x,
self.z,
global_cache,
self,
allowed_biomes,
) {
continue;
}
if set.structures.len() == 1 {
if let Some(entry) = set.structures.first() {
self.try_set_structure_start(
global_cache,
settings.sea_level,
entry,
random_config,
&mut height_sampler,
);
}
continue;
}
let mut candidates = set.structures.to_vec();
let carver_seed = get_carver_seed(seed, self.x, self.z);
let mut random: RandomGenerator =
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed));
let mut total_weight: u32 = candidates.iter().map(|e| e.weight).sum();
while !candidates.is_empty() {
let mut roll = random.next_bounded_i32(total_weight as i32);
let mut selected_idx = 0;
for (i, entry) in candidates.iter().enumerate() {
roll -= entry.weight as i32;
if roll < 0 {
selected_idx = i;
break;
}
}
let selected_entry = &candidates[selected_idx];
if self.try_set_structure_start(
global_cache,
settings.sea_level,
selected_entry,
random_config,
&mut height_sampler,
) {
break;
}
let failed_entry = candidates.remove(selected_idx);
total_weight -= failed_entry.weight;
}
}
self.stage = StagedChunkEnum::StructureStart;
}
fn try_set_structure_start(
&mut self,
global_cache: &GlobalStructureCache,
sea_level: i32,
entry: &WeightedEntry,
random_config: &GlobalRandomConfig,
height_sampler: &mut dyn crate::generation::structure::structures::HeightSampler,
) -> bool {
let chunk_x = self.x;
let chunk_z = self.z;
let position =
global_cache.get_or_compute_structure_start(entry.structure, chunk_x, chunk_z, || {
let structure = Structure::get(&entry.structure);
try_generate_structure(
&entry.structure,
structure,
random_config.seed as i64,
self,
sea_level,
Some(height_sampler),
)
});
if let Some(pos) = position {
self.structure_starts
.insert(entry.structure, StructureInstance::Start(pos));
return true;
}
false
}
#[expect(clippy::too_many_lines)]
pub fn set_structure_references(&mut self, generator: &super::generator::VanillaGenerator) {
debug_assert_eq!(self.stage, StagedChunkEnum::StructureStart);
let random_config = &generator.random_config;
let settings = generator.settings;
let dimension = &generator.dimension;
let noise_router = &generator.base_router;
let global_cache = &generator.global_structure_cache;
let calculator = &generator.structure_calculator;
let start_x = chunk_pos::start_block_x(self.x);
let start_z = chunk_pos::start_block_z(self.z);
let end_x = start_x + 15;
let end_z = start_z + 15;
let seed = random_config.seed as i64;
let active_supplier = if *dimension == Dimension::THE_END {
ActiveSupplier::End(TheEndBiomeSupplier)
} else if *dimension == Dimension::THE_NETHER {
ActiveSupplier::Nether(MultiNoiseBiomeSupplier::NETHER)
} else {
ActiveSupplier::Overworld(MultiNoiseBiomeSupplier::OVERWORLD)
};
let base_supplier: &dyn BiomeSupplier = match &active_supplier {
ActiveSupplier::End(s) => s,
ActiveSupplier::Nether(s) | ActiveSupplier::Overworld(s) => s,
};
let blender = Blender::empty();
let biome_supplier = blender.get_biome_supplier(base_supplier);
let multi_noise_config = MultiNoiseSamplerBuilderOptions::new(0, 0, 0);
let mut multi_noise_sampler =
MultiNoiseSampler::generate(&noise_router.multi_noise, &multi_noise_config);
let mut height_sampler =
crate::generation::structure::height_sampler::NoiseHeightSampler::new(
generator, start_x, start_z,
);
let mut references = Vec::new();
// Constant across every chunk in the dimension, so hoist it out of the loop
// and out of the (cached) structure-start computation below.
let chunk_min_y = self.bottom_y() as i32;
for (set_index, set) in StructureSet::ALL.iter().enumerate() {
let mut candidate_chunks = Vec::new();
match &set.placement.placement_type {
StructurePlacementType::RandomSpread(spread) => {
let region_x = pumpkin_util::math::floor_div(self.x, spread.spacing);
let region_z = pumpkin_util::math::floor_div(self.z, spread.spacing);
for rx in (region_x - 1)..=(region_x + 1) {
for rz in (region_z - 1)..=(region_z + 1) {
candidate_chunks.push(
crate::generation::structure::placement::get_structure_chunk_in_region(
spread,
seed,
rx,
rz,
set.placement.salt,
)
);
}
}
}
StructurePlacementType::ConcentricRings(rings) => {
let allowed_biomes = Self::get_allowed_biomes(set);
let strongholds = global_cache.get_or_calculate_strongholds(
seed,
rings,
self,
&allowed_biomes,
);
for &(cx, cz) in strongholds {
if (cx - self.x).abs() <= 8 && (cz - self.z).abs() <= 8 {
candidate_chunks.push((cx, cz));
}
}
}
}
for (candidate_chunk_x, candidate_chunk_z) in candidate_chunks {
if !should_generate_structure(
&set.placement,
calculator,
candidate_chunk_x,
candidate_chunk_z,
global_cache,
self,
&generator.structure_allowed_biomes[&set_index],
) {
continue;
}
if (candidate_chunk_x - self.x).abs() <= 8
&& (candidate_chunk_z - self.z).abs() <= 8
{
for entry in set.structures {
let structure = Structure::get(&entry.structure);
// A structure's placement depends only on its start chunk and the
// world seed, so cache it: otherwise every surrounding chunk whose
// references overlap it would re-run the (expensive) jigsaw
// expansion. `context` is only built on a cache miss.
let start_data = global_cache.get_or_compute_structure_start(
entry.structure,
candidate_chunk_x,
candidate_chunk_z,
|| {
let context = StructureGeneratorContext {
seed,
chunk_x: candidate_chunk_x,
chunk_z: candidate_chunk_z,
random: create_chunk_random(
seed,
candidate_chunk_x,
candidate_chunk_z,
),
sea_level: settings.sea_level,
min_y: chunk_min_y,
height_sampler: Some(&mut height_sampler),
structure_key: Some(entry.structure),
};
lazily_generate_structure(
&entry.structure,
structure,
context,
&biome_supplier,
&mut multi_noise_sampler,
)
},
);
if let Some(start_data) = start_data
&& start_data
.get_bounding_box()
.intersects_raw_xz(start_x, start_z, end_x, end_z)
{
references.push((entry.structure, start_data.collector.clone()));
break;
}
}
}
}
}
for (key, pos) in references {
self.structure_starts
.entry(key)
.or_insert_with(|| StructureInstance::Reference(pos));
}
self.stage = StagedChunkEnum::StructureReferences;
}
const fn start_cell_x(&self, horizontal_cell_block_count: i32) -> i32 {
self.start_block_x() / horizontal_cell_block_count
}
const fn start_cell_z(&self, horizontal_cell_block_count: i32) -> i32 {
self.start_block_z() / horizontal_cell_block_count
}
const fn start_block_x(&self) -> i32 {
start_block_x(self.x)
}
const fn start_block_z(&self) -> i32 {
start_block_z(self.z)
}
}
impl BlockAccessor for ProtoChunk {
fn get_block(&self, position: &BlockPos) -> &'static Block {
self.get_block_state(&position.0).to_block()
}
fn get_block_state(&self, position: &BlockPos) -> &'static BlockState {
self.get_block_state(&position.0).to_state()
}
fn get_block_state_id(&self, position: &BlockPos) -> BlockStateId {
self.get_block_state(&position.0)
}
fn get_block_and_state(&self, position: &BlockPos) -> (&'static Block, &'static BlockState) {
let id = self.get_block_state(&position.0);
BlockState::from_id_with_block(id)
}
}
impl BlockPlacer for ProtoChunk {
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId {
self.get_block_state(pos)
}
fn set_block_state(&mut self, pos: &Vector3<i32>, state: &BlockState) {
Self::set_block_state(self, pos.x, pos.y, pos.z, state);
}
fn add_block_entity(&mut self, nbt: NbtCompound) {
self.add_block_entity(nbt);
}
}
impl GenerationCache for ProtoChunk {
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk {
self
}
fn get_center_chunk(&self) -> &ProtoChunk {
self
}
fn get_chunk_mut(&mut self, cx: i32, cz: i32) -> Option<&mut ProtoChunk> {
(cx == self.x && cz == self.z).then_some(self)
}
fn get_chunk(&self, cx: i32, cz: i32) -> Option<&ProtoChunk> {
(cx == self.x && cz == self.z).then_some(self)
}
fn try_get_proto_chunk(&self, cx: i32, cz: i32) -> Option<&ProtoChunk> {
self.get_chunk(cx, cz)
}
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId {
Self::get_block_state(self, pos)
}
fn get_fluid_and_fluid_state(&self, _pos: &Vector3<i32>) -> (Fluid, FluidState) {
(
Fluid::EMPTY,
FluidState {
height: 0.0,
level: 0,
is_empty: true,
blast_resistance: 0.0,
block_state_id: BlockStateId::AIR,
is_still: false,
is_source: false,
falling: false,
},
)
}
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState) {
Self::set_block_state(self, pos.x, pos.y, pos.z, block_state);
}
fn add_block_entity(&mut self, _pos: &Vector3<i32>, nbt: NbtCompound) {
self.add_block_entity(nbt);
}
fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
Self::top_motion_blocking_block_height_exclusive(self, x, z)
}
fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32 {
Self::top_motion_blocking_block_no_leaves_height_exclusive(self, x, z)
}
fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
Self::get_top_y(self, heightmap, x, z)
}
fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
Self::top_block_height_exclusive(self, x, z)
}
fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32 {
Self::ocean_floor_height_exclusive(self, x, z)
}
fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
self.is_air(local_pos)
}
fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome {
Self::get_biome(self, x, y, z)
}
fn get_blending_data(
&self,
_cx: i32,
_cz: i32,
) -> Option<&crate::generation::blender::blending_data::BlendingData> {
None
}
}