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