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
use forge_foundation::ZoneType;
use super::{matches_valid_cards_for_sa, EffectContext};
use crate::ids::CardId;
/// End-of-turn revert for ProtectionAll. Mirrors the `GameCommand.run()` in Java
/// `ProtectAllEffect` that removes the granted protection keywords when the
/// effect duration expires.
///
/// Removes the specified protection keyword from the card's pump_keywords.
pub fn run(game: &mut crate::game::GameState, card_id: crate::ids::CardId, keyword: &str) {
if game.card(card_id).zone == ZoneType::Battlefield {
game.card_mut(card_id).pump_keywords.remove(keyword);
}
}
/// `SP$ ProtectionAll` — grant protection to all matching permanents.
///
/// Mirrors Java's `ProtectAllEffect.java`.
/// - `ValidCards$` — filter for which permanents gain protection.
/// - `Gains$` — the protection keyword to grant.
/// - `Choices$` — if present, player chooses the protection quality.
///
/// # Card script examples
/// ```text
/// A:SP$ ProtectionAll | ValidCards$ Creature.YouCtrl | Gains$ Protection from chosen color
/// ```
/// Struct form of this effect so it can participate in the
/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
/// `ProtectAllEffect` class extending `SpellAbilityEffect`.
#[manabrew_engine_macros::spell_effect(ProtectAllEffect)]
fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
let controller = sa.activating_player;
let valid_filter = sa
.ir
.valid_cards_text
.as_deref()
.unwrap_or("Creature.YouCtrl")
.to_string();
let valid_selector = sa.ir.valid_cards_selector.as_ref();
let gains = sa.ir.gains.as_deref().unwrap_or("").to_string();
// If choosing a color, do it once for all targets
let prot_keyword = if gains.contains("chosen color") {
let choices = sa
.ir
.choices
.as_deref()
.map(|s| {
s.split(',')
.map(|c| c.trim().to_string())
.collect::<Vec<_>>()
})
.unwrap_or_else(|| {
vec![
"White".into(),
"Blue".into(),
"Black".into(),
"Red".into(),
"Green".into(),
]
});
let chosen = ctx.agents[controller.index()].choose_color(controller, &choices);
match chosen {
Some(color) => format!("Protection from {}", color.to_lowercase()),
None => return,
}
} else {
gains
};
if prot_keyword.is_empty() {
return;
}
let player_ids = ctx.game.player_order.clone();
let mut targets: Vec<CardId> = Vec::new();
for &pid in &player_ids {
let zone_cards = ctx.game.cards_in_zone(ZoneType::Battlefield, pid).to_vec();
for cid in zone_cards {
if matches_valid_cards_for_sa(
ctx.game,
sa,
ctx.game.card(cid),
valid_selector,
&valid_filter,
) {
targets.push(cid);
}
}
}
for cid in targets {
if ctx.game.card(cid).zone == ZoneType::Battlefield {
ctx.game.card_mut(cid).pump_keywords.add(&prot_keyword);
}
}
}