Skip to main content

battler_data/moves/
move_target.rs

1use serde_string_enum::{
2    DeserializeLabeledStringEnum,
3    SerializeLabeledStringEnum,
4};
5
6/// The acceptable target(s) of a move.
7///
8/// In this enum, the following terms are used:
9/// - "Adjacent" = A reachable Mon.
10/// - "Ally" - A Mon on the same side.
11/// - "Foe" - A Mon on the opposite side.
12/// - "Side" - The side of a battle, not any particular Mon on that side.
13/// - "Team" - All unfainted Mons on a team.
14/// - "User" - The user of a move.
15#[derive(
16    Debug,
17    Default,
18    Clone,
19    Copy,
20    PartialEq,
21    Eq,
22    Hash,
23    SerializeLabeledStringEnum,
24    DeserializeLabeledStringEnum,
25)]
26pub enum MoveTarget {
27    /// An adjacent ally.
28    #[string = "AdjacentAlly"]
29    AdjacentAlly,
30    /// The user or its ally.
31    #[string = "AdjacentAllyOrUser"]
32    AdjacentAllyOrUser,
33    /// An adjacent foe.
34    #[string = "AdjacentFoe"]
35    AdjacentFoe,
36    /// All Mons at once.
37    #[string = "All"]
38    All,
39    /// All adjacent mons (including allies).
40    #[string = "AllAdjacent"]
41    AllAdjacent,
42    /// All adjacent foes.
43    ///
44    /// Also known as a spread move.
45    #[string = "AllAdjacentFoes"]
46    AllAdjacentFoes,
47    /// All active Mons on the user's team.
48    #[string = "Allies"]
49    Allies,
50    /// The user's side.
51    #[string = "AllySide"]
52    AllySide,
53    /// All unfainted Mons on the user's team.
54    #[string = "AllyTeam"]
55    AllyTeam,
56    /// Any other active Mon.
57    #[string = "Any"]
58    Any,
59    /// The field.
60    #[string = "Field"]
61    Field,
62    /// The foe's side.
63    #[string = "FoeSide"]
64    FoeSide,
65    /// One adjacent Mon of the user's choice.
66    ///
67    /// Could also be called "Adjacent."
68    #[string = "Normal"]
69    #[default]
70    Normal,
71    /// Any adjacent foe chosen at random.
72    #[string = "RandomNormal"]
73    RandomNormal,
74    /// The for that damaged the user.
75    #[string = "Scripted"]
76    Scripted,
77    /// The user of the move.
78    #[string = "User"]
79    User,
80}
81
82impl MoveTarget {
83    /// Is the move target choosable?
84    pub fn choosable(&self) -> bool {
85        match self {
86            Self::Normal
87            | Self::Any
88            | Self::AdjacentAlly
89            | Self::AdjacentAllyOrUser
90            | Self::AdjacentFoe => true,
91            _ => false,
92        }
93    }
94
95    /// Does the move require a single target?
96    pub fn requires_target(&self) -> bool {
97        match self {
98            Self::All
99            | Self::AllAdjacent
100            | Self::AllAdjacentFoes
101            | Self::Allies
102            | Self::AllySide
103            | Self::AllyTeam
104            | Self::Field
105            | Self::FoeSide
106            | Self::Scripted => false,
107            _ => true,
108        }
109    }
110
111    /// Does the move have a single target?
112    pub fn has_single_target(&self) -> bool {
113        match self {
114            Self::All
115            | Self::AllAdjacent
116            | Self::AllAdjacentFoes
117            | Self::Allies
118            | Self::AllySide
119            | Self::AllyTeam
120            | Self::Field
121            | Self::FoeSide => false,
122            _ => true,
123        }
124    }
125
126    /// Does the move affect Mons directly?
127    pub fn affects_mons_directly(&self) -> bool {
128        match self {
129            Self::AllySide | Self::AllyTeam | Self::Field | Self::FoeSide => false,
130            _ => true,
131        }
132    }
133
134    /// Can the move target the user?
135    pub fn can_target_user(&self) -> bool {
136        match self {
137            Self::User
138            | Self::All
139            | Self::Allies
140            | Self::AllySide
141            | Self::AllyTeam
142            | Self::AdjacentAllyOrUser => true,
143            _ => false,
144        }
145    }
146
147    /// Can the move target foes?
148    pub fn can_target_foes(&self) -> bool {
149        match self {
150            Self::AdjacentAlly
151            | Self::AdjacentAllyOrUser
152            | Self::Allies
153            | Self::AllySide
154            | Self::AllyTeam => false,
155            _ => true,
156        }
157    }
158
159    /// Can the move only target adjacent Mons?
160    pub fn is_adjacent_only(&self) -> bool {
161        match self {
162            Self::AdjacentAlly
163            | Self::AdjacentAllyOrUser
164            | Self::AdjacentFoe
165            | Self::AllAdjacent
166            | Self::AllAdjacentFoes
167            | Self::Normal
168            | Self::RandomNormal => true,
169            _ => false,
170        }
171    }
172
173    /// Is the target randomly selected?
174    pub fn is_random(&self) -> bool {
175        match self {
176            Self::RandomNormal => true,
177            _ => false,
178        }
179    }
180
181    /// Validates the relative target position.
182    pub fn valid_target(&self, relative_target: isize, adjacency_reach: u8) -> bool {
183        match self {
184            Self::AdjacentAlly
185            | Self::AdjacentAllyOrUser
186            | Self::AdjacentFoe
187            | Self::Any
188            | Self::Normal
189            | Self::RandomNormal
190            | Self::Scripted
191            | Self::User => self.is_affected(relative_target, adjacency_reach),
192            _ => false,
193        }
194    }
195
196    /// Checks if the Mon at the relative target is affected by the move.
197    pub fn is_affected(&self, relative_target: isize, adjacency_reach: u8) -> bool {
198        let is_self = relative_target == 0;
199        let is_foe = relative_target > 0;
200        let is_adjacent = if relative_target > 0 {
201            // Foe side, at most two steps away.
202            relative_target <= adjacency_reach as isize
203        } else {
204            // Same side, at most one step away.
205            relative_target == -(adjacency_reach as isize) + 1
206        };
207
208        match self {
209            Self::AdjacentAlly => is_adjacent && !is_foe,
210            Self::AdjacentAllyOrUser => (is_adjacent && !is_foe) || is_self,
211            Self::AdjacentFoe | Self::AllAdjacentFoes => is_adjacent && is_foe,
212            Self::All => true,
213            Self::AllAdjacent => is_adjacent,
214            Self::Allies => !is_foe && !is_self,
215            Self::AllySide | Self::AllyTeam => !is_foe,
216            Self::Any => !is_self,
217            Self::Field => true,
218            Self::FoeSide => is_foe,
219            Self::Normal | Self::RandomNormal | Self::Scripted => is_adjacent,
220            Self::User => is_self,
221        }
222    }
223}
224
225#[cfg(test)]
226mod move_target_test {
227    use crate::{
228        moves::MoveTarget,
229        test_util::{
230            test_string_deserialization,
231            test_string_serialization,
232        },
233    };
234
235    #[test]
236    fn serializes_to_string() {
237        test_string_serialization(MoveTarget::AdjacentAlly, "AdjacentAlly");
238        test_string_serialization(MoveTarget::AllAdjacentFoes, "AllAdjacentFoes");
239        test_string_serialization(MoveTarget::RandomNormal, "RandomNormal");
240    }
241
242    #[test]
243    fn deserializes_lowercase() {
244        test_string_deserialization("normal", MoveTarget::Normal);
245        test_string_deserialization("allyTeam", MoveTarget::AllyTeam);
246        test_string_deserialization("foeside", MoveTarget::FoeSide);
247    }
248
249    #[test]
250    fn choosable() {
251        assert!(MoveTarget::Normal.choosable());
252        assert!(MoveTarget::Any.choosable());
253        assert!(MoveTarget::AdjacentAlly.choosable());
254        assert!(MoveTarget::AdjacentAllyOrUser.choosable());
255        assert!(MoveTarget::AdjacentFoe.choosable());
256        assert!(!MoveTarget::RandomNormal.choosable());
257        assert!(!MoveTarget::All.choosable());
258        assert!(!MoveTarget::AllAdjacentFoes.choosable());
259    }
260
261    #[test]
262    fn valid_target_any_adjacent() {
263        assert!(MoveTarget::RandomNormal.valid_target(1, 2));
264        assert!(MoveTarget::Scripted.valid_target(1, 2));
265        assert!(MoveTarget::Normal.valid_target(1, 2));
266        assert!(MoveTarget::RandomNormal.valid_target(2, 2));
267        assert!(MoveTarget::Scripted.valid_target(2, 2));
268        assert!(MoveTarget::Normal.valid_target(2, 2));
269        assert!(MoveTarget::RandomNormal.valid_target(-1, 2));
270        assert!(MoveTarget::Scripted.valid_target(-1, 2));
271        assert!(MoveTarget::Normal.valid_target(-1, 2));
272
273        assert!(!MoveTarget::Normal.valid_target(0, 2));
274        assert!(!MoveTarget::Normal.valid_target(3, 2));
275        assert!(!MoveTarget::Normal.valid_target(-2, 2));
276
277        assert!(MoveTarget::Normal.valid_target(3, 3));
278        assert!(MoveTarget::Normal.valid_target(-2, 3));
279        assert!(!MoveTarget::Normal.valid_target(4, 3));
280        assert!(!MoveTarget::Normal.valid_target(-3, 3));
281    }
282
283    #[test]
284    fn valid_target_adjacent_ally() {
285        assert!(MoveTarget::AdjacentAlly.valid_target(-1, 2));
286
287        assert!(!MoveTarget::AdjacentAlly.valid_target(0, 2));
288        assert!(!MoveTarget::AdjacentAlly.valid_target(1, 2));
289        assert!(!MoveTarget::AdjacentAlly.valid_target(2, 2));
290        assert!(!MoveTarget::AdjacentAlly.valid_target(3, 2));
291        assert!(!MoveTarget::AdjacentAlly.valid_target(-2, 2));
292
293        assert!(MoveTarget::AdjacentAlly.valid_target(-2, 3));
294        assert!(!MoveTarget::AdjacentAlly.valid_target(-3, 3));
295    }
296
297    #[test]
298    fn valid_target_adjacent_ally_or_user() {
299        assert!(MoveTarget::AdjacentAllyOrUser.valid_target(-1, 2));
300        assert!(MoveTarget::AdjacentAllyOrUser.valid_target(0, 2));
301
302        assert!(!MoveTarget::AdjacentAllyOrUser.valid_target(1, 2));
303        assert!(!MoveTarget::AdjacentAllyOrUser.valid_target(2, 2));
304        assert!(!MoveTarget::AdjacentAllyOrUser.valid_target(3, 2));
305        assert!(!MoveTarget::AdjacentAllyOrUser.valid_target(-2, 2));
306
307        assert!(MoveTarget::AdjacentAllyOrUser.valid_target(-2, 3));
308        assert!(!MoveTarget::AdjacentAllyOrUser.valid_target(-3, 3));
309    }
310
311    #[test]
312    fn valid_target_adjacent_foe() {
313        assert!(MoveTarget::AdjacentFoe.valid_target(1, 2));
314        assert!(MoveTarget::AdjacentFoe.valid_target(2, 2));
315
316        assert!(!MoveTarget::AdjacentFoe.valid_target(0, 2));
317        assert!(!MoveTarget::AdjacentFoe.valid_target(3, 2));
318        assert!(!MoveTarget::AdjacentFoe.valid_target(-1, 2));
319        assert!(!MoveTarget::AdjacentFoe.valid_target(-2, 2));
320
321        assert!(MoveTarget::AdjacentFoe.valid_target(3, 3));
322        assert!(!MoveTarget::AdjacentFoe.valid_target(-2, 3));
323        assert!(!MoveTarget::AdjacentFoe.valid_target(4, 3));
324        assert!(!MoveTarget::AdjacentFoe.valid_target(-3, 3));
325    }
326
327    #[test]
328    fn valid_target_any_but_user() {
329        assert!(MoveTarget::Any.valid_target(1, 2));
330        assert!(MoveTarget::Any.valid_target(2, 2));
331        assert!(MoveTarget::Any.valid_target(3, 2));
332        assert!(MoveTarget::Any.valid_target(-1, 2));
333        assert!(MoveTarget::Any.valid_target(-2, 2));
334
335        assert!(!MoveTarget::Any.valid_target(0, 2));
336    }
337
338    #[test]
339    fn valid_target_user() {
340        assert!(MoveTarget::User.valid_target(0, 2));
341
342        assert!(!MoveTarget::User.valid_target(1, 2));
343        assert!(!MoveTarget::User.valid_target(2, 2));
344        assert!(!MoveTarget::User.valid_target(3, 2));
345        assert!(!MoveTarget::User.valid_target(-1, 2));
346        assert!(!MoveTarget::User.valid_target(-2, 2));
347    }
348}