steel_core/entity/ai/goal/
move_towards_target.rs1use 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}