1use super::abilities::Abilities;
2use super::damage_calc::type_effectiveness_modifier;
3use super::generate_instructions::{add_remove_status_instructions, apply_boost_instruction};
4use super::items::{get_choice_move_disable_instructions, Items};
5use super::state::{PokemonVolatileStatus, Terrain, Weather};
6use crate::choices::{
7 Boost, Choice, Choices, Effect, Heal, MoveCategory, MoveTarget, Secondary, StatBoosts,
8};
9use crate::instruction::{
10 ApplyVolatileStatusInstruction, BoostInstruction, ChangeItemInstruction,
11 ChangeSideConditionInstruction, ChangeStatusInstruction, ChangeSubsituteHealthInstruction,
12 ChangeTerrain, ChangeType, ChangeWeather, ChangeWishInstruction, DamageInstruction,
13 HealInstruction, Instruction, RemoveVolatileStatusInstruction, SetFutureSightInstruction,
14 SetSleepTurnsInstruction, StateInstructions, ToggleTrickRoomInstruction,
15};
16use crate::pokemon::PokemonName;
17use crate::state::{
18 pokemon_index_iter, LastUsedMove, PokemonBoostableStat, PokemonSideCondition, PokemonStatus,
19 PokemonType, Side, SideReference, State,
20};
21use std::cmp;
22
23const CHOICE_THAWS_USER: [Choices; 10] = [
24 Choices::FLAMEWHEEL,
25 Choices::SACREDFIRE,
26 Choices::FLAREBLITZ,
27 Choices::FUSIONFLARE,
28 Choices::SCALD,
29 Choices::STEAMERUPTION,
30 Choices::BURNUP,
31 Choices::PYROBALL,
32 Choices::SCORCHINGSANDS,
33 Choices::MATCHAGOTCHA,
34];
35
36pub fn modify_choice(
37 state: &State,
38 attacker_choice: &mut Choice,
39 defender_choice: &Choice,
40 attacking_side_ref: &SideReference,
41) {
42 let (attacking_side, defending_side) = state.get_both_sides_immutable(attacking_side_ref);
43 match attacker_choice.move_id {
44 Choices::ROOST => {
45 let attacker = attacking_side.get_active_immutable();
46 if attacker.hp == attacker.maxhp {
47 attacker_choice.volatile_status = None
48 }
49 }
50 Choices::ELECTROSHOT => {
51 if state.weather_is_active(&Weather::RAIN) {
52 attacker_choice.flags.charge = false;
53 }
54 }
55 Choices::DIRECLAW => {
56 attacker_choice.add_or_create_secondaries(Secondary {
59 chance: 16.67,
60 target: MoveTarget::Opponent,
61 effect: Effect::Status(PokemonStatus::POISON),
62 });
63 attacker_choice.add_or_create_secondaries(Secondary {
64 chance: 20.00,
65 target: MoveTarget::Opponent,
66 effect: Effect::Status(PokemonStatus::PARALYZE),
67 });
68 attacker_choice.add_or_create_secondaries(Secondary {
69 chance: 25.0,
70 target: MoveTarget::Opponent,
71 effect: Effect::Status(PokemonStatus::SLEEP),
72 });
73 }
74 Choices::DOUBLESHOCK => {
75 if !attacking_side
76 .get_active_immutable()
77 .has_type(&PokemonType::ELECTRIC)
78 {
79 attacker_choice.remove_all_effects();
80 }
81 }
82 Choices::BURNUP => {
83 if !attacking_side
84 .get_active_immutable()
85 .has_type(&PokemonType::FIRE)
86 {
87 attacker_choice.remove_all_effects();
88 }
89 }
90 Choices::REVERSAL => {
91 let attacker = attacking_side.get_active_immutable();
92 let hp_ratio = attacker.hp as f32 / attacker.maxhp as f32;
93 if hp_ratio >= 0.688 {
94 attacker_choice.base_power = 20.0;
95 } else if hp_ratio >= 0.354 {
96 attacker_choice.base_power = 40.0;
97 } else if hp_ratio >= 0.208 {
98 attacker_choice.base_power = 80.0;
99 } else if hp_ratio >= 0.104 {
100 attacker_choice.base_power = 100.0;
101 } else if hp_ratio >= 0.042 {
102 attacker_choice.base_power = 150.0;
103 } else {
104 attacker_choice.base_power = 200.0;
105 }
106 }
107 Choices::AURAWHEEL => {
108 if attacking_side.get_active_immutable().id == PokemonName::MORPEKOHANGRY {
109 attacker_choice.move_type = PokemonType::DARK;
110 }
111 }
112 Choices::IVYCUDGEL => {
113 let attacker = attacking_side.get_active_immutable();
114 match attacker.item {
115 Items::WELLSPRINGMASK => {
116 attacker_choice.move_type = PokemonType::WATER;
117 }
118 Items::HEARTHFLAMEMASK => {
119 attacker_choice.move_type = PokemonType::FIRE;
120 }
121 Items::CORNERSTONEMASK => {
122 attacker_choice.move_type = PokemonType::ROCK;
123 }
124 _ => {}
125 }
126 }
127 Choices::RAGINGBULL => {
128 if defending_side.side_conditions.reflect > 0
132 || defending_side.side_conditions.aurora_veil > 0
133 {
134 attacker_choice.base_power *= 2.0;
135 }
136 match attacking_side.get_active_immutable().id {
137 PokemonName::TAUROSPALDEACOMBAT => {
138 attacker_choice.move_type = PokemonType::FIGHTING;
139 }
140 PokemonName::TAUROSPALDEABLAZE => {
141 attacker_choice.move_type = PokemonType::FIRE;
142 }
143 PokemonName::TAUROSPALDEAAQUA => {
144 attacker_choice.move_type = PokemonType::WATER;
145 }
146 _ => {}
147 }
148 }
149 Choices::BOLTBEAK | Choices::FISHIOUSREND => {
150 if attacker_choice.first_move {
151 attacker_choice.base_power *= 2.0;
152 }
153 }
154 Choices::HARDPRESS => {
155 let defender = defending_side.get_active_immutable();
156 attacker_choice.base_power = 100.0 * (defender.hp as f32 / defender.maxhp as f32);
157 }
158 Choices::LASTRESPECTS => {
159 let mut bp = 50.0;
161 for pkmn in attacking_side.pokemon.into_iter() {
162 if pkmn.hp == 0 {
163 bp += 50.0;
164 }
165 }
166 attacker_choice.base_power = bp
167 }
168 Choices::CLANGOROUSSOUL => {
169 let attacker = attacking_side.get_active_immutable();
170 if attacker.hp > attacker.maxhp / 3 {
171 attacker_choice.heal = Some(Heal {
172 target: MoveTarget::User,
173 amount: -0.33,
174 });
175 attacker_choice.boost = Some(Boost {
176 target: MoveTarget::User,
177 boosts: StatBoosts {
178 attack: 1,
179 defense: 1,
180 special_attack: 1,
181 special_defense: 1,
182 speed: 1,
183 accuracy: 0,
184 },
185 });
186 }
187 }
188 Choices::EXPANDINGFORCE => {
189 if state.terrain.terrain_type == Terrain::PSYCHICTERRAIN {
190 attacker_choice.base_power *= 1.5;
191 }
192 }
193 Choices::FILLETAWAY => {
194 let attacker = attacking_side.get_active_immutable();
195 if attacker.hp > attacker.maxhp / 2 {
196 attacker_choice.heal = Some(Heal {
197 target: MoveTarget::User,
198 amount: -0.5,
199 });
200 attacker_choice.boost = Some(Boost {
201 target: MoveTarget::User,
202 boosts: StatBoosts {
203 attack: 2,
204 defense: 0,
205 special_attack: 2,
206 special_defense: 0,
207 speed: 2,
208 accuracy: 0,
209 },
210 });
211 }
212 }
213 Choices::FAKEOUT | Choices::FIRSTIMPRESSION => match attacking_side.last_used_move {
214 LastUsedMove::Move(_) => attacker_choice.remove_all_effects(),
215 _ => {}
216 },
217 Choices::GROWTH => {
218 if state.weather_is_active(&Weather::SUN) {
219 attacker_choice.boost = Some(Boost {
220 target: MoveTarget::User,
221 boosts: StatBoosts {
222 attack: 2,
223 defense: 0,
224 special_attack: 2,
225 special_defense: 0,
226 speed: 0,
227 accuracy: 0,
228 },
229 });
230 }
231 }
232 Choices::HEX => {
233 if defending_side.get_active_immutable().status != PokemonStatus::NONE {
234 attacker_choice.base_power *= 2.0;
235 }
236 }
237 Choices::HYDROSTEAM => {
238 if state.weather_is_active(&Weather::SUN) {
239 attacker_choice.base_power *= 3.0; }
241 }
242 Choices::JUDGMENT => {
243 attacker_choice.move_type = attacking_side.get_active_immutable().types.0;
244 }
245 Choices::MULTIATTACK => {
246 attacker_choice.move_type = attacking_side.get_active_immutable().types.0;
247 }
248 Choices::MISTYEXPLOSION => {
249 if state.terrain.terrain_type == Terrain::MISTYTERRAIN {
250 attacker_choice.base_power *= 1.5;
251 }
252 }
253 #[cfg(any(feature = "gen3", feature = "gen4"))]
254 Choices::EXPLOSION | Choices::SELFDESTRUCT => {
255 attacker_choice.base_power *= 2.0;
256 }
257
258 Choices::MORNINGSUN | Choices::MOONLIGHT | Choices::SYNTHESIS => {
259 match state.weather.weather_type {
260 Weather::SUN => {
261 attacker_choice.heal = Some(Heal {
262 target: MoveTarget::User,
263 amount: 0.667,
264 })
265 }
266 Weather::NONE => {}
267 _ => {
268 attacker_choice.heal = Some(Heal {
269 target: MoveTarget::User,
270 amount: 0.25,
271 })
272 }
273 }
274 }
275 Choices::NORETREAT => {
276 if attacking_side
277 .volatile_statuses
278 .contains(&PokemonVolatileStatus::NORETREAT)
279 {
280 attacker_choice.boost = None;
281 }
282 }
283 Choices::POLTERGEIST => {
284 if defending_side.get_active_immutable().item == Items::NONE {
285 attacker_choice.base_power = 0.0;
286 }
287 }
288 Choices::PSYBLADE => {
289 if state.terrain.terrain_type == Terrain::ELECTRICTERRAIN {
290 attacker_choice.base_power *= 1.5;
291 }
292 }
293 Choices::PURSUIT => {
294 if defender_choice.category == MoveCategory::Switch {
295 attacker_choice.base_power *= 2.0;
296 }
297 }
298 Choices::REVELATIONDANCE => {
299 if attacking_side.get_active_immutable().terastallized {
300 attacker_choice.move_type = attacking_side.get_active_immutable().tera_type;
301 } else {
302 attacker_choice.move_type = attacking_side.get_active_immutable().types.0;
303 }
304 }
305 Choices::RISINGVOLTAGE => {
306 if state.terrain.terrain_type == Terrain::ELECTRICTERRAIN {
307 attacker_choice.base_power *= 1.5;
308 }
309 }
310 Choices::SHOREUP => {
311 if state.weather_is_active(&Weather::SAND) {
312 attacker_choice.heal = Some(Heal {
313 target: MoveTarget::User,
314 amount: 0.667,
315 });
316 }
317 }
318 Choices::STEELROLLER => {
319 if state.terrain.terrain_type == Terrain::NONE {
320 attacker_choice.base_power = 0.0;
321 }
322 }
323 Choices::STRENGTHSAP => {
324 attacker_choice.boost = Some(Boost {
325 target: MoveTarget::Opponent,
326 boosts: StatBoosts {
327 attack: -1,
328 defense: 0,
329 special_attack: 0,
330 special_defense: 0,
331 speed: 0,
332 accuracy: 0,
333 },
334 });
335 if defending_side.attack_boost != -6 {
336 let defender_attack =
337 defending_side.calculate_boosted_stat(PokemonBoostableStat::Attack);
338 let attacker_maxhp = attacking_side.get_active_immutable().maxhp;
339
340 if defending_side.get_active_immutable().ability == Abilities::LIQUIDOOZE {
341 attacker_choice.heal = Some(Heal {
342 target: MoveTarget::User,
343 amount: -1.0 * defender_attack as f32 / attacker_maxhp as f32,
344 });
345 } else {
346 attacker_choice.heal = Some(Heal {
347 target: MoveTarget::User,
348 amount: defender_attack as f32 / attacker_maxhp as f32,
349 });
350 }
351 }
352 }
353 Choices::TERABLAST => {
354 let active = attacking_side.get_active_immutable();
355 if active.terastallized {
356 attacker_choice.move_type = active.tera_type;
357 if attacking_side.calculate_boosted_stat(PokemonBoostableStat::Attack)
358 > attacking_side.calculate_boosted_stat(PokemonBoostableStat::SpecialAttack)
359 {
360 attacker_choice.category = MoveCategory::Physical;
361 }
362 if active.tera_type == PokemonType::STELLAR {
363 attacker_choice.add_or_create_secondaries(Secondary {
364 chance: 100.0,
365 target: MoveTarget::User,
366 effect: Effect::Boost(StatBoosts {
367 attack: -1,
368 defense: 0,
369 special_attack: -1,
370 special_defense: 0,
371 speed: 0,
372 accuracy: 0,
373 }),
374 })
375 }
376 }
377 }
378 Choices::PHOTONGEYSER => {
379 if attacking_side.calculate_boosted_stat(PokemonBoostableStat::Attack)
380 > attacking_side.calculate_boosted_stat(PokemonBoostableStat::SpecialAttack)
381 {
382 attacker_choice.category = MoveCategory::Physical;
383 }
384 }
385 Choices::TERRAINPULSE => match state.terrain.terrain_type {
386 Terrain::ELECTRICTERRAIN => {
387 attacker_choice.move_type = PokemonType::ELECTRIC;
388 attacker_choice.base_power *= 2.0;
389 }
390 Terrain::GRASSYTERRAIN => {
391 attacker_choice.move_type = PokemonType::GRASS;
392 attacker_choice.base_power *= 2.0;
393 }
394 Terrain::MISTYTERRAIN => {
395 attacker_choice.move_type = PokemonType::FAIRY;
396 attacker_choice.base_power *= 2.0;
397 }
398 Terrain::PSYCHICTERRAIN => {
399 attacker_choice.move_type = PokemonType::PSYCHIC;
400 attacker_choice.base_power *= 2.0;
401 }
402 Terrain::NONE => {}
403 },
404 Choices::TOXIC => {
405 if attacking_side
406 .get_active_immutable()
407 .has_type(&PokemonType::POISON)
408 {
409 attacker_choice.accuracy = 100.0;
410 }
411 }
412 Choices::WEATHERBALL => match state.weather.weather_type {
413 Weather::SUN | Weather::HARSHSUN => {
414 attacker_choice.base_power = 100.0;
415 attacker_choice.move_type = PokemonType::FIRE;
416 }
417 Weather::RAIN | Weather::HEAVYRAIN => {
418 attacker_choice.base_power = 100.0;
419 attacker_choice.move_type = PokemonType::WATER;
420 }
421 Weather::SAND => {
422 attacker_choice.base_power = 100.0;
423 attacker_choice.move_type = PokemonType::ROCK;
424 }
425 Weather::HAIL | Weather::SNOW => {
426 attacker_choice.base_power = 100.0;
427 attacker_choice.move_type = PokemonType::ICE;
428 }
429 Weather::NONE => {}
430 },
431 Choices::SOLARBEAM | Choices::SOLARBLADE => {
432 if state.weather_is_active(&Weather::SUN) || state.weather_is_active(&Weather::HARSHSUN)
433 {
434 attacker_choice.flags.charge = false;
435 } else if !state.weather_is_active(&Weather::SUN)
436 && state.weather.weather_type != Weather::NONE
437 {
438 attacker_choice.base_power /= 2.0;
439 }
440 }
441 Choices::BLIZZARD => {
442 if state.weather_is_active(&Weather::HAIL) {
443 attacker_choice.accuracy = 100.0;
444 }
445 }
446 Choices::HURRICANE | Choices::THUNDER => {
447 if state.weather_is_active(&Weather::RAIN)
448 || state.weather_is_active(&Weather::HEAVYRAIN)
449 {
450 attacker_choice.accuracy = 100.0;
451 } else if state.weather_is_active(&Weather::SUN)
452 || state.weather_is_active(&Weather::HARSHSUN)
453 {
454 attacker_choice.accuracy = 50.0;
455 }
456 }
457
458 #[cfg(any(feature = "gen6", feature = "gen7", feature = "gen8", feature = "gen9"))]
459 Choices::KNOCKOFF => {
460 let defender = defending_side.get_active_immutable();
462 if !defender.item_is_permanent() && defender.item != Items::NONE {
463 attacker_choice.base_power *= 1.5;
464 }
465 }
466
467 Choices::ACROBATICS => {
468 if attacking_side.get_active_immutable().item == Items::NONE {
469 attacker_choice.base_power *= 2.0;
470 }
471 }
472 Choices::FOCUSPUNCH => {
473 if (defending_side.damage_dealt.move_category == MoveCategory::Physical
474 || defending_side.damage_dealt.move_category == MoveCategory::Special)
475 && !defending_side.damage_dealt.hit_substitute
476 && defending_side.damage_dealt.damage > 0
477 {
478 attacker_choice.remove_all_effects();
479 }
480 }
481 Choices::ELECTROBALL => {
482 let attacker_speed = attacking_side.calculate_boosted_stat(PokemonBoostableStat::Speed);
483 let defender_speed = defending_side.calculate_boosted_stat(PokemonBoostableStat::Speed);
484 let speed_ratio = attacker_speed as f32 / defender_speed as f32;
485 if speed_ratio >= 4.0 {
486 attacker_choice.base_power = 150.0;
487 } else if speed_ratio >= 3.0 {
488 attacker_choice.base_power = 120.0;
489 } else if speed_ratio >= 2.0 {
490 attacker_choice.base_power = 80.0;
491 } else if speed_ratio >= 1.0 {
492 attacker_choice.base_power = 60.0;
493 } else {
494 attacker_choice.base_power = 40.0;
495 }
496 }
497 Choices::GYROBALL => {
498 let attacker_speed = attacking_side.calculate_boosted_stat(PokemonBoostableStat::Speed);
499 let defender_speed = defending_side.calculate_boosted_stat(PokemonBoostableStat::Speed);
500
501 attacker_choice.base_power =
502 ((25.0 * defender_speed as f32 / attacker_speed as f32) + 1.0).min(150.0);
503 }
504 Choices::AVALANCHE => {
505 if !attacker_choice.first_move && defending_side.damage_dealt.damage > 0 {
506 attacker_choice.base_power *= 2.0;
507 }
508 }
509
510 #[cfg(any(feature = "gen3", feature = "gen4"))]
511 Choices::PAYBACK => {
512 if !attacker_choice.first_move {
513 attacker_choice.base_power *= 2.0;
514 }
515 }
516
517 #[cfg(any(
518 feature = "gen5",
519 feature = "gen6",
520 feature = "gen7",
521 feature = "gen8",
522 feature = "gen9"
523 ))]
524 Choices::PAYBACK => {
525 if !attacker_choice.first_move && defender_choice.category != MoveCategory::Switch {
526 attacker_choice.base_power *= 2.0;
527 }
528 }
529
530 Choices::FACADE => {
531 if attacking_side.get_active_immutable().status != PokemonStatus::NONE {
532 attacker_choice.base_power *= 2.0;
533 }
534 }
535 Choices::STOREDPOWER | Choices::POWERTRIP => {
536 let total_boosts = attacking_side.attack_boost.max(0)
537 + attacking_side.defense_boost.max(0)
538 + attacking_side.special_attack_boost.max(0)
539 + attacking_side.special_defense_boost.max(0)
540 + attacking_side.speed_boost.max(0)
541 + attacking_side.accuracy_boost.max(0)
542 + attacking_side.evasion_boost.max(0);
543 if total_boosts > 0 {
544 attacker_choice.base_power += 20.0 * total_boosts as f32;
545 }
546 }
547 Choices::BARBBARRAGE => {
548 let defending_pkmn_status = defending_side.get_active_immutable().status;
549 if defending_pkmn_status == PokemonStatus::POISON
550 || defending_pkmn_status == PokemonStatus::TOXIC
551 {
552 attacker_choice.base_power *= 2.0;
553 }
554 }
555 Choices::FREEZEDRY => {
556 if defending_side
557 .get_active_immutable()
558 .has_type(&PokemonType::WATER)
559 {
560 attacker_choice.base_power *= 4.0; }
562 }
563 Choices::ERUPTION | Choices::WATERSPOUT | Choices::DRAGONENERGY => {
564 let attacker = attacking_side.get_active_immutable();
565 let hp_ratio = attacker.hp as f32 / attacker.maxhp as f32;
566 attacker_choice.base_power *= hp_ratio;
567 }
568 Choices::SUCKERPUNCH | Choices::THUNDERCLAP => {
569 if !attacker_choice.first_move || defender_choice.category == MoveCategory::Status {
570 attacker_choice.base_power = 0.0;
571 }
572 }
573 Choices::UPPERHAND => {
574 if !(attacker_choice.first_move && defender_choice.priority > 0) {
575 attacker_choice.remove_all_effects()
576 }
577 }
578 Choices::COLLISIONCOURSE | Choices::ELECTRODRIFT => {
579 let defender_active = defending_side.get_active_immutable();
580 if type_effectiveness_modifier(&attacker_choice.move_type, &defender_active) > 1.0 {
581 attacker_choice.base_power *= 1.3;
582 }
583 }
584 Choices::GRASSKNOT | Choices::LOWKICK => {
585 let defender_active = defending_side.get_active_immutable();
586 if defender_active.weight_kg < 10.0 {
587 attacker_choice.base_power = 20.0;
588 } else if defender_active.weight_kg < 25.0 {
589 attacker_choice.base_power = 40.0;
590 } else if defender_active.weight_kg < 50.0 {
591 attacker_choice.base_power = 60.0;
592 } else if defender_active.weight_kg < 100.0 {
593 attacker_choice.base_power = 80.0;
594 } else if defender_active.weight_kg < 200.0 {
595 attacker_choice.base_power = 100.0;
596 } else {
597 attacker_choice.base_power = 120.0;
598 }
599 }
600 Choices::HEATCRASH | Choices::HEAVYSLAM => {
601 let attacker = attacking_side.get_active_immutable();
602 let defender = defending_side.get_active_immutable();
603 let weight_ratio = defender.weight_kg / attacker.weight_kg;
604 if weight_ratio > 0.5 {
605 attacker_choice.base_power = 40.0;
606 } else if weight_ratio > 0.3335 {
607 attacker_choice.base_power = 60.0;
608 } else if weight_ratio >= 0.2501 {
609 attacker_choice.base_power = 80.0;
610 } else if weight_ratio >= 0.2001 {
611 attacker_choice.base_power = 100.0;
612 } else {
613 attacker_choice.base_power = 120.0;
614 }
615 }
616 _ => {}
617 }
618}
619
620pub fn choice_after_damage_hit(
621 state: &mut State,
622 choice: &Choice,
623 attacking_side_ref: &SideReference,
624 instructions: &mut StateInstructions,
625 hit_sub: bool,
626) {
627 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
628 let attacker_active = attacking_side.get_active();
629 if choice.flags.recharge {
630 let instruction = Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
631 side_ref: attacking_side_ref.clone(),
632 volatile_status: PokemonVolatileStatus::MUSTRECHARGE,
633 });
634 instructions.instruction_list.push(instruction);
635 attacking_side
636 .volatile_statuses
637 .insert(PokemonVolatileStatus::MUSTRECHARGE);
638
639 } else if attacker_active.ability == Abilities::TRUANT {
641 let instruction = Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
642 side_ref: attacking_side_ref.clone(),
643 volatile_status: PokemonVolatileStatus::TRUANT,
644 });
645 instructions.instruction_list.push(instruction);
646 attacking_side
647 .volatile_statuses
648 .insert(PokemonVolatileStatus::TRUANT);
649 }
650 match choice.move_id {
651 Choices::DOUBLESHOCK => {
652 let attacker_active = attacking_side.get_active_immutable();
653 let instruction = if attacker_active.types.0 == PokemonType::ELECTRIC {
654 Some(Instruction::ChangeType(ChangeType {
655 side_ref: *attacking_side_ref,
656 new_types: (PokemonType::TYPELESS, attacker_active.types.1),
657 old_types: attacker_active.types,
658 }))
659 } else if attacker_active.types.1 == PokemonType::ELECTRIC {
660 Some(Instruction::ChangeType(ChangeType {
661 side_ref: *attacking_side_ref,
662 new_types: (attacker_active.types.0, PokemonType::TYPELESS),
663 old_types: attacker_active.types,
664 }))
665 } else {
666 None
667 };
668 if let Some(typechange_instruction) = instruction {
669 if !attacking_side
670 .volatile_statuses
671 .contains(&PokemonVolatileStatus::TYPECHANGE)
672 {
673 instructions
674 .instruction_list
675 .push(Instruction::ApplyVolatileStatus(
676 ApplyVolatileStatusInstruction {
677 side_ref: attacking_side_ref.clone(),
678 volatile_status: PokemonVolatileStatus::TYPECHANGE,
679 },
680 ));
681 attacking_side
682 .volatile_statuses
683 .insert(PokemonVolatileStatus::TYPECHANGE);
684 }
685 state.apply_one_instruction(&typechange_instruction);
686 instructions.instruction_list.push(typechange_instruction);
687 }
688 }
689 Choices::BURNUP => {
690 let attacker_active = attacking_side.get_active_immutable();
691 let instruction = if attacker_active.types.0 == PokemonType::FIRE {
692 Some(Instruction::ChangeType(ChangeType {
693 side_ref: *attacking_side_ref,
694 new_types: (PokemonType::TYPELESS, attacker_active.types.1),
695 old_types: attacker_active.types,
696 }))
697 } else if attacker_active.types.1 == PokemonType::FIRE {
698 Some(Instruction::ChangeType(ChangeType {
699 side_ref: *attacking_side_ref,
700 new_types: (attacker_active.types.0, PokemonType::TYPELESS),
701 old_types: attacker_active.types,
702 }))
703 } else {
704 None
705 };
706 if let Some(typechange_instruction) = instruction {
707 if !attacking_side
708 .volatile_statuses
709 .contains(&PokemonVolatileStatus::TYPECHANGE)
710 {
711 instructions
712 .instruction_list
713 .push(Instruction::ApplyVolatileStatus(
714 ApplyVolatileStatusInstruction {
715 side_ref: attacking_side_ref.clone(),
716 volatile_status: PokemonVolatileStatus::TYPECHANGE,
717 },
718 ));
719 attacking_side
720 .volatile_statuses
721 .insert(PokemonVolatileStatus::TYPECHANGE);
722 }
723 state.apply_one_instruction(&typechange_instruction);
724 instructions.instruction_list.push(typechange_instruction);
725 }
726 }
727 Choices::RAGINGBULL => {
728 if defending_side.side_conditions.reflect > 0 {
729 instructions
730 .instruction_list
731 .push(Instruction::ChangeSideCondition(
732 ChangeSideConditionInstruction {
733 side_ref: attacking_side_ref.get_other_side(),
734 side_condition: PokemonSideCondition::Reflect,
735 amount: -1 * defending_side.side_conditions.reflect,
736 },
737 ));
738 defending_side.side_conditions.reflect = 0;
739 }
740 if defending_side.side_conditions.light_screen > 0 {
741 instructions
742 .instruction_list
743 .push(Instruction::ChangeSideCondition(
744 ChangeSideConditionInstruction {
745 side_ref: attacking_side_ref.get_other_side(),
746 side_condition: PokemonSideCondition::LightScreen,
747 amount: -1 * defending_side.side_conditions.light_screen,
748 },
749 ));
750 defending_side.side_conditions.light_screen = 0;
751 }
752 if defending_side.side_conditions.aurora_veil > 0 {
753 instructions
754 .instruction_list
755 .push(Instruction::ChangeSideCondition(
756 ChangeSideConditionInstruction {
757 side_ref: attacking_side_ref.get_other_side(),
758 side_condition: PokemonSideCondition::AuroraVeil,
759 amount: -1 * defending_side.side_conditions.aurora_veil,
760 },
761 ));
762 defending_side.side_conditions.aurora_veil = 0;
763 }
764 }
765 Choices::KNOCKOFF => {
766 let defender_active = defending_side.get_active();
767 if defender_active.item_can_be_removed()
768 && defender_active.item != Items::NONE
769 && !hit_sub
770 {
771 let instruction = Instruction::ChangeItem(ChangeItemInstruction {
772 side_ref: attacking_side_ref.get_other_side(),
773 current_item: defender_active.item,
774 new_item: Items::NONE,
775 });
776 instructions.instruction_list.push(instruction);
777 defender_active.item = Items::NONE;
778 }
779 }
780 Choices::THIEF => {
781 let attacker_active = attacking_side.get_active();
782 let defender_active = defending_side.get_active();
783 if defender_active.item_can_be_removed()
784 && defender_active.item != Items::NONE
785 && attacker_active.item == Items::NONE
786 && !hit_sub
787 {
788 let defender_item = defender_active.item;
789
790 let instruction = Instruction::ChangeItem(ChangeItemInstruction {
791 side_ref: attacking_side_ref.get_other_side(),
792 current_item: defender_item,
793 new_item: Items::NONE,
794 });
795 instructions.instruction_list.push(instruction);
796 defender_active.item = Items::NONE;
797
798 let instruction = Instruction::ChangeItem(ChangeItemInstruction {
799 side_ref: *attacking_side_ref,
800 current_item: Items::NONE,
801 new_item: defender_item,
802 });
803 instructions.instruction_list.push(instruction);
804 attacker_active.item = defender_item;
805 }
806 }
807 Choices::CLEARSMOG => {
808 state.reset_boosts(
809 &attacking_side_ref.get_other_side(),
810 &mut instructions.instruction_list,
811 );
812 }
813 Choices::ICESPINNER => {
814 if state.terrain.terrain_type != Terrain::NONE && state.terrain.turns_remaining > 0 {
815 instructions
816 .instruction_list
817 .push(Instruction::ChangeTerrain(ChangeTerrain {
818 new_terrain: Terrain::NONE,
819 new_terrain_turns_remaining: 0,
820 previous_terrain: state.terrain.terrain_type,
821 previous_terrain_turns_remaining: state.terrain.turns_remaining,
822 }));
823 state.terrain.terrain_type = Terrain::NONE;
824 state.terrain.turns_remaining = 0;
825 }
826 }
827 _ => {}
828 }
829}
830
831#[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5", feature = "gen6"))]
832fn destinybond_before_move(
833 attacking_side: &mut Side,
834 attacking_side_ref: &SideReference,
835 choice: &Choice,
836 instructions: &mut StateInstructions,
837) {
838 if choice.move_id != Choices::DESTINYBOND
841 && attacking_side
842 .volatile_statuses
843 .contains(&PokemonVolatileStatus::DESTINYBOND)
844 {
845 instructions
846 .instruction_list
847 .push(Instruction::RemoveVolatileStatus(
848 RemoveVolatileStatusInstruction {
849 side_ref: *attacking_side_ref,
850 volatile_status: PokemonVolatileStatus::DESTINYBOND,
851 },
852 ));
853 attacking_side
854 .volatile_statuses
855 .remove(&PokemonVolatileStatus::DESTINYBOND);
856 }
857}
858
859#[cfg(any(feature = "gen7", feature = "gen8", feature = "gen9"))]
860fn destinybond_before_move(
861 attacking_side: &mut Side,
862 attacking_side_ref: &SideReference,
863 choice: &mut Choice,
864 instructions: &mut StateInstructions,
865) {
866 if attacking_side
868 .volatile_statuses
869 .contains(&PokemonVolatileStatus::DESTINYBOND)
870 {
871 instructions
872 .instruction_list
873 .push(Instruction::RemoveVolatileStatus(
874 RemoveVolatileStatusInstruction {
875 side_ref: *attacking_side_ref,
876 volatile_status: PokemonVolatileStatus::DESTINYBOND,
877 },
878 ));
879 attacking_side
880 .volatile_statuses
881 .remove(&PokemonVolatileStatus::DESTINYBOND);
882 if choice.move_id == Choices::DESTINYBOND {
883 choice.remove_all_effects();
884 }
885 }
886}
887
888pub fn choice_before_move(
889 state: &mut State,
890 choice: &mut Choice,
891 attacking_side_ref: &SideReference,
892 instructions: &mut StateInstructions,
893) {
894 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
895
896 destinybond_before_move(attacking_side, attacking_side_ref, choice, instructions);
897
898 if attacking_side.get_active_immutable().status == PokemonStatus::FREEZE
899 && CHOICE_THAWS_USER.contains(&choice.move_id)
900 {
901 add_remove_status_instructions(
902 instructions,
903 attacking_side.active_index,
904 *attacking_side_ref,
905 attacking_side,
906 );
907 }
908
909 let attacker = attacking_side.get_active();
910 let defender = defending_side.get_active_immutable();
911
912 match choice.move_id {
913 Choices::FUTURESIGHT => {
914 choice.remove_all_effects();
915 if attacking_side.future_sight.0 == 0 {
916 instructions
917 .instruction_list
918 .push(Instruction::SetFutureSight(SetFutureSightInstruction {
919 side_ref: *attacking_side_ref,
920 pokemon_index: attacking_side.active_index,
921 previous_pokemon_index: attacking_side.future_sight.1,
922 }));
923 attacking_side.future_sight = (3, attacking_side.active_index);
924 }
925 }
926 Choices::EXPLOSION | Choices::SELFDESTRUCT | Choices::MISTYEXPLOSION
927 if defender.ability != Abilities::DAMP =>
928 {
929 let damage_amount = attacker.hp;
930 instructions
931 .instruction_list
932 .push(Instruction::Damage(DamageInstruction {
933 side_ref: *attacking_side_ref,
934 damage_amount,
935 }));
936 attacker.hp = 0;
937 }
938 Choices::MINDBLOWN if defender.ability != Abilities::DAMP => {
939 let damage_amount = cmp::min(attacker.maxhp / 2, attacker.hp);
940 instructions
941 .instruction_list
942 .push(Instruction::Damage(DamageInstruction {
943 side_ref: *attacking_side_ref,
944 damage_amount,
945 }));
946 attacker.hp -= damage_amount;
947 }
948 Choices::METEORBEAM | Choices::ELECTROSHOT if choice.flags.charge => {
949 apply_boost_instruction(
950 attacking_side,
951 &PokemonBoostableStat::SpecialAttack,
952 &1,
953 attacking_side_ref,
954 attacking_side_ref,
955 instructions,
956 );
957 }
958 _ => {}
959 }
960 let attacking_side = state.get_side(attacking_side_ref);
961 let attacker = attacking_side.get_active();
962 if choice.flags.charge
963 && attacker.item == Items::POWERHERB
964 && choice.move_id != Choices::SKYDROP
965 {
966 let instruction = Instruction::ChangeItem(ChangeItemInstruction {
967 side_ref: *attacking_side_ref,
968 current_item: Items::POWERHERB,
969 new_item: Items::NONE,
970 });
971 attacker.item = Items::NONE;
972 choice.flags.charge = false;
973 instructions.instruction_list.push(instruction);
974 }
975 if let Some(choice_volatile_status) = &choice.volatile_status {
976 if choice_volatile_status.volatile_status == PokemonVolatileStatus::LOCKEDMOVE
977 && choice_volatile_status.target == MoveTarget::User
978 {
979 let ins =
980 get_choice_move_disable_instructions(attacker, attacking_side_ref, &choice.move_id);
981 for i in ins {
982 state.apply_one_instruction(&i);
983 instructions.instruction_list.push(i);
984 }
985 }
986 }
987}
988
989pub fn choice_hazard_clear(
990 state: &mut State,
991 choice: &Choice,
992 attacking_side_ref: &SideReference,
993 instructions: &mut StateInstructions,
994) {
995 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
996 match choice.move_id {
997 Choices::COURTCHANGE => {
998 let mut instruction_list = vec![];
999 let courtchange_swaps = [
1000 PokemonSideCondition::Stealthrock,
1001 PokemonSideCondition::Spikes,
1002 PokemonSideCondition::ToxicSpikes,
1003 PokemonSideCondition::StickyWeb,
1004 PokemonSideCondition::Reflect,
1005 PokemonSideCondition::LightScreen,
1006 PokemonSideCondition::AuroraVeil,
1007 PokemonSideCondition::Tailwind,
1008 ];
1009
1010 for side in [SideReference::SideOne, SideReference::SideTwo] {
1011 for side_condition in courtchange_swaps {
1012 let side_condition_num = state
1013 .get_side_immutable(&side)
1014 .get_side_condition(side_condition);
1015 if side_condition_num > 0 {
1016 instruction_list.push(Instruction::ChangeSideCondition(
1017 ChangeSideConditionInstruction {
1018 side_ref: side,
1019 side_condition: side_condition,
1020 amount: -1 * side_condition_num,
1021 },
1022 ));
1023 instruction_list.push(Instruction::ChangeSideCondition(
1024 ChangeSideConditionInstruction {
1025 side_ref: side.get_other_side(),
1026 side_condition: side_condition,
1027 amount: side_condition_num,
1028 },
1029 ));
1030 }
1031 }
1032 }
1033 state.apply_instructions(&instruction_list);
1034 for i in instruction_list {
1035 instructions.instruction_list.push(i)
1036 }
1037 }
1038 Choices::DEFOG
1039 if defending_side.get_active_immutable().ability != Abilities::GOODASGOLD =>
1040 {
1041 if state.terrain.terrain_type != Terrain::NONE {
1042 instructions
1043 .instruction_list
1044 .push(Instruction::ChangeTerrain(ChangeTerrain {
1045 new_terrain: Terrain::NONE,
1046 new_terrain_turns_remaining: 0,
1047 previous_terrain: state.terrain.terrain_type,
1048 previous_terrain_turns_remaining: state.terrain.turns_remaining,
1049 }));
1050 state.terrain.terrain_type = Terrain::NONE;
1051 state.terrain.turns_remaining = 0;
1052 }
1053 let side_condition_clears = [
1054 PokemonSideCondition::Stealthrock,
1055 PokemonSideCondition::Spikes,
1056 PokemonSideCondition::ToxicSpikes,
1057 PokemonSideCondition::StickyWeb,
1058 PokemonSideCondition::Reflect,
1059 PokemonSideCondition::LightScreen,
1060 PokemonSideCondition::AuroraVeil,
1061 ];
1062
1063 for side in [SideReference::SideOne, SideReference::SideTwo] {
1064 for side_condition in side_condition_clears {
1065 let side_condition_num = state
1066 .get_side_immutable(&side)
1067 .get_side_condition(side_condition);
1068 if side_condition_num > 0 {
1069 let i = Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
1070 side_ref: side,
1071 side_condition: side_condition,
1072 amount: -1 * side_condition_num,
1073 });
1074 state.apply_one_instruction(&i);
1075 instructions.instruction_list.push(i)
1076 }
1077 }
1078 }
1079 }
1080 Choices::TIDYUP => {
1081 let side_condition_clears = [
1082 PokemonSideCondition::Stealthrock,
1083 PokemonSideCondition::Spikes,
1084 PokemonSideCondition::ToxicSpikes,
1085 PokemonSideCondition::StickyWeb,
1086 ];
1087
1088 for side in [SideReference::SideOne, SideReference::SideTwo] {
1089 for side_condition in side_condition_clears {
1090 let side_condition_num = state
1091 .get_side_immutable(&side)
1092 .get_side_condition(side_condition);
1093 if side_condition_num > 0 {
1094 let i = Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
1095 side_ref: side,
1096 side_condition: side_condition,
1097 amount: -1 * side_condition_num,
1098 });
1099 state.apply_one_instruction(&i);
1100 instructions.instruction_list.push(i)
1101 }
1102 }
1103 }
1104 if state
1105 .side_one
1106 .volatile_statuses
1107 .contains(&PokemonVolatileStatus::SUBSTITUTE)
1108 {
1109 instructions
1110 .instruction_list
1111 .push(Instruction::ChangeSubstituteHealth(
1112 ChangeSubsituteHealthInstruction {
1113 side_ref: SideReference::SideOne,
1114 health_change: -1 * state.side_one.substitute_health,
1115 },
1116 ));
1117 instructions
1118 .instruction_list
1119 .push(Instruction::RemoveVolatileStatus(
1120 RemoveVolatileStatusInstruction {
1121 side_ref: SideReference::SideOne,
1122 volatile_status: PokemonVolatileStatus::SUBSTITUTE,
1123 },
1124 ));
1125 state.side_one.substitute_health = 0;
1126 state
1127 .side_one
1128 .volatile_statuses
1129 .remove(&PokemonVolatileStatus::SUBSTITUTE);
1130 }
1131 if state
1132 .side_two
1133 .volatile_statuses
1134 .contains(&PokemonVolatileStatus::SUBSTITUTE)
1135 {
1136 instructions
1137 .instruction_list
1138 .push(Instruction::ChangeSubstituteHealth(
1139 ChangeSubsituteHealthInstruction {
1140 side_ref: SideReference::SideTwo,
1141 health_change: -1 * state.side_two.substitute_health,
1142 },
1143 ));
1144 instructions
1145 .instruction_list
1146 .push(Instruction::RemoveVolatileStatus(
1147 RemoveVolatileStatusInstruction {
1148 side_ref: SideReference::SideTwo,
1149 volatile_status: PokemonVolatileStatus::SUBSTITUTE,
1150 },
1151 ));
1152 state.side_two.substitute_health = 0;
1153 state
1154 .side_two
1155 .volatile_statuses
1156 .remove(&PokemonVolatileStatus::SUBSTITUTE);
1157 }
1158 }
1159 Choices::RAPIDSPIN | Choices::MORTALSPIN => {
1160 if attacking_side.side_conditions.stealth_rock > 0 {
1161 instructions
1162 .instruction_list
1163 .push(Instruction::ChangeSideCondition(
1164 ChangeSideConditionInstruction {
1165 side_ref: *attacking_side_ref,
1166 side_condition: PokemonSideCondition::Stealthrock,
1167 amount: -1 * attacking_side.side_conditions.stealth_rock,
1168 },
1169 ));
1170 attacking_side.side_conditions.stealth_rock = 0;
1171 }
1172 if attacking_side.side_conditions.spikes > 0 {
1173 instructions
1174 .instruction_list
1175 .push(Instruction::ChangeSideCondition(
1176 ChangeSideConditionInstruction {
1177 side_ref: *attacking_side_ref,
1178 side_condition: PokemonSideCondition::Spikes,
1179 amount: -1 * attacking_side.side_conditions.spikes,
1180 },
1181 ));
1182 attacking_side.side_conditions.spikes = 0;
1183 }
1184 if attacking_side.side_conditions.toxic_spikes > 0 {
1185 instructions
1186 .instruction_list
1187 .push(Instruction::ChangeSideCondition(
1188 ChangeSideConditionInstruction {
1189 side_ref: *attacking_side_ref,
1190 side_condition: PokemonSideCondition::ToxicSpikes,
1191 amount: -1 * attacking_side.side_conditions.toxic_spikes,
1192 },
1193 ));
1194 attacking_side.side_conditions.toxic_spikes = 0;
1195 }
1196 if attacking_side.side_conditions.sticky_web > 0 {
1197 instructions
1198 .instruction_list
1199 .push(Instruction::ChangeSideCondition(
1200 ChangeSideConditionInstruction {
1201 side_ref: *attacking_side_ref,
1202 side_condition: PokemonSideCondition::StickyWeb,
1203 amount: -1 * attacking_side.side_conditions.sticky_web,
1204 },
1205 ));
1206 attacking_side.side_conditions.sticky_web = 0;
1207 }
1208 }
1209 _ => {}
1210 }
1211}
1212
1213pub fn choice_special_effect(
1214 state: &mut State,
1215 choice: &mut Choice,
1216 attacking_side_ref: &SideReference,
1217 instructions: &mut StateInstructions,
1218) {
1219 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
1220 match choice.move_id {
1221 Choices::BELLYDRUM => {
1222 let boost_amount = 6 - attacking_side.attack_boost;
1223 let attacker = attacking_side.get_active();
1224 if attacker.hp > attacker.maxhp / 2 {
1225 instructions
1226 .instruction_list
1227 .push(Instruction::Damage(DamageInstruction {
1228 side_ref: *attacking_side_ref,
1229 damage_amount: attacker.maxhp / 2,
1230 }));
1231 instructions
1232 .instruction_list
1233 .push(Instruction::Boost(BoostInstruction {
1234 side_ref: *attacking_side_ref,
1235 stat: PokemonBoostableStat::Attack,
1236 amount: boost_amount,
1237 }));
1238 attacker.hp -= attacker.maxhp / 2;
1239 attacking_side.attack_boost = 6;
1240 }
1241 }
1242 Choices::COUNTER => {
1243 if defending_side.damage_dealt.move_category == MoveCategory::Physical
1244 && !defending_side
1245 .get_active_immutable()
1246 .has_type(&PokemonType::GHOST)
1247 {
1248 let damage_amount = cmp::min(
1249 defending_side.damage_dealt.damage * 2,
1250 defending_side.get_active_immutable().hp,
1251 );
1252 if damage_amount > 0 {
1253 instructions
1254 .instruction_list
1255 .push(Instruction::Damage(DamageInstruction {
1256 side_ref: attacking_side_ref.get_other_side(),
1257 damage_amount: damage_amount,
1258 }));
1259 defending_side.get_active().hp -= damage_amount;
1260 }
1261 }
1262 }
1263 Choices::MIRRORCOAT => {
1264 if defending_side.damage_dealt.move_category == MoveCategory::Special
1265 && !defending_side
1266 .get_active_immutable()
1267 .has_type(&PokemonType::DARK)
1268 {
1269 let damage_amount = cmp::min(
1270 defending_side.damage_dealt.damage * 2,
1271 defending_side.get_active_immutable().hp,
1272 );
1273 if damage_amount > 0 {
1274 instructions
1275 .instruction_list
1276 .push(Instruction::Damage(DamageInstruction {
1277 side_ref: attacking_side_ref.get_other_side(),
1278 damage_amount: damage_amount,
1279 }));
1280 defending_side.get_active().hp -= damage_amount;
1281 }
1282 }
1283 }
1284 Choices::METALBURST | Choices::COMEUPPANCE => {
1285 if defending_side.damage_dealt.move_category != MoveCategory::Status
1286 && !defending_side.damage_dealt.hit_substitute
1287 && !choice.first_move
1288 {
1289 let damage_amount = cmp::min(
1290 (defending_side.damage_dealt.damage * 3) / 2,
1291 defending_side.get_active_immutable().hp,
1292 );
1293 if damage_amount > 0 {
1294 instructions
1295 .instruction_list
1296 .push(Instruction::Damage(DamageInstruction {
1297 side_ref: attacking_side_ref.get_other_side(),
1298 damage_amount: damage_amount,
1299 }));
1300 defending_side.get_active().hp -= damage_amount;
1301 }
1302 }
1303 }
1304 Choices::WISH => {
1305 if attacking_side.wish.0 == 0 {
1306 let previous_wish_amount = attacking_side.wish.1;
1307 instructions.instruction_list.push(Instruction::ChangeWish(
1308 ChangeWishInstruction {
1309 side_ref: *attacking_side_ref,
1310 wish_amount_change: attacking_side.get_active_immutable().maxhp / 2
1311 - previous_wish_amount,
1312 },
1313 ));
1314 attacking_side.wish = (2, attacking_side.get_active_immutable().maxhp / 2);
1315 }
1316 }
1317 Choices::REFRESH => {
1318 let active_index = attacking_side.active_index;
1319 let active_pkmn = attacking_side.get_active();
1320 if active_pkmn.status != PokemonStatus::NONE {
1321 add_remove_status_instructions(
1322 instructions,
1323 active_index,
1324 *attacking_side_ref,
1325 attacking_side,
1326 );
1327 }
1328 }
1329 Choices::HEALBELL | Choices::AROMATHERAPY => {
1330 for pkmn_index in pokemon_index_iter() {
1331 if attacking_side.pokemon[pkmn_index].status != PokemonStatus::NONE {
1332 add_remove_status_instructions(
1333 instructions,
1334 pkmn_index,
1335 *attacking_side_ref,
1336 attacking_side,
1337 );
1338 }
1339 }
1340 }
1341 Choices::HAZE => {
1342 state.reset_boosts(&SideReference::SideOne, &mut instructions.instruction_list);
1343 state.reset_boosts(&SideReference::SideTwo, &mut instructions.instruction_list);
1344 }
1345 Choices::REST => {
1346 let electric_terrain_active = state.terrain_is_active(&Terrain::ELECTRICTERRAIN);
1347 let attacking_side = state.get_side(attacking_side_ref);
1348 let active_index = attacking_side.active_index;
1349 let active_pkmn = attacking_side.get_active();
1350 if active_pkmn.status != PokemonStatus::SLEEP && !electric_terrain_active {
1351 let heal_amount = active_pkmn.maxhp - active_pkmn.hp;
1352 instructions
1353 .instruction_list
1354 .push(Instruction::ChangeStatus(ChangeStatusInstruction {
1355 side_ref: *attacking_side_ref,
1356 pokemon_index: active_index,
1357 old_status: active_pkmn.status,
1358 new_status: PokemonStatus::SLEEP,
1359 }));
1360 instructions
1361 .instruction_list
1362 .push(Instruction::SetRestTurns(SetSleepTurnsInstruction {
1363 side_ref: *attacking_side_ref,
1364 pokemon_index: active_index,
1365 new_turns: 3,
1366 previous_turns: active_pkmn.rest_turns,
1367 }));
1368 instructions
1369 .instruction_list
1370 .push(Instruction::Heal(HealInstruction {
1371 side_ref: *attacking_side_ref,
1372 heal_amount,
1373 }));
1374 active_pkmn.hp = active_pkmn.maxhp;
1375 active_pkmn.status = PokemonStatus::SLEEP;
1376 active_pkmn.rest_turns = 3;
1377 }
1378 }
1379 Choices::TRICKROOM => {
1380 let new_turns_remaining;
1381 if state.trick_room.active {
1382 new_turns_remaining = 0;
1383 } else {
1384 new_turns_remaining = 5;
1385 }
1386 instructions
1387 .instruction_list
1388 .push(Instruction::ToggleTrickRoom(ToggleTrickRoomInstruction {
1389 currently_active: state.trick_room.active,
1390 new_trickroom_turns_remaining: new_turns_remaining,
1391 previous_trickroom_turns_remaining: state.trick_room.turns_remaining,
1392 }));
1393 state.trick_room.active = !state.trick_room.active;
1394 }
1395 Choices::SUPERFANG | Choices::NATURESMADNESS | Choices::RUINATION => {
1396 let target_pkmn = defending_side.get_active();
1397 if target_pkmn.hp == 1 {
1398 return;
1399 }
1400 if choice.move_id == Choices::SUPERFANG
1401 && type_effectiveness_modifier(&PokemonType::NORMAL, &target_pkmn) == 0.0
1402 {
1403 return;
1404 }
1405 let target_hp = target_pkmn.hp / 2;
1406 instructions
1407 .instruction_list
1408 .push(Instruction::Damage(DamageInstruction {
1409 side_ref: attacking_side_ref.get_other_side(),
1410 damage_amount: target_pkmn.hp - target_hp,
1411 }));
1412 target_pkmn.hp = target_hp;
1413 }
1414 Choices::NIGHTSHADE => {
1415 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
1416 let attacker_level = attacking_side.get_active_immutable().level;
1417 let defender_active = defending_side.get_active();
1418 if type_effectiveness_modifier(&PokemonType::GHOST, &defender_active) == 0.0 {
1419 return;
1420 }
1421
1422 let damage_amount = cmp::min(attacker_level as i16, defender_active.hp);
1423 instructions
1424 .instruction_list
1425 .push(Instruction::Damage(DamageInstruction {
1426 side_ref: attacking_side_ref.get_other_side(),
1427 damage_amount: damage_amount,
1428 }));
1429 defender_active.hp -= damage_amount;
1430 }
1431 Choices::SEISMICTOSS => {
1432 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
1433 let attacker_level = attacking_side.get_active_immutable().level;
1434 let defender_active = defending_side.get_active();
1435 if type_effectiveness_modifier(&PokemonType::NORMAL, &defender_active) == 0.0 {
1436 return;
1437 }
1438
1439 let damage_amount = cmp::min(attacker_level as i16, defender_active.hp);
1440 instructions
1441 .instruction_list
1442 .push(Instruction::Damage(DamageInstruction {
1443 side_ref: attacking_side_ref.get_other_side(),
1444 damage_amount: damage_amount,
1445 }));
1446 defender_active.hp -= damage_amount;
1447 }
1448 Choices::ENDEAVOR => {
1449 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
1450 let attacker = attacking_side.get_active();
1451 let defender = defending_side.get_active();
1452
1453 if type_effectiveness_modifier(&PokemonType::NORMAL, &defender) == 0.0
1454 || attacker.hp >= defender.hp
1455 {
1456 return;
1457 }
1458
1459 let damage_amount = defender.hp - attacker.hp;
1460 instructions
1461 .instruction_list
1462 .push(Instruction::Damage(DamageInstruction {
1463 side_ref: attacking_side_ref.get_other_side(),
1464 damage_amount: damage_amount,
1465 }));
1466 defender.hp -= damage_amount;
1467 }
1468 Choices::FINALGAMBIT => {
1469 let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
1470 let attacker = attacking_side.get_active();
1471 let defender = defending_side.get_active();
1472
1473 if type_effectiveness_modifier(&PokemonType::NORMAL, &defender) == 0.0 {
1474 return;
1475 }
1476
1477 let damage_amount = attacker.hp;
1478 instructions
1479 .instruction_list
1480 .push(Instruction::Damage(DamageInstruction {
1481 side_ref: attacking_side_ref.get_other_side(),
1482 damage_amount: damage_amount,
1483 }));
1484 defender.hp -= damage_amount;
1485
1486 instructions
1487 .instruction_list
1488 .push(Instruction::Damage(DamageInstruction {
1489 side_ref: *attacking_side_ref,
1490 damage_amount: attacker.hp,
1491 }));
1492 attacker.hp = 0;
1493 }
1494 Choices::PAINSPLIT => {
1495 if !defending_side
1496 .volatile_statuses
1497 .contains(&PokemonVolatileStatus::SUBSTITUTE)
1498 {
1499 let target_hp = (attacking_side.get_active_immutable().hp
1500 + defending_side.get_active_immutable().hp)
1501 / 2;
1502 instructions
1503 .instruction_list
1504 .push(Instruction::Damage(DamageInstruction {
1505 side_ref: *attacking_side_ref,
1506 damage_amount: attacking_side.get_active_immutable().hp - target_hp,
1507 }));
1508 instructions
1509 .instruction_list
1510 .push(Instruction::Damage(DamageInstruction {
1511 side_ref: attacking_side_ref.get_other_side(),
1512 damage_amount: defending_side.get_active_immutable().hp - target_hp,
1513 }));
1514
1515 attacking_side.get_active().hp = target_hp;
1516 defending_side.get_active().hp = target_hp;
1517 }
1518 }
1519 Choices::SUBSTITUTE | Choices::SHEDTAIL => {
1520 if attacking_side
1521 .volatile_statuses
1522 .contains(&PokemonVolatileStatus::SUBSTITUTE)
1523 {
1524 return;
1525 }
1526 let sub_current_health = attacking_side.substitute_health;
1527 let active_pkmn = attacking_side.get_active();
1528 let sub_target_health = active_pkmn.maxhp / 4;
1529 let pkmn_health_reduction = if choice.move_id == Choices::SHEDTAIL {
1530 active_pkmn.maxhp / 2
1531 } else {
1532 sub_target_health
1533 };
1534 if active_pkmn.hp > pkmn_health_reduction {
1535 if choice.move_id == Choices::SHEDTAIL {
1536 choice.flags.pivot = true;
1537 }
1538
1539 let damage_instruction = Instruction::Damage(DamageInstruction {
1540 side_ref: attacking_side_ref.clone(),
1541 damage_amount: pkmn_health_reduction,
1542 });
1543 let set_sub_health_instruction =
1544 Instruction::ChangeSubstituteHealth(ChangeSubsituteHealthInstruction {
1545 side_ref: attacking_side_ref.clone(),
1546 health_change: sub_target_health - sub_current_health,
1547 });
1548 let apply_vs_instruction =
1549 Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
1550 side_ref: attacking_side_ref.clone(),
1551 volatile_status: PokemonVolatileStatus::SUBSTITUTE,
1552 });
1553 active_pkmn.hp -= pkmn_health_reduction;
1554 attacking_side.substitute_health = sub_target_health;
1555 attacking_side
1556 .volatile_statuses
1557 .insert(PokemonVolatileStatus::SUBSTITUTE);
1558 instructions.instruction_list.push(damage_instruction);
1559 instructions
1560 .instruction_list
1561 .push(set_sub_health_instruction);
1562 instructions.instruction_list.push(apply_vs_instruction);
1563 }
1564 }
1565 Choices::PERISHSONG => {
1566 for side_ref in [SideReference::SideOne, SideReference::SideTwo] {
1567 let side = state.get_side(&side_ref);
1568 let pkmn = side.get_active();
1569 if pkmn.hp != 0
1570 && pkmn.ability != Abilities::SOUNDPROOF
1571 && !(side
1572 .volatile_statuses
1573 .contains(&PokemonVolatileStatus::PERISH4)
1574 || side
1575 .volatile_statuses
1576 .contains(&PokemonVolatileStatus::PERISH3)
1577 || side
1578 .volatile_statuses
1579 .contains(&PokemonVolatileStatus::PERISH2)
1580 || side
1581 .volatile_statuses
1582 .contains(&PokemonVolatileStatus::PERISH1))
1583 {
1584 instructions
1585 .instruction_list
1586 .push(Instruction::ApplyVolatileStatus(
1587 ApplyVolatileStatusInstruction {
1588 side_ref: side_ref,
1589 volatile_status: PokemonVolatileStatus::PERISH4,
1590 },
1591 ));
1592 side.volatile_statuses
1593 .insert(PokemonVolatileStatus::PERISH4);
1594 }
1595 }
1596 }
1597 Choices::TRICK | Choices::SWITCHEROO => {
1598 let defender_has_sub = defending_side
1599 .volatile_statuses
1600 .contains(&PokemonVolatileStatus::SUBSTITUTE);
1601 let attacker = attacking_side.get_active();
1602 let defender = defending_side.get_active();
1603 let attacker_item = attacker.item;
1604 let defender_item = defender.item;
1605 if attacker_item == defender_item || !defender.item_can_be_removed() || defender_has_sub
1606 {
1607 return;
1608 }
1609 let change_attacker_item_instruction = Instruction::ChangeItem(ChangeItemInstruction {
1610 side_ref: *attacking_side_ref,
1611 current_item: attacker_item,
1612 new_item: defender_item,
1613 });
1614 let change_defender_item_instruction = Instruction::ChangeItem(ChangeItemInstruction {
1615 side_ref: attacking_side_ref.get_other_side(),
1616 current_item: defender_item,
1617 new_item: attacker_item,
1618 });
1619 attacker.item = defender_item;
1620 defender.item = attacker_item;
1621 instructions
1622 .instruction_list
1623 .push(change_attacker_item_instruction);
1624 instructions
1625 .instruction_list
1626 .push(change_defender_item_instruction);
1627 }
1628 Choices::SUNNYDAY => {
1629 if state.weather.weather_type != Weather::SUN {
1630 instructions
1631 .instruction_list
1632 .push(Instruction::ChangeWeather(ChangeWeather {
1633 new_weather: Weather::SUN,
1634 new_weather_turns_remaining: 5,
1635 previous_weather: state.weather.weather_type,
1636 previous_weather_turns_remaining: state.weather.turns_remaining,
1637 }));
1638 state.weather.weather_type = Weather::SUN;
1639 state.weather.turns_remaining = 5;
1640 }
1641 }
1642 Choices::RAINDANCE => {
1643 if state.weather.weather_type != Weather::RAIN {
1644 instructions
1645 .instruction_list
1646 .push(Instruction::ChangeWeather(ChangeWeather {
1647 new_weather: Weather::RAIN,
1648 new_weather_turns_remaining: 5,
1649 previous_weather: state.weather.weather_type,
1650 previous_weather_turns_remaining: state.weather.turns_remaining,
1651 }));
1652 state.weather.weather_type = Weather::RAIN;
1653 state.weather.turns_remaining = 5;
1654 }
1655 }
1656 Choices::SANDSTORM => {
1657 if state.weather.weather_type != Weather::SAND {
1658 instructions
1659 .instruction_list
1660 .push(Instruction::ChangeWeather(ChangeWeather {
1661 new_weather: Weather::SAND,
1662 new_weather_turns_remaining: 5,
1663 previous_weather: state.weather.weather_type,
1664 previous_weather_turns_remaining: state.weather.turns_remaining,
1665 }));
1666 state.weather.weather_type = Weather::SAND;
1667 state.weather.turns_remaining = 5;
1668 }
1669 }
1670 Choices::HAIL => {
1671 if state.weather.weather_type != Weather::HAIL {
1672 instructions
1673 .instruction_list
1674 .push(Instruction::ChangeWeather(ChangeWeather {
1675 new_weather: Weather::HAIL,
1676 new_weather_turns_remaining: 5,
1677 previous_weather: state.weather.weather_type,
1678 previous_weather_turns_remaining: state.weather.turns_remaining,
1679 }));
1680 state.weather.weather_type = Weather::HAIL;
1681 state.weather.turns_remaining = 5;
1682 }
1683 }
1684 Choices::SNOWSCAPE | Choices::CHILLYRECEPTION => {
1685 if state.weather.weather_type != Weather::SNOW {
1686 instructions
1687 .instruction_list
1688 .push(Instruction::ChangeWeather(ChangeWeather {
1689 new_weather: Weather::SNOW,
1690 new_weather_turns_remaining: 5,
1691 previous_weather: state.weather.weather_type,
1692 previous_weather_turns_remaining: state.weather.turns_remaining,
1693 }));
1694 state.weather.weather_type = Weather::SNOW;
1695 state.weather.turns_remaining = 5;
1696 }
1697 }
1698 _ => {}
1699 }
1700}
1701
1702pub fn charge_choice_to_volatile(choice: &Choices) -> PokemonVolatileStatus {
1703 match choice {
1704 Choices::BOUNCE => PokemonVolatileStatus::BOUNCE,
1705 Choices::DIG => PokemonVolatileStatus::DIG,
1706 Choices::DIVE => PokemonVolatileStatus::DIVE,
1707 Choices::FLY => PokemonVolatileStatus::FLY,
1708 Choices::FREEZESHOCK => PokemonVolatileStatus::FREEZESHOCK,
1709 Choices::GEOMANCY => PokemonVolatileStatus::GEOMANCY,
1710 Choices::ICEBURN => PokemonVolatileStatus::ICEBURN,
1711 Choices::METEORBEAM => PokemonVolatileStatus::METEORBEAM,
1712 Choices::ELECTROSHOT => PokemonVolatileStatus::ELECTROSHOT,
1713 Choices::PHANTOMFORCE => PokemonVolatileStatus::PHANTOMFORCE,
1714 Choices::RAZORWIND => PokemonVolatileStatus::RAZORWIND,
1715 Choices::SHADOWFORCE => PokemonVolatileStatus::SHADOWFORCE,
1716 Choices::SKULLBASH => PokemonVolatileStatus::SKULLBASH,
1717 Choices::SKYATTACK => PokemonVolatileStatus::SKYATTACK,
1718 Choices::SKYDROP => PokemonVolatileStatus::SKYDROP,
1719 Choices::SOLARBEAM => PokemonVolatileStatus::SOLARBEAM,
1720 Choices::SOLARBLADE => PokemonVolatileStatus::SOLARBLADE,
1721 _ => {
1722 panic!("Invalid choice for charge: {:?}", choice)
1723 }
1724 }
1725}
1726
1727pub fn charge_volatile_to_choice(volatile: &PokemonVolatileStatus) -> Option<Choices> {
1728 match volatile {
1729 PokemonVolatileStatus::BOUNCE => Some(Choices::BOUNCE),
1730 PokemonVolatileStatus::DIG => Some(Choices::DIG),
1731 PokemonVolatileStatus::DIVE => Some(Choices::DIVE),
1732 PokemonVolatileStatus::FLY => Some(Choices::FLY),
1733 PokemonVolatileStatus::FREEZESHOCK => Some(Choices::FREEZESHOCK),
1734 PokemonVolatileStatus::GEOMANCY => Some(Choices::GEOMANCY),
1735 PokemonVolatileStatus::ICEBURN => Some(Choices::ICEBURN),
1736 PokemonVolatileStatus::METEORBEAM => Some(Choices::METEORBEAM),
1737 PokemonVolatileStatus::ELECTROSHOT => Some(Choices::ELECTROSHOT),
1738 PokemonVolatileStatus::PHANTOMFORCE => Some(Choices::PHANTOMFORCE),
1739 PokemonVolatileStatus::RAZORWIND => Some(Choices::RAZORWIND),
1740 PokemonVolatileStatus::SHADOWFORCE => Some(Choices::SHADOWFORCE),
1741 PokemonVolatileStatus::SKULLBASH => Some(Choices::SKULLBASH),
1742 PokemonVolatileStatus::SKYATTACK => Some(Choices::SKYATTACK),
1743 PokemonVolatileStatus::SKYDROP => Some(Choices::SKYDROP),
1744 PokemonVolatileStatus::SOLARBEAM => Some(Choices::SOLARBEAM),
1745 PokemonVolatileStatus::SOLARBLADE => Some(Choices::SOLARBLADE),
1746 _ => None,
1747 }
1748}