Skip to main content

steel_core/worldgen/feature/features/
sculk_patch.rs

1#![expect(
2    clippy::too_many_arguments,
3    reason = "sculk spreading helpers mirror vanilla cursor state"
4)]
5
6use super::super::prelude::*;
7use super::super::runner::FeatureDecorationRunner;
8use crate::behavior::blocks::multiface_face_property;
9use core::mem;
10use steel_registry::vanilla_block_entity_types;
11use steel_registry::vanilla_block_tags::BlockTag;
12
13const SCULK_DEFAULT_SPREAD_TYPES: [SculkSpreadType; 3] = [
14    SculkSpreadType::SamePosition,
15    SculkSpreadType::SamePlane,
16    SculkSpreadType::WrapAround,
17];
18const SCULK_SAME_SPACE_SPREAD_TYPES: [SculkSpreadType; 1] = [SculkSpreadType::SamePosition];
19
20#[derive(Clone, Copy)]
21enum SculkSpreadType {
22    SamePosition,
23    SamePlane,
24    WrapAround,
25}
26
27struct SculkSpreadPos {
28    pos: BlockPos,
29    face: Direction,
30}
31
32#[derive(Clone, Copy)]
33enum SculkBehaviorKind {
34    Default,
35    Sculk,
36    SculkVein,
37}
38
39struct SculkSpreader {
40    is_world_generation: bool,
41    replaceable_blocks: Identifier,
42    growth_spawn_cost: i32,
43    no_growth_radius: i32,
44    charge_decay_rate: i32,
45    additional_decay_rate: i32,
46    cursors: Vec<SculkChargeCursor>,
47}
48
49impl SculkSpreader {
50    const MAX_CHARGE: i32 = 1000;
51    const MAX_CURSORS: usize = 32;
52
53    const fn worldgen() -> Self {
54        Self {
55            is_world_generation: true,
56            replaceable_blocks: BlockTag::SCULK_REPLACEABLE_WORLD_GEN,
57            growth_spawn_cost: 50,
58            no_growth_radius: 1,
59            charge_decay_rate: 5,
60            additional_decay_rate: 10,
61            cursors: Vec::new(),
62        }
63    }
64
65    fn add_cursors(&mut self, start_pos: BlockPos, mut charge: i32) {
66        while charge > 0 {
67            let current_charge = charge.min(Self::MAX_CHARGE);
68            self.add_cursor(SculkChargeCursor::new(start_pos, current_charge));
69            charge -= current_charge;
70        }
71    }
72
73    fn add_cursor(&mut self, cursor: SculkChargeCursor) {
74        if self.cursors.len() < Self::MAX_CURSORS {
75            self.cursors.push(cursor);
76        }
77    }
78
79    fn clear(&mut self) {
80        self.cursors.clear();
81    }
82}
83
84struct SculkChargeCursor {
85    pos: BlockPos,
86    charge: i32,
87    update_delay: i32,
88    decay_delay: i32,
89    facings: Option<Vec<Direction>>,
90}
91
92impl SculkChargeCursor {
93    const MAX_CURSOR_DISTANCE: i32 = 1024;
94
95    const fn new(pos: BlockPos, charge: i32) -> Self {
96        Self {
97            pos,
98            charge,
99            update_delay: 0,
100            decay_delay: 1,
101            facings: None,
102        }
103    }
104}
105
106impl FeatureDecorationRunner {
107    pub(in crate::worldgen::feature) fn place_sculk_patch_feature(
108        region: &mut WorldGenRegion<'_>,
109        registry: &Registry,
110        random: &mut WorldgenRandom,
111        config: &SculkPatchConfiguration,
112        origin: BlockPos,
113    ) -> bool {
114        if !Self::can_sculk_spread_from(region, origin) {
115            return false;
116        }
117
118        let mut spreader = SculkSpreader::worldgen();
119        let total_rounds = config.spread_rounds + config.growth_rounds;
120        for round in 0..total_rounds {
121            for _ in 0..config.charge_count {
122                spreader.add_cursors(origin, config.amount_per_charge);
123            }
124
125            let spread_veins = round < config.spread_rounds;
126            for _ in 0..config.spread_attempts {
127                Self::sculk_update_cursors(
128                    region,
129                    registry,
130                    random,
131                    origin,
132                    &mut spreader,
133                    spread_veins,
134                );
135            }
136
137            spreader.clear();
138        }
139
140        let below = origin.below();
141        let below_state = region.block_state(below);
142        if random.next_f32() <= config.catalyst_chance
143            && shapes::is_offset_shape_full_block(below_state.get_collision_shape_at(below))
144        {
145            let catalyst = vanilla_blocks::SCULK_CATALYST.default_state();
146            if region.set_block_state(origin, catalyst, UpdateFlags::UPDATE_ALL) {
147                Self::set_empty_block_entity(
148                    region,
149                    origin,
150                    &vanilla_block_entity_types::SCULK_CATALYST,
151                    catalyst,
152                );
153            }
154        }
155
156        let extra_growths = config.extra_rare_growths.sample(random);
157        for _ in 0..extra_growths {
158            let candidate = origin.offset(
159                random.next_i32_bounded(5) - 2,
160                0,
161                random.next_i32_bounded(5) - 2,
162            );
163            let below = candidate.below();
164            if !region.block_state(candidate).is_air()
165                || !region
166                    .block_state(below)
167                    .is_face_sturdy_at(below, Direction::Up)
168            {
169                continue;
170            }
171
172            let shrieker = vanilla_blocks::SCULK_SHRIEKER
173                .default_state()
174                .set_value(&BlockStateProperties::CAN_SUMMON, true);
175            if region.set_block_state(candidate, shrieker, UpdateFlags::UPDATE_ALL) {
176                Self::set_empty_block_entity(
177                    region,
178                    candidate,
179                    &vanilla_block_entity_types::SCULK_SHRIEKER,
180                    shrieker,
181                );
182            }
183        }
184
185        true
186    }
187
188    fn can_sculk_spread_from(region: &WorldGenRegion<'_>, origin: BlockPos) -> bool {
189        let start = region.block_state(origin);
190        if !matches!(Self::sculk_behavior(start), SculkBehaviorKind::Default) {
191            return true;
192        }
193
194        if !start.is_air()
195            && (start.get_block() != &vanilla_blocks::WATER
196                || !get_fluid_state_from_block(start).is_source())
197        {
198            return false;
199        }
200
201        Self::VANILLA_DIRECTION_VALUES.iter().any(|direction| {
202            let pos = origin.relative(*direction);
203            let state = region.block_state(pos);
204            shapes::is_offset_shape_full_block(state.get_collision_shape_at(pos))
205        })
206    }
207
208    fn sculk_update_cursors(
209        region: &mut WorldGenRegion<'_>,
210        registry: &Registry,
211        random: &mut WorldgenRandom,
212        origin: BlockPos,
213        spreader: &mut SculkSpreader,
214        spread_veins: bool,
215    ) {
216        if spreader.cursors.is_empty() {
217            return;
218        }
219
220        let cursors = mem::take(&mut spreader.cursors);
221        for mut cursor in cursors {
222            if Self::sculk_cursor_is_pos_unreasonable(cursor.pos, origin) {
223                continue;
224            }
225
226            Self::sculk_update_cursor(
227                region,
228                registry,
229                random,
230                origin,
231                spreader,
232                spread_veins,
233                &mut cursor,
234            );
235            if cursor.charge > 0 {
236                spreader.cursors.push(cursor);
237            }
238        }
239    }
240
241    fn sculk_update_cursor(
242        region: &mut WorldGenRegion<'_>,
243        registry: &Registry,
244        random: &mut WorldgenRandom,
245        origin: BlockPos,
246        spreader: &SculkSpreader,
247        spread_veins: bool,
248        cursor: &mut SculkChargeCursor,
249    ) {
250        if cursor.charge <= 0 {
251            return;
252        }
253
254        if cursor.update_delay > 0 {
255            cursor.update_delay -= 1;
256            return;
257        }
258
259        let mut current_state = region.block_state(cursor.pos);
260        let mut behavior = Self::sculk_behavior(current_state);
261        if spread_veins
262            && Self::sculk_attempt_spread_vein(
263                region,
264                cursor.pos,
265                current_state,
266                cursor.facings.as_deref(),
267                spreader.is_world_generation,
268                behavior,
269            )
270            && Self::sculk_can_change_block_state_on_spread(behavior)
271        {
272            current_state = region.block_state(cursor.pos);
273            behavior = Self::sculk_behavior(current_state);
274        }
275
276        cursor.charge = Self::sculk_attempt_use_charge(
277            region,
278            registry,
279            random,
280            origin,
281            spreader,
282            spread_veins,
283            cursor,
284            behavior,
285        );
286        if cursor.charge <= 0 {
287            Self::sculk_on_discharged(region, current_state, cursor.pos);
288            return;
289        }
290
291        let transfer_pos = Self::sculk_get_valid_movement_pos(region, cursor.pos, random);
292        if let Some(transfer_pos) = transfer_pos {
293            Self::sculk_on_discharged(region, current_state, cursor.pos);
294            cursor.pos = transfer_pos;
295            if spreader.is_world_generation
296                && !Self::sculk_horizontal_close_to_origin(cursor.pos, origin, 15.0)
297            {
298                cursor.charge = 0;
299                return;
300            }
301            current_state = region.block_state(transfer_pos);
302        }
303
304        if !matches!(
305            Self::sculk_behavior(current_state),
306            SculkBehaviorKind::Default
307        ) {
308            cursor.facings = Some(Self::sculk_available_faces(current_state));
309        }
310
311        cursor.decay_delay = Self::sculk_update_decay_delay(behavior, cursor.decay_delay);
312        cursor.update_delay = Self::sculk_spread_delay(behavior);
313    }
314
315    fn sculk_attempt_spread_vein(
316        region: &mut WorldGenRegion<'_>,
317
318        pos: BlockPos,
319        state: BlockStateId,
320        facings: Option<&[Direction]>,
321        post_process: bool,
322        behavior: SculkBehaviorKind,
323    ) -> bool {
324        match behavior {
325            SculkBehaviorKind::Default => match facings {
326                None => Self::sculk_vein_spread_all(region, state, pos, post_process, true) > 0,
327                Some(faces) if !faces.is_empty() => {
328                    if !state.is_air() && !get_fluid_state_from_block(state).is_water() {
329                        return false;
330                    }
331                    Self::sculk_vein_regrow(region, pos, state, faces)
332                }
333                Some(_) => Self::sculk_vein_spread_all(region, state, pos, post_process, false) > 0,
334            },
335            SculkBehaviorKind::Sculk | SculkBehaviorKind::SculkVein => {
336                Self::sculk_vein_spread_all(region, state, pos, post_process, false) > 0
337            }
338        }
339    }
340
341    fn sculk_attempt_use_charge(
342        region: &mut WorldGenRegion<'_>,
343        registry: &Registry,
344        random: &mut WorldgenRandom,
345        origin: BlockPos,
346        spreader: &SculkSpreader,
347        spread_veins: bool,
348        cursor: &SculkChargeCursor,
349        behavior: SculkBehaviorKind,
350    ) -> i32 {
351        match behavior {
352            SculkBehaviorKind::Default => {
353                if cursor.decay_delay > 0 {
354                    cursor.charge
355                } else {
356                    0
357                }
358            }
359            SculkBehaviorKind::Sculk => {
360                Self::sculk_block_attempt_use_charge(region, random, origin, spreader, cursor)
361            }
362            SculkBehaviorKind::SculkVein => Self::sculk_vein_attempt_use_charge(
363                region,
364                registry,
365                random,
366                spreader,
367                spread_veins,
368                cursor,
369            ),
370        }
371    }
372
373    fn sculk_block_attempt_use_charge(
374        region: &mut WorldGenRegion<'_>,
375        random: &mut WorldgenRandom,
376        origin: BlockPos,
377        spreader: &SculkSpreader,
378        cursor: &SculkChargeCursor,
379    ) -> i32 {
380        let charge = cursor.charge;
381        if charge == 0 || random.next_i32_bounded(spreader.charge_decay_rate) != 0 {
382            return charge;
383        }
384
385        let is_close_to_catalyst =
386            Self::sculk_closer_than(cursor.pos, origin, spreader.no_growth_radius);
387        if !is_close_to_catalyst && Self::sculk_can_place_growth(region, cursor.pos) {
388            if random.next_i32_bounded(spreader.growth_spawn_cost) < charge {
389                let growth_pos = cursor.pos.above();
390                let growth_state = Self::sculk_random_growth_state(
391                    region,
392                    random,
393                    growth_pos,
394                    spreader.is_world_generation,
395                );
396                if region.set_block_state(growth_pos, growth_state, UpdateFlags::UPDATE_ALL) {
397                    Self::set_sculk_growth_block_entity(region, growth_pos, growth_state);
398                }
399            }
400
401            0.max(charge - spreader.growth_spawn_cost)
402        } else if random.next_i32_bounded(spreader.additional_decay_rate) != 0 {
403            charge
404        } else if is_close_to_catalyst {
405            charge - 1
406        } else {
407            charge - Self::sculk_decay_penalty(spreader, cursor.pos, origin, charge)
408        }
409    }
410
411    fn sculk_vein_attempt_use_charge(
412        region: &mut WorldGenRegion<'_>,
413        registry: &Registry,
414        random: &mut WorldgenRandom,
415        spreader: &SculkSpreader,
416        spread_veins: bool,
417        cursor: &SculkChargeCursor,
418    ) -> i32 {
419        if spread_veins
420            && Self::sculk_vein_attempt_place_sculk(region, registry, random, spreader, cursor.pos)
421        {
422            cursor.charge - 1
423        } else if random.next_i32_bounded(spreader.charge_decay_rate) == 0 {
424            fast_floor(f64::from(cursor.charge) * 0.5) as i32
425        } else {
426            cursor.charge
427        }
428    }
429
430    fn sculk_vein_attempt_place_sculk(
431        region: &mut WorldGenRegion<'_>,
432        registry: &Registry,
433        random: &mut WorldgenRandom,
434        spreader: &SculkSpreader,
435        pos: BlockPos,
436    ) -> bool {
437        let state = region.block_state(pos);
438        let directions = Self::shuffled_directions(random, Self::VANILLA_DIRECTION_VALUES);
439        for support in directions {
440            if !Self::sculk_vein_has_face(state, support) {
441                continue;
442            }
443
444            let support_pos = pos.relative(support);
445            let support_state = region.block_state(support_pos);
446            if !registry
447                .blocks
448                .is_in_tag(support_state.get_block(), &spreader.replaceable_blocks)
449            {
450                continue;
451            }
452
453            let sculk = vanilla_blocks::SCULK.default_state();
454            let _ = region.set_block_state(support_pos, sculk, UpdateFlags::UPDATE_ALL);
455            let _ = Self::sculk_vein_spread_all(
456                region,
457                sculk,
458                support_pos,
459                spreader.is_world_generation,
460                false,
461            );
462
463            let skip = support.opposite();
464            for direction in Self::VANILLA_DIRECTION_VALUES {
465                if direction == skip {
466                    continue;
467                }
468
469                let vein_pos = support_pos.relative(direction);
470                let possible_vein = region.block_state(vein_pos);
471                if possible_vein.get_block() == &vanilla_blocks::SCULK_VEIN {
472                    Self::sculk_on_discharged(region, possible_vein, vein_pos);
473                }
474            }
475
476            return true;
477        }
478
479        false
480    }
481
482    fn sculk_vein_spread_all(
483        region: &mut WorldGenRegion<'_>,
484
485        state: BlockStateId,
486        pos: BlockPos,
487        post_process: bool,
488        same_space_only: bool,
489    ) -> i64 {
490        let mut count = 0;
491        for starting_face in Self::VANILLA_DIRECTION_VALUES {
492            if !Self::sculk_vein_can_spread_from(state, starting_face) {
493                continue;
494            }
495
496            for spread_direction in Self::VANILLA_DIRECTION_VALUES {
497                if Self::sculk_vein_spread_from_face_toward_direction(
498                    region,
499                    state,
500                    pos,
501                    starting_face,
502                    spread_direction,
503                    post_process,
504                    same_space_only,
505                )
506                .is_some()
507                {
508                    count += 1;
509                }
510            }
511        }
512        count
513    }
514
515    fn sculk_vein_spread_from_face_toward_direction(
516        region: &mut WorldGenRegion<'_>,
517
518        state: BlockStateId,
519        pos: BlockPos,
520        starting_face: Direction,
521        spread_direction: Direction,
522        post_process: bool,
523        same_space_only: bool,
524    ) -> Option<SculkSpreadPos> {
525        let spread_pos = Self::sculk_vein_get_spread_from_face_toward_direction(
526            region,
527            state,
528            pos,
529            starting_face,
530            spread_direction,
531            same_space_only,
532        )?;
533        if Self::sculk_vein_spread_to_face(region, &spread_pos, post_process) {
534            Some(spread_pos)
535        } else {
536            None
537        }
538    }
539
540    fn sculk_vein_get_spread_from_face_toward_direction(
541        region: &WorldGenRegion<'_>,
542
543        state: BlockStateId,
544        pos: BlockPos,
545        starting_face: Direction,
546        spread_direction: Direction,
547        same_space_only: bool,
548    ) -> Option<SculkSpreadPos> {
549        if spread_direction.axis() == starting_face.axis() {
550            return None;
551        }
552
553        if !Self::sculk_vein_is_other_block_valid_as_source(state)
554            && (!Self::sculk_vein_has_face(state, starting_face)
555                || Self::sculk_vein_has_face(state, spread_direction))
556        {
557            return None;
558        }
559
560        let spread_types = if same_space_only {
561            SCULK_SAME_SPACE_SPREAD_TYPES.as_slice()
562        } else {
563            SCULK_DEFAULT_SPREAD_TYPES.as_slice()
564        };
565        for spread_type in spread_types {
566            let spread_pos =
567                Self::sculk_vein_spread_pos(pos, spread_direction, starting_face, *spread_type);
568            if Self::sculk_vein_can_spread_into(region, pos, &spread_pos) {
569                return Some(spread_pos);
570            }
571        }
572
573        None
574    }
575
576    fn sculk_vein_spread_to_face(
577        region: &mut WorldGenRegion<'_>,
578
579        spread_pos: &SculkSpreadPos,
580        post_process: bool,
581    ) -> bool {
582        let old_state = region.block_state(spread_pos.pos);
583        let Some(spread_state) = Self::sculk_vein_state_for_placement(
584            region,
585            old_state,
586            spread_pos.pos,
587            spread_pos.face,
588        ) else {
589            return false;
590        };
591
592        if post_process {
593            region.mark_pos_for_postprocessing(spread_pos.pos);
594        }
595        region.set_block_state(spread_pos.pos, spread_state, UpdateFlags::UPDATE_CLIENTS)
596    }
597
598    fn sculk_vein_can_spread_into(
599        region: &WorldGenRegion<'_>,
600        source_pos: BlockPos,
601        spread_pos: &SculkSpreadPos,
602    ) -> bool {
603        let existing_state = region.block_state(spread_pos.pos);
604        Self::sculk_patch_vein_state_can_be_replaced(
605            region,
606            source_pos,
607            spread_pos.pos,
608            spread_pos.face,
609            existing_state,
610        ) && Self::sculk_vein_is_valid_state_for_placement(
611            region,
612            existing_state,
613            spread_pos.pos,
614            spread_pos.face,
615        )
616    }
617
618    fn sculk_patch_vein_state_can_be_replaced(
619        region: &WorldGenRegion<'_>,
620        source_pos: BlockPos,
621        placement_pos: BlockPos,
622        placement_direction: Direction,
623        existing_state: BlockStateId,
624    ) -> bool {
625        let against_state = region.block_state(placement_pos.relative(placement_direction));
626        if against_state.get_block() == &vanilla_blocks::SCULK
627            || against_state.get_block() == &vanilla_blocks::SCULK_CATALYST
628            || against_state.get_block() == &vanilla_blocks::MOVING_PISTON
629        {
630            return false;
631        }
632
633        if Self::manhattan_distance(source_pos, placement_pos) == 2 {
634            let neighbor_pos = source_pos.relative(placement_direction.opposite());
635            if region
636                .block_state(neighbor_pos)
637                .is_face_sturdy_at(neighbor_pos, placement_direction)
638            {
639                return false;
640            }
641        }
642
643        let fluid_state = get_fluid_state_from_block(existing_state);
644        if !fluid_state.is_empty() && !fluid_state.is_water() {
645            return false;
646        }
647
648        if existing_state.get_block().has_tag(&BlockTag::FIRE) {
649            return false;
650        }
651
652        existing_state.is_replaceable()
653            || Self::sculk_default_multiface_state_can_be_replaced_for_block(existing_state)
654    }
655
656    fn sculk_default_multiface_state_can_be_replaced_for_block(
657        existing_state: BlockStateId,
658    ) -> bool {
659        existing_state.is_air()
660            || existing_state.get_block() == &vanilla_blocks::SCULK_VEIN
661            || (existing_state.get_block() == &vanilla_blocks::WATER
662                && get_fluid_state_from_block(existing_state).is_source())
663    }
664
665    fn sculk_vein_state_for_placement(
666        region: &WorldGenRegion<'_>,
667        old_state: BlockStateId,
668        placement_pos: BlockPos,
669        placement_direction: Direction,
670    ) -> Option<BlockStateId> {
671        if !Self::sculk_vein_is_valid_state_for_placement(
672            region,
673            old_state,
674            placement_pos,
675            placement_direction,
676        ) {
677            return None;
678        }
679
680        let mut new_state = if old_state.get_block() == &vanilla_blocks::SCULK_VEIN {
681            old_state
682        } else {
683            let state = vanilla_blocks::SCULK_VEIN.default_state();
684            let fluid_state = get_fluid_state_from_block(old_state);
685            if fluid_state.is_water() && fluid_state.is_source() {
686                state.set_value(&BlockStateProperties::WATERLOGGED, true)
687            } else {
688                state
689            }
690        };
691        new_state = new_state.set_value(multiface_face_property(placement_direction), true);
692        Some(new_state)
693    }
694
695    fn sculk_vein_is_valid_state_for_placement(
696        region: &WorldGenRegion<'_>,
697        old_state: BlockStateId,
698        placement_pos: BlockPos,
699        placement_direction: Direction,
700    ) -> bool {
701        if old_state.get_block() == &vanilla_blocks::SCULK_VEIN
702            && Self::sculk_vein_has_face(old_state, placement_direction)
703        {
704            return false;
705        }
706
707        Self::can_attach_to_multiface(region, placement_pos, placement_direction)
708    }
709
710    fn sculk_vein_regrow(
711        region: &mut WorldGenRegion<'_>,
712        pos: BlockPos,
713        existing_state: BlockStateId,
714        faces: &[Direction],
715    ) -> bool {
716        let mut has_face = false;
717        let mut new_state = vanilla_blocks::SCULK_VEIN.default_state();
718
719        for &face in faces {
720            if Self::can_attach_to_multiface(region, pos, face) {
721                new_state = new_state.set_value(multiface_face_property(face), true);
722                has_face = true;
723            }
724        }
725
726        if !has_face {
727            return false;
728        }
729
730        if !get_fluid_state_from_block(existing_state).is_empty() {
731            new_state = new_state.set_value(&BlockStateProperties::WATERLOGGED, true);
732        }
733
734        region.set_block_state(pos, new_state, UpdateFlags::UPDATE_ALL)
735    }
736
737    fn sculk_on_discharged(
738        region: &mut WorldGenRegion<'_>,
739        mut state: BlockStateId,
740        pos: BlockPos,
741    ) {
742        if state.get_block() != &vanilla_blocks::SCULK_VEIN {
743            return;
744        }
745
746        for direction in Self::VANILLA_DIRECTION_VALUES {
747            if Self::sculk_vein_has_face(state, direction)
748                && region.block_state(pos.relative(direction)).get_block() == &vanilla_blocks::SCULK
749            {
750                state = state.set_value(multiface_face_property(direction), false);
751            }
752        }
753
754        if !Self::sculk_vein_has_any_face(state) {
755            state = if get_fluid_state_from_block(state).is_empty() {
756                vanilla_blocks::AIR.default_state()
757            } else {
758                vanilla_blocks::WATER.default_state()
759            };
760        }
761
762        let _ = region.set_block_state(pos, state, UpdateFlags::UPDATE_ALL);
763    }
764
765    fn sculk_get_valid_movement_pos(
766        region: &WorldGenRegion<'_>,
767        pos: BlockPos,
768        random: &mut WorldgenRandom,
769    ) -> Option<BlockPos> {
770        let mut sculk_position = pos;
771        for offset in Self::sculk_randomized_non_corner_neighbor_offsets(random) {
772            let neighbor = pos.offset(offset.x(), offset.y(), offset.z());
773            let transferee = region.block_state(neighbor);
774            if matches!(Self::sculk_behavior(transferee), SculkBehaviorKind::Default)
775                || !Self::sculk_is_movement_unobstructed(region, pos, neighbor)
776            {
777                continue;
778            }
779
780            sculk_position = neighbor;
781            if Self::sculk_vein_has_substrate_access(region, transferee, neighbor) {
782                break;
783            }
784        }
785
786        if sculk_position == pos {
787            None
788        } else {
789            Some(sculk_position)
790        }
791    }
792
793    fn sculk_randomized_non_corner_neighbor_offsets(random: &mut WorldgenRandom) -> Vec<BlockPos> {
794        let mut offsets = Vec::with_capacity(18);
795        for z in -1..=1 {
796            for y in -1..=1 {
797                for x in -1..=1 {
798                    if (x == 0 || y == 0 || z == 0) && (x != 0 || y != 0 || z != 0) {
799                        offsets.push(BlockPos::new(x, y, z));
800                    }
801                }
802            }
803        }
804
805        for i in (1..offsets.len()).rev() {
806            let Ok(bound) = i32::try_from(i + 1) else {
807                panic!("sculk neighbor offset count exceeds i32 range");
808            };
809            let j = random.next_i32_bounded(bound) as usize;
810            offsets.swap(i, j);
811        }
812        offsets
813    }
814
815    fn sculk_is_movement_unobstructed(
816        region: &WorldGenRegion<'_>,
817        from: BlockPos,
818        to: BlockPos,
819    ) -> bool {
820        if Self::manhattan_distance(from, to) == 1 {
821            return true;
822        }
823
824        let dx = to.x() - from.x();
825        let dy = to.y() - from.y();
826        let dz = to.z() - from.z();
827        let direction_x = Self::sculk_direction_from_axis_delta(Axis::X, dx);
828        let direction_y = Self::sculk_direction_from_axis_delta(Axis::Y, dy);
829        let direction_z = Self::sculk_direction_from_axis_delta(Axis::Z, dz);
830        if dx == 0 {
831            Self::sculk_is_unobstructed(region, from, direction_y)
832                || Self::sculk_is_unobstructed(region, from, direction_z)
833        } else if dy == 0 {
834            Self::sculk_is_unobstructed(region, from, direction_x)
835                || Self::sculk_is_unobstructed(region, from, direction_z)
836        } else {
837            Self::sculk_is_unobstructed(region, from, direction_x)
838                || Self::sculk_is_unobstructed(region, from, direction_y)
839        }
840    }
841
842    fn sculk_is_unobstructed(
843        region: &WorldGenRegion<'_>,
844        from: BlockPos,
845        direction: Direction,
846    ) -> bool {
847        let test_pos = from.relative(direction);
848        !region
849            .block_state(test_pos)
850            .is_face_sturdy_at(test_pos, direction.opposite())
851    }
852
853    const fn sculk_direction_from_axis_delta(axis: Axis, delta: i32) -> Direction {
854        match (axis, delta < 0) {
855            (Axis::X, true) => Direction::West,
856            (Axis::X, false) => Direction::East,
857            (Axis::Y, true) => Direction::Down,
858            (Axis::Y, false) => Direction::Up,
859            (Axis::Z, true) => Direction::North,
860            (Axis::Z, false) => Direction::South,
861        }
862    }
863
864    fn sculk_vein_has_substrate_access(
865        region: &WorldGenRegion<'_>,
866
867        state: BlockStateId,
868        pos: BlockPos,
869    ) -> bool {
870        if state.get_block() != &vanilla_blocks::SCULK_VEIN {
871            return false;
872        }
873
874        Self::VANILLA_DIRECTION_VALUES.iter().any(|&direction| {
875            Self::sculk_vein_has_face(state, direction)
876                && region
877                    .block_state(pos.relative(direction))
878                    .get_block()
879                    .has_tag(&BlockTag::SCULK_REPLACEABLE)
880        })
881    }
882
883    fn sculk_can_place_growth(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
884        let above = pos.above();
885        let state_above = region.block_state(above);
886        if !state_above.is_air()
887            && (state_above.get_block() != &vanilla_blocks::WATER
888                || !get_fluid_state_from_block(state_above).is_water())
889        {
890            return false;
891        }
892
893        let mut growth_count = 0;
894        for z in -4..=4 {
895            for y in 0..=2 {
896                for x in -4..=4 {
897                    let state = region.block_state(pos.offset(x, y, z));
898                    if state.get_block() == &vanilla_blocks::SCULK_SENSOR
899                        || state.get_block() == &vanilla_blocks::SCULK_SHRIEKER
900                    {
901                        growth_count += 1;
902                    }
903
904                    if growth_count > 2 {
905                        return false;
906                    }
907                }
908            }
909        }
910
911        true
912    }
913
914    fn sculk_random_growth_state(
915        region: &WorldGenRegion<'_>,
916        random: &mut WorldgenRandom,
917        pos: BlockPos,
918        is_world_generation: bool,
919    ) -> BlockStateId {
920        let state = if random.next_i32_bounded(11) == 0 {
921            vanilla_blocks::SCULK_SHRIEKER
922                .default_state()
923                .set_value(&BlockStateProperties::CAN_SUMMON, is_world_generation)
924        } else {
925            vanilla_blocks::SCULK_SENSOR.default_state()
926        };
927
928        if state
929            .try_get_value(&BlockStateProperties::WATERLOGGED)
930            .is_some()
931            && !get_fluid_state_from_block(region.block_state(pos)).is_empty()
932        {
933            state.set_value(&BlockStateProperties::WATERLOGGED, true)
934        } else {
935            state
936        }
937    }
938
939    fn set_sculk_growth_block_entity(
940        region: &WorldGenRegion<'_>,
941        pos: BlockPos,
942        state: BlockStateId,
943    ) {
944        if state.get_block() == &vanilla_blocks::SCULK_SENSOR {
945            Self::set_empty_block_entity(
946                region,
947                pos,
948                &vanilla_block_entity_types::SCULK_SENSOR,
949                state,
950            );
951        } else if state.get_block() == &vanilla_blocks::SCULK_SHRIEKER {
952            Self::set_empty_block_entity(
953                region,
954                pos,
955                &vanilla_block_entity_types::SCULK_SHRIEKER,
956                state,
957            );
958        }
959    }
960
961    fn sculk_decay_penalty(
962        spreader: &SculkSpreader,
963        pos: BlockPos,
964        origin: BlockPos,
965        charge: i32,
966    ) -> i32 {
967        let no_growth_radius = spreader.no_growth_radius as f32;
968        let dx = (pos.x() - origin.x()) as f32;
969        let dy = (pos.y() - origin.y()) as f32;
970        let dz = (pos.z() - origin.z()) as f32;
971        let distance = (dx * dx + dy * dy + dz * dz).sqrt();
972        let outer_distance_squared = (distance - no_growth_radius) * (distance - no_growth_radius);
973        let max_reach = (24 - spreader.no_growth_radius) as f32;
974        let max_reach_squared = max_reach * max_reach;
975        let distance_factor = (outer_distance_squared / max_reach_squared).min(1.0);
976        1.max((charge as f32 * distance_factor * 0.5) as i32)
977    }
978
979    fn sculk_closer_than(pos: BlockPos, origin: BlockPos, radius: i32) -> bool {
980        let radius_squared = i64::from(radius) * i64::from(radius);
981        Self::sculk_distance_squared(pos, origin) < radius_squared
982    }
983
984    fn sculk_horizontal_close_to_origin(pos: BlockPos, origin: BlockPos, radius: f64) -> bool {
985        let dx = f64::from(pos.x() - origin.x());
986        let dz = f64::from(pos.z() - origin.z());
987        dx * dx + dz * dz < radius * radius
988    }
989
990    fn sculk_distance_squared(left: BlockPos, right: BlockPos) -> i64 {
991        let dx = i64::from(left.x()) - i64::from(right.x());
992        let dy = i64::from(left.y()) - i64::from(right.y());
993        let dz = i64::from(left.z()) - i64::from(right.z());
994        dx * dx + dy * dy + dz * dz
995    }
996
997    fn sculk_cursor_is_pos_unreasonable(pos: BlockPos, origin: BlockPos) -> bool {
998        Self::sculk_abs_diff(pos.x(), origin.x())
999            .max(Self::sculk_abs_diff(pos.y(), origin.y()))
1000            .max(Self::sculk_abs_diff(pos.z(), origin.z()))
1001            > SculkChargeCursor::MAX_CURSOR_DISTANCE
1002    }
1003
1004    const fn sculk_abs_diff(left: i32, right: i32) -> i32 {
1005        if left >= right {
1006            left - right
1007        } else {
1008            right - left
1009        }
1010    }
1011
1012    fn sculk_update_decay_delay(behavior: SculkBehaviorKind, age: i32) -> i32 {
1013        match behavior {
1014            SculkBehaviorKind::Default => (age - 1).max(0),
1015            SculkBehaviorKind::Sculk | SculkBehaviorKind::SculkVein => 1,
1016        }
1017    }
1018
1019    const fn sculk_spread_delay(_behavior: SculkBehaviorKind) -> i32 {
1020        1
1021    }
1022
1023    const fn sculk_can_change_block_state_on_spread(behavior: SculkBehaviorKind) -> bool {
1024        !matches!(behavior, SculkBehaviorKind::Sculk)
1025    }
1026
1027    fn sculk_behavior(state: BlockStateId) -> SculkBehaviorKind {
1028        if state.get_block() == &vanilla_blocks::SCULK {
1029            SculkBehaviorKind::Sculk
1030        } else if state.get_block() == &vanilla_blocks::SCULK_VEIN {
1031            SculkBehaviorKind::SculkVein
1032        } else {
1033            SculkBehaviorKind::Default
1034        }
1035    }
1036
1037    fn sculk_available_faces(state: BlockStateId) -> Vec<Direction> {
1038        let mut faces = Vec::new();
1039        if state.get_block() != &vanilla_blocks::SCULK_VEIN {
1040            return faces;
1041        }
1042
1043        for direction in Self::VANILLA_DIRECTION_VALUES {
1044            if Self::sculk_vein_has_face(state, direction) {
1045                faces.push(direction);
1046            }
1047        }
1048        faces
1049    }
1050
1051    fn sculk_vein_can_spread_from(state: BlockStateId, face: Direction) -> bool {
1052        Self::sculk_vein_is_other_block_valid_as_source(state)
1053            || Self::sculk_vein_has_face(state, face)
1054    }
1055
1056    fn sculk_vein_is_other_block_valid_as_source(state: BlockStateId) -> bool {
1057        state.get_block() != &vanilla_blocks::SCULK_VEIN
1058    }
1059
1060    fn sculk_vein_spread_pos(
1061        pos: BlockPos,
1062        spread_direction: Direction,
1063        from_face: Direction,
1064        spread_type: SculkSpreadType,
1065    ) -> SculkSpreadPos {
1066        match spread_type {
1067            SculkSpreadType::SamePosition => SculkSpreadPos {
1068                pos,
1069                face: spread_direction,
1070            },
1071            SculkSpreadType::SamePlane => SculkSpreadPos {
1072                pos: pos.relative(spread_direction),
1073                face: from_face,
1074            },
1075            SculkSpreadType::WrapAround => SculkSpreadPos {
1076                pos: pos.relative(spread_direction).relative(from_face),
1077                face: spread_direction.opposite(),
1078            },
1079        }
1080    }
1081
1082    fn sculk_vein_has_any_face(state: BlockStateId) -> bool {
1083        Self::VANILLA_DIRECTION_VALUES
1084            .iter()
1085            .any(|&direction| Self::sculk_vein_has_face(state, direction))
1086    }
1087
1088    fn sculk_vein_has_face(state: BlockStateId, direction: Direction) -> bool {
1089        state
1090            .try_get_value(multiface_face_property(direction))
1091            .unwrap_or(false)
1092    }
1093}