1use super::abilities::Abilities;
2use super::state::{PokemonVolatileStatus, Terrain, Weather};
3use crate::choices::{Choice, MoveCategory};
4use crate::choices::{Choices, MOVES};
5use crate::state::{
6 Pokemon, PokemonBoostableStat, PokemonIndex, PokemonStatus, PokemonType, Side, SideReference,
7 State,
8};
9
10#[rustfmt::skip]
11#[cfg(any(feature = "gen9",feature = "gen8",feature = "gen7",feature = "gen6"))]
12const TYPE_MATCHUP_DAMAGE_MULTIPICATION: [[f32; 19]; 19] = [
13[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0],
15[1.0, 0.5, 0.5, 1.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 0.5, 1.0, 2.0, 1.0, 1.0],
16[1.0, 2.0, 0.5, 1.0, 0.5, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 1.0, 1.0, 1.0],
17[1.0, 1.0, 2.0, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 2.0, 1.0, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0, 1.0, 1.0],
18[1.0, 0.5, 2.0, 1.0, 0.5, 1.0, 1.0, 0.5, 2.0, 0.5, 1.0, 0.5, 2.0, 1.0, 0.5, 1.0, 0.5, 1.0, 1.0],
19[1.0, 0.5, 0.5, 1.0, 2.0, 0.5, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 1.0],
20[2.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 0.5, 0.5, 0.5, 2.0, 0.0, 1.0, 2.0, 2.0, 0.5, 1.0],
21[1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 0.0, 2.0, 1.0],
22[1.0, 2.0, 1.0, 2.0, 0.5, 1.0, 1.0, 2.0, 1.0, 0.0, 1.0, 0.5, 2.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0],
23[1.0, 1.0, 1.0, 0.5, 2.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0],
24[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 0.0, 0.5, 1.0, 1.0],
25[1.0, 0.5, 1.0, 1.0, 2.0, 1.0, 0.5, 0.5, 1.0, 0.5, 2.0, 1.0, 1.0, 0.5, 1.0, 2.0, 0.5, 0.5, 1.0],
26[1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 0.5, 2.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0],
27[0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 1.0, 1.0],
28[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 0.0, 1.0],
29[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 0.5, 1.0],
30[1.0, 0.5, 0.5, 0.5, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 0.5, 2.0, 1.0],
31[1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 0.5, 1.0, 1.0],
32[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
33];
34
35#[rustfmt::skip]
36#[cfg(any(feature = "gen5",feature = "gen4", feature = "gen3"))]
37const TYPE_MATCHUP_DAMAGE_MULTIPICATION: [[f32; 19]; 19] = [
38[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.0, 1.0, 1.0, 0.5, 1.0, 1.0],
40[1.0, 0.5, 0.5, 1.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 0.5, 1.0, 2.0, 1.0, 1.0],
41[1.0, 2.0, 0.5, 1.0, 0.5, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 1.0, 1.0, 1.0],
42[1.0, 1.0, 2.0, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 2.0, 1.0, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0, 1.0, 1.0],
43[1.0, 0.5, 2.0, 1.0, 0.5, 1.0, 1.0, 0.5, 2.0, 0.5, 1.0, 0.5, 2.0, 1.0, 0.5, 1.0, 0.5, 1.0, 1.0],
44[1.0, 0.5, 0.5, 1.0, 2.0, 0.5, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 1.0],
45[2.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 1.0, 0.5, 0.5, 0.5, 2.0, 0.0, 1.0, 2.0, 2.0, 0.5, 1.0],
46[1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 1.0, 0.5, 0.5, 1.0, 1.0, 0.0, 2.0, 1.0],
47[1.0, 2.0, 1.0, 2.0, 0.5, 1.0, 1.0, 2.0, 1.0, 0.0, 1.0, 0.5, 2.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0],
48[1.0, 1.0, 1.0, 0.5, 2.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0],
49[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 0.0, 0.5, 1.0, 1.0],
50[1.0, 0.5, 1.0, 1.0, 2.0, 1.0, 0.5, 0.5, 1.0, 0.5, 2.0, 1.0, 1.0, 0.5, 1.0, 2.0, 0.5, 0.5, 1.0],
51[1.0, 2.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 0.5, 2.0, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0],
52[0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 1.0, 0.5, 0.5, 1.0, 1.0],
53[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 0.5, 0.0, 1.0],
54[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 1.0, 0.5, 0.5, 0.5, 1.0],
55[1.0, 0.5, 0.5, 0.5, 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 0.5, 2.0, 1.0],
56[1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 2.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 0.5, 1.0, 1.0],
57[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
58];
59
60#[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5"))]
61pub const CRIT_MULTIPLIER: f32 = 2.0;
62
63#[cfg(any(feature = "gen6", feature = "gen7", feature = "gen8", feature = "gen9"))]
64pub const CRIT_MULTIPLIER: f32 = 1.5;
65
66#[allow(dead_code)]
67pub enum DamageRolls {
68 Average,
69 Min,
70 Max,
71}
72
73fn type_enum_to_type_matchup_int(type_enum: &PokemonType) -> usize {
74 match type_enum {
75 PokemonType::NORMAL => 0,
76 PokemonType::FIRE => 1,
77 PokemonType::WATER => 2,
78 PokemonType::ELECTRIC => 3,
79 PokemonType::GRASS => 4,
80 PokemonType::ICE => 5,
81 PokemonType::FIGHTING => 6,
82 PokemonType::POISON => 7,
83 PokemonType::GROUND => 8,
84 PokemonType::FLYING => 9,
85 PokemonType::PSYCHIC => 10,
86 PokemonType::BUG => 11,
87 PokemonType::ROCK => 12,
88 PokemonType::GHOST => 13,
89 PokemonType::DRAGON => 14,
90 PokemonType::DARK => 15,
91 PokemonType::STEEL => 16,
92 PokemonType::FAIRY => 17,
93 PokemonType::TYPELESS => 18,
94 PokemonType::STELLAR => 18, }
96}
97
98pub fn type_effectiveness_modifier(attacking_type: &PokemonType, defender: &Pokemon) -> f32 {
99 #[cfg(not(feature = "terastallization"))]
100 let defending_types = defender.types;
101 #[cfg(feature = "terastallization")]
102 let defending_types = if defender.terastallized {
103 (defender.tera_type, PokemonType::TYPELESS)
104 } else {
105 defender.types
106 };
107 _type_effectiveness_modifier(attacking_type, &defending_types)
108}
109
110fn _type_effectiveness_modifier(
111 attacking_type: &PokemonType,
112 defending_types: &(PokemonType, PokemonType),
113) -> f32 {
114 let mut modifier = 1.0;
115 let attacking_type_index = type_enum_to_type_matchup_int(attacking_type);
116 modifier = modifier
117 * TYPE_MATCHUP_DAMAGE_MULTIPICATION[attacking_type_index]
118 [type_enum_to_type_matchup_int(&defending_types.0)];
119 modifier = modifier
120 * TYPE_MATCHUP_DAMAGE_MULTIPICATION[attacking_type_index]
121 [type_enum_to_type_matchup_int(&defending_types.1)];
122 modifier
123}
124
125fn weather_modifier(attacking_move_type: &PokemonType, weather: &Weather) -> f32 {
126 match weather {
127 Weather::SUN => match attacking_move_type {
128 PokemonType::FIRE => 1.5,
129 PokemonType::WATER => 0.5,
130 _ => 1.0,
131 },
132 Weather::RAIN => match attacking_move_type {
133 PokemonType::WATER => 1.5,
134 PokemonType::FIRE => 0.5,
135 _ => 1.0,
136 },
137 Weather::HARSHSUN => match attacking_move_type {
138 PokemonType::FIRE => 1.5,
139 PokemonType::WATER => 0.0,
140 _ => 1.0,
141 },
142 Weather::HEAVYRAIN => match attacking_move_type {
143 PokemonType::WATER => 1.5,
144 PokemonType::FIRE => 0.0,
145 _ => 1.0,
146 },
147 _ => 1.0,
148 }
149}
150
151fn stab_modifier(attacking_move_type: &PokemonType, active_pkmn: &Pokemon) -> f32 {
152 if attacking_move_type == &PokemonType::TYPELESS {
153 return 1.0;
154 }
155
156 let active_types = active_pkmn.types;
157 let move_has_basic_stab =
158 attacking_move_type == &active_types.0 || attacking_move_type == &active_types.1;
159 if active_pkmn.terastallized {
160 if &active_pkmn.tera_type == attacking_move_type && move_has_basic_stab {
161 return 2.0;
162 } else if &active_pkmn.tera_type == attacking_move_type || move_has_basic_stab {
163 return 1.5;
164 }
165 } else if move_has_basic_stab {
166 return 1.5;
167 }
168 1.0
169}
170
171fn burn_modifier(
172 attacking_move_category: &MoveCategory,
173 attacking_pokemon_status: &PokemonStatus,
174) -> f32 {
175 if attacking_pokemon_status == &PokemonStatus::BURN
176 && attacking_move_category == &MoveCategory::Physical
177 {
178 return 0.5;
179 }
180
181 1.0
182}
183
184fn terrain_modifier(
185 terrain: &Terrain,
186 attacker: &Pokemon,
187 defender: &Pokemon,
188 choice: &Choice,
189) -> f32 {
190 #[cfg(any(feature = "gen9", feature = "gen8"))]
191 let terrain_boost = 1.3;
192
193 #[cfg(not(any(feature = "gen9", feature = "gen8")))]
194 let terrain_boost = 1.5;
195
196 match terrain {
197 Terrain::ELECTRICTERRAIN => {
198 if choice.move_type == PokemonType::ELECTRIC && attacker.is_grounded() {
199 terrain_boost
200 } else {
201 1.0
202 }
203 }
204 Terrain::GRASSYTERRAIN => {
205 if choice.move_type == PokemonType::GRASS && attacker.is_grounded() {
206 terrain_boost
207 } else if choice.move_id == Choices::EARTHQUAKE {
208 0.5
209 } else {
210 1.0
211 }
212 }
213 Terrain::MISTYTERRAIN => {
214 if choice.move_type == PokemonType::DRAGON && defender.is_grounded() {
215 0.5
216 } else {
217 1.0
218 }
219 }
220 Terrain::PSYCHICTERRAIN => {
221 if choice.move_type == PokemonType::PSYCHIC && attacker.is_grounded() {
222 terrain_boost
223 } else {
224 1.0
225 }
226 }
227 Terrain::NONE => 1.0,
228 }
229}
230
231fn volatile_status_modifier(choice: &Choice, attacking_side: &Side, defending_side: &Side) -> f32 {
232 let mut modifier = 1.0;
233 for vs in attacking_side.volatile_statuses.iter() {
234 match vs {
235 PokemonVolatileStatus::FLASHFIRE if choice.move_type == PokemonType::FIRE => {
236 modifier *= 1.5;
237 }
238 PokemonVolatileStatus::SLOWSTART if choice.category == MoveCategory::Physical => {
239 modifier *= 0.5;
240 }
241 PokemonVolatileStatus::CHARGE if choice.move_type == PokemonType::ELECTRIC => {
242 modifier *= 2.0;
243 }
244 PokemonVolatileStatus::PROTOSYNTHESISATK | PokemonVolatileStatus::QUARKDRIVEATK
245 if choice.category == MoveCategory::Physical =>
246 {
247 modifier *= 1.3;
248 }
249 PokemonVolatileStatus::PROTOSYNTHESISSPA | PokemonVolatileStatus::QUARKDRIVESPA
250 if choice.category == MoveCategory::Special =>
251 {
252 modifier *= 1.3;
253 }
254 _ => {}
255 }
256 }
257
258 for vs in defending_side.volatile_statuses.iter() {
259 match vs {
260 PokemonVolatileStatus::MAGNETRISE
261 if choice.move_type == PokemonType::GROUND
262 && choice.move_id != Choices::THOUSANDARROWS =>
263 {
264 return 0.0;
265 }
266 PokemonVolatileStatus::TARSHOT if choice.move_type == PokemonType::FIRE => {
267 modifier *= 2.0;
268 }
269 PokemonVolatileStatus::PHANTOMFORCE
270 | PokemonVolatileStatus::SHADOWFORCE
271 | PokemonVolatileStatus::BOUNCE
272 | PokemonVolatileStatus::DIG
273 | PokemonVolatileStatus::DIVE
274 | PokemonVolatileStatus::FLY => {
275 return 0.0;
276 }
277 PokemonVolatileStatus::GLAIVERUSH => {
278 modifier *= 2.0;
279 }
280 PokemonVolatileStatus::PROTOSYNTHESISDEF | PokemonVolatileStatus::QUARKDRIVEDEF
281 if choice.category == MoveCategory::Physical =>
282 {
283 modifier /= 1.3;
284 }
285 PokemonVolatileStatus::PROTOSYNTHESISSPD | PokemonVolatileStatus::QUARKDRIVESPD
286 if choice.category == MoveCategory::Special =>
287 {
288 modifier /= 1.3;
289 }
290 _ => {}
291 }
292 }
293
294 modifier
295}
296
297fn get_defending_types(
298 side: &Side,
299 defending_pkmn: &Pokemon,
300 attacking_pkmn: &Pokemon,
301 attacking_choice: &Choice,
302) -> (PokemonType, PokemonType) {
303 if defending_pkmn.terastallized && !(defending_pkmn.tera_type == PokemonType::STELLAR) {
304 return (defending_pkmn.tera_type, PokemonType::TYPELESS);
305 }
306 let mut defender_types = defending_pkmn.types;
307 if side
308 .volatile_statuses
309 .contains(&PokemonVolatileStatus::ROOST)
310 {
311 if defender_types.0 == PokemonType::FLYING {
312 defender_types = (PokemonType::TYPELESS, defender_types.1);
313 }
314 if defender_types.1 == PokemonType::FLYING {
315 defender_types = (defender_types.0, PokemonType::TYPELESS);
316 }
317 }
318 if (attacking_pkmn.ability == Abilities::SCRAPPY
319 || attacking_pkmn.ability == Abilities::MINDSEYE)
320 && (attacking_choice.move_type == PokemonType::NORMAL
321 || attacking_choice.move_type == PokemonType::FIGHTING)
322 {
323 if defender_types.0 == PokemonType::GHOST {
324 defender_types = (PokemonType::TYPELESS, defender_types.1);
325 }
326 if defender_types.1 == PokemonType::GHOST {
327 defender_types = (defender_types.0, PokemonType::TYPELESS);
328 }
329 }
330 defender_types
331}
332
333fn get_attacking_and_defending_stats(
334 attacker: &Pokemon,
335 defender: &Pokemon,
336 attacking_side: &Side,
337 defending_side: &Side,
338 state: &State,
339 choice: &Choice,
340) -> (i16, i16, i16, i16) {
341 let mut should_calc_attacker_boost = true;
342 let mut should_calc_defender_boost = true;
343 let defending_stat;
344 let (
345 attacking_final_stat,
346 mut defending_final_stat,
347 mut crit_attacking_stat,
348 mut crit_defending_stat,
349 );
350
351 match choice.category {
352 MoveCategory::Physical => {
353 if attacking_side.attack_boost > 0 {
354 crit_attacking_stat =
355 attacking_side.calculate_boosted_stat(PokemonBoostableStat::Attack);
356 } else {
357 crit_attacking_stat = attacker.attack;
358 }
359 if defending_side.defense_boost <= 0 {
360 crit_defending_stat =
361 defending_side.calculate_boosted_stat(PokemonBoostableStat::Defense);
362 } else {
363 crit_defending_stat = defender.defense;
364 }
365
366 if defender.ability == Abilities::UNAWARE && attacker.ability != Abilities::MOLDBREAKER
368 {
369 should_calc_attacker_boost = false;
370 }
371 if attacker.ability == Abilities::UNAWARE && defender.ability != Abilities::MOLDBREAKER
372 {
373 should_calc_defender_boost = false;
374 }
375
376 if choice.move_id == Choices::FOULPLAY {
380 if should_calc_attacker_boost {
381 attacking_final_stat =
382 defending_side.calculate_boosted_stat(PokemonBoostableStat::Attack);
383 } else {
384 attacking_final_stat = defender.attack;
385 }
386 crit_attacking_stat = defending_side.get_active_immutable().attack;
387 } else if choice.move_id == Choices::BODYPRESS {
388 if should_calc_attacker_boost {
389 attacking_final_stat =
390 attacking_side.calculate_boosted_stat(PokemonBoostableStat::Defense);
391 } else {
392 attacking_final_stat = attacker.defense;
393 }
394 crit_attacking_stat = attacking_side.get_active_immutable().defense;
395 } else if should_calc_attacker_boost {
396 attacking_final_stat =
397 attacking_side.calculate_boosted_stat(PokemonBoostableStat::Attack);
398 } else {
399 attacking_final_stat = attacker.attack;
400 }
401
402 defending_stat = PokemonBoostableStat::Defense;
404 if should_calc_defender_boost {
405 defending_final_stat =
406 defending_side.calculate_boosted_stat(PokemonBoostableStat::Defense);
407 } else {
408 defending_final_stat = defender.defense;
409 }
410 }
411 MoveCategory::Special => {
412 if attacking_side.special_attack_boost > 0 {
413 crit_attacking_stat =
414 attacking_side.calculate_boosted_stat(PokemonBoostableStat::SpecialAttack);
415 } else {
416 crit_attacking_stat = attacker.special_attack;
417 }
418 if defending_side.special_defense_boost <= 0 {
419 crit_defending_stat =
420 defending_side.calculate_boosted_stat(PokemonBoostableStat::SpecialDefense);
421 } else {
422 crit_defending_stat = defender.special_defense;
423 }
424
425 if defender.ability == Abilities::UNAWARE && attacker.ability != Abilities::MOLDBREAKER
427 {
428 should_calc_attacker_boost = false;
429 }
430 if attacker.ability == Abilities::UNAWARE && defender.ability != Abilities::MOLDBREAKER
431 {
432 should_calc_defender_boost = false;
433 }
434
435 if should_calc_attacker_boost {
437 attacking_final_stat =
438 attacking_side.calculate_boosted_stat(PokemonBoostableStat::SpecialAttack);
439 } else {
440 attacking_final_stat = attacker.special_attack;
441 }
442
443 if choice.move_id == Choices::PSYSHOCK
446 || choice.move_id == Choices::SECRETSWORD
447 || choice.move_id == Choices::PSYSTRIKE
448 {
449 if defending_side.defense_boost <= 0 {
450 crit_defending_stat =
451 defending_side.calculate_boosted_stat(PokemonBoostableStat::Defense);
452 } else {
453 crit_defending_stat = defender.defense;
454 }
455
456 defending_stat = PokemonBoostableStat::Defense;
457 if should_calc_defender_boost {
458 defending_final_stat =
459 defending_side.calculate_boosted_stat(PokemonBoostableStat::Defense);
460 } else {
461 defending_final_stat = defender.defense;
462 }
463 } else {
464 defending_stat = PokemonBoostableStat::SpecialDefense;
465 if should_calc_defender_boost {
466 defending_final_stat =
467 defending_side.calculate_boosted_stat(PokemonBoostableStat::SpecialDefense);
468 } else {
469 defending_final_stat = defender.special_defense;
470 }
471 }
472 }
473 _ => panic!("Can only calculate damage for physical or special moves"),
474 }
475
476 #[cfg(any(
477 feature = "gen4",
478 feature = "gen5",
479 feature = "gen6",
480 feature = "gen7",
481 feature = "gen8",
482 feature = "gen9"
483 ))]
484 if state.weather_is_active(&Weather::SNOW)
485 && defender.has_type(&PokemonType::ICE)
486 && defending_stat == PokemonBoostableStat::Defense
487 {
488 defending_final_stat = (defending_final_stat as f32 * 1.5) as i16;
489 } else if state.weather_is_active(&Weather::SAND)
490 && defender.has_type(&PokemonType::ROCK)
491 && defending_stat == PokemonBoostableStat::SpecialDefense
492 {
493 defending_final_stat = (defending_final_stat as f32 * 1.5) as i16;
494 }
495
496 (
497 attacking_final_stat,
498 defending_final_stat,
499 crit_attacking_stat,
500 crit_defending_stat,
501 )
502}
503
504fn common_pkmn_damage_calc(
505 attacking_side: &Side,
506 attacker: &Pokemon,
507 attacking_stat: i16,
508 defending_side: &Side,
509 defender: &Pokemon,
510 defending_stat: i16,
511 weather: &Weather,
512 terrain: &Terrain,
513 choice: &Choice,
514) -> f32 {
515 let mut damage: f32;
516 damage = 2.0 * attacker.level as f32;
517 damage = damage.floor() / 5.0;
518 damage = damage.floor() + 2.0;
519 damage = damage.floor() * choice.base_power;
520 damage = damage * attacking_stat as f32 / defending_stat as f32;
521 damage = damage.floor() / 50.0;
522 damage = damage.floor() + 2.0;
523
524 let defender_types = get_defending_types(&defending_side, defender, attacker, choice);
525
526 let mut damage_modifier = 1.0;
527
528 if defender.terastallized && choice.move_type == PokemonType::STELLAR {
529 damage_modifier *= 2.0;
530 } else {
531 damage_modifier *= _type_effectiveness_modifier(&choice.move_type, &defender_types);
532 }
533
534 if attacker.ability != Abilities::CLOUDNINE
535 && attacker.ability != Abilities::AIRLOCK
536 && defender.ability != Abilities::CLOUDNINE
537 && defender.ability != Abilities::AIRLOCK
538 {
539 damage_modifier *= weather_modifier(&choice.move_type, weather);
540 }
541
542 damage_modifier *= stab_modifier(&choice.move_type, &attacker);
543 damage_modifier *= burn_modifier(&choice.category, &attacker.status);
544 damage_modifier *= volatile_status_modifier(&choice, attacking_side, defending_side);
545 damage_modifier *= terrain_modifier(terrain, attacker, defender, &choice);
546
547 damage * damage_modifier
548}
549
550pub fn calculate_damage(
556 state: &State,
557 attacking_side: &SideReference,
558 choice: &Choice,
559 _damage_rolls: DamageRolls,
560) -> Option<(i16, i16)> {
561 if choice.category == MoveCategory::Status || choice.category == MoveCategory::Switch {
562 return None;
563 } else if choice.base_power == 0.0 {
564 return Some((0, 0));
565 }
566 let (attacking_side, defending_side) = state.get_both_sides_immutable(attacking_side);
567 let attacker = attacking_side.get_active_immutable();
568 let defender = defending_side.get_active_immutable();
569 let (attacking_stat, defending_stat, crit_attacking_stat, crit_defending_stat) =
570 get_attacking_and_defending_stats(
571 attacker,
572 defender,
573 attacking_side,
574 defending_side,
575 state,
576 &choice,
577 );
578
579 let mut damage = common_pkmn_damage_calc(
580 attacking_side,
581 attacker,
582 attacking_stat,
583 defending_side,
584 defender,
585 defending_stat,
586 &state.weather.weather_type,
587 &state.terrain.terrain_type,
588 choice,
589 );
590 if attacker.ability != Abilities::INFILTRATOR {
591 if defending_side.side_conditions.aurora_veil > 0 {
592 damage *= 0.5
593 } else if defending_side.side_conditions.reflect > 0
594 && choice.category == MoveCategory::Physical
595 {
596 damage *= 0.5
597 } else if defending_side.side_conditions.light_screen > 0
598 && choice.category == MoveCategory::Special
599 {
600 damage *= 0.5
601 }
602 }
603
604 let mut crit_damage = common_pkmn_damage_calc(
605 attacking_side,
606 attacker,
607 crit_attacking_stat,
608 defending_side,
609 defender,
610 crit_defending_stat,
611 &state.weather.weather_type,
612 &state.terrain.terrain_type,
613 choice,
614 );
615 crit_damage *= CRIT_MULTIPLIER;
616
617 match _damage_rolls {
618 DamageRolls::Average => {
619 damage = damage.floor() * 0.925;
620 crit_damage = crit_damage.floor() * 0.925;
621 }
622 DamageRolls::Min => {
623 damage = damage.floor() * 0.85;
624 crit_damage = crit_damage.floor() * 0.85;
625 }
626 DamageRolls::Max => {
627 damage = damage.floor();
628 crit_damage = crit_damage.floor();
629 }
630 }
631
632 Some((damage as i16, crit_damage as i16))
633}
634
635pub fn calculate_futuresight_damage(
636 attacking_side: &Side,
637 defending_side: &Side,
638 attacking_side_pokemon_index: &PokemonIndex,
639) -> i16 {
640 let attacking_stat = attacking_side.pokemon[attacking_side_pokemon_index].special_attack;
641 let defending_stat = defending_side.get_active_immutable().special_defense;
642 let attacker = attacking_side.get_active_immutable();
643 let mut damage = common_pkmn_damage_calc(
644 attacking_side,
645 attacker,
646 attacking_stat,
647 defending_side,
648 defending_side.get_active_immutable(),
649 defending_stat,
650 &Weather::NONE,
651 &Terrain::NONE,
652 MOVES.get(&Choices::FUTURESIGHT).unwrap(),
653 );
654 if attacker.ability != Abilities::INFILTRATOR {
655 if defending_side.side_conditions.light_screen > 0 {
656 damage *= 0.5
657 }
658 }
659
660 (damage * 0.925) as i16
661}
662
663#[cfg(test)]
664mod tests {
665 use std::collections::HashSet;
666 use std::iter::FromIterator;
667
668 use super::super::state::{PokemonVolatileStatus, Weather};
669 use super::*;
670 use crate::state::{PokemonStatus, PokemonType, SideReference, State};
671
672 #[test]
673 fn test_basic_damaging_move() {
674 let state = State::default();
675 let mut choice = Choice {
676 ..Default::default()
677 };
678 choice.move_id = Choices::TACKLE;
679 choice.move_type = PokemonType::TYPELESS;
680 choice.base_power = 40.0;
681 choice.category = MoveCategory::Physical;
682
683 let dmg = calculate_damage(
684 &state,
685 &SideReference::SideOne,
686 &choice,
687 DamageRolls::Average,
688 );
689
690 assert_eq!(32, dmg.unwrap().0);
692 }
693
694 #[test]
695 fn test_basic_non_damaging_move() {
696 let state = State::default();
697 let mut choice = Choice {
698 ..Default::default()
699 };
700 choice.move_id = Choices::PROTECT;
701 choice.category = MoveCategory::Status;
702
703 let dmg = calculate_damage(
704 &state,
705 &SideReference::SideOne,
706 &choice,
707 DamageRolls::Average,
708 );
709
710 assert_eq!(None, dmg);
711 }
712
713 #[test]
714 fn test_move_with_zero_base_power() {
715 let state = State::default();
716 let mut choice = Choice {
717 ..Default::default()
718 };
719 choice.move_id = Choices::TACKLE;
720 choice.move_type = PokemonType::TYPELESS;
721 choice.base_power = 0.0;
722 choice.category = MoveCategory::Physical;
723
724 let dmg = calculate_damage(
725 &state,
726 &SideReference::SideOne,
727 &choice,
728 DamageRolls::Average,
729 );
730
731 assert_eq!(0, dmg.unwrap().0);
732 }
733
734 #[test]
735 fn test_boosted_damaging_move() {
736 let mut state = State::default();
737 let mut choice = Choice {
738 ..Default::default()
739 };
740 state.side_one.attack_boost = 1;
741 choice.move_id = Choices::TACKLE;
742 choice.move_type = PokemonType::TYPELESS;
743 choice.base_power = 40.0;
744 choice.category = MoveCategory::Physical;
745
746 let dmg = calculate_damage(
747 &state,
748 &SideReference::SideOne,
749 &choice,
750 DamageRolls::Average,
751 );
752
753 assert_eq!(48, dmg.unwrap().0);
754 }
755
756 #[test]
757 fn test_unaware_does_not_get_damaged_by_boosted_stats() {
758 let mut state = State::default();
759 let mut choice = Choice {
760 ..Default::default()
761 };
762 state.side_one.attack_boost = 1;
763 state.side_two.get_active().ability = Abilities::UNAWARE;
764 choice.move_id = Choices::TACKLE;
765 choice.move_type = PokemonType::TYPELESS;
766 choice.base_power = 40.0;
767 choice.category = MoveCategory::Physical;
768
769 let dmg = calculate_damage(
770 &state,
771 &SideReference::SideOne,
772 &choice,
773 DamageRolls::Average,
774 );
775
776 assert_eq!(32, dmg.unwrap().0);
777 }
778
779 #[test]
780 fn test_unaware_does_get_damaged_by_boosted_stats_if_attacker_has_moldbreaker() {
781 let mut state = State::default();
782 let mut choice = Choice {
783 ..Default::default()
784 };
785 state.side_one.attack_boost = 1;
786 state.side_two.get_active().ability = Abilities::UNAWARE;
787 state.side_one.get_active().ability = Abilities::MOLDBREAKER;
788 choice.move_id = Choices::TACKLE;
789 choice.move_type = PokemonType::TYPELESS;
790 choice.base_power = 40.0;
791 choice.category = MoveCategory::Physical;
792
793 let dmg = calculate_damage(
794 &state,
795 &SideReference::SideOne,
796 &choice,
797 DamageRolls::Average,
798 );
799
800 assert_eq!(48, dmg.unwrap().0);
801 }
802
803 #[test]
804 fn test_basic_super_effective_move() {
805 let mut state = State::default();
806 let mut choice = Choice {
807 ..Default::default()
808 };
809
810 state.side_two.get_active().types = (PokemonType::FIRE, PokemonType::TYPELESS);
811 choice.move_id = Choices::WATERGUN;
812 choice.move_type = PokemonType::WATER;
813 choice.base_power = 40.0;
814 choice.category = MoveCategory::Special;
815 let dmg = calculate_damage(
816 &state,
817 &SideReference::SideOne,
818 &choice,
819 DamageRolls::Average,
820 );
821
822 assert_eq!(64, dmg.unwrap().0);
823 }
824
825 #[test]
826 fn test_basic_not_very_effective_move() {
827 let mut state = State::default();
828 let mut choice = Choice {
829 ..Default::default()
830 };
831
832 state.side_two.get_active().types = (PokemonType::WATER, PokemonType::TYPELESS);
833 choice.move_id = Choices::WATERGUN;
834 choice.move_type = PokemonType::WATER;
835 choice.base_power = 40.0;
836 choice.category = MoveCategory::Special;
837 let dmg = calculate_damage(
838 &state,
839 &SideReference::SideOne,
840 &choice,
841 DamageRolls::Average,
842 );
843
844 assert_eq!(15, dmg.unwrap().0);
845 }
846
847 macro_rules! weather_tests {
848 ($($name:ident: $value:expr,)*) => {
849 $(
850 #[test]
851 fn $name() {
852 let (weather_type, move_type, expected_damage_amount) = $value;
853 let mut state = State::default();
854 let mut choice = Choice {
855 ..Default::default()
856 };
857 state.weather.weather_type = weather_type;
858
859 choice.move_type = move_type;
860 choice.base_power = 40.0;
861 choice.category = MoveCategory::Special;
862 let dmg = calculate_damage(&state, &SideReference::SideOne, &choice, DamageRolls::Average);
863
864 assert_eq!(expected_damage_amount, dmg.unwrap().0);
865 }
866 )*
867 }
868 }
869 weather_tests! {
870 test_rain_boosting_water: (Weather::RAIN, PokemonType::WATER, 48),
871 test_rain_not_boosting_normal: (Weather::RAIN, PokemonType::NORMAL, 48),
872 test_sun_boosting_fire: (Weather::SUN, PokemonType::FIRE, 48),
873 test_sun_reducing_water: (Weather::SUN, PokemonType::WATER, 15),
874 test_sun_not_boosting_normal: (Weather::SUN, PokemonType::NORMAL, 48),
875 test_heavy_rain_makes_fire_do_zero: (Weather::HEAVYRAIN, PokemonType::FIRE, 0),
876 test_heavy_rain_boost_water: (Weather::HEAVYRAIN, PokemonType::WATER, 48),
877 test_harsh_sun_makes_water_do_zero: (Weather::HARSHSUN, PokemonType::WATER, 0),
878 test_harsh_sun_boosting_fire: (Weather::HARSHSUN, PokemonType::FIRE, 48),
879 }
880
881 macro_rules! stab_tests {
882 ($($name:ident: $value:expr,)*) => {
883 $(
884 #[test]
885 fn $name() {
886 let (attacker_types, attacking_move_type, expected_damage_amount) = $value;
887 let mut state = State::default();
888 let mut choice = Choice {
889 ..Default::default()
890 };
891 state.side_one.get_active().types = attacker_types;
892
893 choice.move_type = attacking_move_type;
894 choice.base_power = 40.0;
895 choice.category = MoveCategory::Special;
896 let dmg = calculate_damage(&state, &SideReference::SideOne, &choice, DamageRolls::Average);
897
898 assert_eq!(expected_damage_amount, dmg.unwrap().0);
899 }
900 )*
901 }
902 }
903 stab_tests! {
904 test_basic_stab: ((PokemonType::WATER, PokemonType::FIRE), PokemonType::WATER, 48),
905 test_basic_without_stab: ((PokemonType::WATER, PokemonType::FIRE), PokemonType::NORMAL, 32),
906 }
907
908 macro_rules! burn_tests {
909 ($($name:ident: $value:expr,)*) => {
910 $(
911 #[test]
912 fn $name() {
913 let (attacking_move_category, expected_damage_amount) = $value;
914 let mut state = State::default();
915 let mut choice = Choice {
916 ..Default::default()
917 };
918 state.side_one.get_active().status = PokemonStatus::BURN;
919
920 choice.category = attacking_move_category;
921 choice.move_type = PokemonType::TYPELESS;
922 choice.base_power = 40.0;
923 let dmg = calculate_damage(&state, &SideReference::SideOne, &choice, DamageRolls::Average);
924
925 assert_eq!(expected_damage_amount, dmg.unwrap().0);
926 }
927 )*
928 }
929 }
930 burn_tests! {
931 test_physical_move_when_burned_reduces: (MoveCategory::Physical, 15),
932 test_special_move_when_burned_does_not_reduce: (MoveCategory::Special, 32),
933 }
934
935 macro_rules! screens_tests {
936 ($($name:ident: $value:expr,)*) => {
937 $(
938 #[test]
939 fn $name() {
940 let (reflect_count, lightscreen_count, auroraveil_count, move_category, expected_damage_amount) = $value;
941 let mut state = State::default();
942 let mut choice = Choice {
943 ..Default::default()
944 };
945 state.side_two.side_conditions.reflect = reflect_count;
946 state.side_two.side_conditions.light_screen = lightscreen_count;
947 state.side_two.side_conditions.aurora_veil = auroraveil_count;
948
949 choice.category = move_category;
950 choice.base_power = 40.0;
951 choice.move_type = PokemonType::TYPELESS;
952 let dmg = calculate_damage(&state, &SideReference::SideOne, &choice, DamageRolls::Average);
953
954 assert_eq!(expected_damage_amount, dmg.unwrap().0);
955 }
956 )*
957 }
958 }
959 screens_tests! {
960 test_reflect_reduces_physical_damage_by_half: (1, 0, 0, MoveCategory::Physical, 15),
961 test_lightscreen_reduces_special_damage_by_half: (0, 1, 0, MoveCategory::Special, 15),
962 test_auroraveil_reduces_physical_damage_by_half: (0, 0, 1, MoveCategory::Physical, 15),
963 test_auroraveil_reduces_special_damage_by_half: (0, 0, 1, MoveCategory::Special, 15),
964 test_reflect_does_not_reduce_special_damage: (1, 0, 0, MoveCategory::Special, 32),
965 test_light_screen_does_not_reduce_physical_damage: (0, 1, 0, MoveCategory::Physical, 32),
966 test_auroraveil_does_not_stack_with_reflect: (1, 1, 1, MoveCategory::Physical, 15),
967 test_auroraveil_does_not_stack_with_lightscreen: (1, 1, 1, MoveCategory::Special, 15),
968 }
969
970 macro_rules! volatile_status_tests{
971 ($($name:ident: $value:expr,)*) => {
972 $(
973 #[test]
974 fn $name() {
975 let (attacking_volatile_status, defending_volatile_status, move_type, move_name, expected_damage_amount) = $value;
976 let mut state = State::default();
977 let mut choice = Choice {
978 ..Default::default()
979 };
980 state.side_one.volatile_statuses = HashSet::from_iter(attacking_volatile_status);
981 state.side_two.volatile_statuses = HashSet::from_iter(defending_volatile_status);
982
983 choice.move_id = move_name;
984 choice.category = MoveCategory::Physical;
985 choice.move_type = move_type;
986 choice.base_power = 40.0;
987 let dmg = calculate_damage(&state, &SideReference::SideOne, &choice, DamageRolls::Average);
988
989 assert_eq!(expected_damage_amount, dmg.unwrap().0);
990 }
991 )*
992 }
993 }
994 volatile_status_tests! {
995 test_flashfire_boosts_fire_move: (
996 vec![PokemonVolatileStatus::FLASHFIRE],
997 vec![],
998 PokemonType::FIRE,
999 Choices::NONE,
1000 48
1001 ),
1002 test_flashfire_does_not_boost_normal_move: (
1003 vec![PokemonVolatileStatus::FLASHFIRE],
1004 vec![],
1005 PokemonType::TYPELESS,
1006 Choices::NONE,
1007 32
1008 ),
1009 test_magnetrise_makes_pkmn_immune_to_ground_move: (
1010 vec![],
1011 vec![PokemonVolatileStatus::MAGNETRISE],
1012 PokemonType::GROUND,
1013 Choices::NONE,
1014 0
1015 ),
1016 test_thousandarrows_can_hit_magnetrise_pokemon: (
1017 vec![],
1018 vec![PokemonVolatileStatus::MAGNETRISE],
1019 PokemonType::GROUND,
1020 Choices::THOUSANDARROWS,
1021 32
1022 ),
1023 test_tarshot_boosts_fire_move: (
1024 vec![],
1025 vec![PokemonVolatileStatus::TARSHOT],
1026 PokemonType::FIRE,
1027 Choices::NONE,
1028 64
1029 ),
1030 test_slowstart_halves_move: (
1031 vec![PokemonVolatileStatus::SLOWSTART],
1032 vec![],
1033 PokemonType::NORMAL,
1034 Choices::NONE,
1035 24
1036 ),
1037 test_tarshot_and_flashfire_together: (
1038 vec![PokemonVolatileStatus::FLASHFIRE],
1039 vec![PokemonVolatileStatus::TARSHOT],
1040 PokemonType::FIRE,
1041 Choices::NONE,
1042 97
1043 ),
1044 test_glaiverush_doubles_damage_against: (
1045 vec![],
1046 vec![PokemonVolatileStatus::GLAIVERUSH],
1047 PokemonType::NORMAL,
1048 Choices::NONE,
1049 97
1050 ),
1051 test_phantomforce_on_defender_causes_0_damage: (
1052 vec![],
1053 vec![PokemonVolatileStatus::PHANTOMFORCE],
1054 PokemonType::NORMAL,
1055 Choices::NONE,
1056 0
1057 ),
1058 }
1059}