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
//! Mana-producing part of an ability.
//!
//! Mirrors Java's `AbilityManaPart.java` — tracks what mana an ability
//! produces, any restrictions on spending it, and side effects.
use serde::{Deserialize, Serialize};
/// Mana-producing component of a spell ability.
/// Mirrors Java's `AbilityManaPart` — stores the produced mana string,
/// restrictions on how it can be spent, and associated effects.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AbilityManaPart {
/// Original mana production string (e.g. "W", "G G", "Any").
orig_produced: String,
/// Restrictions on how the produced mana can be spent (e.g. "Creature").
mana_restrictions: String,
/// Keywords added to the spell cast with this mana.
adds_keywords: Option<String>,
/// Trigger that fires when this mana is spent.
triggers_when_spent: Option<String>,
/// Whether this mana persists between phases.
persistent_mana: bool,
/// Whether this mana can only be spent during combat.
combat_mana: bool,
/// Last express choice made for mana generation (for "Any" mana).
last_express_choice: String,
}
impl AbilityManaPart {
/// Create a new mana part with the given production and restrictions.
/// Mirrors Java's `AbilityManaPart(SpellAbility, String, String)`.
pub fn new(produced: &str, restrictions: &str) -> Self {
Self {
orig_produced: produced.to_string(),
mana_restrictions: restrictions.to_string(),
adds_keywords: None,
triggers_when_spent: None,
persistent_mana: false,
combat_mana: false,
last_express_choice: String::new(),
}
}
/// Check if this ability can produce the given color of mana.
/// Mirrors Java's `AbilityManaPart.canProduce(String)`.
/// Returns true if the color string is found within orig_produced,
/// or if orig_produced is "Any".
pub fn can_produce(&self, color: &str) -> bool {
if self.orig_produced.is_empty() {
return false;
}
if self.orig_produced.eq_ignore_ascii_case("Any") {
return true;
}
// Check each mana symbol in the produced string
self.orig_produced
.split_whitespace()
.any(|token| token.eq_ignore_ascii_case(color))
}
/// Get the original mana production string.
/// Mirrors Java's `AbilityManaPart.getOrigProduced()`.
pub fn get_orig_produced(&self) -> &str {
&self.orig_produced
}
/// Whether this ability can produce any mana at all.
/// Mirrors Java's `AbilityManaPart.canThisProduce()`.
pub fn can_this_produce(&self) -> bool {
!self.orig_produced.is_empty()
}
/// Count the number of individual mana generated.
/// Mirrors Java's `AbilityManaPart.amountOfManaGenerated(SpellAbility)`.
/// Each space-separated token in orig_produced counts as one mana.
pub fn amount_of_mana_generated(&self) -> i32 {
if self.orig_produced.is_empty() {
return 0;
}
self.orig_produced.split_whitespace().count() as i32
}
/// Total amount of mana generated, counting "All" and "Any" as 1 each.
/// Mirrors Java's `AbilityManaPart.totalAmountOfManaGenerated(SpellAbility)`.
pub fn total_amount_of_mana_generated(&self) -> i32 {
if self.orig_produced.is_empty() {
return 0;
}
self.orig_produced
.split_whitespace()
.map(|token| {
if token.eq_ignore_ascii_case("All") || token.eq_ignore_ascii_case("Any") {
1
} else {
1
}
})
.sum()
}
/// Get the mana restrictions string.
pub fn mana_restrictions(&self) -> &str {
&self.mana_restrictions
}
/// Set keywords to add to spells cast with this mana.
pub fn set_adds_keywords(&mut self, keywords: Option<String>) {
self.adds_keywords = keywords;
}
/// Get keywords added by this mana.
pub fn adds_keywords(&self) -> Option<&str> {
self.adds_keywords.as_deref()
}
/// Set the trigger that fires when this mana is spent.
pub fn set_triggers_when_spent(&mut self, trigger: Option<String>) {
self.triggers_when_spent = trigger;
}
/// Get the trigger that fires when this mana is spent.
pub fn triggers_when_spent(&self) -> Option<&str> {
self.triggers_when_spent.as_deref()
}
/// Whether this mana persists between phases.
pub fn is_persistent_mana(&self) -> bool {
self.persistent_mana
}
/// Set whether this mana persists between phases.
pub fn set_persistent_mana(&mut self, val: bool) {
self.persistent_mana = val;
}
/// Whether this mana can only be spent during combat.
pub fn is_combat_mana(&self) -> bool {
self.combat_mana
}
/// Set whether this mana can only be spent during combat.
pub fn set_combat_mana(&mut self, val: bool) {
self.combat_mana = val;
}
/// Get last express choice for mana generation.
pub fn last_express_choice(&self) -> &str {
&self.last_express_choice
}
/// Set last express choice for mana generation.
pub fn set_last_express_choice(&mut self, choice: String) {
self.last_express_choice = choice;
}
/// Clear express choice for mana generation.
/// Mirrors Java's `AbilityManaPart.clearExpressChoice()`.
pub fn clear_express_choice(&mut self) {
self.last_express_choice.clear();
}
/// Produce mana into the mana pool.
/// Mirrors Java's `AbilityManaPart.produceMana(String, Player, SpellAbility)`.
/// Returns the produced mana string for the pool to consume.
pub fn produce_mana(&self) -> &str {
if !self.last_express_choice.is_empty() {
&self.last_express_choice
} else {
&self.orig_produced
}
}
/// Whether this ability taps the source for mana.
/// Mirrors Java's `AbilityManaPart.tapsForMana()`.
pub fn taps_for_mana(&self) -> bool {
self.can_this_produce()
}
/// Whether the mana produced cannot be countered when paid with.
/// Mirrors Java's `AbilityManaPart.cannotCounterPaidWith()`.
pub fn cannot_counter_paid_with(&self) -> bool {
self.mana_restrictions.contains("CantCounter")
}
/// Add a no-counter effect to the mana restrictions.
/// Mirrors Java's `AbilityManaPart.addNoCounterEffect()`.
pub fn add_no_counter_effect(&mut self) {
if !self.mana_restrictions.contains("CantCounter") {
if !self.mana_restrictions.is_empty() {
self.mana_restrictions.push(',');
}
self.mana_restrictions.push_str("CantCounter");
}
}
/// Add keywords to spells cast with this mana.
/// Mirrors Java's `AbilityManaPart.addKeywords()`.
pub fn add_keywords(&mut self, keywords: &str) {
self.adds_keywords = Some(keywords.to_string());
}
/// Whether this mana adds counters to the spell.
/// Mirrors Java's `AbilityManaPart.addsCounters()`.
pub fn adds_counters(&self) -> bool {
self.mana_restrictions.contains("AddsCounter")
}
/// Create ETB counters for the spell cast with this mana.
/// Mirrors Java's `AbilityManaPart.createETBCounters()`.
pub fn create_etb_counters(&self) -> bool {
self.adds_counters()
}
/// Add a trigger that fires when this mana is spent.
/// Mirrors Java's `AbilityManaPart.addTriggersWhenSpent()`.
pub fn add_triggers_when_spent(&mut self, trigger: &str) {
self.triggers_when_spent = Some(trigger.to_string());
}
/// Check if this mana meets the given mana restrictions.
/// Mirrors Java's `AbilityManaPart.meetsManaRestrictions(SpellAbility)`.
pub fn meets_mana_restrictions(&self, restriction: &str) -> bool {
if self.mana_restrictions.is_empty() {
return true;
}
self.mana_restrictions
.split(',')
.any(|r| r.trim().eq_ignore_ascii_case(restriction))
}
/// Check if this mana meets mana shard restrictions.
/// Mirrors Java's `AbilityManaPart.meetsManaShardRestrictions()`.
pub fn meets_mana_shard_restrictions(&self) -> bool {
// Shard restrictions are a subset of mana restrictions
// that limit what colors the mana can pay for.
// By default, no shard restrictions are active.
true
}
/// Check if this mana meets both spell and shard restrictions.
/// Mirrors Java's `AbilityManaPart.meetsSpellAndShardRestrictions(SpellAbility)`.
pub fn meets_spell_and_shard_restrictions(&self) -> bool {
self.meets_mana_shard_restrictions()
}
/// Get the mana representation for pool tracking.
/// Mirrors Java's `AbilityManaPart.mana()`.
pub fn mana(&self) -> &str {
&self.orig_produced
}
}