Skip to main content

rkg_utils/input_data/
stick_input.rs

1/// Errors that can occur while parsing a [`StickInput`].
2#[derive(thiserror::Error, Debug)]
3pub enum StickInputError {
4    /// One or both raw axis values exceeded the maximum encoded value of 14.
5    #[error("Invalid Stick Input")]
6    InvalidStickInput,
7}
8
9/// A single encoded analog stick input entry from a Mario Kart Wii ghost file.
10///
11/// Each entry records the stick position and for how many consecutive frames it
12/// was held. Both axes are encoded in a single byte as 4-bit unsigned values
13/// (0–14), then shifted to the signed range −7 to +7 for intuitive use.
14///
15/// Negative `x` values represent left; positive values represent right.
16/// Negative `y` values represent down; positive values represent up.
17#[derive(Debug)]
18pub struct StickInput {
19    /// Horizontal axis position in the range −7 (full left) to +7 (full right).
20    x: i8,
21    /// Vertical axis position in the range −7 (full down) to +7 (full up).
22    y: i8,
23    /// The number of frames this stick position was held.
24    frame_duration: u32,
25}
26
27impl StickInput {
28    /// Returns the horizontal axis position (−7 to +7).
29    pub fn x(&self) -> i8 {
30        self.x
31    }
32
33    /// Returns the vertical axis position (−7 to +7).
34    pub fn y(&self) -> i8 {
35        self.y
36    }
37
38    /// Returns the number of frames this stick position was held.
39    pub fn frame_duration(&self) -> u32 {
40        self.frame_duration
41    }
42
43    /// Sets the number of frames this stick position was held.
44    pub fn set_frame_duration(&mut self, frame_duration: u32) {
45        self.frame_duration = frame_duration;
46    }
47}
48
49/// Two [`StickInput`] values are equal if their `x` and `y` positions match,
50/// regardless of frame duration.
51impl PartialEq for StickInput {
52    fn eq(&self, other: &Self) -> bool {
53        self.x == other.x && self.y == other.y
54    }
55}
56
57/// Compares a [`StickInput`] against a `[i8; 2]` array of `[x, y]`.
58impl PartialEq<[i8; 2]> for StickInput {
59    fn eq(&self, other: &[i8; 2]) -> bool {
60        self.x == other[0] && self.y == other[1]
61    }
62}
63
64/// Parses a [`StickInput`] from a 2-byte slice.
65///
66/// # Errors
67///
68/// Returns [`StickInputError::InvalidStickInput`] if either axis value exceeds 14.
69impl TryFrom<&[u8]> for StickInput {
70    type Error = StickInputError;
71
72    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
73        let x = (value[0] & 0xF0) >> 4;
74        let y = value[0] & 0x0F;
75
76        if x > 14 || y > 14 {
77            return Err(StickInputError::InvalidStickInput);
78        }
79
80        // store x and y as ranging from -7 to +7, as that's more intuitive for left/right or up/down
81        let x = x as i8 - 7;
82        let y = y as i8 - 7;
83
84        let frame_duration = value[1] as u32;
85
86        Ok(Self {
87            x,
88            y,
89            frame_duration,
90        })
91    }
92}