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 ((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 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 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 }
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 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 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 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 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 self.set_damage_dealt_flag();
1082 self.set_last_used_move_flag();
1083 }
1084}