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}