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