Skip to main content

steel_core/entity/ai/
control.rs

1//! Mob control state.
2
3use glam::DVec3;
4
5pub(crate) const DEFAULT_LOOK_Y_MAX_ROT_SPEED: f32 = 10.0;
6pub(crate) const DEFAULT_LOOK_X_MAX_ROT_ANGLE: f32 = 40.0;
7const HEAD_STABLE_ANGLE: f32 = 15.0;
8const DELAY_UNTIL_STARTING_TO_FACE_FORWARD: i32 = 10;
9const HOW_LONG_IT_TAKES_TO_FACE_FORWARD: f32 = 10.0;
10
11fn wrap_degrees(mut degrees: f32) -> f32 {
12    degrees %= 360.0;
13    if degrees >= 180.0 {
14        degrees -= 360.0;
15    }
16    if degrees < -180.0 {
17        degrees += 360.0;
18    }
19    degrees
20}
21
22pub(crate) fn rotate_towards(from_angle: f32, to_angle: f32, max_rot: f32) -> f32 {
23    let diff = wrap_degrees(to_angle - from_angle);
24    let diff_clamped = diff.clamp(-max_rot, max_rot);
25    from_angle + diff_clamped
26}
27
28pub(crate) fn rotate_if_necessary(base_angle: f32, target_angle: f32, max_angle_diff: f32) -> f32 {
29    let delta_angle = wrap_degrees(target_angle - base_angle);
30    let delta_angle_clamped = delta_angle.clamp(-max_angle_diff, max_angle_diff);
31    target_angle - delta_angle_clamped
32}
33
34#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum MoveControlOperation {
36    Wait,
37    MoveTo,
38    Strafe,
39    Jumping,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub struct MoveControl {
44    wanted_position: DVec3,
45    speed_modifier: f64,
46    strafe_forward: f32,
47    strafe_right: f32,
48    operation: MoveControlOperation,
49}
50
51impl MoveControl {
52    #[must_use]
53    pub const fn new() -> Self {
54        Self {
55            wanted_position: DVec3::ZERO,
56            speed_modifier: 0.0,
57            strafe_forward: 0.0,
58            strafe_right: 0.0,
59            operation: MoveControlOperation::Wait,
60        }
61    }
62
63    #[must_use]
64    pub const fn operation(&self) -> MoveControlOperation {
65        self.operation
66    }
67
68    #[must_use]
69    pub const fn wanted_position(&self) -> DVec3 {
70        self.wanted_position
71    }
72
73    #[must_use]
74    pub const fn speed_modifier(&self) -> f64 {
75        self.speed_modifier
76    }
77
78    #[must_use]
79    pub const fn strafe_forward(&self) -> f32 {
80        self.strafe_forward
81    }
82
83    #[must_use]
84    pub const fn strafe_right(&self) -> f32 {
85        self.strafe_right
86    }
87
88    pub fn set_wanted_position(&mut self, position: DVec3, speed_modifier: f64) {
89        self.wanted_position = position;
90        self.speed_modifier = speed_modifier;
91        if self.operation != MoveControlOperation::Jumping {
92            self.operation = MoveControlOperation::MoveTo;
93        }
94    }
95
96    pub const fn strafe(&mut self, forward: f32, right: f32) {
97        self.operation = MoveControlOperation::Strafe;
98        self.strafe_forward = forward;
99        self.strafe_right = right;
100        self.speed_modifier = 0.25;
101    }
102
103    pub const fn set_wait(&mut self) {
104        self.operation = MoveControlOperation::Wait;
105    }
106
107    pub const fn set_jumping(&mut self) {
108        self.operation = MoveControlOperation::Jumping;
109    }
110}
111
112impl Default for MoveControl {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub struct JumpControl {
120    jump: bool,
121}
122
123impl JumpControl {
124    #[must_use]
125    pub const fn new() -> Self {
126        Self { jump: false }
127    }
128
129    pub const fn jump(&mut self) {
130        self.jump = true;
131    }
132
133    pub const fn tick(&mut self) -> bool {
134        let jump = self.jump;
135        self.jump = false;
136        jump
137    }
138}
139
140impl Default for JumpControl {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq)]
147pub struct LookControl {
148    wanted_position: DVec3,
149    y_max_rot_speed: f32,
150    x_max_rot_angle: f32,
151    look_at_cooldown: i32,
152}
153
154impl LookControl {
155    #[must_use]
156    pub const fn new() -> Self {
157        Self {
158            wanted_position: DVec3::ZERO,
159            y_max_rot_speed: DEFAULT_LOOK_Y_MAX_ROT_SPEED,
160            x_max_rot_angle: DEFAULT_LOOK_X_MAX_ROT_ANGLE,
161            look_at_cooldown: 0,
162        }
163    }
164
165    #[must_use]
166    pub const fn wanted_position(&self) -> DVec3 {
167        self.wanted_position
168    }
169
170    #[must_use]
171    pub const fn y_max_rot_speed(&self) -> f32 {
172        self.y_max_rot_speed
173    }
174
175    #[must_use]
176    pub const fn x_max_rot_angle(&self) -> f32 {
177        self.x_max_rot_angle
178    }
179
180    #[must_use]
181    pub const fn is_looking_at_target(&self) -> bool {
182        self.look_at_cooldown > 0
183    }
184
185    pub const fn set_look_at(
186        &mut self,
187        position: DVec3,
188        y_max_rot_speed: f32,
189        x_max_rot_angle: f32,
190    ) {
191        self.wanted_position = position;
192        self.y_max_rot_speed = y_max_rot_speed;
193        self.x_max_rot_angle = x_max_rot_angle;
194        self.look_at_cooldown = 2;
195    }
196
197    pub const fn tick_cooldown(&mut self) -> bool {
198        if self.look_at_cooldown <= 0 {
199            return false;
200        }
201
202        self.look_at_cooldown -= 1;
203        true
204    }
205}
206
207impl Default for LookControl {
208    fn default() -> Self {
209        Self::new()
210    }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq)]
214pub struct BodyRotationInput {
215    moving: bool,
216    carrying_mob_passenger: bool,
217    y_rot: f32,
218    y_body_rot: f32,
219    y_head_rot: f32,
220    max_head_y_rot: f32,
221}
222
223impl BodyRotationInput {
224    #[must_use]
225    pub const fn new(
226        moving: bool,
227        carrying_mob_passenger: bool,
228        y_rot: f32,
229        y_body_rot: f32,
230        y_head_rot: f32,
231        max_head_y_rot: f32,
232    ) -> Self {
233        Self {
234            moving,
235            carrying_mob_passenger,
236            y_rot,
237            y_body_rot,
238            y_head_rot,
239            max_head_y_rot,
240        }
241    }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq)]
245pub struct BodyRotationUpdate {
246    y_body_rot: f32,
247    y_head_rot: f32,
248}
249
250impl BodyRotationUpdate {
251    #[must_use]
252    pub const fn y_body_rot(self) -> f32 {
253        self.y_body_rot
254    }
255
256    #[must_use]
257    pub const fn y_head_rot(self) -> f32 {
258        self.y_head_rot
259    }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq)]
263pub struct BodyRotationControl {
264    head_stable_time: i32,
265    last_stable_y_head_rot: f32,
266}
267
268impl BodyRotationControl {
269    #[must_use]
270    pub const fn new() -> Self {
271        Self {
272            head_stable_time: 0,
273            last_stable_y_head_rot: 0.0,
274        }
275    }
276
277    #[must_use]
278    pub const fn head_stable_time(self) -> i32 {
279        self.head_stable_time
280    }
281
282    #[must_use]
283    pub const fn last_stable_y_head_rot(self) -> f32 {
284        self.last_stable_y_head_rot
285    }
286
287    pub fn tick(&mut self, input: BodyRotationInput) -> BodyRotationUpdate {
288        let mut y_body_rot = input.y_body_rot;
289        let mut y_head_rot = input.y_head_rot;
290
291        if input.moving {
292            y_body_rot = input.y_rot;
293            y_head_rot = rotate_if_necessary(y_head_rot, y_body_rot, input.max_head_y_rot);
294            self.last_stable_y_head_rot = y_head_rot;
295            self.head_stable_time = 0;
296        } else if !input.carrying_mob_passenger {
297            if (y_head_rot - self.last_stable_y_head_rot).abs() > HEAD_STABLE_ANGLE {
298                self.head_stable_time = 0;
299                self.last_stable_y_head_rot = y_head_rot;
300                y_body_rot = rotate_if_necessary(y_body_rot, y_head_rot, input.max_head_y_rot);
301            } else {
302                self.head_stable_time += 1;
303                if self.head_stable_time > DELAY_UNTIL_STARTING_TO_FACE_FORWARD {
304                    let time_since_starting_to_face_forward =
305                        self.head_stable_time - DELAY_UNTIL_STARTING_TO_FACE_FORWARD;
306                    let face_forward_fraction = (time_since_starting_to_face_forward as f32
307                        / HOW_LONG_IT_TAKES_TO_FACE_FORWARD)
308                        .clamp(0.0, 1.0);
309                    let angle_remaining_until_facing_forward =
310                        input.max_head_y_rot * (1.0 - face_forward_fraction);
311                    y_body_rot = rotate_if_necessary(
312                        y_body_rot,
313                        y_head_rot,
314                        angle_remaining_until_facing_forward,
315                    );
316                }
317            }
318        }
319
320        BodyRotationUpdate {
321            y_body_rot,
322            y_head_rot,
323        }
324    }
325}
326
327impl Default for BodyRotationControl {
328    fn default() -> Self {
329        Self::new()
330    }
331}
332
333#[derive(Debug, Default, Clone, Copy, PartialEq)]
334pub struct MobControls {
335    pub move_control: MoveControl,
336    pub jump_control: JumpControl,
337    pub look_control: LookControl,
338    pub body_rotation_control: BodyRotationControl,
339}
340
341impl MobControls {
342    #[must_use]
343    pub const fn new() -> Self {
344        Self {
345            move_control: MoveControl::new(),
346            jump_control: JumpControl::new(),
347            look_control: LookControl::new(),
348            body_rotation_control: BodyRotationControl::new(),
349        }
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::{BodyRotationControl, BodyRotationInput};
356
357    fn assert_f32_close(left: f32, right: f32) {
358        assert!(
359            (left - right).abs() < 1.0e-6,
360            "expected {left:?} to equal {right:?}"
361        );
362    }
363
364    #[test]
365    fn body_rotation_control_faces_body_forward_while_moving() {
366        let mut control = BodyRotationControl::new();
367
368        let update = control.tick(BodyRotationInput::new(true, false, 90.0, 0.0, 200.0, 75.0));
369
370        assert_f32_close(update.y_body_rot(), 90.0);
371        assert_f32_close(update.y_head_rot(), 165.0);
372        assert_eq!(control.head_stable_time(), 0);
373        assert_f32_close(control.last_stable_y_head_rot(), 165.0);
374    }
375
376    #[test]
377    fn body_rotation_control_turns_body_when_idle_head_moves() {
378        let mut control = BodyRotationControl::new();
379
380        let update = control.tick(BodyRotationInput::new(false, false, 0.0, 0.0, 90.0, 75.0));
381
382        assert_f32_close(update.y_body_rot(), 15.0);
383        assert_f32_close(update.y_head_rot(), 90.0);
384        assert_eq!(control.head_stable_time(), 0);
385        assert_f32_close(control.last_stable_y_head_rot(), 90.0);
386    }
387
388    #[test]
389    fn body_rotation_control_waits_then_turns_body_toward_stable_head() {
390        let mut control = BodyRotationControl::new();
391        let first = control.tick(BodyRotationInput::new(false, false, 0.0, 0.0, 90.0, 75.0));
392        let mut y_body_rot = first.y_body_rot();
393
394        for _ in 0..10 {
395            y_body_rot = control
396                .tick(BodyRotationInput::new(
397                    false, false, 0.0, y_body_rot, 90.0, 75.0,
398                ))
399                .y_body_rot();
400        }
401
402        assert_eq!(control.head_stable_time(), 10);
403        assert_f32_close(y_body_rot, 15.0);
404
405        let update = control.tick(BodyRotationInput::new(
406            false, false, 0.0, y_body_rot, 90.0, 75.0,
407        ));
408
409        assert_eq!(control.head_stable_time(), 11);
410        assert_f32_close(update.y_body_rot(), 22.5);
411    }
412
413    #[test]
414    fn body_rotation_control_does_not_turn_idle_body_when_carrying_mob_passenger() {
415        let mut control = BodyRotationControl::new();
416
417        let update = control.tick(BodyRotationInput::new(false, true, 0.0, 0.0, 90.0, 75.0));
418
419        assert_f32_close(update.y_body_rot(), 0.0);
420        assert_f32_close(update.y_head_rot(), 90.0);
421        assert_eq!(control.head_stable_time(), 0);
422        assert_f32_close(control.last_stable_y_head_rot(), 0.0);
423    }
424}