manabrew_engine/trigger/
trigger_damage_done_once.rs1use std::collections::HashSet;
2
3use serde::{Deserialize, Serialize};
4
5use crate::event::RunParams;
6use crate::game::GameState;
7use crate::ids::CardId;
8use crate::parsing::{keys, Params};
9use crate::spellability::SpellAbility;
10use crate::trigger::TriggerType;
11
12use super::trigger::TriggerBehavior;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct TriggerDamageDoneOnce {
16 pub valid_source: Option<crate::parsing::CompiledSelector>,
17 pub valid_target: Option<crate::parsing::CompiledSelector>,
18 pub combat_damage_only: bool,
19}
20
21impl TriggerDamageDoneOnce {
22 pub fn parse(params: &Params) -> Box<dyn TriggerBehavior> {
23 Box::new(Self {
24 valid_source: params.selector_cloned(keys::VALID_SOURCE),
25 valid_target: params.selector_cloned(keys::VALID_TARGET),
26 combat_damage_only: params.is_true(keys::COMBAT_DAMAGE),
27 })
28 }
29
30 fn damage_amount(
31 &self,
32 trigger: &super::trigger::Trigger,
33 params: &RunParams,
34 game: &GameState,
35 ) -> i32 {
36 if let Some(map) = params.damage_map.as_ref() {
37 return map
38 .entries()
39 .into_iter()
40 .filter(|(source, _, _)| {
41 trigger.matches_optional_valid_card_filter(
42 &self.valid_source,
43 Some(*source),
44 game,
45 )
46 })
47 .map(|(_, _, amount)| amount)
48 .sum();
49 }
50 if self.valid_source.is_some()
51 && !trigger.matches_optional_valid_card_filter(
52 &self.valid_source,
53 params.damage_source,
54 game,
55 )
56 {
57 return 0;
58 }
59 params.damage_amount.unwrap_or(0)
60 }
61
62 fn damage_sources(
63 &self,
64 trigger: &super::trigger::Trigger,
65 params: &RunParams,
66 game: &GameState,
67 ) -> Vec<CardId> {
68 if let Some(map) = params.damage_map.as_ref() {
69 let mut seen = HashSet::new();
70 let mut sources = Vec::new();
71 for (source, _, _) in map.entries() {
72 if !trigger.matches_optional_valid_card_filter(
73 &self.valid_source,
74 Some(source),
75 game,
76 ) {
77 continue;
78 }
79 if seen.insert(source) {
80 sources.push(source);
81 }
82 }
83 return sources;
84 }
85 params.damage_source.into_iter().collect()
86 }
87}
88
89#[typetag::serde]
90impl TriggerBehavior for TriggerDamageDoneOnce {
91 fn trigger_type(&self) -> TriggerType {
92 TriggerType::DamageDoneOnce
93 }
94
95 fn perform_test(
96 &self,
97 trigger: &super::trigger::Trigger,
98 params: &RunParams,
99 game: &GameState,
100 ) -> bool {
101 if self.combat_damage_only && params.is_combat_damage != Some(true) {
102 return false;
103 }
104 if !trigger.matches_damage_target_filter(&self.valid_target, params, game, true) {
105 return false;
106 }
107 self.damage_amount(trigger, params, game) > 0
108 }
109
110 fn set_triggering_objects(
111 &self,
112 trigger: &super::trigger::Trigger,
113 sa: &mut SpellAbility,
114 params: &RunParams,
115 game: &GameState,
116 ) {
117 if let Some(card) = params.damage_target_card {
118 sa.set_triggering_object(crate::ability::AbilityKey::Target, card);
119 sa.set_triggering_object(crate::ability::AbilityKey::TargetCard, card);
120 } else if let Some(player) = params.damage_target_player {
121 sa.set_triggering_object(crate::ability::AbilityKey::Target, player);
122 sa.set_triggering_object(crate::ability::AbilityKey::TargetPlayer, player);
123 }
124 let sources = self.damage_sources(trigger, params, game);
125 if !sources.is_empty() {
126 sa.set_triggering_object(crate::ability::AbilityKey::Sources, sources);
127 }
128 if let Some(p) = params.attacking_player {
129 sa.set_triggering_object(crate::ability::AbilityKey::AttackingPlayer, p);
130 }
131 let amount = self.damage_amount(trigger, params, game);
132 sa.set_triggering_object(crate::ability::AbilityKey::DamageAmount, amount.to_string());
133 }
134
135 fn get_important_stack_objects(
136 &self,
137 _trigger: &super::trigger::Trigger,
138 sa: &SpellAbility,
139 ) -> String {
140 let target = sa
142 .get_triggering_object(crate::ability::AbilityKey::Target)
143 .unwrap_or("");
144 if target.is_empty() {
145 format!(
146 "Amount: {}",
147 sa.get_triggering_object(crate::ability::AbilityKey::DamageAmount)
148 .unwrap_or("")
149 )
150 } else {
151 format!(
152 "Damaged: {}, Amount: {}",
153 target,
154 sa.get_triggering_object(crate::ability::AbilityKey::DamageAmount)
155 .unwrap_or("")
156 )
157 }
158 }
159}
160
161pub fn get_damage_amount(params: &RunParams) -> i32 {
168 match params.damage_map.as_ref() {
169 Some(map) => map.total_amount(),
170 None => 0,
171 }
172}
173
174pub fn get_damage_sources(params: &RunParams) -> Vec<CardId> {
181 match params.damage_map.as_ref() {
182 Some(map) => {
183 let mut seen = HashSet::new();
184 let mut sources = Vec::new();
185 for (source, _, _) in map.entries() {
186 if seen.insert(source) {
187 sources.push(source);
188 }
189 }
190 sources
191 }
192 None => Vec::new(),
193 }
194}