Skip to main content

manabrew_engine/trigger/
trigger_attackers_declared.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    event::RunParams,
5    game::GameState,
6    parsing::compare::compare_expr,
7    parsing::{keys, Params},
8    spellability::SpellAbility,
9    trigger::TriggerType,
10};
11
12use super::trigger::TriggerBehavior;
13
14fn attacked_target_matches(
15    trigger: &super::trigger::Trigger,
16    filter: &crate::parsing::CompiledSelector,
17    params: &RunParams,
18    game: &GameState,
19) -> bool {
20    params
21        .attacked_card
22        .is_some_and(|card_id| trigger.matches_valid_card_filter(filter, card_id, game))
23        || params
24            .attacked_player
25            .is_some_and(|player_id| trigger.matches_valid_player_filter(filter, player_id, game))
26        || params.defenders_card_ids.as_ref().is_some_and(|cards| {
27            cards
28                .iter()
29                .copied()
30                .any(|card_id| trigger.matches_valid_card_filter(filter, card_id, game))
31        })
32        || params.defenders_player_ids.as_ref().is_some_and(|players| {
33            players
34                .iter()
35                .copied()
36                .any(|player_id| trigger.matches_valid_player_filter(filter, player_id, game))
37        })
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct TriggerAttackersDeclared {
42    pub valid_player: Option<crate::parsing::CompiledSelector>,
43    pub valid_attackers: Option<crate::parsing::CompiledSelector>,
44    pub valid_attackers_amount: Option<String>,
45    pub attacked_target: Option<crate::parsing::CompiledSelector>,
46    pub one_target: bool,
47}
48
49impl TriggerAttackersDeclared {
50    pub fn parse(mode_str: &str, params: &Params) -> Box<dyn TriggerBehavior> {
51        Box::new(Self {
52            valid_player: params.selector_cloned_any(&[keys::ATTACKING_PLAYER, keys::VALID_PLAYER]),
53            valid_attackers: params.selector_cloned(keys::VALID_ATTACKERS),
54            valid_attackers_amount: params.get_cloned(keys::VALID_ATTACKERS_AMOUNT),
55            attacked_target: params.selector_cloned("AttackedTarget"),
56            one_target: mode_str == "AttackersDeclaredOneTarget",
57        })
58    }
59}
60
61#[typetag::serde]
62impl TriggerBehavior for TriggerAttackersDeclared {
63    fn trigger_type(&self) -> TriggerType {
64        if self.one_target {
65            TriggerType::AttackersDeclaredOneTarget
66        } else {
67            TriggerType::AttackersDeclared
68        }
69    }
70
71    fn perform_test(
72        &self,
73        trigger: &super::trigger::Trigger,
74        params: &RunParams,
75        game: &GameState,
76    ) -> bool {
77        if !trigger.matches_optional_valid_player_filter(
78            &self.valid_player,
79            params.attacking_player.or(params.player),
80            game,
81        ) {
82            return false;
83        }
84        if let Some(filter) = &self.attacked_target {
85            if !attacked_target_matches(trigger, filter, params, game) {
86                return false;
87            }
88        }
89        if self.valid_attackers.is_none()
90            && self.valid_attackers_amount.is_none()
91            && params.attacker_ids.is_none()
92        {
93            return true;
94        }
95        let Some(attacker_ids) = params.attacker_ids.as_ref() else {
96            return false;
97        };
98        let matching_count = if let Some(filter) = &self.valid_attackers {
99            attacker_ids
100                .iter()
101                .filter(|&&attacker| trigger.matches_valid_card_filter(filter, attacker, game))
102                .count()
103        } else {
104            attacker_ids.len()
105        };
106        if let Some(amount_filter) = &self.valid_attackers_amount {
107            return compare_expr(matching_count as i32, amount_filter);
108        }
109        matching_count > 0
110    }
111
112    fn set_triggering_objects(
113        &self,
114        _trigger: &super::trigger::Trigger,
115        sa: &mut SpellAbility,
116        params: &RunParams,
117        _game: &GameState,
118    ) {
119        // Java: sa.setTriggeringObject(AbilityKey.Attackers, attackers);
120        if let Some(attacker_ids) = params.attacker_ids.as_ref() {
121            let csv = attacker_ids
122                .iter()
123                .map(|c| c.0.to_string())
124                .collect::<Vec<_>>()
125                .join(",");
126            sa.set_triggering_object(crate::ability::AbilityKey::Attackers, &csv);
127        }
128        // Java: sa.setTriggeringObject(AbilityKey.AttackedTarget, attackedTarget);
129        // Combine defender players and defender cards into a single CSV
130        {
131            let mut parts = Vec::new();
132            if let Some(players) = params.defenders_player_ids.as_ref() {
133                for p in players {
134                    parts.push(p.0.to_string());
135                }
136            }
137            if let Some(cards) = params.defenders_card_ids.as_ref() {
138                for c in cards {
139                    parts.push(c.0.to_string());
140                }
141            }
142            if let Some(p) = params.attacked_player {
143                if parts.is_empty() {
144                    parts.push(p.0.to_string());
145                }
146            } else if let Some(c) = params.attacked_card {
147                if parts.is_empty() {
148                    parts.push(c.0.to_string());
149                }
150            }
151            if !parts.is_empty() {
152                sa.set_triggering_object(
153                    crate::ability::AbilityKey::AttackedTarget,
154                    parts.join(","),
155                );
156            }
157        }
158        // Java: sa.setTriggeringObjectsFrom(runParams, AbilityKey.AttackingPlayer);
159        if let Some(p) = params.attacking_player {
160            sa.set_triggering_object(crate::ability::AbilityKey::AttackingPlayer, p.0.to_string());
161        }
162    }
163
164    fn get_important_stack_objects(
165        &self,
166        _trigger: &super::trigger::Trigger,
167        sa: &SpellAbility,
168    ) -> String {
169        format!(
170            "Number Attackers: {}",
171            sa.get_triggering_object(crate::ability::AbilityKey::Attackers)
172                .unwrap_or("")
173        )
174    }
175}