Skip to main content

manabrew_engine/ability/effects/
mana_reflected_effect.rs

1use forge_foundation::mana::ManaAtom;
2
3use super::EffectContext;
4use crate::card::card_util;
5use crate::mana::{color_name_to_mana_atom, Mana};
6
7/// Configure the spell ability during construction.
8/// Mirrors Java `ManaReflectedEffect.buildSpellAbility` — creates the
9/// `AbilityManaPart` from the SA's params and marks as undoable if it
10/// has no parent ability.
11pub fn build_spell_ability(sa: &mut crate::spellability::SpellAbility) {
12    // Set up the mana part from Produced$ parameter
13    let produced = sa
14        .produced_ir()
15        .map(crate::ability::ProducedMana::as_script_text)
16        .unwrap_or("Any".into());
17    let restriction = sa.ir.restrict_valid.as_deref().unwrap_or("").to_string();
18    sa.mana_part = Some(crate::spellability::AbilityManaPart::new(
19        &produced,
20        &restriction,
21    ));
22    sa.is_mana_ability = true;
23}
24
25/// Resolve DB$ ManaReflected — produce mana of a color/type that reflects other cards.
26/// Mirrors Java's ManaReflectedEffect.java.
27///
28/// Key params:
29/// - ReflectProperty$: "Is" (card colors), "Produce" (mana abilities), "Produced" (trigger mana)
30/// - ColorOrType$: "Color" (5 colors) or "Type" (6 = 5 colors + colorless)
31/// - Valid$: filter for which cards to check
32/// - Amount$: how many mana to produce (default 1)
33/// Struct form of this effect so it can participate in the
34/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
35/// `ManaReflectedEffect` class extending `SpellAbilityEffect`.
36#[manabrew_engine_macros::spell_effect(ManaReflectedEffect)]
37fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
38    let player = sa.activating_player;
39    let source_id = match sa.source {
40        Some(id) => id,
41        None => return,
42    };
43
44    let amount = super::resolve_numeric_svar(ctx.game, sa, "Amount", 1);
45    if amount <= 0 {
46        return;
47    }
48
49    let mut available_colors =
50        colors_from_names(card_util::get_reflectable_mana_colors(ctx.game, sa));
51    let color_or_type = sa.ir.color_or_type.as_deref().unwrap_or("Color");
52    if color_or_type == "Type" && !available_colors.contains(&ManaAtom::COLORLESS) {
53        available_colors.push(ManaAtom::COLORLESS);
54    }
55
56    if available_colors.is_empty() {
57        return;
58    }
59
60    // Read metadata from the ability
61    let restriction = sa.ir.restrict_valid.clone();
62    let source_is_snow = ctx.game.card(source_id).type_line.is_snow();
63
64    // Sort available colors in WUBRG(C) order to match Java's ColorSet iteration.
65    let wubrg_order: &[u16] = &[
66        ManaAtom::WHITE,
67        ManaAtom::BLUE,
68        ManaAtom::BLACK,
69        ManaAtom::RED,
70        ManaAtom::GREEN,
71        ManaAtom::COLORLESS,
72    ];
73    let mut sorted_colors: Vec<u16> = Vec::new();
74    for &atom in wubrg_order {
75        if available_colors.contains(&atom) {
76            sorted_colors.push(atom);
77        }
78    }
79    if sorted_colors.is_empty() {
80        sorted_colors = available_colors;
81    }
82
83    // Convert to color names and let the agent choose (mirrors Java's chooseColor).
84    let color_names: Vec<String> = sorted_colors
85        .iter()
86        .map(|&atom| match atom {
87            ManaAtom::WHITE => "White".to_string(),
88            ManaAtom::BLUE => "Blue".to_string(),
89            ManaAtom::BLACK => "Black".to_string(),
90            ManaAtom::RED => "Red".to_string(),
91            ManaAtom::GREEN => "Green".to_string(),
92            ManaAtom::COLORLESS => "Colorless".to_string(),
93            _ => "Colorless".to_string(),
94        })
95        .collect();
96
97    let express_choice = sa
98        .express_mana_choice
99        .filter(|atom| sorted_colors.contains(atom))
100        .or_else(|| {
101            sa.mana_part
102                .as_ref()
103                .map(|part| part.last_express_choice())
104                .filter(|choice| !choice.is_empty())
105                .and_then(color_name_to_mana_atom)
106                .filter(|atom| sorted_colors.contains(atom))
107        });
108
109    let best_color = if let Some(atom) = express_choice {
110        atom
111    } else if let Some(chosen_name) = ctx.agents[player.index()].choose_color(player, &color_names)
112    {
113        match chosen_name.as_str() {
114            "White" => ManaAtom::WHITE,
115            "Blue" => ManaAtom::BLUE,
116            "Black" => ManaAtom::BLACK,
117            "Red" => ManaAtom::RED,
118            "Green" => ManaAtom::GREEN,
119            "Colorless" => ManaAtom::COLORLESS,
120            _ => sorted_colors[0],
121        }
122    } else {
123        sorted_colors[0]
124    };
125
126    // Produce `amount` mana of the chosen color
127    for _ in 0..amount {
128        let mut m = Mana::simple(best_color);
129        m.source_card = Some(source_id);
130        m.is_snow = source_is_snow;
131        m.restriction = restriction.clone();
132        ctx.mana_pools[player.index()].add_mana(m);
133    }
134}
135
136fn colors_from_names(colors: std::collections::HashSet<String>) -> Vec<u16> {
137    let mut out = Vec::new();
138    for color in colors {
139        let atom = match color.as_str() {
140            "white" | "White" => Some(ManaAtom::WHITE),
141            "blue" | "Blue" => Some(ManaAtom::BLUE),
142            "black" | "Black" => Some(ManaAtom::BLACK),
143            "red" | "Red" => Some(ManaAtom::RED),
144            "green" | "Green" => Some(ManaAtom::GREEN),
145            "colorless" | "Colorless" => Some(ManaAtom::COLORLESS),
146            _ => None,
147        };
148        if let Some(atom) = atom {
149            if !out.contains(&atom) {
150                out.push(atom);
151            }
152        }
153    }
154    out
155}