11use std:: sync:: Arc ;
22
3- use pumpkin_data:: { Block , BlockDirection , BlockState } ;
3+ use pumpkin_data:: { Block , BlockDirection , BlockState , Mirror , Rotation } ;
44use 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
911use 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
126120impl 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