1use 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}