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::ai::control::{DEFAULT_LOOK_X_MAX_ROT_ANGLE, DEFAULT_LOOK_Y_MAX_ROT_SPEED};
6use crate::entity::ai::goal::selector::{Goal, GoalControls};
7use crate::entity::{PathfinderMob, SharedEntity};
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 = rand::random_range(0.0..TAU);
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, _entity: &SharedEntity) {
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::Arc;
76
77    use std::sync::Weak;
78
79    use steel_registry::entity_type::EntityTypeRef;
80    use steel_registry::{init_vanilla_registry, vanilla_entities};
81    use steel_utils::locks::SyncMutex;
82
83    use super::*;
84    use crate::entity::{Entity, EntityBase, LivingEntity, LivingEntityBase, Mob, MobBase};
85
86    struct TestPathfinderMob {
87        base: EntityBase,
88        living_base: LivingEntityBase,
89        mob_base: MobBase,
90        mob_flags: SyncMutex<i8>,
91        health: SyncMutex<f32>,
92    }
93
94    impl TestPathfinderMob {
95        fn new() -> Self {
96            init_vanilla_registry();
97            Self {
98                base: EntityBase::new(
99                    1,
100                    DVec3::ZERO,
101                    vanilla_entities::PIG.dimensions,
102                    Weak::new(),
103                ),
104                living_base: LivingEntityBase::new(&vanilla_entities::PIG),
105                mob_base: MobBase::new(),
106                mob_flags: SyncMutex::new(0),
107                health: SyncMutex::new(10.0),
108            }
109        }
110    }
111
112    crate::entity::impl_test_downcast_type!(TestPathfinderMob);
113
114    impl Entity for TestPathfinderMob {
115        fn base(&self) -> &EntityBase {
116            &self.base
117        }
118
119        fn entity_type(&self) -> EntityTypeRef {
120            &vanilla_entities::PIG
121        }
122    }
123
124    impl LivingEntity for TestPathfinderMob {
125        fn living_base(&self) -> &LivingEntityBase {
126            &self.living_base
127        }
128
129        fn get_health(&self) -> f32 {
130            *self.health.lock()
131        }
132
133        fn set_health(&self, health: f32) {
134            *self.health.lock() = health;
135        }
136    }
137
138    impl Mob for TestPathfinderMob {
139        fn mob_base(&self) -> &MobBase {
140            &self.mob_base
141        }
142
143        fn mob_flags(&self) -> i8 {
144            *self.mob_flags.lock()
145        }
146
147        fn set_mob_flags(&self, flags: i8) {
148            *self.mob_flags.lock() = flags;
149        }
150    }
151
152    impl PathfinderMob for TestPathfinderMob {}
153
154    #[test]
155    fn random_look_around_sets_look_control_to_eye_height() {
156        let mob = Arc::new(TestPathfinderMob::new());
157        let mob_entity: SharedEntity = Arc::<TestPathfinderMob>::clone(&mob);
158        let mut goal = RandomLookAroundGoal::new();
159
160        goal.start(mob.as_ref());
161        goal.tick(mob.as_ref(), &mob_entity);
162
163        let look_control = mob.mob_base().controls().lock().look_control;
164        assert!(look_control.is_looking_at_target());
165        assert_eq!(
166            look_control.wanted_position().y.to_bits(),
167            mob.get_eye_y().to_bits()
168        );
169    }
170}