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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
//! Functions related to getting information about monster moves.

use crate::api::_common::get_faint_reason;
use crate::api::dungeon_mode::DungeonEntity;
use crate::api::enums::{MoveCategory, Weather};
use crate::api::items::ItemId;
use crate::api::types::MonsterTypeId;
use crate::ffi;

/// A monster move.
pub type Move = ffi::move_;

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

#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Copy)]
/// Move target (i.e., who does a move affect when used?).
pub enum MoveTarget {
    Enemies = ffi::move_target::TARGET_ENEMIES,
    Party = ffi::move_target::TARGET_PARTY,
    All = ffi::move_target::TARGET_ALL,
    User = ffi::move_target::TARGET_USER,
    EnemiesAfterCharging = ffi::move_target::TARGET_ENEMIES_AFTER_CHARGING,
    AllExceptUser = ffi::move_target::TARGET_ALL_EXCEPT_USER,
    Teammates = ffi::move_target::TARGET_TEAMMATES,
    Special = ffi::move_target::TARGET_SPECIAL,
}

impl TryInto<MoveTarget> for ffi::move_target::Type {
    type Error = ();

    fn try_into(self) -> Result<MoveTarget, Self::Error> {
        match self {
            ffi::move_target::TARGET_ENEMIES => Ok(MoveTarget::Enemies),
            ffi::move_target::TARGET_PARTY => Ok(MoveTarget::Party),
            ffi::move_target::TARGET_ALL => Ok(MoveTarget::All),
            ffi::move_target::TARGET_USER => Ok(MoveTarget::User),
            ffi::move_target::TARGET_ENEMIES_AFTER_CHARGING => Ok(MoveTarget::EnemiesAfterCharging),
            ffi::move_target::TARGET_ALL_EXCEPT_USER => Ok(MoveTarget::AllExceptUser),
            ffi::move_target::TARGET_TEAMMATES => Ok(MoveTarget::Teammates),
            ffi::move_target::TARGET_SPECIAL => Ok(MoveTarget::Special),
            _ => Err(()),
        }
    }
}

#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Copy)]
/// Move range.
pub enum MoveRange {
    Front = ffi::move_range::RANGE_FRONT,
    FrontAndSides = ffi::move_range::RANGE_FRONT_AND_SIDES,
    Nearby = ffi::move_range::RANGE_NEARBY,
    Room = ffi::move_range::RANGE_ROOM,
    Front2 = ffi::move_range::RANGE_FRONT_2,
    Front10 = ffi::move_range::RANGE_FRONT_10,
    Floor = ffi::move_range::RANGE_FLOOR,
    User = ffi::move_range::RANGE_USER,
    FrontWithCornerCutting = ffi::move_range::RANGE_FRONT_WITH_CORNER_CUTTING,
    Front2WithCornerCutting = ffi::move_range::RANGE_FRONT_2_WITH_CORNER_CUTTING,
    Special = ffi::move_range::RANGE_SPECIAL,
}

impl TryInto<MoveRange> for ffi::move_range::Type {
    type Error = ();

    fn try_into(self) -> Result<MoveRange, Self::Error> {
        match self {
            ffi::move_range::RANGE_FRONT => Ok(MoveRange::Front),
            ffi::move_range::RANGE_FRONT_AND_SIDES => Ok(MoveRange::FrontAndSides),
            ffi::move_range::RANGE_NEARBY => Ok(MoveRange::Nearby),
            ffi::move_range::RANGE_ROOM => Ok(MoveRange::Room),
            ffi::move_range::RANGE_FRONT_2 => Ok(MoveRange::Front2),
            ffi::move_range::RANGE_FRONT_10 => Ok(MoveRange::Front10),
            ffi::move_range::RANGE_FLOOR => Ok(MoveRange::Floor),
            ffi::move_range::RANGE_USER => Ok(MoveRange::User),
            ffi::move_range::RANGE_FRONT_WITH_CORNER_CUTTING => {
                Ok(MoveRange::FrontWithCornerCutting)
            }
            ffi::move_range::RANGE_FRONT_2_WITH_CORNER_CUTTING => {
                Ok(MoveRange::Front2WithCornerCutting)
            }
            ffi::move_range::RANGE_SPECIAL => Ok(MoveRange::Special),
            _ => Err(()),
        }
    }
}

#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Copy)]
/// Conditions checked by the AI to determine when a move should be used.
/// It does not affect how the move works.
pub enum MoveAiCondition {
    None = ffi::move_ai_condition::AI_CONDITION_NONE,
    /// The AI will consider a target elegible wirh a chance equal to the
    /// move's "ai_condition_random_chance" value.
    Random = ffi::move_ai_condition::AI_CONDITION_RANDOM,
    /// Target has HP <= 25%
    Hp25 = ffi::move_ai_condition::AI_CONDITION_HP_25,
    /// Target has a negative status condition
    Status = ffi::move_ai_condition::AI_CONDITION_STATUS,
    /// Target is asleep, napping or in a nightmare
    Asleep = ffi::move_ai_condition::AI_CONDITION_ASLEEP,
    /// Target is ghost-type and not exposed
    Ghost = ffi::move_ai_condition::AI_CONDITION_GHOST,
    /// Target has HP <= 25% or a negative status condition
    Hp25OrStatus = ffi::move_ai_condition::AI_CONDITION_HP_25_OR_STATUS,
}

impl TryInto<MoveAiCondition> for ffi::move_ai_condition::Type {
    type Error = ();

    fn try_into(self) -> Result<MoveAiCondition, Self::Error> {
        match self {
            ffi::move_ai_condition::AI_CONDITION_NONE => Ok(MoveAiCondition::None),
            ffi::move_ai_condition::AI_CONDITION_RANDOM => Ok(MoveAiCondition::Random),
            ffi::move_ai_condition::AI_CONDITION_HP_25 => Ok(MoveAiCondition::Hp25),
            ffi::move_ai_condition::AI_CONDITION_STATUS => Ok(MoveAiCondition::Status),
            ffi::move_ai_condition::AI_CONDITION_ASLEEP => Ok(MoveAiCondition::Asleep),
            ffi::move_ai_condition::AI_CONDITION_GHOST => Ok(MoveAiCondition::Ghost),
            ffi::move_ai_condition::AI_CONDITION_HP_25_OR_STATUS => {
                Ok(MoveAiCondition::Hp25OrStatus)
            }
            _ => Err(()),
        }
    }
}

/// Range, target and AI data for a move.
/// Values are None, if they are invalid / non-standard.
pub struct MoveTargetAndRange {
    pub target: Option<MoveTarget>,
    pub range: Option<MoveRange>,
    pub ai_condition: Option<MoveAiCondition>,
    pub unused: u16,
}

impl From<ffi::move_target_and_range> for MoveTargetAndRange {
    fn from(tr: ffi::move_target_and_range) -> Self {
        MoveTargetAndRange {
            target: tr.target().try_into().ok(),
            range: tr.range().try_into().ok(),
            ai_condition: tr.ai_condition().try_into().ok(),
            unused: tr.unused(),
        }
    }
}

/// Will fail, if any values are None in MoveTargetAndRange.
impl TryFrom<MoveTargetAndRange> for ffi::move_target_and_range {
    type Error = ();

    fn try_from(value: MoveTargetAndRange) -> Result<Self, Self::Error> {
        if value.target.is_none() || value.range.is_none() || value.ai_condition.is_none() {
            return Err(());
        }
        Ok(ffi::move_target_and_range {
            _bitfield_align_1: [],
            _bitfield_1: ffi::move_target_and_range::new_bitfield_1(
                value.target.unwrap() as ffi::move_target::Type,
                value.range.unwrap() as ffi::move_range::Type,
                value.ai_condition.unwrap() as ffi::move_ai_condition::Type,
                value.unused,
            ),
        })
    }
}

/// See [`MoveId`] for additional metadata methods.
impl Move {
    /// Initializes a move info struct.
    ///
    /// This sets f_exists and f_enabled_for_ai on the flags, the ID to the given ID,
    /// the PP to the max PP for the move ID, and the ginseng boost to 0.
    ///
    /// # Safety
    /// The pointer must point to a valid Move or uninitialized Move.
    pub unsafe fn init(move_pnt: *mut Self, move_id: MoveId) {
        ffi::InitMove(move_pnt, move_id)
    }
    /// Returns the move ID
    pub fn id(&self) -> MoveId {
        self.id.val()
    }

    /// Gets the move target-and-range field. See struct move_target_and_range in the C headers.
    pub fn get_target_and_range(&self, is_ai: bool) -> MoveTargetAndRange {
        unsafe { ffi::GetMoveTargetAndRange(force_mut_ptr!(self), is_ai as ffi::bool_) }.into()
    }

    /// Gets the base power of the move.
    pub fn get_base_power(&self) -> i32 {
        unsafe { ffi::GetMoveBasePower(force_mut_ptr!(self)) }
    }

    /// Gets the maximum PP for the move.
    ///
    /// Returns max PP for the given move, capped at 99.
    pub fn get_max_pp(&self) -> i32 {
        unsafe { ffi::GetMaxPp(force_mut_ptr!(self)) }
    }

    /// Gets the critical hit chance of the move.
    pub fn get_crit_chance(&self) -> i32 {
        unsafe { ffi::GetMoveCritChance(force_mut_ptr!(self)) }
    }

    /// Returns whether a move's range string is 19 ("User").
    pub fn is_move_range_string_19(&self) -> bool {
        unsafe { ffi::IsMoveRangeString19(force_mut_ptr!(self)) > 0 }
    }

    /// Gets the type of the move.
    pub fn get_type(&self) -> MonsterTypeId {
        unsafe { ffi::GetMoveType(force_mut_ptr!(self)) }
    }

    /// Gets the AI weight of a move.
    pub fn get_ai_weight(&self) -> u8 {
        unsafe { ffi::GetMoveAiWeight(force_mut_ptr!(self)) }
    }

    /// Gets the accuracy1 value for the move.
    pub fn get_accuracy1(&self) -> u8 {
        unsafe { ffi::GetMoveAccuracyOrAiChance(force_mut_ptr!(self), 0) }
    }

    /// Gets the accuracy2 value for the move.
    pub fn get_accuracy2(&self) -> u8 {
        unsafe { ffi::GetMoveAccuracyOrAiChance(force_mut_ptr!(self), 1) }
    }

    /// Gets the `ai_condition_random_chance` value for the move.
    pub fn get_ai_condition_random_chance(&self) -> u8 {
        unsafe { ffi::GetMoveAccuracyOrAiChance(force_mut_ptr!(self), 2) }
    }

    /// Checks whether a moved used by a monster should play its alternative animation.
    /// Includes checks for Curse, Snore, Sleep Talk, Solar Beam and 2-turn moves.
    pub fn should_play_alternative_animation(&self, user: &DungeonEntity) -> bool {
        unsafe {
            ffi::ShouldMovePlayAlternativeAnimation(force_mut_ptr!(self), force_mut_ptr!(user)) > 0
        }
    }

    /// Returns the move animation ID that should be played for a move.
    /// It contains a check for weather ball. After that, if the parameter
    /// `should_play_alternative_animation` is false, the move ID is returned.
    ///
    /// `should_play_alternative_animation` can be retrieved with
    /// [`Self::should_play_alternative_animation`].
    ///
    /// If it's true, there's a bunch of manual ID checks that result on a certain hardcoded return
    /// value.
    pub fn get_animation_id(
        &self,
        apparent_weather: Weather,
        should_play_alternative_animation: bool,
    ) -> u16 {
        unsafe {
            ffi::GetMoveAnimationId(
                force_mut_ptr!(self),
                apparent_weather as ffi::weather_id::Type,
                should_play_alternative_animation as ffi::bool_,
            )
        }
    }
}

/// This impl provides general metadata about moves in the game.
///
/// See [`Move`] for additional metadata methods.
impl MoveId {
    /// Returns the ID struct for the move with the given ID.
    ///
    /// # Safety
    /// The caller must make sure the ID is valid (refers to an existing move),
    /// otherwise this is UB.
    pub const unsafe fn new(id: u32) -> Self {
        Self(id)
    }

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

    /// Checks if the move is a recoil move (affected by Reckless).
    pub fn is_recoil_move(&self) -> bool {
        unsafe { ffi::IsRecoilMove(*self) > 0 }
    }

    /// Checks if the move is a punch move (affected by Iron Fist).
    pub fn is_punch_move(&self) -> bool {
        unsafe { ffi::IsPunchMove(*self) > 0 }
    }

    /// Gets a move's category (physical, special, status). Returns None if the category is invalid.
    pub fn get_category(&self) -> Option<MoveCategory> {
        unsafe { ffi::GetMoveCategory(*self) }.try_into().ok()
    }

    /// Gets the faint reason code (see HandleFaint) for a given move-item combination.
    ///         
    /// If there's no item, the reason code is the move ID. If the item is an orb, return
    /// FAINT_REASON_ORB_ITEM. Otherwise, return FAINT_REASON_NON_ORB_ITEM.
    pub fn get_faint_reason(&self, item_id: ItemId) -> ffi::faint_reason {
        get_faint_reason(*self, item_id)
    }
}

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