pkmn_core_types/
nds.rs

1//! Types that are specific to the Generation 4 and 5 (DS) Pokémon games.
2
3use core::fmt::Display;
4use core::num::{NonZeroU16, TryFromIntError};
5use core::str::FromStr;
6
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use crate::PokemonItem;
10
11/// Identifier for items that appear in the Generation 4 (DS) Pokémon games.
12///
13/// This type is a wrapper around [`NonZeroU16`].
14///
15/// For string conversions, [`Display`], and [`Serialize`], this type uses
16/// the modern English names for all items. These names are all the same as [`PokemonItem`],
17/// including any changes to the names that have happened more recently than Generation 4.
18///
19/// Generation 4 is the point at which Pokémon item numbers mostly stabilized moving forward.
20/// However, there are a few exceptions, which are the reason why this type is separate from
21/// [`PokemonItem`]:
22/// - In HGSS, Yellow Apricorn is item 486 and Blue Apricorn is item 487. These two are swapped
23///   in all later generations.
24/// - None of the mail items from Generation 4 exist in any other generation.
25///
26/// Bulbapedia documents a list of all items in these games by index number:
27/// <https://bulbapedia.bulbagarden.net/wiki/List_of_items_by_index_number_in_Generation_IV>
28#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
29pub struct PokemonItem4(NonZeroU16);
30
31const MAIL_NAMES: [&str; 12] = [
32    "Grass Mail",
33    "Flame Mail",
34    "Bubble Mail",
35    "Bloom Mail",
36    "Tunnel Mail",
37    "Steel Mail",
38    "Heart Mail",
39    "Snow Mail",
40    "Space Mail",
41    "Air Mail",
42    "Mosaic Mail",
43    "Brick Mail",
44];
45
46impl PokemonItem4 {
47    pub fn new(value: u16) -> Option<Self> {
48        Some(Self(NonZeroU16::new(value)?))
49    }
50
51    pub fn new_nonzero(value: NonZeroU16) -> Self {
52        Self(value)
53    }
54
55    /// Get the index number.
56    pub fn get(self) -> u16 {
57        self.0.get()
58    }
59
60    /// Get the index number as a [`NonZeroU16`].
61    pub fn get_nonzero(self) -> NonZeroU16 {
62        self.0
63    }
64
65    /// Checks whether this [`PokemonItem4`] is a valid item in Diamond and Pearl (1-464).
66    pub fn valid_dp(self) -> bool {
67        self.0.get() <= 464
68    }
69
70    /// Checks whether this [`PokemonItem4`] is a valid item in Platinum (1-467).
71    pub fn valid_platinum(self) -> bool {
72        self.0.get() <= 467
73    }
74
75    /// Checks whether this [`PokemonItem4`] is a valid item in HeartGold and SoulSilver (1-536).
76    pub fn valid_hgss(self) -> bool {
77        self.0.get() <= 536
78    }
79
80    fn index_fixes(val: u16) -> Option<NonZeroU16> {
81        let idx = match val {
82            // Swap Blue Apricorn and Yellow Apricorn.
83            486 => 487,
84            487 => 486,
85            x => x,
86        };
87        if (137..=148).contains(&idx) || idx > 536 {
88            // Fail for any mail items and OOB.
89            None
90        } else {
91            NonZeroU16::new(idx)
92        }
93    }
94}
95
96impl From<PokemonItem4> for u16 {
97    fn from(value: PokemonItem4) -> Self {
98        value.0.get()
99    }
100}
101
102impl TryFrom<u16> for PokemonItem4 {
103    type Error = TryFromIntError;
104    fn try_from(value: u16) -> Result<Self, Self::Error> {
105        Ok(Self(NonZeroU16::try_from(value)?))
106    }
107}
108
109impl TryFrom<PokemonItem4> for PokemonItem {
110    type Error = u16;
111    /// Convert this item index number to one used in later generations.
112    ///
113    /// This fails if the item is mail or out of bounds in Generation 4.
114    fn try_from(value: PokemonItem4) -> Result<Self, Self::Error> {
115        let val = value.0.get();
116        if let Some(idx) = PokemonItem4::index_fixes(val) {
117            Ok(PokemonItem::from(idx))
118        } else {
119            Err(val)
120        }
121    }
122}
123
124impl TryFrom<PokemonItem> for PokemonItem4 {
125    type Error = u16;
126    /// Convert an item index number from later generations.
127    ///
128    /// This fails if the item did not yet exist in Generation 4.
129    fn try_from(value: PokemonItem) -> Result<Self, Self::Error> {
130        let val = value.into();
131        if let Some(idx) = PokemonItem4::index_fixes(val) {
132            Ok(PokemonItem4(idx))
133        } else {
134            Err(val)
135        }
136    }
137}
138
139impl TryFrom<PokemonItem4> for &'static str {
140    type Error = u16;
141    fn try_from(value: PokemonItem4) -> Result<Self, Self::Error> {
142        match value.0.get() {
143            137..=148 => Ok(MAIL_NAMES[value.0.get() as usize - 137]),
144            486 => Ok("Yellow Apricorn"),
145            487 => Ok("Blue Apricorn"),
146            x if x > 536 => Err(x),
147            _ => PokemonItem::from(value.0).try_into(),
148        }
149    }
150}
151
152impl Display for PokemonItem4 {
153    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
154        if let Ok(s) = (*self).try_into() {
155            f.write_str(s)
156        } else {
157            write!(f, "Item {}", self.0.get())
158        }
159    }
160}
161
162impl FromStr for PokemonItem4 {
163    type Err = ();
164    fn from_str(s: &str) -> Result<Self, Self::Err> {
165        for (i, m) in MAIL_NAMES.iter().enumerate() {
166            if s == *m {
167                return Ok(PokemonItem4(NonZeroU16::new(137 + i as u16).unwrap()));
168            }
169        }
170        PokemonItem4::try_from(PokemonItem::from_str(s)?).or(Err(()))
171    }
172}
173
174impl Serialize for PokemonItem4 {
175    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
176        s.collect_str(self)
177    }
178}
179
180impl<'de> Deserialize<'de> for PokemonItem4 {
181    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
182        let s = <&str>::deserialize(d)?;
183        Self::from_str(s).or(Err(serde::de::Error::custom("no match")))
184    }
185}