poke_engine/genx/
choice_effects.rs

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            // percentages are a hack and are incorrect in situations
57            // where one or more status effects are not possible
58            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            // this gives the correct result even though it's not the "correct" way to implement it
129            // reflect is only removed if the move hits, but I don't have a way to check that
130            // doubling the power ensures the same damage calculation
131            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            // Technically not correct because of reviving moves but good enough
160            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; // 1.5x for being in sun, 2x for cancelling out rain debuff
240            }
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            // Bonus damage still applies if substitute is hit
461            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; // 2x for being super effective, 2x for nullifying water resistance
561            }
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    // Recharging and truant are mutually exclusive, with recharge taking priority
640    } 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    // gens 2-6 destinybond is only removed if you are not using destinybond
839    // destinybond is preserved, even if used twice in a row
840    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    // gens 7+ destinybond cannot be used if destinybond is active
867    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}