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