steel_core/player/movement/
state.rs1use glam::DVec3;
4
5use crate::physics::ClientAuthoredMovementState;
6use crate::player::PlayerInput;
7
8pub struct MovementState {
10 client_movement: ClientAuthoredMovementState,
12 client_vehicle_movement: ClientAuthoredMovementState,
14 received_movement_this_tick: bool,
16 received_position_this_tick: bool,
18 client_vehicle_id: Option<i32>,
20 pub(in crate::player) vehicle_position_last_reset_at: i32,
22 last_client_input: PlayerInput,
24}
25
26impl MovementState {
27 #[must_use]
28 pub const fn new() -> Self {
29 Self {
30 client_movement: ClientAuthoredMovementState::new(),
31 client_vehicle_movement: ClientAuthoredMovementState::new(),
32 received_movement_this_tick: false,
33 received_position_this_tick: false,
34 client_vehicle_id: None,
35 vehicle_position_last_reset_at: 0,
36 last_client_input: PlayerInput::EMPTY,
37 }
38 }
39
40 pub(in crate::player) const fn set_last_client_input(&mut self, input: PlayerInput) {
42 self.last_client_input = input;
43 }
44
45 #[must_use]
47 pub(in crate::player) const fn last_client_input(&self) -> PlayerInput {
48 self.last_client_input
49 }
50
51 pub(in crate::player) const fn reset_for_tick(&mut self, position: DVec3) {
53 self.client_movement.reset_for_tick(position);
54 }
55
56 pub(in crate::player) const fn reset_vehicle_for_tick(
58 &mut self,
59 vehicle_id: i32,
60 position: DVec3,
61 ) {
62 self.client_vehicle_id = Some(vehicle_id);
63 self.client_vehicle_movement.reset_for_tick(position);
64 }
65
66 pub(in crate::player) const fn clear_vehicle_for_tick(&mut self) {
68 self.client_vehicle_id = None;
69 self.client_vehicle_movement.clear_client_floating();
70 }
71
72 #[must_use]
74 pub(in crate::player) const fn vehicle_good_positions(
75 &self,
76 vehicle_id: i32,
77 ) -> Option<(DVec3, DVec3)> {
78 if !matches!(self.client_vehicle_id, Some(active_id) if active_id == vehicle_id) {
79 return None;
80 }
81
82 Some(self.client_vehicle_movement.good_positions())
83 }
84
85 pub(in crate::player) const fn reset_for_position_sync(&mut self, position: DVec3) {
87 self.client_movement.reset_for_position_sync(position);
88 self.received_movement_this_tick = false;
89 }
90
91 #[must_use]
93 pub(in crate::player) const fn good_positions(&self) -> (DVec3, DVec3) {
94 self.client_movement.good_positions()
95 }
96
97 pub(in crate::player) const fn record_move_packet_delta(&mut self) -> i32 {
99 self.client_movement.record_move_packet_delta()
100 }
101
102 pub(in crate::player) const fn record_position_packet(&mut self) -> bool {
104 if self.received_position_this_tick {
105 return false;
106 }
107 self.received_position_this_tick = true;
108 true
109 }
110
111 pub(in crate::player) const fn mark_last_good_position(&mut self, position: DVec3) {
113 self.client_movement.mark_last_good_position(position);
114 }
115
116 pub(in crate::player) const fn mark_vehicle_last_good_position(
118 &mut self,
119 vehicle_id: i32,
120 position: DVec3,
121 ) {
122 if matches!(self.client_vehicle_id, Some(active_id) if active_id == vehicle_id) {
123 self.client_vehicle_movement
124 .mark_last_good_position(position);
125 }
126 }
127
128 pub(in crate::player) const fn set_last_known_client_movement(&mut self, movement: DVec3) {
130 self.client_movement
131 .set_last_known_client_movement(movement);
132 self.received_movement_this_tick = true;
133 }
134
135 pub(in crate::player) const fn reset_last_known_client_movement(&mut self) {
137 self.client_movement.reset_last_known_client_movement();
138 self.received_movement_this_tick = false;
139 }
140
141 pub(in crate::player) const fn finish_client_tick(&mut self) {
143 if !self.received_movement_this_tick {
144 self.client_movement.reset_last_known_client_movement();
145 }
146 self.received_movement_this_tick = false;
147 self.received_position_this_tick = false;
148 }
149
150 #[must_use]
152 pub(in crate::player) const fn last_known_client_movement(&self) -> DVec3 {
153 self.client_movement.last_known_client_movement()
154 }
155
156 pub(in crate::player) const fn record_client_floating(&mut self, client_is_floating: bool) {
158 self.client_movement
159 .record_client_floating(client_is_floating);
160 }
161
162 pub(in crate::player) const fn record_vehicle_client_floating(
164 &mut self,
165 vehicle_id: i32,
166 client_is_floating: bool,
167 ) {
168 if matches!(self.client_vehicle_id, Some(active_id) if active_id == vehicle_id) {
169 self.client_vehicle_movement
170 .record_client_floating(client_is_floating);
171 }
172 }
173
174 pub(in crate::player) const fn reset_flying_ticks(&mut self) {
176 self.client_movement.reset_flying_ticks();
177 self.client_vehicle_movement.reset_flying_ticks();
178 }
179
180 pub(in crate::player) const fn tick_client_floating(
184 &mut self,
185 should_count: bool,
186 maximum_flying_ticks: i32,
187 ) -> bool {
188 self.client_movement
189 .tick_client_floating(should_count, maximum_flying_ticks)
190 }
191
192 pub(in crate::player) const fn tick_vehicle_client_floating(
196 &mut self,
197 vehicle_id: i32,
198 maximum_flying_ticks: i32,
199 ) -> bool {
200 if !matches!(self.client_vehicle_id, Some(active_id) if active_id == vehicle_id) {
201 self.client_vehicle_movement.clear_client_floating();
202 return false;
203 }
204
205 self.client_vehicle_movement
206 .tick_client_floating(true, maximum_flying_ticks)
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use glam::DVec3;
213
214 use crate::player::PlayerInput;
215
216 use super::MovementState;
217
218 #[test]
219 fn movement_state_starts_with_zero_known_client_movement() {
220 let state = MovementState::new();
221 assert_eq!(state.last_known_client_movement(), DVec3::ZERO);
222 }
223
224 #[test]
225 fn client_tick_end_clears_known_movement_without_accepted_movement() {
226 let mut state = MovementState::new();
227 state.set_last_known_client_movement(DVec3::new(0.1, 0.0, 0.0));
228 state.finish_client_tick();
229
230 state.finish_client_tick();
231
232 assert_eq!(state.last_known_client_movement(), DVec3::ZERO);
233 }
234
235 #[test]
236 fn client_tick_end_keeps_known_movement_after_accepted_movement() {
237 let mut state = MovementState::new();
238 let movement = DVec3::new(0.1, 0.0, 0.0);
239
240 state.set_last_known_client_movement(movement);
241 state.finish_client_tick();
242
243 assert_eq!(state.last_known_client_movement(), movement);
244 }
245
246 #[test]
247 fn movement_state_tracks_last_client_input() {
248 let mut state = MovementState::new();
249 let input = PlayerInput::from_flags(0x01 | 0x08 | 0x10 | 0x40);
250
251 assert_eq!(state.last_client_input(), PlayerInput::EMPTY);
252 state.set_last_client_input(input);
253
254 assert_eq!(state.last_client_input(), input);
255 assert_eq!(
256 state.last_client_input().movement_input(),
257 DVec3::new(-1.0, 0.0, 1.0)
258 );
259 }
260
261 #[test]
262 fn tick_reset_updates_both_good_positions_and_packet_count() {
263 let mut state = MovementState::new();
264 state.mark_last_good_position(DVec3::new(1.0, 2.0, 3.0));
265 state.record_move_packet_delta();
266 state.record_move_packet_delta();
267
268 state.reset_for_tick(DVec3::new(4.0, 5.0, 6.0));
269
270 assert_eq!(
271 state.good_positions(),
272 (DVec3::new(4.0, 5.0, 6.0), DVec3::new(4.0, 5.0, 6.0))
273 );
274 assert_eq!(state.record_move_packet_delta(), 1);
275 }
276
277 #[test]
278 fn position_sync_reset_clears_packet_counts_and_known_movement() {
279 let mut state = MovementState::new();
280 state.record_move_packet_delta();
281 state.set_last_known_client_movement(DVec3::new(0.1, 0.0, 0.0));
282
283 state.reset_for_position_sync(DVec3::new(2.0, 3.0, 4.0));
284
285 assert_eq!(state.good_positions().0, DVec3::new(2.0, 3.0, 4.0));
286 assert_eq!(state.good_positions().1, DVec3::new(2.0, 3.0, 4.0));
287 assert_eq!(state.last_known_client_movement(), DVec3::ZERO);
288 assert_eq!(state.record_move_packet_delta(), 1);
289 }
290
291 #[test]
292 fn floating_tracker_counts_only_while_client_is_floating() {
293 let mut state = MovementState::new();
294 state.record_client_floating(true);
295
296 assert!(!state.tick_client_floating(true, 2));
297 assert!(!state.tick_client_floating(true, 2));
298 assert!(state.tick_client_floating(true, 2));
299
300 state.record_client_floating(false);
301 assert!(!state.tick_client_floating(true, 2));
302
303 state.record_client_floating(true);
304 assert!(!state.tick_client_floating(true, 2));
305 }
306
307 #[test]
308 fn floating_tracker_resets_when_tick_conditions_do_not_count() {
309 let mut state = MovementState::new();
310 state.record_client_floating(true);
311
312 assert!(!state.tick_client_floating(true, 1));
313 assert!(!state.tick_client_floating(false, 1));
314
315 state.record_client_floating(true);
316 assert!(!state.tick_client_floating(true, 1));
317 }
318
319 #[test]
320 fn vehicle_tick_reset_tracks_active_vehicle_id() {
321 let mut state = MovementState::new();
322
323 state.reset_vehicle_for_tick(42, DVec3::new(1.0, 2.0, 3.0));
324
325 assert_eq!(
326 state.vehicle_good_positions(42),
327 Some((DVec3::new(1.0, 2.0, 3.0), DVec3::new(1.0, 2.0, 3.0)))
328 );
329 assert_eq!(state.vehicle_good_positions(41), None);
330 assert!(!state.tick_vehicle_client_floating(41, 0));
331 assert!(!state.tick_vehicle_client_floating(42, 0));
332 }
333
334 #[test]
335 fn vehicle_last_good_update_is_guarded_by_active_vehicle_id() {
336 let mut state = MovementState::new();
337 state.reset_vehicle_for_tick(42, DVec3::new(1.0, 2.0, 3.0));
338
339 state.mark_vehicle_last_good_position(41, DVec3::new(9.0, 9.0, 9.0));
340 assert_eq!(
341 state.vehicle_good_positions(42),
342 Some((DVec3::new(1.0, 2.0, 3.0), DVec3::new(1.0, 2.0, 3.0)))
343 );
344
345 state.mark_vehicle_last_good_position(42, DVec3::new(4.0, 5.0, 6.0));
346
347 assert_eq!(
348 state.vehicle_good_positions(42),
349 Some((DVec3::new(1.0, 2.0, 3.0), DVec3::new(4.0, 5.0, 6.0)))
350 );
351 }
352
353 #[test]
354 fn vehicle_floating_update_is_guarded_by_active_vehicle_id() {
355 let mut state = MovementState::new();
356 state.reset_vehicle_for_tick(42, DVec3::new(1.0, 2.0, 3.0));
357
358 state.record_vehicle_client_floating(41, true);
359 assert!(!state.tick_vehicle_client_floating(42, 0));
360
361 state.record_vehicle_client_floating(42, true);
362 assert!(state.tick_vehicle_client_floating(42, 0));
363 }
364
365 #[test]
366 fn vehicle_state_clears_when_no_controlled_vehicle_is_active() {
367 let mut state = MovementState::new();
368 state.reset_vehicle_for_tick(42, DVec3::new(1.0, 2.0, 3.0));
369
370 state.clear_vehicle_for_tick();
371
372 assert!(!state.tick_vehicle_client_floating(42, 0));
373 }
374
375 #[test]
376 fn reset_flying_ticks_applies_to_vehicle_counter() {
377 let mut state = MovementState::new();
378 state.reset_vehicle_for_tick(42, DVec3::new(1.0, 2.0, 3.0));
379 state.client_vehicle_movement.record_client_floating(true);
380
381 assert!(!state.tick_vehicle_client_floating(42, 1));
382 state.reset_flying_ticks();
383 assert!(!state.tick_vehicle_client_floating(42, 1));
384 assert!(state.tick_vehicle_client_floating(42, 1));
385 }
386}