Skip to main content

manabrew_engine/staticability/
static_ability_cant_target.rs

1use forge_foundation::ZoneType;
2
3use crate::card::{valid_filter, Card};
4use crate::ids::PlayerId;
5use crate::parsing::CompiledSelector;
6use crate::spellability::SpellAbility;
7use crate::staticability::StaticMode;
8
9pub fn cant_target(
10    cards: &[Card],
11    target: &Card,
12    activator: PlayerId,
13    source_card: Option<&Card>,
14    source_sa: Option<&SpellAbility>,
15) -> bool {
16    for source in cards.iter().filter(|c| c.zone == ZoneType::Battlefield) {
17        for st_ab in source
18            .static_abilities
19            .iter()
20            .filter(|sa| sa.check_mode(&StaticMode::CantTarget))
21        {
22            if !st_ab.ir.affected_zones.is_empty() {
23                if !st_ab.ir.affected_zones.contains(&target.zone) {
24                    continue;
25                }
26            } else if target.zone != ZoneType::Battlefield {
27                continue;
28            }
29
30            if !matches_valid_target(st_ab.ir.valid_target.as_ref(), target, source) {
31                continue;
32            }
33            if !matches_valid_activator(st_ab.ir.activator.as_ref(), activator, source.controller) {
34                continue;
35            }
36            if let Some(valid_sa) = st_ab.ir.valid_sa.as_deref() {
37                let Some(sa) = source_sa else {
38                    continue;
39                };
40                if !spell_ability_matches(valid_sa, sa) {
41                    continue;
42                }
43            }
44            if let (Some(valid_source), Some(src)) = (st_ab.ir.valid_source.as_ref(), source_card) {
45                if !matches_valid_target(Some(valid_source), src, source) {
46                    continue;
47                }
48            }
49            return true;
50        }
51    }
52    false
53}
54
55pub fn apply_cant_target_ability(
56    st_ab: &crate::staticability::StaticAbility,
57    target: &Card,
58    source: &Card,
59    activator: PlayerId,
60    source_card: Option<&Card>,
61    source_sa: Option<&SpellAbility>,
62) -> bool {
63    if !st_ab.ir.affected_zones.is_empty() {
64        if !st_ab.ir.affected_zones.contains(&target.zone) {
65            return false;
66        }
67    } else if target.zone != ZoneType::Battlefield {
68        return false;
69    }
70
71    if !matches_valid_target(st_ab.ir.valid_target.as_ref(), target, source) {
72        return false;
73    }
74    if !matches_valid_activator(st_ab.ir.activator.as_ref(), activator, source.controller) {
75        return false;
76    }
77    if let Some(valid_sa) = st_ab.ir.valid_sa.as_deref() {
78        let Some(sa) = source_sa else {
79            return false;
80        };
81        if !spell_ability_matches(valid_sa, sa) {
82            return false;
83        }
84    }
85    if let (Some(valid_source), Some(src)) = (st_ab.ir.valid_source.as_ref(), source_card) {
86        if !matches_valid_target(Some(valid_source), src, source) {
87            return false;
88        }
89    }
90    true
91}
92
93fn spell_ability_matches(valid_sa: &str, sa: &SpellAbility) -> bool {
94    let tokens: Vec<&str> = valid_sa
95        .split(',')
96        .map(|s| s.trim())
97        .filter(|s| !s.is_empty())
98        .collect();
99    if tokens.is_empty() {
100        return true;
101    }
102    tokens
103        .iter()
104        .all(|tok| match tok.to_ascii_lowercase().as_str() {
105            "spell" => sa.is_spell,
106            "istargeting" => sa.target_restrictions.is_some(),
107            "xcost" => sa.cost_has_x(),
108            _ => false,
109        })
110}
111
112fn matches_valid_activator(
113    valid: Option<&CompiledSelector>,
114    player: PlayerId,
115    source_controller: PlayerId,
116) -> bool {
117    valid_filter::matches_valid_player_selector_opt(valid, player, source_controller)
118}
119
120fn matches_valid_target(valid: Option<&CompiledSelector>, target: &Card, source: &Card) -> bool {
121    valid_filter::matches_valid_card_selector_opt(valid, target, source)
122}