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}