Skip to main content

steel_core/entity/ai/goal/
random_look_around.rs

1use std::f64::consts::TAU;
2
3use glam::DVec3;
4
5use crate::entity::PathfinderMob;
6use crate::entity::ai::control::{DEFAULT_LOOK_X_MAX_ROT_ANGLE, DEFAULT_LOOK_Y_MAX_ROT_SPEED};
7use crate::entity::ai::goal::selector::{Goal, GoalControls};
8
9const RANDOM_LOOK_AROUND_CHANCE: f32 = 0.02;
10
11pub struct RandomLookAroundGoal {
12    rel_x: f64,
13    rel_z: f64,
14    look_time: i32,
15}
16
17impl RandomLookAroundGoal {
18    #[must_use]
19    pub const fn new() -> Self {
20        Self {
21            rel_x: 0.0,
22            rel_z: 0.0,
23            look_time: 0,
24        }
25    }
26}
27
28impl Default for RandomLookAroundGoal {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl Goal for RandomLookAroundGoal {
35    fn controls(&self) -> GoalControls {
36        GoalControls::MOVE | GoalControls::LOOK
37    }
38
39    fn can_use(&mut self, _mob: &dyn PathfinderMob) -> bool {
40        rand::random::<f32>() < RANDOM_LOOK_AROUND_CHANCE
41    }
42
43    fn can_continue_to_use(&mut self, _mob: &dyn PathfinderMob) -> bool {
44        self.look_time >= 0
45    }
46
47    fn start(&mut self, _mob: &dyn PathfinderMob) {
48        let direction = TAU * rand::random::<f64>();
49        self.rel_x = direction.cos();
50        self.rel_z = direction.sin();
51        self.look_time = 20 + rand::random_range(0..20);
52    }
53
54    fn requires_update_every_tick(&self) -> bool {
55        true
56    }
57
58    fn tick(&mut self, mob: &dyn PathfinderMob) {
59        self.look_time -= 1;
60        let position = mob.position();
61        mob.mob_base().controls().lock().look_control.set_look_at(
62            DVec3::new(
63                position.x + self.rel_x,
64                mob.get_eye_y(),
65                position.z + self.rel_z,
66            ),
67            DEFAULT_LOOK_Y_MAX_ROT_SPEED,
68            DEFAULT_LOOK_X_MAX_ROT_ANGLE,
69        );
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use std::sync::Weak;
76
77    use steel_registry::entity_type::EntityTypeRef;
78    use steel_registry::{init_vanilla_registry, vanilla_entities};
79    use steel_utils::locks::SyncMutex;
80
81    use super::*;
82    use crate::entity::{Entity, EntityBase, LivingEntity, LivingEntityBase, Mob, MobBase};
83
84    struct TestPathfinderMob {
85        base: EntityBase,
86        living_base: LivingEntityBase,
87        mob_base: MobBase,
88        mob_flags: SyncMutex<i8>,
89        health: SyncMutex<f32>,
90    }
91
92    impl TestPathfinderMob {
93        fn new() -> Self {
94            init_vanilla_registry();
95            Self {
96                base: EntityBase::new(
97                    1,
98                    DVec3::ZERO,
99                    vanilla_entities::PIG.dimensions,
100                    Weak::new(),
101                ),
102                living_base: LivingEntityBase::new(&vanilla_entities::PIG),
103                mob_base: MobBase::new(),
104                mob_flags: SyncMutex::new(0),
105                health: SyncMutex::new(10.0),
106            }
107        }
108    }
109
110    crate::entity::impl_test_downcast_type!(TestPathfinderMob);
111
112    impl Entity for TestPathfinderMob {
113        fn base(&self) -> &EntityBase {
114            &self.base
115        }
116
117        fn entity_type(&self) -> EntityTypeRef {
118            &vanilla_entities::PIG
119        }
120    }
121
122    impl LivingEntity for TestPathfinderMob {
123        fn living_base(&self) -> &LivingEntityBase {
124            &self.living_base
125        }
126
127        fn get_health(&self) -> f32 {
128            *self.health.lock()
129        }
130
131        fn set_health(&self, health: f32) {
132            *self.health.lock() = health;
133        }
134    }
135
136    impl Mob for TestPathfinderMob {
137        fn mob_base(&self) -> &MobBase {
138            &self.mob_base
139        }
140
141        fn mob_flags(&self) -> i8 {
142            *self.mob_flags.lock()
143        }
144
145        fn set_mob_flags(&self, flags: i8) {
146            *self.mob_flags.lock() = flags;
147        }
148    }
149
150    impl PathfinderMob for TestPathfinderMob {}
151
152    #[test]
153    fn random_look_around_sets_look_control_to_eye_height() {
154        let mob = TestPathfinderMob::new();
155        let mut goal = RandomLookAroundGoal::new();
156
157        goal.start(&mob);
158        goal.tick(&mob);
159
160        let look_control = mob.mob_base().controls().lock().look_control;
161        assert!(look_control.is_looking_at_target());
162        assert_eq!(
163            look_control.wanted_position().y.to_bits(),
164            mob.get_eye_y().to_bits()
165        );
166    }
167}