Skip to main content

steel_core/entity/ai/goal/
move_towards_target.rs

1use std::f64::consts::FRAC_PI_2;
2
3use glam::DVec3;
4
5use super::random_pos::default_random_pos_towards;
6use super::selector::{Goal, GoalControls};
7use crate::entity::{PathfinderMob, SharedEntity};
8
9pub struct MoveTowardsTargetGoal {
10    target: Option<SharedEntity>,
11    wanted_position: Option<DVec3>,
12    speed_modifier: f64,
13    within: f32,
14}
15
16impl MoveTowardsTargetGoal {
17    #[must_use]
18    pub(crate) const fn new(speed_modifier: f64, within: f32) -> Self {
19        Self {
20            target: None,
21            wanted_position: None,
22            speed_modifier,
23            within,
24        }
25    }
26
27    fn within_distance_sqr(&self) -> f64 {
28        f64::from(self.within * self.within)
29    }
30}
31
32impl Goal for MoveTowardsTargetGoal {
33    fn controls(&self) -> GoalControls {
34        GoalControls::MOVE
35    }
36
37    fn can_use(&mut self, mob: &dyn PathfinderMob) -> bool {
38        let Some(target) = mob.target() else {
39            return false;
40        };
41
42        if target.position().distance_squared(mob.position()) > self.within_distance_sqr() {
43            return false;
44        }
45
46        let Some(position) = default_random_pos_towards(mob, 16, 7, target.position(), FRAC_PI_2)
47        else {
48            return false;
49        };
50
51        self.target = Some(target);
52        self.wanted_position = Some(position);
53        true
54    }
55
56    fn can_continue_to_use(&mut self, mob: &dyn PathfinderMob) -> bool {
57        let Some(target) = &self.target else {
58            return false;
59        };
60
61        !mob.mob_base().navigation().lock().is_done()
62            && target.is_alive()
63            && target.position().distance_squared(mob.position()) < self.within_distance_sqr()
64    }
65
66    fn stop(&mut self, _mob: &dyn PathfinderMob) {
67        self.target = None;
68    }
69
70    fn start(&mut self, mob: &dyn PathfinderMob) {
71        if let Some(wanted_position) = self.wanted_position {
72            mob.move_to_pos(wanted_position, self.speed_modifier);
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use std::sync::{Arc, Weak};
80
81    use steel_registry::{init_vanilla_registry, vanilla_entities};
82
83    use super::*;
84    use crate::entity::{Mob, entities::PigEntity};
85
86    #[test]
87    fn move_towards_target_goal_uses_move_control() {
88        let goal = MoveTowardsTargetGoal::new(1.0, 16.0);
89
90        assert_eq!(goal.controls(), GoalControls::MOVE);
91    }
92
93    #[test]
94    fn move_towards_target_goal_requires_target() {
95        init_vanilla_registry();
96        let mut goal = MoveTowardsTargetGoal::new(1.0, 16.0);
97        let mob = PigEntity::new(&vanilla_entities::PIG, 1, DVec3::ZERO, Weak::new());
98
99        assert!(!goal.can_use(&mob));
100    }
101
102    #[test]
103    fn move_towards_target_goal_rejects_target_outside_range() {
104        init_vanilla_registry();
105        let mut goal = MoveTowardsTargetGoal::new(1.0, 8.0);
106        let mob = PigEntity::new(&vanilla_entities::PIG, 1, DVec3::ZERO, Weak::new());
107        let target: SharedEntity = Arc::new(PigEntity::new(
108            &vanilla_entities::PIG,
109            2,
110            DVec3::new(9.0, 0.0, 0.0),
111            Weak::new(),
112        ));
113        assert!(mob.set_target(Some(&target)));
114
115        assert!(!goal.can_use(&mob));
116    }
117}