Skip to main content

manabrew_engine/staticability/
static_ability_attack_restrict.rs

1use forge_foundation::ZoneType;
2
3use crate::card::Card;
4use crate::ids::PlayerId;
5use crate::staticability::StaticMode;
6
7pub fn global_attack_restrict(cards: &[Card]) -> Option<i32> {
8    let mut max: Option<i32> = None;
9    for source in cards.iter().filter(|c| c.zone == ZoneType::Battlefield) {
10        for st_ab in source
11            .static_abilities
12            .iter()
13            .filter(|sa| sa.check_mode(&StaticMode::AttackRestrict))
14        {
15            if st_ab.ir.valid_defender_text.is_some() {
16                continue;
17            }
18            let m = st_ab
19                .ir
20                .max_attackers
21                .as_deref()
22                .map(|s| eval_amount(source, s))
23                .unwrap_or(1);
24            max = Some(max.map_or(m, |x| x.min(m)));
25        }
26    }
27    max
28}
29
30pub fn attack_restrict_num_for_defender(cards: &[Card], defender: PlayerId) -> Option<i32> {
31    let mut max: Option<i32> = None;
32    for source in cards.iter().filter(|c| c.zone == ZoneType::Battlefield) {
33        for st_ab in source
34            .static_abilities
35            .iter()
36            .filter(|sa| sa.check_mode(&StaticMode::AttackRestrict))
37        {
38            let Some(valid_defender) = st_ab.ir.valid_defender_text.as_deref() else {
39                continue;
40            };
41            if !matches_valid_defender(valid_defender, defender, source.controller) {
42                continue;
43            }
44            let m = st_ab
45                .ir
46                .max_attackers
47                .as_deref()
48                .map(|s| eval_amount(source, s))
49                .unwrap_or(1);
50            max = Some(max.map_or(m, |x| x.min(m)));
51        }
52    }
53    max
54}
55
56fn eval_amount(source: &Card, expr: &str) -> i32 {
57    if let Ok(n) = expr.parse::<i32>() {
58        return n;
59    }
60    if let Some(svar) = source.svars.get(expr) {
61        if let Ok(n) = svar.parse::<i32>() {
62            return n;
63        }
64        let mut sa =
65            crate::spellability::SpellAbility::new_empty(Some(source.id), source.controller);
66        sa.kicked = false;
67        return crate::ability::effects::evaluate_svar(svar, &sa);
68    }
69    1
70}
71
72fn matches_valid_defender(valid: &str, defender: PlayerId, source_controller: PlayerId) -> bool {
73    if valid.eq_ignore_ascii_case("Player") {
74        return true;
75    }
76    if valid.eq_ignore_ascii_case("You") || valid.eq_ignore_ascii_case("YouCtrl") {
77        return defender == source_controller;
78    }
79    if valid.eq_ignore_ascii_case("Opponent") || valid.eq_ignore_ascii_case("OppCtrl") {
80        return defender != source_controller;
81    }
82    true
83}