Skip to main content

manabrew_engine/trigger/
wrapped_ability.rs

1use std::collections::HashMap;
2
3use crate::game::GameState;
4use crate::keyword::keyword_instance::Keyword;
5use crate::spellability::alternative_cost::AlternativeCost;
6use crate::spellability::SpellAbility;
7use crate::trigger::trigger::Trigger;
8
9/// Minimal wrapped ability shim for trigger parity.
10/// Full Java parity (revalidation at resolve-time) will be implemented here.
11#[derive(Debug, Clone)]
12pub struct WrappedAbility {
13    pub wrapped: SpellAbility,
14    /// The trigger that created this wrapped ability.
15    /// Used by `get_stack_description` and similar methods.
16    pub trigger: Option<Trigger>,
17    additional_ability_lists: HashMap<String, Vec<String>>,
18}
19
20impl WrappedAbility {
21    pub fn new(wrapped: SpellAbility) -> Self {
22        Self {
23            wrapped,
24            trigger: None,
25            additional_ability_lists: HashMap::new(),
26        }
27    }
28
29    pub fn with_trigger(wrapped: SpellAbility, trigger: Trigger) -> Self {
30        Self {
31            wrapped,
32            trigger: Some(trigger),
33            additional_ability_lists: HashMap::new(),
34        }
35    }
36
37    pub fn has_param(&self, key: &str) -> bool {
38        self.get_param(key).is_some() || self.wrapped.has_additional_ability(key)
39    }
40
41    pub fn add_cost_to_hash_list(&mut self, cost_key: &str, value: String) {
42        self.wrapped
43            .paid_hash
44            .entry(cost_key.to_string())
45            .or_default()
46            .push(value);
47    }
48
49    pub fn reset_paid_hash(&mut self) {
50        self.wrapped.paid_hash.clear();
51    }
52
53    pub fn has_triggering_object(&self, key: &str) -> bool {
54        self.wrapped.has_triggering_object(key)
55    }
56
57    pub fn reset_triggering_objects(&mut self) {
58        self.wrapped.trigger_objects.clear();
59    }
60
61    pub fn can_play(&self) -> bool {
62        true
63    }
64
65    pub fn copy(&self) -> Self {
66        self.clone()
67    }
68
69    pub fn yield_key(&self) -> String {
70        if !self.wrapped.stack_description.is_empty() {
71            self.wrapped.stack_description.clone()
72        } else if !self.wrapped.description.is_empty() {
73            self.wrapped.description.clone()
74        } else {
75            self.wrapped.ability_text.clone()
76        }
77    }
78
79    pub fn to_unsuppressed_string(&self) -> String {
80        self.yield_key()
81    }
82
83    pub fn has_s_var(&self, game: &GameState, key: &str) -> bool {
84        self.wrapped
85            .source
86            .map(|cid| game.card(cid).svars.contains_key(key))
87            .unwrap_or(false)
88    }
89
90    pub fn reset_once_resolved(&mut self) {
91        // Placeholder for Java parity; Rust currently tracks resolve state elsewhere.
92    }
93
94    pub fn uses_targeting(&self) -> bool {
95        self.wrapped.uses_targeting()
96    }
97
98    pub fn has_additional_ability(&self, key: &str) -> bool {
99        self.wrapped.has_additional_ability(key)
100            || self.additional_ability_lists.contains_key(key)
101            || self.get_param(key).is_some()
102    }
103
104    pub fn reset_targets(&mut self) {
105        self.wrapped.clear_targets();
106    }
107
108    pub fn resolve(&self) -> bool {
109        true
110    }
111
112    // ── Delegating methods (Java WrappedAbility parity) ──────────────────
113
114    /// Mirrors Java's `WrappedAbility.getParam(String)`.
115    /// Delegates to `sa.getParam(key)`.
116    pub fn get_param(&self, key: &str) -> Option<&str> {
117        if self.wrapped.param_is_true(key) {
118            Some("True")
119        } else {
120            self.wrapped.param_value(key)
121        }
122    }
123
124    /// Mirrors Java's `WrappedAbility.getParamOrDefault(String, String)`.
125    /// Delegates to `sa.getParamOrDefault(key, defaultValue)`.
126    pub fn get_param_or_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
127        self.get_param(key).unwrap_or(default)
128    }
129
130    /// Mirrors Java's `WrappedAbility.setPaidHash(...)`.
131    /// Replaces the paid hash wholesale.
132    pub fn set_paid_hash(&mut self, hash: HashMap<String, Vec<String>>) {
133        self.wrapped.paid_hash = hash;
134    }
135
136    /// Mirrors Java's `WrappedAbility.getPaidList(String, boolean)`.
137    /// Returns the list of paid cost values for the given key.
138    /// The `_intrinsic` flag is unused in Rust (Java uses it to pick column
139    /// from a `TreeBasedTable`; Rust flattens into a single `Vec`).
140    pub fn get_paid_list(&self, key: &str, _intrinsic: bool) -> Vec<String> {
141        self.wrapped.paid_hash.get(key).cloned().unwrap_or_default()
142    }
143
144    /// Mirrors Java's `WrappedAbility.setTriggeringObjects(Map)`.
145    /// Replaces all triggering objects wholesale.
146    pub fn set_triggering_objects(&mut self, objects: HashMap<String, String>) {
147        self.wrapped.trigger_objects.clear();
148        for (key, value) in objects {
149            self.wrapped.set_triggering_object(&key, value);
150        }
151    }
152
153    /// Mirrors Java's `WrappedAbility.setTriggeringObject(AbilityKey, Object)`.
154    /// Sets a single triggering object by key.
155    pub fn set_triggering_object(&mut self, key: &str, value: String) {
156        self.wrapped.set_triggering_object(key, value);
157    }
158
159    /// Mirrors Java's `WrappedAbility.getTriggeringObject(AbilityKey)`.
160    /// Delegates to `sa.getTriggeringObject(key)`.
161    pub fn get_triggering_object(&self, key: &str) -> Option<&str> {
162        self.wrapped.get_triggering_object(key)
163    }
164
165    /// Mirrors Java's `WrappedAbility.getStackDescription(boolean)`.
166    ///
167    /// Simplified version: returns the trigger description (with ABILITY
168    /// replacement) plus important stack objects, if a trigger is available.
169    /// Falls back to the inner SpellAbility's stack_description.
170    pub fn get_stack_description(&self, game: &GameState) -> String {
171        if let Some(ref trigger) = self.trigger {
172            let source = self.wrapped.source.unwrap_or(crate::ids::CardId(0));
173            let player = self.wrapped.activating_player;
174            let base = trigger.replace_ability_text(&trigger.description, game, source, player);
175            let important = trigger
176                .mode
177                .get_important_stack_objects(trigger, &self.wrapped);
178            let mut sb = base;
179            if !important.is_empty() {
180                sb.push_str(" [");
181                sb.push_str(&important);
182                sb.push(']');
183            }
184            sb
185        } else if !self.wrapped.stack_description.is_empty() {
186            self.wrapped.stack_description.clone()
187        } else {
188            self.wrapped.description.clone()
189        }
190    }
191
192    /// Mirrors Java's `WrappedAbility.getSVar(String)`.
193    /// Looks up an SVar on the source card.
194    pub fn get_s_var(&self, game: &GameState, name: &str) -> Option<String> {
195        self.wrapped
196            .source
197            .and_then(|cid| game.card(cid).get_s_var(name).map(str::to_string))
198    }
199
200    /// Mirrors Java's `WrappedAbility.getSVarInt(String)`.
201    /// Returns the SVar parsed as an integer, or `None` if absent/unparseable.
202    pub fn get_s_var_int(&self, game: &GameState, name: &str) -> Option<i32> {
203        self.get_s_var(game, name)
204            .and_then(|v| v.parse::<i32>().ok())
205    }
206
207    /// Mirrors Java's `WrappedAbility.setSVar(String, String)`.
208    /// Sets an SVar on the source card.
209    pub fn set_s_var(&self, game: &mut GameState, name: &str, value: &str) {
210        if let Some(cid) = self.wrapped.source {
211            game.card_mut(cid).set_s_var(name, value);
212        }
213    }
214
215    /// Mirrors Java's `WrappedAbility.getAdditionalAbility(String)`.
216    /// In Java this returns a SpellAbility parsed from the named param;
217    /// in Rust we return the raw param value which callers can parse.
218    pub fn get_additional_ability(&self, key: &str) -> Option<&str> {
219        self.get_param(key)
220    }
221
222    /// Mirrors Java's `WrappedAbility.getAdditionalAbilityList(String)`.
223    /// Returns the param value split by `&` (the Java list separator for
224    /// additional ability lists in card scripts).
225    pub fn get_additional_ability_list(&self, name: &str) -> Vec<String> {
226        if let Some(list) = self.additional_ability_lists.get(name) {
227            return list.clone();
228        }
229        self.get_param(name)
230            .map(|v| v.split('&').map(|s| s.trim().to_string()).collect())
231            .unwrap_or_default()
232    }
233
234    /// Mirrors Java's `WrappedAbility.setAdditionalAbilityList(String, List)`.
235    /// Stores the list as an `&`-joined param value.
236    pub fn set_additional_ability_list(&mut self, name: &str, list: Vec<String>) {
237        self.additional_ability_lists.insert(name.to_string(), list);
238    }
239
240    /// Mirrors Java's `WrappedAbility.isAlternativeCost(AlternativeCost)`.
241    /// Checks whether this ability was cast using the given alternative cost.
242    pub fn is_alternative_cost(&self, ac: AlternativeCost) -> bool {
243        self.wrapped.alt_cost == Some(ac)
244    }
245
246    /// Mirrors Java's `WrappedAbility.isKeyword(Keyword)`.
247    /// Checks whether this ability's params contain a `Keyword$` entry
248    /// matching the given keyword.
249    pub fn is_keyword(&self, kw: Keyword) -> bool {
250        self.wrapped
251            .param_value("Keyword")
252            .map(|v| {
253                let kw_str = format!("{:?}", kw);
254                v.eq_ignore_ascii_case(&kw_str)
255            })
256            .unwrap_or(false)
257    }
258}