1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Structs and functions to interact with the data of monsters in a general context.

use crate::api::enums::MonsterGender;
use crate::ffi;
use crate::ffi::GetLowKickMultiplier;
use alloc::vec::Vec;
use fixed::types::I24F8;

/// A monster species ID with associated methods to get metadata.
///
/// Use the associated constants or the [`Self::new`] method to get instances of this.
pub type MonsterSpeciesId = ffi::monster_id;
impl Copy for MonsterSpeciesId {}

/// This impl provides general metadata about monster species in the game.
impl MonsterSpeciesId {
    /// Returns the ID struct for the monster species with the given ID.
    ///
    /// # Safety
    /// The caller must make sure the ID is valid (refers to an existing monster species),
    /// otherwise this is UB.
    pub const unsafe fn new(id: u32) -> Self {
        Self(id)
    }

    /// Returns the ID of this monster.
    pub const fn id(&self) -> u32 {
        self.0
    }

    /// Checks if the specified monster ID corresponds to any of the pokémon that have multiple
    /// forms and returns the ID of the base form if so. If it doesn't, the same ID is returned.
    ///
    /// Some of the monsters included in the check are Unown, Cherrim and Deoxys.
    pub fn base_form(&self) -> MonsterSpeciesId {
        unsafe { ffi::GetBaseForm(*self) }
    }

    /// Returns the ID of the first form of the specified monster if the specified ID corresponds
    /// to a secondary form with female gender and the first form has male gender.
    ///
    /// If those conditions don't meet, returns the same ID unchanged.
    pub fn base_gender_form(&self) -> MonsterSpeciesId {
        unsafe { ffi::FemaleToMaleForm(*self) }
    }

    /// Returns the gender field of the monster.
    pub fn gender(&self) -> MonsterGender {
        // TODO: Enum.
        unsafe { ffi::GetMonsterGender(*self).into() }
    }

    /// Returns the sprite size of the monster. If the size is between 1 and 6,
    /// 6 will be returned.
    pub fn sprite_size(&self) -> u8 {
        // TODO: Enum.
        unsafe { ffi::GetSpriteSize(*self) }
    }

    /// Returns the sprite file size of the monster.
    pub fn sprite_file_size(&self) -> u8 {
        unsafe { ffi::GetSpriteFileSize(*self) }
    }

    /// Returns the pre-evolution id of a monster given its ID.
    pub fn pre_evolution(&self) -> Self {
        unsafe { ffi::GetMonsterPreEvolution(*self) }
    }

    /// Returns a list of all the possible evolutions.
    ///
    /// This will panic if the monster has more than 32 evolutions.
    ///
    /// # Arguments
    /// * `ignore_sprite_size` - True to skip the check that prevents returning monsters with a
    ///                          different sprite size than the current one.
    /// * `include_shedinja`   - True to skip the check that prevents Shedinja from being counted
    ///                          as a potential evolution.
    pub fn evolutions(&self, ignore_sprite_size: bool, include_shedinja: bool) -> Vec<Self> {
        const MAX_EVOLUTIONS: i32 = 32;
        let mut output_list = [ffi::monster_id(0); MAX_EVOLUTIONS as usize];
        let count = unsafe {
            ffi::GetEvolutions(
                *self,
                output_list.as_mut_ptr(),
                ignore_sprite_size as ffi::bool_,
                include_shedinja as ffi::bool_,
            )
        };
        if count > MAX_EVOLUTIONS {
            // uh oh. Memory is corrupted now, so time to bail.
            // THIS PANIC IS NOT UNWIND SAFE (not that it matters).
            panic!("Monster has more than {} evolutions.", MAX_EVOLUTIONS);
        }
        output_list.into_iter().take(count as usize).collect()
    }

    /// Checks if this is an Unown.
    pub fn is_unown(&self) -> bool {
        unsafe { ffi::IsUnown(*self) > 0 }
    }

    /// Checks if this is a Shaymin.
    pub fn is_shaymin(&self) -> bool {
        unsafe { ffi::IsShaymin(*self) > 0 }
    }

    /// Checks if this is a Castform.
    pub fn is_castform(&self) -> bool {
        unsafe { ffi::IsCastform(*self) > 0 }
    }

    /// Checks if this is a Cherrim.
    pub fn is_cherrim(&self) -> bool {
        unsafe { ffi::IsCherrim(*self) > 0 }
    }

    /// Checks if this is a Deoxys.
    pub fn is_deoxys(&self) -> bool {
        unsafe { ffi::IsDeoxys(*self) > 0 }
    }

    /// Returns the flag that determines if a monster can move in dungeons.
    pub fn get_can_move_flag(&self) -> bool {
        unsafe { ffi::GetCanMoveFlag(*self) > 0 }
    }

    /// Checks if this monster is contained in the [`ffi::MISSION_BANNED_MONSTERS`] array.
    /// The function converts the ID by calling [`Self::base_form`] and
    /// [`Self::base_gender_form`] first.
    pub fn is_mission_allowed(&self) -> bool {
        unsafe { ffi::IsMonsterMissionAllowed(*self) > 0 }
    }

    /// Checks if the specified monster should be allowed to be part of a mission (probably as the
    /// client or the target), accounting for the progress on the story.
    ///
    /// If `PERFORMANCE_PROGRESS_FLAG[9]` is true, the function returns true.
    /// If it isn't, the function checks if the specified monster is contained in the
    /// [`ffi::MISSION_BANNED_STORY_MONSTERS`] array, or if it corresponds to the ID of the player
    /// or the  partner.
    ///
    /// The function converts the ID by calling [`Self::base_form`] and [`Self::base_gender_form`]
    /// first.
    pub fn is_mission_allowed_story(&self) -> bool {
        unsafe { ffi::IsMonsterMissionAllowedStory(*self) > 0 }
    }

    /// Returns whether this monster can be used (probably as the client or as the target) when
    /// generating a mission.
    ///
    /// Excluded monsters include those that haven't been fought in dungeons yet, the second form
    /// of certain monsters and, if `PERFORMANCE_PROGRESS_FLAG[9]` is 0, monsters in
    /// [`ffi::MISSION_BANNED_MONSTERS`, the species of the player and the species of the partner.
    ///
    pub fn can_be_used_for_mission(
        &self,
        exclude_monsters_in_mission_banned_monsters: bool,
    ) -> bool {
        unsafe {
            ffi::CanMonsterBeUsedForMission(
                *self,
                exclude_monsters_in_mission_banned_monsters as ffi::bool_,
            ) > 0
        }
    }

    /// Gets the Low Kick (and Grass Knot) damage multiplier for the given species.
    pub fn get_low_kick_multiplier(&self) -> I24F8 {
        unsafe { I24F8::from_num(GetLowKickMultiplier(*self)) }
    }
}

impl From<MonsterSpeciesId> for u32 {
    fn from(v: MonsterSpeciesId) -> Self {
        v.0
    }
}