Skip to main content

steel_core/entity/ai/
control.rs

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