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        {
687            return true;
688        }
689        if active_pkmn.item == Items::SHEDSHELL || active_pkmn.has_type(&PokemonType::GHOST) {
690            return false;
691        } else if self
692            .volatile_statuses
693            .contains(&PokemonVolatileStatus::PARTIALLYTRAPPED)
694        {
695            return true;
696        } else if opponent_active.ability == Abilities::SHADOWTAG {
697            return true;
698        } else if opponent_active.ability == Abilities::ARENATRAP && active_pkmn.is_grounded() {
699            return true;
700        } else if opponent_active.ability == Abilities::MAGNETPULL
701            && active_pkmn.has_type(&PokemonType::STEEL)
702        {
703            return true;
704        }
705        false
706    }
707
708    pub fn num_fainted_pkmn(&self) -> i8 {
709        let mut count = 0;
710        for p in self.pokemon.into_iter() {
711            if p.hp == 0 {
712                count += 1;
713            }
714        }
715        count
716    }
717}
718
719impl State {
720    pub fn root_get_all_options(&self) -> (Vec<MoveChoice>, Vec<MoveChoice>) {
721        if self.team_preview {
722            let mut s1_options = Vec::with_capacity(6);
723            let mut s2_options = Vec::with_capacity(6);
724
725            let mut pkmn_iter = self.side_one.pokemon.into_iter();
726            while let Some(_) = pkmn_iter.next() {
727                if self.side_one.pokemon[pkmn_iter.pokemon_index].hp > 0 {
728                    s1_options.push(MoveChoice::Switch(pkmn_iter.pokemon_index));
729                }
730            }
731            let mut pkmn_iter = self.side_two.pokemon.into_iter();
732            while let Some(_) = pkmn_iter.next() {
733                if self.side_two.pokemon[pkmn_iter.pokemon_index].hp > 0 {
734                    s2_options.push(MoveChoice::Switch(pkmn_iter.pokemon_index));
735                }
736            }
737            return (s1_options, s2_options);
738        }
739
740        let (mut s1_options, mut s2_options) = self.get_all_options();
741
742        if self.side_one.force_trapped {
743            s1_options.retain(|x| match x {
744                MoveChoice::Move(_) | MoveChoice::MoveTera(_) => true,
745                MoveChoice::Switch(_) => false,
746                MoveChoice::None => true,
747            });
748        }
749        if self.side_one.slow_uturn_move {
750            s1_options.clear();
751            let encored = self
752                .side_one
753                .volatile_statuses
754                .contains(&PokemonVolatileStatus::ENCORE);
755            let taunted = self
756                .side_one
757                .volatile_statuses
758                .contains(&PokemonVolatileStatus::TAUNT);
759            self.side_one.get_active_immutable().add_available_moves(
760                &mut s1_options,
761                &self.side_one.last_used_move,
762                encored,
763                taunted,
764                self.side_one.can_use_tera(),
765            );
766        }
767
768        if self.side_two.force_trapped {
769            s2_options.retain(|x| match x {
770                MoveChoice::Move(_) | MoveChoice::MoveTera(_) => true,
771                MoveChoice::Switch(_) => false,
772                MoveChoice::None => true,
773            });
774        }
775        if self.side_two.slow_uturn_move {
776            s2_options.clear();
777            let encored = self
778                .side_two
779                .volatile_statuses
780                .contains(&PokemonVolatileStatus::ENCORE);
781            let taunted = self
782                .side_two
783                .volatile_statuses
784                .contains(&PokemonVolatileStatus::TAUNT);
785            self.side_two.get_active_immutable().add_available_moves(
786                &mut s2_options,
787                &self.side_two.last_used_move,
788                encored,
789                taunted,
790                self.side_two.can_use_tera(),
791            );
792        }
793
794        if s1_options.len() == 0 {
795            s1_options.push(MoveChoice::None);
796        }
797        if s2_options.len() == 0 {
798            s2_options.push(MoveChoice::None);
799        }
800
801        (s1_options, s2_options)
802    }
803
804    pub fn get_all_options(&self) -> (Vec<MoveChoice>, Vec<MoveChoice>) {
805        let mut side_one_options: Vec<MoveChoice> = Vec::with_capacity(9);
806        let mut side_two_options: Vec<MoveChoice> = Vec::with_capacity(9);
807
808        let side_one_active = self.side_one.get_active_immutable();
809        let side_two_active = self.side_two.get_active_immutable();
810
811        if self.side_one.force_switch {
812            self.side_one.add_switches(&mut side_one_options);
813            if self.side_two.switch_out_move_second_saved_move == Choices::NONE {
814                side_two_options.push(MoveChoice::None);
815            } else {
816                self.side_two.get_active_immutable().add_move_from_choice(
817                    &mut side_two_options,
818                    self.side_two.switch_out_move_second_saved_move,
819                );
820            }
821            return (side_one_options, side_two_options);
822        }
823
824        if self.side_two.force_switch {
825            self.side_two.add_switches(&mut side_two_options);
826            if self.side_one.switch_out_move_second_saved_move == Choices::NONE {
827                side_one_options.push(MoveChoice::None);
828            } else {
829                self.side_one.get_active_immutable().add_move_from_choice(
830                    &mut side_one_options,
831                    self.side_one.switch_out_move_second_saved_move,
832                );
833            }
834            return (side_one_options, side_two_options);
835        }
836
837        let side_one_force_switch = self.side_one.get_active_immutable().hp <= 0;
838        let side_two_force_switch = self.side_two.get_active_immutable().hp <= 0;
839
840        if side_one_force_switch && side_two_force_switch {
841            self.side_one.add_switches(&mut side_one_options);
842            self.side_two.add_switches(&mut side_two_options);
843            return (side_one_options, side_two_options);
844        }
845        if side_one_force_switch {
846            self.side_one.add_switches(&mut side_one_options);
847            side_two_options.push(MoveChoice::None);
848            return (side_one_options, side_two_options);
849        }
850        if side_two_force_switch {
851            side_one_options.push(MoveChoice::None);
852            self.side_two.add_switches(&mut side_two_options);
853            return (side_one_options, side_two_options);
854        }
855
856        if self
857            .side_one
858            .volatile_statuses
859            .contains(&PokemonVolatileStatus::MUSTRECHARGE)
860        {
861            side_one_options.push(MoveChoice::None);
862        } else if let Some(mv_index) = self.side_one.active_is_charging_move() {
863            side_one_options.push(MoveChoice::Move(mv_index));
864        } else {
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 side_one_options,
875                &self.side_one.last_used_move,
876                encored,
877                taunted,
878                self.side_one.can_use_tera(),
879            );
880            if !self.side_one.trapped(side_two_active) {
881                self.side_one.add_switches(&mut side_one_options);
882            }
883        }
884
885        if self
886            .side_two
887            .volatile_statuses
888            .contains(&PokemonVolatileStatus::MUSTRECHARGE)
889        {
890            side_two_options.push(MoveChoice::None);
891        } else if let Some(mv_index) = self.side_two.active_is_charging_move() {
892            side_two_options.push(MoveChoice::Move(mv_index));
893        } else {
894            let encored = self
895                .side_two
896                .volatile_statuses
897                .contains(&PokemonVolatileStatus::ENCORE);
898            let taunted = self
899                .side_two
900                .volatile_statuses
901                .contains(&PokemonVolatileStatus::TAUNT);
902            self.side_two.get_active_immutable().add_available_moves(
903                &mut side_two_options,
904                &self.side_two.last_used_move,
905                encored,
906                taunted,
907                self.side_two.can_use_tera(),
908            );
909            if !self.side_two.trapped(side_one_active) {
910                self.side_two.add_switches(&mut side_two_options);
911            }
912        }
913
914        if side_one_options.len() == 0 {
915            side_one_options.push(MoveChoice::None);
916        }
917        if side_two_options.len() == 0 {
918            side_two_options.push(MoveChoice::None);
919        }
920
921        (side_one_options, side_two_options)
922    }
923
924    pub fn reset_toxic_count(
925        &mut self,
926        side_ref: &SideReference,
927        vec_to_add_to: &mut Vec<Instruction>,
928    ) {
929        let side = self.get_side(side_ref);
930        if side.side_conditions.toxic_count > 0 {
931            vec_to_add_to.push(Instruction::ChangeSideCondition(
932                ChangeSideConditionInstruction {
933                    side_ref: *side_ref,
934                    side_condition: PokemonSideCondition::ToxicCount,
935                    amount: -1 * side.side_conditions.toxic_count,
936                },
937            ));
938            side.side_conditions.toxic_count = 0;
939        }
940    }
941
942    pub fn remove_volatile_statuses_on_switch(
943        &mut self,
944        side_ref: &SideReference,
945        instructions: &mut Vec<Instruction>,
946        baton_passing: bool,
947        shed_tailing: bool,
948    ) {
949        let side = self.get_side(side_ref);
950
951        // Take ownership of the current set to avoid borrow conflicts
952        // since we may need to modify the side in the loop
953        let mut volatile_statuses = std::mem::take(&mut side.volatile_statuses);
954
955        volatile_statuses.retain(|pkmn_volatile_status| {
956            let should_retain = match pkmn_volatile_status {
957                PokemonVolatileStatus::SUBSTITUTE => baton_passing || shed_tailing,
958                PokemonVolatileStatus::LEECHSEED => baton_passing,
959                PokemonVolatileStatus::TYPECHANGE => {
960                    let active = side.get_active();
961                    if active.base_types != active.types {
962                        instructions.push(Instruction::ChangeType(ChangeType {
963                            side_ref: *side_ref,
964                            new_types: active.base_types,
965                            old_types: active.types,
966                        }));
967                        active.types = active.base_types;
968                    }
969                    false
970                }
971                // While you can't switch out of a locked move you can be forced out in other ways
972                PokemonVolatileStatus::LOCKEDMOVE => {
973                    instructions.push(Instruction::ChangeVolatileStatusDuration(
974                        ChangeVolatileStatusDurationInstruction {
975                            side_ref: *side_ref,
976                            volatile_status: *pkmn_volatile_status,
977                            amount: -1 * side.volatile_status_durations.lockedmove,
978                        },
979                    ));
980                    side.volatile_status_durations.lockedmove = 0;
981                    false
982                }
983                PokemonVolatileStatus::YAWN => {
984                    instructions.push(Instruction::ChangeVolatileStatusDuration(
985                        ChangeVolatileStatusDurationInstruction {
986                            side_ref: *side_ref,
987                            volatile_status: *pkmn_volatile_status,
988                            amount: -1 * side.volatile_status_durations.yawn,
989                        },
990                    ));
991                    side.volatile_status_durations.yawn = 0;
992                    false
993                }
994                PokemonVolatileStatus::TAUNT => {
995                    instructions.push(Instruction::ChangeVolatileStatusDuration(
996                        ChangeVolatileStatusDurationInstruction {
997                            side_ref: *side_ref,
998                            volatile_status: *pkmn_volatile_status,
999                            amount: -1 * side.volatile_status_durations.taunt,
1000                        },
1001                    ));
1002                    side.volatile_status_durations.taunt = 0;
1003                    false
1004                }
1005                _ => false,
1006            };
1007
1008            if !should_retain {
1009                instructions.push(Instruction::RemoveVolatileStatus(
1010                    RemoveVolatileStatusInstruction {
1011                        side_ref: *side_ref,
1012                        volatile_status: *pkmn_volatile_status,
1013                    },
1014                ));
1015            }
1016            should_retain
1017        });
1018
1019        // Clean up by re-setting the volatile statuses
1020        side.volatile_statuses = volatile_statuses;
1021    }
1022
1023    pub fn terrain_is_active(&self, terrain: &Terrain) -> bool {
1024        &self.terrain.terrain_type == terrain && self.terrain.turns_remaining > 0
1025    }
1026
1027    pub fn weather_is_active(&self, weather: &Weather) -> bool {
1028        let s1_active = self.side_one.get_active_immutable();
1029        let s2_active = self.side_two.get_active_immutable();
1030        &self.weather.weather_type == weather
1031            && s1_active.ability != Abilities::AIRLOCK
1032            && s1_active.ability != Abilities::CLOUDNINE
1033            && s2_active.ability != Abilities::AIRLOCK
1034            && s2_active.ability != Abilities::CLOUDNINE
1035    }
1036
1037    fn _state_contains_any_move(&self, moves: &[Choices]) -> bool {
1038        for s in [&self.side_one, &self.side_two] {
1039            for pkmn in s.pokemon.into_iter() {
1040                for mv in pkmn.moves.into_iter() {
1041                    if moves.contains(&mv.id) {
1042                        return true;
1043                    }
1044                }
1045            }
1046        }
1047
1048        false
1049    }
1050
1051    pub fn set_damage_dealt_flag(&mut self) {
1052        if self._state_contains_any_move(&[
1053            Choices::COUNTER,
1054            Choices::MIRRORCOAT,
1055            Choices::METALBURST,
1056            Choices::COMEUPPANCE,
1057            Choices::FOCUSPUNCH,
1058        ]) {
1059            self.use_damage_dealt = true
1060        }
1061    }
1062
1063    pub fn set_last_used_move_flag(&mut self) {
1064        if self._state_contains_any_move(&[
1065            Choices::ENCORE,
1066            Choices::FAKEOUT,
1067            Choices::FIRSTIMPRESSION,
1068            Choices::BLOODMOON,
1069            Choices::GIGATONHAMMER,
1070        ]) {
1071            self.use_last_used_move = true
1072        }
1073    }
1074
1075    pub fn set_conditional_mechanics(&mut self) {
1076        /*
1077        These mechanics are not always relevant but when they are it
1078        is important that they are enabled. Enabling them all the time would
1079        suffer about a 20% performance hit.
1080        */
1081        self.set_damage_dealt_flag();
1082        self.set_last_used_move_flag();
1083    }
1084}