Skip to main content

manabrew_engine/spellability/
ability_mana_part.rs

1//! Mana-producing part of an ability.
2//!
3//! Mirrors Java's `AbilityManaPart.java` — tracks what mana an ability
4//! produces, any restrictions on spending it, and side effects.
5
6use serde::{Deserialize, Serialize};
7
8/// Mana-producing component of a spell ability.
9/// Mirrors Java's `AbilityManaPart` — stores the produced mana string,
10/// restrictions on how it can be spent, and associated effects.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AbilityManaPart {
13    /// Original mana production string (e.g. "W", "G G", "Any").
14    orig_produced: String,
15    /// Restrictions on how the produced mana can be spent (e.g. "Creature").
16    mana_restrictions: String,
17    /// Keywords added to the spell cast with this mana.
18    adds_keywords: Option<String>,
19    /// Trigger that fires when this mana is spent.
20    triggers_when_spent: Option<String>,
21    /// Whether this mana persists between phases.
22    persistent_mana: bool,
23    /// Whether this mana can only be spent during combat.
24    combat_mana: bool,
25    /// Last express choice made for mana generation (for "Any" mana).
26    last_express_choice: String,
27}
28
29impl AbilityManaPart {
30    /// Create a new mana part with the given production and restrictions.
31    /// Mirrors Java's `AbilityManaPart(SpellAbility, String, String)`.
32    pub fn new(produced: &str, restrictions: &str) -> Self {
33        Self {
34            orig_produced: produced.to_string(),
35            mana_restrictions: restrictions.to_string(),
36            adds_keywords: None,
37            triggers_when_spent: None,
38            persistent_mana: false,
39            combat_mana: false,
40            last_express_choice: String::new(),
41        }
42    }
43
44    /// Check if this ability can produce the given color of mana.
45    /// Mirrors Java's `AbilityManaPart.canProduce(String)`.
46    /// Returns true if the color string is found within orig_produced,
47    /// or if orig_produced is "Any".
48    pub fn can_produce(&self, color: &str) -> bool {
49        if self.orig_produced.is_empty() {
50            return false;
51        }
52        if self.orig_produced.eq_ignore_ascii_case("Any") {
53            return true;
54        }
55        // Check each mana symbol in the produced string
56        self.orig_produced
57            .split_whitespace()
58            .any(|token| token.eq_ignore_ascii_case(color))
59    }
60
61    /// Get the original mana production string.
62    /// Mirrors Java's `AbilityManaPart.getOrigProduced()`.
63    pub fn get_orig_produced(&self) -> &str {
64        &self.orig_produced
65    }
66
67    /// Whether this ability can produce any mana at all.
68    /// Mirrors Java's `AbilityManaPart.canThisProduce()`.
69    pub fn can_this_produce(&self) -> bool {
70        !self.orig_produced.is_empty()
71    }
72
73    /// Count the number of individual mana generated.
74    /// Mirrors Java's `AbilityManaPart.amountOfManaGenerated(SpellAbility)`.
75    /// Each space-separated token in orig_produced counts as one mana.
76    pub fn amount_of_mana_generated(&self) -> i32 {
77        if self.orig_produced.is_empty() {
78            return 0;
79        }
80        self.orig_produced.split_whitespace().count() as i32
81    }
82
83    /// Total amount of mana generated, counting "All" and "Any" as 1 each.
84    /// Mirrors Java's `AbilityManaPart.totalAmountOfManaGenerated(SpellAbility)`.
85    pub fn total_amount_of_mana_generated(&self) -> i32 {
86        if self.orig_produced.is_empty() {
87            return 0;
88        }
89        self.orig_produced
90            .split_whitespace()
91            .map(|token| {
92                if token.eq_ignore_ascii_case("All") || token.eq_ignore_ascii_case("Any") {
93                    1
94                } else {
95                    1
96                }
97            })
98            .sum()
99    }
100
101    /// Get the mana restrictions string.
102    pub fn mana_restrictions(&self) -> &str {
103        &self.mana_restrictions
104    }
105
106    /// Set keywords to add to spells cast with this mana.
107    pub fn set_adds_keywords(&mut self, keywords: Option<String>) {
108        self.adds_keywords = keywords;
109    }
110
111    /// Get keywords added by this mana.
112    pub fn adds_keywords(&self) -> Option<&str> {
113        self.adds_keywords.as_deref()
114    }
115
116    /// Set the trigger that fires when this mana is spent.
117    pub fn set_triggers_when_spent(&mut self, trigger: Option<String>) {
118        self.triggers_when_spent = trigger;
119    }
120
121    /// Get the trigger that fires when this mana is spent.
122    pub fn triggers_when_spent(&self) -> Option<&str> {
123        self.triggers_when_spent.as_deref()
124    }
125
126    /// Whether this mana persists between phases.
127    pub fn is_persistent_mana(&self) -> bool {
128        self.persistent_mana
129    }
130
131    /// Set whether this mana persists between phases.
132    pub fn set_persistent_mana(&mut self, val: bool) {
133        self.persistent_mana = val;
134    }
135
136    /// Whether this mana can only be spent during combat.
137    pub fn is_combat_mana(&self) -> bool {
138        self.combat_mana
139    }
140
141    /// Set whether this mana can only be spent during combat.
142    pub fn set_combat_mana(&mut self, val: bool) {
143        self.combat_mana = val;
144    }
145
146    /// Get last express choice for mana generation.
147    pub fn last_express_choice(&self) -> &str {
148        &self.last_express_choice
149    }
150
151    /// Set last express choice for mana generation.
152    pub fn set_last_express_choice(&mut self, choice: String) {
153        self.last_express_choice = choice;
154    }
155
156    /// Clear express choice for mana generation.
157    /// Mirrors Java's `AbilityManaPart.clearExpressChoice()`.
158    pub fn clear_express_choice(&mut self) {
159        self.last_express_choice.clear();
160    }
161
162    /// Produce mana into the mana pool.
163    /// Mirrors Java's `AbilityManaPart.produceMana(String, Player, SpellAbility)`.
164    /// Returns the produced mana string for the pool to consume.
165    pub fn produce_mana(&self) -> &str {
166        if !self.last_express_choice.is_empty() {
167            &self.last_express_choice
168        } else {
169            &self.orig_produced
170        }
171    }
172
173    /// Whether this ability taps the source for mana.
174    /// Mirrors Java's `AbilityManaPart.tapsForMana()`.
175    pub fn taps_for_mana(&self) -> bool {
176        self.can_this_produce()
177    }
178
179    /// Whether the mana produced cannot be countered when paid with.
180    /// Mirrors Java's `AbilityManaPart.cannotCounterPaidWith()`.
181    pub fn cannot_counter_paid_with(&self) -> bool {
182        self.mana_restrictions.contains("CantCounter")
183    }
184
185    /// Add a no-counter effect to the mana restrictions.
186    /// Mirrors Java's `AbilityManaPart.addNoCounterEffect()`.
187    pub fn add_no_counter_effect(&mut self) {
188        if !self.mana_restrictions.contains("CantCounter") {
189            if !self.mana_restrictions.is_empty() {
190                self.mana_restrictions.push(',');
191            }
192            self.mana_restrictions.push_str("CantCounter");
193        }
194    }
195
196    /// Add keywords to spells cast with this mana.
197    /// Mirrors Java's `AbilityManaPart.addKeywords()`.
198    pub fn add_keywords(&mut self, keywords: &str) {
199        self.adds_keywords = Some(keywords.to_string());
200    }
201
202    /// Whether this mana adds counters to the spell.
203    /// Mirrors Java's `AbilityManaPart.addsCounters()`.
204    pub fn adds_counters(&self) -> bool {
205        self.mana_restrictions.contains("AddsCounter")
206    }
207
208    /// Create ETB counters for the spell cast with this mana.
209    /// Mirrors Java's `AbilityManaPart.createETBCounters()`.
210    pub fn create_etb_counters(&self) -> bool {
211        self.adds_counters()
212    }
213
214    /// Add a trigger that fires when this mana is spent.
215    /// Mirrors Java's `AbilityManaPart.addTriggersWhenSpent()`.
216    pub fn add_triggers_when_spent(&mut self, trigger: &str) {
217        self.triggers_when_spent = Some(trigger.to_string());
218    }
219
220    /// Check if this mana meets the given mana restrictions.
221    /// Mirrors Java's `AbilityManaPart.meetsManaRestrictions(SpellAbility)`.
222    pub fn meets_mana_restrictions(&self, restriction: &str) -> bool {
223        if self.mana_restrictions.is_empty() {
224            return true;
225        }
226        self.mana_restrictions
227            .split(',')
228            .any(|r| r.trim().eq_ignore_ascii_case(restriction))
229    }
230
231    /// Check if this mana meets mana shard restrictions.
232    /// Mirrors Java's `AbilityManaPart.meetsManaShardRestrictions()`.
233    pub fn meets_mana_shard_restrictions(&self) -> bool {
234        // Shard restrictions are a subset of mana restrictions
235        // that limit what colors the mana can pay for.
236        // By default, no shard restrictions are active.
237        true
238    }
239
240    /// Check if this mana meets both spell and shard restrictions.
241    /// Mirrors Java's `AbilityManaPart.meetsSpellAndShardRestrictions(SpellAbility)`.
242    pub fn meets_spell_and_shard_restrictions(&self) -> bool {
243        self.meets_mana_shard_restrictions()
244    }
245
246    /// Get the mana representation for pool tracking.
247    /// Mirrors Java's `AbilityManaPart.mana()`.
248    pub fn mana(&self) -> &str {
249        &self.orig_produced
250    }
251}