1use super::*;
2
3impl StructureTemplate {
4 pub(super) fn pre_filters_placement_bounds(processors: &[StructureProcessorKind]) -> bool {
8 !processors
9 .iter()
10 .any(|processor| matches!(processor, StructureProcessorKind::Capped { .. }))
11 }
12
13 pub(super) fn palette_blocks_for_placement<F: FnMut(&StructureBlockInfo, BlockPos)>(
14 blocks: &[StructureBlockInfo],
15 position: BlockPos,
16 settings: &StructurePlaceSettings<'_>,
17 mut f: F,
18 ) {
19 if !Self::pre_filters_placement_bounds(settings.processors) {
20 for block in blocks {
21 f(
22 block,
23 Self::transformed_position(position, block.pos, settings),
24 );
25 }
26 return;
27 }
28
29 for block in blocks {
30 let world_pos = Self::transformed_position(position, block.pos, settings);
31 if settings.bounding_box.contains_blockpos(world_pos) {
32 f(block, world_pos);
33 }
34 }
35 }
36
37 pub(super) const fn transformed_position(
38 position: BlockPos,
39 template_pos: BlockPos,
40 settings: &StructurePlaceSettings<'_>,
41 ) -> BlockPos {
42 let transformed = Self::calculate_relative_position(
43 template_pos,
44 settings.mirror,
45 settings.rotation,
46 settings.rotation_pivot,
47 );
48 position.offset(transformed.x(), transformed.y(), transformed.z())
49 }
50
51 pub(super) fn process_block(
52 region: &WorldGenRegion<'_>,
53 registry: &Registry,
54 original: &ProcessedBlockInfo,
55 initial: ProcessedBlockInfo,
56 settings: &StructurePlaceSettings<'_>,
57 reference_pos: BlockPos,
58 random: &mut WorldgenRandom,
59 ) -> Option<ProcessedBlockInfo> {
60 let mut current = initial;
61 if settings.block_ignore.ignores(registry, current.state) {
62 return None;
63 }
64
65 if settings.replace_jigsaws {
66 current = Self::replace_jigsaw_block(registry, current)?;
67 }
68
69 for processor in settings.processors {
70 current = Self::process_block_with_processor(
71 region,
72 registry,
73 processor,
74 original,
75 current,
76 settings,
77 reference_pos,
78 random,
79 )?;
80 }
81 if settings.projection == Some(Projection::TerrainMatching) {
82 current = Self::apply_terrain_matching_projection(region, original, current);
83 }
84 if settings.late_block_ignore.ignores(registry, current.state) {
85 return None;
86 }
87 Some(current)
88 }
89
90 #[expect(
91 clippy::too_many_arguments,
92 reason = "processor calls mirror vanilla StructureProcessor.processBlock inputs"
93 )]
94 pub(super) fn process_block_with_processor(
95 region: &WorldGenRegion<'_>,
96 registry: &Registry,
97 processor: &StructureProcessorKind,
98 original: &ProcessedBlockInfo,
99 current: ProcessedBlockInfo,
100 settings: &StructurePlaceSettings<'_>,
101 reference_pos: BlockPos,
102 random: &mut WorldgenRandom,
103 ) -> Option<ProcessedBlockInfo> {
104 match processor {
105 StructureProcessorKind::BlockRot {
106 rottable_blocks,
107 integrity,
108 } => {
109 if rottable_blocks.as_ref().is_some_and(|tag| {
110 !registry
111 .blocks
112 .is_in_tag(Self::block_for_state(registry, original.state), tag)
113 }) {
114 return Some(current);
115 }
116 (Self::processor_next_f32(settings, current.world_pos, random) <= *integrity)
117 .then_some(current)
118 }
119 StructureProcessorKind::ProtectedBlocks { cannot_replace } => {
120 let existing =
121 Self::block_for_state(registry, region.block_state(current.world_pos));
122 (!existing.has_tag(cannot_replace)).then_some(current)
123 }
124 StructureProcessorKind::Rule { rules } => {
125 let mut rule_random =
126 LegacyRandom::from_seed(Self::block_pos_seed(current.world_pos) as u64);
127 let location_state = region.block_state(current.world_pos);
128 for rule in rules {
129 if Self::rule_matches(
130 registry,
131 rule,
132 current.state,
133 location_state,
134 original.template_pos,
135 current.world_pos,
136 reference_pos,
137 &mut rule_random,
138 ) {
139 return Some(Self::apply_rule(registry, rule, current, &mut rule_random));
140 }
141 }
142 Some(current)
143 }
144 StructureProcessorKind::BlockAge { mossiness } => Some(Self::process_block_age(
145 registry, current, *mossiness, settings, random,
146 )),
147 StructureProcessorKind::LavaSubmergedBlock => Some(Self::process_lava_submerged_block(
148 registry,
149 region.block_state(current.world_pos),
150 current,
151 )),
152 StructureProcessorKind::BlackstoneReplace => {
153 Some(Self::process_blackstone_replace(registry, current))
154 }
155 StructureProcessorKind::Capped { .. } => Some(current),
156 }
157 }
158
159 pub(super) fn process_block_age(
160 registry: &Registry,
161 current: ProcessedBlockInfo,
162 mossiness: f32,
163 settings: &StructurePlaceSettings<'_>,
164 random: &mut WorldgenRandom,
165 ) -> ProcessedBlockInfo {
166 match settings.processor_random {
167 StructureProcessorRandom::Placement => {
168 Self::process_block_age_with_random(registry, current, mossiness, random)
169 }
170 StructureProcessorRandom::Positional => {
171 let mut random =
172 LegacyRandom::from_seed(Self::block_pos_seed(current.world_pos) as u64);
173 Self::process_block_age_with_random(registry, current, mossiness, &mut random)
174 }
175 }
176 }
177
178 pub(super) fn process_block_age_with_random(
179 registry: &Registry,
180 mut current: ProcessedBlockInfo,
181 mossiness: f32,
182 random: &mut impl Random,
183 ) -> ProcessedBlockInfo {
184 let block = Self::block_for_state(registry, current.state);
185 let new_state = if block == &vanilla_blocks::STONE_BRICKS
186 || block == &vanilla_blocks::STONE
187 || block == &vanilla_blocks::CHISELED_STONE_BRICKS
188 {
189 Self::maybe_replace_full_stone_block(registry, mossiness, random)
190 } else if block.has_tag(&BlockTag::STAIRS) {
191 Self::maybe_replace_stairs(registry, current.state, mossiness, random)
192 } else if block.has_tag(&BlockTag::SLABS) {
193 Self::maybe_replace_slab(registry, current.state, mossiness, random)
194 } else if block.has_tag(&BlockTag::WALLS) {
195 Self::maybe_replace_wall(registry, current.state, mossiness, random)
196 } else if block == &vanilla_blocks::OBSIDIAN {
197 Self::maybe_replace_obsidian(registry, random)
198 } else {
199 None
200 };
201
202 if let Some(new_state) = new_state {
203 current.state = new_state;
204 }
205 current
206 }
207
208 pub(super) fn maybe_replace_full_stone_block(
209 registry: &Registry,
210 mossiness: f32,
211 random: &mut impl Random,
212 ) -> Option<BlockStateId> {
213 if random.next_f32() >= 0.5 {
214 return None;
215 }
216
217 let non_mossy = [
218 registry
219 .blocks
220 .get_default_state_id(&vanilla_blocks::CRACKED_STONE_BRICKS),
221 Self::random_facing_stairs(registry, &vanilla_blocks::STONE_BRICK_STAIRS, random),
222 ];
223 let mossy = [
224 registry
225 .blocks
226 .get_default_state_id(&vanilla_blocks::MOSSY_STONE_BRICKS),
227 Self::random_facing_stairs(registry, &vanilla_blocks::MOSSY_STONE_BRICK_STAIRS, random),
228 ];
229 let candidates = if random.next_f32() < mossiness {
230 mossy
231 } else {
232 non_mossy
233 };
234 Some(candidates[random.next_i32_bounded(2) as usize])
235 }
236
237 pub(super) fn maybe_replace_stairs(
238 registry: &Registry,
239 state: BlockStateId,
240 mossiness: f32,
241 random: &mut impl Random,
242 ) -> Option<BlockStateId> {
243 if random.next_f32() >= 0.5 {
244 return None;
245 }
246
247 let non_mossy = [
248 registry
249 .blocks
250 .get_default_state_id(&vanilla_blocks::STONE_SLAB),
251 registry
252 .blocks
253 .get_default_state_id(&vanilla_blocks::STONE_BRICK_SLAB),
254 ];
255 let mossy = [
256 registry
257 .blocks
258 .copy_matching_properties(state, &vanilla_blocks::MOSSY_STONE_BRICK_STAIRS),
259 registry
260 .blocks
261 .get_default_state_id(&vanilla_blocks::MOSSY_STONE_BRICK_SLAB),
262 ];
263 let candidates = if random.next_f32() < mossiness {
264 mossy
265 } else {
266 non_mossy
267 };
268 Some(candidates[random.next_i32_bounded(2) as usize])
269 }
270
271 pub(super) fn maybe_replace_slab(
272 registry: &Registry,
273 state: BlockStateId,
274 mossiness: f32,
275 random: &mut impl Random,
276 ) -> Option<BlockStateId> {
277 (random.next_f32() < mossiness).then(|| {
278 registry
279 .blocks
280 .copy_matching_properties(state, &vanilla_blocks::MOSSY_STONE_BRICK_SLAB)
281 })
282 }
283
284 pub(super) fn maybe_replace_wall(
285 registry: &Registry,
286 state: BlockStateId,
287 mossiness: f32,
288 random: &mut impl Random,
289 ) -> Option<BlockStateId> {
290 (random.next_f32() < mossiness).then(|| {
291 registry
292 .blocks
293 .copy_matching_properties(state, &vanilla_blocks::MOSSY_STONE_BRICK_WALL)
294 })
295 }
296
297 pub(super) fn maybe_replace_obsidian(
298 registry: &Registry,
299 random: &mut impl Random,
300 ) -> Option<BlockStateId> {
301 (random.next_f32() < 0.15).then(|| {
302 registry
303 .blocks
304 .get_default_state_id(&vanilla_blocks::CRYING_OBSIDIAN)
305 })
306 }
307
308 pub(super) fn random_facing_stairs(
309 registry: &Registry,
310 block: BlockRef,
311 random: &mut impl Random,
312 ) -> BlockStateId {
313 const HORIZONTAL_DIRECTIONS: [BlockPropertyDirection; 4] = [
314 BlockPropertyDirection::North,
315 BlockPropertyDirection::East,
316 BlockPropertyDirection::South,
317 BlockPropertyDirection::West,
318 ];
319
320 let facing = HORIZONTAL_DIRECTIONS[random.next_i32_bounded(4) as usize];
321 let half = if random.next_i32_bounded(2) == 0 {
322 Half::Top
323 } else {
324 Half::Bottom
325 };
326 let state = registry.blocks.get_default_state_id(block);
327 let state = registry
328 .blocks
329 .set_property(state, &BlockStateProperties::FACING, facing);
330 registry
331 .blocks
332 .set_property(state, &BlockStateProperties::HALF, half)
333 }
334
335 pub(super) fn process_lava_submerged_block(
336 registry: &Registry,
337 existing_state: BlockStateId,
338 mut current: ProcessedBlockInfo,
339 ) -> ProcessedBlockInfo {
340 if Self::block_for_state(registry, existing_state) == &vanilla_blocks::LAVA
341 && !blocks::shapes::is_offset_shape_full_block(
342 registry
343 .blocks
344 .get_outline_shape_at(current.state, current.world_pos),
345 )
346 {
347 current.state = registry.blocks.get_default_state_id(&vanilla_blocks::LAVA);
348 }
349 current
350 }
351
352 pub(super) fn process_blackstone_replace(
353 registry: &Registry,
354 mut current: ProcessedBlockInfo,
355 ) -> ProcessedBlockInfo {
356 let Some(block) =
357 Self::blackstone_replacement_block(Self::block_for_state(registry, current.state))
358 else {
359 return current;
360 };
361
362 let mut new_state = registry.blocks.get_default_state_id(block);
363 if let Some(facing) = registry
364 .blocks
365 .try_get_property(current.state, &BlockStateProperties::FACING)
366 && registry
367 .blocks
368 .try_get_property(new_state, &BlockStateProperties::FACING)
369 .is_some()
370 {
371 new_state =
372 registry
373 .blocks
374 .set_property(new_state, &BlockStateProperties::FACING, facing);
375 }
376 if let Some(half) = registry
377 .blocks
378 .try_get_property(current.state, &BlockStateProperties::HALF)
379 && registry
380 .blocks
381 .try_get_property(new_state, &BlockStateProperties::HALF)
382 .is_some()
383 {
384 new_state = registry
385 .blocks
386 .set_property(new_state, &BlockStateProperties::HALF, half);
387 }
388 if let Some(slab_type) = registry
389 .blocks
390 .try_get_property(current.state, &BlockStateProperties::SLAB_TYPE)
391 && registry
392 .blocks
393 .try_get_property(new_state, &BlockStateProperties::SLAB_TYPE)
394 .is_some()
395 {
396 new_state = registry.blocks.set_property(
397 new_state,
398 &BlockStateProperties::SLAB_TYPE,
399 slab_type,
400 );
401 }
402
403 current.state = new_state;
404 current
405 }
406
407 pub(super) fn blackstone_replacement_block(block: BlockRef) -> Option<BlockRef> {
408 if block == &vanilla_blocks::COBBLESTONE || block == &vanilla_blocks::MOSSY_COBBLESTONE {
409 Some(&vanilla_blocks::BLACKSTONE)
410 } else if block == &vanilla_blocks::STONE {
411 Some(&vanilla_blocks::POLISHED_BLACKSTONE)
412 } else if block == &vanilla_blocks::STONE_BRICKS
413 || block == &vanilla_blocks::MOSSY_STONE_BRICKS
414 {
415 Some(&vanilla_blocks::POLISHED_BLACKSTONE_BRICKS)
416 } else if block == &vanilla_blocks::COBBLESTONE_STAIRS
417 || block == &vanilla_blocks::MOSSY_COBBLESTONE_STAIRS
418 {
419 Some(&vanilla_blocks::BLACKSTONE_STAIRS)
420 } else if block == &vanilla_blocks::STONE_STAIRS {
421 Some(&vanilla_blocks::POLISHED_BLACKSTONE_STAIRS)
422 } else if block == &vanilla_blocks::STONE_BRICK_STAIRS
423 || block == &vanilla_blocks::MOSSY_STONE_BRICK_STAIRS
424 {
425 Some(&vanilla_blocks::POLISHED_BLACKSTONE_BRICK_STAIRS)
426 } else if block == &vanilla_blocks::COBBLESTONE_SLAB
427 || block == &vanilla_blocks::MOSSY_COBBLESTONE_SLAB
428 {
429 Some(&vanilla_blocks::BLACKSTONE_SLAB)
430 } else if block == &vanilla_blocks::SMOOTH_STONE_SLAB
431 || block == &vanilla_blocks::STONE_SLAB
432 {
433 Some(&vanilla_blocks::POLISHED_BLACKSTONE_SLAB)
434 } else if block == &vanilla_blocks::STONE_BRICK_SLAB
435 || block == &vanilla_blocks::MOSSY_STONE_BRICK_SLAB
436 {
437 Some(&vanilla_blocks::POLISHED_BLACKSTONE_BRICK_SLAB)
438 } else if block == &vanilla_blocks::STONE_BRICK_WALL
439 || block == &vanilla_blocks::MOSSY_STONE_BRICK_WALL
440 {
441 Some(&vanilla_blocks::POLISHED_BLACKSTONE_BRICK_WALL)
442 } else if block == &vanilla_blocks::COBBLESTONE_WALL
443 || block == &vanilla_blocks::MOSSY_COBBLESTONE_WALL
444 {
445 Some(&vanilla_blocks::BLACKSTONE_WALL)
446 } else if block == &vanilla_blocks::CHISELED_STONE_BRICKS {
447 Some(&vanilla_blocks::CHISELED_POLISHED_BLACKSTONE)
448 } else if block == &vanilla_blocks::CRACKED_STONE_BRICKS {
449 Some(&vanilla_blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS)
450 } else if block == &vanilla_blocks::IRON_BARS {
451 Some(&vanilla_blocks::IRON_CHAIN)
452 } else {
453 None
454 }
455 }
456
457 #[expect(
458 clippy::too_many_arguments,
459 reason = "processor finalization receives vanilla's full template processing context"
460 )]
461 pub(super) fn finalize_processing(
462 region: &WorldGenRegion<'_>,
463 registry: &Registry,
464 position: BlockPos,
465 reference_pos: BlockPos,
466 settings: &StructurePlaceSettings<'_>,
467 original_blocks: &[ProcessedBlockInfo],
468 mut processed_blocks: Vec<ProcessedBlockInfo>,
469 random: &mut WorldgenRandom,
470 ) -> Vec<ProcessedBlockInfo> {
471 for processor in settings.processors {
472 if let StructureProcessorKind::Capped { delegate, limit } = processor {
473 processed_blocks = Self::finalize_capped_processing(
474 region,
475 registry,
476 position,
477 reference_pos,
478 delegate,
479 limit,
480 original_blocks,
481 processed_blocks,
482 settings,
483 random,
484 );
485 }
486 }
487 processed_blocks
488 }
489
490 #[expect(
491 clippy::too_many_arguments,
492 reason = "matches vanilla CappedProcessor.finalizeProcessing inputs"
493 )]
494 pub(super) fn finalize_capped_processing(
495 region: &WorldGenRegion<'_>,
496 registry: &Registry,
497 position: BlockPos,
498 reference_pos: BlockPos,
499 delegate: &StructureProcessorKind,
500 limit: &IntProvider,
501 original_blocks: &[ProcessedBlockInfo],
502 mut processed_blocks: Vec<ProcessedBlockInfo>,
503 settings: &StructurePlaceSettings<'_>,
504 random: &mut WorldgenRandom,
505 ) -> Vec<ProcessedBlockInfo> {
506 if limit.max() == 0 || processed_blocks.is_empty() {
507 return processed_blocks;
508 }
509 if original_blocks.len() != processed_blocks.len() {
510 return processed_blocks;
511 }
512
513 let Ok(processed_len_i32) = i32::try_from(processed_blocks.len()) else {
514 panic!(
515 "processed structure block list length {} exceeds i32 range",
516 processed_blocks.len()
517 );
518 };
519
520 let mut cap_random = Self::capped_processor_random(region.seed(), position);
521 let max_to_replace = limit.sample(&mut cap_random).min(processed_len_i32);
522 if max_to_replace < 1 {
523 return processed_blocks;
524 }
525
526 let mut indices = (0..processed_blocks.len()).collect::<Vec<_>>();
527 Self::vanilla_shuffle(&mut indices, &mut cap_random);
528
529 let mut replaced = 0;
530 for index in indices {
531 if replaced >= max_to_replace {
532 break;
533 }
534
535 let current = processed_blocks[index].clone();
536 let Some(altered) = Self::process_block_with_processor(
537 region,
538 registry,
539 delegate,
540 &original_blocks[index],
541 current,
542 settings,
543 reference_pos,
544 random,
545 ) else {
546 continue;
547 };
548
549 if altered != processed_blocks[index] {
550 processed_blocks[index] = altered;
551 replaced += 1;
552 }
553 }
554
555 processed_blocks
556 }
557
558 pub(super) fn processor_next_f32(
559 settings: &StructurePlaceSettings<'_>,
560 pos: BlockPos,
561 random: &mut WorldgenRandom,
562 ) -> f32 {
563 match settings.processor_random {
564 StructureProcessorRandom::Placement => random.next_f32(),
565 StructureProcessorRandom::Positional => {
566 let mut random = LegacyRandom::from_seed(Self::block_pos_seed(pos) as u64);
567 random.next_f32()
568 }
569 }
570 }
571
572 pub(super) fn capped_processor_random(world_seed: i64, position: BlockPos) -> RandomSource {
573 LegacyRandom::from_seed(world_seed as u64)
574 .next_positional()
575 .at(position.x(), position.y(), position.z())
576 }
577
578 pub(super) fn vanilla_shuffle<T>(items: &mut [T], random: &mut impl Random) {
579 for i in (1..items.len()).rev() {
580 let Ok(bound) = i32::try_from(i + 1) else {
581 panic!(
582 "structure processor shuffle length {} exceeds i32 range",
583 items.len()
584 );
585 };
586 let j = random.next_i32_bounded(bound) as usize;
587 items.swap(i, j);
588 }
589 }
590
591 pub(super) fn replace_jigsaw_block(
592 registry: &Registry,
593 mut current: ProcessedBlockInfo,
594 ) -> Option<ProcessedBlockInfo> {
595 if Self::block_for_state(registry, current.state) != &vanilla_blocks::JIGSAW {
596 return Some(current);
597 }
598
599 let Some(nbt) = current.nbt.as_ref() else {
600 return Some(current);
601 };
602 let final_state = nbt
603 .string("final_state")
604 .map_or_else(|| "minecraft:air".into(), |value| value.to_str());
605 current.state = Self::parse_block_state_string(registry, final_state.as_ref())
606 .unwrap_or_else(|| vanilla_blocks::AIR.default_state());
607 current.nbt = None;
608
609 (Self::block_for_state(registry, current.state) != &vanilla_blocks::STRUCTURE_VOID)
610 .then_some(current)
611 }
612
613 pub(super) fn parse_block_state_string(
614 registry: &Registry,
615 value: &str,
616 ) -> Option<BlockStateId> {
617 let (name, rest) = Self::read_block_identifier_prefix(value)?;
618 let id = Identifier::from_str(name).ok()?;
619 let block = registry.blocks.by_key(&id)?;
620
621 let mut parsed_properties = Vec::new();
622 if rest.starts_with('[') {
623 let properties = Self::read_block_state_properties_prefix(rest)?;
624 if !properties.is_empty() {
625 for property in properties.split(',') {
626 let (key, value) = property.split_once('=')?;
627 parsed_properties.push((key, value));
628 }
629 }
630 }
631
632 registry
633 .blocks
634 .state_id_from_block_defaulted_properties(block, parsed_properties)
635 }
636
637 pub(super) fn read_block_identifier_prefix(value: &str) -> Option<(&str, &str)> {
638 let end = value
639 .char_indices()
640 .find_map(|(index, char)| {
641 (char != ':' && !Identifier::valid_char(char)).then_some(index)
642 })
643 .unwrap_or(value.len());
644 (end > 0).then_some((&value[..end], &value[end..]))
645 }
646
647 pub(super) fn read_block_state_properties_prefix(rest: &str) -> Option<&str> {
648 let rest = rest.strip_prefix('[')?;
649 let end = rest.find(']')?;
650 Some(&rest[..end])
651 }
652
653 pub(super) fn apply_terrain_matching_projection(
654 region: &WorldGenRegion<'_>,
655 original: &ProcessedBlockInfo,
656 mut current: ProcessedBlockInfo,
657 ) -> ProcessedBlockInfo {
658 let height = region.height_at(
659 HeightmapType::WorldSurfaceWg,
660 current.world_pos.x(),
661 current.world_pos.z(),
662 ) - 1;
663 current.world_pos = BlockPos::new(
664 current.world_pos.x(),
665 height + original.template_pos.y(),
666 current.world_pos.z(),
667 );
668 current
669 }
670
671 #[expect(
672 clippy::too_many_arguments,
673 reason = "processor rules receive the same state and position tuple as vanilla"
674 )]
675 pub(super) fn rule_matches(
676 registry: &Registry,
677 rule: &ProcessorRuleData,
678 input_state: BlockStateId,
679 location_state: BlockStateId,
680 template_pos: BlockPos,
681 world_pos: BlockPos,
682 reference_pos: BlockPos,
683 random: &mut LegacyRandom,
684 ) -> bool {
685 Self::rule_test_matches(registry, &rule.input_predicate, input_state, random)
686 && Self::rule_test_matches(registry, &rule.location_predicate, location_state, random)
687 && Self::pos_rule_test_matches(
688 &rule.position_predicate,
689 template_pos,
690 world_pos,
691 reference_pos,
692 random,
693 )
694 }
695
696 pub(super) fn rule_test_matches(
697 registry: &Registry,
698 test: &StructureRuleTestData,
699 state: BlockStateId,
700 random: &mut LegacyRandom,
701 ) -> bool {
702 match test {
703 StructureRuleTestData::AlwaysTrue => true,
704 StructureRuleTestData::BlockMatch { block } => registry
705 .blocks
706 .by_key(block)
707 .is_some_and(|block_ref| Self::block_for_state(registry, state) == block_ref),
708 StructureRuleTestData::RandomBlockMatch { block, probability } => {
709 registry
710 .blocks
711 .by_key(block)
712 .is_some_and(|block_ref| Self::block_for_state(registry, state) == block_ref)
713 && random.next_f32() < *probability
714 }
715 StructureRuleTestData::TagMatch { tag } => registry
716 .blocks
717 .is_in_tag(Self::block_for_state(registry, state), tag),
718 StructureRuleTestData::BlockStateMatch { block_state } => {
719 state
720 == WorldgenStateResolver::block_state_from_data(
721 registry,
722 block_state,
723 "structure processor block-state predicate",
724 )
725 }
726 }
727 }
728
729 pub(super) fn pos_rule_test_matches(
730 test: &PosRuleTestData,
731 _template_pos: BlockPos,
732 world_pos: BlockPos,
733 reference_pos: BlockPos,
734 random: &mut LegacyRandom,
735 ) -> bool {
736 match test {
737 PosRuleTestData::AlwaysTrue => true,
738 PosRuleTestData::AxisAlignedLinearPos {
739 axis,
740 min_chance,
741 max_chance,
742 min_dist,
743 max_dist,
744 } => {
745 let dist = match axis {
746 StructureProcessorAxis::X => (world_pos.x() - reference_pos.x()).abs(),
747 StructureProcessorAxis::Y => (world_pos.y() - reference_pos.y()).abs(),
748 StructureProcessorAxis::Z => (world_pos.z() - reference_pos.z()).abs(),
749 };
750 random.next_f32()
751 <= Self::clamped_lerp_inverse(
752 dist,
753 *min_dist,
754 *max_dist,
755 *min_chance,
756 *max_chance,
757 )
758 }
759 }
760 }
761
762 pub(super) fn apply_rule(
763 registry: &Registry,
764 rule: &ProcessorRuleData,
765 mut current: ProcessedBlockInfo,
766 random: &mut LegacyRandom,
767 ) -> ProcessedBlockInfo {
768 current.state = WorldgenStateResolver::block_state_from_data(
769 registry,
770 &rule.output_state,
771 "structure processor output state",
772 );
773 current.nbt = match &rule.block_entity_modifier {
774 RuleBlockEntityModifierData::Passthrough => current.nbt,
775 RuleBlockEntityModifierData::AppendLoot { loot_table } => {
776 let mut nbt = current.nbt.unwrap_or_default();
777 nbt.insert("LootTable", NbtTag::String(loot_table.to_string().into()));
778 nbt.insert("LootTableSeed", NbtTag::Long(random.next_i64()));
779 Some(nbt)
780 }
781 };
782 current
783 }
784}