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}