Skip to main content

manabrew_engine/trigger/
trigger_becomes_target.rs

1use serde::{Deserialize, Serialize};
2
3use crate::card::valid_filter;
4use crate::event::RunParams;
5use crate::game::GameState;
6use crate::parsing::{keys, Params};
7use crate::spellability::SpellAbility;
8use crate::trigger::TriggerType;
9
10use super::trigger::TriggerBehavior;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct TriggerBecomesTarget {
14    pub valid_source: Option<crate::parsing::CompiledSelector>,
15    pub valid_target: Option<crate::parsing::CompiledSelector>,
16    pub require_first_time: bool,
17    pub require_valiant: bool,
18}
19
20impl TriggerBecomesTarget {
21    pub fn parse(params: &Params) -> Box<dyn TriggerBehavior> {
22        // Java parity: BecomesTarget triggers can use either `ValidTarget$` or
23        // `ValidCard$` to filter which card-becoming-target counts. Keyword-generated
24        // Ward uses `ValidCard$ Card.Self` (see keyword_gen.rs); other scripts use
25        // `ValidTarget$`. Without the `ValidCard` fallback Ward fires on *every*
26        // BecomesTarget event in the game, since both filters end up None.
27        let valid_target = params
28            .selector_cloned(keys::VALID_TARGET)
29            .or_else(|| params.selector_cloned(keys::VALID_CARD));
30        Box::new(Self {
31            valid_source: params.selector_cloned(keys::VALID_SOURCE),
32            valid_target,
33            require_first_time: params.has("FirstTime"),
34            require_valiant: params.has("Valiant"),
35        })
36    }
37}
38
39#[typetag::serde]
40impl TriggerBehavior for TriggerBecomesTarget {
41    fn trigger_type(&self) -> TriggerType {
42        TriggerType::BecomesTarget
43    }
44
45    fn perform_test(
46        &self,
47        trigger: &super::trigger::Trigger,
48        params: &RunParams,
49        game: &GameState,
50    ) -> bool {
51        let host_controller = trigger.base.card_trait_base.host_controller(game);
52        if let Some(filter) = self.valid_source.as_ref() {
53            let source_matches = if let Some(source_sa) = params.source_sa.as_ref() {
54                let raw_filter = filter.as_raw();
55                if raw_filter.starts_with("SpellAbility") {
56                    let parts: Vec<&str> = raw_filter.split('.').collect();
57                    let kind_matches = parts
58                        .first()
59                        .is_none_or(|part| part.eq_ignore_ascii_case("SpellAbility"));
60                    if !kind_matches {
61                        false
62                    } else {
63                        parts.iter().skip(1).all(|part| match *part {
64                            "OppCtrl" => source_sa.activating_player != host_controller,
65                            "YouCtrl" => source_sa.activating_player == host_controller,
66                            _ => true,
67                        })
68                    }
69                } else {
70                    source_sa.source.is_some_and(|source_card| {
71                        trigger.matches_valid_card_filter(filter, source_card, game)
72                    })
73                }
74            } else if let Some(source_card) = params.cause_card {
75                trigger.matches_valid_card_filter(filter, source_card, game)
76            } else {
77                false
78            };
79            if !source_matches {
80                return false;
81            }
82        }
83
84        if let Some(filter) = self.valid_target.as_ref() {
85            let target_card = params.target_card.or(params.card);
86            let target_player = params.target_player.or(params.player);
87            let host = game.card(trigger.host_card_id());
88            if !valid_filter::matches_valid(
89                &filter.as_raw(),
90                target_card.map(|id| game.card(id)),
91                target_player,
92                host,
93                host_controller,
94            ) {
95                return false;
96            }
97        }
98
99        if self.require_first_time && params.first_time != Some(true) {
100            return false;
101        }
102        if self.require_valiant && params.valiant != Some(true) {
103            return false;
104        }
105        true
106    }
107
108    fn set_triggering_objects(
109        &self,
110        _trigger: &super::trigger::Trigger,
111        sa: &mut SpellAbility,
112        params: &RunParams,
113        _game: &GameState,
114    ) {
115        if let Some(ref source_sa) = params.source_sa {
116            if let Some(source_card) = source_sa.source {
117                sa.set_triggering_object(
118                    crate::ability::AbilityKey::Source,
119                    source_card.0.to_string(),
120                );
121            }
122            sa.set_triggering_spell_ability("SourceSA", source_sa.clone());
123        }
124        if let Some(card) = params.target_card.or(params.card) {
125            sa.set_triggering_object(crate::ability::AbilityKey::Target, card.0.to_string());
126        } else if let Some(p) = params.target_player {
127            sa.set_triggering_object(crate::ability::AbilityKey::Target, p.0.to_string());
128        }
129    }
130
131    fn get_important_stack_objects(
132        &self,
133        _trigger: &super::trigger::Trigger,
134        sa: &SpellAbility,
135    ) -> String {
136        format!(
137            "Source: {}, Target: {}",
138            sa.get_triggering_object(crate::ability::AbilityKey::Source)
139                .unwrap_or(""),
140            sa.get_triggering_object(crate::ability::AbilityKey::Target)
141                .unwrap_or("")
142        )
143    }
144}