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