Skip to main content

manabrew_engine/card/
alt_costs.rs

1//! Alternative cost getters for Card.
2//!
3//! These methods extract cost information from keywords following the "Keyword:cost" pattern.
4//! They're used by the casting/playability system to present alternative casting options.
5
6use super::Card;
7
8impl Card {
9    // ── Keyword cost helpers (pattern: "Keyword:cost_string") ────────
10
11    /// Get buyback cost (e.g. "Buyback:2" → Some("2")).
12    pub fn get_buyback_cost(&self) -> Option<String> {
13        self.get_keyword_cost("Buyback")
14    }
15
16    /// Get spectacle cost (e.g. "Spectacle:B R" → Some("B R")).
17    pub fn get_spectacle_cost(&self) -> Option<String> {
18        self.get_keyword_cost("Spectacle")
19    }
20
21    /// Get evoke cost (e.g. "Evoke:2 B" → Some("2 B")).
22    pub fn get_evoke_cost(&self) -> Option<String> {
23        self.get_keyword_cost("Evoke")
24    }
25
26    /// Get every Evoke cost on this card (intrinsic + granted). Multiple Evoke
27    /// keywords stack independently — e.g. an Elemental in hand under Ashling,
28    /// the Limitless has both its native `Evoke:2 U` and the granted `Evoke:4`.
29    /// Each is a distinct alternative cost in MTG and a separate playable entry.
30    pub fn get_all_evoke_costs(&self) -> Vec<String> {
31        let prefix = "Evoke:";
32        let mut out = Vec::new();
33        for kw in self.keywords.iter_strings() {
34            if let Some(cost) = kw.strip_prefix(prefix) {
35                out.push(cost.to_string());
36            }
37        }
38        for kw in self.granted_keywords.iter_strings() {
39            if let Some(cost) = kw.strip_prefix(prefix) {
40                out.push(cost.to_string());
41            }
42        }
43        out
44    }
45
46    /// Get bestow cost (e.g. "Bestow:3 G G" → Some("3 G G")).
47    pub fn get_bestow_cost(&self) -> Option<String> {
48        self.get_keyword_cost("Bestow")
49    }
50
51    /// Get dash cost (e.g. "Dash:1 R" → Some("1 R")).
52    pub fn get_dash_cost(&self) -> Option<String> {
53        self.get_keyword_cost("Dash")
54    }
55
56    /// Get blitz cost (e.g. "Blitz:1 R" → Some("1 R")).
57    pub fn get_blitz_cost(&self) -> Option<String> {
58        self.get_keyword_cost("Blitz")
59    }
60
61    /// Get warp cost (e.g. "Warp:1 B" → Some("1 B")).
62    pub fn get_warp_cost(&self) -> Option<String> {
63        self.get_keyword_cost("Warp")
64    }
65
66    /// Get multikicker cost (e.g. "Multikicker:1 G" → Some("1 G")).
67    pub fn get_multikicker_cost(&self) -> Option<String> {
68        self.get_keyword_cost("Multikicker")
69    }
70
71    /// Get replicate cost (e.g. "Replicate:U" → Some("U")).
72    pub fn get_replicate_cost(&self) -> Option<String> {
73        self.get_keyword_cost("Replicate")
74    }
75
76    /// Get entwine cost (e.g. "Entwine:2" → Some("2")).
77    pub fn get_entwine_cost(&self) -> Option<String> {
78        self.get_keyword_cost("Entwine")
79    }
80
81    /// Get escalate cost (e.g. "Escalate:1" → Some("1")).
82    pub fn get_escalate_cost(&self) -> Option<String> {
83        self.get_keyword_cost("Escalate")
84    }
85
86    /// Get escape cost and exile count (e.g. "Escape:1 B B:4" → Some(("1 B B", 4))).
87    /// Delegates parsing to the keyword module.
88    pub fn get_escape_cost(&self) -> Option<(String, i32)> {
89        crate::keyword::extract_escape(&self.keywords)
90            .or_else(|| crate::keyword::extract_escape(&self.granted_keywords))
91            .map(|info| (info.mana_cost, info.exile_count))
92    }
93
94    /// Get overload cost (e.g. "Overload:3 R" → Some("3 R")).
95    pub fn get_overload_cost(&self) -> Option<String> {
96        self.get_keyword_cost("Overload")
97    }
98
99    /// Get madness cost (e.g. "Madness:1 R" → Some("1 R")).
100    pub fn get_madness_cost(&self) -> Option<String> {
101        self.get_keyword_cost("Madness")
102    }
103
104    /// Get strive cost (e.g. "Strive:1 W" → Some("1 W")).
105    pub fn get_strive_cost(&self) -> Option<String> {
106        self.get_keyword_cost("Strive")
107    }
108
109    /// Get suspend cost and time counters (e.g. "Suspend:1 U:3" → Some(("1 U", 3))).
110    /// Delegates parsing to the keyword module.
111    pub fn get_suspend_cost(&self) -> Option<(String, i32)> {
112        crate::keyword::extract_suspend(&self.keywords)
113            .or_else(|| crate::keyword::extract_suspend(&self.granted_keywords))
114            .map(|info| (info.mana_cost, info.time_counters))
115    }
116
117    /// Get foretell cost (e.g. "Foretell:W W" → Some("W W")).
118    pub fn get_foretell_cost(&self) -> Option<String> {
119        self.get_keyword_cost("Foretell")
120    }
121
122    /// Get emerge cost (e.g. "Emerge:5 U U" → Some("5 U U")).
123    pub fn get_emerge_cost(&self) -> Option<String> {
124        self.get_keyword_cost("Emerge")
125    }
126
127    /// Get offering type (e.g. "Offering:Snake" → Some("Snake")).
128    pub fn get_offering_type(&self) -> Option<String> {
129        self.get_keyword_cost("Offering")
130    }
131
132    /// Generic keyword cost parser — delegates to the keyword module.
133    /// Looks for "Keyword:cost" in intrinsic and granted keywords.
134    pub fn get_keyword_cost(&self, keyword: &str) -> Option<String> {
135        crate::keyword::extract_keyword_cost_from_all(
136            [&self.keywords, &self.granted_keywords],
137            keyword,
138        )
139    }
140
141    /// Get a keyword's numeric amount (e.g. "Dredge:2" → Some(2)).
142    pub fn get_keyword_amount(&self, keyword: &str) -> Option<usize> {
143        self.get_keyword_cost(keyword)
144            .and_then(|s| s.trim().parse::<usize>().ok())
145    }
146
147    /// Get Ward cost (e.g. "Ward:2" → Some("2"), "Ward:{U}" → Some("{U}")).
148    pub fn get_ward_cost(&self) -> Option<String> {
149        self.get_keyword_cost("Ward")
150    }
151
152    pub fn get_flashback_cost(&self) -> Option<String> {
153        if let Some(cost) = self.get_keyword_cost("Flashback") {
154            return Some(cost);
155        }
156        if self.has_keyword("Flashback") && !self.mana_cost.is_no_cost() {
157            return Some(mana_cost_script_string(&self.mana_cost));
158        }
159        None
160    }
161
162    /// Get Harmonize cost (e.g. "Harmonize:X G G" → Some("X G G")).
163    pub fn get_harmonize_cost(&self) -> Option<String> {
164        self.get_keyword_cost("Harmonize")
165    }
166
167    /// Get Kicker cost (e.g. "Kicker:W" → Some("W")).
168    pub fn get_kicker_cost(&self) -> Option<String> {
169        self.get_keyword_cost("Kicker")
170    }
171}
172
173fn mana_cost_script_string(mc: &forge_foundation::ManaCost) -> String {
174    let mut tokens: Vec<String> = Vec::new();
175    for shard in mc.shards() {
176        if *shard == forge_foundation::ManaCostShard::X {
177            tokens.push("X".to_string());
178        }
179    }
180    if mc.generic_cost() > 0 {
181        tokens.push(mc.generic_cost().to_string());
182    }
183    for shard in mc.shards() {
184        if *shard != forge_foundation::ManaCostShard::X {
185            tokens.push(shard.short_string().to_string());
186        }
187    }
188    tokens.join(" ")
189}