Skip to content

Commit 4976548

Browse files
authored
Merge branch 'master' into pr-gametest-framework
Signed-off-by: Kyle Davis <kyled@kyledavis.dev>
2 parents 2644d4b + 37437ee commit 4976548

104 files changed

Lines changed: 2775 additions & 1957 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/pumpkin-world/src/generation/structure/structures/igloo.rs

Lines changed: 313 additions & 200 deletions
Large diffs are not rendered by default.
Lines changed: 175 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use std::sync::Arc;
22

3-
use pumpkin_data::{Block, BlockDirection, BlockState};
3+
use pumpkin_data::{Block, BlockDirection, BlockState, Mirror, Rotation};
44
use pumpkin_util::{
55
math::{block_box::BlockBox, position::BlockPos, vector3::Vector3},
6-
random::{RandomDeriverImpl, RandomGenerator, RandomImpl},
6+
random::{
7+
RandomDeriverImpl, RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand,
8+
},
79
};
810

911
use crate::{
@@ -12,18 +14,19 @@ use crate::{
1214
positions::chunk_pos::{start_block_x, start_block_z},
1315
structure::{
1416
piece::StructurePieceType,
15-
shiftable_piece::ShiftableStructurePiece,
1617
structures::{
1718
StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase,
18-
StructurePiecesCollector, StructurePosition,
19+
StructurePiecesCollector, StructurePosition, WorldPortalExt,
20+
},
21+
template::{
22+
BlockStateResolver, StructurePlaceSettings, StructureTemplate, get_template,
23+
processor::{IgnoredBlock, ProcessorContext, StructureProcessor},
1924
},
20-
template::{BlockRotation, StructureTemplate, get_template, place_template},
2125
},
2226
},
23-
world::WorldPortalExt,
2427
};
2528

26-
const TEMPLATE_NAMES: [&str; 14] = [
29+
pub const FOSSILS: [&str; 14] = [
2730
"nether_fossils/fossil_1",
2831
"nether_fossils/fossil_2",
2932
"nether_fossils/fossil_3",
@@ -58,7 +61,7 @@ impl StructureGenerator for NetherFossilGenerator {
5861
// 2. nextInt(16) for Z offset within chunk
5962
// 3. height.get(random) for initial Y (uniform 32..254)
6063
// 4. Column scan (no random calls)
61-
// 5. BlockRotation.random(random) - nextInt(4)
64+
// 5. Rotation.getRandom(random) - nextInt(4)
6265
// 6. Util.getRandom(FOSSILS, random) - nextInt(14)
6366

6467
let x = start_block_x(context.chunk_x) + context.random.next_bounded_i32(16);
@@ -75,36 +78,25 @@ impl StructureGenerator for NetherFossilGenerator {
7578
HEIGHT_MIN + context.random.next_bounded_i32(height_range)
7679
};
7780

78-
// Column scan is deferred to place() since we don't have block data here.
79-
// Consume random in vanilla order for determinism.
8081
let rotation_index = context.random.next_bounded_i32(4) as u8;
81-
let rotation = BlockRotation::from_index(rotation_index);
82+
let rotation = Rotation::from_index(rotation_index);
8283

83-
let template_index = context.random.next_bounded_i32(14) as usize;
84-
let template_name = TEMPLATE_NAMES[template_index];
84+
let template_index = context.random.next_bounded_i32(FOSSILS.len() as i32) as usize;
85+
let template_name = FOSSILS[template_index];
8586

8687
let template = get_template(template_name)?;
87-
88-
let rotated_size = rotation.transform_size(template.size);
88+
let position = Vector3::new(x, initial_y, z);
8989

9090
let mut collector = StructurePiecesCollector::default();
9191

92-
let piece = NetherFossilPiece {
93-
shiftable_structure_piece: ShiftableStructurePiece::new(
94-
StructurePieceType::NetherFossil,
95-
x,
96-
initial_y,
97-
z,
98-
rotated_size.x,
99-
rotated_size.y,
100-
rotated_size.z,
101-
rotation.to_axis(),
102-
),
92+
let piece = NetherFossilPiece::new(
10393
template,
94+
template_name.to_string(),
95+
position,
10496
rotation,
10597
initial_y,
106-
sea_level: context.sea_level,
107-
};
98+
context.sea_level,
99+
);
108100

109101
collector.add_piece(Box::new(piece));
110102

@@ -115,35 +107,48 @@ impl StructureGenerator for NetherFossilGenerator {
115107
}
116108
}
117109

118-
struct NetherFossilPiece {
119-
shiftable_structure_piece: ShiftableStructurePiece,
120-
template: Arc<StructureTemplate>,
121-
rotation: BlockRotation,
122-
initial_y: i32,
123-
sea_level: i32,
110+
pub struct NetherFossilPiece {
111+
pub piece: StructurePiece,
112+
pub template: Arc<StructureTemplate>,
113+
pub template_name: String,
114+
pub place_settings: StructurePlaceSettings,
115+
pub template_position: Vector3<i32>,
116+
pub initial_y: i32,
117+
pub sea_level: i32,
124118
}
125119

126120
impl NetherFossilPiece {
121+
#[must_use]
122+
pub fn new(
123+
template: Arc<StructureTemplate>,
124+
template_name: String,
125+
template_position: Vector3<i32>,
126+
rotation: Rotation,
127+
initial_y: i32,
128+
sea_level: i32,
129+
) -> Self {
130+
let place_settings = make_settings(rotation);
131+
let bounding_box = template.get_bounding_box(&place_settings, template_position);
132+
133+
Self {
134+
piece: StructurePiece::new(StructurePieceType::NetherFossil, bounding_box, 0),
135+
template,
136+
template_name,
137+
place_settings,
138+
template_position,
139+
initial_y,
140+
sea_level,
141+
}
142+
}
143+
127144
/// Vanilla column scan: search downward from `initial_y` for air above (soul sand OR solid block).
128145
/// Returns the Y of the support block, or None if no valid position found above sea level.
129-
///
130-
/// Mirrors vanilla's pre-decrement loop:
131-
/// ```java
132-
/// while (l > k) {
133-
/// BlockState lv5 = lv3.getState(l);
134-
/// BlockState lv6 = lv3.getState(--l); // pre-decrement
135-
/// if (lv5.isAir() && (lv6.isOf(SOUL_SAND) || lv6.isSideSolidFullSquare(...))) break;
136-
/// }
137-
/// if (l <= k) return empty;
138-
/// ```
139-
/// After the loop, l is the support block Y. Vanilla rejects if l <= `sea_level`.
140146
fn find_placement_y(&self, chunk: &ProtoChunk) -> Option<i32> {
141-
let origin = self.shiftable_structure_piece.piece.bounding_box.min;
147+
let origin = self.template_position;
142148
let mut y = self.initial_y;
143149

144150
while y > self.sea_level {
145151
let upper = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z));
146-
// Pre-decrement: y now points to the lower block (vanilla's --l)
147152
y -= 1;
148153
let lower = chunk.get_block_state(&Vector3::new(origin.x, y, origin.z));
149154

@@ -158,106 +163,151 @@ impl NetherFossilPiece {
158163
}
159164
}
160165

161-
// Vanilla: if (l <= k) return empty
162166
if y <= self.sea_level { None } else { Some(y) }
163167
}
164-
}
165168

166-
impl StructurePieceBase for NetherFossilPiece {
167-
fn as_any(&self) -> &dyn std::any::Any {
168-
self
169+
fn place_blocks(&self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) {
170+
let rotation = self.place_settings.get_rotation();
171+
let mirror = self.place_settings.get_mirror();
172+
let pivot = self.place_settings.get_rotation_pivot();
173+
174+
let mut context_rng = LegacyRand::from_seed(hash_block_pos(
175+
self.template_position.x,
176+
self.template_position.y,
177+
self.template_position.z,
178+
) as u64);
179+
let mut context = ProcessorContext::new(
180+
self.template_position,
181+
self.place_settings.get_processors(),
182+
&mut context_rng,
183+
);
184+
185+
for block in &self.template.blocks {
186+
let palette_entry = &self.template.palette[block.state as usize];
187+
188+
let mut block_entity_nbt = block.nbt.clone();
189+
let placed_entry = palette_entry.clone();
190+
191+
let Some(state) = BlockStateResolver::resolve(&placed_entry, rotation, mirror) else {
192+
continue;
193+
};
194+
195+
let local_pos =
196+
StructureTemplate::transform_block_pos(block.pos, mirror, rotation, pivot);
197+
let world_pos = self.template_position + local_pos;
198+
199+
if !chunk_box.contains_pos(&world_pos) {
200+
continue;
201+
}
202+
203+
let mut processed_state = Some(state);
204+
let mut capped_idx = 0;
205+
for processor in self.place_settings.get_processors() {
206+
let Some(current_state) = processed_state else {
207+
break;
208+
};
209+
processed_state = processor.process_with_context(
210+
chunk,
211+
world_pos,
212+
current_state,
213+
&mut block_entity_nbt,
214+
&mut context,
215+
&mut capped_idx,
216+
&mut context_rng,
217+
);
218+
}
219+
220+
let Some(final_state) = processed_state else {
221+
continue;
222+
};
223+
224+
chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, final_state);
225+
}
169226
}
170-
fn place(
171-
&mut self,
227+
228+
fn place_dried_ghast(
172229
chunk: &mut ProtoChunk,
173-
block_registry: &dyn WorldPortalExt,
174-
_random: &mut RandomGenerator,
175230
seed: i64,
176-
chunk_box: &BlockBox,
231+
fossil_bb: &BlockBox,
232+
chunk_bb: &BlockBox,
177233
) {
178-
// Vanilla column scan: find air above soul sand or solid block
179-
let Some(placement_y) = self.find_placement_y(chunk) else {
180-
return;
181-
};
234+
use pumpkin_util::random::xoroshiro128::Xoroshiro;
182235

183-
// Adjust bounding box to placement Y
184-
let current_y = self.shiftable_structure_piece.piece.bounding_box.min.y;
185-
let offset = placement_y - current_y;
186-
self.shiftable_structure_piece.piece.bounding_box.min.y += offset;
187-
self.shiftable_structure_piece.piece.bounding_box.max.y += offset;
188-
189-
let origin = self.shiftable_structure_piece.piece.bounding_box.min;
190-
191-
// Vanilla uses IGNORE_AIR_AND_STRUCTURE_BLOCKS processor
192-
place_template(
193-
chunk,
194-
&self.template,
195-
origin,
196-
(0, 0),
197-
self.rotation,
198-
true,
199-
false,
200-
&[],
201-
Some(chunk_box),
202-
);
236+
let center_x = i32::midpoint(fossil_bb.min.x, fossil_bb.max.x);
237+
let center_y = i32::midpoint(fossil_bb.min.y, fossil_bb.max.y);
238+
let center_z = i32::midpoint(fossil_bb.min.z, fossil_bb.max.z);
203239

204-
// Vanilla: 50% chance to place a dried ghast block at the fossil base.
205-
// Uses a deterministic random seeded from world seed + bounding box center.
206-
self.try_place_dried_ghast(chunk, block_registry, seed);
240+
let mut rng = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64));
241+
let splitter = rng.next_splitter();
242+
let mut positional_random = splitter.split_pos(center_x, center_y, center_z);
243+
244+
if positional_random.next_f32() < 0.5 {
245+
let x_span = (fossil_bb.max.x - fossil_bb.min.x + 1).max(1);
246+
let z_span = (fossil_bb.max.z - fossil_bb.min.z + 1).max(1);
247+
let x = fossil_bb.min.x + positional_random.next_bounded_i32(x_span);
248+
let y = fossil_bb.min.y;
249+
let z = fossil_bb.min.z + positional_random.next_bounded_i32(z_span);
250+
let random_pos = Vector3::new(x, y, z);
251+
252+
let block_at = chunk.get_block_state(&random_pos);
253+
if BlockState::from_id(block_at).is_air() && chunk_bb.contains_pos(&random_pos) {
254+
let rot_idx = positional_random.next_bounded_i32(4) as u8;
255+
let rot = Rotation::from_index(rot_idx);
256+
let state = Block::DRIED_GHAST.default_state.rotate(rot);
257+
chunk.set_block_state(random_pos.x, random_pos.y, random_pos.z, state);
258+
}
259+
}
207260
}
261+
}
208262

263+
impl StructurePieceBase for NetherFossilPiece {
264+
fn as_any(&self) -> &dyn std::any::Any {
265+
self
266+
}
209267
fn get_structure_piece(&self) -> &StructurePiece {
210-
&self.shiftable_structure_piece.piece
268+
&self.piece
211269
}
212-
213270
fn get_structure_piece_mut(&mut self) -> &mut StructurePiece {
214-
&mut self.shiftable_structure_piece.piece
271+
&mut self.piece
215272
}
216-
}
217-
218-
impl NetherFossilPiece {
219-
fn try_place_dried_ghast(
220-
&self,
273+
fn place(
274+
&mut self,
221275
chunk: &mut ProtoChunk,
222276
_block_registry: &dyn WorldPortalExt,
277+
_random: &mut RandomGenerator,
223278
seed: i64,
279+
chunk_box: &BlockBox,
224280
) {
225-
use pumpkin_util::random::xoroshiro128::Xoroshiro;
226-
227-
let bbox = self.shiftable_structure_piece.piece.bounding_box;
228-
let center_x = i32::midpoint(bbox.min.x, bbox.max.x);
229-
let center_y = i32::midpoint(bbox.min.y, bbox.max.y);
230-
let center_z = i32::midpoint(bbox.min.z, bbox.max.z);
231-
232-
// Vanilla: Random.create(world.getSeed()).nextSplitter().split(box.getCenter())
233-
let mut rng = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(seed as u64));
234-
let splitter = rng.next_splitter();
235-
let mut rng = splitter.split_pos(center_x, center_y, center_z);
236-
237-
if rng.next_f32() >= 0.5 {
281+
let Some(placement_y) = self.find_placement_y(chunk) else {
238282
return;
239-
}
240-
241-
let block_count_x = (bbox.max.x - bbox.min.x + 1).max(1);
242-
let block_count_z = (bbox.max.z - bbox.min.z + 1).max(1);
243-
let x = bbox.min.x + rng.next_bounded_i32(block_count_x);
244-
let y = bbox.min.y;
245-
let z = bbox.min.z + rng.next_bounded_i32(block_count_z);
283+
};
246284

247-
// Vanilla: chunkBox.contains(pos) - only place if within current chunk
248-
let chunk_x = start_block_x(chunk.x);
249-
let chunk_z = start_block_z(chunk.z);
250-
if x < chunk_x || x > chunk_x + 15 || z < chunk_z || z > chunk_z + 15 {
251-
return;
252-
}
285+
self.template_position.y = placement_y;
286+
self.piece.bounding_box = self
287+
.template
288+
.get_bounding_box(&self.place_settings, self.template_position);
253289

254-
let block_at = chunk.get_block_state(&Vector3::new(x, y, z));
255-
if !BlockState::from_id(block_at).is_air() {
256-
return;
257-
}
290+
let fossil_bb = self.piece.bounding_box;
291+
let mut enlarged_box = *chunk_box;
292+
enlarged_box.encompass(&fossil_bb);
258293

259-
// Place dried ghast with random rotation (vanilla: Blocks.DRIED_GHAST.getDefaultState().rotate())
260-
// Dried ghast default state is sufficient since rotation is cosmetic
261-
chunk.set_block_state(x, y, z, Block::DRIED_GHAST.default_state);
294+
self.place_blocks(chunk, &enlarged_box);
295+
Self::place_dried_ghast(chunk, seed, &fossil_bb, chunk_box);
262296
}
263297
}
298+
299+
fn make_settings(rotation: Rotation) -> StructurePlaceSettings {
300+
StructurePlaceSettings::new()
301+
.set_rotation(rotation)
302+
.set_mirror(Mirror::None)
303+
.add_processor(StructureProcessor::BlockIgnore(vec![
304+
IgnoredBlock {
305+
block_id: Block::STRUCTURE_BLOCK.id,
306+
properties: None,
307+
},
308+
IgnoredBlock {
309+
block_id: Block::AIR.id,
310+
properties: None,
311+
},
312+
]))
313+
}

0 commit comments

Comments
 (0)