manabrew_engine/trigger/
trigger_attackers_declared.rs1use 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 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 {
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 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}