battler/effect/
effect.rs

1use std::ops::{
2    Deref,
3    DerefMut,
4};
5
6use anyhow::Result;
7use battler_data::{
8    Id,
9    Identifiable,
10};
11use zone_alloc::{
12    ElementRef,
13    ElementRefMut,
14};
15
16use crate::{
17    abilities::Ability,
18    battle::{
19        Context,
20        MoveHandle,
21    },
22    common::split_once_optional,
23    conditions::Condition,
24    config::Clause,
25    effect::fxlang,
26    items::Item,
27    mons::Species,
28    moves::{
29        Move,
30        MoveHitEffectType,
31    },
32};
33
34/// Similar to [`MaybeOwned`][`crate::common::MaybeOwned`], but for an optional mutable reference
35/// backed by a [`ElementRefMut`].
36///
37/// If the reference is owned the [`ElementRefMut`] is stored directly. If the reference is unowned,
38/// it is stored directly with the assumption that it originates from an [`ElementRefMut`].
39#[allow(dead_code)]
40pub enum MaybeElementRef<'a, T> {
41    Owned(ElementRefMut<'a, T>),
42    Unowned(&'a mut T),
43}
44
45impl<T> Deref for MaybeElementRef<'_, T> {
46    type Target = T;
47    fn deref(&self) -> &Self::Target {
48        match self {
49            Self::Owned(val) => val.deref(),
50            Self::Unowned(val) => val,
51        }
52    }
53}
54
55impl<T> DerefMut for MaybeElementRef<'_, T> {
56    fn deref_mut(&mut self) -> &mut Self::Target {
57        match self {
58            Self::Owned(val) => val.deref_mut(),
59            Self::Unowned(val) => val,
60        }
61    }
62}
63
64impl<T> AsMut<T> for MaybeElementRef<'_, T> {
65    fn as_mut(&mut self) -> &mut T {
66        self.deref_mut()
67    }
68}
69
70impl<'a, T> From<ElementRefMut<'a, T>> for MaybeElementRef<'a, T> {
71    fn from(value: ElementRefMut<'a, T>) -> Self {
72        Self::Owned(value)
73    }
74}
75
76impl<'a, T> From<&'a mut T> for MaybeElementRef<'a, T> {
77    fn from(value: &'a mut T) -> Self {
78        Self::Unowned(value)
79    }
80}
81
82/// The type of an [`Effect`].
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum EffectType {
85    Move,
86    Ability,
87    Condition,
88    MoveCondition,
89    AbilityCondition,
90    Item,
91    ItemCondition,
92    Clause,
93    Species,
94}
95
96/// An [`Effect`] handle.
97///
98/// A stable way to identify an [`Effect`].
99#[derive(Clone, Debug, PartialEq, Eq, Hash)]
100pub enum EffectHandle {
101    /// An active move, which is being used or was recently used by a Mon.
102    ActiveMove(MoveHandle, MoveHitEffectType),
103    /// A condition induced by a move.
104    MoveCondition(Id),
105    /// An inactive move, which is the move itself without reference to any individual use.
106    InactiveMove(Id),
107    /// An ability on a Mon.
108    Ability(Id),
109    /// A condition induced by an ability.
110    AbilityCondition(Id),
111    /// A condition on a Mon.
112    Condition(Id),
113    /// An item held by a Mon.
114    Item(Id),
115    /// A condition induced by an item.
116    ItemCondition(Id),
117    /// A clause applied to a battle format.
118    Clause(Id),
119    /// A species.
120    Species(Id),
121    /// Any effect that is applied to some part of the battle that does not really exist.
122    NonExistent(Id),
123}
124
125impl EffectHandle {
126    /// Creates an [`EffectHandle`] from the fxlang ID of the effect.
127    pub fn from_fxlang_id(fxlang_id: &str) -> Self {
128        let (effect_type, id) = split_once_optional(fxlang_id, ':');
129        let (effect_type, id) = match id {
130            Some(id) => (effect_type, id),
131            None => ("condition", fxlang_id),
132        };
133        let id = Id::from(id);
134        match effect_type {
135            "ability" => Self::Ability(id),
136            "abilitycondition" => Self::AbilityCondition(id),
137            "clause" => Self::Clause(id),
138            "item" => Self::Item(id),
139            "itemcondition" => Self::ItemCondition(id),
140            "move" => Self::InactiveMove(id),
141            "movecondition" => Self::MoveCondition(id),
142            "species" => Self::Species(id),
143            _ => Self::Condition(id),
144        }
145    }
146
147    /// Is the effect handle an ability?
148    pub fn is_ability(&self) -> bool {
149        match self {
150            Self::Ability(_) | Self::AbilityCondition(_) => true,
151            _ => false,
152        }
153    }
154
155    /// Is the effect handle an active move?
156    pub fn is_active_move(&self) -> bool {
157        match self {
158            Self::ActiveMove(_, _) => true,
159            _ => false,
160        }
161    }
162
163    /// Is the effect handle a secondary effect of an active move?
164    pub fn is_active_move_secondary(&self) -> bool {
165        match self {
166            Self::ActiveMove(_, MoveHitEffectType::SecondaryEffect(_, _, _)) => true,
167            _ => false,
168        }
169    }
170    /// Is the effect handle an item?
171    pub fn is_item(&self) -> bool {
172        match self {
173            Self::Item(_) | Self::ItemCondition(_) => true,
174            _ => false,
175        }
176    }
177
178    /// Returns the ID associated with the effect handle, if any.
179    pub fn try_id(&self) -> Option<&Id> {
180        match self {
181            Self::ActiveMove(_, _) => None,
182            Self::MoveCondition(id) => Some(&id),
183            Self::InactiveMove(id) => Some(&id),
184            Self::Ability(id) => Some(&id),
185            Self::AbilityCondition(id) => Some(&id),
186            Self::Condition(id) => Some(&id),
187            Self::Item(id) => Some(&id),
188            Self::ItemCondition(id) => Some(&id),
189            Self::Clause(id) => Some(&id),
190            Self::Species(id) => Some(&id),
191            Self::NonExistent(id) => Some(&id),
192        }
193    }
194
195    /// Constructs the stable effect handle for this effect handle.
196    ///
197    /// Every effect handle is stable except for active moves, since active moves can be destroyed
198    /// after a few turns. Active move handles will reference their inactive version.
199    pub fn stable_effect_handle(&self, context: &Context) -> Result<EffectHandle> {
200        match self {
201            Self::ActiveMove(active_move_handle, _) => Ok(EffectHandle::InactiveMove(
202                context.active_move(*active_move_handle)?.id().clone(),
203            )),
204            val @ _ => Ok(val.clone()),
205        }
206    }
207
208    /// Returns the associated condition handle.
209    pub fn condition_handle(&self, context: &Context) -> Result<Option<EffectHandle>> {
210        match self {
211            Self::ActiveMove(active_move_handle, _) => Ok(Some(EffectHandle::MoveCondition(
212                context.active_move(*active_move_handle)?.id().clone(),
213            ))),
214            Self::InactiveMove(id) => Ok(Some(EffectHandle::MoveCondition(id.clone()))),
215            Self::Ability(id) => Ok(Some(EffectHandle::AbilityCondition(id.clone()))),
216            Self::Item(id) => Ok(Some(EffectHandle::ItemCondition(id.clone()))),
217            _ => Ok(None),
218        }
219    }
220
221    /// Returns the associated non-condition handle.
222    ///
223    /// If the effect is already not a condition, it is returned unmodified.
224    pub fn non_condition_handle(&self) -> Option<EffectHandle> {
225        match self {
226            EffectHandle::MoveCondition(id) => Some(EffectHandle::InactiveMove(id.clone())),
227            EffectHandle::AbilityCondition(id) => Some(EffectHandle::Ability(id.clone())),
228            EffectHandle::ItemCondition(id) => Some(EffectHandle::Item(id.clone())),
229            EffectHandle::Condition(_) => None,
230            handle @ _ => Some(handle.clone()),
231        }
232    }
233
234    /// The ID for the effect for unlinked effects.
235    ///
236    /// Only applicable for active moves that use local data with modified effect callbacks. For
237    /// example, the move "Bide" executes a special version of the move with custom effect
238    /// callbacks. To avoid the cached "Bide" move effects from being used, this ID forces the
239    /// evaluation of the custom effects.
240    pub fn unlinked_fxlang_id(&self) -> Option<String> {
241        match self {
242            Self::ActiveMove(active_move_handle, _) => {
243                Some(format!("activemove:{active_move_handle}"))
244            }
245            _ => None,
246        }
247    }
248}
249
250/// A battle effect.
251///
252/// Contains the borrowed data for the effect.
253pub enum Effect<'borrow> {
254    /// A move currently being used by a Mon.
255    ActiveMove(&'borrow mut Move, MoveHitEffectType),
256    /// A condition induced by a previously-used move.
257    MoveCondition(ElementRef<'borrow, Move>),
258    /// An inactive move, which is not currently being used by a Mon.
259    InactiveMove(ElementRef<'borrow, Move>),
260    /// An ability, which is permanently applied to a Mon.
261    Ability(ElementRef<'borrow, Ability>),
262    /// A condition induced by an ability.
263    AbilityCondition(ElementRef<'borrow, Ability>),
264    /// A condition, which is applied to a Mon for some number of turns.
265    Condition(ElementRef<'borrow, Condition>),
266    /// An item, which is held by a Mon.
267    Item(ElementRef<'borrow, Item>),
268    /// A condition induced by an item.
269    ItemCondition(ElementRef<'borrow, Item>),
270    /// A clause applied to a battle format.
271    Clause(ElementRef<'borrow, Clause>),
272    /// A species.
273    Species(ElementRef<'borrow, Species>),
274    /// A non-existent effect, which does nothing.
275    NonExistent(Id),
276}
277
278impl<'borrow> Effect<'borrow> {
279    /// Creates a new effect for the active move.
280    pub fn for_active_move(
281        active_move: &'borrow mut Move,
282        hit_effect_type: MoveHitEffectType,
283    ) -> Self {
284        Self::ActiveMove(active_move, hit_effect_type)
285    }
286
287    /// Creates a new effect for the ability.
288    pub fn for_ability(ability: ElementRef<'borrow, Ability>) -> Self {
289        Self::Ability(ability)
290    }
291
292    /// Creates a new effect for the ability condition.
293    pub fn for_ability_condition(ability: ElementRef<'borrow, Ability>) -> Self {
294        Self::AbilityCondition(ability)
295    }
296
297    /// Creates a new effect for the condition.
298    pub fn for_condition(condition: ElementRef<'borrow, Condition>) -> Self {
299        Self::Condition(condition)
300    }
301
302    /// Creates a new effect for the move condition.
303    pub fn for_move_condition(mov: ElementRef<'borrow, Move>) -> Self {
304        Self::MoveCondition(mov)
305    }
306
307    /// Creates a new effect for the item.
308    pub fn for_item(item: ElementRef<'borrow, Item>) -> Self {
309        Self::Item(item)
310    }
311
312    /// Creates a new effect for the item condition.
313    pub fn for_item_condition(item: ElementRef<'borrow, Item>) -> Self {
314        Self::ItemCondition(item)
315    }
316
317    /// Creates a new effect for the clause.
318    pub fn for_clause(clause: ElementRef<'borrow, Clause>) -> Self {
319        Self::Clause(clause)
320    }
321
322    /// Creates a new effect for the species.
323    pub fn for_species(species: ElementRef<'borrow, Species>) -> Self {
324        Self::Species(species)
325    }
326
327    /// Creates a new effect for the move.
328    pub fn for_inactive_move(mov: ElementRef<'borrow, Move>) -> Self {
329        Self::InactiveMove(mov)
330    }
331
332    /// Creates a new effect for some non-existent effect.
333    pub fn for_non_existent(id: Id) -> Self {
334        Self::NonExistent(id)
335    }
336
337    /// The name of the effect.
338    pub fn name(&self) -> &str {
339        match self {
340            Self::ActiveMove(active_move, _) => &active_move.data.name,
341            Self::MoveCondition(mov) | Self::InactiveMove(mov) => &mov.data.name,
342            Self::Ability(ability) | Self::AbilityCondition(ability) => &ability.data.name,
343            Self::Condition(condition) => &condition.data.name,
344            Self::Item(item) | Self::ItemCondition(item) => &item.data.name,
345            Self::Clause(clause) => &clause.data.name,
346            Self::Species(species) => &species.data.name,
347            Self::NonExistent(id) => id.as_ref(),
348        }
349    }
350
351    /// The type of the effect.
352    pub fn effect_type(&self) -> EffectType {
353        match self {
354            Self::ActiveMove(_, _) => EffectType::Move,
355            Self::MoveCondition(_) => EffectType::MoveCondition,
356            Self::InactiveMove(_) => EffectType::Move,
357            Self::Ability(_) => EffectType::Ability,
358            Self::AbilityCondition(_) => EffectType::AbilityCondition,
359            Self::Condition(_) => EffectType::Condition,
360            Self::Item(_) => EffectType::Item,
361            Self::ItemCondition(_) => EffectType::ItemCondition,
362            Self::Clause(_) => EffectType::Clause,
363            Self::Species(_) => EffectType::Species,
364            Self::NonExistent(_) => EffectType::Condition,
365        }
366    }
367
368    fn effect_type_name(&self) -> &str {
369        match self {
370            Self::ActiveMove(_, _) | Self::MoveCondition(_) | Self::InactiveMove(_) => "move",
371            Self::Ability(_) | Self::AbilityCondition(_) => "ability",
372            Self::Condition(condition) => condition.condition_type_name(),
373            Self::Item(_) | Self::ItemCondition(_) => "item",
374            Self::Clause(_) => "clause",
375            Self::Species(_) => "species",
376            Self::NonExistent(_) => "",
377        }
378    }
379
380    /// The full name of the effect, which is prefixed by its type.
381    pub fn full_name(&self) -> String {
382        match self.effect_type_name() {
383            "" => self.name().to_owned(),
384            prefix => format!("{prefix}:{}", self.name()),
385        }
386    }
387
388    fn fxlang_id_effect_type_name(&self) -> String {
389        match self {
390            Self::ActiveMove(_, hit_effect_type) => match hit_effect_type.secondary_index() {
391                None => "move".to_owned(),
392                Some((target, hit, secondary_index)) => {
393                    format!("movesecondary-{hit}-{target}-{secondary_index}")
394                }
395            },
396            Self::MoveCondition(_) => "movecondition".to_owned(),
397            Self::InactiveMove(_) => "move".to_owned(),
398            Self::Ability(_) => "ability".to_owned(),
399            Self::AbilityCondition(_) => "abilitycondition".to_owned(),
400            Self::Condition(condition) => condition.condition_type_name().to_owned(),
401            Self::Item(_) => "item".to_owned(),
402            Self::ItemCondition(_) => "itemcondition".to_owned(),
403            Self::Clause(_) => "clause".to_owned(),
404            Self::Species(_) => "species".to_owned(),
405            Self::NonExistent(_) => "condition".to_owned(),
406        }
407    }
408
409    /// The ID of the effect, used for caching fxlang effect callbacks.
410    pub fn fxlang_id(&self) -> String {
411        match self.fxlang_id_effect_type_name().as_str() {
412            "" => format!("{}", self.id()),
413            prefix => format!("{prefix}:{}", self.id()),
414        }
415    }
416
417    /// The underlying move, if any.
418    pub fn move_effect<'effect>(&'effect self) -> Option<&'effect Move> {
419        match self {
420            Self::ActiveMove(active_move, _) => Some(active_move.deref()),
421            Self::InactiveMove(mov) => Some(mov),
422            Self::MoveCondition(mov) => Some(mov),
423            _ => None,
424        }
425    }
426
427    /// The underlying condition, if any.
428    pub fn condition<'effect>(&'effect self) -> Option<&'effect Condition> {
429        match self {
430            Self::Condition(condition) => Some(condition.deref()),
431            _ => None,
432        }
433    }
434
435    /// The associated [`fxlang::Effect`].
436    pub fn fxlang_effect<'effect>(&'effect self) -> Option<&'effect fxlang::Effect> {
437        match self {
438            Self::ActiveMove(active_move, hit_effect_type) => {
439                active_move.fxlang_effect(*hit_effect_type)
440            }
441            Self::MoveCondition(mov) => Some(&mov.condition),
442            Self::InactiveMove(mov) => Some(&mov.effect),
443            Self::Ability(ability) => Some(&ability.effect),
444            Self::AbilityCondition(ability) => Some(&ability.condition),
445            Self::Condition(condition) => Some(&condition.condition),
446            Self::Item(item) => Some(&item.effect),
447            Self::ItemCondition(item) => Some(&item.condition),
448            Self::Clause(clause) => Some(&clause.effect),
449            Self::Species(species) => Some(&species.effect),
450            Self::NonExistent(_) => None,
451        }
452    }
453
454    /// Whether the effect is marked as unlinked from its static data.
455    pub fn unlinked(&self) -> bool {
456        match self {
457            Self::ActiveMove(active_move, _) => active_move.unlinked,
458            _ => false,
459        }
460    }
461}
462
463impl Identifiable for Effect<'_> {
464    fn id(&self) -> &Id {
465        match self {
466            Self::ActiveMove(active_move, _) => active_move.id(),
467            Self::MoveCondition(mov) | Self::InactiveMove(mov) => mov.id(),
468            Self::Ability(ability) | Self::AbilityCondition(ability) => ability.id(),
469            Self::Condition(condition) => condition.id(),
470            Self::Item(item) | Self::ItemCondition(item) => item.id(),
471            Self::Clause(clause) => clause.id(),
472            Self::Species(species) => species.id(),
473            Self::NonExistent(id) => id,
474        }
475    }
476}