poke_engine/genx/
generate_instructions.rs

1use super::abilities::{
2    ability_after_damage_hit, ability_before_move, ability_end_of_turn,
3    ability_modify_attack_against, ability_modify_attack_being_used, ability_on_switch_in,
4    ability_on_switch_out, Abilities,
5};
6use super::choice_effects::{
7    charge_choice_to_volatile, choice_after_damage_hit, choice_before_move, choice_hazard_clear,
8    choice_special_effect, modify_choice,
9};
10use crate::choices::{
11    Boost, Choices, Effect, Heal, MoveTarget, MultiHitMove, Secondary, SideCondition, StatBoosts,
12    Status, VolatileStatus, MOVES,
13};
14use crate::instruction::{
15    ApplyVolatileStatusInstruction, BoostInstruction, ChangeDamageDealtDamageInstruction,
16    ChangeDamageDealtMoveCategoryInstruction, ChangeItemInstruction,
17    ChangeSideConditionInstruction, ChangeTerrain, ChangeType,
18    ChangeVolatileStatusDurationInstruction, ChangeWeather, DecrementRestTurnsInstruction,
19    DecrementWishInstruction, HealInstruction, RemoveVolatileStatusInstruction,
20    SetSecondMoveSwitchOutMoveInstruction, SetSleepTurnsInstruction, ToggleBatonPassingInstruction,
21    ToggleDamageDealtHitSubstituteInstruction, ToggleShedTailingInstruction,
22    ToggleTrickRoomInstruction,
23};
24use crate::instruction::{ChangeAbilityInstruction, ToggleTerastallizedInstruction};
25use crate::instruction::{DecrementFutureSightInstruction, FormeChangeInstruction};
26use crate::instruction::{DecrementPPInstruction, SetLastUsedMoveInstruction};
27
28use super::damage_calc::calculate_futuresight_damage;
29use super::damage_calc::{calculate_damage, type_effectiveness_modifier, DamageRolls};
30use super::items::{
31    item_before_move, item_end_of_turn, item_modify_attack_against, item_modify_attack_being_used,
32    item_on_switch_in, Items,
33};
34use super::state::{MoveChoice, PokemonVolatileStatus, Terrain, Weather};
35use crate::choices::{Choice, MoveCategory};
36use crate::instruction::{
37    ChangeStatusInstruction, DamageInstruction, Instruction, StateInstructions, SwitchInstruction,
38};
39use crate::state::{
40    LastUsedMove, PokemonBoostableStat, PokemonIndex, PokemonMoveIndex, PokemonSideCondition,
41    PokemonStatus, PokemonType, Side, SideMovesFirst, SideReference, State,
42};
43use std::cmp;
44
45#[cfg(feature = "terastallization")]
46use crate::choices::MultiAccuracyMove;
47
48#[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5", feature = "gen6"))]
49pub const BASE_CRIT_CHANCE: f32 = 1.0 / 16.0;
50
51#[cfg(any(feature = "gen7", feature = "gen8", feature = "gen9"))]
52pub const BASE_CRIT_CHANCE: f32 = 1.0 / 24.0;
53
54#[cfg(any(feature = "gen3", feature = "gen4"))]
55pub const MAX_SLEEP_TURNS: i8 = 4;
56
57#[cfg(any(
58    feature = "gen5",
59    feature = "gen6",
60    feature = "gen7",
61    feature = "gen8",
62    feature = "gen9"
63))]
64pub const MAX_SLEEP_TURNS: i8 = 3;
65
66#[cfg(any(feature = "gen7", feature = "gen8", feature = "gen9"))]
67pub const HIT_SELF_IN_CONFUSION_CHANCE: f32 = 1.0 / 3.0;
68
69#[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5", feature = "gen6"))]
70pub const HIT_SELF_IN_CONFUSION_CHANCE: f32 = 1.0 / 2.0;
71
72#[cfg(any(
73    feature = "gen5",
74    feature = "gen6",
75    feature = "gen7",
76    feature = "gen8",
77    feature = "gen9"
78))]
79pub const CONSECUTIVE_PROTECT_CHANCE: f32 = 1.0 / 3.0;
80
81#[cfg(any(feature = "gen3", feature = "gen4"))]
82pub const CONSECUTIVE_PROTECT_CHANCE: f32 = 1.0 / 2.0;
83
84pub const SIDE_CONDITION_DURATION: i8 = 5;
85pub const TAILWIND_DURATION: i8 = 4;
86
87const PROTECT_VOLATILES: [PokemonVolatileStatus; 6] = [
88    PokemonVolatileStatus::PROTECT,
89    PokemonVolatileStatus::BANEFULBUNKER,
90    PokemonVolatileStatus::BURNINGBULWARK,
91    PokemonVolatileStatus::SPIKYSHIELD,
92    PokemonVolatileStatus::SILKTRAP,
93    PokemonVolatileStatus::ENDURE,
94];
95
96fn chance_to_wake_up(turns_asleep: i8) -> f32 {
97    if turns_asleep == 0 {
98        0.0
99    } else {
100        1.0 / (1 + MAX_SLEEP_TURNS - turns_asleep) as f32
101    }
102}
103
104fn set_last_used_move_as_switch(
105    side: &mut Side,
106    new_pokemon_index: PokemonIndex,
107    switching_side_ref: SideReference,
108    incoming_instructions: &mut StateInstructions,
109) {
110    incoming_instructions
111        .instruction_list
112        .push(Instruction::SetLastUsedMove(SetLastUsedMoveInstruction {
113            side_ref: switching_side_ref,
114            last_used_move: LastUsedMove::Switch(new_pokemon_index),
115            previous_last_used_move: side.last_used_move,
116        }));
117    side.last_used_move = LastUsedMove::Switch(new_pokemon_index);
118}
119
120fn set_last_used_move_as_move(
121    side: &mut Side,
122    used_move: PokemonMoveIndex,
123    switching_side_ref: SideReference,
124    incoming_instructions: &mut StateInstructions,
125) {
126    if side
127        .volatile_statuses
128        .contains(&PokemonVolatileStatus::FLINCH)
129    {
130        // if we were flinched after just switching in we don't want our last used move to be switch
131        // this makes sure fakeout/firstimpression can't be used on the following turn
132        if matches!(side.last_used_move, LastUsedMove::Switch(_)) {
133            incoming_instructions
134                .instruction_list
135                .push(Instruction::SetLastUsedMove(SetLastUsedMoveInstruction {
136                    side_ref: switching_side_ref,
137                    last_used_move: LastUsedMove::None,
138                    previous_last_used_move: side.last_used_move,
139                }));
140            side.last_used_move = LastUsedMove::None;
141        }
142        return;
143    }
144    match side.last_used_move {
145        LastUsedMove::Move(last_used_move) => {
146            if last_used_move == used_move {
147                return;
148            }
149        }
150        _ => {}
151    }
152    incoming_instructions
153        .instruction_list
154        .push(Instruction::SetLastUsedMove(SetLastUsedMoveInstruction {
155            side_ref: switching_side_ref,
156            last_used_move: LastUsedMove::Move(used_move),
157            previous_last_used_move: side.last_used_move,
158        }));
159    side.last_used_move = LastUsedMove::Move(used_move);
160}
161
162fn generate_instructions_from_switch(
163    state: &mut State,
164    new_pokemon_index: PokemonIndex,
165    switching_side_ref: SideReference,
166    incoming_instructions: &mut StateInstructions,
167) {
168    let should_last_used_move = state.use_last_used_move;
169    state.apply_instructions(&incoming_instructions.instruction_list);
170
171    let (side, opposite_side) = state.get_both_sides(&switching_side_ref);
172    if side.force_switch {
173        side.force_switch = false;
174        match switching_side_ref {
175            SideReference::SideOne => {
176                incoming_instructions
177                    .instruction_list
178                    .push(Instruction::ToggleSideOneForceSwitch);
179            }
180            SideReference::SideTwo => {
181                incoming_instructions
182                    .instruction_list
183                    .push(Instruction::ToggleSideTwoForceSwitch);
184            }
185        }
186    }
187
188    let mut baton_passing = false;
189    if side.baton_passing {
190        baton_passing = true;
191        side.baton_passing = false;
192        match switching_side_ref {
193            SideReference::SideOne => {
194                incoming_instructions
195                    .instruction_list
196                    .push(Instruction::ToggleBatonPassing(
197                        ToggleBatonPassingInstruction {
198                            side_ref: SideReference::SideOne,
199                        },
200                    ));
201            }
202            SideReference::SideTwo => {
203                incoming_instructions
204                    .instruction_list
205                    .push(Instruction::ToggleBatonPassing(
206                        ToggleBatonPassingInstruction {
207                            side_ref: SideReference::SideTwo,
208                        },
209                    ));
210            }
211        }
212    }
213
214    let mut shed_tailing = false;
215    if side.shed_tailing {
216        shed_tailing = true;
217        side.shed_tailing = false;
218        match switching_side_ref {
219            SideReference::SideOne => {
220                incoming_instructions
221                    .instruction_list
222                    .push(Instruction::ToggleShedTailing(
223                        ToggleShedTailingInstruction {
224                            side_ref: SideReference::SideOne,
225                        },
226                    ));
227            }
228            SideReference::SideTwo => {
229                incoming_instructions
230                    .instruction_list
231                    .push(Instruction::ToggleShedTailing(
232                        ToggleShedTailingInstruction {
233                            side_ref: SideReference::SideTwo,
234                        },
235                    ));
236            }
237        }
238    }
239
240    #[cfg(feature = "gen5")]
241    if side.get_active_immutable().status == PokemonStatus::SLEEP {
242        let current_active_index = side.active_index;
243        let active = side.get_active();
244        if active.rest_turns > 0 {
245            let current_rest_turns = active.rest_turns;
246            incoming_instructions
247                .instruction_list
248                .push(Instruction::SetRestTurns(SetSleepTurnsInstruction {
249                    side_ref: switching_side_ref,
250                    pokemon_index: current_active_index,
251                    new_turns: 3,
252                    previous_turns: current_rest_turns,
253                }));
254            active.rest_turns = 3
255        } else {
256            let current_sleep_turns = active.sleep_turns;
257            incoming_instructions
258                .instruction_list
259                .push(Instruction::SetSleepTurns(SetSleepTurnsInstruction {
260                    side_ref: switching_side_ref,
261                    pokemon_index: current_active_index,
262                    new_turns: 0,
263                    previous_turns: current_sleep_turns,
264                }));
265            active.sleep_turns = 0
266        }
267    }
268
269    if opposite_side
270        .volatile_statuses
271        .contains(&PokemonVolatileStatus::PARTIALLYTRAPPED)
272    {
273        incoming_instructions
274            .instruction_list
275            .push(Instruction::RemoveVolatileStatus(
276                RemoveVolatileStatusInstruction {
277                    side_ref: switching_side_ref.get_other_side(),
278                    volatile_status: PokemonVolatileStatus::PARTIALLYTRAPPED,
279                },
280            ));
281        opposite_side
282            .volatile_statuses
283            .remove(&PokemonVolatileStatus::PARTIALLYTRAPPED);
284    }
285
286    state.re_enable_disabled_moves(
287        &switching_side_ref,
288        &mut incoming_instructions.instruction_list,
289    );
290    state.remove_volatile_statuses_on_switch(
291        &switching_side_ref,
292        &mut incoming_instructions.instruction_list,
293        baton_passing,
294        shed_tailing,
295    );
296    state.reset_toxic_count(
297        &switching_side_ref,
298        &mut incoming_instructions.instruction_list,
299    );
300    if !baton_passing {
301        state.reset_boosts(
302            &switching_side_ref,
303            &mut incoming_instructions.instruction_list,
304        );
305    }
306
307    ability_on_switch_out(state, &switching_side_ref, incoming_instructions);
308
309    let switch_instruction = Instruction::Switch(SwitchInstruction {
310        side_ref: switching_side_ref,
311        previous_index: state.get_side(&switching_side_ref).active_index,
312        next_index: new_pokemon_index,
313    });
314
315    let side = state.get_side(&switching_side_ref);
316    side.active_index = new_pokemon_index;
317    incoming_instructions
318        .instruction_list
319        .push(switch_instruction);
320
321    if should_last_used_move {
322        set_last_used_move_as_switch(
323            side,
324            new_pokemon_index,
325            switching_side_ref,
326            incoming_instructions,
327        );
328    }
329
330    if side.side_conditions.healing_wish > 0 {
331        #[cfg(any(feature = "gen8", feature = "gen9"))]
332        let mut healing_wish_consumed = false;
333
334        #[cfg(any(
335            feature = "gen3",
336            feature = "gen4",
337            feature = "gen5",
338            feature = "gen6",
339            feature = "gen7"
340        ))]
341        let mut healing_wish_consumed = true;
342
343        let switched_in_pkmn = side.get_active();
344        if switched_in_pkmn.hp < switched_in_pkmn.maxhp {
345            let heal_amount = switched_in_pkmn.maxhp - switched_in_pkmn.hp;
346            let heal_instruction = Instruction::Heal(HealInstruction {
347                side_ref: switching_side_ref,
348                heal_amount,
349            });
350            incoming_instructions
351                .instruction_list
352                .push(heal_instruction);
353            switched_in_pkmn.hp += heal_amount;
354            healing_wish_consumed = true;
355        }
356        if switched_in_pkmn.status != PokemonStatus::NONE {
357            add_remove_status_instructions(
358                incoming_instructions,
359                new_pokemon_index,
360                switching_side_ref,
361                side,
362            );
363            healing_wish_consumed = true;
364        }
365
366        if healing_wish_consumed {
367            incoming_instructions
368                .instruction_list
369                .push(Instruction::ChangeSideCondition(
370                    ChangeSideConditionInstruction {
371                        side_ref: switching_side_ref,
372                        side_condition: PokemonSideCondition::HealingWish,
373                        amount: -1 * side.side_conditions.healing_wish,
374                    },
375                ));
376            side.side_conditions.healing_wish = 0;
377        }
378    }
379
380    let active = side.get_active_immutable();
381    if active.item != Items::HEAVYDUTYBOOTS {
382        let switched_in_pkmn = side.get_active_immutable();
383        if side.side_conditions.sticky_web == 1 && switched_in_pkmn.is_grounded() {
384            // a pkmn switching in doesn't have any other speed drops,
385            // so no need to check for going below -6
386            apply_boost_instruction(
387                side,
388                &PokemonBoostableStat::Speed,
389                &-1,
390                &switching_side_ref,
391                &switching_side_ref,
392                incoming_instructions,
393            );
394        }
395
396        let side = state.get_side_immutable(&switching_side_ref);
397        let switched_in_pkmn = side.get_active_immutable();
398        let mut toxic_spike_instruction: Option<Instruction> = None;
399        if side.side_conditions.toxic_spikes > 0 && switched_in_pkmn.is_grounded() {
400            if !immune_to_status(
401                &state,
402                &MoveTarget::User,
403                &switching_side_ref,
404                &PokemonStatus::POISON,
405            ) {
406                if side.side_conditions.toxic_spikes == 1 {
407                    toxic_spike_instruction =
408                        Some(Instruction::ChangeStatus(ChangeStatusInstruction {
409                            side_ref: switching_side_ref,
410                            pokemon_index: side.active_index,
411                            old_status: switched_in_pkmn.status,
412                            new_status: PokemonStatus::POISON,
413                        }))
414                } else if side.side_conditions.toxic_spikes == 2 {
415                    toxic_spike_instruction =
416                        Some(Instruction::ChangeStatus(ChangeStatusInstruction {
417                            side_ref: switching_side_ref,
418                            pokemon_index: side.active_index,
419                            old_status: switched_in_pkmn.status,
420                            new_status: PokemonStatus::TOXIC,
421                        }))
422                }
423            } else if switched_in_pkmn.has_type(&PokemonType::POISON) {
424                toxic_spike_instruction = Some(Instruction::ChangeSideCondition(
425                    ChangeSideConditionInstruction {
426                        side_ref: switching_side_ref,
427                        side_condition: PokemonSideCondition::ToxicSpikes,
428                        amount: -1 * side.side_conditions.toxic_spikes,
429                    },
430                ))
431            }
432
433            if let Some(i) = toxic_spike_instruction {
434                state.apply_one_instruction(&i);
435                incoming_instructions.instruction_list.push(i);
436            }
437        }
438
439        let side = state.get_side(&switching_side_ref);
440        let active = side.get_active_immutable();
441        if active.ability != Abilities::MAGICGUARD {
442            if side.side_conditions.stealth_rock == 1 {
443                let switched_in_pkmn = side.get_active();
444                let multiplier = type_effectiveness_modifier(&PokemonType::ROCK, &switched_in_pkmn);
445
446                let dmg_amount = cmp::min(
447                    (switched_in_pkmn.maxhp as f32 * multiplier / 8.0) as i16,
448                    switched_in_pkmn.hp,
449                );
450                let stealth_rock_dmg_instruction = Instruction::Damage(DamageInstruction {
451                    side_ref: switching_side_ref,
452                    damage_amount: dmg_amount,
453                });
454                switched_in_pkmn.hp -= dmg_amount;
455                incoming_instructions
456                    .instruction_list
457                    .push(stealth_rock_dmg_instruction);
458            }
459
460            let switched_in_pkmn = side.get_active_immutable();
461            if side.side_conditions.spikes > 0 && switched_in_pkmn.is_grounded() {
462                let dmg_amount = cmp::min(
463                    switched_in_pkmn.maxhp * side.side_conditions.spikes as i16 / 8,
464                    switched_in_pkmn.hp,
465                );
466                let spikes_dmg_instruction = Instruction::Damage(DamageInstruction {
467                    side_ref: switching_side_ref,
468                    damage_amount: dmg_amount,
469                });
470                side.get_active().hp -= dmg_amount;
471                incoming_instructions
472                    .instruction_list
473                    .push(spikes_dmg_instruction);
474            }
475        }
476    }
477
478    ability_on_switch_in(state, &switching_side_ref, incoming_instructions);
479    item_on_switch_in(state, &switching_side_ref, incoming_instructions);
480
481    state.reverse_instructions(&incoming_instructions.instruction_list);
482}
483
484fn generate_instructions_from_increment_side_condition(
485    state: &mut State,
486    side_condition: &SideCondition,
487    attacking_side_reference: &SideReference,
488    incoming_instructions: &mut StateInstructions,
489) {
490    let affected_side_ref;
491    match side_condition.target {
492        MoveTarget::Opponent => affected_side_ref = attacking_side_reference.get_other_side(),
493        MoveTarget::User => affected_side_ref = *attacking_side_reference,
494    }
495
496    let max_layers = match side_condition.condition {
497        PokemonSideCondition::Spikes => 3,
498        PokemonSideCondition::ToxicSpikes => 2,
499        _ => 1,
500    };
501
502    let affected_side = state.get_side(&affected_side_ref);
503    if affected_side.get_side_condition(side_condition.condition) < max_layers {
504        let ins = Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
505            side_ref: affected_side_ref,
506            side_condition: side_condition.condition,
507            amount: 1,
508        });
509        affected_side.update_side_condition(side_condition.condition, 1);
510        incoming_instructions.instruction_list.push(ins);
511    }
512}
513
514fn generate_instructions_from_duration_side_conditions(
515    state: &mut State,
516    side_condition: &SideCondition,
517    attacking_side_reference: &SideReference,
518    incoming_instructions: &mut StateInstructions,
519    duration: i8,
520) {
521    let affected_side_ref = match side_condition.target {
522        MoveTarget::Opponent => attacking_side_reference.get_other_side(),
523        MoveTarget::User => *attacking_side_reference,
524    };
525    if side_condition.condition == PokemonSideCondition::AuroraVeil
526        && !state.weather_is_active(&Weather::HAIL)
527        && !state.weather_is_active(&Weather::SNOW)
528    {
529        return;
530    }
531    let affected_side = state.get_side(&affected_side_ref);
532    if affected_side.get_side_condition(side_condition.condition) == 0 {
533        let ins = Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
534            side_ref: affected_side_ref,
535            side_condition: side_condition.condition,
536            amount: duration,
537        });
538        affected_side.update_side_condition(side_condition.condition, duration);
539        incoming_instructions.instruction_list.push(ins);
540    }
541}
542
543fn generate_instructions_from_side_conditions(
544    state: &mut State,
545    side_condition: &SideCondition,
546    attacking_side_reference: &SideReference,
547    incoming_instructions: &mut StateInstructions,
548) {
549    match side_condition.condition {
550        PokemonSideCondition::AuroraVeil
551        | PokemonSideCondition::LightScreen
552        | PokemonSideCondition::Reflect
553        | PokemonSideCondition::Safeguard
554        | PokemonSideCondition::Mist => {
555            generate_instructions_from_duration_side_conditions(
556                state,
557                side_condition,
558                attacking_side_reference,
559                incoming_instructions,
560                SIDE_CONDITION_DURATION,
561            );
562        }
563        PokemonSideCondition::Tailwind => {
564            generate_instructions_from_duration_side_conditions(
565                state,
566                side_condition,
567                attacking_side_reference,
568                incoming_instructions,
569                TAILWIND_DURATION,
570            );
571        }
572        _ => generate_instructions_from_increment_side_condition(
573            state,
574            side_condition,
575            attacking_side_reference,
576            incoming_instructions,
577        ),
578    }
579}
580
581fn get_instructions_from_volatile_statuses(
582    state: &mut State,
583    attacker_choice: &Choice,
584    volatile_status: &VolatileStatus,
585    attacking_side_reference: &SideReference,
586    incoming_instructions: &mut StateInstructions,
587) {
588    let target_side: SideReference;
589    match volatile_status.target {
590        MoveTarget::Opponent => target_side = attacking_side_reference.get_other_side(),
591        MoveTarget::User => target_side = *attacking_side_reference,
592    }
593
594    if volatile_status.volatile_status == PokemonVolatileStatus::YAWN
595        && immune_to_status(
596            state,
597            &MoveTarget::Opponent,
598            &target_side,
599            &PokemonStatus::SLEEP,
600        )
601    {
602        return;
603    }
604    let side = state.get_side(&target_side);
605    let affected_pkmn = side.get_active_immutable();
606    if affected_pkmn.volatile_status_can_be_applied(
607        &volatile_status.volatile_status,
608        &side.volatile_statuses,
609        attacker_choice.first_move,
610    ) {
611        let ins = Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
612            side_ref: target_side,
613            volatile_status: volatile_status.volatile_status,
614        });
615
616        side.volatile_statuses
617            .insert(volatile_status.volatile_status);
618        incoming_instructions.instruction_list.push(ins);
619    }
620}
621
622pub fn add_remove_status_instructions(
623    incoming_instructions: &mut StateInstructions,
624    pokemon_index: PokemonIndex,
625    side_reference: SideReference,
626    side: &mut Side,
627) {
628    /*
629    Single place to check for status removals, add the necessary instructions, and update the pokemon's status
630
631    This is necessary because of some side effects to removing statuses
632    i.e. a pre-mature wake-up from rest must set rest_turns to 0
633    */
634    let pkmn = &mut side.pokemon[pokemon_index];
635    incoming_instructions
636        .instruction_list
637        .push(Instruction::ChangeStatus(ChangeStatusInstruction {
638            side_ref: side_reference,
639            pokemon_index: pokemon_index,
640            old_status: pkmn.status,
641            new_status: PokemonStatus::NONE,
642        }));
643    match pkmn.status {
644        PokemonStatus::SLEEP => {
645            if pkmn.rest_turns > 0 {
646                incoming_instructions
647                    .instruction_list
648                    .push(Instruction::SetRestTurns(SetSleepTurnsInstruction {
649                        side_ref: side_reference,
650                        pokemon_index,
651                        new_turns: 0,
652                        previous_turns: pkmn.rest_turns,
653                    }));
654                pkmn.rest_turns = 0;
655            } else if pkmn.sleep_turns > 0 {
656                incoming_instructions
657                    .instruction_list
658                    .push(Instruction::SetSleepTurns(SetSleepTurnsInstruction {
659                        side_ref: side_reference,
660                        pokemon_index,
661                        new_turns: 0,
662                        previous_turns: pkmn.sleep_turns,
663                    }));
664                pkmn.sleep_turns = 0;
665            }
666        }
667        PokemonStatus::TOXIC => {
668            if side.side_conditions.toxic_count != 0 {
669                incoming_instructions
670                    .instruction_list
671                    .push(Instruction::ChangeSideCondition(
672                        ChangeSideConditionInstruction {
673                            side_ref: side_reference,
674                            side_condition: PokemonSideCondition::ToxicCount,
675                            amount: -1 * side.side_conditions.toxic_count,
676                        },
677                    ));
678                side.side_conditions.toxic_count = 0;
679            }
680        }
681        _ => {}
682    }
683    pkmn.status = PokemonStatus::NONE;
684}
685
686pub fn immune_to_status(
687    state: &State,
688    status_target: &MoveTarget,
689    target_side_ref: &SideReference,
690    status: &PokemonStatus,
691) -> bool {
692    let (target_side, attacking_side) = state.get_both_sides_immutable(target_side_ref);
693    let target_pkmn = target_side.get_active_immutable();
694    let attacking_pkmn = attacking_side.get_active_immutable();
695
696    // General Status Immunity
697    match target_pkmn.ability {
698        Abilities::SHIELDSDOWN => return target_pkmn.hp > target_pkmn.maxhp / 2,
699        Abilities::PURIFYINGSALT => return true,
700        Abilities::COMATOSE => return true,
701        Abilities::LEAFGUARD => return state.weather_is_active(&Weather::SUN),
702        _ => {}
703    }
704
705    if target_pkmn.status != PokemonStatus::NONE || target_pkmn.hp <= 0 {
706        true
707    } else if state.terrain.terrain_type == Terrain::MISTYTERRAIN && target_pkmn.is_grounded() {
708        true
709    } else if (target_side
710        .volatile_statuses
711        .contains(&PokemonVolatileStatus::SUBSTITUTE)
712        || target_side.side_conditions.safeguard > 0)
713        && status_target == &MoveTarget::Opponent
714    // substitute/safeguard don't block if the target is yourself (eg. rest)
715    {
716        true
717    } else {
718        // Specific status immunity
719        match status {
720            PokemonStatus::BURN => {
721                target_pkmn.has_type(&PokemonType::FIRE)
722                    || [
723                        Abilities::WATERVEIL,
724                        Abilities::WATERBUBBLE,
725                        Abilities::THERMALEXCHANGE,
726                    ]
727                    .contains(&target_pkmn.ability)
728            }
729            PokemonStatus::FREEZE => {
730                target_pkmn.has_type(&PokemonType::ICE)
731                    || target_pkmn.ability == Abilities::MAGMAARMOR
732                    || state.weather_is_active(&Weather::SUN)
733                    || state.weather_is_active(&Weather::HARSHSUN)
734            }
735            PokemonStatus::SLEEP => {
736                (state.terrain.terrain_type == Terrain::ELECTRICTERRAIN
737                    && target_pkmn.is_grounded())
738                    || [
739                        Abilities::INSOMNIA,
740                        Abilities::SWEETVEIL,
741                        Abilities::VITALSPIRIT,
742                    ]
743                    .contains(&target_pkmn.ability)
744                    || (status_target == &MoveTarget::Opponent
745                        && target_side.has_alive_non_rested_sleeping_pkmn())
746                // sleep clause
747            }
748
749            #[cfg(any(feature = "gen6", feature = "gen7", feature = "gen8", feature = "gen9"))]
750            PokemonStatus::PARALYZE => {
751                target_pkmn.has_type(&PokemonType::ELECTRIC)
752                    || target_pkmn.ability == Abilities::LIMBER
753            }
754
755            #[cfg(any(feature = "gen4", feature = "gen5", feature = "gen3"))]
756            PokemonStatus::PARALYZE => target_pkmn.ability == Abilities::LIMBER,
757
758            PokemonStatus::POISON | PokemonStatus::TOXIC => {
759                ((target_pkmn.has_type(&PokemonType::POISON)
760                    || target_pkmn.has_type(&PokemonType::STEEL))
761                    && attacking_pkmn.ability != Abilities::CORROSION)
762                    || [Abilities::IMMUNITY, Abilities::PASTELVEIL].contains(&target_pkmn.ability)
763            }
764            _ => false,
765        }
766    }
767}
768
769fn get_instructions_from_status_effects(
770    state: &mut State,
771    status: &Status,
772    attacking_side_reference: &SideReference,
773    incoming_instructions: &mut StateInstructions,
774    hit_sub: bool,
775) {
776    let target_side_ref: SideReference;
777    match status.target {
778        MoveTarget::Opponent => target_side_ref = attacking_side_reference.get_other_side(),
779        MoveTarget::User => target_side_ref = *attacking_side_reference,
780    }
781
782    if hit_sub || immune_to_status(state, &status.target, &target_side_ref, &status.status) {
783        return;
784    }
785
786    let target_side = state.get_side(&target_side_ref);
787    let target_side_active = target_side.active_index;
788    let target_pkmn = target_side.get_active();
789
790    let instruction = if target_pkmn.item == Items::LUMBERRY {
791        target_pkmn.item = Items::NONE;
792        Instruction::ChangeItem(ChangeItemInstruction {
793            side_ref: target_side_ref,
794            current_item: Items::LUMBERRY,
795            new_item: Items::NONE,
796        })
797    } else if target_pkmn.item == Items::CHESTOBERRY && status.status == PokemonStatus::SLEEP {
798        target_pkmn.item = Items::NONE;
799        Instruction::ChangeItem(ChangeItemInstruction {
800            side_ref: target_side_ref,
801            current_item: Items::CHESTOBERRY,
802            new_item: Items::NONE,
803        })
804    } else {
805        let old_status = target_pkmn.status;
806        target_pkmn.status = status.status;
807        Instruction::ChangeStatus(ChangeStatusInstruction {
808            side_ref: target_side_ref,
809            pokemon_index: target_side_active,
810            old_status,
811            new_status: status.status,
812        })
813    };
814    incoming_instructions.instruction_list.push(instruction);
815}
816
817pub fn get_boost_amount(side: &Side, boost: &PokemonBoostableStat, amount: i8) -> i8 {
818    /*
819    returns that amount that can actually be applied from the attempted boost amount
820        e.g. using swordsdance at +5 attack would result in a +1 boost instead of +2
821    */
822    let current_boost = side.get_boost_from_boost_enum(boost);
823
824    if amount > 0 {
825        return cmp::min(6 - current_boost, amount);
826    } else if amount < 0 {
827        return cmp::max(-6 - current_boost, amount);
828    }
829    0
830}
831
832pub fn apply_boost_instruction(
833    target_side: &mut Side,
834    stat: &PokemonBoostableStat,
835    boost: &i8,
836    attacking_side_ref: &SideReference,
837    target_side_ref: &SideReference,
838    instructions: &mut StateInstructions,
839) -> bool {
840    // Single point for checking whether a boost can be applied to a pokemon
841    // along with side effects that that boost
842    // applies the boost & side effects if applicable
843    // returns whether the requested boost was actually applied
844    let mut boost_was_applied = false;
845    let target_pkmn = target_side.get_active_immutable();
846    let target_pkmn_ability = target_pkmn.ability;
847
848    if boost != &0
849        && !(target_side_ref != attacking_side_ref
850            && target_pkmn
851                .immune_to_stats_lowered_by_opponent(&stat, &target_side.volatile_statuses))
852        && target_pkmn.hp != 0
853    {
854        let mut boost_amount = *boost;
855        if target_pkmn_ability == Abilities::CONTRARY {
856            boost_amount *= -1;
857        }
858        boost_amount = get_boost_amount(target_side, &stat, boost_amount);
859        if boost_amount != 0 {
860            boost_was_applied = true;
861            match stat {
862                PokemonBoostableStat::Attack => target_side.attack_boost += boost_amount,
863                PokemonBoostableStat::Defense => target_side.defense_boost += boost_amount,
864                PokemonBoostableStat::SpecialAttack => {
865                    target_side.special_attack_boost += boost_amount
866                }
867                PokemonBoostableStat::SpecialDefense => {
868                    target_side.special_defense_boost += boost_amount
869                }
870                PokemonBoostableStat::Speed => target_side.speed_boost += boost_amount,
871                PokemonBoostableStat::Evasion => target_side.accuracy_boost += boost_amount,
872                PokemonBoostableStat::Accuracy => target_side.evasion_boost += boost_amount,
873            }
874            instructions
875                .instruction_list
876                .push(Instruction::Boost(BoostInstruction {
877                    side_ref: *target_side_ref,
878                    stat: *stat,
879                    amount: boost_amount,
880                }));
881
882            if boost_amount < 0 {
883                if target_pkmn_ability == Abilities::DEFIANT
884                    && attacking_side_ref != target_side_ref
885                    && target_side.attack_boost < 6
886                {
887                    let defiant_boost_amount = cmp::min(6 - target_side.attack_boost, 2);
888                    target_side.attack_boost += defiant_boost_amount;
889                    instructions
890                        .instruction_list
891                        .push(Instruction::Boost(BoostInstruction {
892                            side_ref: *target_side_ref,
893                            stat: PokemonBoostableStat::Attack,
894                            amount: defiant_boost_amount,
895                        }));
896                } else if target_pkmn_ability == Abilities::COMPETITIVE
897                    && attacking_side_ref != target_side_ref
898                    && target_side.special_attack_boost < 6
899                {
900                    let competitive_boost_amount =
901                        cmp::min(6 - target_side.special_attack_boost, 2);
902                    target_side.special_attack_boost += competitive_boost_amount;
903                    instructions
904                        .instruction_list
905                        .push(Instruction::Boost(BoostInstruction {
906                            side_ref: *target_side_ref,
907                            stat: PokemonBoostableStat::SpecialAttack,
908                            amount: competitive_boost_amount,
909                        }));
910                }
911            }
912        }
913    }
914    boost_was_applied
915}
916
917fn get_instructions_from_boosts(
918    state: &mut State,
919    boosts: &Boost,
920    attacking_side_reference: &SideReference,
921    incoming_instructions: &mut StateInstructions,
922) {
923    let target_side_ref: SideReference;
924    match boosts.target {
925        MoveTarget::Opponent => target_side_ref = attacking_side_reference.get_other_side(),
926        MoveTarget::User => target_side_ref = *attacking_side_reference,
927    }
928    let boostable_stats = boosts.boosts.get_as_pokemon_boostable();
929    for (pkmn_boostable_stat, boost) in boostable_stats.iter().filter(|(_, b)| b != &0) {
930        let side = state.get_side(&target_side_ref);
931        apply_boost_instruction(
932            side,
933            pkmn_boostable_stat,
934            boost,
935            attacking_side_reference,
936            &target_side_ref,
937            incoming_instructions,
938        );
939    }
940}
941
942fn compare_health_with_damage_multiples(max_damage: i16, health: i16) -> (i16, i16) {
943    let max_damage_f32 = max_damage as f32;
944    let health_f32 = health as f32;
945
946    let mut total_less_than = 0;
947    let mut num_less_than = 0;
948    let mut num_greater_than = 0;
949    let increment = max_damage as f32 * 0.01;
950    let mut damage = max_damage_f32 * 0.85;
951    for _ in 0..16 {
952        if damage < health_f32 {
953            total_less_than += damage as i16;
954            num_less_than += 1;
955        } else if damage > health_f32 {
956            num_greater_than += 1;
957        }
958        damage += increment;
959    }
960
961    (total_less_than / num_less_than, num_greater_than)
962}
963
964fn get_instructions_from_secondaries(
965    state: &mut State,
966    attacker_choice: &Choice,
967    secondaries: &Vec<Secondary>,
968    side_reference: &SideReference,
969    incoming_instructions: StateInstructions,
970    hit_sub: bool,
971) -> Vec<StateInstructions> {
972    let mut return_instruction_list = Vec::with_capacity(4);
973    return_instruction_list.push(incoming_instructions);
974
975    for secondary in secondaries {
976        if secondary.target == MoveTarget::Opponent && hit_sub {
977            continue;
978        }
979        let secondary_percent_hit = (secondary.chance / 100.0).min(1.0);
980
981        let mut i = 0;
982        while i < return_instruction_list.len() {
983            let mut secondary_hit_instructions = return_instruction_list.remove(i);
984
985            if secondary_percent_hit < 1.0 {
986                let mut secondary_miss_instructions = secondary_hit_instructions.clone();
987                secondary_miss_instructions.update_percentage(1.0 - secondary_percent_hit);
988                return_instruction_list.insert(i, secondary_miss_instructions);
989                i += 1;
990            }
991
992            if secondary_percent_hit > 0.0 {
993                secondary_hit_instructions.update_percentage(secondary_percent_hit);
994
995                state.apply_instructions(&secondary_hit_instructions.instruction_list);
996                match &secondary.effect {
997                    Effect::VolatileStatus(volatile_status) => {
998                        get_instructions_from_volatile_statuses(
999                            state,
1000                            attacker_choice,
1001                            &VolatileStatus {
1002                                target: secondary.target.clone(),
1003                                volatile_status: volatile_status.clone(),
1004                            },
1005                            side_reference,
1006                            &mut secondary_hit_instructions,
1007                        );
1008                    }
1009                    Effect::Boost(boost) => {
1010                        get_instructions_from_boosts(
1011                            state,
1012                            &Boost {
1013                                target: secondary.target.clone(),
1014                                boosts: boost.clone(),
1015                            },
1016                            side_reference,
1017                            &mut secondary_hit_instructions,
1018                        );
1019                    }
1020                    Effect::Status(status) => {
1021                        get_instructions_from_status_effects(
1022                            state,
1023                            &Status {
1024                                target: secondary.target.clone(),
1025                                status: status.clone(),
1026                            },
1027                            side_reference,
1028                            &mut secondary_hit_instructions,
1029                            hit_sub,
1030                        );
1031                    }
1032                    Effect::Heal(heal_amount) => {
1033                        get_instructions_from_heal(
1034                            state,
1035                            &Heal {
1036                                target: secondary.target.clone(),
1037                                amount: *heal_amount,
1038                            },
1039                            side_reference,
1040                            &mut secondary_hit_instructions,
1041                        );
1042                    }
1043                    Effect::RemoveItem => {
1044                        let secondary_target_side_ref: SideReference;
1045                        match secondary.target {
1046                            MoveTarget::Opponent => {
1047                                secondary_target_side_ref = side_reference.get_other_side();
1048                            }
1049                            MoveTarget::User => {
1050                                secondary_target_side_ref = *side_reference;
1051                            }
1052                        }
1053                        let target_pkmn = state.get_side(&secondary_target_side_ref).get_active();
1054                        secondary_hit_instructions
1055                            .instruction_list
1056                            .push(Instruction::ChangeItem(ChangeItemInstruction {
1057                                side_ref: secondary_target_side_ref,
1058                                current_item: target_pkmn.item.clone(),
1059                                new_item: Items::NONE,
1060                            }));
1061                        target_pkmn.item = Items::NONE;
1062                    }
1063                }
1064                state.reverse_instructions(&secondary_hit_instructions.instruction_list);
1065                return_instruction_list.insert(i, secondary_hit_instructions);
1066                i += 1; // Increment i only if we didn't remove an element
1067            }
1068        }
1069    }
1070
1071    return_instruction_list
1072}
1073
1074fn get_instructions_from_heal(
1075    state: &mut State,
1076    heal: &Heal,
1077    attacking_side_reference: &SideReference,
1078    incoming_instructions: &mut StateInstructions,
1079) {
1080    let target_side_ref: SideReference;
1081    match heal.target {
1082        MoveTarget::Opponent => target_side_ref = attacking_side_reference.get_other_side(),
1083        MoveTarget::User => target_side_ref = *attacking_side_reference,
1084    }
1085
1086    let target_pkmn = state.get_side(&target_side_ref).get_active();
1087
1088    let mut health_recovered = (heal.amount * target_pkmn.maxhp as f32) as i16;
1089    let final_health = target_pkmn.hp + health_recovered;
1090    if final_health > target_pkmn.maxhp {
1091        health_recovered -= final_health - target_pkmn.maxhp;
1092    } else if final_health < 0 {
1093        health_recovered -= final_health;
1094    }
1095
1096    if health_recovered != 0 {
1097        let ins = Instruction::Heal(HealInstruction {
1098            side_ref: target_side_ref,
1099            heal_amount: health_recovered,
1100        });
1101        target_pkmn.hp += health_recovered;
1102        incoming_instructions.instruction_list.push(ins);
1103    }
1104}
1105
1106fn boosted_accuracy(accuracy_boost: i8) -> f32 {
1107    if accuracy_boost < 0 {
1108        3.0 / (3.0 - accuracy_boost as f32)
1109    } else {
1110        (3.0 + accuracy_boost as f32) / 3.0
1111    }
1112}
1113
1114fn check_move_hit_or_miss(
1115    state: &mut State,
1116    choice: &Choice,
1117    attacking_side_ref: &SideReference,
1118    damage: Option<(i16, i16)>,
1119    incoming_instructions: &mut StateInstructions,
1120    frozen_instructions: &mut Vec<StateInstructions>,
1121) {
1122    /*
1123    Checks whether a move can miss
1124
1125    If the move can miss - adds it to `frozen_instructions`, signifying that the rest of the
1126    half-turn will not run.
1127
1128    Otherwise, update the incoming instructions' percent_hit to reflect the chance of the move hitting
1129    */
1130    let attacking_side = state.get_side(attacking_side_ref);
1131    let attacking_pokemon = attacking_side.get_active_immutable();
1132
1133    let mut percent_hit =
1134        ((choice.accuracy / 100.0) * boosted_accuracy(attacking_side.accuracy_boost)).min(1.0);
1135    if Some((0, 0)) == damage {
1136        percent_hit = 0.0;
1137    }
1138
1139    if percent_hit < 1.0 {
1140        let mut move_missed_instruction = incoming_instructions.clone();
1141        move_missed_instruction.update_percentage(1.0 - percent_hit);
1142        if let Some(crash_fraction) = choice.crash {
1143            let crash_amount = (attacking_pokemon.maxhp as f32 * crash_fraction) as i16;
1144            let crash_instruction = Instruction::Damage(DamageInstruction {
1145                side_ref: *attacking_side_ref,
1146                damage_amount: cmp::min(crash_amount, attacking_pokemon.hp),
1147            });
1148
1149            move_missed_instruction
1150                .instruction_list
1151                .push(crash_instruction);
1152        }
1153
1154        if Items::BLUNDERPOLICY == attacking_pokemon.item {
1155            let boost_amount = get_boost_amount(attacking_side, &PokemonBoostableStat::Speed, 2);
1156            move_missed_instruction
1157                .instruction_list
1158                .push(Instruction::Boost(BoostInstruction {
1159                    side_ref: *attacking_side_ref,
1160                    stat: PokemonBoostableStat::Speed,
1161                    amount: boost_amount,
1162                }));
1163            move_missed_instruction
1164                .instruction_list
1165                .push(Instruction::ChangeItem(ChangeItemInstruction {
1166                    side_ref: *attacking_side_ref,
1167                    current_item: Items::BLUNDERPOLICY,
1168                    new_item: Items::NONE,
1169                }));
1170        }
1171
1172        frozen_instructions.push(move_missed_instruction);
1173    }
1174    incoming_instructions.update_percentage(percent_hit);
1175}
1176
1177fn get_instructions_from_drag(
1178    state: &mut State,
1179    attacking_side_reference: &SideReference,
1180    incoming_instructions: StateInstructions,
1181    frozen_instructions: &mut Vec<StateInstructions>,
1182) {
1183    let defending_side = state.get_side(&attacking_side_reference.get_other_side());
1184    if defending_side.get_active_immutable().hp == 0 {
1185        state.reverse_instructions(&incoming_instructions.instruction_list);
1186        frozen_instructions.push(incoming_instructions);
1187        return;
1188    }
1189
1190    let defending_side_alive_reserve_indices = defending_side.get_alive_pkmn_indices();
1191
1192    state.reverse_instructions(&incoming_instructions.instruction_list);
1193
1194    let num_alive_reserve = defending_side_alive_reserve_indices.len();
1195    if num_alive_reserve == 0 {
1196        frozen_instructions.push(incoming_instructions);
1197        return;
1198    }
1199
1200    for pkmn_id in defending_side_alive_reserve_indices {
1201        let mut cloned_instructions = incoming_instructions.clone();
1202        generate_instructions_from_switch(
1203            state,
1204            pkmn_id,
1205            attacking_side_reference.get_other_side(),
1206            &mut cloned_instructions,
1207        );
1208        cloned_instructions.update_percentage(1.0 / num_alive_reserve as f32);
1209        frozen_instructions.push(cloned_instructions);
1210    }
1211}
1212
1213fn reset_damage_dealt(
1214    side: &Side,
1215    side_reference: &SideReference,
1216    incoming_instructions: &mut StateInstructions,
1217) {
1218    // This creates instructions but does not modify the side
1219    // because this function is called before the state applies the instructions
1220
1221    if side.damage_dealt.damage != 0 {
1222        incoming_instructions
1223            .instruction_list
1224            .push(Instruction::ChangeDamageDealtDamage(
1225                ChangeDamageDealtDamageInstruction {
1226                    side_ref: *side_reference,
1227                    damage_change: 0 - side.damage_dealt.damage,
1228                },
1229            ));
1230    }
1231    if side.damage_dealt.move_category != MoveCategory::Physical {
1232        incoming_instructions
1233            .instruction_list
1234            .push(Instruction::ChangeDamageDealtMoveCatagory(
1235                ChangeDamageDealtMoveCategoryInstruction {
1236                    side_ref: *side_reference,
1237                    move_category: MoveCategory::Physical,
1238                    previous_move_category: side.damage_dealt.move_category,
1239                },
1240            ));
1241    }
1242    if side.damage_dealt.hit_substitute {
1243        incoming_instructions
1244            .instruction_list
1245            .push(Instruction::ToggleDamageDealtHitSubstitute(
1246                ToggleDamageDealtHitSubstituteInstruction {
1247                    side_ref: *side_reference,
1248                },
1249            ));
1250    }
1251}
1252
1253fn set_damage_dealt(
1254    attacking_side: &mut Side,
1255    attacking_side_ref: &SideReference,
1256    damage_dealt: i16,
1257    choice: &Choice,
1258    hit_substitute: bool,
1259    incoming_instructions: &mut StateInstructions,
1260) {
1261    if attacking_side.damage_dealt.damage != damage_dealt {
1262        incoming_instructions
1263            .instruction_list
1264            .push(Instruction::ChangeDamageDealtDamage(
1265                ChangeDamageDealtDamageInstruction {
1266                    side_ref: *attacking_side_ref,
1267                    damage_change: damage_dealt - attacking_side.damage_dealt.damage,
1268                },
1269            ));
1270        attacking_side.damage_dealt.damage = damage_dealt;
1271    }
1272
1273    if attacking_side.damage_dealt.move_category != choice.category {
1274        incoming_instructions
1275            .instruction_list
1276            .push(Instruction::ChangeDamageDealtMoveCatagory(
1277                ChangeDamageDealtMoveCategoryInstruction {
1278                    side_ref: *attacking_side_ref,
1279                    move_category: choice.category,
1280                    previous_move_category: attacking_side.damage_dealt.move_category,
1281                },
1282            ));
1283        attacking_side.damage_dealt.move_category = choice.category;
1284    }
1285
1286    if attacking_side.damage_dealt.hit_substitute != hit_substitute {
1287        incoming_instructions
1288            .instruction_list
1289            .push(Instruction::ToggleDamageDealtHitSubstitute(
1290                ToggleDamageDealtHitSubstituteInstruction {
1291                    side_ref: *attacking_side_ref,
1292                },
1293            ));
1294        attacking_side.damage_dealt.hit_substitute = hit_substitute;
1295    }
1296}
1297
1298fn generate_instructions_from_damage(
1299    mut state: &mut State,
1300    choice: &mut Choice,
1301    calculated_damage: i16,
1302    attacking_side_ref: &SideReference,
1303    mut incoming_instructions: &mut StateInstructions,
1304) -> bool {
1305    /*
1306    TODO:
1307        - arbitrary other after_move as well from the old engine (triggers on hit OR miss)
1308            - dig/dive/bounce/fly volatilestatus
1309    */
1310    let mut hit_sub = false;
1311    let attacking_side = state.get_side(attacking_side_ref);
1312    let attacking_pokemon = attacking_side.get_active();
1313
1314    if calculated_damage <= 0 {
1315        if let Some(crash_fraction) = choice.crash {
1316            let crash_amount = (attacking_pokemon.maxhp as f32 * crash_fraction) as i16;
1317            let damage_taken = cmp::min(crash_amount, attacking_pokemon.hp);
1318            let crash_instruction = Instruction::Damage(DamageInstruction {
1319                side_ref: *attacking_side_ref,
1320                damage_amount: damage_taken,
1321            });
1322            attacking_pokemon.hp -= damage_taken;
1323            incoming_instructions
1324                .instruction_list
1325                .push(crash_instruction);
1326        }
1327        return hit_sub;
1328    }
1329
1330    let percent_hit = (choice.accuracy / 100.0).min(1.0);
1331
1332    if percent_hit > 0.0 {
1333        let should_use_damage_dealt = state.use_damage_dealt;
1334        let (attacking_side, defending_side) = state.get_both_sides(attacking_side_ref);
1335        let attacking_pokemon = attacking_side.get_active();
1336        let mut damage_dealt;
1337        if defending_side
1338            .volatile_statuses
1339            .contains(&PokemonVolatileStatus::SUBSTITUTE)
1340            && !choice.flags.sound
1341            && attacking_pokemon.ability != Abilities::INFILTRATOR
1342        {
1343            damage_dealt = cmp::min(calculated_damage, defending_side.substitute_health);
1344            let substitute_damage_dealt = cmp::min(calculated_damage, damage_dealt);
1345            let substitute_instruction = Instruction::DamageSubstitute(DamageInstruction {
1346                side_ref: attacking_side_ref.get_other_side(),
1347                damage_amount: substitute_damage_dealt,
1348            });
1349            defending_side.substitute_health -= substitute_damage_dealt;
1350            incoming_instructions
1351                .instruction_list
1352                .push(substitute_instruction);
1353
1354            if should_use_damage_dealt {
1355                set_damage_dealt(
1356                    attacking_side,
1357                    attacking_side_ref,
1358                    damage_dealt,
1359                    choice,
1360                    true,
1361                    &mut incoming_instructions,
1362                );
1363            }
1364
1365            if defending_side
1366                .volatile_statuses
1367                .contains(&PokemonVolatileStatus::SUBSTITUTE)
1368                && defending_side.substitute_health == 0
1369            {
1370                incoming_instructions
1371                    .instruction_list
1372                    .push(Instruction::RemoveVolatileStatus(
1373                        RemoveVolatileStatusInstruction {
1374                            side_ref: attacking_side_ref.get_other_side(),
1375                            volatile_status: PokemonVolatileStatus::SUBSTITUTE,
1376                        },
1377                    ));
1378                defending_side
1379                    .volatile_statuses
1380                    .remove(&PokemonVolatileStatus::SUBSTITUTE);
1381            }
1382
1383            hit_sub = true;
1384        } else {
1385            let has_endure = defending_side
1386                .volatile_statuses
1387                .contains(&PokemonVolatileStatus::ENDURE);
1388            let attacking_pokemon = attacking_side.get_active();
1389            let defending_pokemon = defending_side.get_active();
1390            let mut knocked_out = false;
1391            damage_dealt = cmp::min(calculated_damage, defending_pokemon.hp);
1392            if damage_dealt != 0 {
1393                if has_endure
1394                    || ((defending_pokemon.ability == Abilities::STURDY
1395                        || defending_pokemon.item == Items::FOCUSSASH)
1396                        && defending_pokemon.maxhp == defending_pokemon.hp)
1397                {
1398                    damage_dealt -= 1;
1399                }
1400
1401                if damage_dealt >= defending_pokemon.hp {
1402                    knocked_out = true;
1403                }
1404
1405                let damage_instruction = Instruction::Damage(DamageInstruction {
1406                    side_ref: attacking_side_ref.get_other_side(),
1407                    damage_amount: damage_dealt,
1408                });
1409                defending_pokemon.hp -= damage_dealt;
1410                incoming_instructions
1411                    .instruction_list
1412                    .push(damage_instruction);
1413
1414                if knocked_out
1415                    && defending_side
1416                        .volatile_statuses
1417                        .contains(&PokemonVolatileStatus::DESTINYBOND)
1418                {
1419                    let damage_instruction = Instruction::Damage(DamageInstruction {
1420                        side_ref: *attacking_side_ref,
1421                        damage_amount: attacking_pokemon.hp,
1422                    });
1423                    attacking_pokemon.hp = 0;
1424                    incoming_instructions
1425                        .instruction_list
1426                        .push(damage_instruction);
1427                }
1428
1429                if should_use_damage_dealt {
1430                    set_damage_dealt(
1431                        attacking_side,
1432                        attacking_side_ref,
1433                        damage_dealt,
1434                        choice,
1435                        false,
1436                        &mut incoming_instructions,
1437                    );
1438                }
1439
1440                ability_after_damage_hit(
1441                    &mut state,
1442                    choice,
1443                    attacking_side_ref,
1444                    damage_dealt,
1445                    &mut incoming_instructions,
1446                );
1447            }
1448        }
1449
1450        let attacking_pokemon = state.get_side(attacking_side_ref).get_active();
1451        if let Some(drain_fraction) = choice.drain {
1452            let drain_amount = (damage_dealt as f32 * drain_fraction) as i16;
1453            let heal_amount =
1454                cmp::min(drain_amount, attacking_pokemon.maxhp - attacking_pokemon.hp);
1455            if heal_amount != 0 {
1456                let drain_instruction = Instruction::Heal(HealInstruction {
1457                    side_ref: *attacking_side_ref,
1458                    heal_amount: heal_amount,
1459                });
1460                attacking_pokemon.hp += heal_amount;
1461                incoming_instructions
1462                    .instruction_list
1463                    .push(drain_instruction);
1464            }
1465        }
1466
1467        let attacking_pokemon = state.get_side(attacking_side_ref).get_active();
1468        if let Some(recoil_fraction) = choice.recoil {
1469            let recoil_amount = (damage_dealt as f32 * recoil_fraction) as i16;
1470            let damage_amount = cmp::min(recoil_amount, attacking_pokemon.hp);
1471            let recoil_instruction = Instruction::Damage(DamageInstruction {
1472                side_ref: *attacking_side_ref,
1473                damage_amount: damage_amount,
1474            });
1475            attacking_pokemon.hp -= damage_amount;
1476            incoming_instructions
1477                .instruction_list
1478                .push(recoil_instruction);
1479        }
1480        choice_after_damage_hit(
1481            &mut state,
1482            &choice,
1483            attacking_side_ref,
1484            &mut incoming_instructions,
1485            hit_sub,
1486        );
1487    }
1488    hit_sub
1489}
1490
1491fn move_has_no_effect(state: &State, choice: &Choice, attacking_side_ref: &SideReference) -> bool {
1492    let (_attacking_side, defending_side) = state.get_both_sides_immutable(attacking_side_ref);
1493    let defender = defending_side.get_active_immutable();
1494
1495    #[cfg(any(feature = "gen6", feature = "gen7", feature = "gen8", feature = "gen9"))]
1496    if choice.flags.powder
1497        && choice.target == MoveTarget::Opponent
1498        && defender.has_type(&PokemonType::GRASS)
1499    {
1500        return true;
1501    }
1502
1503    if choice.move_type == PokemonType::ELECTRIC
1504        && choice.target == MoveTarget::Opponent
1505        && defender.has_type(&PokemonType::GROUND)
1506    {
1507        return true;
1508    } else if choice.move_id == Choices::ENCORE {
1509        return match state
1510            .get_side_immutable(&attacking_side_ref.get_other_side())
1511            .last_used_move
1512        {
1513            LastUsedMove::None => true,
1514            LastUsedMove::Move(_) => false,
1515            LastUsedMove::Switch(_) => true,
1516        };
1517    } else if state.terrain_is_active(&Terrain::PSYCHICTERRAIN)
1518        && defender.is_grounded()
1519        && choice.target == MoveTarget::Opponent
1520        && choice.priority > 0
1521    {
1522        return true;
1523    }
1524    false
1525}
1526
1527fn cannot_use_move(state: &State, choice: &Choice, attacking_side_ref: &SideReference) -> bool {
1528    let (attacking_side, defending_side) = state.get_both_sides_immutable(attacking_side_ref);
1529
1530    // If the opponent has 0 hp, you can't use a non-status move
1531    if defending_side.get_active_immutable().hp == 0 && choice.category != MoveCategory::Status {
1532        return true;
1533    }
1534
1535    // If you were taunted, you can't use a Physical/Special move
1536    if attacking_side
1537        .volatile_statuses
1538        .contains(&PokemonVolatileStatus::TAUNT)
1539        && matches!(choice.category, MoveCategory::Status)
1540    {
1541        return true;
1542    } else if attacking_side
1543        .volatile_statuses
1544        .contains(&PokemonVolatileStatus::FLINCH)
1545    {
1546        return true;
1547    } else if choice.flags.heal
1548        && attacking_side
1549            .volatile_statuses
1550            .contains(&PokemonVolatileStatus::HEALBLOCK)
1551    {
1552        return true;
1553    }
1554    false
1555}
1556
1557#[cfg(feature = "terastallization")]
1558fn terastallized_base_power_floor(
1559    state: &mut State,
1560    choice: &mut Choice,
1561    attacking_side: &SideReference,
1562) {
1563    let attacker = state
1564        .get_side_immutable(attacking_side)
1565        .get_active_immutable();
1566
1567    if attacker.terastallized
1568        && choice.move_type == attacker.tera_type
1569        && choice.base_power < 60.0
1570        && choice.priority <= 0
1571        && choice.multi_hit() == MultiHitMove::None
1572        && choice.multi_accuracy() == MultiAccuracyMove::None
1573    {
1574        choice.base_power = 60.0;
1575    }
1576}
1577
1578fn before_move(
1579    state: &mut State,
1580    choice: &mut Choice,
1581    defender_choice: &Choice,
1582    attacking_side: &SideReference,
1583    incoming_instructions: &mut StateInstructions,
1584) {
1585    #[cfg(feature = "terastallization")]
1586    terastallized_base_power_floor(state, choice, attacking_side);
1587
1588    ability_before_move(state, choice, attacking_side, incoming_instructions);
1589    item_before_move(state, choice, attacking_side, incoming_instructions);
1590    choice_before_move(state, choice, attacking_side, incoming_instructions);
1591
1592    modify_choice(state, choice, defender_choice, attacking_side);
1593
1594    ability_modify_attack_being_used(state, choice, defender_choice, attacking_side);
1595    ability_modify_attack_against(state, choice, defender_choice, attacking_side);
1596
1597    item_modify_attack_being_used(state, choice, attacking_side);
1598    item_modify_attack_against(state, choice, attacking_side);
1599
1600    /*
1601        TODO: this needs to be here because from_drag is called after the substitute volatilestatus
1602            has already been removed
1603    */
1604    let (attacking_side, defending_side) = state.get_both_sides_immutable(attacking_side);
1605    if defending_side
1606        .volatile_statuses
1607        .contains(&PokemonVolatileStatus::SUBSTITUTE)
1608        && choice.category != MoveCategory::Status
1609    {
1610        choice.flags.drag = false;
1611    }
1612
1613    // Update Choice for `charge` moves
1614    if choice.flags.charge {
1615        let charge_volatile_status = charge_choice_to_volatile(&choice.move_id);
1616        if !attacking_side
1617            .volatile_statuses
1618            .contains(&charge_volatile_status)
1619        {
1620            choice.remove_all_effects();
1621            choice.volatile_status = Some(VolatileStatus {
1622                target: MoveTarget::User,
1623                volatile_status: charge_volatile_status,
1624            });
1625        }
1626    }
1627
1628    // modify choice if defender has protect active
1629    if (defending_side
1630        .volatile_statuses
1631        .contains(&PokemonVolatileStatus::PROTECT)
1632        || defending_side
1633            .volatile_statuses
1634            .contains(&PokemonVolatileStatus::SPIKYSHIELD)
1635        || defending_side
1636            .volatile_statuses
1637            .contains(&PokemonVolatileStatus::BANEFULBUNKER)
1638        || defending_side
1639            .volatile_statuses
1640            .contains(&PokemonVolatileStatus::BURNINGBULWARK)
1641        || defending_side
1642            .volatile_statuses
1643            .contains(&PokemonVolatileStatus::SILKTRAP))
1644        && choice.flags.protect
1645    {
1646        choice.remove_effects_for_protect();
1647        if choice.crash.is_some() {
1648            choice.accuracy = 0.0;
1649        }
1650
1651        if defending_side
1652            .volatile_statuses
1653            .contains(&PokemonVolatileStatus::SPIKYSHIELD)
1654            && choice.flags.contact
1655        {
1656            choice.heal = Some(Heal {
1657                target: MoveTarget::User,
1658                amount: -0.125,
1659            })
1660        } else if defending_side
1661            .volatile_statuses
1662            .contains(&PokemonVolatileStatus::BANEFULBUNKER)
1663            && choice.flags.contact
1664        {
1665            choice.status = Some(Status {
1666                target: MoveTarget::User,
1667                status: PokemonStatus::POISON,
1668            })
1669        } else if defending_side
1670            .volatile_statuses
1671            .contains(&PokemonVolatileStatus::BURNINGBULWARK)
1672            && choice.flags.contact
1673        {
1674            choice.status = Some(Status {
1675                target: MoveTarget::User,
1676                status: PokemonStatus::BURN,
1677            })
1678        } else if defending_side
1679            .volatile_statuses
1680            .contains(&PokemonVolatileStatus::SILKTRAP)
1681            && choice.flags.contact
1682        {
1683            choice.boost = Some(Boost {
1684                target: MoveTarget::User,
1685                boosts: StatBoosts {
1686                    attack: 0,
1687                    defense: 0,
1688                    special_attack: 0,
1689                    special_defense: 0,
1690                    speed: -1,
1691                    accuracy: 0,
1692                },
1693            })
1694        }
1695    }
1696}
1697
1698fn generate_instructions_from_existing_status_conditions(
1699    state: &mut State,
1700    attacking_side_ref: &SideReference,
1701    attacker_choice: &Choice,
1702    incoming_instructions: &mut StateInstructions,
1703    final_instructions: &mut Vec<StateInstructions>,
1704) {
1705    let (attacking_side, _defending_side) = state.get_both_sides(attacking_side_ref);
1706    let current_active_index = attacking_side.active_index;
1707    let attacker_active = attacking_side.get_active();
1708    match attacker_active.status {
1709        PokemonStatus::PARALYZE => {
1710            // Fully-Paralyzed Branch
1711            let mut fully_paralyzed_instruction = incoming_instructions.clone();
1712            fully_paralyzed_instruction.update_percentage(0.25);
1713            final_instructions.push(fully_paralyzed_instruction);
1714
1715            // Non-Paralyzed Branch
1716            incoming_instructions.update_percentage(0.75);
1717        }
1718        PokemonStatus::FREEZE => {
1719            let mut still_frozen_instruction = incoming_instructions.clone();
1720            still_frozen_instruction.update_percentage(0.80);
1721            final_instructions.push(still_frozen_instruction);
1722
1723            incoming_instructions.update_percentage(0.20);
1724            attacker_active.status = PokemonStatus::NONE;
1725            incoming_instructions
1726                .instruction_list
1727                .push(Instruction::ChangeStatus(ChangeStatusInstruction {
1728                    side_ref: attacking_side_ref.clone(),
1729                    pokemon_index: current_active_index,
1730                    old_status: PokemonStatus::FREEZE,
1731                    new_status: PokemonStatus::NONE,
1732                }));
1733        }
1734        PokemonStatus::SLEEP => {
1735            match attacker_active.rest_turns {
1736                // Pokemon is not asleep because of Rest.
1737                0 => {
1738                    let current_sleep_turns = attacker_active.sleep_turns;
1739                    let chance_to_wake = chance_to_wake_up(current_sleep_turns);
1740                    if chance_to_wake == 1.0 {
1741                        attacker_active.status = PokemonStatus::NONE;
1742                        attacker_active.sleep_turns = 0;
1743                        incoming_instructions
1744                            .instruction_list
1745                            .push(Instruction::ChangeStatus(ChangeStatusInstruction {
1746                                side_ref: *attacking_side_ref,
1747                                pokemon_index: current_active_index,
1748                                old_status: PokemonStatus::SLEEP,
1749                                new_status: PokemonStatus::NONE,
1750                            }));
1751                        incoming_instructions
1752                            .instruction_list
1753                            .push(Instruction::SetSleepTurns(SetSleepTurnsInstruction {
1754                                side_ref: *attacking_side_ref,
1755                                pokemon_index: current_active_index,
1756                                new_turns: 0,
1757                                previous_turns: current_sleep_turns,
1758                            }));
1759                    } else if chance_to_wake == 0.0 {
1760                        if attacker_choice.move_id == Choices::SLEEPTALK {
1761                            // if we are using sleeptalk we want to continue using this move
1762                            incoming_instructions.instruction_list.push(
1763                                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
1764                                    side_ref: *attacking_side_ref,
1765                                    pokemon_index: current_active_index,
1766                                    new_turns: current_sleep_turns + 1,
1767                                    previous_turns: current_sleep_turns,
1768                                }),
1769                            );
1770                        } else {
1771                            let mut still_asleep_instruction = incoming_instructions.clone();
1772                            still_asleep_instruction.update_percentage(1.0);
1773                            still_asleep_instruction.instruction_list.push(
1774                                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
1775                                    side_ref: *attacking_side_ref,
1776                                    pokemon_index: current_active_index,
1777                                    new_turns: current_sleep_turns + 1,
1778                                    previous_turns: current_sleep_turns,
1779                                }),
1780                            );
1781                            final_instructions.push(still_asleep_instruction);
1782                            incoming_instructions.update_percentage(0.0);
1783                        }
1784                    } else {
1785                        // This code deals with the situation where there is a chance to wake up
1786                        // as well as a chance to stay asleep.
1787                        // This logic will branch the state and one branch will represent where
1788                        // nothing happens and the other will represent where something happens
1789                        // Normally "nothing happens" means you stay asleep and "something happens"
1790                        // means you wake up. If the move is sleeptalk these are reversed.
1791                        let do_nothing_percentage;
1792                        let mut do_nothing_instructions = incoming_instructions.clone();
1793                        if attacker_choice.move_id == Choices::SLEEPTALK {
1794                            do_nothing_percentage = chance_to_wake;
1795                            do_nothing_instructions.instruction_list.push(
1796                                Instruction::ChangeStatus(ChangeStatusInstruction {
1797                                    side_ref: *attacking_side_ref,
1798                                    pokemon_index: current_active_index,
1799                                    old_status: PokemonStatus::SLEEP,
1800                                    new_status: PokemonStatus::NONE,
1801                                }),
1802                            );
1803                            do_nothing_instructions.instruction_list.push(
1804                                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
1805                                    side_ref: *attacking_side_ref,
1806                                    pokemon_index: current_active_index,
1807                                    new_turns: 0,
1808                                    previous_turns: current_sleep_turns,
1809                                }),
1810                            );
1811                            incoming_instructions.instruction_list.push(
1812                                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
1813                                    side_ref: *attacking_side_ref,
1814                                    pokemon_index: current_active_index,
1815                                    new_turns: current_sleep_turns + 1,
1816                                    previous_turns: current_sleep_turns,
1817                                }),
1818                            );
1819                            attacker_active.sleep_turns += 1;
1820                        } else {
1821                            do_nothing_percentage = 1.0 - chance_to_wake;
1822                            do_nothing_instructions.instruction_list.push(
1823                                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
1824                                    side_ref: *attacking_side_ref,
1825                                    pokemon_index: current_active_index,
1826                                    new_turns: current_sleep_turns + 1,
1827                                    previous_turns: current_sleep_turns,
1828                                }),
1829                            );
1830                            incoming_instructions
1831                                .instruction_list
1832                                .push(Instruction::ChangeStatus(ChangeStatusInstruction {
1833                                    side_ref: *attacking_side_ref,
1834                                    pokemon_index: current_active_index,
1835                                    old_status: PokemonStatus::SLEEP,
1836                                    new_status: PokemonStatus::NONE,
1837                                }));
1838                            incoming_instructions.instruction_list.push(
1839                                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
1840                                    side_ref: *attacking_side_ref,
1841                                    pokemon_index: current_active_index,
1842                                    new_turns: 0,
1843                                    previous_turns: current_sleep_turns,
1844                                }),
1845                            );
1846                            attacker_active.status = PokemonStatus::NONE;
1847                            attacker_active.sleep_turns = 0;
1848                        }
1849                        do_nothing_instructions.update_percentage(do_nothing_percentage);
1850                        incoming_instructions.update_percentage(1.0 - do_nothing_percentage);
1851                        final_instructions.push(do_nothing_instructions);
1852                    }
1853                }
1854                // Pokemon is asleep because of Rest, and will wake up this turn
1855                1 => {
1856                    attacker_active.status = PokemonStatus::NONE;
1857                    attacker_active.rest_turns -= 1;
1858                    incoming_instructions
1859                        .instruction_list
1860                        .push(Instruction::ChangeStatus(ChangeStatusInstruction {
1861                            side_ref: *attacking_side_ref,
1862                            pokemon_index: current_active_index,
1863                            old_status: PokemonStatus::SLEEP,
1864                            new_status: PokemonStatus::NONE,
1865                        }));
1866                    incoming_instructions
1867                        .instruction_list
1868                        .push(Instruction::DecrementRestTurns(
1869                            DecrementRestTurnsInstruction {
1870                                side_ref: *attacking_side_ref,
1871                            },
1872                        ));
1873                }
1874                // Pokemon is asleep because of Rest, and will stay asleep this turn
1875                2 | 3 => {
1876                    attacker_active.rest_turns -= 1;
1877                    incoming_instructions
1878                        .instruction_list
1879                        .push(Instruction::DecrementRestTurns(
1880                            DecrementRestTurnsInstruction {
1881                                side_ref: *attacking_side_ref,
1882                            },
1883                        ));
1884                }
1885                _ => panic!("Invalid rest_turns value: {}", attacker_active.rest_turns),
1886            }
1887        }
1888        _ => {}
1889    }
1890
1891    if attacking_side
1892        .volatile_statuses
1893        .contains(&PokemonVolatileStatus::CONFUSION)
1894    {
1895        let mut hit_yourself_instruction = incoming_instructions.clone();
1896        hit_yourself_instruction.update_percentage(HIT_SELF_IN_CONFUSION_CHANCE);
1897
1898        let attacking_stat = attacking_side.calculate_boosted_stat(PokemonBoostableStat::Attack);
1899        let defending_stat = attacking_side.calculate_boosted_stat(PokemonBoostableStat::Defense);
1900
1901        let attacker_active = attacking_side.get_active();
1902        let mut damage_dealt = 2.0 * attacker_active.level as f32;
1903        damage_dealt = damage_dealt.floor() / 5.0;
1904        damage_dealt = damage_dealt.floor() + 2.0;
1905        damage_dealt = damage_dealt.floor() * 40.0; // 40 is the base power of confusion damage
1906        damage_dealt = damage_dealt * attacking_stat as f32 / defending_stat as f32;
1907        damage_dealt = damage_dealt.floor() / 50.0;
1908        damage_dealt = damage_dealt.floor() + 2.0;
1909        if attacker_active.status == PokemonStatus::BURN {
1910            damage_dealt /= 2.0;
1911        }
1912
1913        let damage_dealt = cmp::min(damage_dealt as i16, attacker_active.hp);
1914        let damage_instruction = Instruction::Damage(DamageInstruction {
1915            side_ref: *attacking_side_ref,
1916            damage_amount: damage_dealt,
1917        });
1918        hit_yourself_instruction
1919            .instruction_list
1920            .push(damage_instruction);
1921
1922        final_instructions.push(hit_yourself_instruction);
1923
1924        incoming_instructions.update_percentage(1.0 - HIT_SELF_IN_CONFUSION_CHANCE);
1925    }
1926
1927    if attacking_side.side_conditions.protect > 0 {
1928        if let Some(vs) = &attacker_choice.volatile_status {
1929            if PROTECT_VOLATILES.contains(&vs.volatile_status) {
1930                let protect_success_chance =
1931                    CONSECUTIVE_PROTECT_CHANCE.powi(attacking_side.side_conditions.protect as i32);
1932                let mut protect_fail_instruction = incoming_instructions.clone();
1933                protect_fail_instruction.update_percentage(1.0 - protect_success_chance);
1934                final_instructions.push(protect_fail_instruction);
1935                incoming_instructions.update_percentage(protect_success_chance);
1936            }
1937        }
1938    }
1939}
1940
1941pub fn generate_instructions_from_move(
1942    state: &mut State,
1943    choice: &mut Choice,
1944    defender_choice: &Choice,
1945    attacking_side: SideReference,
1946    mut incoming_instructions: StateInstructions,
1947    mut final_instructions: &mut Vec<StateInstructions>,
1948    branch_on_damage: bool,
1949) {
1950    if state.use_damage_dealt {
1951        reset_damage_dealt(
1952            state.get_side(&attacking_side),
1953            &attacking_side,
1954            &mut incoming_instructions,
1955        );
1956    }
1957
1958    if choice.category == MoveCategory::Switch {
1959        generate_instructions_from_switch(
1960            state,
1961            choice.switch_id,
1962            attacking_side,
1963            &mut incoming_instructions,
1964        );
1965        final_instructions.push(incoming_instructions);
1966        return;
1967    }
1968
1969    let attacker_side = state.get_side(&attacking_side);
1970
1971    if choice.move_id == Choices::NONE {
1972        if attacker_side
1973            .volatile_statuses
1974            .contains(&PokemonVolatileStatus::MUSTRECHARGE)
1975        {
1976            incoming_instructions
1977                .instruction_list
1978                .push(Instruction::RemoveVolatileStatus(
1979                    RemoveVolatileStatusInstruction {
1980                        side_ref: attacking_side,
1981                        volatile_status: PokemonVolatileStatus::MUSTRECHARGE,
1982                    },
1983                ));
1984        }
1985        final_instructions.push(incoming_instructions);
1986        return;
1987    }
1988
1989    if attacker_side
1990        .volatile_statuses
1991        .contains(&PokemonVolatileStatus::TRUANT)
1992    {
1993        incoming_instructions
1994            .instruction_list
1995            .push(Instruction::RemoveVolatileStatus(
1996                RemoveVolatileStatusInstruction {
1997                    side_ref: attacking_side,
1998                    volatile_status: PokemonVolatileStatus::TRUANT,
1999                },
2000            ));
2001        final_instructions.push(incoming_instructions);
2002        return;
2003    }
2004
2005    // TODO: test first-turn dragontail missing - it should not trigger this early return
2006    if !choice.first_move && defender_choice.flags.drag {
2007        final_instructions.push(incoming_instructions);
2008        return;
2009    }
2010
2011    state.apply_instructions(&incoming_instructions.instruction_list);
2012
2013    let side = state.get_side(&attacking_side);
2014    if side
2015        .volatile_statuses
2016        .contains(&PokemonVolatileStatus::ENCORE)
2017    {
2018        match side.last_used_move {
2019            LastUsedMove::Move(last_used_move) => {
2020                if choice.move_index != last_used_move {
2021                    *choice = MOVES
2022                        .get(&side.get_active_immutable().moves[&last_used_move].id)
2023                        .unwrap()
2024                        .clone();
2025                    choice.move_index = last_used_move;
2026                }
2027            }
2028            _ => panic!("Encore should not be active when last used move is not a move"),
2029        }
2030
2031        // this value is incremented when an encored move has been used
2032        // the value being 2 means we are currently using the 3rd move so we can remove it
2033        #[cfg(any(
2034            feature = "gen5",
2035            feature = "gen6",
2036            feature = "gen7",
2037            feature = "gen8",
2038            feature = "gen9"
2039        ))]
2040        if side.volatile_status_durations.encore == 2 {
2041            incoming_instructions
2042                .instruction_list
2043                .push(Instruction::RemoveVolatileStatus(
2044                    RemoveVolatileStatusInstruction {
2045                        side_ref: attacking_side,
2046                        volatile_status: PokemonVolatileStatus::ENCORE,
2047                    },
2048                ));
2049            incoming_instructions
2050                .instruction_list
2051                .push(Instruction::ChangeVolatileStatusDuration(
2052                    ChangeVolatileStatusDurationInstruction {
2053                        side_ref: attacking_side,
2054                        volatile_status: PokemonVolatileStatus::ENCORE,
2055                        amount: -2,
2056                    },
2057                ));
2058            side.volatile_status_durations.encore = 0;
2059            side.volatile_statuses
2060                .remove(&PokemonVolatileStatus::ENCORE);
2061        } else {
2062            incoming_instructions
2063                .instruction_list
2064                .push(Instruction::ChangeVolatileStatusDuration(
2065                    ChangeVolatileStatusDurationInstruction {
2066                        side_ref: attacking_side,
2067                        volatile_status: PokemonVolatileStatus::ENCORE,
2068                        amount: 1,
2069                    },
2070                ));
2071            side.volatile_status_durations.encore += 1;
2072        }
2073    }
2074
2075    #[cfg(any(
2076        feature = "gen5",
2077        feature = "gen6",
2078        feature = "gen7",
2079        feature = "gen8",
2080        feature = "gen9"
2081    ))]
2082    if side
2083        .volatile_statuses
2084        .contains(&PokemonVolatileStatus::TAUNT)
2085    {
2086        match side.volatile_status_durations.taunt {
2087            0 | 1 => {
2088                incoming_instructions.instruction_list.push(
2089                    Instruction::ChangeVolatileStatusDuration(
2090                        ChangeVolatileStatusDurationInstruction {
2091                            side_ref: attacking_side,
2092                            volatile_status: PokemonVolatileStatus::TAUNT,
2093                            amount: 1,
2094                        },
2095                    ),
2096                );
2097                side.volatile_status_durations.taunt += 1;
2098            }
2099
2100            // Technically taunt is removed at the end of the turn but because we are already
2101            // dealing with taunt here we can save a check at the end of the turn
2102            // This shouldn't change anything because taunt only affects which move is selected
2103            // and by this point a move has been chosen
2104            2 => {
2105                side.volatile_statuses.remove(&PokemonVolatileStatus::TAUNT);
2106                incoming_instructions
2107                    .instruction_list
2108                    .push(Instruction::RemoveVolatileStatus(
2109                        RemoveVolatileStatusInstruction {
2110                            side_ref: attacking_side,
2111                            volatile_status: PokemonVolatileStatus::TAUNT,
2112                        },
2113                    ));
2114                incoming_instructions.instruction_list.push(
2115                    Instruction::ChangeVolatileStatusDuration(
2116                        ChangeVolatileStatusDurationInstruction {
2117                            side_ref: attacking_side,
2118                            volatile_status: PokemonVolatileStatus::TAUNT,
2119                            amount: -2,
2120                        },
2121                    ),
2122                );
2123                side.volatile_status_durations.taunt = 0;
2124                state.re_enable_disabled_moves(
2125                    &attacking_side,
2126                    &mut incoming_instructions.instruction_list,
2127                );
2128            }
2129            _ => panic!(
2130                "Taunt duration cannot be {} when taunt volatile is active",
2131                side.volatile_status_durations.taunt
2132            ),
2133        }
2134    }
2135
2136    if !choice.first_move
2137        && state
2138            .get_side(&attacking_side.get_other_side())
2139            .force_switch
2140    {
2141        state
2142            .get_side(&attacking_side)
2143            .switch_out_move_second_saved_move = choice.move_id;
2144        state.reverse_instructions(&incoming_instructions.instruction_list);
2145        final_instructions.push(incoming_instructions);
2146        return;
2147    }
2148
2149    if state
2150        .get_side_immutable(&attacking_side)
2151        .get_active_immutable()
2152        .hp
2153        == 0
2154    {
2155        state.reverse_instructions(&incoming_instructions.instruction_list);
2156        final_instructions.push(incoming_instructions);
2157        return;
2158    }
2159
2160    // If the move is a charge move, remove the volatile status if damage was done
2161    if choice.flags.charge {
2162        let side = state.get_side(&attacking_side);
2163        let volatile_status = charge_choice_to_volatile(&choice.move_id);
2164        if side.volatile_statuses.contains(&volatile_status) {
2165            choice.flags.charge = false;
2166            let instruction = Instruction::RemoveVolatileStatus(RemoveVolatileStatusInstruction {
2167                side_ref: attacking_side,
2168                volatile_status: volatile_status,
2169            });
2170            incoming_instructions.instruction_list.push(instruction);
2171            side.volatile_statuses.remove(&volatile_status);
2172        }
2173    }
2174
2175    before_move(
2176        state,
2177        choice,
2178        defender_choice,
2179        &attacking_side,
2180        &mut incoming_instructions,
2181    );
2182    if incoming_instructions.percentage == 0.0 {
2183        state.reverse_instructions(&incoming_instructions.instruction_list);
2184        return;
2185    }
2186
2187    if state.use_last_used_move {
2188        set_last_used_move_as_move(
2189            state.get_side(&attacking_side),
2190            choice.move_index,
2191            attacking_side,
2192            &mut incoming_instructions,
2193        );
2194    }
2195
2196    if cannot_use_move(state, &choice, &attacking_side) {
2197        state.reverse_instructions(&incoming_instructions.instruction_list);
2198        final_instructions.push(incoming_instructions);
2199        return;
2200    }
2201
2202    // most of the time pp decrement doesn't matter and just adds another instruction
2203    // so we only decrement pp if the move is at 10 or less pp since that is when it starts
2204    // to matter
2205    let (attacker_side, defender_side) = state.get_both_sides(&attacking_side);
2206    let active = attacker_side.get_active();
2207    if active.moves[&choice.move_index].pp < 10 {
2208        let pp_decrement_amount = if choice.target == MoveTarget::Opponent
2209            && defender_side.get_active_immutable().ability == Abilities::PRESSURE
2210        {
2211            2
2212        } else {
2213            1
2214        };
2215        incoming_instructions
2216            .instruction_list
2217            .push(Instruction::DecrementPP(DecrementPPInstruction {
2218                side_ref: attacking_side,
2219                move_index: choice.move_index,
2220                amount: pp_decrement_amount,
2221            }));
2222        active.moves[&choice.move_index].pp -= pp_decrement_amount;
2223    }
2224
2225    if !choice.sleep_talk_move {
2226        generate_instructions_from_existing_status_conditions(
2227            state,
2228            &attacking_side,
2229            &choice,
2230            &mut incoming_instructions,
2231            &mut final_instructions,
2232        );
2233    }
2234    let attacker = state
2235        .get_side_immutable(&attacking_side)
2236        .get_active_immutable();
2237    if choice.move_id == Choices::SLEEPTALK && attacker.status == PokemonStatus::SLEEP {
2238        let new_choices = attacker.get_sleep_talk_choices();
2239        state.reverse_instructions(&incoming_instructions.instruction_list);
2240        let num_choices = new_choices.len() as f32;
2241        for mut new_choice in new_choices {
2242            new_choice.sleep_talk_move = true;
2243            let mut sleep_talk_instructions = incoming_instructions.clone();
2244            sleep_talk_instructions.update_percentage(1.0 / num_choices);
2245            generate_instructions_from_move(
2246                state,
2247                &mut new_choice,
2248                defender_choice,
2249                attacking_side,
2250                sleep_talk_instructions,
2251                &mut final_instructions,
2252                false,
2253            );
2254        }
2255        return;
2256    } else if attacker.status == PokemonStatus::SLEEP && !choice.sleep_talk_move {
2257        state.reverse_instructions(&incoming_instructions.instruction_list);
2258        if incoming_instructions.percentage > 0.0 {
2259            final_instructions.push(incoming_instructions);
2260        }
2261        return;
2262    }
2263
2264    if move_has_no_effect(state, &choice, &attacking_side) {
2265        state.reverse_instructions(&incoming_instructions.instruction_list);
2266        final_instructions.push(incoming_instructions);
2267        return;
2268    }
2269    choice_special_effect(state, choice, &attacking_side, &mut incoming_instructions);
2270    let damage = calculate_damage(state, &attacking_side, &choice, DamageRolls::Max);
2271    check_move_hit_or_miss(
2272        state,
2273        &choice,
2274        &attacking_side,
2275        damage,
2276        &mut incoming_instructions,
2277        &mut final_instructions,
2278    );
2279
2280    if incoming_instructions.percentage == 0.0 {
2281        state.reverse_instructions(&incoming_instructions.instruction_list);
2282        return;
2283    }
2284
2285    // start multi-hit
2286    let hit_count;
2287    match choice.multi_hit() {
2288        MultiHitMove::None => {
2289            hit_count = 1;
2290        }
2291        MultiHitMove::DoubleHit => {
2292            hit_count = 2;
2293        }
2294        MultiHitMove::TripleHit => {
2295            hit_count = 3;
2296        }
2297        MultiHitMove::TwoToFiveHits => {
2298            hit_count =
2299                if state.get_side(&attacking_side).get_active().ability == Abilities::SKILLLINK {
2300                    5
2301                } else if state.get_side(&attacking_side).get_active().item == Items::LOADEDDICE {
2302                    4
2303                } else {
2304                    3 // too lazy to implement branching here. Average is 3.2 so this is a fine approximation
2305                };
2306        }
2307        MultiHitMove::PopulationBomb => {
2308            // population bomb checks accuracy each time but lets approximate
2309            hit_count = if state.get_side(&attacking_side).get_active().item == Items::WIDELENS {
2310                9
2311            } else {
2312                6
2313            };
2314        }
2315        MultiHitMove::TripleAxel => {
2316            // triple axel checks accuracy each time but until multi-accuracy is implemented this
2317            // is the best we can do
2318            hit_count = 3
2319        }
2320    }
2321
2322    let (_attacker_side, defender_side) = state.get_both_sides(&attacking_side);
2323    let defender_active = defender_side.get_active();
2324    let mut does_damage = false;
2325    let (mut branch_damage, mut regular_damage) = (0, 0);
2326    let mut branch_instructions: Option<StateInstructions> = None;
2327    if let Some((max_damage_dealt, max_crit_damage)) = damage {
2328        does_damage = true;
2329        let avg_damage_dealt = (max_damage_dealt as f32 * 0.925) as i16;
2330        let min_damage_dealt = (max_damage_dealt as f32 * 0.85) as i16;
2331        if branch_on_damage
2332            && max_damage_dealt >= defender_active.hp
2333            && min_damage_dealt < defender_active.hp
2334        {
2335            let (average_non_kill_damage, num_kill_rolls) =
2336                compare_health_with_damage_multiples(max_damage_dealt, defender_active.hp);
2337
2338            let crit_rate = if defender_active.ability == Abilities::BATTLEARMOR
2339                || defender_active.ability == Abilities::SHELLARMOR
2340            {
2341                0.0
2342            } else if choice.move_id.guaranteed_crit() {
2343                1.0
2344            } else if choice.move_id.increased_crit_ratio() {
2345                1.0 / 8.0
2346            } else {
2347                BASE_CRIT_CHANCE
2348            };
2349
2350            // the chance of a branch is the chance of the roll killing + the chance of a crit
2351            let branch_chance = ((1.0 - crit_rate) * (num_kill_rolls as f32 / 16.0)) + crit_rate;
2352
2353            let mut branch_ins = incoming_instructions.clone();
2354            branch_ins.update_percentage(branch_chance);
2355            branch_instructions = Some(branch_ins);
2356            branch_damage = defender_active.hp;
2357
2358            incoming_instructions.update_percentage(1.0 - branch_chance);
2359            regular_damage = average_non_kill_damage;
2360        } else if branch_on_damage && max_damage_dealt < defender_active.hp {
2361            let crit_rate = if defender_active.ability == Abilities::BATTLEARMOR
2362                || defender_active.ability == Abilities::SHELLARMOR
2363            {
2364                0.0
2365            } else if choice.move_id.guaranteed_crit() {
2366                1.0
2367            } else if choice.move_id.increased_crit_ratio() {
2368                1.0 / 8.0
2369            } else {
2370                BASE_CRIT_CHANCE
2371            };
2372            let mut branch_ins = incoming_instructions.clone();
2373            branch_ins.update_percentage(crit_rate);
2374            branch_instructions = Some(branch_ins);
2375            branch_damage = (max_crit_damage as f32 * 0.925) as i16;
2376            incoming_instructions.update_percentage(1.0 - crit_rate);
2377            regular_damage = (max_damage_dealt as f32 * 0.925) as i16;
2378        } else {
2379            regular_damage = avg_damage_dealt;
2380        }
2381    }
2382
2383    if incoming_instructions.percentage != 0.0 {
2384        run_move(
2385            state,
2386            attacking_side,
2387            incoming_instructions,
2388            hit_count,
2389            does_damage,
2390            regular_damage,
2391            choice,
2392            defender_choice,
2393            &mut final_instructions,
2394        );
2395    } else {
2396        state.reverse_instructions(&incoming_instructions.instruction_list);
2397    }
2398
2399    // A branch representing either a roll that kills the opponent or a crit
2400    if let Some(branch_ins) = branch_instructions {
2401        if branch_ins.percentage != 0.0 {
2402            state.apply_instructions(&branch_ins.instruction_list);
2403            run_move(
2404                state,
2405                attacking_side,
2406                branch_ins,
2407                hit_count,
2408                does_damage,
2409                branch_damage,
2410                choice,
2411                defender_choice,
2412                &mut final_instructions,
2413            );
2414        }
2415    }
2416
2417    combine_duplicate_instructions(&mut final_instructions);
2418    return;
2419}
2420
2421fn combine_duplicate_instructions(list_of_instructions: &mut Vec<StateInstructions>) {
2422    for i in 0..list_of_instructions.len() {
2423        let mut j = i + 1;
2424        while j < list_of_instructions.len() {
2425            if list_of_instructions[i].instruction_list == list_of_instructions[j].instruction_list
2426            {
2427                list_of_instructions[i].percentage += list_of_instructions[j].percentage;
2428                list_of_instructions.remove(j);
2429            } else {
2430                j += 1;
2431            }
2432        }
2433    }
2434}
2435
2436fn get_effective_speed(state: &State, side_reference: &SideReference) -> i16 {
2437    let side = state.get_side_immutable(side_reference);
2438    let active_pkmn = side.get_active_immutable();
2439
2440    let mut boosted_speed = side.calculate_boosted_stat(PokemonBoostableStat::Speed) as f32;
2441
2442    match state.weather.weather_type {
2443        Weather::SUN | Weather::HARSHSUN if active_pkmn.ability == Abilities::CHLOROPHYLL => {
2444            boosted_speed *= 2.0
2445        }
2446        Weather::RAIN | Weather::HEAVYRAIN if active_pkmn.ability == Abilities::SWIFTSWIM => {
2447            boosted_speed *= 2.0
2448        }
2449        Weather::SAND if active_pkmn.ability == Abilities::SANDRUSH => boosted_speed *= 2.0,
2450        Weather::HAIL if active_pkmn.ability == Abilities::SLUSHRUSH => boosted_speed *= 2.0,
2451        _ => {}
2452    }
2453
2454    match active_pkmn.ability {
2455        Abilities::SURGESURFER if state.terrain.terrain_type == Terrain::ELECTRICTERRAIN => {
2456            boosted_speed *= 2.0
2457        }
2458        Abilities::UNBURDEN
2459            if side
2460                .volatile_statuses
2461                .contains(&PokemonVolatileStatus::UNBURDEN) =>
2462        {
2463            boosted_speed *= 2.0
2464        }
2465        Abilities::QUICKFEET if active_pkmn.status != PokemonStatus::NONE => boosted_speed *= 1.5,
2466        _ => {}
2467    }
2468
2469    if side
2470        .volatile_statuses
2471        .contains(&PokemonVolatileStatus::SLOWSTART)
2472    {
2473        boosted_speed *= 0.5;
2474    }
2475
2476    if side
2477        .volatile_statuses
2478        .contains(&PokemonVolatileStatus::PROTOSYNTHESISSPE)
2479        || side
2480            .volatile_statuses
2481            .contains(&PokemonVolatileStatus::QUARKDRIVESPE)
2482    {
2483        boosted_speed *= 1.5;
2484    }
2485
2486    if side.side_conditions.tailwind > 0 {
2487        boosted_speed *= 2.0
2488    }
2489
2490    match active_pkmn.item {
2491        Items::IRONBALL => boosted_speed *= 0.5,
2492        Items::CHOICESCARF => boosted_speed *= 1.5,
2493        _ => {}
2494    }
2495
2496    #[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5", feature = "gen6"))]
2497    if active_pkmn.status == PokemonStatus::PARALYZE && active_pkmn.ability != Abilities::QUICKFEET
2498    {
2499        boosted_speed *= 0.25;
2500    }
2501
2502    #[cfg(any(feature = "gen7", feature = "gen8", feature = "gen9"))]
2503    if active_pkmn.status == PokemonStatus::PARALYZE && active_pkmn.ability != Abilities::QUICKFEET
2504    {
2505        boosted_speed *= 0.50;
2506    }
2507
2508    boosted_speed as i16
2509}
2510
2511fn modify_choice_priority(state: &State, side_reference: &SideReference, choice: &mut Choice) {
2512    let side = state.get_side_immutable(side_reference);
2513    let active_pkmn = side.get_active_immutable();
2514
2515    if choice.move_id == Choices::GRASSYGLIDE && state.terrain_is_active(&Terrain::GRASSYTERRAIN) {
2516        choice.priority += 1;
2517    }
2518
2519    match active_pkmn.ability {
2520        Abilities::PRANKSTER if choice.category == MoveCategory::Status => choice.priority += 1,
2521        Abilities::GALEWINGS
2522            if choice.move_type == PokemonType::FLYING && active_pkmn.hp == active_pkmn.maxhp =>
2523        {
2524            choice.priority += 1
2525        }
2526        Abilities::TRIAGE if choice.flags.heal => choice.priority += 3,
2527        _ => {}
2528    }
2529}
2530
2531fn moves_first(
2532    state: &State,
2533    side_one_choice: &Choice,
2534    side_two_choice: &Choice,
2535    incoming_instructions: &mut StateInstructions,
2536) -> SideMovesFirst {
2537    let side_one_effective_speed = get_effective_speed(&state, &SideReference::SideOne);
2538    let side_two_effective_speed = get_effective_speed(&state, &SideReference::SideTwo);
2539
2540    if side_one_choice.category == MoveCategory::Switch
2541        && side_two_choice.category == MoveCategory::Switch
2542    {
2543        return if side_one_effective_speed > side_two_effective_speed {
2544            SideMovesFirst::SideOne
2545        } else if side_one_effective_speed == side_two_effective_speed {
2546            SideMovesFirst::SpeedTie
2547        } else {
2548            SideMovesFirst::SideTwo
2549        };
2550    } else if side_one_choice.category == MoveCategory::Switch {
2551        return if side_two_choice.move_id != Choices::PURSUIT {
2552            SideMovesFirst::SideOne
2553        } else {
2554            SideMovesFirst::SideTwo
2555        };
2556    } else if side_two_choice.category == MoveCategory::Switch {
2557        return if side_one_choice.move_id == Choices::PURSUIT {
2558            SideMovesFirst::SideOne
2559        } else {
2560            SideMovesFirst::SideTwo
2561        };
2562    }
2563
2564    let side_one_active = state.side_one.get_active_immutable();
2565    let side_two_active = state.side_two.get_active_immutable();
2566    if side_one_choice.priority == side_two_choice.priority {
2567        if side_one_active.item == Items::CUSTAPBERRY
2568            && side_one_active.hp < side_one_active.maxhp / 4
2569        {
2570            incoming_instructions
2571                .instruction_list
2572                .push(Instruction::ChangeItem(ChangeItemInstruction {
2573                    side_ref: SideReference::SideOne,
2574                    new_item: Items::NONE,
2575                    current_item: Items::CUSTAPBERRY,
2576                }));
2577            return SideMovesFirst::SideOne;
2578        } else if side_two_active.item == Items::CUSTAPBERRY
2579            && side_two_active.hp < side_two_active.maxhp / 4
2580        {
2581            incoming_instructions
2582                .instruction_list
2583                .push(Instruction::ChangeItem(ChangeItemInstruction {
2584                    side_ref: SideReference::SideTwo,
2585                    new_item: Items::NONE,
2586                    current_item: Items::CUSTAPBERRY,
2587                }));
2588            return SideMovesFirst::SideTwo;
2589        }
2590
2591        if side_one_effective_speed == side_two_effective_speed {
2592            return SideMovesFirst::SpeedTie;
2593        }
2594
2595        match state.trick_room.active {
2596            true => {
2597                if side_one_effective_speed < side_two_effective_speed {
2598                    SideMovesFirst::SideOne
2599                } else {
2600                    SideMovesFirst::SideTwo
2601                }
2602            }
2603            false => {
2604                if side_one_effective_speed > side_two_effective_speed {
2605                    SideMovesFirst::SideOne
2606                } else {
2607                    SideMovesFirst::SideTwo
2608                }
2609            }
2610        }
2611    } else {
2612        if side_one_choice.priority > side_two_choice.priority {
2613            SideMovesFirst::SideOne
2614        } else {
2615            SideMovesFirst::SideTwo
2616        }
2617    }
2618}
2619
2620fn get_active_protosynthesis(side: &Side) -> Option<PokemonVolatileStatus> {
2621    if side
2622        .volatile_statuses
2623        .contains(&PokemonVolatileStatus::PROTOSYNTHESISATK)
2624    {
2625        Some(PokemonVolatileStatus::PROTOSYNTHESISATK)
2626    } else if side
2627        .volatile_statuses
2628        .contains(&PokemonVolatileStatus::PROTOSYNTHESISDEF)
2629    {
2630        Some(PokemonVolatileStatus::PROTOSYNTHESISDEF)
2631    } else if side
2632        .volatile_statuses
2633        .contains(&PokemonVolatileStatus::PROTOSYNTHESISSPA)
2634    {
2635        Some(PokemonVolatileStatus::PROTOSYNTHESISSPA)
2636    } else if side
2637        .volatile_statuses
2638        .contains(&PokemonVolatileStatus::PROTOSYNTHESISSPD)
2639    {
2640        Some(PokemonVolatileStatus::PROTOSYNTHESISSPD)
2641    } else if side
2642        .volatile_statuses
2643        .contains(&PokemonVolatileStatus::PROTOSYNTHESISSPE)
2644    {
2645        Some(PokemonVolatileStatus::PROTOSYNTHESISSPE)
2646    } else {
2647        None
2648    }
2649}
2650
2651fn get_active_quarkdrive(side: &Side) -> Option<PokemonVolatileStatus> {
2652    if side
2653        .volatile_statuses
2654        .contains(&PokemonVolatileStatus::QUARKDRIVEATK)
2655    {
2656        Some(PokemonVolatileStatus::QUARKDRIVEATK)
2657    } else if side
2658        .volatile_statuses
2659        .contains(&PokemonVolatileStatus::QUARKDRIVEDEF)
2660    {
2661        Some(PokemonVolatileStatus::QUARKDRIVEDEF)
2662    } else if side
2663        .volatile_statuses
2664        .contains(&PokemonVolatileStatus::QUARKDRIVESPA)
2665    {
2666        Some(PokemonVolatileStatus::QUARKDRIVESPA)
2667    } else if side
2668        .volatile_statuses
2669        .contains(&PokemonVolatileStatus::QUARKDRIVESPD)
2670    {
2671        Some(PokemonVolatileStatus::QUARKDRIVESPD)
2672    } else if side
2673        .volatile_statuses
2674        .contains(&PokemonVolatileStatus::QUARKDRIVESPE)
2675    {
2676        Some(PokemonVolatileStatus::QUARKDRIVESPE)
2677    } else {
2678        None
2679    }
2680}
2681
2682fn on_weather_end(
2683    state: &mut State,
2684    sides: [&SideReference; 2],
2685    incoming_instructions: &mut StateInstructions,
2686) {
2687    match state.weather.weather_type {
2688        Weather::SUN => {
2689            for side_ref in sides {
2690                let side = state.get_side(side_ref);
2691                if side.get_active_immutable().ability == Abilities::PROTOSYNTHESIS {
2692                    if let Some(volatile_status) = get_active_protosynthesis(side) {
2693                        let active = side.get_active();
2694                        if active.item == Items::BOOSTERENERGY {
2695                            incoming_instructions
2696                                .instruction_list
2697                                .push(Instruction::ChangeItem(ChangeItemInstruction {
2698                                    side_ref: *side_ref,
2699                                    current_item: Items::BOOSTERENERGY,
2700                                    new_item: Items::NONE,
2701                                }));
2702                            active.item = Items::NONE;
2703                        } else {
2704                            incoming_instructions.instruction_list.push(
2705                                Instruction::RemoveVolatileStatus(
2706                                    RemoveVolatileStatusInstruction {
2707                                        side_ref: *side_ref,
2708                                        volatile_status,
2709                                    },
2710                                ),
2711                            );
2712                            side.volatile_statuses.remove(&volatile_status);
2713                        }
2714                    }
2715                }
2716            }
2717        }
2718        _ => {}
2719    }
2720}
2721
2722fn on_terrain_end(
2723    state: &mut State,
2724    sides: [&SideReference; 2],
2725    incoming_instructions: &mut StateInstructions,
2726) {
2727    match state.terrain.terrain_type {
2728        Terrain::ELECTRICTERRAIN => {
2729            for side_ref in sides {
2730                let side = state.get_side(side_ref);
2731                if side.get_active_immutable().ability == Abilities::QUARKDRIVE {
2732                    if let Some(volatile_status) = get_active_quarkdrive(side) {
2733                        let active = side.get_active();
2734                        if active.item == Items::BOOSTERENERGY {
2735                            incoming_instructions
2736                                .instruction_list
2737                                .push(Instruction::ChangeItem(ChangeItemInstruction {
2738                                    side_ref: *side_ref,
2739                                    current_item: Items::BOOSTERENERGY,
2740                                    new_item: Items::NONE,
2741                                }));
2742                            active.item = Items::NONE;
2743                        } else {
2744                            incoming_instructions.instruction_list.push(
2745                                Instruction::RemoveVolatileStatus(
2746                                    RemoveVolatileStatusInstruction {
2747                                        side_ref: *side_ref,
2748                                        volatile_status,
2749                                    },
2750                                ),
2751                            );
2752                            side.volatile_statuses.remove(&volatile_status);
2753                        }
2754                    }
2755                }
2756            }
2757        }
2758        _ => {}
2759    }
2760}
2761
2762fn add_end_of_turn_instructions(
2763    state: &mut State,
2764    mut incoming_instructions: &mut StateInstructions,
2765    first_move_side: &SideReference,
2766) {
2767    if state.side_one.force_switch || state.side_two.force_switch {
2768        return;
2769    }
2770
2771    let sides = [first_move_side, &first_move_side.get_other_side()];
2772
2773    // Weather decrement / dissipation
2774    if state.weather.turns_remaining > 0 && state.weather.weather_type != Weather::NONE {
2775        let weather_dissipate_instruction = Instruction::DecrementWeatherTurnsRemaining;
2776        incoming_instructions
2777            .instruction_list
2778            .push(weather_dissipate_instruction);
2779        state.weather.turns_remaining -= 1;
2780        if state.weather.turns_remaining == 0 {
2781            on_weather_end(state, sides, &mut incoming_instructions);
2782            let weather_end_instruction = Instruction::ChangeWeather(ChangeWeather {
2783                new_weather: Weather::NONE,
2784                new_weather_turns_remaining: 0,
2785                previous_weather: state.weather.weather_type,
2786                previous_weather_turns_remaining: 0,
2787            });
2788            incoming_instructions
2789                .instruction_list
2790                .push(weather_end_instruction);
2791            state.weather.weather_type = Weather::NONE;
2792        }
2793    }
2794
2795    // Trick Room decrement / dissipation
2796    if state.trick_room.turns_remaining > 0 && state.trick_room.active {
2797        incoming_instructions
2798            .instruction_list
2799            .push(Instruction::DecrementTrickRoomTurnsRemaining);
2800        state.trick_room.turns_remaining -= 1;
2801        if state.trick_room.turns_remaining == 0 {
2802            incoming_instructions
2803                .instruction_list
2804                .push(Instruction::ToggleTrickRoom(ToggleTrickRoomInstruction {
2805                    currently_active: true,
2806                    new_trickroom_turns_remaining: 0,
2807                    previous_trickroom_turns_remaining: 0,
2808                }));
2809            state.trick_room.active = false;
2810        }
2811    }
2812
2813    // Terrain decrement / dissipation
2814    if state.terrain.turns_remaining > 0 && state.terrain.terrain_type != Terrain::NONE {
2815        if state.terrain.terrain_type == Terrain::GRASSYTERRAIN {
2816            for side_ref in sides {
2817                let side = state.get_side(side_ref);
2818                let active_pkmn = side.get_active();
2819                if active_pkmn.hp == 0 || !active_pkmn.is_grounded() {
2820                    continue;
2821                }
2822                let heal_amount = cmp::min(
2823                    (active_pkmn.maxhp as f32 * 0.0625) as i16,
2824                    active_pkmn.maxhp - active_pkmn.hp,
2825                );
2826                if heal_amount > 0 {
2827                    let heal_instruction = Instruction::Heal(HealInstruction {
2828                        side_ref: *side_ref,
2829                        heal_amount,
2830                    });
2831                    active_pkmn.hp += heal_amount;
2832                    incoming_instructions
2833                        .instruction_list
2834                        .push(heal_instruction);
2835                }
2836            }
2837        }
2838        let terrain_dissipate_instruction = Instruction::DecrementTerrainTurnsRemaining;
2839        incoming_instructions
2840            .instruction_list
2841            .push(terrain_dissipate_instruction);
2842        state.terrain.turns_remaining -= 1;
2843        if state.terrain.turns_remaining == 0 {
2844            on_terrain_end(state, sides, &mut incoming_instructions);
2845            let terrain_end_instruction = Instruction::ChangeTerrain(ChangeTerrain {
2846                new_terrain: Terrain::NONE,
2847                new_terrain_turns_remaining: 0,
2848                previous_terrain: state.terrain.terrain_type,
2849                previous_terrain_turns_remaining: 0,
2850            });
2851            incoming_instructions
2852                .instruction_list
2853                .push(terrain_end_instruction);
2854            state.terrain.terrain_type = Terrain::NONE;
2855        }
2856    }
2857
2858    // Side Condition decrement
2859    for side_ref in sides {
2860        let side = state.get_side(side_ref);
2861        if side.side_conditions.reflect > 0 {
2862            incoming_instructions
2863                .instruction_list
2864                .push(Instruction::ChangeSideCondition(
2865                    ChangeSideConditionInstruction {
2866                        side_ref: *side_ref,
2867                        side_condition: PokemonSideCondition::Reflect,
2868                        amount: -1,
2869                    },
2870                ));
2871            side.side_conditions.reflect -= 1;
2872        }
2873        if side.side_conditions.light_screen > 0 {
2874            incoming_instructions
2875                .instruction_list
2876                .push(Instruction::ChangeSideCondition(
2877                    ChangeSideConditionInstruction {
2878                        side_ref: *side_ref,
2879                        side_condition: PokemonSideCondition::LightScreen,
2880                        amount: -1,
2881                    },
2882                ));
2883            side.side_conditions.light_screen -= 1;
2884        }
2885        if side.side_conditions.aurora_veil > 0 {
2886            incoming_instructions
2887                .instruction_list
2888                .push(Instruction::ChangeSideCondition(
2889                    ChangeSideConditionInstruction {
2890                        side_ref: *side_ref,
2891                        side_condition: PokemonSideCondition::AuroraVeil,
2892                        amount: -1,
2893                    },
2894                ));
2895            side.side_conditions.aurora_veil -= 1;
2896        }
2897        if side.side_conditions.tailwind > 0 {
2898            incoming_instructions
2899                .instruction_list
2900                .push(Instruction::ChangeSideCondition(
2901                    ChangeSideConditionInstruction {
2902                        side_ref: *side_ref,
2903                        side_condition: PokemonSideCondition::Tailwind,
2904                        amount: -1,
2905                    },
2906                ));
2907            side.side_conditions.tailwind -= 1;
2908        }
2909    }
2910
2911    // Weather Damage
2912    for side_ref in sides {
2913        if state.weather_is_active(&Weather::HAIL) {
2914            let active_pkmn = state.get_side(side_ref).get_active();
2915            if active_pkmn.hp == 0
2916                || active_pkmn.ability == Abilities::MAGICGUARD
2917                || active_pkmn.ability == Abilities::OVERCOAT
2918                || active_pkmn.ability == Abilities::ICEBODY
2919                || active_pkmn.has_type(&PokemonType::ICE)
2920            {
2921                continue;
2922            }
2923
2924            let damage_amount =
2925                cmp::min((active_pkmn.maxhp as f32 * 0.0625) as i16, active_pkmn.hp);
2926            let hail_damage_instruction = Instruction::Damage(DamageInstruction {
2927                side_ref: *side_ref,
2928                damage_amount: damage_amount,
2929            });
2930
2931            active_pkmn.hp -= damage_amount;
2932            incoming_instructions
2933                .instruction_list
2934                .push(hail_damage_instruction);
2935        } else if state.weather_is_active(&Weather::SAND) {
2936            let active_pkmn = state.get_side(side_ref).get_active();
2937            if active_pkmn.hp == 0
2938                || active_pkmn.ability == Abilities::MAGICGUARD
2939                || active_pkmn.ability == Abilities::OVERCOAT
2940                || active_pkmn.has_type(&PokemonType::GROUND)
2941                || active_pkmn.has_type(&PokemonType::STEEL)
2942                || active_pkmn.has_type(&PokemonType::ROCK)
2943            {
2944                continue;
2945            }
2946            let damage_amount =
2947                cmp::min((active_pkmn.maxhp as f32 * 0.0625) as i16, active_pkmn.hp);
2948            let sand_damage_instruction = Instruction::Damage(DamageInstruction {
2949                side_ref: *side_ref,
2950                damage_amount: damage_amount,
2951            });
2952            active_pkmn.hp -= damage_amount;
2953            incoming_instructions
2954                .instruction_list
2955                .push(sand_damage_instruction);
2956        }
2957    }
2958
2959    // future sight
2960    for side_ref in sides {
2961        let (attacking_side, defending_side) = state.get_both_sides(side_ref);
2962        if attacking_side.future_sight.0 > 0 {
2963            let decrement_future_sight_instruction =
2964                Instruction::DecrementFutureSight(DecrementFutureSightInstruction {
2965                    side_ref: *side_ref,
2966                });
2967            if attacking_side.future_sight.0 == 1 {
2968                let mut damage = calculate_futuresight_damage(
2969                    &attacking_side,
2970                    &defending_side,
2971                    &attacking_side.future_sight.1,
2972                );
2973                let defender = defending_side.get_active();
2974                damage = cmp::min(damage, defender.hp);
2975                let future_sight_damage_instruction = Instruction::Damage(DamageInstruction {
2976                    side_ref: side_ref.get_other_side(),
2977                    damage_amount: damage,
2978                });
2979                incoming_instructions
2980                    .instruction_list
2981                    .push(future_sight_damage_instruction);
2982                defender.hp -= damage;
2983            }
2984            attacking_side.future_sight.0 -= 1;
2985            incoming_instructions
2986                .instruction_list
2987                .push(decrement_future_sight_instruction);
2988        }
2989    }
2990
2991    // wish
2992    for side_ref in sides {
2993        let side = state.get_side(side_ref);
2994        let side_wish = side.wish;
2995        let active_pkmn = side.get_active();
2996
2997        if side_wish.0 > 0 {
2998            let decrement_wish_instruction = Instruction::DecrementWish(DecrementWishInstruction {
2999                side_ref: *side_ref,
3000            });
3001            if side_wish.0 == 1 && 0 < active_pkmn.hp && active_pkmn.hp < active_pkmn.maxhp {
3002                #[cfg(not(feature = "gen4"))]
3003                let heal_amount = cmp::min(active_pkmn.maxhp - active_pkmn.hp, side_wish.1);
3004
3005                #[cfg(feature = "gen4")]
3006                let heal_amount =
3007                    cmp::min(active_pkmn.maxhp - active_pkmn.hp, active_pkmn.maxhp / 2);
3008
3009                let wish_heal_instruction = Instruction::Heal(HealInstruction {
3010                    side_ref: *side_ref,
3011                    heal_amount: heal_amount,
3012                });
3013                incoming_instructions
3014                    .instruction_list
3015                    .push(wish_heal_instruction);
3016                active_pkmn.hp += heal_amount;
3017            }
3018            side.wish.0 -= 1;
3019            incoming_instructions
3020                .instruction_list
3021                .push(decrement_wish_instruction);
3022        }
3023    }
3024
3025    // status damage
3026    for side_ref in sides {
3027        let (side, other_side) = state.get_both_sides(side_ref);
3028        let toxic_count = side.side_conditions.toxic_count as f32;
3029        let active_pkmn = side.get_active();
3030        let other_side_active = other_side.get_active();
3031        if active_pkmn.hp == 0 || active_pkmn.ability == Abilities::MAGICGUARD {
3032            continue;
3033        }
3034
3035        match active_pkmn.status {
3036            PokemonStatus::BURN => {
3037                #[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5", feature = "gen6"))]
3038                let mut damage_factor = 0.125;
3039
3040                #[cfg(any(feature = "gen7", feature = "gen8", feature = "gen9",))]
3041                let mut damage_factor = 0.0625;
3042
3043                if active_pkmn.ability == Abilities::HEATPROOF {
3044                    damage_factor /= 2.0;
3045                }
3046                let damage_amount = cmp::max(
3047                    cmp::min(
3048                        (active_pkmn.maxhp as f32 * damage_factor) as i16,
3049                        active_pkmn.hp,
3050                    ),
3051                    1,
3052                );
3053                let burn_damage_instruction = Instruction::Damage(DamageInstruction {
3054                    side_ref: *side_ref,
3055                    damage_amount: damage_amount,
3056                });
3057                active_pkmn.hp -= damage_amount;
3058                incoming_instructions
3059                    .instruction_list
3060                    .push(burn_damage_instruction);
3061            }
3062            PokemonStatus::POISON if active_pkmn.ability != Abilities::POISONHEAL => {
3063                let damage_amount = cmp::max(
3064                    1,
3065                    cmp::min((active_pkmn.maxhp as f32 * 0.125) as i16, active_pkmn.hp),
3066                );
3067
3068                let poison_damage_instruction = Instruction::Damage(DamageInstruction {
3069                    side_ref: *side_ref,
3070                    damage_amount: damage_amount,
3071                });
3072                active_pkmn.hp -= damage_amount;
3073                incoming_instructions
3074                    .instruction_list
3075                    .push(poison_damage_instruction);
3076            }
3077            PokemonStatus::TOXIC => {
3078                if active_pkmn.ability != Abilities::POISONHEAL
3079                    || other_side_active.ability == Abilities::NEUTRALIZINGGAS
3080                {
3081                    let toxic_multiplier = (1.0 / 16.0) * toxic_count + (1.0 / 16.0);
3082                    let damage_amount = cmp::max(
3083                        cmp::min(
3084                            (active_pkmn.maxhp as f32 * toxic_multiplier) as i16,
3085                            active_pkmn.hp,
3086                        ),
3087                        1,
3088                    );
3089                    let toxic_damage_instruction = Instruction::Damage(DamageInstruction {
3090                        side_ref: *side_ref,
3091                        damage_amount,
3092                    });
3093
3094                    active_pkmn.hp -= damage_amount;
3095                    incoming_instructions
3096                        .instruction_list
3097                        .push(toxic_damage_instruction);
3098                }
3099
3100                // toxic counter is always incremented, even if the pokemon has poison heal
3101                let toxic_counter_increment_instruction =
3102                    Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
3103                        side_ref: *side_ref,
3104                        side_condition: PokemonSideCondition::ToxicCount,
3105                        amount: 1,
3106                    });
3107                side.side_conditions.toxic_count += 1;
3108                incoming_instructions
3109                    .instruction_list
3110                    .push(toxic_counter_increment_instruction);
3111            }
3112            _ => {}
3113        }
3114    }
3115
3116    // ability/item end-of-turn effects
3117    for side_ref in sides {
3118        let side = state.get_side(side_ref);
3119        let active_pkmn = side.get_active();
3120        if active_pkmn.hp == 0 {
3121            continue;
3122        }
3123
3124        item_end_of_turn(state, side_ref, &mut incoming_instructions);
3125        ability_end_of_turn(state, side_ref, &mut incoming_instructions);
3126    }
3127
3128    // leechseed sap
3129    for side_ref in sides {
3130        let (leechseed_side, other_side) = state.get_both_sides(side_ref);
3131        if leechseed_side
3132            .volatile_statuses
3133            .contains(&PokemonVolatileStatus::LEECHSEED)
3134        {
3135            let active_pkmn = leechseed_side.get_active();
3136            let other_active_pkmn = other_side.get_active();
3137            if active_pkmn.hp == 0
3138                || other_active_pkmn.hp == 0
3139                || active_pkmn.ability == Abilities::MAGICGUARD
3140            {
3141                continue;
3142            }
3143
3144            let health_sapped = cmp::min((active_pkmn.maxhp as f32 * 0.125) as i16, active_pkmn.hp);
3145            let damage_ins = Instruction::Damage(DamageInstruction {
3146                side_ref: *side_ref,
3147                damage_amount: health_sapped,
3148            });
3149            active_pkmn.hp -= health_sapped;
3150            incoming_instructions.instruction_list.push(damage_ins);
3151
3152            let health_recovered = cmp::min(
3153                health_sapped,
3154                other_active_pkmn.maxhp - other_active_pkmn.hp,
3155            );
3156            if health_recovered > 0 {
3157                let heal_ins = Instruction::Heal(HealInstruction {
3158                    side_ref: side_ref.get_other_side(),
3159                    heal_amount: health_recovered,
3160                });
3161                other_active_pkmn.hp += health_recovered;
3162                incoming_instructions.instruction_list.push(heal_ins);
3163            }
3164        }
3165    }
3166
3167    // volatile statuses
3168    for side_ref in sides {
3169        let side = state.get_side(side_ref);
3170        if side.get_active().hp == 0 {
3171            continue;
3172        }
3173
3174        if side
3175            .volatile_statuses
3176            .contains(&PokemonVolatileStatus::SLOWSTART)
3177        {
3178            incoming_instructions
3179                .instruction_list
3180                .push(Instruction::ChangeVolatileStatusDuration(
3181                    ChangeVolatileStatusDurationInstruction {
3182                        side_ref: *side_ref,
3183                        volatile_status: PokemonVolatileStatus::SLOWSTART,
3184                        amount: -1,
3185                    },
3186                ));
3187            side.volatile_status_durations.slowstart -= 1;
3188            if side.volatile_status_durations.slowstart == 0 {
3189                incoming_instructions
3190                    .instruction_list
3191                    .push(Instruction::RemoveVolatileStatus(
3192                        RemoveVolatileStatusInstruction {
3193                            side_ref: *side_ref,
3194                            volatile_status: PokemonVolatileStatus::SLOWSTART,
3195                        },
3196                    ));
3197                side.volatile_statuses
3198                    .remove(&PokemonVolatileStatus::SLOWSTART);
3199            }
3200        }
3201
3202        if side
3203            .volatile_statuses
3204            .contains(&PokemonVolatileStatus::LOCKEDMOVE)
3205        {
3206            // the number says 2 but this is 3 turns of using a locking move
3207            // because turn 0 is the first turn the move is used
3208            // branching is not implemented here so the engine assumes it always lasts 3 turns
3209            if side.volatile_status_durations.lockedmove == 2 {
3210                side.volatile_status_durations.lockedmove = 0;
3211                side.volatile_statuses
3212                    .remove(&PokemonVolatileStatus::LOCKEDMOVE);
3213                incoming_instructions.instruction_list.push(
3214                    Instruction::ChangeVolatileStatusDuration(
3215                        ChangeVolatileStatusDurationInstruction {
3216                            side_ref: *side_ref,
3217                            volatile_status: PokemonVolatileStatus::LOCKEDMOVE,
3218                            amount: -2,
3219                        },
3220                    ),
3221                );
3222                incoming_instructions
3223                    .instruction_list
3224                    .push(Instruction::RemoveVolatileStatus(
3225                        RemoveVolatileStatusInstruction {
3226                            side_ref: *side_ref,
3227                            volatile_status: PokemonVolatileStatus::LOCKEDMOVE,
3228                        },
3229                    ));
3230                if !side
3231                    .volatile_statuses
3232                    .contains(&PokemonVolatileStatus::CONFUSION)
3233                {
3234                    incoming_instructions
3235                        .instruction_list
3236                        .push(Instruction::ApplyVolatileStatus(
3237                            ApplyVolatileStatusInstruction {
3238                                side_ref: *side_ref,
3239                                volatile_status: PokemonVolatileStatus::CONFUSION,
3240                            },
3241                        ));
3242                    side.volatile_statuses
3243                        .insert(PokemonVolatileStatus::CONFUSION);
3244                }
3245            } else {
3246                side.volatile_status_durations.lockedmove += 1;
3247                incoming_instructions.instruction_list.push(
3248                    Instruction::ChangeVolatileStatusDuration(
3249                        ChangeVolatileStatusDurationInstruction {
3250                            side_ref: *side_ref,
3251                            volatile_status: PokemonVolatileStatus::LOCKEDMOVE,
3252                            amount: 1,
3253                        },
3254                    ),
3255                );
3256            }
3257        }
3258
3259        if side
3260            .volatile_statuses
3261            .contains(&PokemonVolatileStatus::YAWN)
3262        {
3263            match side.volatile_status_durations.yawn {
3264                0 => {
3265                    incoming_instructions.instruction_list.push(
3266                        Instruction::ChangeVolatileStatusDuration(
3267                            ChangeVolatileStatusDurationInstruction {
3268                                side_ref: *side_ref,
3269                                volatile_status: PokemonVolatileStatus::YAWN,
3270                                amount: 1,
3271                            },
3272                        ),
3273                    );
3274                    side.volatile_status_durations.yawn += 1;
3275                }
3276                1 => {
3277                    side.volatile_statuses.remove(&PokemonVolatileStatus::YAWN);
3278                    incoming_instructions
3279                        .instruction_list
3280                        .push(Instruction::RemoveVolatileStatus(
3281                            RemoveVolatileStatusInstruction {
3282                                side_ref: *side_ref,
3283                                volatile_status: PokemonVolatileStatus::YAWN,
3284                            },
3285                        ));
3286                    incoming_instructions.instruction_list.push(
3287                        Instruction::ChangeVolatileStatusDuration(
3288                            ChangeVolatileStatusDurationInstruction {
3289                                side_ref: *side_ref,
3290                                volatile_status: PokemonVolatileStatus::YAWN,
3291                                amount: -1,
3292                            },
3293                        ),
3294                    );
3295                    side.volatile_status_durations.yawn -= 1;
3296
3297                    let active = side.get_active();
3298                    if active.status == PokemonStatus::NONE {
3299                        active.status = PokemonStatus::SLEEP;
3300                        incoming_instructions
3301                            .instruction_list
3302                            .push(Instruction::ChangeStatus(ChangeStatusInstruction {
3303                                side_ref: *side_ref,
3304                                pokemon_index: side.active_index,
3305                                old_status: PokemonStatus::NONE,
3306                                new_status: PokemonStatus::SLEEP,
3307                            }));
3308                    }
3309                }
3310                _ => panic!(
3311                    "Yawn duration cannot be {} when yawn volatile is active",
3312                    side.volatile_status_durations.yawn
3313                ),
3314            }
3315        }
3316
3317        if side
3318            .volatile_statuses
3319            .contains(&PokemonVolatileStatus::PERISH1)
3320        {
3321            let active_pkmn = side.get_active();
3322            incoming_instructions
3323                .instruction_list
3324                .push(Instruction::Damage(DamageInstruction {
3325                    side_ref: *side_ref,
3326                    damage_amount: active_pkmn.hp,
3327                }));
3328            active_pkmn.hp = 0;
3329        }
3330
3331        if side
3332            .volatile_statuses
3333            .remove(&PokemonVolatileStatus::PERISH2)
3334        {
3335            side.volatile_statuses
3336                .insert(PokemonVolatileStatus::PERISH1);
3337            incoming_instructions
3338                .instruction_list
3339                .push(Instruction::RemoveVolatileStatus(
3340                    RemoveVolatileStatusInstruction {
3341                        side_ref: *side_ref,
3342                        volatile_status: PokemonVolatileStatus::PERISH2,
3343                    },
3344                ));
3345            incoming_instructions
3346                .instruction_list
3347                .push(Instruction::ApplyVolatileStatus(
3348                    ApplyVolatileStatusInstruction {
3349                        side_ref: *side_ref,
3350                        volatile_status: PokemonVolatileStatus::PERISH1,
3351                    },
3352                ));
3353        }
3354        if side
3355            .volatile_statuses
3356            .remove(&PokemonVolatileStatus::PERISH3)
3357        {
3358            side.volatile_statuses
3359                .insert(PokemonVolatileStatus::PERISH2);
3360            incoming_instructions
3361                .instruction_list
3362                .push(Instruction::RemoveVolatileStatus(
3363                    RemoveVolatileStatusInstruction {
3364                        side_ref: *side_ref,
3365                        volatile_status: PokemonVolatileStatus::PERISH3,
3366                    },
3367                ));
3368            incoming_instructions
3369                .instruction_list
3370                .push(Instruction::ApplyVolatileStatus(
3371                    ApplyVolatileStatusInstruction {
3372                        side_ref: *side_ref,
3373                        volatile_status: PokemonVolatileStatus::PERISH2,
3374                    },
3375                ));
3376        }
3377        if side
3378            .volatile_statuses
3379            .remove(&PokemonVolatileStatus::PERISH4)
3380        {
3381            side.volatile_statuses
3382                .insert(PokemonVolatileStatus::PERISH3);
3383            incoming_instructions
3384                .instruction_list
3385                .push(Instruction::RemoveVolatileStatus(
3386                    RemoveVolatileStatusInstruction {
3387                        side_ref: *side_ref,
3388                        volatile_status: PokemonVolatileStatus::PERISH4,
3389                    },
3390                ));
3391            incoming_instructions
3392                .instruction_list
3393                .push(Instruction::ApplyVolatileStatus(
3394                    ApplyVolatileStatusInstruction {
3395                        side_ref: *side_ref,
3396                        volatile_status: PokemonVolatileStatus::PERISH3,
3397                    },
3398                ));
3399        }
3400
3401        if side
3402            .volatile_statuses
3403            .remove(&PokemonVolatileStatus::FLINCH)
3404        {
3405            incoming_instructions
3406                .instruction_list
3407                .push(Instruction::RemoveVolatileStatus(
3408                    RemoveVolatileStatusInstruction {
3409                        side_ref: *side_ref,
3410                        volatile_status: PokemonVolatileStatus::FLINCH,
3411                    },
3412                ));
3413        }
3414        if side.volatile_statuses.remove(&PokemonVolatileStatus::ROOST) {
3415            incoming_instructions
3416                .instruction_list
3417                .push(Instruction::RemoveVolatileStatus(
3418                    RemoveVolatileStatusInstruction {
3419                        side_ref: *side_ref,
3420                        volatile_status: PokemonVolatileStatus::ROOST,
3421                    },
3422                ));
3423        }
3424
3425        if side
3426            .volatile_statuses
3427            .contains(&PokemonVolatileStatus::PARTIALLYTRAPPED)
3428        {
3429            let active_pkmn = side.get_active();
3430
3431            #[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5"))]
3432            let damage_amount = cmp::min((active_pkmn.maxhp as f32 / 16.0) as i16, active_pkmn.hp);
3433
3434            #[cfg(any(feature = "gen6", feature = "gen7", feature = "gen8", feature = "gen9"))]
3435            let damage_amount = cmp::min((active_pkmn.maxhp as f32 / 8.0) as i16, active_pkmn.hp);
3436
3437            incoming_instructions
3438                .instruction_list
3439                .push(Instruction::Damage(DamageInstruction {
3440                    side_ref: *side_ref,
3441                    damage_amount,
3442                }));
3443            active_pkmn.hp -= damage_amount;
3444        }
3445        if side
3446            .volatile_statuses
3447            .contains(&PokemonVolatileStatus::SALTCURE)
3448        {
3449            let active_pkmn = side.get_active();
3450            let mut divisor = 8.0;
3451            if active_pkmn.has_type(&PokemonType::WATER)
3452                || active_pkmn.has_type(&PokemonType::STEEL)
3453            {
3454                divisor = 4.0;
3455            }
3456            let damage_amount =
3457                cmp::min((active_pkmn.maxhp as f32 / divisor) as i16, active_pkmn.hp);
3458            incoming_instructions
3459                .instruction_list
3460                .push(Instruction::Damage(DamageInstruction {
3461                    side_ref: *side_ref,
3462                    damage_amount: damage_amount,
3463                }));
3464            active_pkmn.hp -= damage_amount;
3465        }
3466
3467        let possible_statuses = [
3468            PokemonVolatileStatus::PROTECT,
3469            PokemonVolatileStatus::BANEFULBUNKER,
3470            PokemonVolatileStatus::BURNINGBULWARK,
3471            PokemonVolatileStatus::SPIKYSHIELD,
3472            PokemonVolatileStatus::SILKTRAP,
3473            PokemonVolatileStatus::ENDURE,
3474        ];
3475
3476        let mut protect_vs = None;
3477        for status in &possible_statuses {
3478            if side.volatile_statuses.contains(status) {
3479                protect_vs = Some(*status);
3480                break;
3481            }
3482        }
3483
3484        if let Some(protect_vs) = protect_vs {
3485            incoming_instructions
3486                .instruction_list
3487                .push(Instruction::RemoveVolatileStatus(
3488                    RemoveVolatileStatusInstruction {
3489                        side_ref: *side_ref,
3490                        volatile_status: protect_vs,
3491                    },
3492                ));
3493            side.volatile_statuses.remove(&protect_vs);
3494            incoming_instructions
3495                .instruction_list
3496                .push(Instruction::ChangeSideCondition(
3497                    ChangeSideConditionInstruction {
3498                        side_ref: *side_ref,
3499                        side_condition: PokemonSideCondition::Protect,
3500                        amount: 1,
3501                    },
3502                ));
3503            side.side_conditions.protect += 1;
3504        } else if side.side_conditions.protect > 0 {
3505            incoming_instructions
3506                .instruction_list
3507                .push(Instruction::ChangeSideCondition(
3508                    ChangeSideConditionInstruction {
3509                        side_ref: *side_ref,
3510                        side_condition: PokemonSideCondition::Protect,
3511                        amount: -1 * side.side_conditions.protect,
3512                    },
3513                ));
3514            side.side_conditions.protect -= side.side_conditions.protect;
3515        }
3516    } // end volatile statuses
3517}
3518
3519fn run_move(
3520    state: &mut State,
3521    attacking_side: SideReference,
3522    mut instructions: StateInstructions,
3523    hit_count: i8,
3524    does_damage: bool,
3525    damage_amount: i16,
3526    choice: &mut Choice,
3527    defender_choice: &Choice,
3528    final_instructions: &mut Vec<StateInstructions>,
3529) {
3530    let mut hit_sub = false;
3531    for _ in 0..hit_count {
3532        if does_damage {
3533            hit_sub = generate_instructions_from_damage(
3534                state,
3535                choice,
3536                damage_amount,
3537                &attacking_side,
3538                &mut instructions,
3539            );
3540        }
3541        if let Some(side_condition) = &choice.side_condition {
3542            generate_instructions_from_side_conditions(
3543                state,
3544                side_condition,
3545                &attacking_side,
3546                &mut instructions,
3547            );
3548        }
3549        choice_hazard_clear(state, &choice, &attacking_side, &mut instructions);
3550        if let Some(volatile_status) = &choice.volatile_status {
3551            get_instructions_from_volatile_statuses(
3552                state,
3553                &choice,
3554                volatile_status,
3555                &attacking_side,
3556                &mut instructions,
3557            );
3558        }
3559        if let Some(status) = &choice.status {
3560            get_instructions_from_status_effects(
3561                state,
3562                status,
3563                &attacking_side,
3564                &mut instructions,
3565                hit_sub,
3566            );
3567        }
3568        if let Some(heal) = &choice.heal {
3569            get_instructions_from_heal(state, heal, &attacking_side, &mut instructions);
3570        }
3571    } // end multi-hit
3572      // this is wrong, but I am deciding it is good enough for this engine (for now)
3573      // each multi-hit move should trigger a chance for a secondary effect,
3574      // but the way this engine was structured makes it difficult to implement
3575      // without some performance hits.
3576
3577    if let Some(boost) = &choice.boost {
3578        get_instructions_from_boosts(state, boost, &attacking_side, &mut instructions);
3579    }
3580
3581    if choice.flags.drag
3582        && state
3583            .get_side_immutable(&attacking_side.get_other_side())
3584            .get_active_immutable()
3585            .ability
3586            != Abilities::GUARDDOG
3587    {
3588        get_instructions_from_drag(state, &attacking_side, instructions, final_instructions);
3589        return;
3590    }
3591
3592    // Only entered if the move causes a switch-out
3593    // U-turn, Volt Switch, Baton Pass, etc.
3594    // This deals with a bunch of flags that are required for the next turn to run properly
3595    if choice.flags.pivot {
3596        match attacking_side {
3597            SideReference::SideOne => {
3598                if state.side_one.visible_alive_pkmn() > 1 {
3599                    if choice.move_id == Choices::BATONPASS {
3600                        state.side_one.baton_passing = !state.side_one.baton_passing;
3601                        instructions
3602                            .instruction_list
3603                            .push(Instruction::ToggleBatonPassing(
3604                                ToggleBatonPassingInstruction {
3605                                    side_ref: SideReference::SideOne,
3606                                },
3607                            ));
3608                    } else if choice.move_id == Choices::SHEDTAIL {
3609                        state.side_one.shed_tailing = !state.side_one.shed_tailing;
3610                        instructions
3611                            .instruction_list
3612                            .push(Instruction::ToggleShedTailing(
3613                                ToggleShedTailingInstruction {
3614                                    side_ref: SideReference::SideOne,
3615                                },
3616                            ));
3617                    }
3618                    state.side_one.force_switch = !state.side_one.force_switch;
3619                    instructions
3620                        .instruction_list
3621                        .push(Instruction::ToggleSideOneForceSwitch);
3622
3623                    if choice.first_move {
3624                        instructions.instruction_list.push(
3625                            Instruction::SetSideTwoMoveSecondSwitchOutMove(
3626                                SetSecondMoveSwitchOutMoveInstruction {
3627                                    new_choice: defender_choice.move_id,
3628                                    previous_choice: state
3629                                        .side_two
3630                                        .switch_out_move_second_saved_move,
3631                                },
3632                            ),
3633                        );
3634                        state.side_two.switch_out_move_second_saved_move = defender_choice.move_id;
3635                    } else {
3636                        instructions.instruction_list.push(
3637                            Instruction::SetSideTwoMoveSecondSwitchOutMove(
3638                                SetSecondMoveSwitchOutMoveInstruction {
3639                                    new_choice: Choices::NONE,
3640                                    previous_choice: state
3641                                        .side_two
3642                                        .switch_out_move_second_saved_move,
3643                                },
3644                            ),
3645                        );
3646                        state.side_two.switch_out_move_second_saved_move = defender_choice.move_id;
3647                    }
3648                }
3649            }
3650            SideReference::SideTwo => {
3651                if state.side_two.visible_alive_pkmn() > 1 {
3652                    if choice.move_id == Choices::BATONPASS {
3653                        state.side_two.baton_passing = !state.side_two.baton_passing;
3654                        instructions
3655                            .instruction_list
3656                            .push(Instruction::ToggleBatonPassing(
3657                                ToggleBatonPassingInstruction {
3658                                    side_ref: SideReference::SideTwo,
3659                                },
3660                            ));
3661                    } else if choice.move_id == Choices::SHEDTAIL {
3662                        state.side_two.shed_tailing = !state.side_two.shed_tailing;
3663                        instructions
3664                            .instruction_list
3665                            .push(Instruction::ToggleShedTailing(
3666                                ToggleShedTailingInstruction {
3667                                    side_ref: SideReference::SideTwo,
3668                                },
3669                            ));
3670                    }
3671                    state.side_two.force_switch = !state.side_two.force_switch;
3672                    instructions
3673                        .instruction_list
3674                        .push(Instruction::ToggleSideTwoForceSwitch);
3675
3676                    if choice.first_move {
3677                        instructions.instruction_list.push(
3678                            Instruction::SetSideOneMoveSecondSwitchOutMove(
3679                                SetSecondMoveSwitchOutMoveInstruction {
3680                                    new_choice: defender_choice.move_id,
3681                                    previous_choice: state
3682                                        .side_one
3683                                        .switch_out_move_second_saved_move,
3684                                },
3685                            ),
3686                        );
3687                        state.side_one.switch_out_move_second_saved_move = defender_choice.move_id;
3688                    } else {
3689                        instructions.instruction_list.push(
3690                            Instruction::SetSideOneMoveSecondSwitchOutMove(
3691                                SetSecondMoveSwitchOutMoveInstruction {
3692                                    new_choice: Choices::NONE,
3693                                    previous_choice: state
3694                                        .side_one
3695                                        .switch_out_move_second_saved_move,
3696                                },
3697                            ),
3698                        );
3699                        state.side_one.switch_out_move_second_saved_move = defender_choice.move_id;
3700                    }
3701                }
3702            }
3703        }
3704    }
3705
3706    if state
3707        .get_side_immutable(&attacking_side.get_other_side())
3708        .get_active_immutable()
3709        .item
3710        == Items::COVERTCLOAK
3711    {
3712        state.reverse_instructions(&instructions.instruction_list);
3713        final_instructions.push(instructions);
3714    } else if let Some(secondaries_vec) = &choice.secondaries {
3715        state.reverse_instructions(&instructions.instruction_list);
3716        let instructions_vec_after_secondaries = get_instructions_from_secondaries(
3717            state,
3718            &choice,
3719            secondaries_vec,
3720            &attacking_side,
3721            instructions,
3722            hit_sub,
3723        );
3724        final_instructions.extend(instructions_vec_after_secondaries);
3725    } else {
3726        state.reverse_instructions(&instructions.instruction_list);
3727        final_instructions.push(instructions);
3728    }
3729}
3730
3731fn after_move_finish(state: &mut State, final_instructions: &mut Vec<StateInstructions>) {
3732    for state_instructions in final_instructions.iter_mut() {
3733        state.apply_instructions(&state_instructions.instruction_list);
3734
3735        // check if anybody has negative boosts and a whiteherb
3736        // if so, consume the item and set the boosts to 0
3737        for side_ref in [SideReference::SideOne, SideReference::SideTwo] {
3738            let side = state.get_side(&side_ref);
3739            let active_has_whiteherb = side.get_active_immutable().item == Items::WHITEHERB;
3740            if active_has_whiteherb {
3741                if side.reset_negative_boosts(side_ref, state_instructions) {
3742                    let active = side.get_active();
3743                    active.item = Items::NONE;
3744                    state_instructions
3745                        .instruction_list
3746                        .push(Instruction::ChangeItem(ChangeItemInstruction {
3747                            side_ref,
3748                            current_item: Items::WHITEHERB,
3749                            new_item: Items::NONE,
3750                        }));
3751                }
3752            }
3753        }
3754        state.reverse_instructions(&state_instructions.instruction_list);
3755    }
3756}
3757
3758fn handle_both_moves(
3759    state: &mut State,
3760    first_move_side_choice: &mut Choice,
3761    second_move_side_choice: &mut Choice,
3762    first_move_side_ref: SideReference,
3763    incoming_instructions: StateInstructions,
3764    state_instructions_vec: &mut Vec<StateInstructions>,
3765    branch_on_damage: bool,
3766) {
3767    generate_instructions_from_move(
3768        state,
3769        first_move_side_choice,
3770        second_move_side_choice,
3771        first_move_side_ref,
3772        incoming_instructions,
3773        state_instructions_vec,
3774        branch_on_damage,
3775    );
3776    after_move_finish(state, state_instructions_vec);
3777
3778    let mut i = 0;
3779    let vec_len = state_instructions_vec.len();
3780    second_move_side_choice.first_move = false;
3781    while i < vec_len {
3782        let state_instruction = state_instructions_vec.remove(0);
3783        generate_instructions_from_move(
3784            state,
3785            &mut second_move_side_choice.clone(), // this clone is needed because the choice may be modified in this loop
3786            first_move_side_choice,
3787            first_move_side_ref.get_other_side(),
3788            state_instruction,
3789            state_instructions_vec,
3790            branch_on_damage,
3791        );
3792        after_move_finish(state, state_instructions_vec);
3793        i += 1;
3794    }
3795}
3796
3797fn mega_evolve(state: &mut State, side_ref: SideReference, instructions: &mut StateInstructions) {
3798    let side = state.get_side(&side_ref);
3799    let active_pkmn = side.get_active();
3800
3801    // assumes that you can mega-evolve if this function is called
3802    let mega_evolve_data = active_pkmn
3803        .id
3804        .mega_evolve_target(active_pkmn.item)
3805        .unwrap_or_else(|| {
3806            panic!(
3807                "cannot mega evolve {:?} with {:?}",
3808                active_pkmn.id, active_pkmn.item
3809            )
3810        });
3811
3812    // change id
3813    instructions
3814        .instruction_list
3815        .push(Instruction::FormeChange(FormeChangeInstruction {
3816            side_ref,
3817            name_change: mega_evolve_data.id as i16 - active_pkmn.id as i16,
3818        }));
3819    active_pkmn.id = mega_evolve_data.id;
3820
3821    // change stats
3822    active_pkmn.recalculate_stats(&side_ref, instructions);
3823
3824    // change ability
3825    if mega_evolve_data.ability != active_pkmn.ability {
3826        instructions
3827            .instruction_list
3828            .push(Instruction::ChangeAbility(ChangeAbilityInstruction {
3829                side_ref,
3830                ability_change: mega_evolve_data.ability as i16 - active_pkmn.ability as i16,
3831            }));
3832        active_pkmn.ability = mega_evolve_data.ability;
3833    }
3834    // change type
3835    if mega_evolve_data.types != active_pkmn.types {
3836        instructions
3837            .instruction_list
3838            .push(Instruction::ChangeType(ChangeType {
3839                side_ref,
3840                new_types: mega_evolve_data.types,
3841                old_types: active_pkmn.types,
3842            }));
3843        active_pkmn.types = mega_evolve_data.types;
3844    }
3845
3846    // ability on switch in
3847    ability_on_switch_in(state, &side_ref, instructions);
3848}
3849
3850pub fn generate_instructions_from_move_pair(
3851    state: &mut State,
3852    side_one_move: &MoveChoice,
3853    side_two_move: &MoveChoice,
3854    branch_on_damage: bool,
3855) -> Vec<StateInstructions> {
3856    let mut side_one_choice;
3857    let mut s1_tera = false;
3858    let mut s1_mega = false;
3859    let mut s1_replacing_fainted_pkmn = false;
3860    match side_one_move {
3861        MoveChoice::Switch(switch_id) => {
3862            if state.side_one.get_active().hp == 0 {
3863                s1_replacing_fainted_pkmn = true;
3864            }
3865            side_one_choice = Choice::default();
3866            side_one_choice.switch_id = *switch_id;
3867            side_one_choice.category = MoveCategory::Switch;
3868        }
3869        MoveChoice::Move(move_index) => {
3870            side_one_choice = state.side_one.get_active().moves[move_index].choice.clone();
3871            side_one_choice.move_index = *move_index;
3872        }
3873        MoveChoice::MoveTera(move_index) => {
3874            side_one_choice = state.side_one.get_active().moves[move_index].choice.clone();
3875            side_one_choice.move_index = *move_index;
3876            s1_tera = true;
3877        }
3878        MoveChoice::MoveMega(move_index) => {
3879            side_one_choice = state.side_one.get_active().moves[move_index].choice.clone();
3880            side_one_choice.move_index = *move_index;
3881            s1_mega = true;
3882        }
3883        MoveChoice::None => {
3884            side_one_choice = Choice::default();
3885        }
3886    }
3887
3888    let mut side_two_choice;
3889    let mut s2_replacing_fainted_pkmn = false;
3890    let mut s2_tera = false;
3891    let mut s2_mega = false;
3892    match side_two_move {
3893        MoveChoice::Switch(switch_id) => {
3894            if state.side_two.get_active().hp == 0 {
3895                s2_replacing_fainted_pkmn = true;
3896            }
3897            side_two_choice = Choice::default();
3898            side_two_choice.switch_id = *switch_id;
3899            side_two_choice.category = MoveCategory::Switch;
3900        }
3901        MoveChoice::Move(move_index) => {
3902            side_two_choice = state.side_two.get_active().moves[move_index].choice.clone();
3903            side_two_choice.move_index = *move_index;
3904        }
3905        MoveChoice::MoveTera(move_index) => {
3906            side_two_choice = state.side_two.get_active().moves[move_index].choice.clone();
3907            side_two_choice.move_index = *move_index;
3908            s2_tera = true;
3909        }
3910        MoveChoice::MoveMega(move_index) => {
3911            side_two_choice = state.side_two.get_active().moves[move_index].choice.clone();
3912            side_two_choice.move_index = *move_index;
3913            s2_mega = true;
3914        }
3915        MoveChoice::None => {
3916            side_two_choice = Choice::default();
3917        }
3918    }
3919
3920    let mut state_instructions_vec: Vec<StateInstructions> = Vec::with_capacity(4);
3921    let mut incoming_instructions: StateInstructions = StateInstructions::default();
3922
3923    // Run terastallization / Mega evolutions
3924    // Note: only create/apply instructions, don't apply changes
3925    // generate_instructions_from_move() assumes instructions have not been applied
3926    // technically, switches should happen _before_ this, but this is fine for now
3927    if s1_tera {
3928        state.side_one.get_active().terastallized = true;
3929        incoming_instructions
3930            .instruction_list
3931            .push(Instruction::ToggleTerastallized(
3932                ToggleTerastallizedInstruction {
3933                    side_ref: SideReference::SideOne,
3934                },
3935            ));
3936    }
3937    if s2_tera {
3938        state.side_two.get_active().terastallized = true;
3939        incoming_instructions
3940            .instruction_list
3941            .push(Instruction::ToggleTerastallized(
3942                ToggleTerastallizedInstruction {
3943                    side_ref: SideReference::SideTwo,
3944                },
3945            ));
3946    }
3947    if s1_mega {
3948        mega_evolve(state, SideReference::SideOne, &mut incoming_instructions);
3949    }
3950    if s2_mega {
3951        mega_evolve(state, SideReference::SideTwo, &mut incoming_instructions);
3952    }
3953
3954    modify_choice_priority(&state, &SideReference::SideOne, &mut side_one_choice);
3955    modify_choice_priority(&state, &SideReference::SideTwo, &mut side_two_choice);
3956
3957    // reverse instructions because mega-evolving might've added some
3958    state.reverse_instructions(&incoming_instructions.instruction_list);
3959
3960    match moves_first(
3961        &state,
3962        &side_one_choice,
3963        &side_two_choice,
3964        &mut incoming_instructions,
3965    ) {
3966        SideMovesFirst::SideOne => {
3967            handle_both_moves(
3968                state,
3969                &mut side_one_choice,
3970                &mut side_two_choice,
3971                SideReference::SideOne,
3972                incoming_instructions,
3973                &mut state_instructions_vec,
3974                branch_on_damage,
3975            );
3976
3977            for state_instruction in state_instructions_vec.iter_mut() {
3978                state.apply_instructions(&state_instruction.instruction_list);
3979                if !(s1_replacing_fainted_pkmn
3980                    || s2_replacing_fainted_pkmn
3981                    || state.side_one.force_switch
3982                    || state.side_two.force_switch)
3983                {
3984                    add_end_of_turn_instructions(state, state_instruction, &SideReference::SideOne);
3985                }
3986                state.reverse_instructions(&state_instruction.instruction_list);
3987            }
3988        }
3989        SideMovesFirst::SideTwo => {
3990            handle_both_moves(
3991                state,
3992                &mut side_two_choice,
3993                &mut side_one_choice,
3994                SideReference::SideTwo,
3995                incoming_instructions,
3996                &mut state_instructions_vec,
3997                branch_on_damage,
3998            );
3999            for state_instruction in state_instructions_vec.iter_mut() {
4000                state.apply_instructions(&state_instruction.instruction_list);
4001                if !(s1_replacing_fainted_pkmn
4002                    || s2_replacing_fainted_pkmn
4003                    || state.side_one.force_switch
4004                    || state.side_two.force_switch)
4005                {
4006                    add_end_of_turn_instructions(state, state_instruction, &SideReference::SideTwo);
4007                }
4008                state.reverse_instructions(&state_instruction.instruction_list);
4009            }
4010        }
4011        SideMovesFirst::SpeedTie => {
4012            let mut side_one_moves_first_instruction = incoming_instructions.clone();
4013            incoming_instructions.update_percentage(0.5);
4014            side_one_moves_first_instruction.update_percentage(0.5);
4015
4016            // side_one moves first
4017            handle_both_moves(
4018                state,
4019                &mut side_one_choice,
4020                &mut side_two_choice,
4021                SideReference::SideOne,
4022                side_one_moves_first_instruction,
4023                &mut state_instructions_vec,
4024                branch_on_damage,
4025            );
4026            for state_instruction in state_instructions_vec.iter_mut() {
4027                state.apply_instructions(&state_instruction.instruction_list);
4028                if !(s1_replacing_fainted_pkmn
4029                    || s2_replacing_fainted_pkmn
4030                    || state.side_one.force_switch
4031                    || state.side_two.force_switch)
4032                {
4033                    add_end_of_turn_instructions(state, state_instruction, &SideReference::SideOne);
4034                }
4035                state.reverse_instructions(&state_instruction.instruction_list);
4036            }
4037
4038            // side_two moves first
4039            let mut side_two_moves_first_si = Vec::with_capacity(4);
4040            handle_both_moves(
4041                state,
4042                &mut side_two_choice,
4043                &mut side_one_choice,
4044                SideReference::SideTwo,
4045                incoming_instructions,
4046                &mut side_two_moves_first_si,
4047                branch_on_damage,
4048            );
4049            for state_instruction in side_two_moves_first_si.iter_mut() {
4050                state.apply_instructions(&state_instruction.instruction_list);
4051                if !(s1_replacing_fainted_pkmn
4052                    || s2_replacing_fainted_pkmn
4053                    || state.side_one.force_switch
4054                    || state.side_two.force_switch)
4055                {
4056                    add_end_of_turn_instructions(state, state_instruction, &SideReference::SideTwo);
4057                }
4058                state.reverse_instructions(&state_instruction.instruction_list);
4059            }
4060
4061            // combine both vectors into the final vector
4062            state_instructions_vec.extend(side_two_moves_first_si);
4063        }
4064    }
4065    state_instructions_vec
4066}
4067
4068pub fn calculate_damage_rolls(
4069    mut state: State,
4070    attacking_side_ref: &SideReference,
4071    mut choice: Choice,
4072    mut defending_choice: &Choice,
4073) -> Option<Vec<i16>> {
4074    let mut incoming_instructions = StateInstructions::default();
4075
4076    if choice.flags.charge {
4077        choice.flags.charge = false;
4078    }
4079    if choice.move_id == Choices::FAKEOUT || choice.move_id == Choices::FIRSTIMPRESSION {
4080        state.get_side(attacking_side_ref).last_used_move = LastUsedMove::Switch(PokemonIndex::P0);
4081    }
4082
4083    let attacker_active = state
4084        .get_side_immutable(attacking_side_ref)
4085        .get_active_immutable();
4086    let defender_active = state
4087        .get_side_immutable(&attacking_side_ref.get_other_side())
4088        .get_active_immutable();
4089    match choice.move_id {
4090        Choices::SEISMICTOSS => {
4091            if type_effectiveness_modifier(&PokemonType::NORMAL, &defender_active) == 0.0 {
4092                return None;
4093            }
4094            return Some(vec![attacker_active.level as i16]);
4095        }
4096        Choices::NIGHTSHADE => {
4097            if type_effectiveness_modifier(&PokemonType::GHOST, &defender_active) == 0.0 {
4098                return None;
4099            }
4100            return Some(vec![attacker_active.level as i16]);
4101        }
4102        Choices::FINALGAMBIT => {
4103            if type_effectiveness_modifier(&PokemonType::GHOST, &defender_active) == 0.0 {
4104                return None;
4105            }
4106            return Some(vec![attacker_active.hp]);
4107        }
4108        Choices::ENDEAVOR => {
4109            if type_effectiveness_modifier(&PokemonType::GHOST, &defender_active) == 0.0
4110                || defender_active.hp <= attacker_active.hp
4111            {
4112                return None;
4113            }
4114            return Some(vec![defender_active.hp - attacker_active.hp]);
4115        }
4116        Choices::PAINSPLIT => {
4117            if type_effectiveness_modifier(&PokemonType::GHOST, &defender_active) == 0.0
4118                || defender_active.hp <= attacker_active.hp
4119            {
4120                return None;
4121            }
4122            return Some(vec![
4123                defender_active.hp - (attacker_active.hp + defender_active.hp) / 2,
4124            ]);
4125        }
4126        Choices::SUPERFANG
4127            if type_effectiveness_modifier(&PokemonType::NORMAL, &defender_active) == 0.0 =>
4128        {
4129            return None;
4130        }
4131        Choices::SUPERFANG | Choices::NATURESMADNESS | Choices::RUINATION => {
4132            return Some(vec![defender_active.hp / 2]);
4133        }
4134        Choices::SUCKERPUNCH | Choices::THUNDERCLAP => {
4135            defending_choice = MOVES.get(&Choices::TACKLE).unwrap();
4136        }
4137
4138        _ => {}
4139    }
4140
4141    before_move(
4142        &mut state,
4143        &mut choice,
4144        defending_choice,
4145        attacking_side_ref,
4146        &mut incoming_instructions,
4147    );
4148
4149    if choice.move_id == Choices::FUTURESIGHT {
4150        choice = MOVES.get(&Choices::FUTURESIGHT)?.clone();
4151    }
4152
4153    let mut return_vec = Vec::with_capacity(4);
4154    if let Some((damage, crit_damage)) =
4155        calculate_damage(&state, attacking_side_ref, &choice, DamageRolls::Max)
4156    {
4157        return_vec.push(damage);
4158        return_vec.push(crit_damage);
4159        Some(return_vec)
4160    } else {
4161        None
4162    }
4163}
4164
4165pub fn calculate_both_damage_rolls(
4166    state: &State,
4167    mut s1_choice: Choice,
4168    mut s2_choice: Choice,
4169    side_one_moves_first: bool,
4170) -> (Option<Vec<i16>>, Option<Vec<i16>>) {
4171    if side_one_moves_first {
4172        s1_choice.first_move = true;
4173        s2_choice.first_move = false;
4174    } else {
4175        s1_choice.first_move = false;
4176        s2_choice.first_move = true;
4177    }
4178
4179    let damages_dealt_s1 = calculate_damage_rolls(
4180        state.clone(),
4181        &SideReference::SideOne,
4182        s1_choice.clone(),
4183        &s2_choice,
4184    );
4185    let damages_dealt_s2 = calculate_damage_rolls(
4186        state.clone(),
4187        &SideReference::SideTwo,
4188        s2_choice,
4189        &s1_choice,
4190    );
4191
4192    (damages_dealt_s1, damages_dealt_s2)
4193}
4194
4195#[cfg(test)]
4196mod tests {
4197    use super::super::abilities::Abilities;
4198    use super::super::state::{PokemonVolatileStatus, Terrain, Weather};
4199    use super::*;
4200    use crate::choices::{Choices, MOVES};
4201    use crate::instruction::{
4202        ApplyVolatileStatusInstruction, BoostInstruction, ChangeItemInstruction,
4203        ChangeStatusInstruction, ChangeSubsituteHealthInstruction, ChangeTerrain,
4204        DamageInstruction, EnableMoveInstruction, SwitchInstruction,
4205    };
4206    use crate::state::{
4207        Move, PokemonBoostableStat, PokemonIndex, PokemonMoveIndex, PokemonSideCondition,
4208        PokemonStatus, SideReference, State,
4209    };
4210
4211    #[test]
4212    fn test_drag_move_as_second_move_exits_early_if_opponent_used_drag_move() {
4213        let mut state: State = State::default();
4214        let mut choice = MOVES.get(&Choices::DRAGONTAIL).unwrap().to_owned();
4215        choice.first_move = false;
4216
4217        let mut instructions = vec![];
4218        generate_instructions_from_move(
4219            &mut state,
4220            &mut choice,
4221            &MOVES.get(&Choices::DRAGONTAIL).unwrap(),
4222            SideReference::SideOne,
4223            StateInstructions::default(),
4224            &mut instructions,
4225            false,
4226        );
4227        assert_eq!(instructions, vec![StateInstructions::default()])
4228    }
4229
4230    #[test]
4231    fn test_electric_move_does_nothing_versus_ground_type() {
4232        let mut state: State = State::default();
4233        let mut choice = MOVES.get(&Choices::THUNDERBOLT).unwrap().to_owned();
4234        state.side_two.get_active().types = (PokemonType::GROUND, PokemonType::TYPELESS);
4235        choice.first_move = false;
4236
4237        let mut instructions = vec![];
4238        generate_instructions_from_move(
4239            &mut state,
4240            &mut choice,
4241            &MOVES.get(&Choices::TACKLE).unwrap(),
4242            SideReference::SideOne,
4243            StateInstructions::default(),
4244            &mut instructions,
4245            false,
4246        );
4247        assert_eq!(instructions, vec![StateInstructions::default()])
4248    }
4249
4250    #[test]
4251    fn test_grass_type_cannot_have_powder_move_used_against_it() {
4252        let mut state: State = State::default();
4253        let mut choice = MOVES.get(&Choices::SPORE).unwrap().to_owned(); // Spore is a powder move
4254        state.side_two.get_active().types = (PokemonType::GRASS, PokemonType::TYPELESS);
4255        choice.first_move = false;
4256
4257        let mut instructions = vec![];
4258        generate_instructions_from_move(
4259            &mut state,
4260            &mut choice,
4261            &MOVES.get(&Choices::TACKLE).unwrap(),
4262            SideReference::SideOne,
4263            StateInstructions::default(),
4264            &mut instructions,
4265            false,
4266        );
4267
4268        #[cfg(any(feature = "gen6", feature = "gen7", feature = "gen8", feature = "gen9"))]
4269        let expected_instructions = vec![StateInstructions::default()];
4270
4271        #[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5"))]
4272        let expected_instructions = vec![StateInstructions {
4273            percentage: 100.0,
4274            instruction_list: vec![Instruction::ChangeStatus(ChangeStatusInstruction {
4275                side_ref: SideReference::SideTwo,
4276                pokemon_index: PokemonIndex::P0,
4277                old_status: PokemonStatus::NONE,
4278                new_status: PokemonStatus::SLEEP,
4279            })],
4280        }];
4281
4282        assert_eq!(instructions, expected_instructions)
4283    }
4284
4285    #[test]
4286    fn test_spikes_sets_first_layer() {
4287        let mut state: State = State::default();
4288        let mut choice = MOVES.get(&Choices::SPIKES).unwrap().to_owned();
4289
4290        let mut instructions = vec![];
4291        generate_instructions_from_move(
4292            &mut state,
4293            &mut choice,
4294            &MOVES.get(&Choices::TACKLE).unwrap(),
4295            SideReference::SideOne,
4296            StateInstructions::default(),
4297            &mut instructions,
4298            false,
4299        );
4300
4301        let expected_instructions: StateInstructions = StateInstructions {
4302            percentage: 100.0,
4303            instruction_list: vec![Instruction::ChangeSideCondition(
4304                ChangeSideConditionInstruction {
4305                    side_ref: SideReference::SideTwo,
4306                    side_condition: PokemonSideCondition::Spikes,
4307                    amount: 1,
4308                },
4309            )],
4310        };
4311
4312        assert_eq!(instructions, vec![expected_instructions])
4313    }
4314
4315    #[test]
4316    fn test_spikes_layers_cannot_exceed_3() {
4317        let mut state: State = State::default();
4318        state.side_two.side_conditions.spikes = 3;
4319        let mut choice = MOVES.get(&Choices::SPIKES).unwrap().to_owned();
4320
4321        let mut instructions = vec![];
4322        generate_instructions_from_move(
4323            &mut state,
4324            &mut choice,
4325            &MOVES.get(&Choices::TACKLE).unwrap(),
4326            SideReference::SideOne,
4327            StateInstructions::default(),
4328            &mut instructions,
4329            false,
4330        );
4331
4332        let expected_instructions: StateInstructions = StateInstructions {
4333            percentage: 100.0,
4334            instruction_list: vec![],
4335        };
4336
4337        assert_eq!(instructions, vec![expected_instructions])
4338    }
4339
4340    #[test]
4341    fn test_aurora_veil_works_in_hail() {
4342        let mut state: State = State::default();
4343        state.weather.weather_type = Weather::HAIL;
4344        let mut choice = MOVES.get(&Choices::AURORAVEIL).unwrap().to_owned();
4345
4346        let mut instructions = vec![];
4347        generate_instructions_from_move(
4348            &mut state,
4349            &mut choice,
4350            &MOVES.get(&Choices::TACKLE).unwrap(),
4351            SideReference::SideOne,
4352            StateInstructions::default(),
4353            &mut instructions,
4354            false,
4355        );
4356
4357        let expected_instructions: StateInstructions = StateInstructions {
4358            percentage: 100.0,
4359            instruction_list: vec![Instruction::ChangeSideCondition(
4360                ChangeSideConditionInstruction {
4361                    side_ref: SideReference::SideOne,
4362                    side_condition: PokemonSideCondition::AuroraVeil,
4363                    amount: 5,
4364                },
4365            )],
4366        };
4367
4368        assert_eq!(instructions, vec![expected_instructions])
4369    }
4370
4371    #[test]
4372    fn test_auroa_veil_fails_outside_hail() {
4373        let mut state: State = State::default();
4374        let mut choice = MOVES.get(&Choices::AURORAVEIL).unwrap().to_owned();
4375
4376        let mut instructions = vec![];
4377        generate_instructions_from_move(
4378            &mut state,
4379            &mut choice,
4380            &MOVES.get(&Choices::TACKLE).unwrap(),
4381            SideReference::SideOne,
4382            StateInstructions::default(),
4383            &mut instructions,
4384            false,
4385        );
4386
4387        let expected_instructions: StateInstructions = StateInstructions {
4388            percentage: 100.0,
4389            instruction_list: vec![],
4390        };
4391
4392        assert_eq!(instructions, vec![expected_instructions])
4393    }
4394
4395    #[test]
4396    fn test_auroa_veil_fails_outside_of_hail() {
4397        let mut state: State = State::default();
4398        state.weather.weather_type = Weather::NONE;
4399        let mut choice = MOVES.get(&Choices::AURORAVEIL).unwrap().to_owned();
4400
4401        let mut instructions = vec![];
4402        generate_instructions_from_move(
4403            &mut state,
4404            &mut choice,
4405            &MOVES.get(&Choices::TACKLE).unwrap(),
4406            SideReference::SideOne,
4407            StateInstructions::default(),
4408            &mut instructions,
4409            false,
4410        );
4411
4412        let expected_instructions: StateInstructions = StateInstructions {
4413            percentage: 100.0,
4414            instruction_list: vec![],
4415        };
4416
4417        assert_eq!(instructions, vec![expected_instructions])
4418    }
4419
4420    #[test]
4421    fn test_stealthrock_cannot_exceed_1_layer() {
4422        let mut state: State = State::default();
4423        state.side_two.side_conditions.stealth_rock = 1;
4424        let mut choice = MOVES.get(&Choices::STEALTHROCK).unwrap().to_owned();
4425
4426        let mut instructions = vec![];
4427        generate_instructions_from_move(
4428            &mut state,
4429            &mut choice,
4430            &MOVES.get(&Choices::TACKLE).unwrap(),
4431            SideReference::SideOne,
4432            StateInstructions::default(),
4433            &mut instructions,
4434            false,
4435        );
4436
4437        let expected_instructions: StateInstructions = StateInstructions {
4438            percentage: 100.0,
4439            instruction_list: vec![],
4440        };
4441
4442        assert_eq!(instructions, vec![expected_instructions])
4443    }
4444
4445    #[test]
4446    fn test_stoneaxe_damage_and_stealthrock_setting() {
4447        let mut state: State = State::default();
4448        let mut choice = MOVES.get(&Choices::STONEAXE).unwrap().to_owned();
4449
4450        let mut instructions = vec![];
4451        generate_instructions_from_move(
4452            &mut state,
4453            &mut choice,
4454            &MOVES.get(&Choices::TACKLE).unwrap(),
4455            SideReference::SideOne,
4456            StateInstructions::default(),
4457            &mut instructions,
4458            false,
4459        );
4460
4461        let expected_instructions = vec![
4462            StateInstructions {
4463                percentage: 10.000002,
4464                instruction_list: vec![],
4465            },
4466            StateInstructions {
4467                percentage: 90.0,
4468                instruction_list: vec![
4469                    Instruction::Damage(DamageInstruction {
4470                        side_ref: SideReference::SideTwo,
4471                        damage_amount: 51,
4472                    }),
4473                    Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
4474                        side_ref: SideReference::SideTwo,
4475                        side_condition: PokemonSideCondition::Stealthrock,
4476                        amount: 1,
4477                    }),
4478                ],
4479            },
4480        ];
4481
4482        assert_eq!(instructions, expected_instructions)
4483    }
4484
4485    #[test]
4486    fn test_ceaselessedge_damage_and_stealthrock_setting() {
4487        let mut state: State = State::default();
4488        let mut choice = MOVES.get(&Choices::CEASELESSEDGE).unwrap().to_owned();
4489
4490        let mut instructions = vec![];
4491        generate_instructions_from_move(
4492            &mut state,
4493            &mut choice,
4494            &MOVES.get(&Choices::TACKLE).unwrap(),
4495            SideReference::SideOne,
4496            StateInstructions::default(),
4497            &mut instructions,
4498            false,
4499        );
4500
4501        let expected_instructions = vec![
4502            StateInstructions {
4503                percentage: 10.000002,
4504                instruction_list: vec![],
4505            },
4506            StateInstructions {
4507                percentage: 90.0,
4508                instruction_list: vec![
4509                    Instruction::Damage(DamageInstruction {
4510                        side_ref: SideReference::SideTwo,
4511                        damage_amount: 51,
4512                    }),
4513                    Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
4514                        side_ref: SideReference::SideTwo,
4515                        side_condition: PokemonSideCondition::Spikes,
4516                        amount: 1,
4517                    }),
4518                ],
4519            },
4520        ];
4521
4522        assert_eq!(instructions, expected_instructions)
4523    }
4524
4525    #[test]
4526    fn test_100_percent_secondary_volatilestatus() {
4527        let mut state: State = State::default();
4528        let mut choice = MOVES.get(&Choices::CHATTER).unwrap().to_owned();
4529
4530        let mut instructions = vec![];
4531        generate_instructions_from_move(
4532            &mut state,
4533            &mut choice,
4534            &MOVES.get(&Choices::TACKLE).unwrap(),
4535            SideReference::SideOne,
4536            StateInstructions::default(),
4537            &mut instructions,
4538            false,
4539        );
4540
4541        let expected_instructions = vec![StateInstructions {
4542            percentage: 100.0,
4543            instruction_list: vec![
4544                Instruction::Damage(DamageInstruction {
4545                    side_ref: SideReference::SideTwo,
4546                    damage_amount: 51,
4547                }),
4548                Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
4549                    side_ref: SideReference::SideTwo,
4550                    volatile_status: PokemonVolatileStatus::CONFUSION,
4551                }),
4552            ],
4553        }];
4554
4555        assert_eq!(instructions, expected_instructions)
4556    }
4557
4558    #[test]
4559    fn test_possible_secondary_volatilestatus() {
4560        let mut state: State = State::default();
4561        let mut choice = MOVES.get(&Choices::CONFUSION).unwrap().to_owned();
4562
4563        let mut instructions = vec![];
4564        generate_instructions_from_move(
4565            &mut state,
4566            &mut choice,
4567            &MOVES.get(&Choices::TACKLE).unwrap(),
4568            SideReference::SideOne,
4569            StateInstructions::default(),
4570            &mut instructions,
4571            false,
4572        );
4573
4574        let expected_instructions = vec![
4575            StateInstructions {
4576                percentage: 90.0,
4577                instruction_list: vec![Instruction::Damage(DamageInstruction {
4578                    side_ref: SideReference::SideTwo,
4579                    damage_amount: 40,
4580                })],
4581            },
4582            StateInstructions {
4583                percentage: 10.0,
4584                instruction_list: vec![
4585                    Instruction::Damage(DamageInstruction {
4586                        side_ref: SideReference::SideTwo,
4587                        damage_amount: 40,
4588                    }),
4589                    Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
4590                        side_ref: SideReference::SideTwo,
4591                        volatile_status: PokemonVolatileStatus::CONFUSION,
4592                    }),
4593                ],
4594            },
4595        ];
4596
4597        assert_eq!(instructions, expected_instructions)
4598    }
4599
4600    #[test]
4601    fn test_possible_secondary_volatilestatus_with_possible_accuracy() {
4602        let mut state: State = State::default();
4603        state.side_two.get_active().hp = 400;
4604        state.side_two.get_active().maxhp = 400;
4605        let mut choice = MOVES.get(&Choices::AXEKICK).unwrap().to_owned();
4606
4607        let mut instructions = vec![];
4608        generate_instructions_from_move(
4609            &mut state,
4610            &mut choice,
4611            &MOVES.get(&Choices::TACKLE).unwrap(),
4612            SideReference::SideOne,
4613            StateInstructions::default(),
4614            &mut instructions,
4615            false,
4616        );
4617
4618        let expected_instructions = vec![
4619            StateInstructions {
4620                percentage: 10.000002,
4621                instruction_list: vec![Instruction::Damage(DamageInstruction {
4622                    side_ref: SideReference::SideOne,
4623                    damage_amount: 50, // This move has recoil lol
4624                })],
4625            },
4626            StateInstructions {
4627                percentage: 63.0,
4628                instruction_list: vec![Instruction::Damage(DamageInstruction {
4629                    side_ref: SideReference::SideTwo,
4630                    damage_amount: 188,
4631                })],
4632            },
4633            StateInstructions {
4634                percentage: 27.0000019,
4635                instruction_list: vec![
4636                    Instruction::Damage(DamageInstruction {
4637                        side_ref: SideReference::SideTwo,
4638                        damage_amount: 188,
4639                    }),
4640                    Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
4641                        side_ref: SideReference::SideTwo,
4642                        volatile_status: PokemonVolatileStatus::CONFUSION,
4643                    }),
4644                ],
4645            },
4646        ];
4647
4648        assert_eq!(instructions, expected_instructions)
4649    }
4650
4651    #[test]
4652    fn test_basic_volatile_status_applied_to_self() {
4653        let mut state: State = State::default();
4654        let mut choice = MOVES.get(&Choices::AQUARING).unwrap().to_owned();
4655
4656        let mut instructions = vec![];
4657        generate_instructions_from_move(
4658            &mut state,
4659            &mut choice,
4660            &MOVES.get(&Choices::TACKLE).unwrap(),
4661            SideReference::SideOne,
4662            StateInstructions::default(),
4663            &mut instructions,
4664            false,
4665        );
4666
4667        let expected_instructions = vec![StateInstructions {
4668            percentage: 100.0,
4669            instruction_list: vec![Instruction::ApplyVolatileStatus(
4670                ApplyVolatileStatusInstruction {
4671                    side_ref: SideReference::SideOne,
4672                    volatile_status: PokemonVolatileStatus::AQUARING,
4673                },
4674            )],
4675        }];
4676
4677        assert_eq!(instructions, expected_instructions)
4678    }
4679
4680    #[test]
4681    fn test_basic_volatile_status_applied_to_opponent() {
4682        let mut state: State = State::default();
4683        let mut choice = MOVES.get(&Choices::ATTRACT).unwrap().to_owned();
4684
4685        let mut instructions = vec![];
4686        generate_instructions_from_move(
4687            &mut state,
4688            &mut choice,
4689            &MOVES.get(&Choices::TACKLE).unwrap(),
4690            SideReference::SideOne,
4691            StateInstructions::default(),
4692            &mut instructions,
4693            false,
4694        );
4695
4696        let expected_instructions = vec![StateInstructions {
4697            percentage: 100.0,
4698            instruction_list: vec![Instruction::ApplyVolatileStatus(
4699                ApplyVolatileStatusInstruction {
4700                    side_ref: SideReference::SideTwo,
4701                    volatile_status: PokemonVolatileStatus::ATTRACT,
4702                },
4703            )],
4704        }];
4705
4706        assert_eq!(instructions, expected_instructions)
4707    }
4708
4709    #[test]
4710    fn test_cannot_apply_volatile_status_twice() {
4711        let mut state: State = State::default();
4712        state
4713            .side_two
4714            .volatile_statuses
4715            .insert(PokemonVolatileStatus::ATTRACT);
4716        let mut choice = MOVES.get(&Choices::ATTRACT).unwrap().to_owned();
4717
4718        let mut instructions = vec![];
4719        generate_instructions_from_move(
4720            &mut state,
4721            &mut choice,
4722            &MOVES.get(&Choices::TACKLE).unwrap(),
4723            SideReference::SideOne,
4724            StateInstructions::default(),
4725            &mut instructions,
4726            false,
4727        );
4728
4729        let expected_instructions = vec![StateInstructions {
4730            percentage: 100.0,
4731            instruction_list: vec![],
4732        }];
4733
4734        assert_eq!(instructions, expected_instructions)
4735    }
4736
4737    #[test]
4738    fn test_substitute_failing_if_user_has_less_than_25_percent_hp() {
4739        let mut state: State = State::default();
4740        state.side_one.get_active().hp = 25;
4741        let mut choice = MOVES.get(&Choices::SUBSTITUTE).unwrap().to_owned();
4742
4743        let mut instructions = vec![];
4744        generate_instructions_from_move(
4745            &mut state,
4746            &mut choice,
4747            &MOVES.get(&Choices::TACKLE).unwrap(),
4748            SideReference::SideOne,
4749            StateInstructions::default(),
4750            &mut instructions,
4751            false,
4752        );
4753
4754        let expected_instructions = vec![StateInstructions {
4755            percentage: 100.0,
4756            instruction_list: vec![],
4757        }];
4758
4759        assert_eq!(instructions, expected_instructions)
4760    }
4761
4762    #[test]
4763    fn test_shedtail_failing_if_user_has_less_than_50_percent_hp() {
4764        let mut state: State = State::default();
4765        state.side_one.get_active().hp = 50;
4766        let mut choice = MOVES.get(&Choices::SHEDTAIL).unwrap().to_owned();
4767
4768        let mut instructions = vec![];
4769        generate_instructions_from_move(
4770            &mut state,
4771            &mut choice,
4772            &MOVES.get(&Choices::TACKLE).unwrap(),
4773            SideReference::SideOne,
4774            StateInstructions::default(),
4775            &mut instructions,
4776            false,
4777        );
4778
4779        let expected_instructions = vec![StateInstructions {
4780            percentage: 100.0,
4781            instruction_list: vec![],
4782        }];
4783
4784        assert_eq!(instructions, expected_instructions)
4785    }
4786
4787    #[test]
4788    fn test_basic_drag_move() {
4789        let mut state: State = State::default();
4790        let mut choice = MOVES.get(&Choices::WHIRLWIND).unwrap().to_owned();
4791
4792        let mut instructions = vec![];
4793        generate_instructions_from_move(
4794            &mut state,
4795            &mut choice,
4796            &MOVES.get(&Choices::TACKLE).unwrap(),
4797            SideReference::SideOne,
4798            StateInstructions::default(),
4799            &mut instructions,
4800            false,
4801        );
4802
4803        let expected_instructions = vec![
4804            StateInstructions {
4805                percentage: 20.0,
4806                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4807                    side_ref: SideReference::SideTwo,
4808                    previous_index: PokemonIndex::P0,
4809                    next_index: PokemonIndex::P1,
4810                })],
4811            },
4812            StateInstructions {
4813                percentage: 20.0,
4814                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4815                    side_ref: SideReference::SideTwo,
4816                    previous_index: PokemonIndex::P0,
4817                    next_index: PokemonIndex::P2,
4818                })],
4819            },
4820            StateInstructions {
4821                percentage: 20.0,
4822                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4823                    side_ref: SideReference::SideTwo,
4824                    previous_index: PokemonIndex::P0,
4825                    next_index: PokemonIndex::P3,
4826                })],
4827            },
4828            StateInstructions {
4829                percentage: 20.0,
4830                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4831                    side_ref: SideReference::SideTwo,
4832                    previous_index: PokemonIndex::P0,
4833                    next_index: PokemonIndex::P4,
4834                })],
4835            },
4836            StateInstructions {
4837                percentage: 20.0,
4838                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4839                    side_ref: SideReference::SideTwo,
4840                    previous_index: PokemonIndex::P0,
4841                    next_index: PokemonIndex::P5,
4842                })],
4843            },
4844        ];
4845
4846        assert_eq!(instructions, expected_instructions)
4847    }
4848
4849    #[test]
4850    fn test_basic_drag_move_with_fainted_reserve() {
4851        let mut state: State = State::default();
4852        state.side_two.pokemon[PokemonIndex::P1].hp = 0;
4853        state.side_two.pokemon[PokemonIndex::P3].hp = 0;
4854        let mut choice = MOVES.get(&Choices::WHIRLWIND).unwrap().to_owned();
4855
4856        let mut instructions = vec![];
4857        generate_instructions_from_move(
4858            &mut state,
4859            &mut choice,
4860            &MOVES.get(&Choices::TACKLE).unwrap(),
4861            SideReference::SideOne,
4862            StateInstructions::default(),
4863            &mut instructions,
4864            false,
4865        );
4866
4867        let expected_instructions = vec![
4868            StateInstructions {
4869                percentage: 33.333336,
4870                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4871                    side_ref: SideReference::SideTwo,
4872                    previous_index: PokemonIndex::P0,
4873                    next_index: PokemonIndex::P2,
4874                })],
4875            },
4876            StateInstructions {
4877                percentage: 33.333336,
4878                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4879                    side_ref: SideReference::SideTwo,
4880                    previous_index: PokemonIndex::P0,
4881                    next_index: PokemonIndex::P4,
4882                })],
4883            },
4884            StateInstructions {
4885                percentage: 33.333336,
4886                instruction_list: vec![Instruction::Switch(SwitchInstruction {
4887                    side_ref: SideReference::SideTwo,
4888                    previous_index: PokemonIndex::P0,
4889                    next_index: PokemonIndex::P5,
4890                })],
4891            },
4892        ];
4893
4894        assert_eq!(instructions, expected_instructions)
4895    }
4896
4897    #[test]
4898    fn test_basic_damaging_drag_move_with_fainted_reserve() {
4899        let mut state: State = State::default();
4900        state.side_two.pokemon[PokemonIndex::P1].hp = 0;
4901        state.side_two.pokemon[PokemonIndex::P3].hp = 0;
4902        let mut choice = MOVES.get(&Choices::DRAGONTAIL).unwrap().to_owned();
4903
4904        let mut instructions = vec![];
4905        generate_instructions_from_move(
4906            &mut state,
4907            &mut choice,
4908            &MOVES.get(&Choices::TACKLE).unwrap(),
4909            SideReference::SideOne,
4910            StateInstructions::default(),
4911            &mut instructions,
4912            false,
4913        );
4914
4915        let expected_instructions = vec![
4916            StateInstructions {
4917                percentage: 10.0000019,
4918                instruction_list: vec![], // The move missed
4919            },
4920            StateInstructions {
4921                percentage: 30.0,
4922                instruction_list: vec![
4923                    Instruction::Damage(DamageInstruction {
4924                        side_ref: SideReference::SideTwo,
4925                        damage_amount: 48,
4926                    }),
4927                    Instruction::Switch(SwitchInstruction {
4928                        side_ref: SideReference::SideTwo,
4929                        previous_index: PokemonIndex::P0,
4930                        next_index: PokemonIndex::P2,
4931                    }),
4932                ],
4933            },
4934            StateInstructions {
4935                percentage: 30.0,
4936                instruction_list: vec![
4937                    Instruction::Damage(DamageInstruction {
4938                        side_ref: SideReference::SideTwo,
4939                        damage_amount: 48,
4940                    }),
4941                    Instruction::Switch(SwitchInstruction {
4942                        side_ref: SideReference::SideTwo,
4943                        previous_index: PokemonIndex::P0,
4944                        next_index: PokemonIndex::P4,
4945                    }),
4946                ],
4947            },
4948            StateInstructions {
4949                percentage: 30.0,
4950                instruction_list: vec![
4951                    Instruction::Damage(DamageInstruction {
4952                        side_ref: SideReference::SideTwo,
4953                        damage_amount: 48,
4954                    }),
4955                    Instruction::Switch(SwitchInstruction {
4956                        side_ref: SideReference::SideTwo,
4957                        previous_index: PokemonIndex::P0,
4958                        next_index: PokemonIndex::P5,
4959                    }),
4960                ],
4961            },
4962        ];
4963
4964        assert_eq!(instructions, expected_instructions)
4965    }
4966
4967    #[test]
4968    fn test_basic_damaging_drag_that_knocks_out_defender() {
4969        let mut state: State = State::default();
4970        state.side_two.pokemon[PokemonIndex::P1].hp = 0;
4971        state.side_two.pokemon[PokemonIndex::P3].hp = 0;
4972        state.side_two.get_active().hp = 5;
4973        let mut choice = MOVES.get(&Choices::DRAGONTAIL).unwrap().to_owned();
4974
4975        let mut instructions = vec![];
4976        generate_instructions_from_move(
4977            &mut state,
4978            &mut choice,
4979            &MOVES.get(&Choices::TACKLE).unwrap(),
4980            SideReference::SideOne,
4981            StateInstructions::default(),
4982            &mut instructions,
4983            false,
4984        );
4985
4986        let expected_instructions = vec![
4987            StateInstructions {
4988                percentage: 10.0000019,
4989                instruction_list: vec![], // The move missed
4990            },
4991            StateInstructions {
4992                percentage: 90.0,
4993                instruction_list: vec![Instruction::Damage(DamageInstruction {
4994                    side_ref: SideReference::SideTwo,
4995                    damage_amount: 5,
4996                })],
4997            },
4998        ];
4999
5000        assert_eq!(instructions, expected_instructions)
5001    }
5002
5003    #[test]
5004    fn test_drag_versus_no_alive_reserved() {
5005        let mut state: State = State::default();
5006        state.side_two.pokemon[PokemonIndex::P1].hp = 0;
5007        state.side_two.pokemon[PokemonIndex::P2].hp = 0;
5008        state.side_two.pokemon[PokemonIndex::P3].hp = 0;
5009        state.side_two.pokemon[PokemonIndex::P4].hp = 0;
5010        state.side_two.pokemon[PokemonIndex::P5].hp = 0;
5011        let mut choice = MOVES.get(&Choices::WHIRLWIND).unwrap().to_owned();
5012
5013        let mut instructions = vec![];
5014        generate_instructions_from_move(
5015            &mut state,
5016            &mut choice,
5017            &MOVES.get(&Choices::TACKLE).unwrap(),
5018            SideReference::SideOne,
5019            StateInstructions::default(),
5020            &mut instructions,
5021            false,
5022        );
5023
5024        let expected_instructions = vec![StateInstructions {
5025            percentage: 100.0,
5026            instruction_list: vec![],
5027        }];
5028
5029        assert_eq!(instructions, expected_instructions)
5030    }
5031
5032    #[test]
5033    fn test_basic_drag_move_with_fainted_reserve_and_prior_instruction() {
5034        let mut state: State = State::default();
5035        state.side_two.pokemon[PokemonIndex::P1].hp = 0;
5036        state.side_two.pokemon[PokemonIndex::P3].hp = 0;
5037        let mut choice = MOVES.get(&Choices::WHIRLWIND).unwrap().to_owned();
5038
5039        let previous_instruction = StateInstructions {
5040            percentage: 50.0,
5041            instruction_list: vec![Instruction::Damage(DamageInstruction {
5042                side_ref: SideReference::SideOne,
5043                damage_amount: 5,
5044            })],
5045        };
5046
5047        let mut instructions = vec![];
5048        generate_instructions_from_move(
5049            &mut state,
5050            &mut choice,
5051            &MOVES.get(&Choices::TACKLE).unwrap(),
5052            SideReference::SideOne,
5053            previous_instruction,
5054            &mut instructions,
5055            false,
5056        );
5057
5058        let expected_instructions = vec![
5059            StateInstructions {
5060                percentage: 16.666668,
5061                instruction_list: vec![
5062                    Instruction::Damage(DamageInstruction {
5063                        side_ref: SideReference::SideOne,
5064                        damage_amount: 5,
5065                    }),
5066                    Instruction::Switch(SwitchInstruction {
5067                        side_ref: SideReference::SideTwo,
5068                        previous_index: PokemonIndex::P0,
5069                        next_index: PokemonIndex::P2,
5070                    }),
5071                ],
5072            },
5073            StateInstructions {
5074                percentage: 16.666668,
5075                instruction_list: vec![
5076                    Instruction::Damage(DamageInstruction {
5077                        side_ref: SideReference::SideOne,
5078                        damage_amount: 5,
5079                    }),
5080                    Instruction::Switch(SwitchInstruction {
5081                        side_ref: SideReference::SideTwo,
5082                        previous_index: PokemonIndex::P0,
5083                        next_index: PokemonIndex::P4,
5084                    }),
5085                ],
5086            },
5087            StateInstructions {
5088                percentage: 16.666668,
5089                instruction_list: vec![
5090                    Instruction::Damage(DamageInstruction {
5091                        side_ref: SideReference::SideOne,
5092                        damage_amount: 5,
5093                    }),
5094                    Instruction::Switch(SwitchInstruction {
5095                        side_ref: SideReference::SideTwo,
5096                        previous_index: PokemonIndex::P0,
5097                        next_index: PokemonIndex::P5,
5098                    }),
5099                ],
5100            },
5101        ];
5102
5103        assert_eq!(instructions, expected_instructions)
5104    }
5105
5106    #[test]
5107    #[cfg(feature = "gen9")]
5108    fn test_basic_status_move() {
5109        let mut state: State = State::default();
5110        let mut choice = MOVES.get(&Choices::GLARE).unwrap().to_owned();
5111
5112        let mut instructions = vec![];
5113        generate_instructions_from_move(
5114            &mut state,
5115            &mut choice,
5116            &MOVES.get(&Choices::TACKLE).unwrap(),
5117            SideReference::SideOne,
5118            StateInstructions::default(),
5119            &mut instructions,
5120            false,
5121        );
5122
5123        let expected_instructions = vec![StateInstructions {
5124            percentage: 100.0,
5125            instruction_list: vec![Instruction::ChangeStatus(ChangeStatusInstruction {
5126                side_ref: SideReference::SideTwo,
5127                pokemon_index: PokemonIndex::P0,
5128                old_status: PokemonStatus::NONE,
5129                new_status: PokemonStatus::PARALYZE,
5130            })],
5131        }];
5132
5133        assert_eq!(instructions, expected_instructions)
5134    }
5135
5136    #[test]
5137    #[cfg(feature = "gen9")]
5138    fn test_status_move_that_can_miss() {
5139        let mut state: State = State::default();
5140        let mut choice = MOVES.get(&Choices::THUNDERWAVE).unwrap().to_owned();
5141
5142        let mut instructions = vec![];
5143        generate_instructions_from_move(
5144            &mut state,
5145            &mut choice,
5146            &MOVES.get(&Choices::TACKLE).unwrap(),
5147            SideReference::SideOne,
5148            StateInstructions::default(),
5149            &mut instructions,
5150            false,
5151        );
5152
5153        let expected_instructions = vec![
5154            StateInstructions {
5155                percentage: 10.000002,
5156                instruction_list: vec![],
5157            },
5158            StateInstructions {
5159                percentage: 90.0,
5160                instruction_list: vec![Instruction::ChangeStatus(ChangeStatusInstruction {
5161                    side_ref: SideReference::SideTwo,
5162                    pokemon_index: PokemonIndex::P0,
5163                    old_status: PokemonStatus::NONE,
5164                    new_status: PokemonStatus::PARALYZE,
5165                })],
5166            },
5167        ];
5168
5169        assert_eq!(instructions, expected_instructions)
5170    }
5171
5172    #[test]
5173    fn test_status_move_that_can_miss_but_is_blocked_by_ability() {
5174        let mut state: State = State::default();
5175        state.side_two.get_active().ability = Abilities::LIMBER;
5176        let mut choice = MOVES.get(&Choices::THUNDERWAVE).unwrap().to_owned();
5177
5178        let mut instructions = vec![];
5179        generate_instructions_from_move(
5180            &mut state,
5181            &mut choice,
5182            &MOVES.get(&Choices::TACKLE).unwrap(),
5183            SideReference::SideOne,
5184            StateInstructions::default(),
5185            &mut instructions,
5186            false,
5187        );
5188
5189        let expected_instructions = vec![StateInstructions {
5190            percentage: 100.0,
5191            instruction_list: vec![],
5192        }];
5193
5194        assert_eq!(instructions, expected_instructions)
5195    }
5196
5197    #[test]
5198    fn test_flamebody_conditional_burn_on_contact() {
5199        let mut state: State = State::default();
5200        state.side_two.get_active().ability = Abilities::FLAMEBODY;
5201        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
5202
5203        let mut instructions = vec![];
5204        generate_instructions_from_move(
5205            &mut state,
5206            &mut choice,
5207            &MOVES.get(&Choices::TACKLE).unwrap(),
5208            SideReference::SideOne,
5209            StateInstructions::default(),
5210            &mut instructions,
5211            false,
5212        );
5213
5214        let expected_instructions = vec![
5215            StateInstructions {
5216                percentage: 70.0,
5217                instruction_list: vec![Instruction::Damage(DamageInstruction {
5218                    side_ref: SideReference::SideTwo,
5219                    damage_amount: 48,
5220                })],
5221            },
5222            StateInstructions {
5223                percentage: 30.0000019,
5224                instruction_list: vec![
5225                    Instruction::Damage(DamageInstruction {
5226                        side_ref: SideReference::SideTwo,
5227                        damage_amount: 48,
5228                    }),
5229                    Instruction::ChangeStatus(ChangeStatusInstruction {
5230                        side_ref: SideReference::SideOne,
5231                        pokemon_index: PokemonIndex::P0,
5232                        old_status: PokemonStatus::NONE,
5233                        new_status: PokemonStatus::BURN,
5234                    }),
5235                ],
5236            },
5237        ];
5238
5239        assert_eq!(instructions, expected_instructions)
5240    }
5241
5242    #[test]
5243    fn test_protectivepads_stops_flamebody() {
5244        let mut state: State = State::default();
5245        state.side_two.get_active().ability = Abilities::FLAMEBODY;
5246        state.side_one.get_active().item = Items::PROTECTIVEPADS;
5247        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
5248
5249        let mut instructions = vec![];
5250        generate_instructions_from_move(
5251            &mut state,
5252            &mut choice,
5253            &MOVES.get(&Choices::TACKLE).unwrap(),
5254            SideReference::SideOne,
5255            StateInstructions::default(),
5256            &mut instructions,
5257            false,
5258        );
5259
5260        let expected_instructions = vec![StateInstructions {
5261            percentage: 100.0,
5262            instruction_list: vec![Instruction::Damage(DamageInstruction {
5263                side_ref: SideReference::SideTwo,
5264                damage_amount: 48,
5265            })],
5266        }];
5267
5268        assert_eq!(instructions, expected_instructions)
5269    }
5270
5271    #[test]
5272    fn test_flamebody_versus_noncontact_move() {
5273        let mut state: State = State::default();
5274        state.side_two.get_active().ability = Abilities::FLAMEBODY;
5275        let mut choice = MOVES.get(&Choices::WATERGUN).unwrap().to_owned();
5276
5277        let mut instructions = vec![];
5278        generate_instructions_from_move(
5279            &mut state,
5280            &mut choice,
5281            &MOVES.get(&Choices::TACKLE).unwrap(),
5282            SideReference::SideOne,
5283            StateInstructions::default(),
5284            &mut instructions,
5285            false,
5286        );
5287
5288        let expected_instructions = vec![StateInstructions {
5289            percentage: 100.0,
5290            instruction_list: vec![Instruction::Damage(DamageInstruction {
5291                side_ref: SideReference::SideTwo,
5292                damage_amount: 32,
5293            })],
5294        }];
5295
5296        assert_eq!(instructions, expected_instructions)
5297    }
5298
5299    #[test]
5300    fn test_flamebody_versus_fire_type() {
5301        let mut state: State = State::default();
5302        state.side_one.get_active().types.0 = PokemonType::FIRE;
5303        state.side_two.get_active().ability = Abilities::FLAMEBODY;
5304        let mut choice = MOVES.get(&Choices::WATERGUN).unwrap().to_owned();
5305
5306        let mut instructions = vec![];
5307        generate_instructions_from_move(
5308            &mut state,
5309            &mut choice,
5310            &MOVES.get(&Choices::TACKLE).unwrap(),
5311            SideReference::SideOne,
5312            StateInstructions::default(),
5313            &mut instructions,
5314            false,
5315        );
5316
5317        let expected_instructions = vec![StateInstructions {
5318            percentage: 100.0,
5319            instruction_list: vec![Instruction::Damage(DamageInstruction {
5320                side_ref: SideReference::SideTwo,
5321                damage_amount: 32,
5322            })],
5323        }];
5324
5325        assert_eq!(instructions, expected_instructions)
5326    }
5327
5328    #[test]
5329    fn test_move_with_multiple_secondaries() {
5330        let mut state: State = State::default();
5331        let mut choice = MOVES.get(&Choices::FIREFANG).unwrap().to_owned();
5332
5333        let mut instructions = vec![];
5334        generate_instructions_from_move(
5335            &mut state,
5336            &mut choice,
5337            &MOVES.get(&Choices::TACKLE).unwrap(),
5338            SideReference::SideOne,
5339            StateInstructions::default(),
5340            &mut instructions,
5341            false,
5342        );
5343
5344        let expected_instructions = vec![
5345            StateInstructions {
5346                percentage: 5.00000095,
5347                instruction_list: vec![],
5348            },
5349            StateInstructions {
5350                percentage: 76.9499969,
5351                instruction_list: vec![Instruction::Damage(DamageInstruction {
5352                    side_ref: SideReference::SideTwo,
5353                    damage_amount: 51,
5354                })],
5355            },
5356            StateInstructions {
5357                percentage: 8.55000019,
5358                instruction_list: vec![
5359                    Instruction::Damage(DamageInstruction {
5360                        side_ref: SideReference::SideTwo,
5361                        damage_amount: 51,
5362                    }),
5363                    Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
5364                        side_ref: SideReference::SideTwo,
5365                        volatile_status: PokemonVolatileStatus::FLINCH,
5366                    }),
5367                ],
5368            },
5369            StateInstructions {
5370                percentage: 8.55000019,
5371                instruction_list: vec![
5372                    Instruction::Damage(DamageInstruction {
5373                        side_ref: SideReference::SideTwo,
5374                        damage_amount: 51,
5375                    }),
5376                    Instruction::ChangeStatus(ChangeStatusInstruction {
5377                        side_ref: SideReference::SideTwo,
5378                        pokemon_index: PokemonIndex::P0,
5379                        old_status: PokemonStatus::NONE,
5380                        new_status: PokemonStatus::BURN,
5381                    }),
5382                ],
5383            },
5384            StateInstructions {
5385                percentage: 0.949999988,
5386                instruction_list: vec![
5387                    Instruction::Damage(DamageInstruction {
5388                        side_ref: SideReference::SideTwo,
5389                        damage_amount: 51,
5390                    }),
5391                    Instruction::ChangeStatus(ChangeStatusInstruction {
5392                        side_ref: SideReference::SideTwo,
5393                        pokemon_index: PokemonIndex::P0,
5394                        old_status: PokemonStatus::NONE,
5395                        new_status: PokemonStatus::BURN,
5396                    }),
5397                    Instruction::ApplyVolatileStatus(ApplyVolatileStatusInstruction {
5398                        side_ref: SideReference::SideTwo,
5399                        volatile_status: PokemonVolatileStatus::FLINCH,
5400                    }),
5401                ],
5402            },
5403        ];
5404
5405        assert_eq!(instructions, expected_instructions)
5406    }
5407
5408    #[test]
5409    fn test_flamebody() {
5410        let mut state: State = State::default();
5411        state.side_two.get_active().ability = Abilities::FLAMEBODY;
5412        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
5413
5414        let mut instructions = vec![];
5415        generate_instructions_from_move(
5416            &mut state,
5417            &mut choice,
5418            &MOVES.get(&Choices::TACKLE).unwrap(),
5419            SideReference::SideOne,
5420            StateInstructions::default(),
5421            &mut instructions,
5422            false,
5423        );
5424
5425        let expected_instructions = vec![
5426            StateInstructions {
5427                percentage: 70.0,
5428                instruction_list: vec![Instruction::Damage(DamageInstruction {
5429                    side_ref: SideReference::SideTwo,
5430                    damage_amount: 48,
5431                })],
5432            },
5433            StateInstructions {
5434                percentage: 30.000002,
5435                instruction_list: vec![
5436                    Instruction::Damage(DamageInstruction {
5437                        side_ref: SideReference::SideTwo,
5438                        damage_amount: 48,
5439                    }),
5440                    Instruction::ChangeStatus(ChangeStatusInstruction {
5441                        side_ref: SideReference::SideOne,
5442                        pokemon_index: PokemonIndex::P0,
5443                        old_status: PokemonStatus::NONE,
5444                        new_status: PokemonStatus::BURN,
5445                    }),
5446                ],
5447            },
5448        ];
5449
5450        assert_eq!(instructions, expected_instructions)
5451    }
5452
5453    #[test]
5454    fn test_flamebody_creating_a_move_with_multiple_secondaries() {
5455        let mut state: State = State::default();
5456        state.side_two.get_active().ability = Abilities::FLAMEBODY;
5457        let mut choice = MOVES.get(&Choices::FIREPUNCH).unwrap().to_owned();
5458
5459        let mut instructions = vec![];
5460        generate_instructions_from_move(
5461            &mut state,
5462            &mut choice,
5463            &MOVES.get(&Choices::TACKLE).unwrap(),
5464            SideReference::SideOne,
5465            StateInstructions::default(),
5466            &mut instructions,
5467            false,
5468        );
5469
5470        let expected_instructions = vec![
5471            StateInstructions {
5472                percentage: 63.0,
5473                instruction_list: vec![Instruction::Damage(DamageInstruction {
5474                    side_ref: SideReference::SideTwo,
5475                    damage_amount: 60,
5476                })],
5477            },
5478            StateInstructions {
5479                percentage: 27.0000019,
5480                instruction_list: vec![
5481                    Instruction::Damage(DamageInstruction {
5482                        side_ref: SideReference::SideTwo,
5483                        damage_amount: 60,
5484                    }),
5485                    Instruction::ChangeStatus(ChangeStatusInstruction {
5486                        side_ref: SideReference::SideOne,
5487                        pokemon_index: PokemonIndex::P0,
5488                        old_status: PokemonStatus::NONE,
5489                        new_status: PokemonStatus::BURN,
5490                    }),
5491                ],
5492            },
5493            StateInstructions {
5494                percentage: 7.0,
5495                instruction_list: vec![
5496                    Instruction::Damage(DamageInstruction {
5497                        side_ref: SideReference::SideTwo,
5498                        damage_amount: 60,
5499                    }),
5500                    Instruction::ChangeStatus(ChangeStatusInstruction {
5501                        side_ref: SideReference::SideTwo,
5502                        pokemon_index: PokemonIndex::P0,
5503                        old_status: PokemonStatus::NONE,
5504                        new_status: PokemonStatus::BURN,
5505                    }),
5506                ],
5507            },
5508            StateInstructions {
5509                percentage: 3.0,
5510                instruction_list: vec![
5511                    Instruction::Damage(DamageInstruction {
5512                        side_ref: SideReference::SideTwo,
5513                        damage_amount: 60,
5514                    }),
5515                    Instruction::ChangeStatus(ChangeStatusInstruction {
5516                        side_ref: SideReference::SideTwo,
5517                        pokemon_index: PokemonIndex::P0,
5518                        old_status: PokemonStatus::NONE,
5519                        new_status: PokemonStatus::BURN,
5520                    }),
5521                    Instruction::ChangeStatus(ChangeStatusInstruction {
5522                        side_ref: SideReference::SideOne,
5523                        pokemon_index: PokemonIndex::P0,
5524                        old_status: PokemonStatus::NONE,
5525                        new_status: PokemonStatus::BURN,
5526                    }),
5527                ],
5528            },
5529        ];
5530        assert_eq!(instructions, expected_instructions)
5531    }
5532
5533    #[test]
5534    fn test_substitute_does_not_block_rest() {
5535        let mut state: State = State::default();
5536        state
5537            .side_one
5538            .volatile_statuses
5539            .insert(PokemonVolatileStatus::SUBSTITUTE);
5540        state.side_one.get_active().hp = state.side_one.get_active().maxhp - 1;
5541        let mut choice = MOVES.get(&Choices::REST).unwrap().to_owned();
5542
5543        let mut instructions = vec![];
5544        generate_instructions_from_move(
5545            &mut state,
5546            &mut choice,
5547            &MOVES.get(&Choices::TACKLE).unwrap(),
5548            SideReference::SideOne,
5549            StateInstructions::default(),
5550            &mut instructions,
5551            false,
5552        );
5553
5554        let expected_instructions = vec![StateInstructions {
5555            percentage: 100.0,
5556            instruction_list: vec![
5557                Instruction::ChangeStatus(ChangeStatusInstruction {
5558                    side_ref: SideReference::SideOne,
5559                    pokemon_index: PokemonIndex::P0,
5560                    old_status: PokemonStatus::NONE,
5561                    new_status: PokemonStatus::SLEEP,
5562                }),
5563                Instruction::SetRestTurns(SetSleepTurnsInstruction {
5564                    side_ref: SideReference::SideOne,
5565                    pokemon_index: PokemonIndex::P0,
5566                    new_turns: 3,
5567                    previous_turns: 0,
5568                }),
5569                Instruction::Heal(HealInstruction {
5570                    side_ref: SideReference::SideOne,
5571                    heal_amount: 1,
5572                }),
5573            ],
5574        }];
5575
5576        assert_eq!(instructions, expected_instructions)
5577    }
5578
5579    #[test]
5580    fn test_basic_heal_move() {
5581        let mut state: State = State::default();
5582        state.side_one.get_active().hp = 1;
5583        let mut choice = MOVES.get(&Choices::RECOVER).unwrap().to_owned();
5584
5585        let mut instructions = vec![];
5586        generate_instructions_from_move(
5587            &mut state,
5588            &mut choice,
5589            &MOVES.get(&Choices::TACKLE).unwrap(),
5590            SideReference::SideOne,
5591            StateInstructions::default(),
5592            &mut instructions,
5593            false,
5594        );
5595
5596        let expected_instructions = vec![StateInstructions {
5597            percentage: 100.0,
5598            instruction_list: vec![Instruction::Heal(HealInstruction {
5599                side_ref: SideReference::SideOne,
5600                heal_amount: 50,
5601            })],
5602        }];
5603
5604        assert_eq!(instructions, expected_instructions)
5605    }
5606
5607    #[test]
5608    fn test_heal_move_generates_no_instruction_at_maxhp() {
5609        let mut state: State = State::default();
5610        let mut choice = MOVES.get(&Choices::RECOVER).unwrap().to_owned();
5611
5612        let mut instructions = vec![];
5613        generate_instructions_from_move(
5614            &mut state,
5615            &mut choice,
5616            &MOVES.get(&Choices::TACKLE).unwrap(),
5617            SideReference::SideOne,
5618            StateInstructions::default(),
5619            &mut instructions,
5620            false,
5621        );
5622
5623        let expected_instructions = vec![StateInstructions {
5624            percentage: 100.0,
5625            instruction_list: vec![],
5626        }];
5627
5628        assert_eq!(instructions, expected_instructions)
5629    }
5630
5631    #[test]
5632    fn test_basic_negative_heal_move() {
5633        let mut state: State = State::default();
5634        let mut choice = MOVES.get(&Choices::EXPLOSION).unwrap().to_owned();
5635
5636        let mut instructions = vec![];
5637        generate_instructions_from_move(
5638            &mut state,
5639            &mut choice,
5640            &MOVES.get(&Choices::TACKLE).unwrap(),
5641            SideReference::SideOne,
5642            StateInstructions::default(),
5643            &mut instructions,
5644            false,
5645        );
5646
5647        let expected_instructions = vec![StateInstructions {
5648            percentage: 100.0,
5649            instruction_list: vec![
5650                Instruction::Damage(DamageInstruction {
5651                    side_ref: SideReference::SideOne,
5652                    damage_amount: 100,
5653                }),
5654                Instruction::Damage(DamageInstruction {
5655                    side_ref: SideReference::SideTwo,
5656                    damage_amount: 100,
5657                }),
5658            ],
5659        }];
5660
5661        assert_eq!(instructions, expected_instructions)
5662    }
5663
5664    #[test]
5665    fn test_negative_heal_move_does_not_overkill() {
5666        let mut state: State = State::default();
5667        state.side_one.get_active().hp = 1;
5668        let mut choice = MOVES.get(&Choices::EXPLOSION).unwrap().to_owned();
5669
5670        let mut instructions = vec![];
5671        generate_instructions_from_move(
5672            &mut state,
5673            &mut choice,
5674            &MOVES.get(&Choices::TACKLE).unwrap(),
5675            SideReference::SideOne,
5676            StateInstructions::default(),
5677            &mut instructions,
5678            false,
5679        );
5680
5681        let expected_instructions = vec![StateInstructions {
5682            percentage: 100.0,
5683            instruction_list: vec![
5684                Instruction::Damage(DamageInstruction {
5685                    side_ref: SideReference::SideOne,
5686                    damage_amount: 1,
5687                }),
5688                Instruction::Damage(DamageInstruction {
5689                    side_ref: SideReference::SideTwo,
5690                    damage_amount: 100,
5691                }),
5692            ],
5693        }];
5694
5695        assert_eq!(instructions, expected_instructions)
5696    }
5697
5698    #[test]
5699    fn test_heal_move_does_not_overheal() {
5700        let mut state: State = State::default();
5701        state.side_one.get_active().hp = 55;
5702        let mut choice = MOVES.get(&Choices::RECOVER).unwrap().to_owned();
5703
5704        let mut instructions = vec![];
5705        generate_instructions_from_move(
5706            &mut state,
5707            &mut choice,
5708            &MOVES.get(&Choices::TACKLE).unwrap(),
5709            SideReference::SideOne,
5710            StateInstructions::default(),
5711            &mut instructions,
5712            false,
5713        );
5714
5715        let expected_instructions = vec![StateInstructions {
5716            percentage: 100.0,
5717            instruction_list: vec![Instruction::Heal(HealInstruction {
5718                side_ref: SideReference::SideOne,
5719                heal_amount: 45,
5720            })],
5721        }];
5722
5723        assert_eq!(instructions, expected_instructions)
5724    }
5725
5726    #[test]
5727    fn test_basic_boosting_move() {
5728        let mut state: State = State::default();
5729        let mut choice = MOVES.get(&Choices::SWORDSDANCE).unwrap().to_owned();
5730
5731        let mut instructions = vec![];
5732        generate_instructions_from_move(
5733            &mut state,
5734            &mut choice,
5735            &MOVES.get(&Choices::TACKLE).unwrap(),
5736            SideReference::SideOne,
5737            StateInstructions::default(),
5738            &mut instructions,
5739            false,
5740        );
5741
5742        let expected_instructions = vec![StateInstructions {
5743            percentage: 100.0,
5744            instruction_list: vec![Instruction::Boost(BoostInstruction {
5745                side_ref: SideReference::SideOne,
5746                stat: PokemonBoostableStat::Attack,
5747                amount: 2,
5748            })],
5749        }];
5750
5751        assert_eq!(instructions, expected_instructions)
5752    }
5753
5754    #[test]
5755    fn test_does_not_overboost() {
5756        let mut state: State = State::default();
5757        state.side_one.attack_boost = 5;
5758        let mut choice = MOVES.get(&Choices::SWORDSDANCE).unwrap().to_owned();
5759
5760        let mut instructions = vec![];
5761        generate_instructions_from_move(
5762            &mut state,
5763            &mut choice,
5764            &MOVES.get(&Choices::TACKLE).unwrap(),
5765            SideReference::SideOne,
5766            StateInstructions::default(),
5767            &mut instructions,
5768            false,
5769        );
5770
5771        let expected_instructions = vec![StateInstructions {
5772            percentage: 100.0,
5773            instruction_list: vec![Instruction::Boost(BoostInstruction {
5774                side_ref: SideReference::SideOne,
5775                stat: PokemonBoostableStat::Attack,
5776                amount: 1,
5777            })],
5778        }];
5779
5780        assert_eq!(instructions, expected_instructions)
5781    }
5782
5783    #[test]
5784    fn test_no_instruction_when_boosting_at_max() {
5785        let mut state: State = State::default();
5786        state.side_one.attack_boost = 6;
5787        let mut choice = MOVES.get(&Choices::SWORDSDANCE).unwrap().to_owned();
5788
5789        let mut instructions = vec![];
5790        generate_instructions_from_move(
5791            &mut state,
5792            &mut choice,
5793            &MOVES.get(&Choices::TACKLE).unwrap(),
5794            SideReference::SideOne,
5795            StateInstructions::default(),
5796            &mut instructions,
5797            false,
5798        );
5799
5800        let expected_instructions = vec![StateInstructions {
5801            percentage: 100.0,
5802            instruction_list: vec![],
5803        }];
5804
5805        assert_eq!(instructions, expected_instructions)
5806    }
5807
5808    #[test]
5809    fn test_boost_lowering_that_can_miss() {
5810        let mut state: State = State::default();
5811        let mut choice = MOVES.get(&Choices::KINESIS).unwrap().to_owned();
5812
5813        let mut instructions = vec![];
5814        generate_instructions_from_move(
5815            &mut state,
5816            &mut choice,
5817            &MOVES.get(&Choices::TACKLE).unwrap(),
5818            SideReference::SideOne,
5819            StateInstructions::default(),
5820            &mut instructions,
5821            false,
5822        );
5823
5824        let expected_instructions = vec![
5825            StateInstructions {
5826                percentage: 19.999998,
5827                instruction_list: vec![],
5828            },
5829            StateInstructions {
5830                percentage: 80.0,
5831                instruction_list: vec![Instruction::Boost(BoostInstruction {
5832                    side_ref: SideReference::SideTwo,
5833                    stat: PokemonBoostableStat::Accuracy,
5834                    amount: -1,
5835                })],
5836            },
5837        ];
5838
5839        assert_eq!(instructions, expected_instructions)
5840    }
5841
5842    #[test]
5843    fn test_basic_boost_lowering() {
5844        let mut state: State = State::default();
5845        let mut choice = MOVES.get(&Choices::CHARM).unwrap().to_owned();
5846
5847        let mut instructions = vec![];
5848        generate_instructions_from_move(
5849            &mut state,
5850            &mut choice,
5851            &MOVES.get(&Choices::TACKLE).unwrap(),
5852            SideReference::SideOne,
5853            StateInstructions::default(),
5854            &mut instructions,
5855            false,
5856        );
5857
5858        let expected_instructions = vec![StateInstructions {
5859            percentage: 100.0,
5860            instruction_list: vec![Instruction::Boost(BoostInstruction {
5861                side_ref: SideReference::SideTwo,
5862                stat: PokemonBoostableStat::Attack,
5863                amount: -2,
5864            })],
5865        }];
5866
5867        assert_eq!(instructions, expected_instructions)
5868    }
5869
5870    #[test]
5871    fn test_cannot_boost_lower_than_negative_6() {
5872        let mut state: State = State::default();
5873        state.side_two.attack_boost = -5;
5874        let mut choice = MOVES.get(&Choices::CHARM).unwrap().to_owned();
5875
5876        let mut instructions = vec![];
5877        generate_instructions_from_move(
5878            &mut state,
5879            &mut choice,
5880            &MOVES.get(&Choices::TACKLE).unwrap(),
5881            SideReference::SideOne,
5882            StateInstructions::default(),
5883            &mut instructions,
5884            false,
5885        );
5886
5887        let expected_instructions = vec![StateInstructions {
5888            percentage: 100.0,
5889            instruction_list: vec![Instruction::Boost(BoostInstruction {
5890                side_ref: SideReference::SideTwo,
5891                stat: PokemonBoostableStat::Attack,
5892                amount: -1,
5893            })],
5894        }];
5895
5896        assert_eq!(instructions, expected_instructions)
5897    }
5898
5899    #[test]
5900    fn test_no_boost_when_already_at_minimum() {
5901        let mut state: State = State::default();
5902        state.side_two.attack_boost = -6;
5903        let mut choice = MOVES.get(&Choices::CHARM).unwrap().to_owned();
5904
5905        let mut instructions = vec![];
5906        generate_instructions_from_move(
5907            &mut state,
5908            &mut choice,
5909            &MOVES.get(&Choices::TACKLE).unwrap(),
5910            SideReference::SideOne,
5911            StateInstructions::default(),
5912            &mut instructions,
5913            false,
5914        );
5915
5916        let expected_instructions = vec![StateInstructions {
5917            percentage: 100.0,
5918            instruction_list: vec![],
5919        }];
5920
5921        assert_eq!(instructions, expected_instructions)
5922    }
5923
5924    #[test]
5925    fn test_clearbody_blocks_stat_lowering() {
5926        let mut state: State = State::default();
5927        state.side_two.get_active().ability = Abilities::CLEARBODY;
5928        let mut choice = MOVES.get(&Choices::CHARM).unwrap().to_owned();
5929
5930        let mut instructions = vec![];
5931        generate_instructions_from_move(
5932            &mut state,
5933            &mut choice,
5934            &MOVES.get(&Choices::TACKLE).unwrap(),
5935            SideReference::SideOne,
5936            StateInstructions::default(),
5937            &mut instructions,
5938            false,
5939        );
5940
5941        let expected_instructions = vec![StateInstructions {
5942            percentage: 100.0,
5943            instruction_list: vec![],
5944        }];
5945
5946        assert_eq!(instructions, expected_instructions)
5947    }
5948
5949    #[test]
5950    fn test_clearbody_does_not_block_self_stat_lowering() {
5951        let mut state: State = State::default();
5952        state.side_one.get_active().ability = Abilities::CLEARBODY;
5953        let mut choice = MOVES.get(&Choices::SHELLSMASH).unwrap().to_owned();
5954
5955        let mut instructions = vec![];
5956        generate_instructions_from_move(
5957            &mut state,
5958            &mut choice,
5959            &MOVES.get(&Choices::TACKLE).unwrap(),
5960            SideReference::SideOne,
5961            StateInstructions::default(),
5962            &mut instructions,
5963            false,
5964        );
5965
5966        let expected_instructions = vec![StateInstructions {
5967            percentage: 100.0,
5968            instruction_list: vec![
5969                Instruction::Boost(BoostInstruction {
5970                    side_ref: SideReference::SideOne,
5971                    stat: PokemonBoostableStat::Attack,
5972                    amount: 2,
5973                }),
5974                Instruction::Boost(BoostInstruction {
5975                    side_ref: SideReference::SideOne,
5976                    stat: PokemonBoostableStat::Defense,
5977                    amount: -1,
5978                }),
5979                Instruction::Boost(BoostInstruction {
5980                    side_ref: SideReference::SideOne,
5981                    stat: PokemonBoostableStat::SpecialAttack,
5982                    amount: 2,
5983                }),
5984                Instruction::Boost(BoostInstruction {
5985                    side_ref: SideReference::SideOne,
5986                    stat: PokemonBoostableStat::SpecialDefense,
5987                    amount: -1,
5988                }),
5989                Instruction::Boost(BoostInstruction {
5990                    side_ref: SideReference::SideOne,
5991                    stat: PokemonBoostableStat::Speed,
5992                    amount: 2,
5993                }),
5994            ],
5995        }];
5996
5997        assert_eq!(instructions, expected_instructions)
5998    }
5999
6000    #[test]
6001    fn test_defog_does_not_change_terrain_if_terrain_is_none() {
6002        let mut state: State = State::default();
6003
6004        let mut choice = MOVES.get(&Choices::DEFOG).unwrap().to_owned();
6005
6006        let mut instructions = vec![];
6007        generate_instructions_from_move(
6008            &mut state,
6009            &mut choice,
6010            &MOVES.get(&Choices::TACKLE).unwrap(),
6011            SideReference::SideOne,
6012            StateInstructions::default(),
6013            &mut instructions,
6014            false,
6015        );
6016
6017        let expected_instructions = vec![StateInstructions {
6018            percentage: 100.0,
6019            instruction_list: vec![],
6020        }];
6021
6022        assert_eq!(instructions, expected_instructions)
6023    }
6024
6025    #[test]
6026    fn test_defog_clears_terrain() {
6027        let mut state: State = State::default();
6028        state.terrain.terrain_type = Terrain::ELECTRICTERRAIN;
6029        state.terrain.turns_remaining = 1;
6030
6031        let mut choice = MOVES.get(&Choices::DEFOG).unwrap().to_owned();
6032
6033        let mut instructions = vec![];
6034        generate_instructions_from_move(
6035            &mut state,
6036            &mut choice,
6037            &MOVES.get(&Choices::TACKLE).unwrap(),
6038            SideReference::SideOne,
6039            StateInstructions::default(),
6040            &mut instructions,
6041            false,
6042        );
6043
6044        let expected_instructions = vec![StateInstructions {
6045            percentage: 100.0,
6046            instruction_list: vec![Instruction::ChangeTerrain(ChangeTerrain {
6047                new_terrain: Terrain::NONE,
6048                new_terrain_turns_remaining: 0,
6049                previous_terrain: Terrain::ELECTRICTERRAIN,
6050                previous_terrain_turns_remaining: 1,
6051            })],
6052        }];
6053
6054        assert_eq!(instructions, expected_instructions)
6055    }
6056
6057    #[test]
6058    fn test_defog_clears_terrain_and_side_conditions() {
6059        let mut state: State = State::default();
6060        state.terrain.terrain_type = Terrain::ELECTRICTERRAIN;
6061        state.terrain.turns_remaining = 1;
6062        state.side_one.side_conditions.reflect = 1;
6063        state.side_two.side_conditions.reflect = 1;
6064
6065        let mut choice = MOVES.get(&Choices::DEFOG).unwrap().to_owned();
6066
6067        let mut instructions = vec![];
6068        generate_instructions_from_move(
6069            &mut state,
6070            &mut choice,
6071            &MOVES.get(&Choices::TACKLE).unwrap(),
6072            SideReference::SideOne,
6073            StateInstructions::default(),
6074            &mut instructions,
6075            false,
6076        );
6077
6078        let expected_instructions = vec![StateInstructions {
6079            percentage: 100.0,
6080            instruction_list: vec![
6081                Instruction::ChangeTerrain(ChangeTerrain {
6082                    new_terrain: Terrain::NONE,
6083                    new_terrain_turns_remaining: 0,
6084                    previous_terrain: Terrain::ELECTRICTERRAIN,
6085                    previous_terrain_turns_remaining: 1,
6086                }),
6087                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6088                    side_ref: SideReference::SideOne,
6089                    side_condition: PokemonSideCondition::Reflect,
6090                    amount: -1,
6091                }),
6092                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6093                    side_ref: SideReference::SideTwo,
6094                    side_condition: PokemonSideCondition::Reflect,
6095                    amount: -1,
6096                }),
6097            ],
6098        }];
6099        assert_eq!(instructions, expected_instructions)
6100    }
6101
6102    #[test]
6103    fn test_tidyup_clears_side_conditions_and_substitutes() {
6104        let mut state: State = State::default();
6105        state.terrain.terrain_type = Terrain::ELECTRICTERRAIN;
6106        state
6107            .side_one
6108            .volatile_statuses
6109            .insert(PokemonVolatileStatus::SUBSTITUTE);
6110        state
6111            .side_two
6112            .volatile_statuses
6113            .insert(PokemonVolatileStatus::SUBSTITUTE);
6114        state.side_one.substitute_health = 10;
6115        state.side_two.substitute_health = 25;
6116        state.terrain.turns_remaining = 1;
6117        state.side_one.side_conditions.spikes = 2;
6118        state.side_two.side_conditions.stealth_rock = 1;
6119
6120        let mut choice = MOVES.get(&Choices::TIDYUP).unwrap().to_owned();
6121
6122        let mut instructions = vec![];
6123        generate_instructions_from_move(
6124            &mut state,
6125            &mut choice,
6126            &MOVES.get(&Choices::TACKLE).unwrap(),
6127            SideReference::SideOne,
6128            StateInstructions::default(),
6129            &mut instructions,
6130            false,
6131        );
6132
6133        let expected_instructions = vec![StateInstructions {
6134            percentage: 100.0,
6135            instruction_list: vec![
6136                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6137                    side_ref: SideReference::SideOne,
6138                    side_condition: PokemonSideCondition::Spikes,
6139                    amount: -2,
6140                }),
6141                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6142                    side_ref: SideReference::SideTwo,
6143                    side_condition: PokemonSideCondition::Stealthrock,
6144                    amount: -1,
6145                }),
6146                Instruction::ChangeSubstituteHealth(ChangeSubsituteHealthInstruction {
6147                    side_ref: SideReference::SideOne,
6148                    health_change: -10,
6149                }),
6150                Instruction::RemoveVolatileStatus(RemoveVolatileStatusInstruction {
6151                    side_ref: SideReference::SideOne,
6152                    volatile_status: PokemonVolatileStatus::SUBSTITUTE,
6153                }),
6154                Instruction::ChangeSubstituteHealth(ChangeSubsituteHealthInstruction {
6155                    side_ref: SideReference::SideTwo,
6156                    health_change: -25,
6157                }),
6158                Instruction::RemoveVolatileStatus(RemoveVolatileStatusInstruction {
6159                    side_ref: SideReference::SideTwo,
6160                    volatile_status: PokemonVolatileStatus::SUBSTITUTE,
6161                }),
6162                Instruction::Boost(BoostInstruction {
6163                    side_ref: SideReference::SideOne,
6164                    stat: PokemonBoostableStat::Attack,
6165                    amount: 1,
6166                }),
6167                Instruction::Boost(BoostInstruction {
6168                    side_ref: SideReference::SideOne,
6169                    stat: PokemonBoostableStat::Speed,
6170                    amount: 1,
6171                }),
6172            ],
6173        }];
6174        assert_eq!(instructions, expected_instructions)
6175    }
6176
6177    #[test]
6178    #[cfg(any(feature = "gen8", feature = "gen9"))]
6179    fn test_rapidspin_clears_hazards() {
6180        let mut state: State = State::default();
6181        state.side_one.side_conditions.stealth_rock = 1;
6182
6183        let mut choice = MOVES.get(&Choices::RAPIDSPIN).unwrap().to_owned();
6184
6185        let mut instructions = vec![];
6186        generate_instructions_from_move(
6187            &mut state,
6188            &mut choice,
6189            &MOVES.get(&Choices::TACKLE).unwrap(),
6190            SideReference::SideOne,
6191            StateInstructions::default(),
6192            &mut instructions,
6193            false,
6194        );
6195
6196        let expected_instructions = vec![StateInstructions {
6197            percentage: 100.0,
6198            instruction_list: vec![
6199                Instruction::Damage(DamageInstruction {
6200                    side_ref: SideReference::SideTwo,
6201                    damage_amount: 61,
6202                }),
6203                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6204                    side_ref: SideReference::SideOne,
6205                    side_condition: PokemonSideCondition::Stealthrock,
6206                    amount: -1,
6207                }),
6208                Instruction::Boost(BoostInstruction {
6209                    side_ref: SideReference::SideOne,
6210                    amount: 1,
6211                    stat: PokemonBoostableStat::Speed,
6212                }),
6213            ],
6214        }];
6215
6216        assert_eq!(instructions, expected_instructions)
6217    }
6218
6219    #[test]
6220    fn test_missing_rapidspin_does_not_clear_hazards() {
6221        let mut state: State = State::default();
6222        state.side_two.get_active().types = (PokemonType::GHOST, PokemonType::NORMAL);
6223        state.side_one.side_conditions.stealth_rock = 1;
6224
6225        let mut choice = MOVES.get(&Choices::RAPIDSPIN).unwrap().to_owned();
6226
6227        let mut instructions = vec![];
6228        generate_instructions_from_move(
6229            &mut state,
6230            &mut choice,
6231            &MOVES.get(&Choices::TACKLE).unwrap(),
6232            SideReference::SideOne,
6233            StateInstructions::default(),
6234            &mut instructions,
6235            false,
6236        );
6237
6238        let expected_instructions = vec![StateInstructions {
6239            percentage: 100.0,
6240            instruction_list: vec![],
6241        }];
6242        assert_eq!(instructions, expected_instructions)
6243    }
6244
6245    #[test]
6246    fn test_acid_into_steel_type() {
6247        let mut state: State = State::default();
6248        state.side_two.get_active().types = (PokemonType::STEEL, PokemonType::NORMAL);
6249
6250        let mut choice = MOVES.get(&Choices::ACID).unwrap().to_owned();
6251
6252        let mut instructions = vec![];
6253        generate_instructions_from_move(
6254            &mut state,
6255            &mut choice,
6256            &MOVES.get(&Choices::TACKLE).unwrap(),
6257            SideReference::SideOne,
6258            StateInstructions::default(),
6259            &mut instructions,
6260            false,
6261        );
6262
6263        let expected_instructions = vec![StateInstructions {
6264            percentage: 100.0,
6265            instruction_list: vec![],
6266        }];
6267        assert_eq!(instructions, expected_instructions)
6268    }
6269
6270    #[test]
6271    #[cfg(any(feature = "gen8", feature = "gen9"))]
6272    fn test_rapidspin_clears_multiple_hazards() {
6273        let mut state: State = State::default();
6274        state.side_one.side_conditions.stealth_rock = 1;
6275        state.side_one.side_conditions.toxic_spikes = 2;
6276        state.side_one.side_conditions.spikes = 3;
6277        state.side_one.side_conditions.sticky_web = 1;
6278
6279        let mut choice = MOVES.get(&Choices::RAPIDSPIN).unwrap().to_owned();
6280
6281        let mut instructions = vec![];
6282        generate_instructions_from_move(
6283            &mut state,
6284            &mut choice,
6285            &MOVES.get(&Choices::TACKLE).unwrap(),
6286            SideReference::SideOne,
6287            StateInstructions::default(),
6288            &mut instructions,
6289            false,
6290        );
6291
6292        let expected_instructions = vec![StateInstructions {
6293            percentage: 100.0,
6294            instruction_list: vec![
6295                Instruction::Damage(DamageInstruction {
6296                    side_ref: SideReference::SideTwo,
6297                    damage_amount: 61,
6298                }),
6299                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6300                    side_ref: SideReference::SideOne,
6301                    side_condition: PokemonSideCondition::Stealthrock,
6302                    amount: -1,
6303                }),
6304                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6305                    side_ref: SideReference::SideOne,
6306                    side_condition: PokemonSideCondition::Spikes,
6307                    amount: -3,
6308                }),
6309                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6310                    side_ref: SideReference::SideOne,
6311                    side_condition: PokemonSideCondition::ToxicSpikes,
6312                    amount: -2,
6313                }),
6314                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6315                    side_ref: SideReference::SideOne,
6316                    side_condition: PokemonSideCondition::StickyWeb,
6317                    amount: -1,
6318                }),
6319                Instruction::Boost(BoostInstruction {
6320                    side_ref: SideReference::SideOne,
6321                    amount: 1,
6322                    stat: PokemonBoostableStat::Speed,
6323                }),
6324            ],
6325        }];
6326
6327        assert_eq!(instructions, expected_instructions)
6328    }
6329
6330    #[test]
6331    #[cfg(any(feature = "gen8", feature = "gen9"))]
6332    fn test_rapidspin_does_not_clear_opponent_hazards() {
6333        let mut state: State = State::default();
6334        state.side_two.side_conditions.stealth_rock = 1;
6335        state.side_two.side_conditions.toxic_spikes = 2;
6336        state.side_two.side_conditions.spikes = 3;
6337        state.side_two.side_conditions.sticky_web = 1;
6338
6339        let mut choice = MOVES.get(&Choices::RAPIDSPIN).unwrap().to_owned();
6340
6341        let mut instructions = vec![];
6342        generate_instructions_from_move(
6343            &mut state,
6344            &mut choice,
6345            &MOVES.get(&Choices::TACKLE).unwrap(),
6346            SideReference::SideOne,
6347            StateInstructions::default(),
6348            &mut instructions,
6349            false,
6350        );
6351
6352        let expected_instructions = vec![StateInstructions {
6353            percentage: 100.0,
6354            instruction_list: vec![
6355                Instruction::Damage(DamageInstruction {
6356                    side_ref: SideReference::SideTwo,
6357                    damage_amount: 61,
6358                }),
6359                Instruction::Boost(BoostInstruction {
6360                    side_ref: SideReference::SideOne,
6361                    amount: 1,
6362                    stat: PokemonBoostableStat::Speed,
6363                }),
6364            ],
6365        }];
6366
6367        assert_eq!(instructions, expected_instructions)
6368    }
6369
6370    #[test]
6371    fn test_courtchange_basic_swap() {
6372        let mut state: State = State::default();
6373        state.side_one.side_conditions.stealth_rock = 1;
6374
6375        let mut choice = MOVES.get(&Choices::COURTCHANGE).unwrap().to_owned();
6376
6377        let mut instructions = vec![];
6378        generate_instructions_from_move(
6379            &mut state,
6380            &mut choice,
6381            &MOVES.get(&Choices::TACKLE).unwrap(),
6382            SideReference::SideOne,
6383            StateInstructions::default(),
6384            &mut instructions,
6385            false,
6386        );
6387
6388        let expected_instructions = vec![StateInstructions {
6389            percentage: 100.0,
6390            instruction_list: vec![
6391                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6392                    side_ref: SideReference::SideOne,
6393                    side_condition: PokemonSideCondition::Stealthrock,
6394                    amount: -1,
6395                }),
6396                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6397                    side_ref: SideReference::SideTwo,
6398                    side_condition: PokemonSideCondition::Stealthrock,
6399                    amount: 1,
6400                }),
6401            ],
6402        }];
6403
6404        assert_eq!(instructions, expected_instructions)
6405    }
6406
6407    #[test]
6408    fn test_courtchange_complicated_swap() {
6409        let mut state: State = State::default();
6410        state.side_one.side_conditions.stealth_rock = 1;
6411        state.side_two.side_conditions.toxic_spikes = 2;
6412        state.side_two.side_conditions.spikes = 3;
6413        state.side_two.side_conditions.sticky_web = 1;
6414
6415        let mut choice = MOVES.get(&Choices::COURTCHANGE).unwrap().to_owned();
6416
6417        let mut instructions = vec![];
6418        generate_instructions_from_move(
6419            &mut state,
6420            &mut choice,
6421            &MOVES.get(&Choices::TACKLE).unwrap(),
6422            SideReference::SideOne,
6423            StateInstructions::default(),
6424            &mut instructions,
6425            false,
6426        );
6427
6428        let expected_instructions = vec![StateInstructions {
6429            percentage: 100.0,
6430            instruction_list: vec![
6431                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6432                    side_ref: SideReference::SideOne,
6433                    side_condition: PokemonSideCondition::Stealthrock,
6434                    amount: -1,
6435                }),
6436                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6437                    side_ref: SideReference::SideTwo,
6438                    side_condition: PokemonSideCondition::Stealthrock,
6439                    amount: 1,
6440                }),
6441                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6442                    side_ref: SideReference::SideTwo,
6443                    side_condition: PokemonSideCondition::Spikes,
6444                    amount: -3,
6445                }),
6446                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6447                    side_ref: SideReference::SideOne,
6448                    side_condition: PokemonSideCondition::Spikes,
6449                    amount: 3,
6450                }),
6451                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6452                    side_ref: SideReference::SideTwo,
6453                    side_condition: PokemonSideCondition::ToxicSpikes,
6454                    amount: -2,
6455                }),
6456                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6457                    side_ref: SideReference::SideOne,
6458                    side_condition: PokemonSideCondition::ToxicSpikes,
6459                    amount: 2,
6460                }),
6461                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6462                    side_ref: SideReference::SideTwo,
6463                    side_condition: PokemonSideCondition::StickyWeb,
6464                    amount: -1,
6465                }),
6466                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
6467                    side_ref: SideReference::SideOne,
6468                    side_condition: PokemonSideCondition::StickyWeb,
6469                    amount: 1,
6470                }),
6471            ],
6472        }];
6473
6474        assert_eq!(instructions, expected_instructions)
6475    }
6476
6477    #[test]
6478    fn test_stoneaxe_does_not_set_stealthrock_if_already_set() {
6479        let mut state: State = State::default();
6480        state.side_two.side_conditions.stealth_rock = 1;
6481        let mut choice = MOVES.get(&Choices::STONEAXE).unwrap().to_owned();
6482
6483        let mut instructions = vec![];
6484        generate_instructions_from_move(
6485            &mut state,
6486            &mut choice,
6487            &MOVES.get(&Choices::TACKLE).unwrap(),
6488            SideReference::SideOne,
6489            StateInstructions::default(),
6490            &mut instructions,
6491            false,
6492        );
6493
6494        let expected_instructions = vec![
6495            StateInstructions {
6496                percentage: 10.000002,
6497                instruction_list: vec![],
6498            },
6499            StateInstructions {
6500                percentage: 90.0,
6501                instruction_list: vec![Instruction::Damage(DamageInstruction {
6502                    side_ref: SideReference::SideTwo,
6503                    damage_amount: 51,
6504                })],
6505            },
6506        ];
6507
6508        assert_eq!(instructions, expected_instructions)
6509    }
6510
6511    #[test]
6512    fn test_flinched_pokemon_cannot_move() {
6513        let mut state: State = State::default();
6514        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
6515        state
6516            .side_one
6517            .volatile_statuses
6518            .insert(PokemonVolatileStatus::FLINCH);
6519
6520        let mut instructions = vec![];
6521        generate_instructions_from_move(
6522            &mut state,
6523            &mut choice,
6524            &MOVES.get(&Choices::TACKLE).unwrap(),
6525            SideReference::SideOne,
6526            StateInstructions::default(),
6527            &mut instructions,
6528            false,
6529        );
6530        assert_eq!(instructions, vec![StateInstructions::default()])
6531    }
6532
6533    #[test]
6534    fn test_dead_pokemon_moving_second_does_nothing() {
6535        let mut state: State = State::default();
6536        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
6537        choice.first_move = false;
6538        state.side_one.get_active().hp = 0;
6539
6540        let mut instructions = vec![];
6541        generate_instructions_from_move(
6542            &mut state,
6543            &mut choice,
6544            &MOVES.get(&Choices::TACKLE).unwrap(),
6545            SideReference::SideOne,
6546            StateInstructions::default(),
6547            &mut instructions,
6548            false,
6549        );
6550        assert_eq!(instructions, vec![StateInstructions::default()])
6551    }
6552
6553    #[test]
6554    fn test_cannot_ohko_versus_study() {
6555        let mut state: State = State::default();
6556        let mut choice = MOVES.get(&Choices::EARTHQUAKE).unwrap().to_owned();
6557        state.side_two.get_active().ability = Abilities::STURDY;
6558        state.side_two.get_active().hp = 50;
6559        state.side_two.get_active().maxhp = 50;
6560
6561        let mut instructions = vec![];
6562        generate_instructions_from_move(
6563            &mut state,
6564            &mut choice,
6565            &MOVES.get(&Choices::TACKLE).unwrap(),
6566            SideReference::SideOne,
6567            StateInstructions::default(),
6568            &mut instructions,
6569            false,
6570        );
6571
6572        let expected_instructions: StateInstructions = StateInstructions {
6573            percentage: 100.0,
6574            instruction_list: vec![Instruction::Damage(DamageInstruction {
6575                side_ref: SideReference::SideTwo,
6576                damage_amount: 49,
6577            })],
6578        };
6579
6580        assert_eq!(instructions, vec![expected_instructions])
6581    }
6582
6583    #[test]
6584    fn test_cannot_ohko_versus_sash() {
6585        let mut state: State = State::default();
6586        let mut choice = MOVES.get(&Choices::EARTHQUAKE).unwrap().to_owned();
6587        state.side_two.get_active().item = Items::FOCUSSASH;
6588        state.side_two.get_active().hp = 50;
6589        state.side_two.get_active().maxhp = 50;
6590
6591        let mut instructions = vec![];
6592        generate_instructions_from_move(
6593            &mut state,
6594            &mut choice,
6595            &MOVES.get(&Choices::TACKLE).unwrap(),
6596            SideReference::SideOne,
6597            StateInstructions::default(),
6598            &mut instructions,
6599            false,
6600        );
6601
6602        let expected_instructions: StateInstructions = StateInstructions {
6603            percentage: 100.0,
6604            instruction_list: vec![Instruction::Damage(DamageInstruction {
6605                side_ref: SideReference::SideTwo,
6606                damage_amount: 49,
6607            })],
6608        };
6609
6610        assert_eq!(instructions, vec![expected_instructions])
6611    }
6612
6613    #[test]
6614    fn test_sturdy_does_not_affect_non_ohko_move() {
6615        let mut state: State = State::default();
6616        let mut choice = MOVES.get(&Choices::EARTHQUAKE).unwrap().to_owned();
6617        state.side_two.get_active().ability = Abilities::STURDY;
6618        state.side_two.get_active().hp = 45;
6619        state.side_two.get_active().maxhp = 50;
6620
6621        let mut instructions = vec![];
6622        generate_instructions_from_move(
6623            &mut state,
6624            &mut choice,
6625            &MOVES.get(&Choices::TACKLE).unwrap(),
6626            SideReference::SideOne,
6627            StateInstructions::default(),
6628            &mut instructions,
6629            false,
6630        );
6631
6632        let expected_instructions: StateInstructions = StateInstructions {
6633            percentage: 100.0,
6634            instruction_list: vec![Instruction::Damage(DamageInstruction {
6635                side_ref: SideReference::SideTwo,
6636                damage_amount: 45,
6637            })],
6638        };
6639
6640        assert_eq!(instructions, vec![expected_instructions])
6641    }
6642
6643    #[test]
6644    fn test_beastboost_boosts_on_kill() {
6645        let mut state: State = State::default();
6646        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
6647        state.side_one.get_active().ability = Abilities::BEASTBOOST;
6648        state.side_one.get_active().attack = 500; // highest stat
6649        state.side_two.get_active().hp = 1;
6650
6651        let mut instructions = vec![];
6652        generate_instructions_from_move(
6653            &mut state,
6654            &mut choice,
6655            &MOVES.get(&Choices::TACKLE).unwrap(),
6656            SideReference::SideOne,
6657            StateInstructions::default(),
6658            &mut instructions,
6659            false,
6660        );
6661
6662        let expected_instructions: StateInstructions = StateInstructions {
6663            percentage: 100.0,
6664            instruction_list: vec![
6665                Instruction::Damage(DamageInstruction {
6666                    side_ref: SideReference::SideTwo,
6667                    damage_amount: 1,
6668                }),
6669                Instruction::Boost(BoostInstruction {
6670                    side_ref: SideReference::SideOne,
6671                    stat: PokemonBoostableStat::Attack,
6672                    amount: 1,
6673                }),
6674            ],
6675        };
6676        assert_eq!(instructions, vec![expected_instructions])
6677    }
6678
6679    #[test]
6680    fn test_beastboost_boosts_different_stat_on_kill() {
6681        let mut state: State = State::default();
6682        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
6683        state.side_one.get_active().ability = Abilities::BEASTBOOST;
6684        state.side_one.get_active().defense = 500; // highest stat
6685        state.side_two.get_active().hp = 1;
6686
6687        let mut instructions = vec![];
6688        generate_instructions_from_move(
6689            &mut state,
6690            &mut choice,
6691            &MOVES.get(&Choices::TACKLE).unwrap(),
6692            SideReference::SideOne,
6693            StateInstructions::default(),
6694            &mut instructions,
6695            false,
6696        );
6697
6698        let expected_instructions: StateInstructions = StateInstructions {
6699            percentage: 100.0,
6700            instruction_list: vec![
6701                Instruction::Damage(DamageInstruction {
6702                    side_ref: SideReference::SideTwo,
6703                    damage_amount: 1,
6704                }),
6705                Instruction::Boost(BoostInstruction {
6706                    side_ref: SideReference::SideOne,
6707                    stat: PokemonBoostableStat::Defense,
6708                    amount: 1,
6709                }),
6710            ],
6711        };
6712        assert_eq!(instructions, vec![expected_instructions])
6713    }
6714
6715    #[test]
6716    fn test_beastboost_does_not_overboost() {
6717        let mut state: State = State::default();
6718        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
6719        state.side_one.get_active().ability = Abilities::BEASTBOOST;
6720        state.side_one.get_active().attack = 500; // highest stat
6721        state.side_one.attack_boost = 6; // max boosts already
6722        state.side_two.get_active().hp = 1;
6723
6724        let mut instructions = vec![];
6725        generate_instructions_from_move(
6726            &mut state,
6727            &mut choice,
6728            &MOVES.get(&Choices::TACKLE).unwrap(),
6729            SideReference::SideOne,
6730            StateInstructions::default(),
6731            &mut instructions,
6732            false,
6733        );
6734
6735        let expected_instructions: StateInstructions = StateInstructions {
6736            percentage: 100.0,
6737            instruction_list: vec![Instruction::Damage(DamageInstruction {
6738                side_ref: SideReference::SideTwo,
6739                damage_amount: 1,
6740            })],
6741        };
6742
6743        assert_eq!(instructions, vec![expected_instructions])
6744    }
6745
6746    #[test]
6747    fn test_beastboost_does_not_boost_without_kill() {
6748        let mut state: State = State::default();
6749        let mut choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
6750        state.side_one.get_active().ability = Abilities::BEASTBOOST;
6751        state.side_one.get_active().attack = 150; // highest stat
6752        state.side_two.get_active().hp = 100;
6753
6754        let mut instructions = vec![];
6755        generate_instructions_from_move(
6756            &mut state,
6757            &mut choice,
6758            &MOVES.get(&Choices::TACKLE).unwrap(),
6759            SideReference::SideOne,
6760            StateInstructions::default(),
6761            &mut instructions,
6762            false,
6763        );
6764
6765        let expected_instructions: StateInstructions = StateInstructions {
6766            percentage: 100.0,
6767            instruction_list: vec![Instruction::Damage(DamageInstruction {
6768                side_ref: SideReference::SideTwo,
6769                damage_amount: 72,
6770            })],
6771        };
6772
6773        assert_eq!(instructions, vec![expected_instructions])
6774    }
6775
6776    #[test]
6777    fn test_drain_move_heals() {
6778        let mut state: State = State::default();
6779        let mut choice = MOVES.get(&Choices::ABSORB).unwrap().to_owned();
6780        state.side_one.get_active().hp = 100;
6781        state.side_one.get_active().maxhp = 200;
6782
6783        let mut instructions = vec![];
6784        generate_instructions_from_move(
6785            &mut state,
6786            &mut choice,
6787            &MOVES.get(&Choices::TACKLE).unwrap(),
6788            SideReference::SideOne,
6789            StateInstructions::default(),
6790            &mut instructions,
6791            false,
6792        );
6793
6794        let expected_instructions: StateInstructions = StateInstructions {
6795            percentage: 100.0,
6796            instruction_list: vec![
6797                Instruction::Damage(DamageInstruction {
6798                    side_ref: SideReference::SideTwo,
6799                    damage_amount: 16,
6800                }),
6801                Instruction::Heal(HealInstruction {
6802                    side_ref: SideReference::SideOne,
6803                    heal_amount: 8,
6804                }),
6805            ],
6806        };
6807
6808        assert_eq!(instructions, vec![expected_instructions])
6809    }
6810
6811    #[test]
6812    fn test_drain_move_does_not_overheal() {
6813        let mut state: State = State::default();
6814        let mut choice = MOVES.get(&Choices::ABSORB).unwrap().to_owned();
6815        state.side_one.get_active().hp = 100;
6816        state.side_one.get_active().maxhp = 105;
6817
6818        let mut instructions = vec![];
6819        generate_instructions_from_move(
6820            &mut state,
6821            &mut choice,
6822            &MOVES.get(&Choices::TACKLE).unwrap(),
6823            SideReference::SideOne,
6824            StateInstructions::default(),
6825            &mut instructions,
6826            false,
6827        );
6828
6829        let expected_instructions: StateInstructions = StateInstructions {
6830            percentage: 100.0,
6831            instruction_list: vec![
6832                Instruction::Damage(DamageInstruction {
6833                    side_ref: SideReference::SideTwo,
6834                    damage_amount: 16,
6835                }),
6836                Instruction::Heal(HealInstruction {
6837                    side_ref: SideReference::SideOne,
6838                    heal_amount: 5,
6839                }),
6840            ],
6841        };
6842
6843        assert_eq!(instructions, vec![expected_instructions])
6844    }
6845
6846    #[test]
6847    fn test_recoil_damage() {
6848        let mut state: State = State::default();
6849        let mut choice = MOVES.get(&Choices::BRAVEBIRD).unwrap().to_owned();
6850        state.side_one.get_active().hp = 105;
6851        state.side_one.get_active().maxhp = 105;
6852
6853        let mut instructions = vec![];
6854        generate_instructions_from_move(
6855            &mut state,
6856            &mut choice,
6857            &MOVES.get(&Choices::TACKLE).unwrap(),
6858            SideReference::SideOne,
6859            StateInstructions::default(),
6860            &mut instructions,
6861            false,
6862        );
6863
6864        let expected_instructions: StateInstructions = StateInstructions {
6865            percentage: 100.0,
6866            instruction_list: vec![
6867                Instruction::Damage(DamageInstruction {
6868                    side_ref: SideReference::SideTwo,
6869                    damage_amount: 94,
6870                }),
6871                Instruction::Damage(DamageInstruction {
6872                    side_ref: SideReference::SideOne,
6873                    damage_amount: 31,
6874                }),
6875            ],
6876        };
6877
6878        assert_eq!(instructions, vec![expected_instructions])
6879    }
6880
6881    #[test]
6882    fn test_recoil_cannot_overkill() {
6883        let mut state: State = State::default();
6884        let mut choice = MOVES.get(&Choices::BRAVEBIRD).unwrap().to_owned();
6885        state.side_one.get_active().hp = 5;
6886        state.side_one.get_active().maxhp = 105;
6887
6888        let mut instructions = vec![];
6889        generate_instructions_from_move(
6890            &mut state,
6891            &mut choice,
6892            &MOVES.get(&Choices::TACKLE).unwrap(),
6893            SideReference::SideOne,
6894            StateInstructions::default(),
6895            &mut instructions,
6896            false,
6897        );
6898
6899        let expected_instructions: StateInstructions = StateInstructions {
6900            percentage: 100.0,
6901            instruction_list: vec![
6902                Instruction::Damage(DamageInstruction {
6903                    side_ref: SideReference::SideTwo,
6904                    damage_amount: 94,
6905                }),
6906                Instruction::Damage(DamageInstruction {
6907                    side_ref: SideReference::SideOne,
6908                    damage_amount: 5,
6909                }),
6910            ],
6911        };
6912
6913        assert_eq!(instructions, vec![expected_instructions])
6914    }
6915
6916    #[test]
6917    fn test_drain_and_recoil_together() {
6918        let mut state: State = State::default();
6919        let mut choice = MOVES.get(&Choices::ABSORB).unwrap().to_owned();
6920        choice.recoil = Some(0.33);
6921        state.side_one.get_active().hp = 1;
6922        state.side_one.get_active().maxhp = 105;
6923
6924        let mut instructions = vec![];
6925        generate_instructions_from_move(
6926            &mut state,
6927            &mut choice,
6928            &MOVES.get(&Choices::TACKLE).unwrap(),
6929            SideReference::SideOne,
6930            StateInstructions::default(),
6931            &mut instructions,
6932            false,
6933        );
6934
6935        let expected_instructions: StateInstructions = StateInstructions {
6936            percentage: 100.0,
6937            instruction_list: vec![
6938                Instruction::Damage(DamageInstruction {
6939                    side_ref: SideReference::SideTwo,
6940                    damage_amount: 16,
6941                }),
6942                Instruction::Heal(HealInstruction {
6943                    side_ref: SideReference::SideOne,
6944                    heal_amount: 8,
6945                }),
6946                Instruction::Damage(DamageInstruction {
6947                    side_ref: SideReference::SideOne,
6948                    damage_amount: 5,
6949                }),
6950            ],
6951        };
6952
6953        assert_eq!(instructions, vec![expected_instructions])
6954    }
6955
6956    #[test]
6957    fn test_crash_move_missing() {
6958        let mut state: State = State::default();
6959        let mut choice = MOVES.get(&Choices::JUMPKICK).unwrap().to_owned();
6960
6961        let mut instructions = vec![];
6962        generate_instructions_from_move(
6963            &mut state,
6964            &mut choice,
6965            &MOVES.get(&Choices::TACKLE).unwrap(),
6966            SideReference::SideOne,
6967            StateInstructions::default(),
6968            &mut instructions,
6969            false,
6970        );
6971
6972        let expected_instructions: Vec<StateInstructions> = vec![
6973            StateInstructions {
6974                percentage: 5.000001,
6975                instruction_list: vec![Instruction::Damage(DamageInstruction {
6976                    side_ref: SideReference::SideOne,
6977                    damage_amount: 50,
6978                })],
6979            },
6980            StateInstructions {
6981                percentage: 95.0,
6982                instruction_list: vec![Instruction::Damage(DamageInstruction {
6983                    side_ref: SideReference::SideTwo,
6984                    damage_amount: 100,
6985                })],
6986            },
6987        ];
6988
6989        assert_eq!(instructions, expected_instructions)
6990    }
6991
6992    #[test]
6993    fn test_crash_move_missing_versus_ghost_type() {
6994        let mut state: State = State::default();
6995        state.side_two.get_active().types.0 = PokemonType::GHOST;
6996        let mut choice = MOVES.get(&Choices::JUMPKICK).unwrap().to_owned();
6997
6998        let mut instructions = vec![];
6999        generate_instructions_from_move(
7000            &mut state,
7001            &mut choice,
7002            &MOVES.get(&Choices::TACKLE).unwrap(),
7003            SideReference::SideOne,
7004            StateInstructions::default(),
7005            &mut instructions,
7006            false,
7007        );
7008
7009        let expected_instructions: Vec<StateInstructions> = vec![StateInstructions {
7010            percentage: 100.0,
7011            instruction_list: vec![Instruction::Damage(DamageInstruction {
7012                side_ref: SideReference::SideOne,
7013                damage_amount: 50,
7014            })],
7015        }];
7016
7017        assert_eq!(instructions, expected_instructions)
7018    }
7019
7020    #[test]
7021    fn test_crash_move_missing_cannot_overkill() {
7022        let mut state: State = State::default();
7023        state.get_side(&SideReference::SideOne).get_active().hp = 5;
7024        let mut choice = MOVES.get(&Choices::JUMPKICK).unwrap().to_owned();
7025
7026        let mut instructions = vec![];
7027        generate_instructions_from_move(
7028            &mut state,
7029            &mut choice,
7030            &MOVES.get(&Choices::TACKLE).unwrap(),
7031            SideReference::SideOne,
7032            StateInstructions::default(),
7033            &mut instructions,
7034            false,
7035        );
7036
7037        let expected_instructions: Vec<StateInstructions> = vec![
7038            StateInstructions {
7039                percentage: 5.000001,
7040                instruction_list: vec![Instruction::Damage(DamageInstruction {
7041                    side_ref: SideReference::SideOne,
7042                    damage_amount: 5,
7043                })],
7044            },
7045            StateInstructions {
7046                percentage: 95.0,
7047                instruction_list: vec![Instruction::Damage(DamageInstruction {
7048                    side_ref: SideReference::SideTwo,
7049                    damage_amount: 100,
7050                })],
7051            },
7052        ];
7053
7054        assert_eq!(instructions, expected_instructions)
7055    }
7056
7057    #[test]
7058    #[cfg(feature = "gen9")]
7059    fn test_knockoff_removing_item() {
7060        let mut state: State = State::default();
7061        let mut choice = MOVES.get(&Choices::KNOCKOFF).unwrap().to_owned();
7062        state.get_side(&SideReference::SideTwo).get_active().item = Items::HEAVYDUTYBOOTS;
7063
7064        let mut instructions = vec![];
7065        generate_instructions_from_move(
7066            &mut state,
7067            &mut choice,
7068            &MOVES.get(&Choices::TACKLE).unwrap(),
7069            SideReference::SideOne,
7070            StateInstructions::default(),
7071            &mut instructions,
7072            false,
7073        );
7074
7075        let expected_instructions: StateInstructions = StateInstructions {
7076            percentage: 100.0,
7077            instruction_list: vec![
7078                Instruction::Damage(DamageInstruction {
7079                    side_ref: SideReference::SideTwo,
7080                    damage_amount: 76,
7081                }),
7082                Instruction::ChangeItem(ChangeItemInstruction {
7083                    side_ref: SideReference::SideTwo,
7084                    current_item: Items::HEAVYDUTYBOOTS,
7085                    new_item: Items::NONE,
7086                }),
7087            ],
7088        };
7089
7090        assert_eq!(instructions, vec![expected_instructions])
7091    }
7092
7093    #[test]
7094    fn test_blunderpolicy_boost() {
7095        let mut state: State = State::default();
7096        let mut choice = MOVES.get(&Choices::CROSSCHOP).unwrap().to_owned();
7097        state.get_side(&SideReference::SideOne).get_active().item = Items::BLUNDERPOLICY;
7098
7099        let mut instructions = vec![];
7100        generate_instructions_from_move(
7101            &mut state,
7102            &mut choice,
7103            &MOVES.get(&Choices::TACKLE).unwrap(),
7104            SideReference::SideOne,
7105            StateInstructions::default(),
7106            &mut instructions,
7107            false,
7108        );
7109
7110        let expected_instructions: Vec<StateInstructions> = vec![
7111            StateInstructions {
7112                percentage: 19.999998,
7113                instruction_list: vec![
7114                    Instruction::Boost(BoostInstruction {
7115                        side_ref: SideReference::SideOne,
7116                        stat: PokemonBoostableStat::Speed,
7117                        amount: 2,
7118                    }),
7119                    Instruction::ChangeItem(ChangeItemInstruction {
7120                        side_ref: SideReference::SideOne,
7121                        current_item: Items::BLUNDERPOLICY,
7122                        new_item: Items::NONE,
7123                    }),
7124                ],
7125            },
7126            StateInstructions {
7127                percentage: 80.0,
7128                instruction_list: vec![Instruction::Damage(DamageInstruction {
7129                    side_ref: SideReference::SideTwo,
7130                    damage_amount: 100,
7131                })],
7132            },
7133        ];
7134
7135        assert_eq!(instructions, expected_instructions);
7136    }
7137
7138    #[test]
7139    fn test_basic_switch_functionality_with_no_prior_instructions() {
7140        let mut state: State = State::default();
7141        let mut choice = Choice {
7142            ..Default::default()
7143        };
7144
7145        choice.switch_id = PokemonIndex::P1;
7146
7147        let expected_instructions: StateInstructions = StateInstructions {
7148            percentage: 100.0,
7149            instruction_list: vec![Instruction::Switch(SwitchInstruction {
7150                side_ref: SideReference::SideOne,
7151                previous_index: PokemonIndex::P0,
7152                next_index: PokemonIndex::P1,
7153            })],
7154            ..Default::default()
7155        };
7156
7157        let mut incoming_instructions = StateInstructions::default();
7158        generate_instructions_from_switch(
7159            &mut state,
7160            choice.switch_id,
7161            SideReference::SideOne,
7162            &mut incoming_instructions,
7163        );
7164
7165        assert_eq!(expected_instructions, incoming_instructions);
7166    }
7167
7168    #[test]
7169    fn test_basic_switch_with_volatile_statuses() {
7170        let mut state: State = State::default();
7171        state
7172            .side_one
7173            .volatile_statuses
7174            .insert(PokemonVolatileStatus::LEECHSEED);
7175        let mut choice = Choice {
7176            ..Default::default()
7177        };
7178        choice.switch_id = PokemonIndex::P1;
7179
7180        let expected_instructions: StateInstructions = StateInstructions {
7181            percentage: 100.0,
7182            instruction_list: vec![
7183                Instruction::RemoveVolatileStatus(RemoveVolatileStatusInstruction {
7184                    side_ref: SideReference::SideOne,
7185                    volatile_status: PokemonVolatileStatus::LEECHSEED,
7186                }),
7187                Instruction::Switch(SwitchInstruction {
7188                    side_ref: SideReference::SideOne,
7189                    previous_index: PokemonIndex::P0,
7190                    next_index: PokemonIndex::P1,
7191                }),
7192            ],
7193            ..Default::default()
7194        };
7195
7196        let mut incoming_instructions = StateInstructions::default();
7197        generate_instructions_from_switch(
7198            &mut state,
7199            choice.switch_id,
7200            SideReference::SideOne,
7201            &mut incoming_instructions,
7202        );
7203
7204        assert_eq!(expected_instructions, incoming_instructions);
7205    }
7206
7207    #[test]
7208    fn test_basic_switch_with_toxic_count() {
7209        let mut state: State = State::default();
7210        state.side_one.side_conditions.toxic_count = 2;
7211        let mut choice = Choice {
7212            ..Default::default()
7213        };
7214        choice.switch_id = PokemonIndex::P1;
7215
7216        let expected_instructions: StateInstructions = StateInstructions {
7217            percentage: 100.0,
7218            instruction_list: vec![
7219                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
7220                    side_ref: SideReference::SideOne,
7221                    side_condition: PokemonSideCondition::ToxicCount,
7222                    amount: -2,
7223                }),
7224                Instruction::Switch(SwitchInstruction {
7225                    side_ref: SideReference::SideOne,
7226                    previous_index: PokemonIndex::P0,
7227                    next_index: PokemonIndex::P1,
7228                }),
7229            ],
7230            ..Default::default()
7231        };
7232
7233        let mut incoming_instructions = StateInstructions::default();
7234        generate_instructions_from_switch(
7235            &mut state,
7236            choice.switch_id,
7237            SideReference::SideOne,
7238            &mut incoming_instructions,
7239        );
7240
7241        assert_eq!(expected_instructions, incoming_instructions);
7242    }
7243
7244    #[test]
7245    fn test_basic_switch_with_boost() {
7246        let mut state: State = State::default();
7247        state.side_one.attack_boost = 2;
7248        state.side_one.speed_boost = 5;
7249        let mut choice = Choice {
7250            ..Default::default()
7251        };
7252        choice.switch_id = PokemonIndex::P1;
7253
7254        let expected_instructions: StateInstructions = StateInstructions {
7255            percentage: 100.0,
7256            instruction_list: vec![
7257                Instruction::Boost(BoostInstruction {
7258                    side_ref: SideReference::SideOne,
7259                    stat: PokemonBoostableStat::Attack,
7260                    amount: -2,
7261                }),
7262                Instruction::Boost(BoostInstruction {
7263                    side_ref: SideReference::SideOne,
7264                    stat: PokemonBoostableStat::Speed,
7265                    amount: -5,
7266                }),
7267                Instruction::Switch(SwitchInstruction {
7268                    side_ref: SideReference::SideOne,
7269                    previous_index: PokemonIndex::P0,
7270                    next_index: PokemonIndex::P1,
7271                }),
7272            ],
7273            ..Default::default()
7274        };
7275
7276        let mut incoming_instructions = StateInstructions::default();
7277        generate_instructions_from_switch(
7278            &mut state,
7279            choice.switch_id,
7280            SideReference::SideOne,
7281            &mut incoming_instructions,
7282        );
7283
7284        assert_eq!(expected_instructions, incoming_instructions);
7285    }
7286
7287    #[test]
7288    fn test_basic_switch_with_disabled_move() {
7289        let mut state: State = State::default();
7290        state.side_one.get_active().moves.m0 = Move {
7291            id: Choices::NONE,
7292            disabled: true,
7293            pp: 32,
7294            ..Default::default()
7295        };
7296        state.side_one.get_active().moves.m1 = Move {
7297            id: Choices::NONE,
7298            disabled: false,
7299            pp: 32,
7300            ..Default::default()
7301        };
7302
7303        let mut choice = Choice {
7304            ..Default::default()
7305        };
7306        choice.switch_id = PokemonIndex::P1;
7307
7308        let expected_instructions: StateInstructions = StateInstructions {
7309            percentage: 100.0,
7310            instruction_list: vec![
7311                Instruction::EnableMove(EnableMoveInstruction {
7312                    side_ref: SideReference::SideOne,
7313                    move_index: PokemonMoveIndex::M0,
7314                }),
7315                Instruction::Switch(SwitchInstruction {
7316                    side_ref: SideReference::SideOne,
7317                    previous_index: PokemonIndex::P0,
7318                    next_index: PokemonIndex::P1,
7319                }),
7320            ],
7321            ..Default::default()
7322        };
7323
7324        let mut incoming_instructions = StateInstructions::default();
7325        generate_instructions_from_switch(
7326            &mut state,
7327            choice.switch_id,
7328            SideReference::SideOne,
7329            &mut incoming_instructions,
7330        );
7331
7332        assert_eq!(expected_instructions, incoming_instructions);
7333    }
7334
7335    #[test]
7336    fn test_basic_switch_with_multiple_disabled_moves() {
7337        let mut state: State = State::default();
7338        state.side_one.get_active().moves.m0 = Move {
7339            id: Choices::NONE,
7340            disabled: true,
7341            pp: 32,
7342            ..Default::default()
7343        };
7344        state.side_one.get_active().moves.m1 = Move {
7345            id: Choices::NONE,
7346            disabled: true,
7347            pp: 32,
7348            ..Default::default()
7349        };
7350        state.side_one.get_active().moves.m2 = Move {
7351            id: Choices::NONE,
7352            disabled: false,
7353            pp: 32,
7354            ..Default::default()
7355        };
7356        state.side_one.get_active().moves.m3 = Move {
7357            id: Choices::NONE,
7358            disabled: true,
7359            pp: 32,
7360            ..Default::default()
7361        };
7362        let mut choice = Choice {
7363            ..Default::default()
7364        };
7365        choice.switch_id = PokemonIndex::P1;
7366
7367        let expected_instructions: StateInstructions = StateInstructions {
7368            percentage: 100.0,
7369            instruction_list: vec![
7370                Instruction::EnableMove(EnableMoveInstruction {
7371                    side_ref: SideReference::SideOne,
7372                    move_index: PokemonMoveIndex::M0,
7373                }),
7374                Instruction::EnableMove(EnableMoveInstruction {
7375                    side_ref: SideReference::SideOne,
7376                    move_index: PokemonMoveIndex::M1,
7377                }),
7378                Instruction::EnableMove(EnableMoveInstruction {
7379                    side_ref: SideReference::SideOne,
7380                    move_index: PokemonMoveIndex::M3,
7381                }),
7382                Instruction::Switch(SwitchInstruction {
7383                    side_ref: SideReference::SideOne,
7384                    previous_index: PokemonIndex::P0,
7385                    next_index: PokemonIndex::P1,
7386                }),
7387            ],
7388            ..Default::default()
7389        };
7390
7391        let mut incoming_instructions = StateInstructions::default();
7392        generate_instructions_from_switch(
7393            &mut state,
7394            choice.switch_id,
7395            SideReference::SideOne,
7396            &mut incoming_instructions,
7397        );
7398
7399        assert_eq!(expected_instructions, incoming_instructions);
7400    }
7401
7402    #[test]
7403    fn test_basic_switch_functionality_with_a_prior_instruction() {
7404        let mut state: State = State::default();
7405        let mut incoming_instructions = StateInstructions::default();
7406        let mut choice = Choice {
7407            ..Default::default()
7408        };
7409
7410        choice.switch_id = PokemonIndex::P1;
7411        incoming_instructions
7412            .instruction_list
7413            .push(Instruction::Damage(DamageInstruction {
7414                side_ref: SideReference::SideOne,
7415                damage_amount: 1,
7416            }));
7417
7418        let expected_instructions: StateInstructions = StateInstructions {
7419            percentage: 100.0,
7420            instruction_list: vec![
7421                Instruction::Damage(DamageInstruction {
7422                    side_ref: SideReference::SideOne,
7423                    damage_amount: 1,
7424                }),
7425                Instruction::Switch(SwitchInstruction {
7426                    side_ref: SideReference::SideOne,
7427                    previous_index: PokemonIndex::P0,
7428                    next_index: PokemonIndex::P1,
7429                }),
7430            ],
7431            ..Default::default()
7432        };
7433
7434        generate_instructions_from_switch(
7435            &mut state,
7436            choice.switch_id,
7437            SideReference::SideOne,
7438            &mut incoming_instructions,
7439        );
7440
7441        assert_eq!(expected_instructions, incoming_instructions);
7442    }
7443
7444    #[test]
7445    fn test_switch_with_regenerator() {
7446        let mut state: State = State::default();
7447        state.side_one.get_active().hp -= 10;
7448        state.side_one.get_active().ability = Abilities::REGENERATOR;
7449        state.side_one.get_active().base_ability = Abilities::REGENERATOR;
7450        let mut choice = Choice {
7451            ..Default::default()
7452        };
7453        choice.switch_id = PokemonIndex::P1;
7454
7455        let expected_instructions: StateInstructions = StateInstructions {
7456            percentage: 100.0,
7457            instruction_list: vec![
7458                Instruction::Heal(HealInstruction {
7459                    side_ref: SideReference::SideOne,
7460                    heal_amount: 10,
7461                }),
7462                Instruction::Switch(SwitchInstruction {
7463                    side_ref: SideReference::SideOne,
7464                    previous_index: PokemonIndex::P0,
7465                    next_index: PokemonIndex::P1,
7466                }),
7467            ],
7468            ..Default::default()
7469        };
7470
7471        let mut incoming_instructions = StateInstructions::default();
7472        generate_instructions_from_switch(
7473            &mut state,
7474            choice.switch_id,
7475            SideReference::SideOne,
7476            &mut incoming_instructions,
7477        );
7478
7479        assert_eq!(expected_instructions, incoming_instructions);
7480    }
7481
7482    #[test]
7483    fn test_switch_with_regenerator_plus_move_enabling() {
7484        let mut state: State = State::default();
7485        state.side_one.get_active().moves.m0 = Move {
7486            id: Choices::NONE,
7487            disabled: true,
7488            pp: 32,
7489            ..Default::default()
7490        };
7491        state.side_one.get_active().moves.m1 = Move {
7492            id: Choices::NONE,
7493            disabled: true,
7494            pp: 32,
7495            ..Default::default()
7496        };
7497        state.side_one.get_active().moves.m2 = Move {
7498            id: Choices::NONE,
7499            disabled: false,
7500            pp: 32,
7501            ..Default::default()
7502        };
7503        state.side_one.get_active().moves.m3 = Move {
7504            id: Choices::NONE,
7505            disabled: true,
7506            pp: 32,
7507            ..Default::default()
7508        };
7509        state.side_one.get_active().hp -= 10;
7510        state.side_one.get_active().ability = Abilities::REGENERATOR;
7511        state.side_one.get_active().base_ability = Abilities::REGENERATOR;
7512        let mut choice = Choice {
7513            ..Default::default()
7514        };
7515        choice.switch_id = PokemonIndex::P1;
7516
7517        let expected_instructions: StateInstructions = StateInstructions {
7518            percentage: 100.0,
7519            instruction_list: vec![
7520                Instruction::EnableMove(EnableMoveInstruction {
7521                    side_ref: SideReference::SideOne,
7522                    move_index: PokemonMoveIndex::M0,
7523                }),
7524                Instruction::EnableMove(EnableMoveInstruction {
7525                    side_ref: SideReference::SideOne,
7526                    move_index: PokemonMoveIndex::M1,
7527                }),
7528                Instruction::EnableMove(EnableMoveInstruction {
7529                    side_ref: SideReference::SideOne,
7530                    move_index: PokemonMoveIndex::M3,
7531                }),
7532                Instruction::Heal(HealInstruction {
7533                    side_ref: SideReference::SideOne,
7534                    heal_amount: 10,
7535                }),
7536                Instruction::Switch(SwitchInstruction {
7537                    side_ref: SideReference::SideOne,
7538                    previous_index: PokemonIndex::P0,
7539                    next_index: PokemonIndex::P1,
7540                }),
7541            ],
7542            ..Default::default()
7543        };
7544
7545        let mut incoming_instructions = StateInstructions::default();
7546        generate_instructions_from_switch(
7547            &mut state,
7548            choice.switch_id,
7549            SideReference::SideOne,
7550            &mut incoming_instructions,
7551        );
7552
7553        assert_eq!(expected_instructions, incoming_instructions);
7554    }
7555
7556    #[test]
7557    fn test_switch_with_regenerator_but_no_damage_taken() {
7558        let mut state: State = State::default();
7559        state.side_one.get_active().ability = Abilities::REGENERATOR;
7560        state.side_one.get_active().base_ability = Abilities::REGENERATOR;
7561        let mut choice = Choice {
7562            ..Default::default()
7563        };
7564        choice.switch_id = PokemonIndex::P1;
7565
7566        let expected_instructions: StateInstructions = StateInstructions {
7567            percentage: 100.0,
7568            instruction_list: vec![Instruction::Switch(SwitchInstruction {
7569                side_ref: SideReference::SideOne,
7570                previous_index: PokemonIndex::P0,
7571                next_index: PokemonIndex::P1,
7572            })],
7573            ..Default::default()
7574        };
7575
7576        let mut incoming_instructions = StateInstructions::default();
7577        generate_instructions_from_switch(
7578            &mut state,
7579            choice.switch_id,
7580            SideReference::SideOne,
7581            &mut incoming_instructions,
7582        );
7583
7584        assert_eq!(expected_instructions, incoming_instructions);
7585    }
7586
7587    #[test]
7588    fn test_fainted_pokemon_with_regenerator_does_not_heal() {
7589        let mut state: State = State::default();
7590        state.side_one.get_active().ability = Abilities::REGENERATOR;
7591        state.side_one.get_active().base_ability = Abilities::REGENERATOR;
7592        state.side_one.get_active().hp = 0;
7593        let mut choice = Choice {
7594            ..Default::default()
7595        };
7596        choice.switch_id = PokemonIndex::P1;
7597
7598        let expected_instructions: StateInstructions = StateInstructions {
7599            percentage: 100.0,
7600            instruction_list: vec![Instruction::Switch(SwitchInstruction {
7601                side_ref: SideReference::SideOne,
7602                previous_index: PokemonIndex::P0,
7603                next_index: PokemonIndex::P1,
7604            })],
7605            ..Default::default()
7606        };
7607
7608        let mut incoming_instructions = StateInstructions::default();
7609        generate_instructions_from_switch(
7610            &mut state,
7611            choice.switch_id,
7612            SideReference::SideOne,
7613            &mut incoming_instructions,
7614        );
7615
7616        assert_eq!(expected_instructions, incoming_instructions);
7617    }
7618
7619    #[test]
7620    fn test_regenerator_only_heals_one_third() {
7621        let mut state: State = State::default();
7622        state.side_one.get_active().ability = Abilities::REGENERATOR;
7623        state.side_one.get_active().base_ability = Abilities::REGENERATOR;
7624        state.side_one.get_active().hp = 3;
7625        let mut choice = Choice {
7626            ..Default::default()
7627        };
7628        choice.switch_id = PokemonIndex::P1;
7629
7630        let expected_instructions: StateInstructions = StateInstructions {
7631            percentage: 100.0,
7632            instruction_list: vec![
7633                Instruction::Heal(HealInstruction {
7634                    side_ref: SideReference::SideOne,
7635                    heal_amount: 33,
7636                }),
7637                Instruction::Switch(SwitchInstruction {
7638                    side_ref: SideReference::SideOne,
7639                    previous_index: PokemonIndex::P0,
7640                    next_index: PokemonIndex::P1,
7641                }),
7642            ],
7643            ..Default::default()
7644        };
7645
7646        let mut incoming_instructions = StateInstructions::default();
7647        generate_instructions_from_switch(
7648            &mut state,
7649            choice.switch_id,
7650            SideReference::SideOne,
7651            &mut incoming_instructions,
7652        );
7653
7654        assert_eq!(expected_instructions, incoming_instructions);
7655    }
7656
7657    #[test]
7658    fn test_naturalcure() {
7659        let mut state: State = State::default();
7660        state.side_one.get_active().ability = Abilities::NATURALCURE;
7661        state.side_one.get_active().base_ability = Abilities::NATURALCURE;
7662        state.side_one.get_active().status = PokemonStatus::PARALYZE;
7663        let mut choice = Choice {
7664            ..Default::default()
7665        };
7666        choice.switch_id = PokemonIndex::P1;
7667
7668        let expected_instructions: StateInstructions = StateInstructions {
7669            percentage: 100.0,
7670            instruction_list: vec![
7671                Instruction::ChangeStatus(ChangeStatusInstruction {
7672                    side_ref: SideReference::SideOne,
7673                    pokemon_index: PokemonIndex::P0,
7674                    old_status: PokemonStatus::PARALYZE,
7675                    new_status: PokemonStatus::NONE,
7676                }),
7677                Instruction::Switch(SwitchInstruction {
7678                    side_ref: SideReference::SideOne,
7679                    previous_index: PokemonIndex::P0,
7680                    next_index: PokemonIndex::P1,
7681                }),
7682            ],
7683            ..Default::default()
7684        };
7685
7686        let mut incoming_instructions = StateInstructions::default();
7687        generate_instructions_from_switch(
7688            &mut state,
7689            choice.switch_id,
7690            SideReference::SideOne,
7691            &mut incoming_instructions,
7692        );
7693
7694        assert_eq!(expected_instructions, incoming_instructions);
7695    }
7696
7697    #[test]
7698    fn test_naturalcure_with_no_status() {
7699        let mut state: State = State::default();
7700        state.side_one.get_active().ability = Abilities::NATURALCURE;
7701        state.side_one.get_active().base_ability = Abilities::NATURALCURE;
7702        state.side_one.get_active().status = PokemonStatus::NONE;
7703        let mut choice = Choice {
7704            ..Default::default()
7705        };
7706        choice.switch_id = PokemonIndex::P1;
7707
7708        let expected_instructions: StateInstructions = StateInstructions {
7709            percentage: 100.0,
7710            instruction_list: vec![Instruction::Switch(SwitchInstruction {
7711                side_ref: SideReference::SideOne,
7712                previous_index: PokemonIndex::P0,
7713                next_index: PokemonIndex::P1,
7714            })],
7715            ..Default::default()
7716        };
7717
7718        let mut incoming_instructions = StateInstructions::default();
7719        generate_instructions_from_switch(
7720            &mut state,
7721            choice.switch_id,
7722            SideReference::SideOne,
7723            &mut incoming_instructions,
7724        );
7725
7726        assert_eq!(expected_instructions, incoming_instructions);
7727    }
7728
7729    #[test]
7730    fn test_switching_into_stealthrock() {
7731        let mut state: State = State::default();
7732        state.side_one.side_conditions.stealth_rock = 1;
7733        let mut choice = Choice {
7734            ..Default::default()
7735        };
7736        choice.switch_id = PokemonIndex::P1;
7737
7738        let expected_instructions: StateInstructions = StateInstructions {
7739            percentage: 100.0,
7740            instruction_list: vec![
7741                Instruction::Switch(SwitchInstruction {
7742                    side_ref: SideReference::SideOne,
7743                    previous_index: PokemonIndex::P0,
7744                    next_index: PokemonIndex::P1,
7745                }),
7746                Instruction::Damage(DamageInstruction {
7747                    side_ref: SideReference::SideOne,
7748                    damage_amount: state.side_one.get_active().hp / 8,
7749                }),
7750            ],
7751            ..Default::default()
7752        };
7753
7754        let mut incoming_instructions = StateInstructions::default();
7755        generate_instructions_from_switch(
7756            &mut state,
7757            choice.switch_id,
7758            SideReference::SideOne,
7759            &mut incoming_instructions,
7760        );
7761
7762        assert_eq!(expected_instructions, incoming_instructions);
7763    }
7764
7765    #[test]
7766    fn test_switching_into_resisted_stealthrock() {
7767        let mut state: State = State::default();
7768        state.side_one.side_conditions.stealth_rock = 1;
7769        state.side_one.pokemon[PokemonIndex::P1].types = (PokemonType::GROUND, PokemonType::NORMAL);
7770        let mut choice = Choice {
7771            ..Default::default()
7772        };
7773        choice.switch_id = PokemonIndex::P1;
7774
7775        let expected_instructions: StateInstructions = StateInstructions {
7776            percentage: 100.0,
7777            instruction_list: vec![
7778                Instruction::Switch(SwitchInstruction {
7779                    side_ref: SideReference::SideOne,
7780                    previous_index: PokemonIndex::P0,
7781                    next_index: PokemonIndex::P1,
7782                }),
7783                Instruction::Damage(DamageInstruction {
7784                    side_ref: SideReference::SideOne,
7785                    damage_amount: state.side_one.get_active().hp / 16,
7786                }),
7787            ],
7788            ..Default::default()
7789        };
7790
7791        let mut incoming_instructions = StateInstructions::default();
7792        generate_instructions_from_switch(
7793            &mut state,
7794            choice.switch_id,
7795            SideReference::SideOne,
7796            &mut incoming_instructions,
7797        );
7798
7799        assert_eq!(expected_instructions, incoming_instructions);
7800    }
7801
7802    #[test]
7803    fn test_switching_into_stealthrock_does_not_overkill() {
7804        let mut state: State = State::default();
7805        state.side_one.side_conditions.stealth_rock = 1;
7806        state.side_one.pokemon[PokemonIndex::P1].hp = 5;
7807        let mut choice = Choice {
7808            ..Default::default()
7809        };
7810        choice.switch_id = PokemonIndex::P1;
7811
7812        let expected_instructions: StateInstructions = StateInstructions {
7813            percentage: 100.0,
7814            instruction_list: vec![
7815                Instruction::Switch(SwitchInstruction {
7816                    side_ref: SideReference::SideOne,
7817                    previous_index: PokemonIndex::P0,
7818                    next_index: PokemonIndex::P1,
7819                }),
7820                Instruction::Damage(DamageInstruction {
7821                    side_ref: SideReference::SideOne,
7822                    damage_amount: 5,
7823                }),
7824            ],
7825            ..Default::default()
7826        };
7827
7828        let mut incoming_instructions = StateInstructions::default();
7829        generate_instructions_from_switch(
7830            &mut state,
7831            choice.switch_id,
7832            SideReference::SideOne,
7833            &mut incoming_instructions,
7834        );
7835
7836        assert_eq!(expected_instructions, incoming_instructions);
7837    }
7838
7839    #[test]
7840    fn test_switching_into_stickyweb() {
7841        let mut state: State = State::default();
7842        state.side_one.side_conditions.sticky_web = 1;
7843        let mut choice = Choice {
7844            ..Default::default()
7845        };
7846        choice.switch_id = PokemonIndex::P1;
7847
7848        let expected_instructions: StateInstructions = StateInstructions {
7849            percentage: 100.0,
7850            instruction_list: vec![
7851                Instruction::Switch(SwitchInstruction {
7852                    side_ref: SideReference::SideOne,
7853                    previous_index: PokemonIndex::P0,
7854                    next_index: PokemonIndex::P1,
7855                }),
7856                Instruction::Boost(BoostInstruction {
7857                    side_ref: SideReference::SideOne,
7858                    stat: PokemonBoostableStat::Speed,
7859                    amount: -1,
7860                }),
7861            ],
7862            ..Default::default()
7863        };
7864
7865        let mut incoming_instructions = StateInstructions::default();
7866        generate_instructions_from_switch(
7867            &mut state,
7868            choice.switch_id,
7869            SideReference::SideOne,
7870            &mut incoming_instructions,
7871        );
7872
7873        assert_eq!(expected_instructions, incoming_instructions);
7874    }
7875
7876    #[test]
7877    fn test_switching_into_stickyweb_with_heavydutyboots() {
7878        let mut state: State = State::default();
7879        state.side_one.side_conditions.sticky_web = 1;
7880        state.side_one.pokemon[PokemonIndex::P1].item = Items::HEAVYDUTYBOOTS;
7881        let mut choice = Choice {
7882            ..Default::default()
7883        };
7884        choice.switch_id = PokemonIndex::P1;
7885
7886        let expected_instructions: StateInstructions = StateInstructions {
7887            percentage: 100.0,
7888            instruction_list: vec![Instruction::Switch(SwitchInstruction {
7889                side_ref: SideReference::SideOne,
7890                previous_index: PokemonIndex::P0,
7891                next_index: PokemonIndex::P1,
7892            })],
7893            ..Default::default()
7894        };
7895
7896        let mut incoming_instructions = StateInstructions::default();
7897        generate_instructions_from_switch(
7898            &mut state,
7899            choice.switch_id,
7900            SideReference::SideOne,
7901            &mut incoming_instructions,
7902        );
7903
7904        assert_eq!(expected_instructions, incoming_instructions);
7905    }
7906
7907    #[test]
7908    fn test_switching_into_stickyweb_with_contrary() {
7909        let mut state: State = State::default();
7910        state.side_one.side_conditions.sticky_web = 1;
7911        state.side_one.pokemon[PokemonIndex::P1].ability = Abilities::CONTRARY;
7912        let mut choice = Choice {
7913            ..Default::default()
7914        };
7915        choice.switch_id = PokemonIndex::P1;
7916
7917        let expected_instructions: StateInstructions = StateInstructions {
7918            percentage: 100.0,
7919            instruction_list: vec![
7920                Instruction::Switch(SwitchInstruction {
7921                    side_ref: SideReference::SideOne,
7922                    previous_index: PokemonIndex::P0,
7923                    next_index: PokemonIndex::P1,
7924                }),
7925                Instruction::Boost(BoostInstruction {
7926                    side_ref: SideReference::SideOne,
7927                    stat: PokemonBoostableStat::Speed,
7928                    amount: 1,
7929                }),
7930            ],
7931            ..Default::default()
7932        };
7933
7934        let mut incoming_instructions = StateInstructions::default();
7935        generate_instructions_from_switch(
7936            &mut state,
7937            choice.switch_id,
7938            SideReference::SideOne,
7939            &mut incoming_instructions,
7940        );
7941
7942        assert_eq!(expected_instructions, incoming_instructions);
7943    }
7944
7945    #[test]
7946    fn test_switching_into_single_layer_toxicspikes() {
7947        let mut state: State = State::default();
7948        state.side_one.side_conditions.toxic_spikes = 1;
7949        let mut choice = Choice {
7950            ..Default::default()
7951        };
7952        choice.switch_id = PokemonIndex::P1;
7953
7954        let expected_instructions: StateInstructions = StateInstructions {
7955            percentage: 100.0,
7956            instruction_list: vec![
7957                Instruction::Switch(SwitchInstruction {
7958                    side_ref: SideReference::SideOne,
7959                    previous_index: PokemonIndex::P0,
7960                    next_index: PokemonIndex::P1,
7961                }),
7962                Instruction::ChangeStatus(ChangeStatusInstruction {
7963                    side_ref: SideReference::SideOne,
7964                    pokemon_index: PokemonIndex::P1,
7965                    old_status: PokemonStatus::NONE,
7966                    new_status: PokemonStatus::POISON,
7967                }),
7968            ],
7969            ..Default::default()
7970        };
7971
7972        let mut incoming_instructions = StateInstructions::default();
7973        generate_instructions_from_switch(
7974            &mut state,
7975            choice.switch_id,
7976            SideReference::SideOne,
7977            &mut incoming_instructions,
7978        );
7979
7980        assert_eq!(expected_instructions, incoming_instructions);
7981    }
7982
7983    #[test]
7984    fn test_switching_into_double_layer_toxicspikes() {
7985        let mut state: State = State::default();
7986        state.side_one.side_conditions.toxic_spikes = 2;
7987        let mut choice = Choice {
7988            ..Default::default()
7989        };
7990        choice.switch_id = PokemonIndex::P1;
7991
7992        let expected_instructions: StateInstructions = StateInstructions {
7993            percentage: 100.0,
7994            instruction_list: vec![
7995                Instruction::Switch(SwitchInstruction {
7996                    side_ref: SideReference::SideOne,
7997                    previous_index: PokemonIndex::P0,
7998                    next_index: PokemonIndex::P1,
7999                }),
8000                Instruction::ChangeStatus(ChangeStatusInstruction {
8001                    side_ref: SideReference::SideOne,
8002                    pokemon_index: PokemonIndex::P1,
8003                    old_status: PokemonStatus::NONE,
8004                    new_status: PokemonStatus::TOXIC,
8005                }),
8006            ],
8007            ..Default::default()
8008        };
8009
8010        let mut incoming_instructions = StateInstructions::default();
8011        generate_instructions_from_switch(
8012            &mut state,
8013            choice.switch_id,
8014            SideReference::SideOne,
8015            &mut incoming_instructions,
8016        );
8017
8018        assert_eq!(expected_instructions, incoming_instructions);
8019    }
8020
8021    #[test]
8022    fn test_switching_into_double_layer_toxicspikes_as_flying_type() {
8023        let mut state: State = State::default();
8024        state.side_one.side_conditions.toxic_spikes = 2;
8025        state.side_one.pokemon[PokemonIndex::P1].types.0 = PokemonType::FLYING;
8026        let mut choice = Choice {
8027            ..Default::default()
8028        };
8029        choice.switch_id = PokemonIndex::P1;
8030
8031        let expected_instructions: StateInstructions = StateInstructions {
8032            percentage: 100.0,
8033            instruction_list: vec![Instruction::Switch(SwitchInstruction {
8034                side_ref: SideReference::SideOne,
8035                previous_index: PokemonIndex::P0,
8036                next_index: PokemonIndex::P1,
8037            })],
8038            ..Default::default()
8039        };
8040
8041        let mut incoming_instructions = StateInstructions::default();
8042        generate_instructions_from_switch(
8043            &mut state,
8044            choice.switch_id,
8045            SideReference::SideOne,
8046            &mut incoming_instructions,
8047        );
8048
8049        assert_eq!(expected_instructions, incoming_instructions);
8050    }
8051
8052    #[test]
8053    fn test_switching_into_double_layer_toxicspikes_as_poison_and_flying_type() {
8054        let mut state: State = State::default();
8055        state.side_one.side_conditions.toxic_spikes = 2;
8056        state.side_one.pokemon[PokemonIndex::P1].types.0 = PokemonType::FLYING;
8057        state.side_one.pokemon[PokemonIndex::P1].types.1 = PokemonType::POISON;
8058        let mut choice = Choice {
8059            ..Default::default()
8060        };
8061        choice.switch_id = PokemonIndex::P1;
8062
8063        let expected_instructions: StateInstructions = StateInstructions {
8064            percentage: 100.0,
8065            instruction_list: vec![Instruction::Switch(SwitchInstruction {
8066                side_ref: SideReference::SideOne,
8067                previous_index: PokemonIndex::P0,
8068                next_index: PokemonIndex::P1,
8069            })],
8070            ..Default::default()
8071        };
8072
8073        let mut incoming_instructions = StateInstructions::default();
8074        generate_instructions_from_switch(
8075            &mut state,
8076            choice.switch_id,
8077            SideReference::SideOne,
8078            &mut incoming_instructions,
8079        );
8080
8081        assert_eq!(expected_instructions, incoming_instructions);
8082    }
8083
8084    #[test]
8085    fn test_switching_in_with_intimidate() {
8086        let mut state: State = State::default();
8087        state.side_one.pokemon[PokemonIndex::P1].ability = Abilities::INTIMIDATE;
8088        let mut choice = Choice {
8089            ..Default::default()
8090        };
8091        choice.switch_id = PokemonIndex::P1;
8092
8093        let expected_instructions: StateInstructions = StateInstructions {
8094            percentage: 100.0,
8095            instruction_list: vec![
8096                Instruction::Switch(SwitchInstruction {
8097                    side_ref: SideReference::SideOne,
8098                    previous_index: PokemonIndex::P0,
8099                    next_index: PokemonIndex::P1,
8100                }),
8101                Instruction::Boost(BoostInstruction {
8102                    side_ref: SideReference::SideTwo,
8103                    stat: PokemonBoostableStat::Attack,
8104                    amount: -1,
8105                }),
8106            ],
8107            ..Default::default()
8108        };
8109
8110        let mut incoming_instructions = StateInstructions::default();
8111        generate_instructions_from_switch(
8112            &mut state,
8113            choice.switch_id,
8114            SideReference::SideOne,
8115            &mut incoming_instructions,
8116        );
8117
8118        assert_eq!(expected_instructions, incoming_instructions);
8119    }
8120
8121    #[test]
8122    fn test_switching_in_with_intimidate_when_opponent_is_already_lowest_atk_boost() {
8123        let mut state: State = State::default();
8124        state.side_one.pokemon[PokemonIndex::P1].ability = Abilities::INTIMIDATE;
8125        state.side_two.attack_boost = -6;
8126        let mut choice = Choice {
8127            ..Default::default()
8128        };
8129        choice.switch_id = PokemonIndex::P1;
8130
8131        let expected_instructions: StateInstructions = StateInstructions {
8132            percentage: 100.0,
8133            instruction_list: vec![Instruction::Switch(SwitchInstruction {
8134                side_ref: SideReference::SideOne,
8135                previous_index: PokemonIndex::P0,
8136                next_index: PokemonIndex::P1,
8137            })],
8138            ..Default::default()
8139        };
8140
8141        let mut incoming_instructions = StateInstructions::default();
8142        generate_instructions_from_switch(
8143            &mut state,
8144            choice.switch_id,
8145            SideReference::SideOne,
8146            &mut incoming_instructions,
8147        );
8148
8149        assert_eq!(expected_instructions, incoming_instructions);
8150    }
8151
8152    #[test]
8153    fn test_switching_in_with_intimidate_versus_clearbody() {
8154        let mut state: State = State::default();
8155        state.side_one.pokemon[PokemonIndex::P1].ability = Abilities::INTIMIDATE;
8156        state.side_two.get_active().ability = Abilities::CLEARBODY;
8157        let mut choice = Choice {
8158            ..Default::default()
8159        };
8160        choice.switch_id = PokemonIndex::P1;
8161
8162        let expected_instructions: StateInstructions = StateInstructions {
8163            percentage: 100.0,
8164            instruction_list: vec![Instruction::Switch(SwitchInstruction {
8165                side_ref: SideReference::SideOne,
8166                previous_index: PokemonIndex::P0,
8167                next_index: PokemonIndex::P1,
8168            })],
8169            ..Default::default()
8170        };
8171
8172        let mut incoming_instructions = StateInstructions::default();
8173        generate_instructions_from_switch(
8174            &mut state,
8175            choice.switch_id,
8176            SideReference::SideOne,
8177            &mut incoming_instructions,
8178        );
8179
8180        assert_eq!(expected_instructions, incoming_instructions);
8181    }
8182
8183    #[test]
8184    fn test_switching_into_double_layer_toxicspikes_as_poison_type() {
8185        let mut state: State = State::default();
8186        state.side_one.pokemon[PokemonIndex::P1].types.0 = PokemonType::POISON;
8187        state.side_one.side_conditions.toxic_spikes = 2;
8188        let mut choice = Choice {
8189            ..Default::default()
8190        };
8191        choice.switch_id = PokemonIndex::P1;
8192
8193        let expected_instructions: StateInstructions = StateInstructions {
8194            percentage: 100.0,
8195            instruction_list: vec![
8196                Instruction::Switch(SwitchInstruction {
8197                    side_ref: SideReference::SideOne,
8198                    previous_index: PokemonIndex::P0,
8199                    next_index: PokemonIndex::P1,
8200                }),
8201                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
8202                    side_ref: SideReference::SideOne,
8203                    side_condition: PokemonSideCondition::ToxicSpikes,
8204                    amount: -2,
8205                }),
8206            ],
8207            ..Default::default()
8208        };
8209
8210        let mut incoming_instructions = StateInstructions::default();
8211        generate_instructions_from_switch(
8212            &mut state,
8213            choice.switch_id,
8214            SideReference::SideOne,
8215            &mut incoming_instructions,
8216        );
8217
8218        assert_eq!(expected_instructions, incoming_instructions);
8219    }
8220
8221    #[test]
8222    fn test_switching_into_stealthrock_and_spikes_does_not_overkill() {
8223        let mut state: State = State::default();
8224        state.side_one.side_conditions.stealth_rock = 1;
8225        state.side_one.side_conditions.spikes = 1;
8226        state.side_one.pokemon[PokemonIndex::P1].hp = 15;
8227        let mut choice = Choice {
8228            ..Default::default()
8229        };
8230        choice.switch_id = PokemonIndex::P1;
8231
8232        let expected_instructions: StateInstructions = StateInstructions {
8233            percentage: 100.0,
8234            instruction_list: vec![
8235                Instruction::Switch(SwitchInstruction {
8236                    side_ref: SideReference::SideOne,
8237                    previous_index: PokemonIndex::P0,
8238                    next_index: PokemonIndex::P1,
8239                }),
8240                Instruction::Damage(DamageInstruction {
8241                    side_ref: SideReference::SideOne,
8242                    damage_amount: 12,
8243                }),
8244                Instruction::Damage(DamageInstruction {
8245                    side_ref: SideReference::SideOne,
8246                    damage_amount: 3,
8247                }),
8248            ],
8249            ..Default::default()
8250        };
8251
8252        let mut incoming_instructions = StateInstructions::default();
8253        generate_instructions_from_switch(
8254            &mut state,
8255            choice.switch_id,
8256            SideReference::SideOne,
8257            &mut incoming_instructions,
8258        );
8259
8260        assert_eq!(expected_instructions, incoming_instructions);
8261    }
8262
8263    #[test]
8264    fn test_switching_into_stealthrock_and_multiple_layers_of_spikes_does_not_overkill() {
8265        let mut state: State = State::default();
8266        state.side_one.side_conditions.stealth_rock = 1;
8267        state.side_one.side_conditions.spikes = 3;
8268        state.side_one.pokemon[PokemonIndex::P1].hp = 25;
8269        let mut choice = Choice {
8270            ..Default::default()
8271        };
8272        choice.switch_id = PokemonIndex::P1;
8273
8274        let expected_instructions: StateInstructions = StateInstructions {
8275            percentage: 100.0,
8276            instruction_list: vec![
8277                Instruction::Switch(SwitchInstruction {
8278                    side_ref: SideReference::SideOne,
8279                    previous_index: PokemonIndex::P0,
8280                    next_index: PokemonIndex::P1,
8281                }),
8282                Instruction::Damage(DamageInstruction {
8283                    side_ref: SideReference::SideOne,
8284                    damage_amount: 12,
8285                }),
8286                Instruction::Damage(DamageInstruction {
8287                    side_ref: SideReference::SideOne,
8288                    damage_amount: 13,
8289                }),
8290            ],
8291            ..Default::default()
8292        };
8293
8294        let mut incoming_instructions = StateInstructions::default();
8295        generate_instructions_from_switch(
8296            &mut state,
8297            choice.switch_id,
8298            SideReference::SideOne,
8299            &mut incoming_instructions,
8300        );
8301
8302        assert_eq!(expected_instructions, incoming_instructions);
8303    }
8304
8305    #[test]
8306    fn test_healthy_pokemon_with_no_prior_instructions() {
8307        let mut state = State::default();
8308        let mut incoming_instructions = StateInstructions::default();
8309
8310        let expected_instructions = StateInstructions::default();
8311
8312        generate_instructions_from_existing_status_conditions(
8313            &mut state,
8314            &SideReference::SideOne,
8315            &Choice::default(),
8316            &mut incoming_instructions,
8317            &mut vec![],
8318        );
8319
8320        assert_eq!(expected_instructions, incoming_instructions);
8321    }
8322
8323    #[test]
8324    fn test_rest_turns_at_3_with_no_prior_instructions() {
8325        let mut state = State::default();
8326        state.side_one.get_active().status = PokemonStatus::SLEEP;
8327        state.side_one.get_active().rest_turns = 3;
8328        let mut incoming_instructions = StateInstructions::default();
8329
8330        let expected_instructions = StateInstructions {
8331            percentage: 100.0,
8332            instruction_list: vec![Instruction::DecrementRestTurns(
8333                DecrementRestTurnsInstruction {
8334                    side_ref: SideReference::SideOne,
8335                },
8336            )],
8337        };
8338
8339        let expected_frozen_instructions: &mut Vec<StateInstructions> = &mut vec![];
8340
8341        let frozen_instructions = &mut vec![];
8342        generate_instructions_from_existing_status_conditions(
8343            &mut state,
8344            &SideReference::SideOne,
8345            &Choice::default(),
8346            &mut incoming_instructions,
8347            frozen_instructions,
8348        );
8349
8350        assert_eq!(expected_instructions, incoming_instructions);
8351        assert_eq!(expected_frozen_instructions, frozen_instructions);
8352    }
8353
8354    #[test]
8355    fn test_rest_turns_at_2_with_no_prior_instructions() {
8356        let mut state = State::default();
8357        state.side_one.get_active().status = PokemonStatus::SLEEP;
8358        state.side_one.get_active().rest_turns = 2;
8359        let mut incoming_instructions = StateInstructions::default();
8360
8361        let expected_instructions = StateInstructions {
8362            percentage: 100.0,
8363            instruction_list: vec![Instruction::DecrementRestTurns(
8364                DecrementRestTurnsInstruction {
8365                    side_ref: SideReference::SideOne,
8366                },
8367            )],
8368        };
8369
8370        let expected_frozen_instructions: &mut Vec<StateInstructions> = &mut vec![];
8371
8372        let frozen_instructions = &mut vec![];
8373
8374        generate_instructions_from_existing_status_conditions(
8375            &mut state,
8376            &SideReference::SideOne,
8377            &Choice::default(),
8378            &mut incoming_instructions,
8379            frozen_instructions,
8380        );
8381
8382        assert_eq!(expected_instructions, incoming_instructions);
8383        assert_eq!(expected_frozen_instructions, frozen_instructions);
8384    }
8385
8386    #[test]
8387    fn test_paralyzed_pokemon_with_no_prior_instructions() {
8388        let mut state = State::default();
8389        state.side_one.get_active().status = PokemonStatus::PARALYZE;
8390        let mut incoming_instructions = StateInstructions::default();
8391
8392        let expected_instructions = StateInstructions {
8393            percentage: 75.0,
8394            instruction_list: vec![],
8395        };
8396
8397        let expected_frozen_instructions = &mut vec![StateInstructions {
8398            percentage: 25.0,
8399            instruction_list: vec![],
8400        }];
8401
8402        let frozen_instructions = &mut vec![];
8403
8404        generate_instructions_from_existing_status_conditions(
8405            &mut state,
8406            &SideReference::SideOne,
8407            &Choice::default(),
8408            &mut incoming_instructions,
8409            frozen_instructions,
8410        );
8411
8412        assert_eq!(expected_instructions, incoming_instructions);
8413        assert_eq!(expected_frozen_instructions, frozen_instructions);
8414    }
8415
8416    #[test]
8417    fn test_confused_pokemon_with_no_prior_instructions() {
8418        let mut state = State::default();
8419        state
8420            .side_one
8421            .volatile_statuses
8422            .insert(PokemonVolatileStatus::CONFUSION);
8423        let mut incoming_instructions = StateInstructions::default();
8424
8425        let expected_instructions = StateInstructions {
8426            percentage: 100.0 * (1.0 - HIT_SELF_IN_CONFUSION_CHANCE),
8427            instruction_list: vec![],
8428        };
8429
8430        let expected_frozen_instructions = &mut vec![StateInstructions {
8431            percentage: 100.0 * (HIT_SELF_IN_CONFUSION_CHANCE),
8432            instruction_list: vec![Instruction::Damage(DamageInstruction {
8433                side_ref: SideReference::SideOne,
8434                damage_amount: 35,
8435            })],
8436        }];
8437
8438        let frozen_instructions = &mut vec![];
8439
8440        generate_instructions_from_existing_status_conditions(
8441            &mut state,
8442            &SideReference::SideOne,
8443            &Choice::default(),
8444            &mut incoming_instructions,
8445            frozen_instructions,
8446        );
8447
8448        assert_eq!(expected_instructions, incoming_instructions);
8449        assert_eq!(expected_frozen_instructions, frozen_instructions);
8450    }
8451
8452    #[test]
8453    fn test_confused_pokemon_with_prior_instruction() {
8454        let mut state = State::default();
8455        state
8456            .side_one
8457            .volatile_statuses
8458            .insert(PokemonVolatileStatus::CONFUSION);
8459        let mut incoming_instructions = StateInstructions::default();
8460        incoming_instructions.instruction_list = vec![Instruction::Damage(DamageInstruction {
8461            side_ref: SideReference::SideOne,
8462            damage_amount: 1,
8463        })];
8464
8465        let expected_instructions = StateInstructions {
8466            percentage: 100.0 * (1.0 - HIT_SELF_IN_CONFUSION_CHANCE),
8467            instruction_list: vec![Instruction::Damage(DamageInstruction {
8468                side_ref: SideReference::SideOne,
8469                damage_amount: 1,
8470            })],
8471        };
8472
8473        let expected_frozen_instructions = &mut vec![StateInstructions {
8474            percentage: 100.0 * HIT_SELF_IN_CONFUSION_CHANCE,
8475            instruction_list: vec![
8476                Instruction::Damage(DamageInstruction {
8477                    side_ref: SideReference::SideOne,
8478                    damage_amount: 1,
8479                }),
8480                Instruction::Damage(DamageInstruction {
8481                    side_ref: SideReference::SideOne,
8482                    damage_amount: 35,
8483                }),
8484            ],
8485        }];
8486
8487        let frozen_instructions = &mut vec![];
8488
8489        generate_instructions_from_existing_status_conditions(
8490            &mut state,
8491            &SideReference::SideOne,
8492            &Choice::default(),
8493            &mut incoming_instructions,
8494            frozen_instructions,
8495        );
8496
8497        assert_eq!(expected_instructions, incoming_instructions);
8498        assert_eq!(expected_frozen_instructions, frozen_instructions);
8499    }
8500
8501    #[test]
8502    fn test_confused_pokemon_with_prior_instruction_does_not_overkill() {
8503        let mut state = State::default();
8504        state
8505            .side_one
8506            .volatile_statuses
8507            .insert(PokemonVolatileStatus::CONFUSION);
8508        let mut incoming_instructions = StateInstructions::default();
8509        state.side_one.get_active().hp = 2;
8510        incoming_instructions.instruction_list = vec![Instruction::Damage(DamageInstruction {
8511            side_ref: SideReference::SideOne,
8512            damage_amount: 1,
8513        })];
8514
8515        let expected_instructions = StateInstructions {
8516            percentage: 100.0 * (1.0 - HIT_SELF_IN_CONFUSION_CHANCE),
8517            instruction_list: vec![Instruction::Damage(DamageInstruction {
8518                side_ref: SideReference::SideOne,
8519                damage_amount: 1,
8520            })],
8521        };
8522
8523        let expected_frozen_instructions = &mut vec![StateInstructions {
8524            percentage: 100.0 * HIT_SELF_IN_CONFUSION_CHANCE,
8525            instruction_list: vec![
8526                Instruction::Damage(DamageInstruction {
8527                    side_ref: SideReference::SideOne,
8528                    damage_amount: 1,
8529                }),
8530                Instruction::Damage(DamageInstruction {
8531                    side_ref: SideReference::SideOne,
8532                    damage_amount: 2,
8533                }),
8534            ],
8535        }];
8536
8537        let frozen_instructions = &mut vec![];
8538
8539        generate_instructions_from_existing_status_conditions(
8540            &mut state,
8541            &SideReference::SideOne,
8542            &Choice::default(),
8543            &mut incoming_instructions,
8544            frozen_instructions,
8545        );
8546
8547        assert_eq!(expected_instructions, incoming_instructions);
8548        assert_eq!(expected_frozen_instructions, frozen_instructions);
8549    }
8550
8551    #[test]
8552    fn test_frozen_pokemon_with_no_prior_instructions() {
8553        let mut state = State::default();
8554        state.side_one.get_active().status = PokemonStatus::FREEZE;
8555        let mut incoming_instructions = StateInstructions::default();
8556
8557        let expected_instructions = StateInstructions {
8558            percentage: 20.0,
8559            instruction_list: vec![Instruction::ChangeStatus(ChangeStatusInstruction {
8560                side_ref: SideReference::SideOne,
8561                pokemon_index: state.side_one.active_index,
8562                old_status: PokemonStatus::FREEZE,
8563                new_status: PokemonStatus::NONE,
8564            })],
8565        };
8566
8567        let expected_frozen_instructions = &mut vec![StateInstructions {
8568            percentage: 80.0,
8569            instruction_list: vec![],
8570        }];
8571
8572        let frozen_instructions = &mut vec![];
8573
8574        generate_instructions_from_existing_status_conditions(
8575            &mut state,
8576            &SideReference::SideOne,
8577            &Choice::default(),
8578            &mut incoming_instructions,
8579            frozen_instructions,
8580        );
8581
8582        assert_eq!(expected_instructions, incoming_instructions);
8583        assert_eq!(expected_frozen_instructions, frozen_instructions);
8584    }
8585
8586    #[test]
8587    fn test_asleep_pokemon_with_no_prior_instructions() {
8588        let mut state = State::default();
8589        state.side_one.get_active().status = PokemonStatus::SLEEP;
8590        state.side_one.get_active().sleep_turns = MAX_SLEEP_TURNS;
8591        let mut incoming_instructions = StateInstructions::default();
8592
8593        let expected_instructions = StateInstructions {
8594            percentage: 100.0,
8595            instruction_list: vec![
8596                Instruction::ChangeStatus(ChangeStatusInstruction {
8597                    side_ref: SideReference::SideOne,
8598                    pokemon_index: state.side_one.active_index,
8599                    old_status: PokemonStatus::SLEEP,
8600                    new_status: PokemonStatus::NONE,
8601                }),
8602                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
8603                    side_ref: SideReference::SideOne,
8604                    pokemon_index: PokemonIndex::P0,
8605                    new_turns: 0,
8606                    previous_turns: MAX_SLEEP_TURNS,
8607                }),
8608            ],
8609        };
8610
8611        let expected_frozen_instructions: &mut Vec<StateInstructions> = &mut vec![];
8612
8613        let frozen_instructions = &mut vec![];
8614
8615        generate_instructions_from_existing_status_conditions(
8616            &mut state,
8617            &SideReference::SideOne,
8618            &Choice::default(),
8619            &mut incoming_instructions,
8620            frozen_instructions,
8621        );
8622
8623        assert_eq!(expected_instructions, incoming_instructions);
8624        assert_eq!(expected_frozen_instructions, frozen_instructions);
8625    }
8626
8627    #[test]
8628    fn test_asleep_waking_up_and_confused() {
8629        let mut state = State::default();
8630        state.side_one.get_active().status = PokemonStatus::SLEEP;
8631        state.side_one.get_active().sleep_turns = MAX_SLEEP_TURNS;
8632        state
8633            .side_one
8634            .volatile_statuses
8635            .insert(PokemonVolatileStatus::CONFUSION);
8636        let mut incoming_instructions = StateInstructions::default();
8637
8638        let expected_instructions = StateInstructions {
8639            percentage: 100.0 * (1.0 - HIT_SELF_IN_CONFUSION_CHANCE),
8640            instruction_list: vec![
8641                Instruction::ChangeStatus(ChangeStatusInstruction {
8642                    side_ref: SideReference::SideOne,
8643                    pokemon_index: state.side_one.active_index,
8644                    old_status: PokemonStatus::SLEEP,
8645                    new_status: PokemonStatus::NONE,
8646                }),
8647                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
8648                    side_ref: SideReference::SideOne,
8649                    pokemon_index: PokemonIndex::P0,
8650                    new_turns: 0,
8651                    previous_turns: MAX_SLEEP_TURNS,
8652                }),
8653            ],
8654        };
8655
8656        let expected_frozen_instructions = &mut vec![StateInstructions {
8657            percentage: 100.0 * HIT_SELF_IN_CONFUSION_CHANCE,
8658            instruction_list: vec![
8659                Instruction::ChangeStatus(ChangeStatusInstruction {
8660                    side_ref: SideReference::SideOne,
8661                    pokemon_index: state.side_one.active_index,
8662                    old_status: PokemonStatus::SLEEP,
8663                    new_status: PokemonStatus::NONE,
8664                }),
8665                Instruction::SetSleepTurns(SetSleepTurnsInstruction {
8666                    side_ref: SideReference::SideOne,
8667                    pokemon_index: PokemonIndex::P0,
8668                    new_turns: 0,
8669                    previous_turns: MAX_SLEEP_TURNS,
8670                }),
8671                Instruction::Damage(DamageInstruction {
8672                    side_ref: SideReference::SideOne,
8673                    damage_amount: 35,
8674                }),
8675            ],
8676        }];
8677
8678        let frozen_instructions = &mut vec![];
8679
8680        generate_instructions_from_existing_status_conditions(
8681            &mut state,
8682            &SideReference::SideOne,
8683            &Choice::default(),
8684            &mut incoming_instructions,
8685            frozen_instructions,
8686        );
8687
8688        assert_eq!(expected_instructions, incoming_instructions);
8689        assert_eq!(expected_frozen_instructions, frozen_instructions);
8690    }
8691
8692    #[test]
8693    fn test_asleep_pokemon_waking_up_with_1_rest_turn() {
8694        let mut state = State::default();
8695        state.side_one.get_active().status = PokemonStatus::SLEEP;
8696        state.side_one.get_active().rest_turns = 1;
8697        let mut incoming_instructions = StateInstructions::default();
8698
8699        let expected_instructions = StateInstructions {
8700            percentage: 100.0,
8701            instruction_list: vec![
8702                Instruction::ChangeStatus(ChangeStatusInstruction {
8703                    side_ref: SideReference::SideOne,
8704                    pokemon_index: state.side_one.active_index,
8705                    old_status: PokemonStatus::SLEEP,
8706                    new_status: PokemonStatus::NONE,
8707                }),
8708                Instruction::DecrementRestTurns(DecrementRestTurnsInstruction {
8709                    side_ref: SideReference::SideOne,
8710                }),
8711            ],
8712        };
8713
8714        let expected_frozen_instructions: &mut Vec<StateInstructions> = &mut vec![];
8715        let frozen_instructions = &mut vec![];
8716
8717        generate_instructions_from_existing_status_conditions(
8718            &mut state,
8719            &SideReference::SideOne,
8720            &Choice::default(),
8721            &mut incoming_instructions,
8722            frozen_instructions,
8723        );
8724
8725        assert_eq!(expected_instructions, incoming_instructions);
8726        assert_eq!(expected_frozen_instructions, frozen_instructions);
8727    }
8728
8729    #[test]
8730    fn test_asleep_pokemon_staying_asleep_with_two_rest_turns() {
8731        let mut state = State::default();
8732        state.side_one.get_active().status = PokemonStatus::SLEEP;
8733        state.side_one.get_active().rest_turns = 1;
8734        let mut incoming_instructions = StateInstructions::default();
8735
8736        let expected_instructions = StateInstructions {
8737            percentage: 100.0,
8738            instruction_list: vec![
8739                Instruction::ChangeStatus(ChangeStatusInstruction {
8740                    side_ref: SideReference::SideOne,
8741                    pokemon_index: state.side_one.active_index,
8742                    old_status: PokemonStatus::SLEEP,
8743                    new_status: PokemonStatus::NONE,
8744                }),
8745                Instruction::DecrementRestTurns(DecrementRestTurnsInstruction {
8746                    side_ref: SideReference::SideOne,
8747                }),
8748            ],
8749        };
8750
8751        let expected_frozen_instructions: &mut Vec<StateInstructions> = &mut vec![];
8752        let frozen_instructions = &mut vec![];
8753
8754        generate_instructions_from_existing_status_conditions(
8755            &mut state,
8756            &SideReference::SideOne,
8757            &Choice::default(),
8758            &mut incoming_instructions,
8759            frozen_instructions,
8760        );
8761
8762        assert_eq!(expected_instructions, incoming_instructions);
8763        assert_eq!(expected_frozen_instructions, frozen_instructions);
8764    }
8765
8766    #[test]
8767    fn test_paralyzed_pokemon_preserves_prior_instructions() {
8768        let mut state = State::default();
8769        state.side_one.get_active().status = PokemonStatus::PARALYZE;
8770        let mut incoming_instructions = StateInstructions::default();
8771        incoming_instructions.instruction_list = vec![Instruction::Damage(DamageInstruction {
8772            side_ref: SideReference::SideOne,
8773            damage_amount: 1,
8774        })];
8775
8776        let expected_instructions = StateInstructions {
8777            percentage: 75.0,
8778            instruction_list: vec![Instruction::Damage(DamageInstruction {
8779                side_ref: SideReference::SideOne,
8780                damage_amount: 1,
8781            })],
8782        };
8783
8784        let expected_frozen_instructions = &mut vec![StateInstructions {
8785            percentage: 25.0,
8786            instruction_list: vec![Instruction::Damage(DamageInstruction {
8787                side_ref: SideReference::SideOne,
8788                damage_amount: 1,
8789            })],
8790        }];
8791
8792        let frozen_instructions = &mut vec![];
8793
8794        generate_instructions_from_existing_status_conditions(
8795            &mut state,
8796            &SideReference::SideOne,
8797            &Choice::default(),
8798            &mut incoming_instructions,
8799            frozen_instructions,
8800        );
8801
8802        assert_eq!(expected_instructions, incoming_instructions);
8803        assert_eq!(expected_frozen_instructions, frozen_instructions);
8804    }
8805
8806    #[test]
8807    fn test_basic_side_two_moves_first() {
8808        let mut state = State::default();
8809        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8810        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8811        state.side_one.get_active().speed = 100;
8812        state.side_two.get_active().speed = 101;
8813
8814        assert_eq!(
8815            SideMovesFirst::SideTwo,
8816            moves_first(
8817                &state,
8818                &side_one_choice,
8819                &side_two_choice,
8820                &mut StateInstructions::default()
8821            )
8822        )
8823    }
8824
8825    #[test]
8826    fn test_custap_berry_when_less_than_25_percent_activates() {
8827        let mut state = State::default();
8828        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8829        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8830        state.side_one.get_active().item = Items::CUSTAPBERRY;
8831        state.side_one.get_active().hp = 24;
8832        state.side_one.get_active().speed = 100;
8833        state.side_two.get_active().speed = 101;
8834
8835        assert_eq!(
8836            SideMovesFirst::SideOne,
8837            moves_first(
8838                &state,
8839                &side_one_choice,
8840                &side_two_choice,
8841                &mut StateInstructions::default()
8842            )
8843        )
8844    }
8845
8846    #[test]
8847    fn test_quarkdrivespe_boost_works() {
8848        let mut state = State::default();
8849        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8850        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8851        state
8852            .side_one
8853            .volatile_statuses
8854            .insert(PokemonVolatileStatus::QUARKDRIVESPE);
8855        state.side_one.get_active().hp = 24;
8856        state.side_one.get_active().speed = 100;
8857        state.side_two.get_active().speed = 101;
8858
8859        assert_eq!(
8860            SideMovesFirst::SideOne,
8861            moves_first(
8862                &state,
8863                &side_one_choice,
8864                &side_two_choice,
8865                &mut StateInstructions::default()
8866            )
8867        )
8868    }
8869
8870    #[test]
8871    fn test_protosynthesisspe_boost_works() {
8872        let mut state = State::default();
8873        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8874        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8875        state
8876            .side_one
8877            .volatile_statuses
8878            .insert(PokemonVolatileStatus::PROTOSYNTHESISSPE);
8879        state.side_one.get_active().hp = 24;
8880        state.side_one.get_active().speed = 100;
8881        state.side_two.get_active().speed = 101;
8882
8883        assert_eq!(
8884            SideMovesFirst::SideOne,
8885            moves_first(
8886                &state,
8887                &side_one_choice,
8888                &side_two_choice,
8889                &mut StateInstructions::default()
8890            )
8891        )
8892    }
8893
8894    #[test]
8895    fn test_custap_berry_when_greater_than_25_percent_does_not_activate() {
8896        let mut state = State::default();
8897        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8898        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8899        state.side_one.get_active().item = Items::CUSTAPBERRY;
8900        state.side_one.get_active().speed = 100;
8901        state.side_two.get_active().speed = 101;
8902
8903        assert_eq!(
8904            SideMovesFirst::SideTwo,
8905            moves_first(
8906                &state,
8907                &side_one_choice,
8908                &side_two_choice,
8909                &mut StateInstructions::default()
8910            )
8911        )
8912    }
8913
8914    #[test]
8915    fn test_custap_berry_does_not_matter_when_opponent_uses_increased_priority_move() {
8916        let mut state = State::default();
8917        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8918        let side_two_choice = MOVES.get(&Choices::QUICKATTACK).unwrap().to_owned();
8919        state.side_one.get_active().item = Items::CUSTAPBERRY;
8920        state.side_one.get_active().hp = 24;
8921        state.side_one.get_active().speed = 100;
8922        state.side_two.get_active().speed = 101;
8923
8924        assert_eq!(
8925            SideMovesFirst::SideTwo,
8926            moves_first(
8927                &state,
8928                &side_one_choice,
8929                &side_two_choice,
8930                &mut StateInstructions::default()
8931            )
8932        )
8933    }
8934
8935    #[test]
8936    fn test_slowstart_halves_effective_speed() {
8937        let mut state = State::default();
8938        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8939        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8940        state.side_one.get_active().speed = 100;
8941        state.side_two.get_active().speed = 101;
8942        state
8943            .side_two
8944            .volatile_statuses
8945            .insert(PokemonVolatileStatus::SLOWSTART);
8946
8947        assert_eq!(
8948            SideMovesFirst::SideOne,
8949            moves_first(
8950                &state,
8951                &side_one_choice,
8952                &side_two_choice,
8953                &mut StateInstructions::default()
8954            )
8955        )
8956    }
8957
8958    #[test]
8959    fn test_basic_side_one_moves_first() {
8960        let mut state = State::default();
8961        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8962        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8963        state.side_one.get_active().speed = 101;
8964        state.side_two.get_active().speed = 100;
8965
8966        assert_eq!(
8967            SideMovesFirst::SideOne,
8968            moves_first(
8969                &state,
8970                &side_one_choice,
8971                &side_two_choice,
8972                &mut StateInstructions::default()
8973            )
8974        )
8975    }
8976
8977    #[test]
8978    fn test_paralysis_reduces_effective_speed() {
8979        let mut state = State::default();
8980        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8981        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
8982
8983        state.side_one.get_active().status = PokemonStatus::PARALYZE;
8984        state.side_one.get_active().speed = 101;
8985        state.side_two.get_active().speed = 100;
8986
8987        assert_eq!(
8988            SideMovesFirst::SideTwo,
8989            moves_first(
8990                &state,
8991                &side_one_choice,
8992                &side_two_choice,
8993                &mut StateInstructions::default()
8994            )
8995        )
8996    }
8997
8998    #[test]
8999    #[cfg(any(feature = "gen7", feature = "gen8", feature = "gen9"))]
9000    fn test_later_gen_speed_cutting_in_half() {
9001        let mut state = State::default();
9002        state.side_one.get_active().status = PokemonStatus::PARALYZE;
9003        state.side_one.get_active().speed = 100;
9004
9005        assert_eq!(50, get_effective_speed(&state, &SideReference::SideOne))
9006    }
9007
9008    #[test]
9009    #[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5", feature = "gen6"))]
9010    fn test_earlier_gen_speed_cutting_by_75_percent() {
9011        let mut state = State::default();
9012        state.side_one.get_active().status = PokemonStatus::PARALYZE;
9013        state.side_one.get_active().speed = 100;
9014
9015        assert_eq!(25, get_effective_speed(&state, &SideReference::SideOne))
9016    }
9017
9018    #[test]
9019    fn test_choicescarf_multiplying_speed() {
9020        let mut state = State::default();
9021        state.side_one.get_active().speed = 100;
9022        state.side_one.get_active().item = Items::CHOICESCARF;
9023
9024        assert_eq!(150, get_effective_speed(&state, &SideReference::SideOne))
9025    }
9026
9027    #[test]
9028    fn test_iron_ball_halving_speed() {
9029        let mut state = State::default();
9030        state.side_one.get_active().speed = 100;
9031        state.side_one.get_active().item = Items::IRONBALL;
9032
9033        assert_eq!(50, get_effective_speed(&state, &SideReference::SideOne))
9034    }
9035
9036    #[test]
9037    fn test_speed_tie_goes_to_side_two() {
9038        let mut state = State::default();
9039        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
9040        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
9041        state.side_one.get_active().speed = 100;
9042        state.side_two.get_active().speed = 100;
9043
9044        assert_eq!(
9045            SideMovesFirst::SpeedTie,
9046            moves_first(
9047                &state,
9048                &side_one_choice,
9049                &side_two_choice,
9050                &mut StateInstructions::default()
9051            )
9052        )
9053    }
9054
9055    #[test]
9056    fn test_higher_priority_ignores_speed_diff() {
9057        let mut state = State::default();
9058        let side_one_choice = MOVES.get(&Choices::QUICKATTACK).unwrap().to_owned();
9059        let side_two_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
9060        state.side_one.get_active().speed = 100;
9061        state.side_two.get_active().speed = 101;
9062
9063        assert_eq!(
9064            SideMovesFirst::SideOne,
9065            moves_first(
9066                &state,
9067                &side_one_choice,
9068                &side_two_choice,
9069                &mut StateInstructions::default()
9070            )
9071        )
9072    }
9073
9074    #[test]
9075    fn test_side_two_higher_priority_ignores_speed_diff() {
9076        let mut state = State::default();
9077        let side_one_choice = MOVES.get(&Choices::TACKLE).unwrap().to_owned();
9078        let side_two_choice = MOVES.get(&Choices::QUICKATTACK).unwrap().to_owned();
9079        state.side_one.get_active().speed = 101;
9080        state.side_two.get_active().speed = 100;
9081
9082        assert_eq!(
9083            SideMovesFirst::SideTwo,
9084            moves_first(
9085                &state,
9086                &side_one_choice,
9087                &side_two_choice,
9088                &mut StateInstructions::default()
9089            )
9090        )
9091    }
9092
9093    #[test]
9094    fn test_both_higher_priority_defaults_back_to_speed() {
9095        let mut state = State::default();
9096        let side_one_choice = MOVES.get(&Choices::QUICKATTACK).unwrap().to_owned();
9097        let side_two_choice = MOVES.get(&Choices::QUICKATTACK).unwrap().to_owned();
9098        state.side_one.get_active().speed = 101;
9099        state.side_two.get_active().speed = 100;
9100
9101        assert_eq!(
9102            SideMovesFirst::SideOne,
9103            moves_first(
9104                &state,
9105                &side_one_choice,
9106                &side_two_choice,
9107                &mut StateInstructions::default()
9108            )
9109        )
9110    }
9111
9112    #[test]
9113    fn test_switch_always_goes_first() {
9114        let mut state = State::default();
9115        let mut side_one_choice = MOVES.get(&Choices::SPLASH).unwrap().to_owned();
9116        side_one_choice.category = MoveCategory::Switch;
9117        let side_two_choice = MOVES.get(&Choices::QUICKATTACK).unwrap().to_owned();
9118        state.side_one.get_active().speed = 99;
9119        state.side_two.get_active().speed = 100;
9120
9121        assert_eq!(
9122            SideMovesFirst::SideOne,
9123            moves_first(
9124                &state,
9125                &side_one_choice,
9126                &side_two_choice,
9127                &mut StateInstructions::default()
9128            )
9129        )
9130    }
9131
9132    #[test]
9133    fn test_double_switch_checks_higher_speed() {
9134        let mut state = State::default();
9135        let mut side_one_choice = MOVES.get(&Choices::SPLASH).unwrap().to_owned();
9136        side_one_choice.category = MoveCategory::Switch;
9137        let mut side_two_choice = MOVES.get(&Choices::SPLASH).unwrap().to_owned();
9138        side_two_choice.category = MoveCategory::Switch;
9139
9140        state.side_one.get_active().speed = 99;
9141        state.side_two.get_active().speed = 100;
9142
9143        assert_eq!(
9144            SideMovesFirst::SideTwo,
9145            moves_first(
9146                &state,
9147                &side_one_choice,
9148                &side_two_choice,
9149                &mut StateInstructions::default()
9150            )
9151        )
9152    }
9153
9154    #[test]
9155    fn test_pursuit_goes_before_switch() {
9156        let mut state = State::default();
9157        let side_one_choice = MOVES.get(&Choices::PURSUIT).unwrap().to_owned();
9158        let mut side_two_choice = MOVES.get(&Choices::SPLASH).unwrap().to_owned();
9159        side_two_choice.category = MoveCategory::Switch;
9160
9161        state.side_one.get_active().speed = 50;
9162        state.side_two.get_active().speed = 100;
9163
9164        assert_eq!(
9165            SideMovesFirst::SideOne,
9166            moves_first(
9167                &state,
9168                &side_one_choice,
9169                &side_two_choice,
9170                &mut StateInstructions::default()
9171            )
9172        )
9173    }
9174
9175    #[test]
9176    fn test_end_of_turn_hail_damage() {
9177        let mut state = State::default();
9178        state.weather.weather_type = Weather::HAIL;
9179
9180        let mut incoming_instructions = StateInstructions::default();
9181        add_end_of_turn_instructions(
9182            &mut state,
9183            &mut incoming_instructions,
9184            &SideReference::SideOne,
9185        );
9186
9187        let expected_instructions = StateInstructions {
9188            percentage: 100.0,
9189            instruction_list: vec![
9190                Instruction::Damage(DamageInstruction {
9191                    side_ref: SideReference::SideOne,
9192                    damage_amount: 6,
9193                }),
9194                Instruction::Damage(DamageInstruction {
9195                    side_ref: SideReference::SideTwo,
9196                    damage_amount: 6,
9197                }),
9198            ],
9199        };
9200
9201        assert_eq!(expected_instructions, incoming_instructions)
9202    }
9203
9204    #[test]
9205    fn test_end_of_turn_hail_damage_against_ice_type() {
9206        let mut state = State::default();
9207        state.weather.weather_type = Weather::HAIL;
9208        state.side_two.get_active().types.0 = PokemonType::ICE;
9209
9210        let mut incoming_instructions = StateInstructions::default();
9211        add_end_of_turn_instructions(
9212            &mut state,
9213            &mut incoming_instructions,
9214            &SideReference::SideOne,
9215        );
9216
9217        let expected_instructions = StateInstructions {
9218            percentage: 100.0,
9219            instruction_list: vec![
9220                // no damage to side_two
9221                Instruction::Damage(DamageInstruction {
9222                    side_ref: SideReference::SideOne,
9223                    damage_amount: 6,
9224                }),
9225            ],
9226        };
9227
9228        assert_eq!(expected_instructions, incoming_instructions)
9229    }
9230
9231    #[test]
9232    fn test_end_of_turn_sand_damage() {
9233        let mut state = State::default();
9234        state.weather.weather_type = Weather::SAND;
9235
9236        let mut incoming_instructions = StateInstructions::default();
9237        add_end_of_turn_instructions(
9238            &mut state,
9239            &mut incoming_instructions,
9240            &SideReference::SideOne,
9241        );
9242
9243        let expected_instructions = StateInstructions {
9244            percentage: 100.0,
9245            instruction_list: vec![
9246                Instruction::Damage(DamageInstruction {
9247                    side_ref: SideReference::SideOne,
9248                    damage_amount: 6,
9249                }),
9250                Instruction::Damage(DamageInstruction {
9251                    side_ref: SideReference::SideTwo,
9252                    damage_amount: 6,
9253                }),
9254            ],
9255        };
9256
9257        assert_eq!(expected_instructions, incoming_instructions)
9258    }
9259
9260    #[test]
9261    fn test_end_of_turn_sand_damage_against_ground_type() {
9262        let mut state = State::default();
9263        state.weather.weather_type = Weather::SAND;
9264        state.side_two.get_active().types.0 = PokemonType::GROUND;
9265
9266        let mut incoming_instructions = StateInstructions::default();
9267        add_end_of_turn_instructions(
9268            &mut state,
9269            &mut incoming_instructions,
9270            &SideReference::SideOne,
9271        );
9272
9273        let expected_instructions = StateInstructions {
9274            percentage: 100.0,
9275
9276            // no damage to side_two
9277            instruction_list: vec![Instruction::Damage(DamageInstruction {
9278                side_ref: SideReference::SideOne,
9279                damage_amount: 6,
9280            })],
9281        };
9282
9283        assert_eq!(expected_instructions, incoming_instructions)
9284    }
9285
9286    #[test]
9287    fn test_hail_does_not_overkill() {
9288        let mut state = State::default();
9289        state.weather.weather_type = Weather::HAIL;
9290        state.side_one.get_active().hp = 3;
9291
9292        let mut incoming_instructions = StateInstructions::default();
9293        add_end_of_turn_instructions(
9294            &mut state,
9295            &mut incoming_instructions,
9296            &SideReference::SideOne,
9297        );
9298
9299        let expected_instructions = StateInstructions {
9300            percentage: 100.0,
9301            instruction_list: vec![
9302                Instruction::Damage(DamageInstruction {
9303                    side_ref: SideReference::SideOne,
9304                    damage_amount: 3,
9305                }),
9306                Instruction::Damage(DamageInstruction {
9307                    side_ref: SideReference::SideTwo,
9308                    damage_amount: 6,
9309                }),
9310            ],
9311        };
9312
9313        assert_eq!(expected_instructions, incoming_instructions)
9314    }
9315
9316    #[test]
9317    fn test_fainted_pkmn_does_not_take_hail_dmg() {
9318        let mut state = State::default();
9319        state.weather.weather_type = Weather::HAIL;
9320        state.side_one.get_active().hp = 0;
9321
9322        let mut incoming_instructions = StateInstructions::default();
9323        add_end_of_turn_instructions(
9324            &mut state,
9325            &mut incoming_instructions,
9326            &SideReference::SideOne,
9327        );
9328
9329        let expected_instructions = StateInstructions {
9330            percentage: 100.0,
9331            instruction_list: vec![Instruction::Damage(DamageInstruction {
9332                side_ref: SideReference::SideTwo,
9333                damage_amount: 6,
9334            })],
9335        };
9336
9337        assert_eq!(expected_instructions, incoming_instructions)
9338    }
9339
9340    #[test]
9341    #[cfg(not(feature = "gen4"))]
9342    fn test_wished_pokemon_gets_healed() {
9343        let mut state = State::default();
9344        state.side_one.wish = (1, 5);
9345        state.side_one.get_active().hp = 50;
9346
9347        let mut incoming_instructions = StateInstructions::default();
9348        add_end_of_turn_instructions(
9349            &mut state,
9350            &mut incoming_instructions,
9351            &SideReference::SideOne,
9352        );
9353
9354        let expected_instructions = StateInstructions {
9355            percentage: 100.0,
9356            instruction_list: vec![
9357                Instruction::Heal(HealInstruction {
9358                    side_ref: SideReference::SideOne,
9359                    heal_amount: 5,
9360                }),
9361                Instruction::DecrementWish(DecrementWishInstruction {
9362                    side_ref: SideReference::SideOne,
9363                }),
9364            ],
9365        };
9366
9367        assert_eq!(expected_instructions, incoming_instructions)
9368    }
9369
9370    #[test]
9371    fn test_wish_does_not_overheal() {
9372        let mut state = State::default();
9373        state.side_one.wish = (1, 50);
9374        state.side_one.get_active().hp = 95;
9375
9376        let mut incoming_instructions = StateInstructions::default();
9377        add_end_of_turn_instructions(
9378            &mut state,
9379            &mut incoming_instructions,
9380            &SideReference::SideOne,
9381        );
9382
9383        let expected_instructions = StateInstructions {
9384            percentage: 100.0,
9385            instruction_list: vec![
9386                Instruction::Heal(HealInstruction {
9387                    side_ref: SideReference::SideOne,
9388                    heal_amount: 5,
9389                }),
9390                Instruction::DecrementWish(DecrementWishInstruction {
9391                    side_ref: SideReference::SideOne,
9392                }),
9393            ],
9394        };
9395
9396        assert_eq!(expected_instructions, incoming_instructions)
9397    }
9398
9399    #[test]
9400    fn test_wish_does_nothing_when_maxhp() {
9401        let mut state = State::default();
9402        state.side_one.wish = (1, 50);
9403        state.side_one.get_active().hp = 100;
9404
9405        let mut incoming_instructions = StateInstructions::default();
9406        add_end_of_turn_instructions(
9407            &mut state,
9408            &mut incoming_instructions,
9409            &SideReference::SideOne,
9410        );
9411
9412        let expected_instructions = StateInstructions {
9413            percentage: 100.0,
9414            instruction_list: vec![Instruction::DecrementWish(DecrementWishInstruction {
9415                side_ref: SideReference::SideOne,
9416            })],
9417        };
9418
9419        assert_eq!(expected_instructions, incoming_instructions)
9420    }
9421
9422    #[test]
9423    fn test_wish_does_nothing_when_fainted() {
9424        let mut state = State::default();
9425        state.side_one.wish = (1, 50);
9426        state.side_one.get_active().hp = 0;
9427
9428        let mut incoming_instructions = StateInstructions::default();
9429        add_end_of_turn_instructions(
9430            &mut state,
9431            &mut incoming_instructions,
9432            &SideReference::SideOne,
9433        );
9434
9435        let expected_instructions = StateInstructions {
9436            percentage: 100.0,
9437            instruction_list: vec![Instruction::DecrementWish(DecrementWishInstruction {
9438                side_ref: SideReference::SideOne,
9439            })],
9440        };
9441
9442        assert_eq!(expected_instructions, incoming_instructions)
9443    }
9444
9445    #[test]
9446    fn test_wish_at_2_does_not_heal() {
9447        let mut state = State::default();
9448        state.side_one.wish = (2, 50);
9449        state.side_one.get_active().hp = 95;
9450
9451        let mut incoming_instructions = StateInstructions::default();
9452        add_end_of_turn_instructions(
9453            &mut state,
9454            &mut incoming_instructions,
9455            &SideReference::SideOne,
9456        );
9457
9458        let expected_instructions = StateInstructions {
9459            percentage: 100.0,
9460            instruction_list: vec![Instruction::DecrementWish(DecrementWishInstruction {
9461                side_ref: SideReference::SideOne,
9462            })],
9463        };
9464
9465        assert_eq!(expected_instructions, incoming_instructions)
9466    }
9467
9468    #[test]
9469    fn test_leftovers_heals_at_end_of_turn() {
9470        let mut state = State::default();
9471        state.side_one.get_active().hp = 50;
9472        state.side_one.get_active().item = Items::LEFTOVERS;
9473
9474        let mut incoming_instructions = StateInstructions::default();
9475        add_end_of_turn_instructions(
9476            &mut state,
9477            &mut incoming_instructions,
9478            &SideReference::SideOne,
9479        );
9480
9481        let expected_instructions = StateInstructions {
9482            percentage: 100.0,
9483            instruction_list: vec![Instruction::Heal(HealInstruction {
9484                side_ref: SideReference::SideOne,
9485                heal_amount: 6,
9486            })],
9487        };
9488
9489        assert_eq!(expected_instructions, incoming_instructions)
9490    }
9491
9492    #[test]
9493    fn test_leftovers_does_not_overheal() {
9494        let mut state = State::default();
9495        state.side_one.get_active().hp = 99;
9496        state.side_one.get_active().item = Items::LEFTOVERS;
9497
9498        let mut incoming_instructions = StateInstructions::default();
9499        add_end_of_turn_instructions(
9500            &mut state,
9501            &mut incoming_instructions,
9502            &SideReference::SideOne,
9503        );
9504
9505        let expected_instructions = StateInstructions {
9506            percentage: 100.0,
9507            instruction_list: vec![Instruction::Heal(HealInstruction {
9508                side_ref: SideReference::SideOne,
9509                heal_amount: 1,
9510            })],
9511        };
9512
9513        assert_eq!(expected_instructions, incoming_instructions)
9514    }
9515
9516    #[test]
9517    fn test_leftovers_generates_no_instruction_at_maxhp() {
9518        let mut state = State::default();
9519        state.side_one.get_active().hp = 100;
9520        state.side_one.get_active().item = Items::LEFTOVERS;
9521
9522        let mut incoming_instructions = StateInstructions::default();
9523        add_end_of_turn_instructions(
9524            &mut state,
9525            &mut incoming_instructions,
9526            &SideReference::SideOne,
9527        );
9528
9529        let expected_instructions = StateInstructions {
9530            percentage: 100.0,
9531            instruction_list: vec![],
9532        };
9533
9534        assert_eq!(expected_instructions, incoming_instructions)
9535    }
9536
9537    #[test]
9538    fn test_leftovers_generates_no_instruction_when_fainted() {
9539        let mut state = State::default();
9540        state.side_one.get_active().hp = 0;
9541        state.side_one.get_active().item = Items::LEFTOVERS;
9542
9543        let mut incoming_instructions = StateInstructions::default();
9544        add_end_of_turn_instructions(
9545            &mut state,
9546            &mut incoming_instructions,
9547            &SideReference::SideOne,
9548        );
9549
9550        let expected_instructions = StateInstructions {
9551            percentage: 100.0,
9552            instruction_list: vec![],
9553        };
9554
9555        assert_eq!(expected_instructions, incoming_instructions)
9556    }
9557
9558    #[test]
9559    fn test_blacksludge_heal_as_poison_type() {
9560        let mut state = State::default();
9561        state.side_one.get_active().hp = 50;
9562        state.side_one.get_active().item = Items::BLACKSLUDGE;
9563        state.side_one.get_active().types.0 = PokemonType::POISON;
9564
9565        let mut incoming_instructions = StateInstructions::default();
9566        add_end_of_turn_instructions(
9567            &mut state,
9568            &mut incoming_instructions,
9569            &SideReference::SideOne,
9570        );
9571
9572        let expected_instructions = StateInstructions {
9573            percentage: 100.0,
9574            instruction_list: vec![Instruction::Heal(HealInstruction {
9575                side_ref: SideReference::SideOne,
9576                heal_amount: 6,
9577            })],
9578        };
9579
9580        assert_eq!(expected_instructions, incoming_instructions)
9581    }
9582
9583    #[test]
9584    fn test_blacksludge_damage_as_non_poison_type() {
9585        let mut state = State::default();
9586        state.side_one.get_active().hp = 50;
9587        state.side_one.get_active().item = Items::BLACKSLUDGE;
9588
9589        let mut incoming_instructions = StateInstructions::default();
9590        add_end_of_turn_instructions(
9591            &mut state,
9592            &mut incoming_instructions,
9593            &SideReference::SideOne,
9594        );
9595
9596        let expected_instructions = StateInstructions {
9597            percentage: 100.0,
9598            instruction_list: vec![Instruction::Damage(DamageInstruction {
9599                side_ref: SideReference::SideOne,
9600                damage_amount: 6,
9601            })],
9602        };
9603
9604        assert_eq!(expected_instructions, incoming_instructions)
9605    }
9606
9607    #[test]
9608    fn test_blacksludge_does_not_overheal() {
9609        let mut state = State::default();
9610        state.side_one.get_active().hp = 99;
9611        state.side_one.get_active().item = Items::BLACKSLUDGE;
9612        state.side_one.get_active().types.0 = PokemonType::POISON;
9613
9614        let mut incoming_instructions = StateInstructions::default();
9615        add_end_of_turn_instructions(
9616            &mut state,
9617            &mut incoming_instructions,
9618            &SideReference::SideOne,
9619        );
9620
9621        let expected_instructions = StateInstructions {
9622            percentage: 100.0,
9623            instruction_list: vec![Instruction::Heal(HealInstruction {
9624                side_ref: SideReference::SideOne,
9625                heal_amount: 1,
9626            })],
9627        };
9628
9629        assert_eq!(expected_instructions, incoming_instructions)
9630    }
9631
9632    #[test]
9633    fn test_flameorb_end_of_turn_burn() {
9634        let mut state = State::default();
9635        state.side_one.get_active().item = Items::FLAMEORB;
9636
9637        let mut incoming_instructions = StateInstructions::default();
9638        add_end_of_turn_instructions(
9639            &mut state,
9640            &mut incoming_instructions,
9641            &SideReference::SideOne,
9642        );
9643
9644        let expected_instructions = StateInstructions {
9645            percentage: 100.0,
9646            instruction_list: vec![Instruction::ChangeStatus(ChangeStatusInstruction {
9647                side_ref: SideReference::SideOne,
9648                pokemon_index: PokemonIndex::P0,
9649                old_status: PokemonStatus::NONE,
9650                new_status: PokemonStatus::BURN,
9651            })],
9652        };
9653
9654        assert_eq!(expected_instructions, incoming_instructions)
9655    }
9656
9657    #[test]
9658    fn test_fire_type_cannot_be_burned_by_flameorb() {
9659        let mut state = State::default();
9660        state.side_one.get_active().item = Items::FLAMEORB;
9661        state.side_one.get_active().types.0 = PokemonType::FIRE;
9662        let mut incoming_instructions = StateInstructions::default();
9663        add_end_of_turn_instructions(
9664            &mut state,
9665            &mut incoming_instructions,
9666            &SideReference::SideOne,
9667        );
9668
9669        let expected_instructions = StateInstructions {
9670            percentage: 100.0,
9671            instruction_list: vec![],
9672        };
9673
9674        assert_eq!(expected_instructions, incoming_instructions)
9675    }
9676
9677    #[test]
9678    fn test_toxicorb_applies_status() {
9679        let mut state = State::default();
9680        state.side_one.get_active().item = Items::TOXICORB;
9681
9682        let mut incoming_instructions = StateInstructions::default();
9683        add_end_of_turn_instructions(
9684            &mut state,
9685            &mut incoming_instructions,
9686            &SideReference::SideOne,
9687        );
9688
9689        let expected_instructions = StateInstructions {
9690            percentage: 100.0,
9691            instruction_list: vec![Instruction::ChangeStatus(ChangeStatusInstruction {
9692                side_ref: SideReference::SideOne,
9693                pokemon_index: PokemonIndex::P0,
9694                old_status: PokemonStatus::NONE,
9695                new_status: PokemonStatus::TOXIC,
9696            })],
9697        };
9698
9699        assert_eq!(expected_instructions, incoming_instructions)
9700    }
9701
9702    #[test]
9703    fn test_toxicorb_does_not_apply_to_poison_type() {
9704        let mut state = State::default();
9705        state.side_one.get_active().item = Items::TOXICORB;
9706        state.side_one.get_active().types.0 = PokemonType::POISON;
9707
9708        let mut incoming_instructions = StateInstructions::default();
9709        add_end_of_turn_instructions(
9710            &mut state,
9711            &mut incoming_instructions,
9712            &SideReference::SideOne,
9713        );
9714
9715        let expected_instructions = StateInstructions {
9716            percentage: 100.0,
9717            instruction_list: vec![],
9718        };
9719
9720        assert_eq!(expected_instructions, incoming_instructions)
9721    }
9722
9723    #[test]
9724    fn test_poisonheal_heals_at_end_of_turn() {
9725        let mut state = State::default();
9726        state.side_one.get_active().ability = Abilities::POISONHEAL;
9727        state.side_one.get_active().status = PokemonStatus::POISON;
9728        state.side_one.get_active().hp = 50;
9729
9730        let mut incoming_instructions = StateInstructions::default();
9731        add_end_of_turn_instructions(
9732            &mut state,
9733            &mut incoming_instructions,
9734            &SideReference::SideOne,
9735        );
9736
9737        let expected_instructions = StateInstructions {
9738            percentage: 100.0,
9739            instruction_list: vec![Instruction::Heal(HealInstruction {
9740                side_ref: SideReference::SideOne,
9741                heal_amount: 12,
9742            })],
9743        };
9744
9745        assert_eq!(expected_instructions, incoming_instructions)
9746    }
9747
9748    #[test]
9749    fn test_poisonheal_while_toxiced_still_increases_toxic_count() {
9750        let mut state = State::default();
9751        state.side_one.get_active().ability = Abilities::POISONHEAL;
9752        state.side_one.get_active().status = PokemonStatus::TOXIC;
9753        state.side_one.get_active().hp = 50;
9754
9755        let mut incoming_instructions = StateInstructions::default();
9756        add_end_of_turn_instructions(
9757            &mut state,
9758            &mut incoming_instructions,
9759            &SideReference::SideOne,
9760        );
9761
9762        let expected_instructions = StateInstructions {
9763            percentage: 100.0,
9764            instruction_list: vec![
9765                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
9766                    side_ref: SideReference::SideOne,
9767                    side_condition: PokemonSideCondition::ToxicCount,
9768                    amount: 1,
9769                }),
9770                Instruction::Heal(HealInstruction {
9771                    side_ref: SideReference::SideOne,
9772                    heal_amount: 12,
9773                }),
9774            ],
9775        };
9776
9777        assert_eq!(expected_instructions, incoming_instructions)
9778    }
9779
9780    #[test]
9781    fn test_poisonheal_does_not_overheal() {
9782        let mut state = State::default();
9783        state.side_one.get_active().ability = Abilities::POISONHEAL;
9784        state.side_one.get_active().status = PokemonStatus::POISON;
9785        state.side_one.get_active().hp = 99;
9786
9787        let mut incoming_instructions = StateInstructions::default();
9788        add_end_of_turn_instructions(
9789            &mut state,
9790            &mut incoming_instructions,
9791            &SideReference::SideOne,
9792        );
9793
9794        let expected_instructions = StateInstructions {
9795            percentage: 100.0,
9796            instruction_list: vec![Instruction::Heal(HealInstruction {
9797                side_ref: SideReference::SideOne,
9798                heal_amount: 1,
9799            })],
9800        };
9801
9802        assert_eq!(expected_instructions, incoming_instructions)
9803    }
9804
9805    #[test]
9806    fn test_poisonheal_does_nothing_at_maxhp() {
9807        let mut state = State::default();
9808        state.side_one.get_active().ability = Abilities::POISONHEAL;
9809        state.side_one.get_active().status = PokemonStatus::POISON;
9810
9811        let mut incoming_instructions = StateInstructions::default();
9812        add_end_of_turn_instructions(
9813            &mut state,
9814            &mut incoming_instructions,
9815            &SideReference::SideOne,
9816        );
9817
9818        let expected_instructions = StateInstructions {
9819            percentage: 100.0,
9820            instruction_list: vec![],
9821        };
9822
9823        assert_eq!(expected_instructions, incoming_instructions)
9824    }
9825
9826    #[test]
9827    fn test_speedboost() {
9828        let mut state = State::default();
9829        state.side_one.get_active().ability = Abilities::SPEEDBOOST;
9830
9831        let mut incoming_instructions = StateInstructions::default();
9832        add_end_of_turn_instructions(
9833            &mut state,
9834            &mut incoming_instructions,
9835            &SideReference::SideOne,
9836        );
9837
9838        let expected_instructions = StateInstructions {
9839            percentage: 100.0,
9840            instruction_list: vec![Instruction::Boost(BoostInstruction {
9841                side_ref: SideReference::SideOne,
9842                stat: PokemonBoostableStat::Speed,
9843                amount: 1,
9844            })],
9845        };
9846
9847        assert_eq!(expected_instructions, incoming_instructions)
9848    }
9849
9850    #[test]
9851    fn test_speedboost_does_not_boost_beyond_6() {
9852        let mut state = State::default();
9853        state.side_one.get_active().ability = Abilities::SPEEDBOOST;
9854        state.side_one.speed_boost = 6;
9855
9856        let mut incoming_instructions = StateInstructions::default();
9857        add_end_of_turn_instructions(
9858            &mut state,
9859            &mut incoming_instructions,
9860            &SideReference::SideOne,
9861        );
9862
9863        let expected_instructions = StateInstructions {
9864            percentage: 100.0,
9865            instruction_list: vec![],
9866        };
9867
9868        assert_eq!(expected_instructions, incoming_instructions)
9869    }
9870
9871    #[test]
9872    fn test_end_of_turn_poison_damage() {
9873        let mut state = State::default();
9874        state.side_one.get_active().status = PokemonStatus::POISON;
9875
9876        let mut incoming_instructions = StateInstructions::default();
9877        add_end_of_turn_instructions(
9878            &mut state,
9879            &mut incoming_instructions,
9880            &SideReference::SideOne,
9881        );
9882
9883        let expected_instructions = StateInstructions {
9884            percentage: 100.0,
9885            instruction_list: vec![Instruction::Damage(DamageInstruction {
9886                side_ref: SideReference::SideOne,
9887                damage_amount: 12,
9888            })],
9889        };
9890
9891        assert_eq!(expected_instructions, incoming_instructions)
9892    }
9893
9894    #[test]
9895    fn test_poison_damage_does_not_overkill() {
9896        let mut state = State::default();
9897        state.side_one.get_active().status = PokemonStatus::POISON;
9898        state.side_one.get_active().hp = 5;
9899
9900        let mut incoming_instructions = StateInstructions::default();
9901        add_end_of_turn_instructions(
9902            &mut state,
9903            &mut incoming_instructions,
9904            &SideReference::SideOne,
9905        );
9906
9907        let expected_instructions = StateInstructions {
9908            percentage: 100.0,
9909            instruction_list: vec![Instruction::Damage(DamageInstruction {
9910                side_ref: SideReference::SideOne,
9911                damage_amount: 5,
9912            })],
9913        };
9914
9915        assert_eq!(expected_instructions, incoming_instructions)
9916    }
9917
9918    #[test]
9919    #[cfg(any(feature = "gen9", feature = "gen8", feature = "gen7"))]
9920    fn test_end_of_turn_burn_damage() {
9921        let mut state = State::default();
9922        state.side_one.get_active().status = PokemonStatus::BURN;
9923
9924        let mut incoming_instructions = StateInstructions::default();
9925        add_end_of_turn_instructions(
9926            &mut state,
9927            &mut incoming_instructions,
9928            &SideReference::SideOne,
9929        );
9930
9931        let expected_instructions = StateInstructions {
9932            percentage: 100.0,
9933            instruction_list: vec![Instruction::Damage(DamageInstruction {
9934                side_ref: SideReference::SideOne,
9935                damage_amount: 6,
9936            })],
9937        };
9938
9939        assert_eq!(expected_instructions, incoming_instructions)
9940    }
9941
9942    #[test]
9943    #[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5", feature = "gen6"))]
9944    fn test_early_generation_burn_one_eigth() {
9945        let mut state = State::default();
9946        state.side_one.get_active().status = PokemonStatus::BURN;
9947
9948        let mut incoming_instructions = StateInstructions::default();
9949        add_end_of_turn_instructions(
9950            &mut state,
9951            &mut incoming_instructions,
9952            &SideReference::SideOne,
9953        );
9954
9955        let expected_instructions = StateInstructions {
9956            percentage: 100.0,
9957            instruction_list: vec![Instruction::Damage(DamageInstruction {
9958                side_ref: SideReference::SideOne,
9959                damage_amount: 12,
9960            })],
9961        };
9962
9963        assert_eq!(expected_instructions, incoming_instructions)
9964    }
9965
9966    #[test]
9967    fn test_burn_damage_does_not_overkill() {
9968        let mut state = State::default();
9969        state.side_one.get_active().status = PokemonStatus::BURN;
9970        state.side_one.get_active().hp = 5;
9971
9972        let mut incoming_instructions = StateInstructions::default();
9973        add_end_of_turn_instructions(
9974            &mut state,
9975            &mut incoming_instructions,
9976            &SideReference::SideOne,
9977        );
9978
9979        let expected_instructions = StateInstructions {
9980            percentage: 100.0,
9981            instruction_list: vec![Instruction::Damage(DamageInstruction {
9982                side_ref: SideReference::SideOne,
9983                damage_amount: 5,
9984            })],
9985        };
9986
9987        assert_eq!(expected_instructions, incoming_instructions)
9988    }
9989
9990    #[test]
9991    fn test_burn_damage_ignored_if_has_magicguard() {
9992        let mut state = State::default();
9993        state.side_one.get_active().status = PokemonStatus::BURN;
9994        state.side_one.get_active().ability = Abilities::MAGICGUARD;
9995        state.side_one.get_active().hp = 5;
9996
9997        let mut incoming_instructions = StateInstructions::default();
9998        add_end_of_turn_instructions(
9999            &mut state,
10000            &mut incoming_instructions,
10001            &SideReference::SideOne,
10002        );
10003
10004        let expected_instructions = StateInstructions {
10005            percentage: 100.0,
10006            instruction_list: vec![],
10007        };
10008
10009        assert_eq!(expected_instructions, incoming_instructions)
10010    }
10011
10012    #[test]
10013    fn test_first_toxic_damage() {
10014        let mut state = State::default();
10015        state.side_one.get_active().status = PokemonStatus::TOXIC;
10016
10017        let mut incoming_instructions = StateInstructions::default();
10018        add_end_of_turn_instructions(
10019            &mut state,
10020            &mut incoming_instructions,
10021            &SideReference::SideOne,
10022        );
10023
10024        let expected_instructions = StateInstructions {
10025            percentage: 100.0,
10026            instruction_list: vec![
10027                Instruction::Damage(DamageInstruction {
10028                    side_ref: SideReference::SideOne,
10029                    damage_amount: 6,
10030                }),
10031                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
10032                    side_ref: SideReference::SideOne,
10033                    side_condition: PokemonSideCondition::ToxicCount,
10034                    amount: 1,
10035                }),
10036            ],
10037        };
10038
10039        assert_eq!(expected_instructions, incoming_instructions)
10040    }
10041
10042    #[test]
10043    fn test_leechseed_sap() {
10044        let mut state = State::default();
10045        state
10046            .side_one
10047            .volatile_statuses
10048            .insert(PokemonVolatileStatus::LEECHSEED);
10049        state.side_one.get_active().hp = 50;
10050        state.side_two.get_active().hp = 50;
10051
10052        let mut incoming_instructions = StateInstructions::default();
10053        add_end_of_turn_instructions(
10054            &mut state,
10055            &mut incoming_instructions,
10056            &SideReference::SideOne,
10057        );
10058
10059        let expected_instructions = StateInstructions {
10060            percentage: 100.0,
10061            instruction_list: vec![
10062                Instruction::Damage(DamageInstruction {
10063                    side_ref: SideReference::SideOne,
10064                    damage_amount: 12,
10065                }),
10066                Instruction::Heal(HealInstruction {
10067                    side_ref: SideReference::SideTwo,
10068                    heal_amount: 12,
10069                }),
10070            ],
10071        };
10072
10073        assert_eq!(expected_instructions, incoming_instructions)
10074    }
10075
10076    #[test]
10077    fn test_leechseed_sap_does_not_heal_if_receiving_side_is_maxhp() {
10078        let mut state = State::default();
10079        state
10080            .side_one
10081            .volatile_statuses
10082            .insert(PokemonVolatileStatus::LEECHSEED);
10083        state.side_one.get_active().hp = 50;
10084        state.side_two.get_active().hp = 100;
10085
10086        let mut incoming_instructions = StateInstructions::default();
10087        add_end_of_turn_instructions(
10088            &mut state,
10089            &mut incoming_instructions,
10090            &SideReference::SideOne,
10091        );
10092
10093        let expected_instructions = StateInstructions {
10094            percentage: 100.0,
10095            instruction_list: vec![Instruction::Damage(DamageInstruction {
10096                side_ref: SideReference::SideOne,
10097                damage_amount: 12,
10098            })],
10099        };
10100
10101        assert_eq!(expected_instructions, incoming_instructions)
10102    }
10103
10104    #[test]
10105    fn test_leechseed_sap_does_not_overkill() {
10106        let mut state = State::default();
10107        state
10108            .side_one
10109            .volatile_statuses
10110            .insert(PokemonVolatileStatus::LEECHSEED);
10111        state.side_one.get_active().hp = 5;
10112        state.side_two.get_active().hp = 50;
10113
10114        let mut incoming_instructions = StateInstructions::default();
10115        add_end_of_turn_instructions(
10116            &mut state,
10117            &mut incoming_instructions,
10118            &SideReference::SideOne,
10119        );
10120
10121        let expected_instructions = StateInstructions {
10122            percentage: 100.0,
10123            instruction_list: vec![
10124                Instruction::Damage(DamageInstruction {
10125                    side_ref: SideReference::SideOne,
10126                    damage_amount: 5,
10127                }),
10128                Instruction::Heal(HealInstruction {
10129                    side_ref: SideReference::SideTwo,
10130                    heal_amount: 5,
10131                }),
10132            ],
10133        };
10134
10135        assert_eq!(expected_instructions, incoming_instructions)
10136    }
10137
10138    #[test]
10139    fn test_leechseed_sap_does_not_overheal() {
10140        let mut state = State::default();
10141        state
10142            .side_one
10143            .volatile_statuses
10144            .insert(PokemonVolatileStatus::LEECHSEED);
10145        state.side_one.get_active().hp = 50;
10146        state.side_two.get_active().hp = 95;
10147
10148        let mut incoming_instructions = StateInstructions::default();
10149        add_end_of_turn_instructions(
10150            &mut state,
10151            &mut incoming_instructions,
10152            &SideReference::SideOne,
10153        );
10154
10155        let expected_instructions = StateInstructions {
10156            percentage: 100.0,
10157            instruction_list: vec![
10158                Instruction::Damage(DamageInstruction {
10159                    side_ref: SideReference::SideOne,
10160                    damage_amount: 12,
10161                }),
10162                Instruction::Heal(HealInstruction {
10163                    side_ref: SideReference::SideTwo,
10164                    heal_amount: 5,
10165                }),
10166            ],
10167        };
10168
10169        assert_eq!(expected_instructions, incoming_instructions)
10170    }
10171
10172    #[test]
10173    fn test_protect_volatile_being_removed() {
10174        let mut state = State::default();
10175        state
10176            .side_one
10177            .volatile_statuses
10178            .insert(PokemonVolatileStatus::PROTECT);
10179
10180        let mut incoming_instructions = StateInstructions::default();
10181        add_end_of_turn_instructions(
10182            &mut state,
10183            &mut incoming_instructions,
10184            &SideReference::SideOne,
10185        );
10186
10187        let expected_instructions = StateInstructions {
10188            percentage: 100.0,
10189            instruction_list: vec![
10190                Instruction::RemoveVolatileStatus(RemoveVolatileStatusInstruction {
10191                    side_ref: SideReference::SideOne,
10192                    volatile_status: PokemonVolatileStatus::PROTECT,
10193                }),
10194                Instruction::ChangeSideCondition(ChangeSideConditionInstruction {
10195                    side_ref: SideReference::SideOne,
10196                    side_condition: PokemonSideCondition::Protect,
10197                    amount: 1,
10198                }),
10199            ],
10200        };
10201
10202        assert_eq!(expected_instructions, incoming_instructions)
10203    }
10204
10205    #[test]
10206    fn test_protect_side_condition_being_removed() {
10207        let mut state = State::default();
10208        state.side_one.side_conditions.protect = 2;
10209
10210        let mut incoming_instructions = StateInstructions::default();
10211        add_end_of_turn_instructions(
10212            &mut state,
10213            &mut incoming_instructions,
10214            &SideReference::SideOne,
10215        );
10216
10217        let expected_instructions = StateInstructions {
10218            percentage: 100.0,
10219            instruction_list: vec![Instruction::ChangeSideCondition(
10220                ChangeSideConditionInstruction {
10221                    side_ref: SideReference::SideOne,
10222                    side_condition: PokemonSideCondition::Protect,
10223                    amount: -2,
10224                },
10225            )],
10226        };
10227
10228        assert_eq!(expected_instructions, incoming_instructions)
10229    }
10230
10231    #[test]
10232    fn test_roost_vs_removal() {
10233        let mut state = State::default();
10234        state
10235            .side_one
10236            .volatile_statuses
10237            .insert(PokemonVolatileStatus::ROOST);
10238
10239        let mut incoming_instructions = StateInstructions::default();
10240        add_end_of_turn_instructions(
10241            &mut state,
10242            &mut incoming_instructions,
10243            &SideReference::SideOne,
10244        );
10245
10246        let expected_instructions = StateInstructions {
10247            percentage: 100.0,
10248            instruction_list: vec![Instruction::RemoveVolatileStatus(
10249                RemoveVolatileStatusInstruction {
10250                    side_ref: SideReference::SideOne,
10251                    volatile_status: PokemonVolatileStatus::ROOST,
10252                },
10253            )],
10254        };
10255
10256        assert_eq!(expected_instructions, incoming_instructions)
10257    }
10258
10259    #[test]
10260    fn test_partiallytrapped_damage() {
10261        let mut state = State::default();
10262        state
10263            .side_one
10264            .volatile_statuses
10265            .insert(PokemonVolatileStatus::PARTIALLYTRAPPED);
10266
10267        let mut incoming_instructions = StateInstructions::default();
10268        add_end_of_turn_instructions(
10269            &mut state,
10270            &mut incoming_instructions,
10271            &SideReference::SideOne,
10272        );
10273
10274        #[cfg(any(feature = "gen3", feature = "gen4", feature = "gen5"))]
10275        let expected_instructions = StateInstructions {
10276            percentage: 100.0,
10277            instruction_list: vec![Instruction::Damage(DamageInstruction {
10278                side_ref: SideReference::SideOne,
10279                damage_amount: 6,
10280            })],
10281        };
10282
10283        #[cfg(any(feature = "gen6", feature = "gen7", feature = "gen8", feature = "gen9"))]
10284        let expected_instructions = StateInstructions {
10285            percentage: 100.0,
10286            instruction_list: vec![Instruction::Damage(DamageInstruction {
10287                side_ref: SideReference::SideOne,
10288                damage_amount: 12,
10289            })],
10290        };
10291
10292        assert_eq!(expected_instructions, incoming_instructions)
10293    }
10294
10295    #[test]
10296    fn test_saltcure_on_water_type_damage() {
10297        let mut state = State::default();
10298        state.side_one.get_active().types.0 = PokemonType::WATER;
10299        state
10300            .side_one
10301            .volatile_statuses
10302            .insert(PokemonVolatileStatus::SALTCURE);
10303
10304        let mut incoming_instructions = StateInstructions::default();
10305        add_end_of_turn_instructions(
10306            &mut state,
10307            &mut incoming_instructions,
10308            &SideReference::SideOne,
10309        );
10310
10311        let expected_instructions = StateInstructions {
10312            percentage: 100.0,
10313            instruction_list: vec![Instruction::Damage(DamageInstruction {
10314                side_ref: SideReference::SideOne,
10315                damage_amount: 25,
10316            })],
10317        };
10318
10319        assert_eq!(expected_instructions, incoming_instructions)
10320    }
10321
10322    #[test]
10323    fn test_chance_to_wake_up_with_no_turns_asleep_is_0() {
10324        assert_eq!(0.0, chance_to_wake_up(0));
10325    }
10326
10327    #[test]
10328    #[cfg(any(feature = "gen4"))]
10329    fn test_gen4_25_percent_to_wake_after_1_sleep_turn() {
10330        assert_eq!(0.25, chance_to_wake_up(1));
10331    }
10332
10333    #[test]
10334    #[cfg(any(feature = "gen4"))]
10335    fn test_gen4_100_percent_to_wake_after_4_sleep_turn() {
10336        assert_eq!(1.0, chance_to_wake_up(4));
10337    }
10338}