poke_engine/genx/
state.rs

1use super::abilities::Abilities;
2use super::choice_effects::charge_volatile_to_choice;
3use super::items::Items;
4use crate::choices::{Choices, MoveCategory};
5use crate::define_enum_with_from_str;
6use crate::instruction::BoostInstruction;
7use crate::instruction::{
8    ChangeSideConditionInstruction, ChangeStatInstruction, ChangeType,
9    ChangeVolatileStatusDurationInstruction, Instruction, RemoveVolatileStatusInstruction,
10    StateInstructions,
11};
12use crate::pokemon::PokemonName;
13use crate::state::{
14    LastUsedMove, Pokemon, PokemonBoostableStat, PokemonIndex, PokemonMoveIndex,
15    PokemonSideCondition, PokemonStatus, PokemonType, Side, SideReference, State,
16};
17use core::panic;
18use std::collections::HashSet;
19
20fn common_pkmn_stat_calc(stat: u16, ev: u16, level: u16) -> u16 {
21    // 31 IV always used
22    ((2 * stat + 31 + (ev / 4)) * level) / 100
23}
24
25fn multiply_boost(boost_num: i8, stat_value: i16) -> i16 {
26    match boost_num {
27        -6 => stat_value * 2 / 8,
28        -5 => stat_value * 2 / 7,
29        -4 => stat_value * 2 / 6,
30        -3 => stat_value * 2 / 5,
31        -2 => stat_value * 2 / 4,
32        -1 => stat_value * 2 / 3,
33        0 => stat_value,
34        1 => stat_value * 3 / 2,
35        2 => stat_value * 4 / 2,
36        3 => stat_value * 5 / 2,
37        4 => stat_value * 6 / 2,
38        5 => stat_value * 7 / 2,
39        6 => stat_value * 8 / 2,
40        _ => panic!("Invalid boost number: {}", boost_num),
41    }
42}
43
44#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
45pub enum MoveChoice {
46    MoveTera(PokemonMoveIndex),
47    MoveMega(PokemonMoveIndex),
48    Move(PokemonMoveIndex),
49    Switch(PokemonIndex),
50    None,
51}
52
53impl MoveChoice {
54    pub fn to_string(&self, side: &Side) -> String {
55        match self {
56            MoveChoice::MoveTera(index) => {
57                format!("{}-tera", side.get_active_immutable().moves[&index].id).to_lowercase()
58            }
59            MoveChoice::MoveMega(index) => {
60                format!("{}-mega", side.get_active_immutable().moves[&index].id).to_lowercase()
61            }
62            MoveChoice::Move(index) => {
63                format!("{}", side.get_active_immutable().moves[&index].id).to_lowercase()
64            }
65            MoveChoice::Switch(index) => format!("{}", side.pokemon[*index].id).to_lowercase(),
66            MoveChoice::None => "No Move".to_string(),
67        }
68    }
69    pub fn from_string(s: &str, side: &Side) -> Option<MoveChoice> {
70        let s = s.to_lowercase();
71        if s == "none" {
72            return Some(MoveChoice::None);
73        }
74
75        let mut pkmn_iter = side.pokemon.into_iter();
76        while let Some(pkmn) = pkmn_iter.next() {
77            if pkmn.id.to_string().to_lowercase() == s
78                && pkmn_iter.pokemon_index != side.active_index
79            {
80                return Some(MoveChoice::Switch(pkmn_iter.pokemon_index));
81            }
82        }
83
84        // check if s endswith `-tera`
85        // if it does, find the move with the name and return MoveChoice::MoveTera
86        // if it doesn't, find the move with the name and return MoveChoice::Move
87        let mut move_iter = side.get_active_immutable().moves.into_iter();
88        let mut move_name = s;
89        if move_name.ends_with("-tera") {
90            move_name = move_name[..move_name.len() - 5].to_string();
91            while let Some(mv) = move_iter.next() {
92                if format!("{:?}", mv.id).to_lowercase() == move_name {
93                    return Some(MoveChoice::MoveTera(move_iter.pokemon_move_index));
94                }
95            }
96        } else if move_name.ends_with("-mega") {
97            move_name = move_name[..move_name.len() - 5].to_string();
98            while let Some(mv) = move_iter.next() {
99                if format!("{:?}", mv.id).to_lowercase() == move_name {
100                    return Some(MoveChoice::MoveMega(move_iter.pokemon_move_index));
101                }
102            }
103        } else {
104            while let Some(mv) = move_iter.next() {
105                if format!("{:?}", mv.id).to_lowercase() == move_name {
106                    return Some(MoveChoice::Move(move_iter.pokemon_move_index));
107                }
108            }
109        }
110
111        None
112    }
113}
114
115define_enum_with_from_str! {
116    #[repr(u8)]
117    #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
118    PokemonVolatileStatus {
119        NONE,
120        AQUARING,
121        ATTRACT,
122        AUTOTOMIZE,
123        BANEFULBUNKER,
124        BIDE,
125        BOUNCE,
126        BURNINGBULWARK,
127        CHARGE,
128        CONFUSION,
129        CURSE,
130        DEFENSECURL,
131        DESTINYBOND,
132        DIG,
133        DISABLE,
134        DIVE,
135        ELECTRIFY,
136        ELECTROSHOT,
137        EMBARGO,
138        ENCORE,
139        ENDURE,
140        FLASHFIRE,
141        FLINCH,
142        FLY,
143        FOCUSENERGY,
144        FOLLOWME,
145        FORESIGHT,
146        FREEZESHOCK,
147        GASTROACID,
148        GEOMANCY,
149        GLAIVERUSH,
150        GRUDGE,
151        HEALBLOCK,
152        HELPINGHAND,
153        ICEBURN,
154        IMPRISON,
155        INGRAIN,
156        KINGSSHIELD,
157        LASERFOCUS,
158        LEECHSEED,
159        LIGHTSCREEN,
160        LOCKEDMOVE,
161        MAGICCOAT,
162        MAGNETRISE,
163        MAXGUARD,
164        METEORBEAM,
165        MINIMIZE,
166        MIRACLEEYE,
167        MUSTRECHARGE,
168        NIGHTMARE,
169        NORETREAT,
170        OCTOLOCK,
171        PARTIALLYTRAPPED,
172        PERISH4,
173        PERISH3,
174        PERISH2,
175        PERISH1,
176        PHANTOMFORCE,
177        POWDER,
178        POWERSHIFT,
179        POWERTRICK,
180        PROTECT,
181        PROTOSYNTHESISATK,
182        PROTOSYNTHESISDEF,
183        PROTOSYNTHESISSPA,
184        PROTOSYNTHESISSPD,
185        PROTOSYNTHESISSPE,
186        QUARKDRIVEATK,
187        QUARKDRIVEDEF,
188        QUARKDRIVESPA,
189        QUARKDRIVESPD,
190        QUARKDRIVESPE,
191        RAGE,
192        RAGEPOWDER,
193        RAZORWIND,
194        REFLECT,
195        ROOST,
196        SALTCURE,
197        SHADOWFORCE,
198        SKULLBASH,
199        SKYATTACK,
200        SKYDROP,
201        SILKTRAP,
202        SLOWSTART,
203        SMACKDOWN,
204        SNATCH,
205        SOLARBEAM,
206        SOLARBLADE,
207        SPARKLINGARIA,
208        SPIKYSHIELD,
209        SPOTLIGHT,
210        STOCKPILE,
211        SUBSTITUTE,
212        SYRUPBOMB,
213        TARSHOT,
214        TAUNT,
215        TELEKINESIS,
216        THROATCHOP,
217        TRUANT,
218        TORMENT,
219        TYPECHANGE,
220        UNBURDEN,
221        UPROAR,
222        YAWN,
223    },
224    default = NONE
225}
226
227define_enum_with_from_str! {
228    #[repr(u8)]
229    #[derive(Debug, PartialEq, Copy, Clone)]
230    Weather {
231        NONE,
232        SUN,
233        RAIN,
234        SAND,
235        HAIL,
236        SNOW,
237        HARSHSUN,
238        HEAVYRAIN,
239    }
240}
241
242define_enum_with_from_str! {
243    #[repr(u8)]
244    #[derive(Debug, PartialEq, Copy, Clone)]
245    Terrain {
246        NONE,
247        ELECTRICTERRAIN,
248        PSYCHICTERRAIN,
249        MISTYTERRAIN,
250        GRASSYTERRAIN,
251    }
252}
253
254impl Pokemon {
255    pub fn can_mega_evolve(&self) -> bool {
256        // this assumes that if you have the correct mega stone, you can always mega evolve
257        // even if another pkmn on the team already mega evolved
258        // it is incorrect but practically most teams aren't going to have multiple mega stones
259        if let Some(_mega_evolve_data) = self.id.mega_evolve_target(self.item) {
260            true
261        } else {
262            false
263        }
264    }
265
266    pub fn recalculate_stats(
267        &mut self,
268        side_ref: &SideReference,
269        instructions: &mut StateInstructions,
270    ) {
271        // recalculate stats from base-stats and push any changes made to the StateInstructions
272        let stats = self.calculate_stats_from_base_stats();
273        if stats.1 != self.attack {
274            let ins = Instruction::ChangeAttack(ChangeStatInstruction {
275                side_ref: *side_ref,
276                amount: stats.1 - self.attack,
277            });
278            self.attack = stats.1;
279            instructions.instruction_list.push(ins);
280        }
281        if stats.2 != self.defense {
282            let ins = Instruction::ChangeDefense(ChangeStatInstruction {
283                side_ref: *side_ref,
284                amount: stats.2 - self.defense,
285            });
286            self.defense = stats.2;
287            instructions.instruction_list.push(ins);
288        }
289        if stats.3 != self.special_attack {
290            let ins = Instruction::ChangeSpecialAttack(ChangeStatInstruction {
291                side_ref: *side_ref,
292                amount: stats.3 - self.special_attack,
293            });
294            self.special_attack = stats.3;
295            instructions.instruction_list.push(ins);
296        }
297        if stats.4 != self.special_defense {
298            let ins = Instruction::ChangeSpecialDefense(ChangeStatInstruction {
299                side_ref: *side_ref,
300                amount: stats.4 - self.special_defense,
301            });
302            self.special_defense = stats.4;
303            instructions.instruction_list.push(ins);
304        }
305        if stats.5 != self.speed {
306            let ins = Instruction::ChangeSpeed(ChangeStatInstruction {
307                side_ref: *side_ref,
308                amount: stats.5 - self.speed,
309            });
310            self.speed = stats.5;
311            instructions.instruction_list.push(ins);
312        }
313    }
314    pub fn calculate_stats_from_base_stats(&self) -> (i16, i16, i16, i16, i16, i16) {
315        let base_stats = self.id.base_stats();
316        (
317            (common_pkmn_stat_calc(base_stats.0 as u16, self.evs.0 as u16, self.level as u16)
318                + self.level as u16
319                + 10) as i16,
320            (common_pkmn_stat_calc(base_stats.1 as u16, self.evs.1 as u16, self.level as u16) + 5)
321                as i16,
322            (common_pkmn_stat_calc(base_stats.2 as u16, self.evs.2 as u16, self.level as u16) + 5)
323                as i16,
324            (common_pkmn_stat_calc(base_stats.3 as u16, self.evs.3 as u16, self.level as u16) + 5)
325                as i16,
326            (common_pkmn_stat_calc(base_stats.4 as u16, self.evs.4 as u16, self.level as u16) + 5)
327                as i16,
328            (common_pkmn_stat_calc(base_stats.5 as u16, self.evs.5 as u16, self.level as u16) + 5)
329                as i16,
330        )
331    }
332    pub fn add_available_moves(
333        &self,
334        vec: &mut Vec<MoveChoice>,
335        last_used_move: &LastUsedMove,
336        encored: bool,
337        taunted: bool,
338        can_tera: bool,
339    ) {
340        let mut iter = self.moves.into_iter();
341        while let Some(p) = iter.next() {
342            if !p.disabled && p.pp > 0 {
343                match last_used_move {
344                    LastUsedMove::Move(last_used_move) => {
345                        if encored && last_used_move != &iter.pokemon_move_index {
346                            continue;
347                        } else if (self.moves[last_used_move].id == Choices::BLOODMOON
348                            || self.moves[last_used_move].id == Choices::GIGATONHAMMER)
349                            && &iter.pokemon_move_index == last_used_move
350                        {
351                            continue;
352                        }
353                    }
354                    _ => {
355                        // there are some situations where you switched out and got encored into
356                        // a move from a different pokemon because you also have that move.
357                        // just assume nothing is locked in this case
358                    }
359                }
360                if (self.item == Items::ASSAULTVEST || taunted)
361                    && self.moves[&iter.pokemon_move_index].choice.category == MoveCategory::Status
362                {
363                    continue;
364                }
365                vec.push(MoveChoice::Move(iter.pokemon_move_index));
366                if can_tera {
367                    vec.push(MoveChoice::MoveTera(iter.pokemon_move_index));
368                }
369                if self.can_mega_evolve() {
370                    vec.push(MoveChoice::MoveMega(iter.pokemon_move_index));
371                }
372            }
373        }
374    }
375
376    pub fn add_move_from_choice(&self, vec: &mut Vec<MoveChoice>, choice: Choices) {
377        let mut iter = self.moves.into_iter();
378        while let Some(p) = iter.next() {
379            if p.id == choice {
380                vec.push(MoveChoice::Move(iter.pokemon_move_index));
381            }
382        }
383    }
384
385    #[cfg(feature = "terastallization")]
386    pub fn has_type(&self, pkmn_type: &PokemonType) -> bool {
387        if self.terastallized {
388            pkmn_type == &self.tera_type
389        } else {
390            pkmn_type == &self.types.0 || pkmn_type == &self.types.1
391        }
392    }
393
394    #[cfg(not(feature = "terastallization"))]
395    pub fn has_type(&self, pkmn_type: &PokemonType) -> bool {
396        pkmn_type == &self.types.0 || pkmn_type == &self.types.1
397    }
398
399    pub fn item_is_permanent(&self) -> bool {
400        match self.item {
401            Items::LUSTROUSGLOBE => self.id == PokemonName::PALKIAORIGIN,
402            Items::GRISEOUSCORE => self.id == PokemonName::GIRATINAORIGIN,
403            Items::ADAMANTCRYSTAL => self.id == PokemonName::DIALGAORIGIN,
404            Items::RUSTEDSWORD => {
405                self.id == PokemonName::ZACIANCROWNED || self.id == PokemonName::ZACIAN
406            }
407            Items::RUSTEDSHIELD => {
408                self.id == PokemonName::ZAMAZENTACROWNED || self.id == PokemonName::ZAMAZENTA
409            }
410            Items::SPLASHPLATE => self.id == PokemonName::ARCEUSWATER,
411            Items::TOXICPLATE => self.id == PokemonName::ARCEUSPOISON,
412            Items::EARTHPLATE => self.id == PokemonName::ARCEUSGROUND,
413            Items::STONEPLATE => self.id == PokemonName::ARCEUSROCK,
414            Items::INSECTPLATE => self.id == PokemonName::ARCEUSBUG,
415            Items::SPOOKYPLATE => self.id == PokemonName::ARCEUSGHOST,
416            Items::IRONPLATE => self.id == PokemonName::ARCEUSSTEEL,
417            Items::FLAMEPLATE => self.id == PokemonName::ARCEUSFIRE,
418            Items::MEADOWPLATE => self.id == PokemonName::ARCEUSGRASS,
419            Items::ZAPPLATE => self.id == PokemonName::ARCEUSELECTRIC,
420            Items::MINDPLATE => self.id == PokemonName::ARCEUSPSYCHIC,
421            Items::ICICLEPLATE => self.id == PokemonName::ARCEUSICE,
422            Items::DRACOPLATE => self.id == PokemonName::ARCEUSDRAGON,
423            Items::DREADPLATE => self.id == PokemonName::ARCEUSDARK,
424            Items::FISTPLATE => self.id == PokemonName::ARCEUSFIGHTING,
425            Items::BLANKPLATE => self.id == PokemonName::ARCEUS,
426            Items::SKYPLATE => self.id == PokemonName::ARCEUSFLYING,
427            Items::PIXIEPLATE => self.id == PokemonName::ARCEUSFAIRY,
428            Items::BUGMEMORY => self.id == PokemonName::SILVALLYBUG,
429            Items::FIGHTINGMEMORY => self.id == PokemonName::SILVALLYFIGHTING,
430            Items::GHOSTMEMORY => self.id == PokemonName::SILVALLYGHOST,
431            Items::PSYCHICMEMORY => self.id == PokemonName::SILVALLYPSYCHIC,
432            Items::FLYINGMEMORY => self.id == PokemonName::SILVALLYFLYING,
433            Items::STEELMEMORY => self.id == PokemonName::SILVALLYSTEEL,
434            Items::ICEMEMORY => self.id == PokemonName::SILVALLYICE,
435            Items::POISONMEMORY => self.id == PokemonName::SILVALLYPOISON,
436            Items::FIREMEMORY => self.id == PokemonName::SILVALLYFIRE,
437            Items::DRAGONMEMORY => self.id == PokemonName::SILVALLYDRAGON,
438            Items::GROUNDMEMORY => self.id == PokemonName::SILVALLYGROUND,
439            Items::WATERMEMORY => self.id == PokemonName::SILVALLYWATER,
440            Items::DARKMEMORY => self.id == PokemonName::SILVALLYDARK,
441            Items::ROCKMEMORY => self.id == PokemonName::SILVALLYROCK,
442            Items::GRASSMEMORY => self.id == PokemonName::SILVALLYGRASS,
443            Items::FAIRYMEMORY => self.id == PokemonName::SILVALLYFAIRY,
444            Items::ELECTRICMEMORY => self.id == PokemonName::SILVALLYELECTRIC,
445            Items::CORNERSTONEMASK => {
446                self.id == PokemonName::OGERPONCORNERSTONE
447                    || self.id == PokemonName::OGERPONCORNERSTONETERA
448            }
449            Items::HEARTHFLAMEMASK => {
450                self.id == PokemonName::OGERPONHEARTHFLAME
451                    || self.id == PokemonName::OGERPONHEARTHFLAMETERA
452            }
453            Items::WELLSPRINGMASK => {
454                self.id == PokemonName::OGERPONWELLSPRING
455                    || self.id == PokemonName::OGERPONWELLSPRINGTERA
456            }
457            _ => false,
458        }
459    }
460
461    pub fn item_can_be_removed(&self) -> bool {
462        if self.ability == Abilities::STICKYHOLD {
463            return false;
464        }
465        !self.item_is_permanent()
466    }
467
468    pub fn is_grounded(&self) -> bool {
469        if self.item == Items::IRONBALL {
470            return true;
471        }
472        if self.has_type(&PokemonType::FLYING)
473            || self.ability == Abilities::LEVITATE
474            || self.item == Items::AIRBALLOON
475        {
476            return false;
477        }
478        true
479    }
480
481    pub fn volatile_status_can_be_applied(
482        &self,
483        volatile_status: &PokemonVolatileStatus,
484        active_volatiles: &HashSet<PokemonVolatileStatus>,
485        first_move: bool,
486    ) -> bool {
487        if active_volatiles.contains(volatile_status) || self.hp == 0 {
488            return false;
489        }
490        match volatile_status {
491            PokemonVolatileStatus::LEECHSEED => {
492                if self.has_type(&PokemonType::GRASS)
493                    || active_volatiles.contains(&PokemonVolatileStatus::SUBSTITUTE)
494                {
495                    return false;
496                }
497                true
498            }
499            PokemonVolatileStatus::CONFUSION => {
500                if active_volatiles.contains(&PokemonVolatileStatus::SUBSTITUTE) {
501                    return false;
502                }
503                true
504            }
505            PokemonVolatileStatus::SUBSTITUTE => self.hp > self.maxhp / 4,
506            PokemonVolatileStatus::FLINCH => {
507                if !first_move || [Abilities::INNERFOCUS].contains(&self.ability) {
508                    return false;
509                }
510                true
511            }
512            PokemonVolatileStatus::PROTECT => first_move,
513            PokemonVolatileStatus::TAUNT
514            | PokemonVolatileStatus::TORMENT
515            | PokemonVolatileStatus::ENCORE
516            | PokemonVolatileStatus::DISABLE
517            | PokemonVolatileStatus::HEALBLOCK
518            | PokemonVolatileStatus::ATTRACT => self.ability != Abilities::AROMAVEIL,
519            _ => true,
520        }
521    }
522
523    pub fn immune_to_stats_lowered_by_opponent(
524        &self,
525        stat: &PokemonBoostableStat,
526        volatiles: &HashSet<PokemonVolatileStatus>,
527    ) -> bool {
528        if [
529            Abilities::CLEARBODY,
530            Abilities::WHITESMOKE,
531            Abilities::FULLMETALBODY,
532        ]
533        .contains(&self.ability)
534            || ([Items::CLEARAMULET].contains(&self.item))
535        {
536            return true;
537        }
538
539        if volatiles.contains(&PokemonVolatileStatus::SUBSTITUTE) {
540            return true;
541        }
542
543        if stat == &PokemonBoostableStat::Attack && self.ability == Abilities::HYPERCUTTER {
544            return true;
545        } else if stat == &PokemonBoostableStat::Accuracy && self.ability == Abilities::KEENEYE {
546            return true;
547        }
548
549        false
550    }
551}
552
553impl Side {
554    pub fn reset_negative_boosts(
555        &mut self,
556        side_ref: SideReference,
557        instructions: &mut StateInstructions,
558    ) -> bool {
559        let mut changed = false;
560        if self.attack_boost < 0 {
561            instructions
562                .instruction_list
563                .push(Instruction::Boost(BoostInstruction {
564                    side_ref,
565                    stat: PokemonBoostableStat::Attack,
566                    amount: -self.attack_boost,
567                }));
568            self.attack_boost = 0;
569            changed = true;
570        }
571        if self.defense_boost < 0 {
572            instructions
573                .instruction_list
574                .push(Instruction::Boost(BoostInstruction {
575                    side_ref,
576                    stat: PokemonBoostableStat::Defense,
577                    amount: -self.defense_boost,
578                }));
579            self.defense_boost = 0;
580            changed = true;
581        }
582        if self.special_attack_boost < 0 {
583            instructions
584                .instruction_list
585                .push(Instruction::Boost(BoostInstruction {
586                    side_ref,
587                    stat: PokemonBoostableStat::SpecialAttack,
588                    amount: -self.special_attack_boost,
589                }));
590            self.special_attack_boost = 0;
591            changed = true;
592        }
593        if self.special_defense_boost < 0 {
594            instructions
595                .instruction_list
596                .push(Instruction::Boost(BoostInstruction {
597                    side_ref,
598                    stat: PokemonBoostableStat::SpecialDefense,
599                    amount: -self.special_defense_boost,
600                }));
601            self.special_defense_boost = 0;
602            changed = true;
603        }
604        if self.speed_boost < 0 {
605            instructions
606                .instruction_list
607                .push(Instruction::Boost(BoostInstruction {
608                    side_ref,
609                    stat: PokemonBoostableStat::Speed,
610                    amount: -self.speed_boost,
611                }));
612            self.speed_boost = 0;
613            changed = true;
614        }
615        if self.accuracy_boost < 0 {
616            instructions
617                .instruction_list
618                .push(Instruction::Boost(BoostInstruction {
619                    side_ref,
620                    stat: PokemonBoostableStat::Accuracy,
621                    amount: -self.accuracy_boost,
622                }));
623            self.accuracy_boost = 0;
624            changed = true;
625        }
626        if self.evasion_boost < 0 {
627            instructions
628                .instruction_list
629                .push(Instruction::Boost(BoostInstruction {
630                    side_ref,
631                    stat: PokemonBoostableStat::Evasion,
632                    amount: -self.evasion_boost,
633                }));
634            self.evasion_boost = 0;
635            changed = true;
636        }
637        changed
638    }
639    pub fn active_is_charging_move(&self) -> Option<PokemonMoveIndex> {
640        for volatile in self.volatile_statuses.iter() {
641            if let Some(choice) = charge_volatile_to_choice(volatile) {
642                let mut iter = self.get_active_immutable().moves.into_iter();
643                while let Some(mv) = iter.next() {
644                    if mv.id == choice {
645                        return Some(iter.pokemon_move_index);
646                    }
647                }
648            }
649        }
650        None
651    }
652
653    pub fn calculate_highest_stat(&self) -> PokemonBoostableStat {
654        let mut highest_stat = PokemonBoostableStat::Attack;
655        let mut highest_stat_value = self.calculate_boosted_stat(PokemonBoostableStat::Attack);
656        for stat in [
657            PokemonBoostableStat::Defense,
658            PokemonBoostableStat::SpecialAttack,
659            PokemonBoostableStat::SpecialDefense,
660            PokemonBoostableStat::Speed,
661        ] {
662            let stat_value = self.calculate_boosted_stat(stat);
663            if stat_value > highest_stat_value {
664                highest_stat = stat;
665                highest_stat_value = stat_value;
666            }
667        }
668        highest_stat
669    }
670    pub fn get_boost_from_boost_enum(&self, boost_enum: &PokemonBoostableStat) -> i8 {
671        match boost_enum {
672            PokemonBoostableStat::Attack => self.attack_boost,
673            PokemonBoostableStat::Defense => self.defense_boost,
674            PokemonBoostableStat::SpecialAttack => self.special_attack_boost,
675            PokemonBoostableStat::SpecialDefense => self.special_defense_boost,
676            PokemonBoostableStat::Speed => self.speed_boost,
677            PokemonBoostableStat::Evasion => self.evasion_boost,
678            PokemonBoostableStat::Accuracy => self.accuracy_boost,
679        }
680    }
681
682    pub fn calculate_boosted_stat(&self, stat: PokemonBoostableStat) -> i16 {
683        /*
684        In Gen4, simple doubles the effective boost, without it visually being doubled
685        It will not boost beyond an effective value of 6 though.
686        */
687        let active = self.get_active_immutable();
688        match stat {
689            PokemonBoostableStat::Attack => {
690                #[cfg(feature = "gen4")]
691                let boost = if active.ability == Abilities::SIMPLE {
692                    (self.attack_boost * 2).min(6).max(-6)
693                } else {
694                    self.attack_boost
695                };
696
697                #[cfg(not(feature = "gen4"))]
698                let boost = self.attack_boost;
699
700                multiply_boost(boost, active.attack)
701            }
702            PokemonBoostableStat::Defense => {
703                #[cfg(feature = "gen4")]
704                let boost = if active.ability == Abilities::SIMPLE {
705                    (self.defense_boost * 2).min(6).max(-6)
706                } else {
707                    self.defense_boost
708                };
709                #[cfg(not(feature = "gen4"))]
710                let boost = self.defense_boost;
711
712                multiply_boost(boost, active.defense)
713            }
714            PokemonBoostableStat::SpecialAttack => {
715                #[cfg(feature = "gen4")]
716                let boost = if active.ability == Abilities::SIMPLE {
717                    (self.special_attack_boost * 2).min(6).max(-6)
718                } else {
719                    self.special_attack_boost
720                };
721                #[cfg(not(feature = "gen4"))]
722                let boost = self.special_attack_boost;
723
724                multiply_boost(boost, active.special_attack)
725            }
726            PokemonBoostableStat::SpecialDefense => {
727                #[cfg(feature = "gen4")]
728                let boost = if active.ability == Abilities::SIMPLE {
729                    (self.special_defense_boost * 2).min(6).max(-6)
730                } else {
731                    self.special_defense_boost
732                };
733                #[cfg(not(feature = "gen4"))]
734                let boost = self.special_defense_boost;
735
736                multiply_boost(boost, active.special_defense)
737            }
738            PokemonBoostableStat::Speed => {
739                #[cfg(feature = "gen4")]
740                let boost = if active.ability == Abilities::SIMPLE {
741                    (self.speed_boost * 2).min(6).max(-6)
742                } else {
743                    self.speed_boost
744                };
745                #[cfg(not(feature = "gen4"))]
746                let boost = self.speed_boost;
747
748                multiply_boost(boost, active.speed)
749            }
750            _ => {
751                panic!("Not implemented")
752            }
753        }
754    }
755
756    pub fn has_alive_non_rested_sleeping_pkmn(&self) -> bool {
757        for p in self.pokemon.into_iter() {
758            if p.status == PokemonStatus::SLEEP && p.hp > 0 && p.rest_turns == 0 {
759                return true;
760            }
761        }
762        false
763    }
764
765    #[cfg(not(feature = "terastallization"))]
766    pub fn can_use_tera(&self) -> bool {
767        false
768    }
769
770    #[cfg(feature = "terastallization")]
771    pub fn can_use_tera(&self) -> bool {
772        for p in self.pokemon.into_iter() {
773            if p.terastallized {
774                return false;
775            }
776        }
777        true
778    }
779
780    pub fn add_switches(&self, vec: &mut Vec<MoveChoice>) {
781        let mut iter = self.pokemon.into_iter();
782        while let Some(p) = iter.next() {
783            if p.hp > 0 && iter.pokemon_index != self.active_index {
784                vec.push(MoveChoice::Switch(iter.pokemon_index));
785            }
786        }
787        if vec.len() == 0 {
788            vec.push(MoveChoice::None);
789        }
790    }
791
792    pub fn trapped(&self, opponent_active: &Pokemon) -> bool {
793        let active_pkmn = self.get_active_immutable();
794        if self
795            .volatile_statuses
796            .contains(&PokemonVolatileStatus::LOCKEDMOVE)
797            || self
798                .volatile_statuses
799                .contains(&PokemonVolatileStatus::NORETREAT)
800        {
801            return true;
802        }
803        if active_pkmn.item == Items::SHEDSHELL || active_pkmn.has_type(&PokemonType::GHOST) {
804            return false;
805        } else if self
806            .volatile_statuses
807            .contains(&PokemonVolatileStatus::PARTIALLYTRAPPED)
808        {
809            return true;
810        } else if opponent_active.ability == Abilities::SHADOWTAG {
811            return true;
812        } else if opponent_active.ability == Abilities::ARENATRAP && active_pkmn.is_grounded() {
813            return true;
814        } else if opponent_active.ability == Abilities::MAGNETPULL
815            && active_pkmn.has_type(&PokemonType::STEEL)
816        {
817            return true;
818        }
819        false
820    }
821
822    pub fn num_fainted_pkmn(&self) -> i8 {
823        let mut count = 0;
824        for p in self.pokemon.into_iter() {
825            if p.hp == 0 {
826                count += 1;
827            }
828        }
829        count
830    }
831}
832
833impl State {
834    pub fn root_get_all_options(&self) -> (Vec<MoveChoice>, Vec<MoveChoice>) {
835        if self.team_preview {
836            let mut s1_options = Vec::with_capacity(6);
837            let mut s2_options = Vec::with_capacity(6);
838
839            let mut pkmn_iter = self.side_one.pokemon.into_iter();
840            while let Some(_) = pkmn_iter.next() {
841                if self.side_one.pokemon[pkmn_iter.pokemon_index].hp > 0 {
842                    s1_options.push(MoveChoice::Switch(pkmn_iter.pokemon_index));
843                }
844            }
845            let mut pkmn_iter = self.side_two.pokemon.into_iter();
846            while let Some(_) = pkmn_iter.next() {
847                if self.side_two.pokemon[pkmn_iter.pokemon_index].hp > 0 {
848                    s2_options.push(MoveChoice::Switch(pkmn_iter.pokemon_index));
849                }
850            }
851            return (s1_options, s2_options);
852        }
853
854        let (mut s1_options, mut s2_options) = self.get_all_options();
855
856        if self.side_one.force_trapped {
857            s1_options.retain(|x| match x {
858                MoveChoice::Move(_) | MoveChoice::MoveTera(_) | MoveChoice::MoveMega(_) => true,
859                MoveChoice::Switch(_) => false,
860                MoveChoice::None => true,
861            });
862        }
863        if self.side_one.slow_uturn_move {
864            s1_options.clear();
865            let encored = self
866                .side_one
867                .volatile_statuses
868                .contains(&PokemonVolatileStatus::ENCORE);
869            let taunted = self
870                .side_one
871                .volatile_statuses
872                .contains(&PokemonVolatileStatus::TAUNT);
873            self.side_one.get_active_immutable().add_available_moves(
874                &mut s1_options,
875                &self.side_one.last_used_move,
876                encored,
877                taunted,
878                self.side_one.can_use_tera(),
879            );
880        }
881
882        if self.side_two.force_trapped {
883            s2_options.retain(|x| match x {
884                MoveChoice::Move(_) | MoveChoice::MoveTera(_) | MoveChoice::MoveMega(_) => true,
885                MoveChoice::Switch(_) => false,
886                MoveChoice::None => true,
887            });
888        }
889        if self.side_two.slow_uturn_move {
890            s2_options.clear();
891            let encored = self
892                .side_two
893                .volatile_statuses
894                .contains(&PokemonVolatileStatus::ENCORE);
895            let taunted = self
896                .side_two
897                .volatile_statuses
898                .contains(&PokemonVolatileStatus::TAUNT);
899            self.side_two.get_active_immutable().add_available_moves(
900                &mut s2_options,
901                &self.side_two.last_used_move,
902                encored,
903                taunted,
904                self.side_two.can_use_tera(),
905            );
906        }
907
908        if s1_options.len() == 0 {
909            s1_options.push(MoveChoice::None);
910        }
911        if s2_options.len() == 0 {
912            s2_options.push(MoveChoice::None);
913        }
914
915        (s1_options, s2_options)
916    }
917
918    pub fn get_all_options(&self) -> (Vec<MoveChoice>, Vec<MoveChoice>) {
919        let mut side_one_options: Vec<MoveChoice> = Vec::with_capacity(9);
920        let mut side_two_options: Vec<MoveChoice> = Vec::with_capacity(9);
921
922        let side_one_active = self.side_one.get_active_immutable();
923        let side_two_active = self.side_two.get_active_immutable();
924
925        if self.side_one.force_switch {
926            self.side_one.add_switches(&mut side_one_options);
927            if self.side_two.switch_out_move_second_saved_move == Choices::NONE {
928                side_two_options.push(MoveChoice::None);
929            } else {
930                self.side_two.get_active_immutable().add_move_from_choice(
931                    &mut side_two_options,
932                    self.side_two.switch_out_move_second_saved_move,
933                );
934            }
935            return (side_one_options, side_two_options);
936        }
937
938        if self.side_two.force_switch {
939            self.side_two.add_switches(&mut side_two_options);
940            if self.side_one.switch_out_move_second_saved_move == Choices::NONE {
941                side_one_options.push(MoveChoice::None);
942            } else {
943                self.side_one.get_active_immutable().add_move_from_choice(
944                    &mut side_one_options,
945                    self.side_one.switch_out_move_second_saved_move,
946                );
947            }
948            return (side_one_options, side_two_options);
949        }
950
951        let side_one_force_switch = self.side_one.get_active_immutable().hp <= 0;
952        let side_two_force_switch = self.side_two.get_active_immutable().hp <= 0;
953
954        if side_one_force_switch && side_two_force_switch {
955            self.side_one.add_switches(&mut side_one_options);
956            self.side_two.add_switches(&mut side_two_options);
957            return (side_one_options, side_two_options);
958        }
959        if side_one_force_switch {
960            self.side_one.add_switches(&mut side_one_options);
961            side_two_options.push(MoveChoice::None);
962            return (side_one_options, side_two_options);
963        }
964        if side_two_force_switch {
965            side_one_options.push(MoveChoice::None);
966            self.side_two.add_switches(&mut side_two_options);
967            return (side_one_options, side_two_options);
968        }
969
970        if self
971            .side_one
972            .volatile_statuses
973            .contains(&PokemonVolatileStatus::MUSTRECHARGE)
974        {
975            side_one_options.push(MoveChoice::None);
976        } else if let Some(mv_index) = self.side_one.active_is_charging_move() {
977            side_one_options.push(MoveChoice::Move(mv_index));
978        } else {
979            let encored = self
980                .side_one
981                .volatile_statuses
982                .contains(&PokemonVolatileStatus::ENCORE);
983            let taunted = self
984                .side_one
985                .volatile_statuses
986                .contains(&PokemonVolatileStatus::TAUNT);
987            self.side_one.get_active_immutable().add_available_moves(
988                &mut side_one_options,
989                &self.side_one.last_used_move,
990                encored,
991                taunted,
992                self.side_one.can_use_tera(),
993            );
994            if !self.side_one.trapped(side_two_active) {
995                self.side_one.add_switches(&mut side_one_options);
996            }
997        }
998
999        if self
1000            .side_two
1001            .volatile_statuses
1002            .contains(&PokemonVolatileStatus::MUSTRECHARGE)
1003        {
1004            side_two_options.push(MoveChoice::None);
1005        } else if let Some(mv_index) = self.side_two.active_is_charging_move() {
1006            side_two_options.push(MoveChoice::Move(mv_index));
1007        } else {
1008            let encored = self
1009                .side_two
1010                .volatile_statuses
1011                .contains(&PokemonVolatileStatus::ENCORE);
1012            let taunted = self
1013                .side_two
1014                .volatile_statuses
1015                .contains(&PokemonVolatileStatus::TAUNT);
1016            self.side_two.get_active_immutable().add_available_moves(
1017                &mut side_two_options,
1018                &self.side_two.last_used_move,
1019                encored,
1020                taunted,
1021                self.side_two.can_use_tera(),
1022            );
1023            if !self.side_two.trapped(side_one_active) {
1024                self.side_two.add_switches(&mut side_two_options);
1025            }
1026        }
1027
1028        if side_one_options.len() == 0 {
1029            side_one_options.push(MoveChoice::None);
1030        }
1031        if side_two_options.len() == 0 {
1032            side_two_options.push(MoveChoice::None);
1033        }
1034
1035        (side_one_options, side_two_options)
1036    }
1037
1038    pub fn reset_toxic_count(
1039        &mut self,
1040        side_ref: &SideReference,
1041        vec_to_add_to: &mut Vec<Instruction>,
1042    ) {
1043        let side = self.get_side(side_ref);
1044        if side.side_conditions.toxic_count > 0 {
1045            vec_to_add_to.push(Instruction::ChangeSideCondition(
1046                ChangeSideConditionInstruction {
1047                    side_ref: *side_ref,
1048                    side_condition: PokemonSideCondition::ToxicCount,
1049                    amount: -1 * side.side_conditions.toxic_count,
1050                },
1051            ));
1052            side.side_conditions.toxic_count = 0;
1053        }
1054    }
1055
1056    pub fn remove_volatile_statuses_on_switch(
1057        &mut self,
1058        side_ref: &SideReference,
1059        instructions: &mut Vec<Instruction>,
1060        baton_passing: bool,
1061        shed_tailing: bool,
1062    ) {
1063        let side = self.get_side(side_ref);
1064
1065        // Take ownership of the current set to avoid borrow conflicts
1066        // since we may need to modify the side in the loop
1067        let mut volatile_statuses = std::mem::take(&mut side.volatile_statuses);
1068
1069        volatile_statuses.retain(|pkmn_volatile_status| {
1070            let should_retain = match pkmn_volatile_status {
1071                PokemonVolatileStatus::SUBSTITUTE => baton_passing || shed_tailing,
1072                PokemonVolatileStatus::LEECHSEED => baton_passing,
1073                PokemonVolatileStatus::TYPECHANGE => {
1074                    let active = side.get_active();
1075                    if active.base_types != active.types {
1076                        instructions.push(Instruction::ChangeType(ChangeType {
1077                            side_ref: *side_ref,
1078                            new_types: active.base_types,
1079                            old_types: active.types,
1080                        }));
1081                        active.types = active.base_types;
1082                    }
1083                    false
1084                }
1085                // While you can't switch out of a locked move you can be forced out in other ways
1086                PokemonVolatileStatus::LOCKEDMOVE => {
1087                    instructions.push(Instruction::ChangeVolatileStatusDuration(
1088                        ChangeVolatileStatusDurationInstruction {
1089                            side_ref: *side_ref,
1090                            volatile_status: *pkmn_volatile_status,
1091                            amount: -1 * side.volatile_status_durations.lockedmove,
1092                        },
1093                    ));
1094                    side.volatile_status_durations.lockedmove = 0;
1095                    false
1096                }
1097                PokemonVolatileStatus::YAWN => {
1098                    instructions.push(Instruction::ChangeVolatileStatusDuration(
1099                        ChangeVolatileStatusDurationInstruction {
1100                            side_ref: *side_ref,
1101                            volatile_status: *pkmn_volatile_status,
1102                            amount: -1 * side.volatile_status_durations.yawn,
1103                        },
1104                    ));
1105                    side.volatile_status_durations.yawn = 0;
1106                    false
1107                }
1108                PokemonVolatileStatus::TAUNT => {
1109                    instructions.push(Instruction::ChangeVolatileStatusDuration(
1110                        ChangeVolatileStatusDurationInstruction {
1111                            side_ref: *side_ref,
1112                            volatile_status: *pkmn_volatile_status,
1113                            amount: -1 * side.volatile_status_durations.taunt,
1114                        },
1115                    ));
1116                    side.volatile_status_durations.taunt = 0;
1117                    false
1118                }
1119                _ => false,
1120            };
1121
1122            if !should_retain {
1123                instructions.push(Instruction::RemoveVolatileStatus(
1124                    RemoveVolatileStatusInstruction {
1125                        side_ref: *side_ref,
1126                        volatile_status: *pkmn_volatile_status,
1127                    },
1128                ));
1129            }
1130            should_retain
1131        });
1132
1133        // Clean up by re-setting the volatile statuses
1134        side.volatile_statuses = volatile_statuses;
1135    }
1136
1137    pub fn terrain_is_active(&self, terrain: &Terrain) -> bool {
1138        &self.terrain.terrain_type == terrain && self.terrain.turns_remaining > 0
1139    }
1140
1141    pub fn get_terrain(&self) -> Terrain {
1142        if self.terrain.turns_remaining > 0 {
1143            self.terrain.terrain_type
1144        } else {
1145            Terrain::NONE
1146        }
1147    }
1148
1149    pub fn weather_is_active(&self, weather: &Weather) -> bool {
1150        let s1_active = self.side_one.get_active_immutable();
1151        let s2_active = self.side_two.get_active_immutable();
1152        &self.weather.weather_type == weather
1153            && s1_active.ability != Abilities::AIRLOCK
1154            && s1_active.ability != Abilities::CLOUDNINE
1155            && s2_active.ability != Abilities::AIRLOCK
1156            && s2_active.ability != Abilities::CLOUDNINE
1157    }
1158
1159    fn _state_contains_any_move(&self, moves: &[Choices]) -> bool {
1160        for s in [&self.side_one, &self.side_two] {
1161            for pkmn in s.pokemon.into_iter() {
1162                for mv in pkmn.moves.into_iter() {
1163                    if moves.contains(&mv.id) {
1164                        return true;
1165                    }
1166                }
1167            }
1168        }
1169
1170        false
1171    }
1172
1173    pub fn set_damage_dealt_flag(&mut self) {
1174        if self._state_contains_any_move(&[
1175            Choices::COUNTER,
1176            Choices::MIRRORCOAT,
1177            Choices::METALBURST,
1178            Choices::COMEUPPANCE,
1179            Choices::FOCUSPUNCH,
1180            Choices::AVALANCHE,
1181        ]) {
1182            self.use_damage_dealt = true
1183        }
1184    }
1185
1186    pub fn set_last_used_move_flag(&mut self) {
1187        if self._state_contains_any_move(&[
1188            Choices::ENCORE,
1189            Choices::FAKEOUT,
1190            Choices::FIRSTIMPRESSION,
1191            Choices::BLOODMOON,
1192            Choices::GIGATONHAMMER,
1193        ]) {
1194            self.use_last_used_move = true
1195        }
1196    }
1197
1198    pub fn set_conditional_mechanics(&mut self) {
1199        /*
1200        These mechanics are not always relevant but when they are it
1201        is important that they are enabled. Enabling them all the time would
1202        suffer about a 20% performance hit.
1203        */
1204        self.set_damage_dealt_flag();
1205        self.set_last_used_move_flag();
1206    }
1207}