Skip to main content

steel_core/player/movement/
input.rs

1//! Last client input state sent by the player.
2
3use glam::DVec3;
4
5const FLAG_FORWARD: u8 = 0x01;
6const FLAG_BACKWARD: u8 = 0x02;
7const FLAG_LEFT: u8 = 0x04;
8const FLAG_RIGHT: u8 = 0x08;
9const FLAG_JUMP: u8 = 0x10;
10const FLAG_SHIFT: u8 = 0x20;
11const FLAG_SPRINT: u8 = 0x40;
12
13/// Vanilla `Input` snapshot from the latest serverbound player-input packet.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct PlayerInput {
16    forward: bool,
17    backward: bool,
18    left: bool,
19    right: bool,
20    jump: bool,
21    shift: bool,
22    sprint: bool,
23}
24
25impl PlayerInput {
26    /// Empty input state used before the client sends input.
27    pub const EMPTY: Self = Self::from_flags(0);
28
29    /// Decodes vanilla `Input` bit flags.
30    #[must_use]
31    pub const fn from_flags(flags: u8) -> Self {
32        Self {
33            forward: flags & FLAG_FORWARD != 0,
34            backward: flags & FLAG_BACKWARD != 0,
35            left: flags & FLAG_LEFT != 0,
36            right: flags & FLAG_RIGHT != 0,
37            jump: flags & FLAG_JUMP != 0,
38            shift: flags & FLAG_SHIFT != 0,
39            sprint: flags & FLAG_SPRINT != 0,
40        }
41    }
42
43    /// Encodes this input snapshot as vanilla `Input` bit flags.
44    #[must_use]
45    pub const fn flags(self) -> u8 {
46        let mut flags = 0;
47        flags |= if self.forward { FLAG_FORWARD } else { 0 };
48        flags |= if self.backward { FLAG_BACKWARD } else { 0 };
49        flags |= if self.left { FLAG_LEFT } else { 0 };
50        flags |= if self.right { FLAG_RIGHT } else { 0 };
51        flags |= if self.jump { FLAG_JUMP } else { 0 };
52        flags |= if self.shift { FLAG_SHIFT } else { 0 };
53        flags |= if self.sprint { FLAG_SPRINT } else { 0 };
54        flags
55    }
56
57    /// Returns true if forward input is pressed.
58    #[must_use]
59    pub const fn forward(self) -> bool {
60        self.forward
61    }
62
63    /// Returns true if backward input is pressed.
64    #[must_use]
65    pub const fn backward(self) -> bool {
66        self.backward
67    }
68
69    /// Returns true if left input is pressed.
70    #[must_use]
71    pub const fn left(self) -> bool {
72        self.left
73    }
74
75    /// Returns true if right input is pressed.
76    #[must_use]
77    pub const fn right(self) -> bool {
78        self.right
79    }
80
81    /// Returns true if jump input is pressed.
82    #[must_use]
83    pub const fn jump(self) -> bool {
84        self.jump
85    }
86
87    /// Returns true if shift input is pressed.
88    #[must_use]
89    pub const fn shift(self) -> bool {
90        self.shift
91    }
92
93    /// Returns true if sprint input is pressed.
94    #[must_use]
95    pub const fn sprint(self) -> bool {
96        self.sprint
97    }
98
99    /// Returns vanilla's unrotated left/right movement intent.
100    #[must_use]
101    pub const fn left_intent(self) -> f32 {
102        if self.left == self.right {
103            0.0
104        } else if self.left {
105            1.0
106        } else {
107            -1.0
108        }
109    }
110
111    /// Returns vanilla's unrotated forward/backward movement intent.
112    #[must_use]
113    pub const fn forward_intent(self) -> f32 {
114        if self.forward == self.backward {
115            0.0
116        } else if self.forward {
117            1.0
118        } else {
119            -1.0
120        }
121    }
122
123    /// Returns the unrotated movement input vector used by vanilla player intent.
124    #[must_use]
125    pub fn movement_input(self) -> DVec3 {
126        DVec3::new(
127            f64::from(self.left_intent()),
128            0.0,
129            f64::from(self.forward_intent()),
130        )
131    }
132}
133
134impl Default for PlayerInput {
135    fn default() -> Self {
136        Self::EMPTY
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::PlayerInput;
143    use glam::DVec3;
144
145    #[test]
146    fn movement_input_matches_vanilla_intent_axes() {
147        assert_eq!(PlayerInput::EMPTY.movement_input(), DVec3::ZERO);
148        assert_eq!(
149            PlayerInput::from_flags(0x01 | 0x08).movement_input(),
150            DVec3::new(-1.0, 0.0, 1.0)
151        );
152        assert_eq!(
153            PlayerInput::from_flags(0x01 | 0x02 | 0x04 | 0x08).movement_input(),
154            DVec3::ZERO
155        );
156    }
157
158    #[test]
159    fn input_flags_round_trip() {
160        let flags = 0x01 | 0x10 | 0x20 | 0x40;
161        let input = PlayerInput::from_flags(flags);
162
163        assert!(input.forward());
164        assert!(!input.backward());
165        assert!(input.jump());
166        assert!(input.shift());
167        assert!(input.sprint());
168        assert_eq!(input.flags(), flags);
169    }
170}