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