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