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