Skip to main content

manabrew_engine/replacement/
replace_produce_mana.rs

1//! Replacement logic for `Event$ ProduceMana`.
2//!
3//! Mirrors Java `ReplaceProduceMana.java` in `forge/game/replacement/`.
4
5use crate::card::Card;
6use crate::game::GameState;
7use crate::ids::CardId;
8
9use super::replacement_effect::ReplacementEffect;
10use super::replacement_handler::ReplacementEvent;
11use super::replacement_result::ReplacementResult;
12use super::replacement_type::ReplacementType;
13use crate::card_trait_base::CardTrait;
14
15/// Mirrors Java `ReplaceProduceMana.canReplace()`.
16pub fn can_replace(
17    effect: &ReplacementEffect,
18    event: &ReplacementEvent,
19    game: &GameState,
20    source_card: &Card,
21) -> bool {
22    if effect.event != ReplacementType::ProduceMana {
23        return false;
24    }
25    let (source_id, activator) = match event {
26        ReplacementEvent::ProduceMana {
27            source, activator, ..
28        } => (*source, *activator),
29        _ => return false,
30    };
31    let producing_card = &game.cards[source_id.index()];
32    if let Some(valid) = effect.ir.valid_card_selector.as_ref() {
33        if !valid.is_any_of(["Permanent", "Card"])
34            && !effect.matches_compiled_valid_card(valid, producing_card, source_card)
35        {
36            return false;
37        }
38    }
39    if let Some(valid_player) = effect.ir.valid_activator_text.as_deref() {
40        if !effect.matches_valid_player(valid_player, activator, source_card) {
41            return false;
42        }
43    } else if let Some(valid_player) = effect.ir.valid_player_selector.as_ref() {
44        if !effect.matches_compiled_valid_player(valid_player, activator, source_card) {
45            return false;
46        }
47    }
48    true
49}
50
51/// Mirrors Java `ReplaceManaEffect.resolve()`.
52pub fn execute(
53    effect: &ReplacementEffect,
54    event: &mut ReplacementEvent,
55    game: &GameState,
56    source_card_id: CardId,
57) -> ReplacementResult {
58    let mana = match event {
59        ReplacementEvent::ProduceMana { mana, .. } => mana,
60        _ => return ReplacementResult::NotReplaced,
61    };
62
63    fn color_word_to_short(s: &str) -> String {
64        match s.to_lowercase().as_str() {
65            "white" | "w" => "W".into(),
66            "blue" | "u" => "U".into(),
67            "black" | "b" => "B".into(),
68            "red" | "r" => "R".into(),
69            "green" | "g" => "G".into(),
70            "colorless" | "c" => "C".into(),
71            _ => s.to_uppercase(),
72        }
73    }
74
75    if let Some(replace_mana) = effect.ir.replace_mana_text.as_deref() {
76        let replacement = if replace_mana == "Any" {
77            "W".to_string()
78        } else {
79            color_word_to_short(replace_mana)
80        };
81        *mana = replacement;
82        return ReplacementResult::Updated;
83    } else if let Some(replace_type) = effect.ir.replace_type_text.as_deref() {
84        let color = if replace_type == "Any" {
85            "W".to_string()
86        } else {
87            color_word_to_short(replace_type)
88        };
89        let replaced: Vec<&str> = mana.split_whitespace().collect();
90        let new_parts: Vec<String> = replaced.iter().map(|_| color.clone()).collect();
91        *mana = new_parts.join(" ");
92        return ReplacementResult::Updated;
93    } else if let Some(replace_color) = effect.ir.replace_color_text.as_deref() {
94        let color = if replace_color == "Chosen" {
95            let host_card = &game.cards[source_card_id.index()];
96            host_card
97                .chosen_colors
98                .first()
99                .map(|c| color_word_to_short(c))
100                .unwrap_or_else(|| "W".into())
101        } else {
102            color_word_to_short(replace_color)
103        };
104        let replace_only = effect
105            .ir
106            .replace_only_text
107            .as_deref()
108            .map(color_word_to_short);
109        let colored = ["W", "U", "B", "R", "G"];
110        let replaced: Vec<String> = mana
111            .split_whitespace()
112            .map(|tok| {
113                if let Some(ref only) = replace_only {
114                    if tok == only {
115                        color.clone()
116                    } else {
117                        tok.to_string()
118                    }
119                } else if colored.contains(&tok) {
120                    color.clone()
121                } else {
122                    tok.to_string()
123                }
124            })
125            .collect();
126        *mana = replaced.join(" ");
127        return ReplacementResult::Updated;
128    } else if let Some(replace_with) = effect.replace_with() {
129        let multiplier = if replace_with.contains("Triple") || replace_with.contains("Thrice") {
130            3usize
131        } else if replace_with.contains("Twice") || replace_with.contains("Double") {
132            2usize
133        } else {
134            effect
135                .ir
136                .replace_amount_text
137                .as_deref()
138                .and_then(|s| s.parse::<usize>().ok())
139                .unwrap_or(2)
140        };
141        if multiplier > 1 {
142            let parts: Vec<&str> = mana.split_whitespace().collect();
143            let mut repeated = Vec::new();
144            for _ in 0..multiplier {
145                repeated.extend_from_slice(&parts);
146            }
147            *mana = repeated.join(" ");
148            return ReplacementResult::Updated;
149        }
150    } else if let Some(amount) = effect.ir.replace_amount_text.as_deref() {
151        if let Ok(multiplier) = amount.parse::<usize>() {
152            if multiplier > 1 {
153                let parts: Vec<&str> = mana.split_whitespace().collect();
154                let mut repeated = Vec::new();
155                for _ in 0..multiplier {
156                    repeated.extend_from_slice(&parts);
157                }
158                *mana = repeated.join(" ");
159                return ReplacementResult::Updated;
160            }
161        }
162    }
163    ReplacementResult::Replaced
164}