mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: Add Ocean Monument Structure Generation (#2737)
* feat: Implement ocean monument structures * Fix monuments being cut in half * Fix structure generation height handling
This commit is contained in:
@@ -360,17 +360,21 @@ mod tests {
|
||||
let Chunk::Proto(chunk) = chunk else {
|
||||
panic!("features stage should return a proto chunk");
|
||||
};
|
||||
let mut hash = 0xcbf29ce484222325u64;
|
||||
let mut non_air = 0;
|
||||
for y in 123..=146 {
|
||||
for x in -4896..=-4881 {
|
||||
for z in -4405..=-4393 {
|
||||
for z in -4400..=-4393 {
|
||||
let state =
|
||||
chunk.get_block_state(&pumpkin_util::math::vector3::Vector3::new(x, y, z));
|
||||
hash ^= u64::from(state.as_u16());
|
||||
hash = hash.wrapping_mul(0x100000001b3);
|
||||
non_air += usize::from(!state.to_state().is_air());
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(non_air, 59);
|
||||
assert_eq!(hash, 0x5af3_06b3_536d_8053);
|
||||
assert!(chunk.pending_block_entities.iter().any(|nbt| {
|
||||
nbt.get_string("id") == Some("minecraft:skull")
|
||||
&& nbt.get_int("x") == Some(-4888)
|
||||
|
||||
@@ -180,6 +180,11 @@ impl TerrainCache {
|
||||
}
|
||||
|
||||
impl ProtoChunk {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn has_structure(&self, key: StructureKeys) -> bool {
|
||||
self.structure_starts.contains_key(&key)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new(x: i32, z: i32, generator: &super::generator::WorldGenerator) -> Self {
|
||||
let dimension = generator.dimension();
|
||||
@@ -348,7 +353,21 @@ impl ProtoChunk {
|
||||
}
|
||||
}
|
||||
|
||||
proto_chunk.stage = StagedChunkEnum::from(chunk_data.status);
|
||||
let saved_stage = StagedChunkEnum::from(chunk_data.status);
|
||||
proto_chunk.stage = saved_stage;
|
||||
if let super::generator::WorldGenerator::Noise(generator) = generator
|
||||
&& (StagedChunkEnum::StructureStart..StagedChunkEnum::Features).contains(&saved_stage)
|
||||
{
|
||||
// Structure starts and references are currently transient proto-chunk data.
|
||||
// Rebuild them when resuming a partially generated chunk so structures that
|
||||
// cross chunk boundaries are not truncated at the unload boundary.
|
||||
proto_chunk.stage = StagedChunkEnum::Biomes;
|
||||
proto_chunk.set_structure_starts(generator);
|
||||
if saved_stage >= StagedChunkEnum::StructureReferences {
|
||||
proto_chunk.set_structure_references(generator);
|
||||
}
|
||||
proto_chunk.stage = saved_stage;
|
||||
}
|
||||
proto_chunk
|
||||
}
|
||||
|
||||
@@ -1300,7 +1319,7 @@ impl ProtoChunk {
|
||||
global_cache,
|
||||
settings.sea_level,
|
||||
entry,
|
||||
random_config,
|
||||
generator,
|
||||
&mut height_sampler,
|
||||
);
|
||||
}
|
||||
@@ -1332,7 +1351,7 @@ impl ProtoChunk {
|
||||
global_cache,
|
||||
settings.sea_level,
|
||||
selected_entry,
|
||||
random_config,
|
||||
generator,
|
||||
&mut height_sampler,
|
||||
) {
|
||||
break;
|
||||
@@ -1350,9 +1369,28 @@ impl ProtoChunk {
|
||||
global_cache: &GlobalStructureCache,
|
||||
sea_level: i32,
|
||||
entry: &WeightedEntry,
|
||||
random_config: &GlobalRandomConfig,
|
||||
generator: &super::generator::VanillaGenerator,
|
||||
height_sampler: &mut dyn crate::generation::structure::structures::HeightSampler,
|
||||
) -> bool {
|
||||
if entry.structure == StructureKeys::Monument {
|
||||
let config = MultiNoiseSamplerBuilderOptions::new(0, 0, 0);
|
||||
let mut sampler =
|
||||
MultiNoiseSampler::generate(&generator.base_router.multi_noise, &config);
|
||||
let center_x = chunk_pos::get_center_x(self.x);
|
||||
let center_z = chunk_pos::get_center_z(self.z);
|
||||
let start_y = height_sampler.estimate_ocean_floor_height(center_x, center_z);
|
||||
if !crate::generation::structure::structures::ocean_monument::has_valid_biomes(
|
||||
&MultiNoiseBiomeSupplier::OVERWORLD,
|
||||
&mut sampler,
|
||||
self.x,
|
||||
self.z,
|
||||
sea_level,
|
||||
start_y,
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let chunk_x = self.x;
|
||||
let chunk_z = self.z;
|
||||
let position =
|
||||
@@ -1361,7 +1399,7 @@ impl ProtoChunk {
|
||||
try_generate_structure(
|
||||
&entry.structure,
|
||||
structure,
|
||||
random_config.seed as i64,
|
||||
generator.random_config.seed as i64,
|
||||
self,
|
||||
sea_level,
|
||||
Some(height_sampler),
|
||||
|
||||
@@ -6,6 +6,36 @@ mod test {
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_util::world_seed::Seed;
|
||||
|
||||
#[test]
|
||||
fn structure_references_are_rebuilt_when_resuming_generation() {
|
||||
use crate::chunk_system::chunk_state::Chunk;
|
||||
use pumpkin_config::lighting::LightingEngineConfig;
|
||||
use pumpkin_data::structures::StructureKeys;
|
||||
|
||||
let seed = Seed(1_782_124_772_053_846_960);
|
||||
let world_gen = get_world_gen(seed, Dimension::OVERWORLD, false, Vec::new(), String::new());
|
||||
let WorldGenerator::Noise(generator) = &*world_gen else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
// This chunk contains the far edge of a monument starting at (-553, 173).
|
||||
let mut proto = ProtoChunk::new(-553, 174, &world_gen);
|
||||
proto.step_to_biomes(generator);
|
||||
proto.set_structure_starts(generator);
|
||||
proto.set_structure_references(generator);
|
||||
assert!(proto.has_structure(StructureKeys::Monument));
|
||||
|
||||
let mut staged = Chunk::Proto(Box::new(proto));
|
||||
staged.upgrade_to_level_chunk(&Dimension::OVERWORLD, &LightingEngineConfig::Default);
|
||||
let Chunk::Level(chunk_data) = staged else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let resumed = ProtoChunk::from_chunk_data(&chunk_data, &world_gen);
|
||||
assert_eq!(resumed.stage, StagedChunkEnum::StructureReferences);
|
||||
assert!(resumed.has_structure(StructureKeys::Monument));
|
||||
}
|
||||
|
||||
// Regression test for transposed heightmaps during Noise-stage chunk resume.
|
||||
// Flat terrain cannot expose this bug, so use a sloped chunk.
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::{Block, BlockId, block_properties::blocks_movement};
|
||||
use pumpkin_util::math::floor_div;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct NoiseHeightSampler<'a> {
|
||||
generator: &'a VanillaGenerator,
|
||||
preliminary: SurfaceHeightEstimateSampler<'a>,
|
||||
heights: FxHashMap<(i32, i32), i32>,
|
||||
ocean_floor_heights: FxHashMap<(i32, i32), i32>,
|
||||
}
|
||||
|
||||
impl<'a> NoiseHeightSampler<'a> {
|
||||
@@ -42,10 +43,11 @@ impl<'a> NoiseHeightSampler<'a> {
|
||||
generator,
|
||||
preliminary,
|
||||
heights: FxHashMap::default(),
|
||||
ocean_floor_heights: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_column(&mut self, x: i32, z: i32) -> i32 {
|
||||
fn sample_column(&mut self, x: i32, z: i32, ocean_floor: bool) -> i32 {
|
||||
let settings = self.generator.settings;
|
||||
let shape = &settings.shape;
|
||||
let horizontal = i32::from(shape.horizontal_cell_block_count());
|
||||
@@ -100,7 +102,11 @@ impl<'a> NoiseHeightSampler<'a> {
|
||||
&mut self.preliminary,
|
||||
)
|
||||
.unwrap_or(self.generator.default_block);
|
||||
if !state.is_air() {
|
||||
if if ocean_floor {
|
||||
blocks_movement(state, BlockId::from_state_id(state.id))
|
||||
} else {
|
||||
!state.is_air()
|
||||
} {
|
||||
return y + 1;
|
||||
}
|
||||
}
|
||||
@@ -116,8 +122,20 @@ impl HeightSampler for NoiseHeightSampler<'_> {
|
||||
if let Some(height) = self.heights.get(&key) {
|
||||
return *height;
|
||||
}
|
||||
let height = self.sample_column(block_x, block_z);
|
||||
let height = self.sample_column(block_x, block_z, false);
|
||||
self.heights.insert(key, height);
|
||||
height
|
||||
}
|
||||
|
||||
fn estimate_ocean_floor_height(&mut self, block_x: i32, block_z: i32) -> i32 {
|
||||
let key = (block_x, block_z);
|
||||
if let Some(height) = self.ocean_floor_heights.get(&key) {
|
||||
return *height;
|
||||
}
|
||||
// Vanilla's structure helper asks for the highest occupied block, while
|
||||
// heightmaps store the first free block above it.
|
||||
let height = self.sample_column(block_x, block_z, true) - 1;
|
||||
self.ocean_floor_heights.insert(key, height);
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,23 @@ use crate::{
|
||||
noise::router::multi_noise_sampler::MultiNoiseSampler,
|
||||
structure::structures::{
|
||||
StructureGenerator, StructureGeneratorContext, StructurePosition,
|
||||
buried_treasure::BuriedTreasureGenerator, create_chunk_random,
|
||||
desert_pyramid::DesertPyramidGenerator, end_city::EndCityGenerator,
|
||||
igloo::IglooGenerator, jigsaw::JigsawGenerator, jungle_temple::JungleTempleGenerator,
|
||||
mansion::MansionGenerator, mineshaft::MineshaftGenerator,
|
||||
nether_fortress::NetherFortressGenerator, nether_fossil::NetherFossilGenerator,
|
||||
ocean_monument::OceanMonumentGenerator, ocean_ruin::OceanRuinGenerator,
|
||||
ruined_portal::RuinedPortalGenerator, shipwreck::ShipwreckGenerator,
|
||||
stronghold::StrongholdGenerator, swamp_hut::SwampHutGenerator,
|
||||
buried_treasure::BuriedTreasureGenerator,
|
||||
create_chunk_random,
|
||||
desert_pyramid::DesertPyramidGenerator,
|
||||
end_city::EndCityGenerator,
|
||||
igloo::IglooGenerator,
|
||||
jigsaw::JigsawGenerator,
|
||||
jungle_temple::JungleTempleGenerator,
|
||||
mansion::MansionGenerator,
|
||||
mineshaft::MineshaftGenerator,
|
||||
nether_fortress::NetherFortressGenerator,
|
||||
nether_fossil::NetherFossilGenerator,
|
||||
ocean_monument::{OceanMonumentGenerator, has_valid_biomes},
|
||||
ocean_ruin::OceanRuinGenerator,
|
||||
ruined_portal::RuinedPortalGenerator,
|
||||
shipwreck::ShipwreckGenerator,
|
||||
stronghold::StrongholdGenerator,
|
||||
swamp_hut::SwampHutGenerator,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -180,10 +189,31 @@ pub fn try_generate_structure(
|
||||
pub fn lazily_generate_structure(
|
||||
key: &StructureKeys,
|
||||
structure: &Structure,
|
||||
context: StructureGeneratorContext, // Replaces 5 separate arguments!
|
||||
mut context: StructureGeneratorContext, // Replaces 5 separate arguments!
|
||||
biome_supplier: &dyn BiomeSupplier,
|
||||
multi_noise_sampler: &mut MultiNoiseSampler,
|
||||
) -> Option<StructurePosition> {
|
||||
if *key == StructureKeys::Monument {
|
||||
let center_x = crate::generation::positions::chunk_pos::get_center_x(context.chunk_x);
|
||||
let center_z = crate::generation::positions::chunk_pos::get_center_z(context.chunk_z);
|
||||
let start_y = context
|
||||
.height_sampler
|
||||
.as_deref_mut()
|
||||
.map_or(context.sea_level, |sampler| {
|
||||
sampler.estimate_ocean_floor_height(center_x, center_z)
|
||||
});
|
||||
if !has_valid_biomes(
|
||||
biome_supplier,
|
||||
multi_noise_sampler,
|
||||
context.chunk_x,
|
||||
context.chunk_z,
|
||||
context.sea_level,
|
||||
start_y,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let structure_pos = generate_structure_position(key, structure, context);
|
||||
|
||||
if let Some(pos) = structure_pos {
|
||||
|
||||
@@ -149,7 +149,7 @@ impl StructurePiece {
|
||||
}
|
||||
}
|
||||
|
||||
const fn offset_pos(&self, x: i32, y: i32, z: i32) -> Vector3<i32> {
|
||||
pub(crate) const fn offset_pos(&self, x: i32, y: i32, z: i32) -> Vector3<i32> {
|
||||
Vector3::new(
|
||||
self.apply_x_transform(x, z),
|
||||
self.apply_y_transform(y),
|
||||
@@ -672,6 +672,10 @@ pub trait StructureGenerator {
|
||||
|
||||
pub trait HeightSampler {
|
||||
fn estimate_height(&mut self, block_x: i32, block_z: i32) -> i32;
|
||||
|
||||
fn estimate_ocean_floor_height(&mut self, block_x: i32, block_z: i32) -> i32 {
|
||||
self.estimate_height(block_x, block_z)
|
||||
}
|
||||
}
|
||||
|
||||
impl HeightSampler
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_util::{
|
||||
math::{block_box::BlockBox, position::BlockPos},
|
||||
random::RandomGenerator,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ProtoChunk,
|
||||
generation::{
|
||||
positions::chunk_pos::{get_center_x, get_center_z},
|
||||
structure::{
|
||||
piece::StructurePieceType,
|
||||
structures::{
|
||||
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
||||
StructurePiecesCollector, StructurePosition, WorldPortalExt,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct OceanMonumentGenerator;
|
||||
|
||||
impl StructureGenerator for OceanMonumentGenerator {
|
||||
fn get_structure_position(
|
||||
&self,
|
||||
context: StructureGeneratorContext<'_>,
|
||||
) -> Option<StructurePosition> {
|
||||
let chunk_center_x = get_center_x(context.chunk_x);
|
||||
let chunk_center_z = get_center_z(context.chunk_z);
|
||||
|
||||
let bounding_box = BlockBox::new(
|
||||
chunk_center_x - 29,
|
||||
context.min_y,
|
||||
chunk_center_z - 29,
|
||||
chunk_center_x + 29,
|
||||
256,
|
||||
chunk_center_z + 29,
|
||||
);
|
||||
|
||||
let mut collector = StructurePiecesCollector::default();
|
||||
collector.add_piece(Box::new(OceanMonumentPiece {
|
||||
piece: StructurePiece::new(StructurePieceType::OceanMonumentBase, bounding_box, 0),
|
||||
}));
|
||||
|
||||
Some(StructurePosition {
|
||||
start_pos: BlockPos::new(chunk_center_x, 64, chunk_center_z),
|
||||
collector: Arc::new(collector.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OceanMonumentPiece {
|
||||
piece: StructurePiece,
|
||||
}
|
||||
|
||||
impl StructurePieceBase for OceanMonumentPiece {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn get_structure_piece(&self) -> &StructurePiece {
|
||||
&self.piece
|
||||
}
|
||||
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
|
||||
&mut self.piece
|
||||
}
|
||||
fn place(
|
||||
&mut self,
|
||||
chunk: &mut ProtoChunk,
|
||||
_block_registry: &dyn WorldPortalExt,
|
||||
_random: &mut RandomGenerator,
|
||||
_seed: i64,
|
||||
chunk_box: &BlockBox,
|
||||
) {
|
||||
let origin = self.piece.bounding_box.min;
|
||||
let sample_y = chunk.get_top_y(&pumpkin_util::HeightMap::OceanFloorWg, origin.x, origin.z);
|
||||
let start_y = if sample_y <= 0 { 40 } else { sample_y - 4 };
|
||||
|
||||
// Draw a giant 58x58 pyramid/stepped prismarine temple!
|
||||
for dy in 0..15 {
|
||||
let half_sz = 29 - dy;
|
||||
if half_sz < 0 {
|
||||
break;
|
||||
}
|
||||
let min_x = origin.x - half_sz;
|
||||
let max_x = origin.x + half_sz;
|
||||
let min_z = origin.z - half_sz;
|
||||
let max_z = origin.z + half_sz;
|
||||
let y = start_y + dy;
|
||||
|
||||
for x in min_x..=max_x {
|
||||
for z in min_z..=max_z {
|
||||
if chunk_box.contains(x, y, z) {
|
||||
let is_border = x == min_x || x == max_x || z == min_z || z == max_z;
|
||||
let block = if is_border {
|
||||
Block::DARK_PRISMARINE
|
||||
} else if dy % 3 == 0 {
|
||||
Block::PRISMARINE_BRICKS
|
||||
} else {
|
||||
Block::PRISMARINE
|
||||
};
|
||||
chunk.set_block_state(x, y, z, block.default_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
use pumpkin_util::{math::block_box::BlockBox, random::RandomGenerator};
|
||||
|
||||
use crate::ProtoChunk;
|
||||
|
||||
use super::{BASE_BLACK, BASE_GRAY, BASE_LIGHT, LAMP, MonumentBuilding};
|
||||
|
||||
impl MonumentBuilding {
|
||||
pub(super) fn place_building(
|
||||
&self,
|
||||
chunk: &mut ProtoChunk,
|
||||
random: &mut RandomGenerator,
|
||||
chunk_box: &BlockBox,
|
||||
) {
|
||||
let sea_level = self.sea_level;
|
||||
let water_height = sea_level.max(64) - self.base.piece.bounding_box.min.y;
|
||||
self.base
|
||||
.water(chunk, chunk_box, sea_level, 0, 0, 0, 58, water_height, 58);
|
||||
self.generate_wing(chunk, chunk_box, false, 0);
|
||||
self.generate_wing(chunk, chunk_box, true, 33);
|
||||
self.generate_entrance_arches(chunk, chunk_box);
|
||||
self.generate_entrance_wall(chunk, chunk_box);
|
||||
self.generate_roof(chunk, chunk_box);
|
||||
self.generate_lower_wall(chunk, chunk_box);
|
||||
self.generate_middle_wall(chunk, chunk_box);
|
||||
self.generate_upper_wall(chunk, chunk_box);
|
||||
|
||||
for pillar_x in 0..7 {
|
||||
let mut pillar_z = 0;
|
||||
while pillar_z < 7 {
|
||||
if pillar_z == 0 && pillar_x == 3 {
|
||||
pillar_z = 6;
|
||||
}
|
||||
let x = pillar_x * 9;
|
||||
let z = pillar_z * 9;
|
||||
for dx in 0..4 {
|
||||
for dz in 0..4 {
|
||||
self.base
|
||||
.block(chunk, chunk_box, x + dx, 0, z + dz, BASE_LIGHT);
|
||||
self.base.piece.fill_downwards(
|
||||
chunk,
|
||||
BASE_LIGHT,
|
||||
x + dx,
|
||||
-1,
|
||||
z + dz,
|
||||
chunk_box,
|
||||
);
|
||||
}
|
||||
}
|
||||
pillar_z += if pillar_x == 0 || pillar_x == 6 { 1 } else { 6 };
|
||||
}
|
||||
}
|
||||
|
||||
for offset in 0..5 {
|
||||
self.base.water(
|
||||
chunk,
|
||||
chunk_box,
|
||||
sea_level,
|
||||
-1 - offset,
|
||||
offset * 2,
|
||||
-1 - offset,
|
||||
-1 - offset,
|
||||
23,
|
||||
58 + offset,
|
||||
);
|
||||
self.base.water(
|
||||
chunk,
|
||||
chunk_box,
|
||||
sea_level,
|
||||
58 + offset,
|
||||
offset * 2,
|
||||
-1 - offset,
|
||||
58 + offset,
|
||||
23,
|
||||
58 + offset,
|
||||
);
|
||||
self.base.water(
|
||||
chunk,
|
||||
chunk_box,
|
||||
sea_level,
|
||||
-offset,
|
||||
offset * 2,
|
||||
-1 - offset,
|
||||
57 + offset,
|
||||
23,
|
||||
-1 - offset,
|
||||
);
|
||||
self.base.water(
|
||||
chunk,
|
||||
chunk_box,
|
||||
sea_level,
|
||||
-offset,
|
||||
offset * 2,
|
||||
58 + offset,
|
||||
57 + offset,
|
||||
23,
|
||||
58 + offset,
|
||||
);
|
||||
}
|
||||
|
||||
for child in &self.children {
|
||||
if child.piece.piece.bounding_box.intersects(chunk_box) {
|
||||
child.place(chunk, random, chunk_box, sea_level, &self.graph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_wing(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox, flipped: bool, x: i32) {
|
||||
let p = &self.base;
|
||||
if !p.chunk_intersects(chunk_box, x, 0, x + 23, 20) {
|
||||
return;
|
||||
}
|
||||
p.fill(chunk, chunk_box, x, 0, 0, x + 24, 0, 20, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, x, 1, 0, x + 24, 10, 20);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + i,
|
||||
i + 1,
|
||||
i,
|
||||
x + i,
|
||||
i + 1,
|
||||
20,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + i + 7,
|
||||
i + 5,
|
||||
i + 7,
|
||||
x + i + 7,
|
||||
i + 5,
|
||||
20,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + 17 - i,
|
||||
i + 5,
|
||||
i + 7,
|
||||
x + 17 - i,
|
||||
i + 5,
|
||||
20,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + 24 - i,
|
||||
i + 1,
|
||||
i,
|
||||
x + 24 - i,
|
||||
i + 1,
|
||||
20,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + i + 1,
|
||||
i + 1,
|
||||
i,
|
||||
x + 23 - i,
|
||||
i + 1,
|
||||
i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + i + 8,
|
||||
i + 5,
|
||||
i + 7,
|
||||
x + 16 - i,
|
||||
i + 5,
|
||||
i + 7,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
p.fill(chunk, chunk_box, x + 4, 4, 4, x + 6, 4, 20, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, x + 7, 4, 4, x + 17, 4, 6, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, x + 18, 4, 4, x + 20, 4, 20, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, x + 11, 8, 11, x + 13, 8, 20, BASE_GRAY);
|
||||
for z in [12, 15, 18] {
|
||||
p.block(chunk, chunk_box, x + 12, 9, z, BASE_LIGHT);
|
||||
}
|
||||
let left = x + if flipped { 19 } else { 5 };
|
||||
let right = x + if flipped { 5 } else { 19 };
|
||||
for z in (5..=20).rev().step_by(3) {
|
||||
p.block(chunk, chunk_box, left, 5, z, BASE_LIGHT);
|
||||
}
|
||||
for z in (7..=19).rev().step_by(3) {
|
||||
p.block(chunk, chunk_box, right, 5, z, BASE_LIGHT);
|
||||
}
|
||||
for i in 0..4 {
|
||||
let pos = if flipped {
|
||||
x + 24 - (17 - i * 3)
|
||||
} else {
|
||||
x + 17 - i * 3
|
||||
};
|
||||
p.block(chunk, chunk_box, pos, 5, 5, BASE_LIGHT);
|
||||
}
|
||||
p.block(chunk, chunk_box, right, 5, 5, BASE_LIGHT);
|
||||
p.fill(chunk, chunk_box, x + 11, 1, 12, x + 13, 7, 12, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, x + 12, 1, 11, x + 12, 7, 13, BASE_GRAY);
|
||||
}
|
||||
|
||||
fn generate_entrance_arches(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
|
||||
let p = &self.base;
|
||||
if !p.chunk_intersects(chunk_box, 22, 5, 35, 17) {
|
||||
return;
|
||||
}
|
||||
p.water(chunk, chunk_box, self.sea_level, 25, 0, 0, 32, 8, 20);
|
||||
for i in 0..4 {
|
||||
let z = 5 + i * 4;
|
||||
p.fill(chunk, chunk_box, 24, 2, z, 24, 4, z, BASE_LIGHT);
|
||||
p.fill(chunk, chunk_box, 22, 4, z, 23, 4, z, BASE_LIGHT);
|
||||
p.block(chunk, chunk_box, 25, 5, z, BASE_LIGHT);
|
||||
p.block(chunk, chunk_box, 26, 6, z, BASE_LIGHT);
|
||||
p.block(chunk, chunk_box, 26, 5, z, LAMP);
|
||||
p.fill(chunk, chunk_box, 33, 2, z, 33, 4, z, BASE_LIGHT);
|
||||
p.fill(chunk, chunk_box, 34, 4, z, 35, 4, z, BASE_LIGHT);
|
||||
p.block(chunk, chunk_box, 32, 5, z, BASE_LIGHT);
|
||||
p.block(chunk, chunk_box, 31, 6, z, BASE_LIGHT);
|
||||
p.block(chunk, chunk_box, 31, 5, z, LAMP);
|
||||
p.fill(chunk, chunk_box, 27, 6, z, 30, 6, z, BASE_GRAY);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_entrance_wall(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
|
||||
let p = &self.base;
|
||||
if !p.chunk_intersects(chunk_box, 15, 20, 42, 21) {
|
||||
return;
|
||||
}
|
||||
p.fill(chunk, chunk_box, 15, 0, 21, 42, 0, 21, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 26, 1, 21, 31, 3, 21);
|
||||
for &(x0, y, x1) in &[
|
||||
(21, 12, 36),
|
||||
(17, 11, 40),
|
||||
(16, 10, 41),
|
||||
(15, 9, 42),
|
||||
(15, 8, 42),
|
||||
(15, 7, 42),
|
||||
(16, 6, 41),
|
||||
(17, 5, 40),
|
||||
(21, 4, 36),
|
||||
(22, 3, 26),
|
||||
(31, 3, 35),
|
||||
(23, 2, 25),
|
||||
(32, 2, 34),
|
||||
] {
|
||||
p.fill(chunk, chunk_box, x0, y, 21, x1, y, 21, BASE_GRAY);
|
||||
}
|
||||
p.fill(chunk, chunk_box, 28, 4, 20, 29, 4, 21, BASE_LIGHT);
|
||||
for &(x, y) in &[(27, 3), (30, 3), (26, 2), (31, 2), (25, 1), (32, 1)] {
|
||||
p.block(chunk, chunk_box, x, y, 21, BASE_LIGHT);
|
||||
}
|
||||
for i in 0..7 {
|
||||
p.block(chunk, chunk_box, 28 - i, 6 + i, 21, BASE_BLACK);
|
||||
p.block(chunk, chunk_box, 29 + i, 6 + i, 21, BASE_BLACK);
|
||||
}
|
||||
for i in 0..4 {
|
||||
p.block(chunk, chunk_box, 28 - i, 9 + i, 21, BASE_BLACK);
|
||||
p.block(chunk, chunk_box, 29 + i, 9 + i, 21, BASE_BLACK);
|
||||
}
|
||||
p.block(chunk, chunk_box, 28, 12, 21, BASE_BLACK);
|
||||
p.block(chunk, chunk_box, 29, 12, 21, BASE_BLACK);
|
||||
for i in 0..3 {
|
||||
for y in 8..=9 {
|
||||
p.block(chunk, chunk_box, 22 - i * 2, y, 21, BASE_BLACK);
|
||||
p.block(chunk, chunk_box, 35 + i * 2, y, 21, BASE_BLACK);
|
||||
}
|
||||
}
|
||||
for &(x0, y0, x1, y1) in &[
|
||||
(15, 13, 42, 15),
|
||||
(15, 1, 15, 6),
|
||||
(16, 1, 16, 5),
|
||||
(17, 1, 20, 4),
|
||||
(21, 1, 21, 3),
|
||||
(22, 1, 22, 2),
|
||||
(23, 1, 24, 1),
|
||||
(42, 1, 42, 6),
|
||||
(41, 1, 41, 5),
|
||||
(37, 1, 40, 4),
|
||||
(36, 1, 36, 3),
|
||||
(33, 1, 34, 1),
|
||||
(35, 1, 35, 2),
|
||||
] {
|
||||
p.water(chunk, chunk_box, self.sea_level, x0, y0, 21, x1, y1, 21);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_roof(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
|
||||
let p = &self.base;
|
||||
if !p.chunk_intersects(chunk_box, 21, 21, 36, 36) {
|
||||
return;
|
||||
}
|
||||
p.fill(chunk, chunk_box, 21, 0, 22, 36, 0, 36, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 21, 1, 22, 36, 23, 36);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
21 + i,
|
||||
13 + i,
|
||||
21 + i,
|
||||
36 - i,
|
||||
13 + i,
|
||||
21 + i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
21 + i,
|
||||
13 + i,
|
||||
36 - i,
|
||||
36 - i,
|
||||
13 + i,
|
||||
36 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
21 + i,
|
||||
13 + i,
|
||||
22 + i,
|
||||
21 + i,
|
||||
13 + i,
|
||||
35 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
36 - i,
|
||||
13 + i,
|
||||
22 + i,
|
||||
36 - i,
|
||||
13 + i,
|
||||
35 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
p.fill(chunk, chunk_box, 25, 16, 25, 32, 16, 32, BASE_GRAY);
|
||||
for &(x, z) in &[(25, 25), (32, 25), (25, 32), (32, 32)] {
|
||||
p.fill(chunk, chunk_box, x, 17, z, x, 19, z, BASE_LIGHT);
|
||||
}
|
||||
for &(x, y, z, block) in &[
|
||||
(26, 20, 26, BASE_LIGHT),
|
||||
(27, 21, 27, BASE_LIGHT),
|
||||
(27, 20, 27, LAMP),
|
||||
(26, 20, 31, BASE_LIGHT),
|
||||
(27, 21, 30, BASE_LIGHT),
|
||||
(27, 20, 30, LAMP),
|
||||
(31, 20, 31, BASE_LIGHT),
|
||||
(30, 21, 30, BASE_LIGHT),
|
||||
(30, 20, 30, LAMP),
|
||||
(31, 20, 26, BASE_LIGHT),
|
||||
(30, 21, 27, BASE_LIGHT),
|
||||
(30, 20, 27, LAMP),
|
||||
] {
|
||||
p.block(chunk, chunk_box, x, y, z, block);
|
||||
}
|
||||
p.fill(chunk, chunk_box, 28, 21, 27, 29, 21, 27, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, 27, 21, 28, 27, 21, 29, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, 28, 21, 30, 29, 21, 30, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, 30, 21, 28, 30, 21, 29, BASE_GRAY);
|
||||
}
|
||||
|
||||
fn generate_lower_wall(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
|
||||
let p = &self.base;
|
||||
if p.chunk_intersects(chunk_box, 0, 21, 6, 58) {
|
||||
p.fill(chunk, chunk_box, 0, 0, 21, 6, 0, 57, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 0, 1, 21, 6, 7, 57);
|
||||
p.fill(chunk, chunk_box, 4, 4, 21, 6, 4, 53, BASE_GRAY);
|
||||
for i in 0..4 {
|
||||
p.fill(chunk, chunk_box, i, i + 1, 21, i, i + 1, 57 - i, BASE_LIGHT);
|
||||
}
|
||||
for z in (23..53).step_by(3) {
|
||||
p.block(chunk, chunk_box, 5, 5, z, BASE_LIGHT);
|
||||
}
|
||||
p.block(chunk, chunk_box, 5, 5, 52, BASE_LIGHT);
|
||||
p.fill(chunk, chunk_box, 4, 1, 52, 6, 3, 52, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, 5, 1, 51, 5, 3, 53, BASE_GRAY);
|
||||
}
|
||||
if p.chunk_intersects(chunk_box, 51, 21, 58, 58) {
|
||||
p.fill(chunk, chunk_box, 51, 0, 21, 57, 0, 57, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 51, 1, 21, 57, 7, 57);
|
||||
p.fill(chunk, chunk_box, 51, 4, 21, 53, 4, 53, BASE_GRAY);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
57 - i,
|
||||
i + 1,
|
||||
21,
|
||||
57 - i,
|
||||
i + 1,
|
||||
57 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
for z in (23..53).step_by(3) {
|
||||
p.block(chunk, chunk_box, 52, 5, z, BASE_LIGHT);
|
||||
}
|
||||
p.block(chunk, chunk_box, 52, 5, 52, BASE_LIGHT);
|
||||
p.fill(chunk, chunk_box, 51, 1, 52, 53, 3, 52, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, 52, 1, 51, 52, 3, 53, BASE_GRAY);
|
||||
}
|
||||
if p.chunk_intersects(chunk_box, 0, 51, 57, 57) {
|
||||
p.fill(chunk, chunk_box, 7, 0, 51, 50, 0, 57, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 7, 1, 51, 50, 10, 57);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
i + 1,
|
||||
i + 1,
|
||||
57 - i,
|
||||
56 - i,
|
||||
i + 1,
|
||||
57 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_middle_wall(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
|
||||
let p = &self.base;
|
||||
if p.chunk_intersects(chunk_box, 7, 21, 13, 50) {
|
||||
p.fill(chunk, chunk_box, 7, 0, 21, 13, 0, 50, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 7, 1, 21, 13, 10, 50);
|
||||
p.fill(chunk, chunk_box, 11, 8, 21, 13, 8, 53, BASE_GRAY);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
i + 7,
|
||||
i + 5,
|
||||
21,
|
||||
i + 7,
|
||||
i + 5,
|
||||
54,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
for z in (21..=45).step_by(3) {
|
||||
p.block(chunk, chunk_box, 12, 9, z, BASE_LIGHT);
|
||||
}
|
||||
}
|
||||
if p.chunk_intersects(chunk_box, 44, 21, 50, 54) {
|
||||
p.fill(chunk, chunk_box, 44, 0, 21, 50, 0, 50, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 44, 1, 21, 50, 10, 50);
|
||||
p.fill(chunk, chunk_box, 44, 8, 21, 46, 8, 53, BASE_GRAY);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
50 - i,
|
||||
i + 5,
|
||||
21,
|
||||
50 - i,
|
||||
i + 5,
|
||||
54,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
for z in (21..=45).step_by(3) {
|
||||
p.block(chunk, chunk_box, 45, 9, z, BASE_LIGHT);
|
||||
}
|
||||
}
|
||||
if p.chunk_intersects(chunk_box, 8, 44, 49, 54) {
|
||||
p.fill(chunk, chunk_box, 14, 0, 44, 43, 0, 50, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 14, 1, 44, 43, 10, 50);
|
||||
for x in (12..=45).step_by(3) {
|
||||
p.block(chunk, chunk_box, x, 9, 45, BASE_LIGHT);
|
||||
p.block(chunk, chunk_box, x, 9, 52, BASE_LIGHT);
|
||||
if [12, 18, 24, 33, 39, 45].contains(&x) {
|
||||
for &(y, z) in &[
|
||||
(9, 47),
|
||||
(9, 50),
|
||||
(10, 45),
|
||||
(10, 46),
|
||||
(10, 51),
|
||||
(10, 52),
|
||||
(11, 47),
|
||||
(11, 50),
|
||||
(12, 48),
|
||||
(12, 49),
|
||||
] {
|
||||
p.block(chunk, chunk_box, x, y, z, BASE_LIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..3 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
8 + i,
|
||||
5 + i,
|
||||
54,
|
||||
49 - i,
|
||||
5 + i,
|
||||
54,
|
||||
BASE_GRAY,
|
||||
);
|
||||
}
|
||||
p.fill(chunk, chunk_box, 11, 8, 54, 46, 8, 54, BASE_LIGHT);
|
||||
p.fill(chunk, chunk_box, 14, 8, 44, 43, 8, 53, BASE_GRAY);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_upper_wall(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
|
||||
let p = &self.base;
|
||||
if p.chunk_intersects(chunk_box, 14, 21, 20, 43) {
|
||||
p.fill(chunk, chunk_box, 14, 0, 21, 20, 0, 43, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 14, 1, 22, 20, 14, 43);
|
||||
p.fill(chunk, chunk_box, 18, 12, 22, 20, 12, 39, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, 18, 12, 21, 20, 12, 21, BASE_LIGHT);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
i + 14,
|
||||
i + 9,
|
||||
21,
|
||||
i + 14,
|
||||
i + 9,
|
||||
43 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
for z in (23..=39).step_by(3) {
|
||||
p.block(chunk, chunk_box, 19, 13, z, BASE_LIGHT);
|
||||
}
|
||||
}
|
||||
if p.chunk_intersects(chunk_box, 37, 21, 43, 43) {
|
||||
p.fill(chunk, chunk_box, 37, 0, 21, 43, 0, 43, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 37, 1, 22, 43, 14, 43);
|
||||
p.fill(chunk, chunk_box, 37, 12, 22, 39, 12, 39, BASE_GRAY);
|
||||
p.fill(chunk, chunk_box, 37, 12, 21, 39, 12, 21, BASE_LIGHT);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
43 - i,
|
||||
i + 9,
|
||||
21,
|
||||
43 - i,
|
||||
i + 9,
|
||||
43 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
for z in (23..=39).step_by(3) {
|
||||
p.block(chunk, chunk_box, 38, 13, z, BASE_LIGHT);
|
||||
}
|
||||
}
|
||||
if p.chunk_intersects(chunk_box, 15, 37, 42, 43) {
|
||||
p.fill(chunk, chunk_box, 21, 0, 37, 36, 0, 43, BASE_GRAY);
|
||||
p.water(chunk, chunk_box, self.sea_level, 21, 1, 37, 36, 14, 43);
|
||||
p.fill(chunk, chunk_box, 21, 12, 37, 36, 12, 39, BASE_GRAY);
|
||||
for i in 0..4 {
|
||||
p.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
15 + i,
|
||||
i + 9,
|
||||
43 - i,
|
||||
42 - i,
|
||||
i + 9,
|
||||
43 - i,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
for x in (21..=36).step_by(3) {
|
||||
p.block(chunk, chunk_box, x, 13, 38, BASE_LIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
use pumpkin_util::{
|
||||
BlockDirection,
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
};
|
||||
|
||||
const GRID_SIZE: usize = 75;
|
||||
const SOURCE_INDEX: i32 = room_index(2, 0, 0);
|
||||
const TOP_CONNECT_INDEX: i32 = room_index(2, 2, 0);
|
||||
const LEFT_CONNECT_INDEX: i32 = room_index(0, 1, 0);
|
||||
const RIGHT_CONNECT_INDEX: i32 = room_index(4, 1, 0);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct RoomDefinition {
|
||||
pub index: i32,
|
||||
pub connections: [Option<usize>; 6],
|
||||
pub openings: [bool; 6],
|
||||
pub claimed: bool,
|
||||
source: bool,
|
||||
}
|
||||
|
||||
impl RoomDefinition {
|
||||
const fn new(index: i32) -> Self {
|
||||
Self {
|
||||
index,
|
||||
connections: [None; 6],
|
||||
openings: [false; 6],
|
||||
claimed: false,
|
||||
source: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_special(&self) -> bool {
|
||||
self.index >= 75
|
||||
}
|
||||
|
||||
pub fn count_openings(&self) -> usize {
|
||||
self.openings.iter().filter(|opening| **opening).count()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct RoomGraph {
|
||||
pub rooms: Vec<RoomDefinition>,
|
||||
pub source: usize,
|
||||
pub core: usize,
|
||||
}
|
||||
|
||||
impl RoomGraph {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn generate(random: &mut RandomGenerator) -> (Self, Vec<usize>) {
|
||||
let mut graph = Self {
|
||||
rooms: Vec::with_capacity(49),
|
||||
source: 0,
|
||||
core: 0,
|
||||
};
|
||||
let mut grid = [None; GRID_SIZE];
|
||||
for y in 0..=1 {
|
||||
for z in 0..4 {
|
||||
for x in 0..5 {
|
||||
let index = room_index(x, y, z);
|
||||
grid[index as usize] = Some(graph.push(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
for z in 0..2 {
|
||||
for x in 1..4 {
|
||||
let index = room_index(x, 2, z);
|
||||
grid[index as usize] = Some(graph.push(index));
|
||||
}
|
||||
}
|
||||
graph.source = grid[SOURCE_INDEX as usize].unwrap();
|
||||
|
||||
for x in 0..5 {
|
||||
for z in 0..5 {
|
||||
for y in 0..3 {
|
||||
let index = room_index(x, y, z);
|
||||
let Some(room) = grid[index as usize] else {
|
||||
continue;
|
||||
};
|
||||
for direction in DIRECTIONS {
|
||||
let step = direction.to_vector();
|
||||
let nx = x + step.x;
|
||||
let ny = y + step.y;
|
||||
let nz = z + step.z;
|
||||
if !(0..5).contains(&nx) || !(0..3).contains(&ny) || !(0..5).contains(&nz) {
|
||||
continue;
|
||||
}
|
||||
let Some(neighbor) = grid[room_index(nx, ny, nz) as usize] else {
|
||||
continue;
|
||||
};
|
||||
let connection_direction = if nz == z {
|
||||
direction
|
||||
} else {
|
||||
direction.opposite()
|
||||
};
|
||||
graph.connect(room, connection_direction, neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let roof = graph.push(1003);
|
||||
let left_wing = graph.push(1001);
|
||||
let right_wing = graph.push(1002);
|
||||
graph.connect(
|
||||
grid[TOP_CONNECT_INDEX as usize].unwrap(),
|
||||
BlockDirection::Up,
|
||||
roof,
|
||||
);
|
||||
graph.connect(
|
||||
grid[LEFT_CONNECT_INDEX as usize].unwrap(),
|
||||
BlockDirection::South,
|
||||
left_wing,
|
||||
);
|
||||
graph.connect(
|
||||
grid[RIGHT_CONNECT_INDEX as usize].unwrap(),
|
||||
BlockDirection::South,
|
||||
right_wing,
|
||||
);
|
||||
graph.rooms[roof].claimed = true;
|
||||
graph.rooms[left_wing].claimed = true;
|
||||
graph.rooms[right_wing].claimed = true;
|
||||
graph.rooms[graph.source].source = true;
|
||||
|
||||
graph.core = grid[room_index(random.next_bounded_i32(4), 0, 2) as usize].unwrap();
|
||||
graph.claim_core();
|
||||
for room in &mut graph.rooms {
|
||||
// Vanilla updates every grid room and the roof room here. The two wing
|
||||
// sentinels deliberately keep their side closed so that edge pruning
|
||||
// cannot disconnect their grid attachment.
|
||||
if !room.is_special() || room.index == 1003 {
|
||||
for direction in DIRECTIONS {
|
||||
room.openings[direction as usize] =
|
||||
room.connections[direction as usize].is_some();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut order = grid.into_iter().flatten().collect::<Vec<_>>();
|
||||
shuffle(&mut order, random);
|
||||
for &room in &order {
|
||||
let mut closed = 0;
|
||||
for _ in 0..5 {
|
||||
if closed == 2 {
|
||||
break;
|
||||
}
|
||||
let direction = random.next_bounded_i32(6) as usize;
|
||||
if !graph.rooms[room].openings[direction] {
|
||||
continue;
|
||||
}
|
||||
let neighbor = graph.rooms[room].connections[direction].unwrap();
|
||||
let opposite = opposite_index(direction);
|
||||
graph.rooms[room].openings[direction] = false;
|
||||
graph.rooms[neighbor].openings[opposite] = false;
|
||||
if graph.find_source(room) && graph.find_source(neighbor) {
|
||||
closed += 1;
|
||||
} else {
|
||||
graph.rooms[room].openings[direction] = true;
|
||||
graph.rooms[neighbor].openings[opposite] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
order.extend([roof, left_wing, right_wing]);
|
||||
(graph, order)
|
||||
}
|
||||
|
||||
fn push(&mut self, index: i32) -> usize {
|
||||
let room = self.rooms.len();
|
||||
self.rooms.push(RoomDefinition::new(index));
|
||||
room
|
||||
}
|
||||
|
||||
fn connect(&mut self, room: usize, direction: BlockDirection, neighbor: usize) {
|
||||
self.rooms[room].connections[direction as usize] = Some(neighbor);
|
||||
self.rooms[neighbor].connections[direction.opposite() as usize] = Some(room);
|
||||
}
|
||||
|
||||
fn claim_core(&mut self) {
|
||||
let east = self.connection(self.core, BlockDirection::East);
|
||||
let north = self.connection(self.core, BlockDirection::North);
|
||||
let north_east = self.connection(east, BlockDirection::North);
|
||||
let up = self.connection(self.core, BlockDirection::Up);
|
||||
let east_up = self.connection(east, BlockDirection::Up);
|
||||
let north_up = self.connection(north, BlockDirection::Up);
|
||||
let north_east_up = self.connection(north_east, BlockDirection::Up);
|
||||
for room in [
|
||||
self.core,
|
||||
east,
|
||||
north,
|
||||
north_east,
|
||||
up,
|
||||
east_up,
|
||||
north_up,
|
||||
north_east_up,
|
||||
] {
|
||||
self.rooms[room].claimed = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn find_source(&self, start: usize) -> bool {
|
||||
let mut visited = vec![false; self.rooms.len()];
|
||||
let mut pending = vec![start];
|
||||
while let Some(room) = pending.pop() {
|
||||
if std::mem::replace(&mut visited[room], true) {
|
||||
continue;
|
||||
}
|
||||
if self.rooms[room].source {
|
||||
return true;
|
||||
}
|
||||
for direction in 0..6 {
|
||||
if self.rooms[room].openings[direction]
|
||||
&& let Some(neighbor) = self.rooms[room].connections[direction]
|
||||
{
|
||||
pending.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn connection(&self, room: usize, direction: BlockDirection) -> usize {
|
||||
self.rooms[room].connections[direction as usize].unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
const DIRECTIONS: [BlockDirection; 6] = [
|
||||
BlockDirection::Down,
|
||||
BlockDirection::Up,
|
||||
BlockDirection::North,
|
||||
BlockDirection::South,
|
||||
BlockDirection::West,
|
||||
BlockDirection::East,
|
||||
];
|
||||
|
||||
const fn room_index(x: i32, y: i32, z: i32) -> i32 {
|
||||
y * 25 + z * 5 + x
|
||||
}
|
||||
|
||||
const fn opposite_index(direction: usize) -> usize {
|
||||
[1, 0, 3, 2, 5, 4][direction]
|
||||
}
|
||||
|
||||
fn shuffle(values: &mut [usize], random: &mut RandomGenerator) {
|
||||
for index in (1..values.len()).rev() {
|
||||
values.swap(index, random.next_bounded_i32(index as i32 + 1) as usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pumpkin_util::random::{RandomGenerator, legacy_rand::LegacyRand};
|
||||
|
||||
use super::RoomGraph;
|
||||
|
||||
#[test]
|
||||
fn generated_graph_keeps_every_room_connected() {
|
||||
for seed in 0..128 {
|
||||
let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(seed));
|
||||
let (graph, order) = RoomGraph::generate(&mut random);
|
||||
|
||||
assert_eq!(graph.rooms.len(), 49);
|
||||
assert_eq!(order.len(), 49);
|
||||
assert_eq!(
|
||||
graph.rooms.iter().filter(|room| room.is_special()).count(),
|
||||
3
|
||||
);
|
||||
assert!(
|
||||
graph
|
||||
.rooms
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, definition)| !definition.is_special())
|
||||
.all(|(room, _)| graph.find_source(room))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
mod building;
|
||||
mod graph;
|
||||
mod rooms;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use pumpkin_data::{Block, BlockState};
|
||||
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
|
||||
use pumpkin_util::{
|
||||
BlockDirection,
|
||||
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ProtoChunk,
|
||||
biome::BiomeSupplier,
|
||||
generation::{
|
||||
biome_coords,
|
||||
noise::router::multi_noise_sampler::MultiNoiseSampler,
|
||||
positions::chunk_pos::{get_center_x, get_center_z, start_block_x, start_block_z},
|
||||
structure::{
|
||||
piece::StructurePieceType,
|
||||
structures::{
|
||||
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
|
||||
StructurePiecesCollector, StructurePosition, WorldPortalExt,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use self::{
|
||||
graph::RoomGraph,
|
||||
rooms::{RoomKind, RoomPiece},
|
||||
};
|
||||
|
||||
const BASE_GRAY: &BlockState = Block::PRISMARINE.default_state;
|
||||
const BASE_LIGHT: &BlockState = Block::PRISMARINE_BRICKS.default_state;
|
||||
const BASE_BLACK: &BlockState = Block::DARK_PRISMARINE.default_state;
|
||||
const LAMP: &BlockState = Block::SEA_LANTERN.default_state;
|
||||
const WATER: &BlockState = Block::WATER.default_state;
|
||||
|
||||
pub struct OceanMonumentGenerator;
|
||||
|
||||
pub(crate) fn has_valid_biomes(
|
||||
biome_supplier: &dyn BiomeSupplier,
|
||||
sampler: &mut MultiNoiseSampler,
|
||||
chunk_x: i32,
|
||||
chunk_z: i32,
|
||||
sea_level: i32,
|
||||
start_y: i32,
|
||||
) -> bool {
|
||||
let start_x = get_center_x(chunk_x);
|
||||
let start_z = get_center_z(chunk_z);
|
||||
let start_biomes = pumpkin_data::tag::WorldgenBiome::MINECRAFT_HAS_STRUCTURE_OCEAN_MONUMENT.1;
|
||||
if !start_biomes.contains(
|
||||
&(biome_supplier
|
||||
.biome(
|
||||
biome_coords::from_block(start_x),
|
||||
biome_coords::from_block(start_y),
|
||||
biome_coords::from_block(start_z),
|
||||
sampler,
|
||||
)
|
||||
.id as u16),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let center_x = start_block_x(chunk_x) + 9;
|
||||
let center_z = start_block_z(chunk_z) + 9;
|
||||
let min_x = biome_coords::from_block(center_x - 29);
|
||||
let max_x = biome_coords::from_block(center_x + 29);
|
||||
let min_y = biome_coords::from_block(sea_level - 29);
|
||||
let max_y = biome_coords::from_block(sea_level + 29);
|
||||
let min_z = biome_coords::from_block(center_z - 29);
|
||||
let max_z = biome_coords::from_block(center_z + 29);
|
||||
let allowed = pumpkin_data::tag::WorldgenBiome::MINECRAFT_REQUIRED_OCEAN_MONUMENT_SURROUNDING.1;
|
||||
|
||||
(min_x..=max_x).all(|x| {
|
||||
(min_z..=max_z).all(|z| {
|
||||
(min_y..=max_y)
|
||||
.all(|y| allowed.contains(&(biome_supplier.biome(x, y, z, sampler).id as u16)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
impl StructureGenerator for OceanMonumentGenerator {
|
||||
fn get_structure_position(
|
||||
&self,
|
||||
mut context: StructureGeneratorContext<'_>,
|
||||
) -> Option<StructurePosition> {
|
||||
let center_x = get_center_x(context.chunk_x);
|
||||
let center_z = get_center_z(context.chunk_z);
|
||||
let start_y = context
|
||||
.height_sampler
|
||||
.as_deref_mut()
|
||||
.map_or(context.sea_level, |sampler| {
|
||||
sampler.estimate_ocean_floor_height(center_x, center_z)
|
||||
});
|
||||
let direction = BlockDirection::get_random_horizontal_direction(&mut context.random);
|
||||
let building = MonumentBuilding::new(
|
||||
&mut context.random,
|
||||
start_block_x(context.chunk_x) - 29,
|
||||
start_block_z(context.chunk_z) - 29,
|
||||
direction,
|
||||
context.sea_level,
|
||||
);
|
||||
let mut collector = StructurePiecesCollector::default();
|
||||
collector.add_piece(Box::new(building));
|
||||
|
||||
Some(StructurePosition {
|
||||
start_pos: BlockPos::new(center_x, start_y, center_z),
|
||||
collector: Arc::new(Mutex::new(collector)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct MonumentBuilding {
|
||||
base: MonumentPiece,
|
||||
graph: RoomGraph,
|
||||
children: Vec<RoomPiece>,
|
||||
sea_level: i32,
|
||||
}
|
||||
|
||||
impl MonumentBuilding {
|
||||
fn new(
|
||||
random: &mut RandomGenerator,
|
||||
west: i32,
|
||||
north: i32,
|
||||
direction: BlockDirection,
|
||||
sea_level: i32,
|
||||
) -> Self {
|
||||
let mut piece = StructurePiece::new(
|
||||
StructurePieceType::OceanMonumentBase,
|
||||
BlockBox::new(west, 39, north, west + 57, 61, north + 57),
|
||||
0,
|
||||
);
|
||||
piece.set_facing(Some(direction));
|
||||
let base = MonumentPiece { piece };
|
||||
let (mut graph, room_order) = RoomGraph::generate(random);
|
||||
graph.rooms[graph.source].claimed = true;
|
||||
|
||||
let mut children = vec![
|
||||
RoomPiece::for_room(
|
||||
RoomKind::Entry,
|
||||
direction,
|
||||
graph.source,
|
||||
graph.rooms[graph.source].index,
|
||||
),
|
||||
RoomPiece::for_room(
|
||||
RoomKind::Core,
|
||||
direction,
|
||||
graph.core,
|
||||
graph.rooms[graph.core].index,
|
||||
),
|
||||
];
|
||||
for room in room_order {
|
||||
if graph.rooms[room].claimed || graph.rooms[room].is_special() {
|
||||
continue;
|
||||
}
|
||||
children.push(RoomPiece::fit(random, direction, room, &mut graph));
|
||||
}
|
||||
|
||||
let room_offset = base.piece.offset_pos(9, 0, 22);
|
||||
for child in &mut children {
|
||||
child
|
||||
.piece
|
||||
.piece
|
||||
.bounding_box
|
||||
.move_pos(room_offset.x, room_offset.y, room_offset.z);
|
||||
}
|
||||
|
||||
let left_wing = box_from_corners(
|
||||
base.piece.offset_pos(1, 1, 1),
|
||||
base.piece.offset_pos(23, 8, 21),
|
||||
);
|
||||
let right_wing = box_from_corners(
|
||||
base.piece.offset_pos(34, 1, 1),
|
||||
base.piece.offset_pos(56, 8, 21),
|
||||
);
|
||||
let penthouse = box_from_corners(
|
||||
base.piece.offset_pos(22, 13, 22),
|
||||
base.piece.offset_pos(35, 17, 35),
|
||||
);
|
||||
let wing_design = random.next_i32();
|
||||
children.push(RoomPiece::special(
|
||||
RoomKind::Wing(wing_design),
|
||||
direction,
|
||||
left_wing,
|
||||
));
|
||||
children.push(RoomPiece::special(
|
||||
RoomKind::Wing(wing_design.wrapping_add(1)),
|
||||
direction,
|
||||
right_wing,
|
||||
));
|
||||
children.push(RoomPiece::special(
|
||||
RoomKind::Penthouse,
|
||||
direction,
|
||||
penthouse,
|
||||
));
|
||||
|
||||
Self {
|
||||
base,
|
||||
graph,
|
||||
children,
|
||||
sea_level,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StructurePieceBase for MonumentBuilding {
|
||||
fn get_structure_piece(&self) -> &StructurePiece {
|
||||
&self.base.piece
|
||||
}
|
||||
|
||||
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
|
||||
&mut self.base.piece
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn place(
|
||||
&mut self,
|
||||
chunk: &mut ProtoChunk,
|
||||
_block_registry: &dyn WorldPortalExt,
|
||||
random: &mut RandomGenerator,
|
||||
_seed: i64,
|
||||
chunk_box: &BlockBox,
|
||||
) {
|
||||
self.place_building(chunk, random, chunk_box);
|
||||
}
|
||||
}
|
||||
|
||||
struct MonumentPiece {
|
||||
piece: StructurePiece,
|
||||
}
|
||||
|
||||
impl MonumentPiece {
|
||||
const fn for_room(
|
||||
kind: RoomKind,
|
||||
direction: BlockDirection,
|
||||
room_index: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
depth: i32,
|
||||
) -> Self {
|
||||
let room_x = room_index % 5;
|
||||
let room_z = room_index / 5 % 5;
|
||||
let room_y = room_index / 25;
|
||||
let mut bounding_box = match direction {
|
||||
BlockDirection::North | BlockDirection::South => {
|
||||
BlockBox::new(0, 0, 0, width * 8 - 1, height * 4 - 1, depth * 8 - 1)
|
||||
}
|
||||
_ => BlockBox::new(0, 0, 0, depth * 8 - 1, height * 4 - 1, width * 8 - 1),
|
||||
};
|
||||
match direction {
|
||||
BlockDirection::North => {
|
||||
bounding_box.move_pos(room_x * 8, room_y * 4, -(room_z + depth) * 8 + 1);
|
||||
}
|
||||
BlockDirection::South => {
|
||||
bounding_box.move_pos(room_x * 8, room_y * 4, room_z * 8);
|
||||
}
|
||||
BlockDirection::West => {
|
||||
bounding_box.move_pos(-(room_z + depth) * 8 + 1, room_y * 4, room_x * 8);
|
||||
}
|
||||
_ => {
|
||||
bounding_box.move_pos(room_z * 8, room_y * 4, room_x * 8);
|
||||
}
|
||||
}
|
||||
let mut piece = StructurePiece::new(kind.piece_type(), bounding_box, 1);
|
||||
piece.set_facing(Some(direction));
|
||||
Self { piece }
|
||||
}
|
||||
|
||||
const fn special(kind: RoomKind, direction: BlockDirection, bounding_box: BlockBox) -> Self {
|
||||
let mut piece = StructurePiece::new(kind.piece_type(), bounding_box, 1);
|
||||
piece.set_facing(Some(direction));
|
||||
Self { piece }
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn fill(
|
||||
&self,
|
||||
chunk: &mut ProtoChunk,
|
||||
chunk_box: &BlockBox,
|
||||
x0: i32,
|
||||
y0: i32,
|
||||
z0: i32,
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
z1: i32,
|
||||
block: &BlockState,
|
||||
) {
|
||||
self.piece
|
||||
.fill(chunk, chunk_box, x0, y0, z0, x1, y1, z1, block);
|
||||
}
|
||||
|
||||
fn block(
|
||||
&self,
|
||||
chunk: &mut ProtoChunk,
|
||||
chunk_box: &BlockBox,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
block: &BlockState,
|
||||
) {
|
||||
self.piece.add_block(chunk, block, x, y, z, chunk_box);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn water(
|
||||
&self,
|
||||
chunk: &mut ProtoChunk,
|
||||
chunk_box: &BlockBox,
|
||||
sea_level: i32,
|
||||
x0: i32,
|
||||
y0: i32,
|
||||
z0: i32,
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
z1: i32,
|
||||
) {
|
||||
for y in y0..=y1 {
|
||||
for x in x0..=x1 {
|
||||
for z in z0..=z1 {
|
||||
let state = self.piece.get_block_at(chunk, x, y, z, chunk_box);
|
||||
let block_id = state.id.to_block_id();
|
||||
if [
|
||||
Block::ICE.id,
|
||||
Block::PACKED_ICE.id,
|
||||
Block::BLUE_ICE.id,
|
||||
Block::WATER.id,
|
||||
]
|
||||
.contains(&block_id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let state = if self.piece.offset_pos(x, y, z).y >= sea_level {
|
||||
Block::AIR.default_state
|
||||
} else {
|
||||
WATER
|
||||
};
|
||||
self.block(chunk, chunk_box, x, y, z, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_floor(
|
||||
&self,
|
||||
chunk: &mut ProtoChunk,
|
||||
chunk_box: &BlockBox,
|
||||
x: i32,
|
||||
z: i32,
|
||||
down_opening: bool,
|
||||
) {
|
||||
if !down_opening {
|
||||
self.fill(chunk, chunk_box, x, 0, z, x + 7, 0, z + 7, BASE_GRAY);
|
||||
return;
|
||||
}
|
||||
self.fill(chunk, chunk_box, x, 0, z, x + 2, 0, z + 7, BASE_GRAY);
|
||||
self.fill(chunk, chunk_box, x + 5, 0, z, x + 7, 0, z + 7, BASE_GRAY);
|
||||
self.fill(chunk, chunk_box, x + 3, 0, z, x + 4, 0, z + 2, BASE_GRAY);
|
||||
self.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + 3,
|
||||
0,
|
||||
z + 5,
|
||||
x + 4,
|
||||
0,
|
||||
z + 7,
|
||||
BASE_GRAY,
|
||||
);
|
||||
self.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + 3,
|
||||
0,
|
||||
z + 2,
|
||||
x + 4,
|
||||
0,
|
||||
z + 2,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
self.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + 3,
|
||||
0,
|
||||
z + 5,
|
||||
x + 4,
|
||||
0,
|
||||
z + 5,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
self.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + 2,
|
||||
0,
|
||||
z + 3,
|
||||
x + 2,
|
||||
0,
|
||||
z + 4,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
self.fill(
|
||||
chunk,
|
||||
chunk_box,
|
||||
x + 5,
|
||||
0,
|
||||
z + 3,
|
||||
x + 5,
|
||||
0,
|
||||
z + 4,
|
||||
BASE_LIGHT,
|
||||
);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn fill_on_water(
|
||||
&self,
|
||||
chunk: &mut ProtoChunk,
|
||||
chunk_box: &BlockBox,
|
||||
x0: i32,
|
||||
y0: i32,
|
||||
z0: i32,
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
z1: i32,
|
||||
block: &BlockState,
|
||||
) {
|
||||
for y in y0..=y1 {
|
||||
for x in x0..=x1 {
|
||||
for z in z0..=z1 {
|
||||
if self
|
||||
.piece
|
||||
.get_block_at(chunk, x, y, z, chunk_box)
|
||||
.id
|
||||
.to_block_id()
|
||||
== Block::WATER.id
|
||||
{
|
||||
self.block(chunk, chunk_box, x, y, z, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn chunk_intersects(&self, chunk_box: &BlockBox, x0: i32, z0: i32, x1: i32, z1: i32) -> bool {
|
||||
let first = self.piece.offset_pos(x0, 0, z0);
|
||||
let second = self.piece.offset_pos(x1, 0, z1);
|
||||
chunk_box.intersects_raw_xz(
|
||||
first.x.min(second.x),
|
||||
first.z.min(second.z),
|
||||
first.x.max(second.x),
|
||||
first.z.max(second.z),
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn_elder(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox, x: i32, y: i32, z: i32) {
|
||||
let pos = self.piece.offset_pos(x, y, z);
|
||||
if !chunk_box.contains_pos(&pos) {
|
||||
return;
|
||||
}
|
||||
chunk.add_structure_entity(elder_guardian_nbt(pos));
|
||||
}
|
||||
}
|
||||
|
||||
fn elder_guardian_nbt(pos: Vector3<i32>) -> NbtCompound {
|
||||
let mut nbt = NbtCompound::new();
|
||||
nbt.put_string("id", "minecraft:elder_guardian".to_string());
|
||||
nbt.put(
|
||||
"Pos",
|
||||
NbtTag::List(vec![
|
||||
(f64::from(pos.x) + 0.5).into(),
|
||||
f64::from(pos.y).into(),
|
||||
(f64::from(pos.z) + 0.5).into(),
|
||||
]),
|
||||
);
|
||||
nbt.put(
|
||||
"Motion",
|
||||
NbtTag::List(vec![0.0f64.into(), 0.0f64.into(), 0.0f64.into()]),
|
||||
);
|
||||
nbt.put("Rotation", NbtTag::List(vec![0.0f32.into(), 0.0f32.into()]));
|
||||
nbt.put_float("Health", 80.0);
|
||||
nbt.put_bool("PersistenceRequired", true);
|
||||
nbt
|
||||
}
|
||||
|
||||
fn box_from_corners(first: Vector3<i32>, second: Vector3<i32>) -> BlockBox {
|
||||
BlockBox::new(
|
||||
first.x.min(second.x),
|
||||
first.y.min(second.y),
|
||||
first.z.min(second.z),
|
||||
first.x.max(second.x),
|
||||
first.y.max(second.y),
|
||||
first.z.max(second.z),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pumpkin_data::{Block, dimension::Dimension};
|
||||
use pumpkin_util::{
|
||||
BlockDirection,
|
||||
math::block_box::BlockBox,
|
||||
random::{RandomGenerator, legacy_rand::LegacyRand},
|
||||
world_seed::Seed,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ProtoChunk,
|
||||
generation::{
|
||||
get_world_gen,
|
||||
positions::chunk_pos::{start_block_x, start_block_z},
|
||||
structure::structures::{HeightSampler, StructureGenerator, StructureGeneratorContext},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{MonumentBuilding, OceanMonumentGenerator, elder_guardian_nbt};
|
||||
|
||||
struct OceanFloorSampler;
|
||||
|
||||
impl HeightSampler for OceanFloorSampler {
|
||||
fn estimate_height(&mut self, _block_x: i32, _block_z: i32) -> i32 {
|
||||
panic!("monuments must not use the world-surface heightmap");
|
||||
}
|
||||
|
||||
fn estimate_ocean_floor_height(&mut self, _block_x: i32, _block_z: i32) -> i32 {
|
||||
37
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structure_start_uses_the_ocean_floor_heightmap() {
|
||||
let mut height_sampler = OceanFloorSampler;
|
||||
let context = StructureGeneratorContext {
|
||||
seed: 0,
|
||||
chunk_x: 2,
|
||||
chunk_z: -3,
|
||||
random: RandomGenerator::Legacy(LegacyRand::from_seed(0)),
|
||||
sea_level: 63,
|
||||
min_y: -64,
|
||||
height_sampler: Some(&mut height_sampler),
|
||||
structure_key: None,
|
||||
};
|
||||
|
||||
let position = OceanMonumentGenerator
|
||||
.get_structure_position(context)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(position.start_pos.0.y, 37);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orientation_does_not_move_the_monument_origin() {
|
||||
for direction in [
|
||||
BlockDirection::North,
|
||||
BlockDirection::South,
|
||||
BlockDirection::West,
|
||||
BlockDirection::East,
|
||||
] {
|
||||
let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(0));
|
||||
let building = MonumentBuilding::new(&mut random, 100, 200, direction, 63);
|
||||
let bounds = building.base.piece.bounding_box;
|
||||
|
||||
assert_eq!((bounds.min.x, bounds.min.y, bounds.min.z), (100, 39, 200));
|
||||
assert_eq!((bounds.max.x, bounds.max.y, bounds.max.z), (157, 61, 257));
|
||||
assert!(building.children.iter().all(|child| {
|
||||
let child = child.piece.piece.bounding_box;
|
||||
child.min.x >= bounds.min.x
|
||||
&& child.min.y >= bounds.min.y
|
||||
&& child.min.z >= bounds.min.z
|
||||
&& child.max.x <= bounds.max.x
|
||||
&& child.max.y <= bounds.max.y
|
||||
&& child.max.z <= bounds.max.z
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elder_guardian_nbt_is_accepted_by_the_entity_loader() {
|
||||
let nbt = elder_guardian_nbt(pumpkin_util::math::vector3::Vector3::new(1, 2, 3));
|
||||
|
||||
assert_eq!(nbt.get_string("id"), Some("minecraft:elder_guardian"));
|
||||
assert_eq!(nbt.get_list("Pos").unwrap().len(), 3);
|
||||
assert_eq!(nbt.get_list("Motion").unwrap().len(), 3);
|
||||
assert_eq!(nbt.get_list("Rotation").unwrap().len(), 2);
|
||||
assert_eq!(nbt.get_float("Health"), Some(80.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_orientation_places_the_complete_treasure_core() {
|
||||
let world_gen = get_world_gen(
|
||||
Seed(0),
|
||||
Dimension::OVERWORLD,
|
||||
false,
|
||||
Vec::new(),
|
||||
String::new(),
|
||||
);
|
||||
|
||||
for direction in [
|
||||
BlockDirection::North,
|
||||
BlockDirection::South,
|
||||
BlockDirection::West,
|
||||
BlockDirection::East,
|
||||
] {
|
||||
let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(0));
|
||||
let building = MonumentBuilding::new(&mut random, 0, 0, direction, 63);
|
||||
let bounds = building.base.piece.bounding_box;
|
||||
let mut gold_blocks = 0;
|
||||
|
||||
for chunk_x in bounds.min.x.div_euclid(16)..=bounds.max.x.div_euclid(16) {
|
||||
for chunk_z in bounds.min.z.div_euclid(16)..=bounds.max.z.div_euclid(16) {
|
||||
let mut chunk = ProtoChunk::new(chunk_x, chunk_z, &world_gen);
|
||||
let chunk_box = BlockBox::new(
|
||||
start_block_x(chunk_x),
|
||||
chunk.bottom_y() as i32,
|
||||
start_block_z(chunk_z),
|
||||
start_block_x(chunk_x) + 15,
|
||||
chunk.bottom_y() as i32 + chunk.height() as i32 - 1,
|
||||
start_block_z(chunk_z) + 15,
|
||||
);
|
||||
let mut placement_random = RandomGenerator::Legacy(LegacyRand::from_seed(1));
|
||||
building.place_building(&mut chunk, &mut placement_random, &chunk_box);
|
||||
|
||||
for x in 0..16 {
|
||||
for y in 0..chunk.height() as i32 {
|
||||
for z in 0..16 {
|
||||
if chunk
|
||||
.get_block_state_raw(x, y, z)
|
||||
.to_state()
|
||||
.id
|
||||
.to_block_id()
|
||||
== Block::GOLD_BLOCK.id
|
||||
{
|
||||
gold_blocks += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(gold_blocks, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user