Skip to main content

manabrew_engine/ability/effects/
tap_or_untap_all_effect.rs

1use forge_foundation::ZoneType;
2
3use super::{matches_valid_cards_for_sa, resolve_defined_players, EffectContext};
4use crate::agent::BinaryChoiceKind;
5use crate::ids::{CardId, PlayerId};
6
7/// `SP$ TapOrUntapAll` — choose tap or untap, then apply to all matching cards.
8///
9/// Mirrors Java `TapOrUntapAllEffect.java` binary prompt behavior.
10/// Struct form of this effect so it can participate in the
11/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
12/// `TapOrUntapAllEffect` class extending `SpellAbilityEffect`.
13#[manabrew_engine_macros::spell_effect(TapOrUntapAllEffect)]
14fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
15    let controller = sa.activating_player;
16    let source_name = sa
17        .source
18        .map(|cid| ctx.game.card(cid).card_name.clone())
19        .unwrap_or_else(|| "Ability".to_string());
20
21    let prompt = "Tap or untap all matching permanents?";
22    let to_tap = ctx.agents[controller.index()].choose_binary(
23        controller,
24        prompt,
25        BinaryChoiceKind::TapOrUntap,
26        None,
27        sa.source,
28        sa.api,
29    );
30
31    let valid_filter = sa
32        .ir
33        .valid_cards_text
34        .as_deref()
35        .unwrap_or("Permanent")
36        .to_string();
37    let valid_selector = sa.ir.valid_cards_selector.as_ref();
38
39    let restrict_controllers: Option<Vec<PlayerId>> =
40        if let Some(pid) = sa.target_chosen.target_player {
41            Some(vec![pid])
42        } else if let Some(defined) = sa.ir.defined_text.as_deref() {
43            Some(resolve_defined_players(defined, controller, ctx.game))
44        } else {
45            None
46        };
47
48    let mut cards: Vec<CardId> = Vec::new();
49    for &pid in &ctx.game.player_order {
50        for cid in ctx
51            .game
52            .cards_in_zone(ZoneType::Battlefield, pid)
53            .iter()
54            .copied()
55        {
56            if let Some(ref allowed) = restrict_controllers {
57                if !allowed.contains(&ctx.game.card(cid).controller) {
58                    continue;
59                }
60            }
61            if matches_valid_cards_for_sa(
62                ctx.game,
63                sa,
64                ctx.game.card(cid),
65                valid_selector,
66                &valid_filter,
67            ) {
68                cards.push(cid);
69            }
70        }
71    }
72
73    for cid in cards {
74        if ctx.game.card(cid).zone != ZoneType::Battlefield {
75            continue;
76        }
77        if to_tap {
78            if ctx.game.tap(cid) {
79                ctx.trigger_handler.run_trigger(
80                    crate::trigger::TriggerType::Taps,
81                    crate::event::RunParams {
82                        card: Some(cid),
83                        player: Some(controller),
84                        ..Default::default()
85                    },
86                    false,
87                );
88            }
89        } else if ctx.game.untap(cid) {
90            ctx.trigger_handler.run_trigger(
91                crate::trigger::TriggerType::Untaps,
92                crate::event::RunParams {
93                    card: Some(cid),
94                    player: Some(controller),
95                    ..Default::default()
96                },
97                false,
98            );
99        }
100    }
101}