Skip to main content

manabrew_engine/trigger/
trigger_damage_done_once.rs

1use 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        // Java: if Target != null { "Damaged: " + Target + ", " } + "Amount: " + DamageAmount
141        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
161/// Returns the total damage amount from the damage map.
162/// Java: TriggerDamageDoneOnce.getDamageAmount
163///
164/// Note: The Java version filters entries by ValidSource param; this standalone
165/// function passes all entries through. Filtering will be added when trigger
166/// param context is available.
167pub 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
174/// Returns the damage source card IDs from the damage map.
175/// Java: TriggerDamageDoneOnce.getDamageSources
176///
177/// Note: The Java version filters entries by ValidSource param; this standalone
178/// function returns all source card IDs. Filtering will be added when trigger
179/// param context is available.
180pub 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}