Skip to main content

manabrew_engine/ability/effects/
protect_all_effect.rs

1use forge_foundation::ZoneType;
2
3use super::{matches_valid_cards_for_sa, EffectContext};
4use crate::ids::CardId;
5
6/// End-of-turn revert for ProtectionAll. Mirrors the `GameCommand.run()` in Java
7/// `ProtectAllEffect` that removes the granted protection keywords when the
8/// effect duration expires.
9///
10/// Removes the specified protection keyword from the card's pump_keywords.
11pub fn run(game: &mut crate::game::GameState, card_id: crate::ids::CardId, keyword: &str) {
12    if game.card(card_id).zone == ZoneType::Battlefield {
13        game.card_mut(card_id).pump_keywords.remove(keyword);
14    }
15}
16
17/// `SP$ ProtectionAll` — grant protection to all matching permanents.
18///
19/// Mirrors Java's `ProtectAllEffect.java`.
20/// - `ValidCards$` — filter for which permanents gain protection.
21/// - `Gains$` — the protection keyword to grant.
22/// - `Choices$` — if present, player chooses the protection quality.
23///
24/// # Card script examples
25/// ```text
26/// A:SP$ ProtectionAll | ValidCards$ Creature.YouCtrl | Gains$ Protection from chosen color
27/// ```
28/// Struct form of this effect so it can participate in the
29/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
30/// `ProtectAllEffect` class extending `SpellAbilityEffect`.
31#[manabrew_engine_macros::spell_effect(ProtectAllEffect)]
32fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
33    let controller = sa.activating_player;
34    let valid_filter = sa
35        .ir
36        .valid_cards_text
37        .as_deref()
38        .unwrap_or("Creature.YouCtrl")
39        .to_string();
40    let valid_selector = sa.ir.valid_cards_selector.as_ref();
41    let gains = sa.ir.gains.as_deref().unwrap_or("").to_string();
42
43    // If choosing a color, do it once for all targets
44    let prot_keyword = if gains.contains("chosen color") {
45        let choices = sa
46            .ir
47            .choices
48            .as_deref()
49            .map(|s| {
50                s.split(',')
51                    .map(|c| c.trim().to_string())
52                    .collect::<Vec<_>>()
53            })
54            .unwrap_or_else(|| {
55                vec![
56                    "White".into(),
57                    "Blue".into(),
58                    "Black".into(),
59                    "Red".into(),
60                    "Green".into(),
61                ]
62            });
63        let chosen = ctx.agents[controller.index()].choose_color(controller, &choices);
64        match chosen {
65            Some(color) => format!("Protection from {}", color.to_lowercase()),
66            None => return,
67        }
68    } else {
69        gains
70    };
71
72    if prot_keyword.is_empty() {
73        return;
74    }
75
76    let player_ids = ctx.game.player_order.clone();
77    let mut targets: Vec<CardId> = Vec::new();
78
79    for &pid in &player_ids {
80        let zone_cards = ctx.game.cards_in_zone(ZoneType::Battlefield, pid).to_vec();
81        for cid in zone_cards {
82            if matches_valid_cards_for_sa(
83                ctx.game,
84                sa,
85                ctx.game.card(cid),
86                valid_selector,
87                &valid_filter,
88            ) {
89                targets.push(cid);
90            }
91        }
92    }
93
94    for cid in targets {
95        if ctx.game.card(cid).zone == ZoneType::Battlefield {
96            ctx.game.card_mut(cid).pump_keywords.add(&prot_keyword);
97        }
98    }
99}