Skip to main content

rkg_utils/header/
combo.rs

1use crate::byte_handler::{ByteHandler, ByteHandlerError, FromByteHandler};
2use std::fmt::Display;
3
4/// Represents a valid character and vehicle combination from a Mario Kart Wii RKG ghost file.
5///
6/// A combo is only valid when the character and vehicle share the same [`WeightClass`].
7/// Construction via [`Combo::new`] enforces this constraint.
8pub struct Combo {
9    /// The character used in the run.
10    character: Character,
11    /// The vehicle used in the run.
12    vehicle: Vehicle,
13}
14
15/// Formats the combo as `"{character} on {vehicle}"` (e.g. `"Mario on Mach Bike"`).
16impl Display for Combo {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        write!(f, "{} on {}", self.character(), self.vehicle())
19    }
20}
21
22/// Errors that can occur while constructing or deserializing a [`Combo`].
23#[derive(thiserror::Error, Debug)]
24pub enum ComboError {
25    /// The input iterator did not contain enough bytes to extract a combo.
26    #[error("Insufficiently Long Iterator")]
27    InsufficientlyLongIterator,
28    /// The character and vehicle belong to different weight classes.
29    #[error("The combo has incongruent weight classes")]
30    IncongruentWeightClasses,
31    /// The vehicle byte did not map to a known [`Vehicle`] variant.
32    #[error("Invalid Vehicle ID")]
33    InvalidVehicleId,
34    /// The character byte did not map to a known [`Character`] variant.
35    #[error("Invalid Character ID")]
36    InvalidCharacterId,
37    /// The character ID corresponds to a character that cannot appear in ghost files.
38    #[error("Impossible Character ID")]
39    ImpossibleCharacterId,
40    /// A `ByteHandler` operation failed.
41    #[error("ByteHandler Error: {0}")]
42    ByteHandlerError(#[from] ByteHandlerError),
43}
44
45impl Combo {
46    /// Creates a new [`Combo`] from a vehicle and character.
47    ///
48    /// # Errors
49    ///
50    /// Returns [`ComboError::IncongruentWeightClasses`] if the character and vehicle
51    /// do not share the same [`WeightClass`].
52    #[inline(always)]
53    pub fn new(vehicle: Vehicle, character: Character) -> Result<Self, ComboError> {
54        if character.get_weight_class() != vehicle.get_weight_class() {
55            return Err(ComboError::IncongruentWeightClasses);
56        }
57
58        Ok(Self { vehicle, character })
59    }
60
61    /// Returns the character used in the run.
62    pub fn character(&self) -> Character {
63        self.character
64    }
65
66    /// Returns the vehicle used in the run.
67    pub fn vehicle(&self) -> Vehicle {
68        self.vehicle
69    }
70}
71
72/// Deserializes a [`Combo`] from a `ByteHandler` containing 2 bytes at header offset `0x08..0x0A`.
73///
74/// The bytes are packed as follows:
75/// ```text
76/// Byte 1: VVVVVVCC
77/// Byte 2: CCCCXXXX
78/// ```
79/// where `V` = vehicle ID bits and `C` = character ID bits.
80impl FromByteHandler for Combo {
81    type Err = ComboError;
82
83    fn from_byte_handler<T>(handler: T) -> Result<Self, Self::Err>
84    where
85        T: TryInto<ByteHandler>,
86        Self::Err: From<T::Error>,
87    {
88        let mut handler = handler.try_into()?;
89
90        handler.shift_right(2); // 1. 00VVVVVV
91        let vehicle = handler.copy_byte(0);
92
93        handler.shift_right(2); // 2. VVCCCCCC
94        let character = handler.copy_byte(1) & 0x3F;
95
96        Self::new(
97            Vehicle::try_from(vehicle).map_err(|_| ComboError::InvalidVehicleId)?,
98            Character::try_from(character).map_err(|_| ComboError::InvalidCharacterId)?,
99        )
100    }
101}
102
103/// Returns the weight class of the combo, which is always equal to both the
104/// character's and vehicle's weight class (enforced at construction time).
105impl GetWeightClass for Combo {
106    fn get_weight_class(&self) -> WeightClass {
107        self.character.get_weight_class()
108    }
109}
110
111/// The weight class of a character or vehicle in Mario Kart Wii.
112///
113/// A valid [`Combo`] requires the character and vehicle to share the same weight class.
114#[derive(Debug, Clone, Copy, PartialEq)]
115pub enum WeightClass {
116    Small,
117    Medium,
118    Large,
119}
120
121/// Trait for types that have an associated [`WeightClass`].
122pub trait GetWeightClass {
123    /// Returns the weight class of this character, vehicle, or combo.
124    fn get_weight_class(&self) -> WeightClass;
125}
126
127/// All playable characters in Mario Kart Wii, including Mii outfit variants and
128/// menu-only characters.
129///
130/// Character identifiers are documented at
131/// <https://wiki.tockdom.com/wiki/List_of_Identifiers#Characters>.
132#[derive(Debug, Clone, Copy, PartialEq)]
133pub enum Character {
134    Mario,
135    BabyPeach,
136    Waluigi,
137    Bowser,
138    BabyDaisy,
139    DryBones,
140    BabyMario,
141    Luigi,
142    Toad,
143    DonkeyKong,
144    Yoshi,
145    Wario,
146    BabyLuigi,
147    Toadette,
148    KoopaTroopa,
149    Daisy,
150    Peach,
151    Birdo,
152    DiddyKong,
153    KingBoo,
154    BowserJr,
155    DryBowser,
156    FunkyKong,
157    Rosalina,
158    SmallMiiOutfitAMale,
159    SmallMiiOutfitAFemale,
160    SmallMiiOutfitBMale,
161    SmallMiiOutfitBFemale,
162    SmallMiiOutfitCMale,
163    SmallMiiOutfitCFemale,
164    MediumMiiOutfitAMale,
165    MediumMiiOutfitAFemale,
166    MediumMiiOutfitBMale,
167    MediumMiiOutfitBFemale,
168    MediumMiiOutfitCMale,
169    MediumMiiOutfitCFemale,
170    LargeMiiOutfitAMale,
171    LargeMiiOutfitAFemale,
172    LargeMiiOutfitBMale,
173    LargeMiiOutfitBFemale,
174    LargeMiiOutfitCMale,
175    LargeMiiOutfitCFemale,
176    /// Generic medium-class Mii without a specific outfit variant.
177    MediumMii,
178    /// Generic small-class Mii without a specific outfit variant.
179    SmallMii,
180    /// Generic large-class Mii without a specific outfit variant.
181    LargeMii,
182    /// Peach as she appears in menu screens; cannot appear in ghost files.
183    MenuPeach,
184    /// Daisy as she appears in menu screens; cannot appear in ghost files.
185    MenuDaisy,
186    /// Rosalina as she appears in menu screens; cannot appear in ghost files.
187    MenuRosalina,
188}
189
190/// Formats the character as her/his display name (e.g. `"Donkey Kong"`, `"Baby Peach"`).
191impl Display for Character {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        let s = match self {
194            Character::Mario => "Mario",
195            Character::BabyPeach => "Baby Peach",
196            Character::Waluigi => "Waluigi",
197            Character::Bowser => "Bowser",
198            Character::BabyDaisy => "Baby Daisy",
199            Character::DryBones => "Dry Bones",
200            Character::BabyMario => "Baby Mario",
201            Character::Luigi => "Luigi",
202            Character::Toad => "Toad",
203            Character::DonkeyKong => "Donkey Kong",
204            Character::Yoshi => "Yoshi",
205            Character::Wario => "Wario",
206            Character::BabyLuigi => "Baby Luigi",
207            Character::Toadette => "Toadette",
208            Character::KoopaTroopa => "Koopa Troopa",
209            Character::Daisy => "Daisy",
210            Character::Peach => "Peach",
211            Character::Birdo => "Birdo",
212            Character::DiddyKong => "Diddy Kong",
213            Character::KingBoo => "King Boo",
214            Character::BowserJr => "Bowser Jr.",
215            Character::DryBowser => "Dry Bowser",
216            Character::FunkyKong => "Funky Kong",
217            Character::Rosalina => "Rosalina",
218            Character::SmallMiiOutfitAMale => "Small Mii Outfit A (Male)",
219            Character::SmallMiiOutfitAFemale => "Small Mii Outfit A (Female)",
220            Character::SmallMiiOutfitBMale => "Small Mii Outfit B (Male)",
221            Character::SmallMiiOutfitBFemale => "Small Mii Outfit B (Female)",
222            Character::SmallMiiOutfitCMale => "Small Mii Outfit C (Male)",
223            Character::SmallMiiOutfitCFemale => "Small Mii Outfit C (Female)",
224            Character::MediumMiiOutfitAMale => "Medium Mii Outfit A (Male)",
225            Character::MediumMiiOutfitAFemale => "Medium Mii Outfit A (Female)",
226            Character::MediumMiiOutfitBMale => "Medium Mii Outfit B (Male)",
227            Character::MediumMiiOutfitBFemale => "Medium Mii Outfit B (Female)",
228            Character::MediumMiiOutfitCMale => "Medium Mii Outfit C (Male)",
229            Character::MediumMiiOutfitCFemale => "Medium Mii Outfit C (Female)",
230            Character::LargeMiiOutfitAMale => "Large Mii Outfit A (Male)",
231            Character::LargeMiiOutfitAFemale => "Large Mii Outfit A (Female)",
232            Character::LargeMiiOutfitBMale => "Large Mii Outfit B (Male)",
233            Character::LargeMiiOutfitBFemale => "Large Mii Outfit B (Female)",
234            Character::LargeMiiOutfitCMale => "Large Mii Outfit C (Male)",
235            Character::LargeMiiOutfitCFemale => "Large Mii Outfit C (Female)",
236            Character::MediumMii => "Medium Mii",
237            Character::SmallMii => "Small Mii",
238            Character::LargeMii => "Large Mii",
239            Character::MenuPeach => "Peach (Menu)",
240            Character::MenuDaisy => "Daisy (Menu)",
241            Character::MenuRosalina => "Rosalina (Menu)",
242        };
243        write!(f, "{}", s)
244    }
245}
246
247impl Character {
248    /// Returns `true` if this character cannot legitimately appear in a ghost file.
249    ///
250    /// The impossible characters are the generic Mii variants (`SmallMii`, `MediumMii`,
251    /// `LargeMii`) and the menu-only versions of Peach, Daisy, and Rosalina.
252    pub fn is_impossible(self) -> bool {
253        match self {
254            Self::Mario
255            | Self::BabyPeach
256            | Self::Waluigi
257            | Self::Bowser
258            | Self::BabyDaisy
259            | Self::DryBones
260            | Self::BabyMario
261            | Self::Luigi
262            | Self::Toad
263            | Self::DonkeyKong
264            | Self::Yoshi
265            | Self::Wario
266            | Self::BabyLuigi
267            | Self::Toadette
268            | Self::KoopaTroopa
269            | Self::Daisy
270            | Self::Peach
271            | Self::Birdo
272            | Self::DiddyKong
273            | Self::KingBoo
274            | Self::BowserJr
275            | Self::DryBowser
276            | Self::FunkyKong
277            | Self::Rosalina
278            | Self::SmallMiiOutfitAMale
279            | Self::SmallMiiOutfitAFemale
280            | Self::SmallMiiOutfitBMale
281            | Self::SmallMiiOutfitBFemale
282            | Self::SmallMiiOutfitCMale
283            | Self::SmallMiiOutfitCFemale
284            | Self::MediumMiiOutfitAMale
285            | Self::MediumMiiOutfitAFemale
286            | Self::MediumMiiOutfitBMale
287            | Self::MediumMiiOutfitBFemale
288            | Self::MediumMiiOutfitCMale
289            | Self::MediumMiiOutfitCFemale
290            | Self::LargeMiiOutfitAMale
291            | Self::LargeMiiOutfitAFemale
292            | Self::LargeMiiOutfitBMale
293            | Self::LargeMiiOutfitBFemale
294            | Self::LargeMiiOutfitCMale
295            | Self::LargeMiiOutfitCFemale => false,
296            Self::MenuPeach
297            | Self::MenuDaisy
298            | Self::MenuRosalina
299            | Self::MediumMii
300            | Self::SmallMii
301            | Self::LargeMii => true,
302        }
303    }
304}
305
306/// Converts a raw byte value from the RKG header into a [`Character`].
307///
308/// Returns `Err(())` if the byte does not correspond to any known character.
309impl TryFrom<u8> for Character {
310    type Error = ();
311    fn try_from(value: u8) -> Result<Self, Self::Error> {
312        match value {
313            0x00 => Ok(Self::Mario),
314            0x01 => Ok(Self::BabyPeach),
315            0x02 => Ok(Self::Waluigi),
316            0x03 => Ok(Self::Bowser),
317            0x04 => Ok(Self::BabyDaisy),
318            0x05 => Ok(Self::DryBones),
319            0x06 => Ok(Self::BabyMario),
320            0x07 => Ok(Self::Luigi),
321            0x08 => Ok(Self::Toad),
322            0x09 => Ok(Self::DonkeyKong),
323            0x0A => Ok(Self::Yoshi),
324            0x0B => Ok(Self::Wario),
325            0x0C => Ok(Self::BabyLuigi),
326            0x0D => Ok(Self::Toadette),
327            0x0E => Ok(Self::KoopaTroopa),
328            0x0F => Ok(Self::Daisy),
329            0x10 => Ok(Self::Peach),
330            0x11 => Ok(Self::Birdo),
331            0x12 => Ok(Self::DiddyKong),
332            0x13 => Ok(Self::KingBoo),
333            0x14 => Ok(Self::BowserJr),
334            0x15 => Ok(Self::DryBowser),
335            0x16 => Ok(Self::FunkyKong),
336            0x17 => Ok(Self::Rosalina),
337            0x18 => Ok(Self::SmallMiiOutfitAMale),
338            0x19 => Ok(Self::SmallMiiOutfitAFemale),
339            0x1A => Ok(Self::SmallMiiOutfitBMale),
340            0x1B => Ok(Self::SmallMiiOutfitBFemale),
341            0x1C => Ok(Self::SmallMiiOutfitCMale),
342            0x1D => Ok(Self::SmallMiiOutfitCFemale),
343            0x1E => Ok(Self::MediumMiiOutfitAMale),
344            0x1F => Ok(Self::MediumMiiOutfitAFemale),
345            0x20 => Ok(Self::MediumMiiOutfitBMale),
346            0x21 => Ok(Self::MediumMiiOutfitBFemale),
347            0x22 => Ok(Self::MediumMiiOutfitCMale),
348            0x23 => Ok(Self::MediumMiiOutfitCFemale),
349            0x24 => Ok(Self::LargeMiiOutfitAMale),
350            0x25 => Ok(Self::LargeMiiOutfitAFemale),
351            0x26 => Ok(Self::LargeMiiOutfitBMale),
352            0x27 => Ok(Self::LargeMiiOutfitBFemale),
353            0x28 => Ok(Self::LargeMiiOutfitCMale),
354            0x29 => Ok(Self::LargeMiiOutfitCFemale),
355            0x2A => Ok(Self::MediumMii),
356            0x2B => Ok(Self::SmallMii),
357            0x2C => Ok(Self::LargeMii),
358            0x2D => Ok(Self::MenuPeach),
359            0x2E => Ok(Self::MenuDaisy),
360            0x2F => Ok(Self::MenuRosalina),
361            _ => Err(()),
362        }
363    }
364}
365
366/// Converts a [`Character`] into its raw byte representation for the RKG header.
367impl From<Character> for u8 {
368    fn from(value: Character) -> Self {
369        match value {
370            Character::Mario => 0x00,
371            Character::BabyPeach => 0x01,
372            Character::Waluigi => 0x02,
373            Character::Bowser => 0x03,
374            Character::BabyDaisy => 0x04,
375            Character::DryBones => 0x05,
376            Character::BabyMario => 0x06,
377            Character::Luigi => 0x07,
378            Character::Toad => 0x08,
379            Character::DonkeyKong => 0x09,
380            Character::Yoshi => 0x0A,
381            Character::Wario => 0x0B,
382            Character::BabyLuigi => 0x0C,
383            Character::Toadette => 0x0D,
384            Character::KoopaTroopa => 0x0E,
385            Character::Daisy => 0x0F,
386            Character::Peach => 0x10,
387            Character::Birdo => 0x11,
388            Character::DiddyKong => 0x12,
389            Character::KingBoo => 0x13,
390            Character::BowserJr => 0x14,
391            Character::DryBowser => 0x15,
392            Character::FunkyKong => 0x16,
393            Character::Rosalina => 0x17,
394            Character::SmallMiiOutfitAMale => 0x18,
395            Character::SmallMiiOutfitAFemale => 0x19,
396            Character::SmallMiiOutfitBMale => 0x1A,
397            Character::SmallMiiOutfitBFemale => 0x1B,
398            Character::SmallMiiOutfitCMale => 0x1C,
399            Character::SmallMiiOutfitCFemale => 0x1D,
400            Character::MediumMiiOutfitAMale => 0x1E,
401            Character::MediumMiiOutfitAFemale => 0x1F,
402            Character::MediumMiiOutfitBMale => 0x20,
403            Character::MediumMiiOutfitBFemale => 0x21,
404            Character::MediumMiiOutfitCMale => 0x22,
405            Character::MediumMiiOutfitCFemale => 0x23,
406            Character::LargeMiiOutfitAMale => 0x24,
407            Character::LargeMiiOutfitAFemale => 0x25,
408            Character::LargeMiiOutfitBMale => 0x26,
409            Character::LargeMiiOutfitBFemale => 0x27,
410            Character::LargeMiiOutfitCMale => 0x28,
411            Character::LargeMiiOutfitCFemale => 0x29,
412            Character::MediumMii => 0x2A,
413            Character::SmallMii => 0x2B,
414            Character::LargeMii => 0x2C,
415            Character::MenuPeach => 0x2D,
416            Character::MenuDaisy => 0x2E,
417            Character::MenuRosalina => 0x2F,
418        }
419    }
420}
421
422/// Returns the [`WeightClass`] of this character.
423impl GetWeightClass for Character {
424    fn get_weight_class(&self) -> WeightClass {
425        match self {
426            Self::BabyDaisy
427            | Self::BabyLuigi
428            | Self::BabyMario
429            | Self::BabyPeach
430            | Self::DryBones
431            | Self::KoopaTroopa
432            | Self::SmallMii
433            | Self::SmallMiiOutfitAMale
434            | Self::SmallMiiOutfitAFemale
435            | Self::SmallMiiOutfitBMale
436            | Self::SmallMiiOutfitBFemale
437            | Self::SmallMiiOutfitCMale
438            | Self::SmallMiiOutfitCFemale
439            | Self::Toad
440            | Self::Toadette => WeightClass::Small,
441            Self::Birdo
442            | Self::BowserJr
443            | Self::Daisy
444            | Self::MenuDaisy
445            | Self::DiddyKong
446            | Self::Luigi
447            | Self::Mario
448            | Self::MediumMii
449            | Self::MediumMiiOutfitAMale
450            | Self::MediumMiiOutfitAFemale
451            | Self::MediumMiiOutfitBMale
452            | Self::MediumMiiOutfitBFemale
453            | Self::MediumMiiOutfitCMale
454            | Self::MediumMiiOutfitCFemale
455            | Self::Peach
456            | Self::MenuPeach
457            | Self::Yoshi => WeightClass::Medium,
458            Self::Bowser
459            | Self::DonkeyKong
460            | Self::DryBowser
461            | Self::FunkyKong
462            | Self::KingBoo
463            | Self::LargeMii
464            | Self::LargeMiiOutfitAMale
465            | Self::LargeMiiOutfitAFemale
466            | Self::LargeMiiOutfitBMale
467            | Self::LargeMiiOutfitBFemale
468            | Self::LargeMiiOutfitCMale
469            | Self::LargeMiiOutfitCFemale
470            | Self::MenuRosalina
471            | Self::Rosalina
472            | Self::Waluigi
473            | Self::Wario => WeightClass::Large,
474        }
475    }
476}
477
478/// Represents all drivable vehicles in Mario Kart Wii.
479///
480/// Vehicle identifiers are documented at
481/// <https://wiki.tockdom.com/wiki/List_of_Identifiers#Vehicles>.
482#[derive(Debug, Clone, Copy, PartialEq)]
483pub enum Vehicle {
484    StandardKartS,
485    StandardKartM,
486    StandardKartL,
487    BoosterSeat,
488    ClassicDragster,
489    Offroader,
490    MiniBeast,
491    WildWing,
492    FlameFlyer,
493    CheepCharger,
494    SuperBlooper,
495    PiranhaProwler,
496    TinyTitan,
497    Daytripper,
498    Jetsetter,
499    BlueFalcon,
500    Sprinter,
501    Honeycoupe,
502    StandardBikeS,
503    StandardBikeM,
504    StandardBikeL,
505    BulletBike,
506    MachBike,
507    FlameRunner,
508    BitBike,
509    Sugarscoot,
510    WarioBike,
511    Quacker,
512    ZipZip,
513    ShootingStar,
514    Magikruiser,
515    Sneakster,
516    Spear,
517    JetBubble,
518    DolphinDasher,
519    Phantom,
520}
521
522/// Formats the vehicle as its display name (e.g. `"Mach Bike"`, `"Flame Runner"`).
523impl Display for Vehicle {
524    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
525        let s = match self {
526            Vehicle::StandardKartS => "Standard Kart S",
527            Vehicle::StandardKartM => "Standard Kart M",
528            Vehicle::StandardKartL => "Standard Kart L",
529            Vehicle::BoosterSeat => "Booster Seat",
530            Vehicle::ClassicDragster => "Classic Dragster",
531            Vehicle::Offroader => "Offroader",
532            Vehicle::MiniBeast => "Mini Beast",
533            Vehicle::WildWing => "Wild Wing",
534            Vehicle::FlameFlyer => "Flame Flyer",
535            Vehicle::CheepCharger => "Cheep Charger",
536            Vehicle::SuperBlooper => "Super Blooper",
537            Vehicle::PiranhaProwler => "Piranha Prowler",
538            Vehicle::TinyTitan => "Tiny Titan",
539            Vehicle::Daytripper => "Daytripper",
540            Vehicle::Jetsetter => "Jetsetter",
541            Vehicle::BlueFalcon => "Blue Falcon",
542            Vehicle::Sprinter => "Sprinter",
543            Vehicle::Honeycoupe => "Honeycoupe",
544            Vehicle::StandardBikeS => "Standard Bike S",
545            Vehicle::StandardBikeM => "Standard Bike M",
546            Vehicle::StandardBikeL => "Standard Bike L",
547            Vehicle::BulletBike => "Bullet Bike",
548            Vehicle::MachBike => "Mach Bike",
549            Vehicle::FlameRunner => "Flame Runner",
550            Vehicle::BitBike => "Bit Bike",
551            Vehicle::Sugarscoot => "Sugarscoot",
552            Vehicle::WarioBike => "Wario Bike",
553            Vehicle::Quacker => "Quacker",
554            Vehicle::ZipZip => "Zip Zip",
555            Vehicle::ShootingStar => "Shooting Star",
556            Vehicle::Magikruiser => "Magikruiser",
557            Vehicle::Sneakster => "Sneakster",
558            Vehicle::Spear => "Spear",
559            Vehicle::JetBubble => "Jet Bubble",
560            Vehicle::DolphinDasher => "Dolphin Dasher",
561            Vehicle::Phantom => "Phantom",
562        };
563
564        write!(f, "{}", s)
565    }
566}
567
568/// Converts a raw byte value from the RKG header into a [`Vehicle`].
569///
570/// Returns `Err(())` if the byte does not correspond to any known vehicle.
571impl TryFrom<u8> for Vehicle {
572    type Error = ();
573    fn try_from(value: u8) -> Result<Self, Self::Error> {
574        match value {
575            0x00 => Ok(Self::StandardKartS),
576            0x01 => Ok(Self::StandardKartM),
577            0x02 => Ok(Self::StandardKartL),
578            0x03 => Ok(Self::BoosterSeat),
579            0x04 => Ok(Self::ClassicDragster),
580            0x05 => Ok(Self::Offroader),
581            0x06 => Ok(Self::MiniBeast),
582            0x07 => Ok(Self::WildWing),
583            0x08 => Ok(Self::FlameFlyer),
584            0x09 => Ok(Self::CheepCharger),
585            0x0A => Ok(Self::SuperBlooper),
586            0x0B => Ok(Self::PiranhaProwler),
587            0x0C => Ok(Self::TinyTitan),
588            0x0D => Ok(Self::Daytripper),
589            0x0E => Ok(Self::Jetsetter),
590            0x0F => Ok(Self::BlueFalcon),
591            0x10 => Ok(Self::Sprinter),
592            0x11 => Ok(Self::Honeycoupe),
593            0x12 => Ok(Self::StandardBikeS),
594            0x13 => Ok(Self::StandardBikeM),
595            0x14 => Ok(Self::StandardBikeL),
596            0x15 => Ok(Self::BulletBike),
597            0x16 => Ok(Self::MachBike),
598            0x17 => Ok(Self::FlameRunner),
599            0x18 => Ok(Self::BitBike),
600            0x19 => Ok(Self::Sugarscoot),
601            0x1A => Ok(Self::WarioBike),
602            0x1B => Ok(Self::Quacker),
603            0x1C => Ok(Self::ZipZip),
604            0x1D => Ok(Self::ShootingStar),
605            0x1E => Ok(Self::Magikruiser),
606            0x1F => Ok(Self::Sneakster),
607            0x20 => Ok(Self::Spear),
608            0x21 => Ok(Self::JetBubble),
609            0x22 => Ok(Self::DolphinDasher),
610            0x23 => Ok(Self::Phantom),
611            _ => Err(()),
612        }
613    }
614}
615
616/// Converts a [`Vehicle`] into its raw byte representation for the RKG header.
617impl From<Vehicle> for u8 {
618    fn from(value: Vehicle) -> Self {
619        match value {
620            Vehicle::StandardKartS => 0x00,
621            Vehicle::StandardKartM => 0x01,
622            Vehicle::StandardKartL => 0x02,
623            Vehicle::BoosterSeat => 0x03,
624            Vehicle::ClassicDragster => 0x04,
625            Vehicle::Offroader => 0x05,
626            Vehicle::MiniBeast => 0x06,
627            Vehicle::WildWing => 0x07,
628            Vehicle::FlameFlyer => 0x08,
629            Vehicle::CheepCharger => 0x09,
630            Vehicle::SuperBlooper => 0x0A,
631            Vehicle::PiranhaProwler => 0x0B,
632            Vehicle::TinyTitan => 0x0C,
633            Vehicle::Daytripper => 0x0D,
634            Vehicle::Jetsetter => 0x0E,
635            Vehicle::BlueFalcon => 0x0F,
636            Vehicle::Sprinter => 0x10,
637            Vehicle::Honeycoupe => 0x11,
638            Vehicle::StandardBikeS => 0x12,
639            Vehicle::StandardBikeM => 0x13,
640            Vehicle::StandardBikeL => 0x14,
641            Vehicle::BulletBike => 0x15,
642            Vehicle::MachBike => 0x16,
643            Vehicle::FlameRunner => 0x17,
644            Vehicle::BitBike => 0x18,
645            Vehicle::Sugarscoot => 0x19,
646            Vehicle::WarioBike => 0x1A,
647            Vehicle::Quacker => 0x1B,
648            Vehicle::ZipZip => 0x1C,
649            Vehicle::ShootingStar => 0x1D,
650            Vehicle::Magikruiser => 0x1E,
651            Vehicle::Sneakster => 0x1F,
652            Vehicle::Spear => 0x20,
653            Vehicle::JetBubble => 0x21,
654            Vehicle::DolphinDasher => 0x22,
655            Vehicle::Phantom => 0x23,
656        }
657    }
658}
659
660/// Returns the [`WeightClass`] of this vehicle.
661impl GetWeightClass for Vehicle {
662    fn get_weight_class(&self) -> WeightClass {
663        match self {
664            Self::StandardKartS
665            | Self::BoosterSeat
666            | Self::MiniBeast
667            | Self::CheepCharger
668            | Self::TinyTitan
669            | Self::BlueFalcon
670            | Self::StandardBikeS
671            | Self::BulletBike
672            | Self::BitBike
673            | Self::Quacker
674            | Self::Magikruiser
675            | Self::JetBubble => WeightClass::Small,
676            Self::StandardKartM
677            | Self::ClassicDragster
678            | Self::WildWing
679            | Self::SuperBlooper
680            | Self::Daytripper
681            | Self::Sprinter
682            | Self::StandardBikeM
683            | Self::MachBike
684            | Self::Sugarscoot
685            | Self::ZipZip
686            | Self::Sneakster
687            | Self::DolphinDasher => WeightClass::Medium,
688            Self::Offroader
689            | Self::StandardKartL
690            | Self::FlameFlyer
691            | Self::PiranhaProwler
692            | Self::Jetsetter
693            | Self::Honeycoupe
694            | Self::StandardBikeL
695            | Self::FlameRunner
696            | Self::WarioBike
697            | Self::ShootingStar
698            | Self::Spear
699            | Self::Phantom => WeightClass::Large,
700        }
701    }
702}