manabrew_engine/staticability/
static_ability_cant_target.rs1use 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}