Skip to main content

steel_core/entity/ai/goal/
random_swimming.rs

1use glam::DVec3;
2use steel_utils::BlockPos;
3
4use super::random_pos::default_random_pos;
5use super::random_stroll::RandomStrollGoal;
6use super::selector::{Goal, GoalControls};
7use crate::behavior::BlockStateBehaviorExt as _;
8use crate::entity::PathfinderMob;
9use crate::entity::ai::path::PathComputationType;
10
11const RANDOM_SWIMMING_HORIZONTAL_RANGE: i32 = 10;
12const RANDOM_SWIMMING_VERTICAL_RANGE: i32 = 7;
13const RANDOM_SWIMMING_RETRIES: i32 = 10;
14
15pub struct RandomSwimmingGoal {
16    stroll: RandomStrollGoal,
17}
18
19impl RandomSwimmingGoal {
20    #[must_use]
21    pub(crate) const fn new(speed_modifier: f64, interval: i32) -> Self {
22        Self {
23            stroll: RandomStrollGoal::with_interval(speed_modifier, interval),
24        }
25    }
26}
27
28impl Goal for RandomSwimmingGoal {
29    fn controls(&self) -> GoalControls {
30        self.stroll.controls()
31    }
32
33    fn can_use(&mut self, mob: &dyn PathfinderMob) -> bool {
34        self.stroll.can_use_with_position(mob, random_swimmable_pos)
35    }
36
37    fn can_continue_to_use(&mut self, mob: &dyn PathfinderMob) -> bool {
38        self.stroll.can_continue_to_use(mob)
39    }
40
41    fn start(&mut self, mob: &dyn PathfinderMob) {
42        self.stroll.start(mob);
43    }
44
45    fn stop(&mut self, mob: &dyn PathfinderMob) {
46        self.stroll.stop(mob);
47    }
48}
49
50fn random_swimmable_pos(mob: &dyn PathfinderMob) -> Option<DVec3> {
51    retry_random_swimmable_pos(
52        default_random_pos(
53            mob,
54            RANDOM_SWIMMING_HORIZONTAL_RANGE,
55            RANDOM_SWIMMING_VERTICAL_RANGE,
56        ),
57        || {
58            default_random_pos(
59                mob,
60                RANDOM_SWIMMING_HORIZONTAL_RANGE,
61                RANDOM_SWIMMING_VERTICAL_RANGE,
62            )
63        },
64        |pos| is_water_pathfindable(mob, pos),
65    )
66}
67
68fn retry_random_swimmable_pos(
69    mut target_pos: Option<DVec3>,
70    mut next_pos: impl FnMut() -> Option<DVec3>,
71    mut is_water_pathfindable: impl FnMut(DVec3) -> bool,
72) -> Option<DVec3> {
73    let mut count = 0;
74    loop {
75        let pos = target_pos?;
76        if is_water_pathfindable(pos) || count >= RANDOM_SWIMMING_RETRIES {
77            return Some(pos);
78        }
79
80        count += 1;
81        target_pos = next_pos();
82    }
83}
84
85fn is_water_pathfindable(mob: &dyn PathfinderMob, pos: DVec3) -> bool {
86    let Some(world) = mob.level() else {
87        return false;
88    };
89    world
90        .get_block_state(BlockPos::containing(pos.x, pos.y, pos.z))
91        .is_pathfindable(PathComputationType::Water)
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn random_swimming_goal_uses_random_stroll_controls() {
100        let goal = RandomSwimmingGoal::new(1.0, 120);
101
102        assert_eq!(goal.controls(), GoalControls::MOVE);
103    }
104
105    #[test]
106    fn random_swimmable_retry_accepts_first_pathfindable_pos() {
107        let result = retry_random_swimmable_pos(Some(DVec3::new(1.0, 2.0, 3.0)), || None, |_| true);
108
109        assert_eq!(result, Some(DVec3::new(1.0, 2.0, 3.0)));
110    }
111
112    #[test]
113    fn random_swimmable_retry_uses_next_candidate_until_pathfindable() {
114        let mut next_x = 1.0;
115        let result = retry_random_swimmable_pos(
116            Some(DVec3::ZERO),
117            || {
118                let pos = DVec3::new(next_x, 0.0, 0.0);
119                next_x += 1.0;
120                Some(pos)
121            },
122            |pos| pos.x.to_bits() == 3.0_f64.to_bits(),
123        );
124
125        assert_eq!(result, Some(DVec3::new(3.0, 0.0, 0.0)));
126    }
127
128    #[test]
129    fn random_swimmable_retry_returns_last_candidate_after_vanilla_retry_limit() {
130        let mut next_x = 1.0;
131        let result = retry_random_swimmable_pos(
132            Some(DVec3::ZERO),
133            || {
134                let pos = DVec3::new(next_x, 0.0, 0.0);
135                next_x += 1.0;
136                Some(pos)
137            },
138            |_| false,
139        );
140
141        assert_eq!(result, Some(DVec3::new(10.0, 0.0, 0.0)));
142    }
143}