manabrew_engine/ability/effects/
untap_all_effect.rs1use forge_foundation::ZoneType;
2
3use super::{matches_valid_cards_for_sa, EffectContext};
4use crate::ids::CardId;
5
6#[manabrew_engine_macros::spell_effect(UntapAllEffect)]
19fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
20 let valid_cards = sa.ir.valid_cards_selector.as_ref();
21
22 let player_ids = ctx.game.player_order.clone();
23 let mut to_untap: Vec<CardId> = Vec::new();
24 for &pid in &player_ids {
25 let zone_cards = ctx.game.cards_in_zone(ZoneType::Battlefield, pid).to_vec();
26 for cid in zone_cards {
27 if matches_valid_cards_for_sa(
28 ctx.game,
29 sa,
30 ctx.game.card(cid),
31 valid_cards,
32 "Permanent",
33 ) {
34 to_untap.push(cid);
35 }
36 }
37 }
38
39 for card_id in to_untap {
40 if ctx.game.card(card_id).zone == ZoneType::Battlefield {
41 ctx.game.untap(card_id);
42 ctx.trigger_handler.run_trigger(
44 crate::trigger::TriggerType::Untaps,
45 crate::event::RunParams {
46 card: Some(card_id),
47 ..Default::default()
48 },
49 false,
50 );
51 }
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use crate::ability::spell_ability_effect::SpellAbilityEffect;
58 use forge_foundation::{CardTypeLine, ColorSet, ManaCost, ZoneType};
59 use std::collections::HashMap;
60
61 use crate::ability::effects::EffectContext;
62 use crate::agent::PassAgent;
63 use crate::card::Card;
64 use crate::game::GameState;
65 use crate::ids::{CardId, PlayerId};
66 use crate::mana::ManaPool;
67 use crate::spellability::SpellAbility;
68 use crate::trigger::handler::TriggerHandler;
69
70 fn make_land(game: &mut GameState, owner: PlayerId) -> CardId {
71 let c = Card::new(
72 CardId(0),
73 "Forest".into(),
74 owner,
75 CardTypeLine::parse("Basic Land Forest"),
76 ManaCost::parse(""),
77 ColorSet::COLORLESS,
78 None,
79 None,
80 vec![],
81 vec![],
82 );
83 game.create_card(c)
84 }
85
86 #[test]
87 fn untap_all_untaps_matching_permanents() {
88 let mut game = GameState::new(&["Alice", "Bob"], 20);
89 let p0 = PlayerId(0);
90 let l1 = make_land(&mut game, p0);
91 let l2 = make_land(&mut game, p0);
92 game.move_card(l1, ZoneType::Battlefield, p0);
93 game.move_card(l2, ZoneType::Battlefield, p0);
94 game.tap(l1);
95 game.tap(l2);
96 assert!(game.card(l1).tapped);
97 assert!(game.card(l2).tapped);
98
99 let sa = SpellAbility::new_simple(None, p0, "A:SP$ UntapAll | ValidCards$ Land.YouCtrl");
100 let mut th = TriggerHandler::new();
101 let mut agents: Vec<Box<dyn crate::agent::PlayerAgent>> =
102 vec![Box::new(PassAgent), Box::new(PassAgent)];
103 let mut mp = vec![ManaPool::default(), ManaPool::default()];
104 let templates = HashMap::new();
105 let templates_variants = HashMap::new();
106 let token_fallback = HashMap::new();
107 let edition_dates: HashMap<String, String> = HashMap::new();
108 let mut rng_adapter = crate::game_rng::ThreadRngAdapter;
109 let mut ctx = EffectContext {
110 game: &mut game,
111 combat: None,
112 agents: &mut agents,
113 trigger_handler: &mut th,
114 token_templates: &templates,
115 token_art_variants: &templates_variants,
116 token_fallback: &token_fallback,
117 edition_dates: &edition_dates,
118 mana_pools: &mut mp,
119 parent_target_card: None,
120 rng: &mut rng_adapter,
121 };
122 super::UntapAllEffect::resolve(&mut ctx, &sa);
123
124 assert!(!ctx.game.card(l1).tapped);
125 assert!(!ctx.game.card(l2).tapped);
126 }
127}