1use core::ops::{Deref, DerefMut};
2
3use pokedex::{
4    moves::{Accuracy, CriticalRate, MoveCategory, Power},
5    pokemon::{
6        owned::OwnedPokemon,
7        stat::{BaseStat, StatType},
8        Experience, Health,
9    },
10    types::{Effective, PokemonType},
11};
12use rand::Rng;
13
14use crate::{
15    data::BattleType,
16    moves::{
17        damage::{DamageKind, DamageResult},
18        Percent,
19    },
20    pokemon::stat::{BattleStatType, StatStages},
21};
22
23pub fn throw_move<R: rand::Rng>(random: &mut R, accuracy: Option<Accuracy>) -> bool {
25    accuracy
26        .map(|accuracy| random.gen_range(0..100) < accuracy)
27        .unwrap_or(true)
28}
29
30pub fn crit(random: &mut impl Rng, crit_rate: CriticalRate) -> bool {
31    random.gen_bool(match crit_rate {
32        0 => 0.0625, 1 => 0.125,  2 => 0.25,   3 => 1.0 / 3.0,
36        _ => 0.5, })
38}
39
40pub fn damage_range(random: &mut impl Rng) -> Percent {
41    random.gen_range(85..=100u8)
42}
43
44#[derive(Debug, Clone)]
45pub struct BattlePokemon {
46    pub p: OwnedPokemon,
47    pub stages: StatStages,
48    }
52
53impl BattlePokemon {
54    pub fn battle_exp_from(&self, type_: &BattleType) -> Experience {
64        let experience = self.exp_from();
65        let experience = match matches!(type_, BattleType::Wild) {
66            true => experience.saturating_mul(3) / 2,
67            false => experience,
68        };
69
70        #[cfg(debug_assertions)]
71        let experience = experience.saturating_mul(7);
72
73        experience
74    }
75
76    pub fn stat(&self, stat: StatType) -> BaseStat {
77        StatStages::mult(self.p.stat(stat), self.stages[BattleStatType::Basic(stat)])
78    }
79
80    pub fn damage_kind(
81        &self,
82        random: &mut impl Rng,
83        target: &Self,
84        kind: DamageKind,
85        category: MoveCategory,
86        move_type: PokemonType,
87        crit_rate: CriticalRate,
88    ) -> DamageResult<Health> {
89        let effective = target.pokemon.effective(move_type, category);
90        let crit = crit(random, crit_rate);
91
92        if let DamageKind::Power(power) = kind {
93            self.move_power_damage_random(random, target, power, category, move_type, crit)
94        } else {
95            DamageResult {
96                damage: match matches!(effective, Effective::Ineffective) {
97                    true => 0,
98                    false => match kind {
99                        DamageKind::PercentCurrent(percent) => {
100                            (target.hp() as f32 * effective.multiplier() * percent as f32 / 100.0)
101                                as Health
102                        }
103                        DamageKind::PercentMax(percent) => {
104                            (target.max_hp() as f32 * effective.multiplier() * percent as f32
105                                / 100.0) as Health
106                        }
107                        DamageKind::Constant(damage) => damage,
108                        DamageKind::Power(..) => unreachable!(),
109                    },
110                },
111                effective,
112                crit,
113            }
114        }
115    }
116
117    pub fn move_power_damage_random(
118        &self,
119        random: &mut impl Rng,
120        target: &Self,
121        power: Power,
122        category: MoveCategory,
123        move_type: PokemonType,
124        crit: bool,
125    ) -> DamageResult<Health> {
126        self.move_power_damage(
127            target,
128            power,
129            category,
130            move_type,
131            crit,
132            damage_range(random),
133        )
134    }
135
136    pub fn move_power_damage(
137        &self,
138        target: &Self,
139        power: Power,
140        category: MoveCategory,
141        move_type: PokemonType,
142        crit: bool,
143        range: u8,
144    ) -> DamageResult<Health> {
145        let effective = target.pokemon.effective(move_type, category);
146        let (attack, defense) = category.stats();
147        let attack = self.stat(attack);
148        let defense = target.stat(defense);
149        if matches!(effective, Effective::Ineffective) {
150            return DamageResult::default();
151        }
152
153        fn stab(t1: PokemonType, t2: PokemonType) -> f64 {
155            crit_dmg(t1 == t2)
156        }
157
158        fn crit_dmg(crit: bool) -> f64 {
159            match crit {
160                true => 1.5,
161                false => 1.0,
162            }
163        }
164
165        let mut e_mult = move_type
166            .effective(target.pokemon.types.primary, category)
167            .multiplier();
168        if let Some(secondary) = target.pokemon.types.secondary {
169            e_mult *= move_type.effective(secondary, category).multiplier();
170        }
171        let e_mult = e_mult as f64;
172
173        let mut damage = 2.0 * self.level as f64;
174        damage /= 5.0;
175        damage += 2.0;
176        damage = damage.floor();
177        damage *= power as f64;
178        damage *= attack as f64 / defense as f64;
179        damage = damage.floor();
180        damage /= 50.0;
181        damage = damage.floor();
182        damage += 2.0;
183
184        damage *= range as f64 / 100.0;
185        damage *= stab(self.pokemon.types.primary, move_type);
186        damage *= crit_dmg(crit);
187        damage *= e_mult;
188
189        DamageResult {
195            damage: damage.round() as _,
196            effective,
197            crit,
198        }
199    }
200}
201
202impl From<OwnedPokemon> for BattlePokemon {
203    fn from(p: OwnedPokemon) -> Self {
204        Self {
205            p,
206            stages: Default::default(),
207        }
208    }
209}
210
211impl Deref for BattlePokemon {
212    type Target = OwnedPokemon;
213
214    fn deref(&self) -> &Self::Target {
215        &self.p
216    }
217}
218
219impl DerefMut for BattlePokemon {
220    fn deref_mut(&mut self) -> &mut Self::Target {
221        &mut self.p
222    }
223}
224
225#[cfg(test)]
226mod tests {
227
228    use std::sync::Arc;
229
230    use firecore_pokedex::{
231        moves::{set::OwnedMoveSet, MoveCategory},
232        pokemon::{
233            data::{Breeding, Gender, GrowthRate, Training},
234            owned::OwnedPokemon,
235            stat::{StatSet, StatType},
236            Nature, Pokemon,
237        },
238        stat_set,
239        types::{PokemonType, Types},
240    };
241
242    use super::BattlePokemon;
243
244    #[test]
245    fn damage() {
246        let feraligatr = Arc::new(Pokemon {
247            id: 160,
248            name: "Feraligatr".to_owned(),
249            types: Types {
250                primary: PokemonType::Water,
251                secondary: None,
252            },
253            moves: vec![],
254            base: stat_set! {
255                StatType::Health => 85,
256                StatType::Attack => 105,
257                StatType::Defense => 100,
258                StatType::SpAttack => 79,
259                StatType::SpDefense => 83,
260                StatType::Speed => 78,
261            },
262            species: "Big Jaw".to_owned(),
263            evolution: None,
264            height: 23,
265            weight: 888,
266            training: Training {
267                base_exp: 239,
268                growth: GrowthRate::MediumSlow,
269            },
270            breeding: Breeding { gender: Some(6) },
271        });
272
273        let geodude = Arc::new(Pokemon {
274            id: 74,
275            name: "Geodude".to_owned(),
276            types: Types {
277                primary: PokemonType::Rock,
278                secondary: Some(PokemonType::Ground),
279            },
280            moves: vec![],
281            base: stat_set! {
282                StatType::Health => 40,
283                StatType::Attack =>  80,
284                StatType::Defense => 100,
285                StatType::SpAttack => 30,
286                StatType::SpDefense => 30,
287                StatType::Speed =>  20,
288            },
289            species: "Rock".to_owned(),
290            evolution: None,
291            height: 4,
292            weight: 20,
293            training: Training {
294                base_exp: 60,
295                growth: GrowthRate::MediumSlow,
296            },
297            breeding: Breeding { gender: Some(3) },
298        });
299
300        let mut user = OwnedPokemon {
301            pokemon: feraligatr,
302            level: 50,
303            gender: Gender::Male,
304            nature: Nature::Adamant,
305            hp: 0,
306            ivs: StatSet::uniform(15),
307            evs: StatSet::uniform(50),
308            friendship: Pokemon::default_friendship(),
309            ailment: None,
310            nickname: None,
311            moves: OwnedMoveSet::default(),
312            item: None,
313            experience: 0,
314        };
315
316        user.heal_hp(None);
317
318        let mut target = OwnedPokemon {
319            pokemon: geodude,
320            level: 10,
321            gender: Gender::Female,
322            nature: Nature::Hardy,
323            hp: 0,
324            ivs: StatSet::uniform(0),
325            evs: StatSet::uniform(0),
326            friendship: Pokemon::default_friendship(),
327            ailment: None,
328            nickname: None,
329            moves: OwnedMoveSet::default(),
330            item: None,
331            experience: 0,
332        };
333
334        target.heal_hp(None);
335
336        let user = BattlePokemon::from(user);
337
338        let target = target.into();
339
340        let damage = user
341            .move_power_damage(
342                &target,
343                80,
344                MoveCategory::Physical,
345                PokemonType::Water,
346                false,
347                100,
348            )
349            .damage;
350        assert!(damage <= 1200, "Damage passed threshold! {} > 1200", damage);
351        assert!(
352            damage >= 1100,
353            "Damage could not reach threshold! {} < 1100",
354            damage
355        );
356    }
357}