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