Skip to main content

battler_data/mons/
species_data.rs

1use alloc::{
2    borrow::ToOwned,
3    format,
4    string::String,
5    vec::Vec,
6};
7
8use hashbrown::{
9    HashMap,
10    HashSet,
11};
12use serde::{
13    Deserialize,
14    Serialize,
15};
16
17use crate::{
18    Color,
19    EventData,
20    EvolutionData,
21    LearnSet,
22    LevelingRate,
23    SpeciesFlag,
24    StatTable,
25    Type,
26};
27
28fn default_as_true() -> bool {
29    true
30}
31
32/// Data about a particular species.
33///
34/// Species data is common to all Mons of a given species. Data about a specific Mon (such as its
35/// nature, stats, or battle-specific conditions) does not belong here.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct SpeciesData {
38    /// The name of the species plus any forme name.
39    ///
40    /// Should be equal to [base_species][`SpeciesData::base_species`] +
41    /// [forme][`SpeciesData::forme`].
42    ///
43    /// The species name should be unique across all species and formes.
44    pub name: String,
45    /// The base species name, which excludes any forme name.
46    pub base_species: String,
47    /// The forme name, if it exists.
48    pub forme: Option<String>,
49    /// The species class, which displays on the Dex page.
50    pub class: String,
51    /// The primary color of the species.
52    pub color: Color,
53    /// The primary type of the species.
54    pub primary_type: Type,
55    /// The secondary type of the species, if it exists.
56    pub secondary_type: Option<Type>,
57    /// Abilities.
58    pub abilities: Vec<String>,
59    /// Hidden ability, if it exists.
60    pub hidden_ability: Option<String>,
61    /// Gender ratio.
62    ///
63    /// Gender ratio is represented as one byte (a value between 0 and 255). There are three
64    /// special values:
65    /// - 0 = male only
66    /// - 254 = female only
67    /// - 255 = gender unknown
68    ///
69    /// Otherwise, the gender ratio is compared to a random number between 1 and 252 (inclusive).
70    /// If the random number is less than `gender_ratio`, the Mon will be female.
71    pub gender_ratio: u8,
72    /// Catch rate.
73    ///
74    /// Catch rate is represented as one byte (a value between 0 and 255).
75    pub catch_rate: u8,
76    /// Can the species be hatched from an egg?
77    #[serde(default = "default_as_true")]
78    pub can_hatch: bool,
79    /// Egg groups the species belongs to, which indicates which species can be bred together.
80    pub egg_groups: HashSet<String>,
81    /// The number of egg cycles required to hatch an egg of this species.
82    ///
83    /// One egg cycle is equal to 255 steps.
84    pub hatch_time: u8,
85    /// Height in meters (m).
86    pub height: u32,
87    /// Weight in kilograms (kg).
88    pub weight: u32,
89    /// Base experience yield when defeating this species.
90    pub base_exp_yield: u16,
91    /// Leveling rate of this species, which determines how much experience is required for
92    /// leveling up.
93    pub leveling_rate: LevelingRate,
94    /// EV (effort value) yield when defeating this species in battle.
95    pub ev_yield: StatTable,
96    /// Base friendship.
97    ///
98    /// Base friendship is represented as one byte (a value between 0 and 255).
99    pub base_friendship: u8,
100    /// Maximum HP override.
101    ///
102    /// This is used for Shedinja, which always has a maximum HP of 1.
103    pub max_hp: Option<u16>,
104    /// Base stats.
105    pub base_stats: StatTable,
106
107    /// Pre-evolution, if it exists.
108    pub prevo: Option<String>,
109    /// Evolutions.
110    #[serde(default)]
111    pub evos: Vec<String>,
112    /// Evolution data, which gives information on how the species' pre-evolution evolves into this
113    /// species.
114    pub evolution_data: Option<EvolutionData>,
115
116    /// The name of the base forme of this species, if it exists.
117    ///
118    /// For example, Giratina's base forme is "Altered".
119    pub base_forme: Option<String>,
120    /// Formes, which have distinct species data.
121    #[serde(default)]
122    pub formes: HashSet<String>,
123    /// Cosmetic formes, which have no impact on species data.
124    #[serde(default)]
125    pub cosmetic_formes: HashSet<String>,
126    /// Is this forme available only in battles?
127    #[serde(default)]
128    pub battle_only_forme: bool,
129    /// Moves required for transforming into this forme. At least one move must be known.
130    #[serde(default)]
131    pub required_moves: HashSet<String>,
132    /// Items required for transforming into this forme. At least one item must be held.
133    #[serde(default)]
134    pub required_items: HashSet<String>,
135    /// The species and forme name this forme transforms from.
136    pub changes_from: Option<String>,
137    /// The Gigantamax move, if any.
138    pub gigantamax_move: Option<String>,
139
140    /// Flags.
141    #[serde(default)]
142    pub flags: HashSet<SpeciesFlag>,
143    /// Event giveaways for this species.
144    #[serde(default)]
145    pub events: HashMap<String, EventData>,
146
147    /// Learnset, which contains all legal moves for this species.
148    ///
149    /// The learnset also defines how moves are learned by the species
150    /// ([`crate::mons::MoveSource`]).
151    #[serde(default)]
152    pub learnset: LearnSet,
153
154    /// Dynamic battle effects.
155    #[serde(default)]
156    pub effect: serde_json::Value,
157}
158
159impl SpeciesData {
160    /// The display name of the species with the forme name.
161    pub fn display_name(&self) -> String {
162        match self.forme.clone().or(self.base_forme.clone()) {
163            None => self.name.to_owned(),
164            Some(forme) => format!("{} ({})", self.base_species, forme),
165        }
166    }
167
168    /// Utility method for returning the species' two types.
169    pub fn types(&self) -> (Type, Option<Type>) {
170        (self.primary_type, self.secondary_type)
171    }
172
173    /// The base state total (BST) of the species.
174    pub fn bst(&self) -> u32 {
175        self.base_stats.sum()
176    }
177
178    /// Is the species male only?
179    pub fn male_only(&self) -> bool {
180        self.gender_ratio == 0
181    }
182
183    /// Is the species female only?
184    pub fn female_only(&self) -> bool {
185        self.gender_ratio == 254
186    }
187
188    /// Is the species genderless?
189    pub fn unknown_gender(&self) -> bool {
190        self.gender_ratio == 255
191    }
192
193    // Is the species not-fully evolved (has an evolution)?
194    pub fn not_fully_evolved(&self) -> bool {
195        !self.evos.is_empty()
196    }
197
198    /// Is the species a Mega Evolution?
199    pub fn mega(&self) -> bool {
200        match self.forme.as_ref().map(|s| s.as_str()) {
201            Some("Mega" | "Mega-X" | "Mega-Y") => true,
202            _ => false,
203        }
204    }
205
206    /// Is the species a Gigantamax?
207    pub fn gigantamax(&self) -> bool {
208        match self.forme.as_ref().map(|s| s.as_str()) {
209            Some("Gmax") => true,
210            _ => false,
211        }
212    }
213
214    /// Creates cosmetic forme data by moving and modifying this instance of [`SpeciesData`].
215    pub fn create_cosmetic_forme_data(mut self, forme: String) -> Self {
216        self.forme = Some(
217            forme
218                .strip_prefix(&format!("{}-", self.base_species))
219                .map(|s| s.to_owned())
220                .unwrap_or(forme.clone()),
221        );
222        self.name = forme;
223        self.base_forme = None;
224        self.cosmetic_formes.clear();
225        self
226    }
227}