manabrew_engine/ability/effects/
animate_all_effect.rs1use forge_foundation::{ColorSet, ZoneType};
2
3use super::trait_animate_effect::parse_animate_params;
4use super::{matches_valid_cards_for_sa, EffectContext};
5use crate::card::card_trait_changes::CardTraitChanges;
6use crate::card::perpetual::perpetual_interface::PerpetualInterface;
7use crate::card::perpetual::{
8 perpetual_abilities, perpetual_colors, perpetual_incorporate, perpetual_keywords,
9 perpetual_mana_cost, perpetual_new_pt, perpetual_types,
10};
11use crate::card::AnimateState;
12use crate::parsing::keys;
13use crate::trigger::parse_trigger;
14use forge_foundation::ManaCost;
15
16#[manabrew_engine_macros::spell_effect(AnimateAllEffect)]
34fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
35 let valid_filter = sa
36 .ir
37 .valid_cards_text
38 .clone()
39 .unwrap_or_else(|| "Card".to_string());
40 let valid_selector = sa.ir.valid_cards_selector.clone();
41
42 let anim_params = parse_animate_params(sa);
44 let power_str = anim_params.power.map(|p| p.to_string());
45 let toughness_str = anim_params.toughness.map(|t| t.to_string());
46 let types_str = if anim_params.add_types.is_empty() {
47 None
48 } else {
49 Some(anim_params.add_types.join(","))
50 };
51 let keywords_str = sa.ir.animate_keywords_text.clone();
54 let triggers_str = sa.ir.animate_triggers_text.clone();
55 let colors_str = anim_params.colors.map(|c| c.join(","));
56 let overwrite_colors = sa.param_is_true(keys::OVERWRITE_COLORS);
57 let remove_creature_types = sa.ir.animate_remove_creature_types;
58 let remove_all_abilities = sa.ir.animate_remove_all_abilities;
59 let incorporate_cost = sa.ir.animate_incorporate_text.clone();
60 let mana_cost_override = sa.ir.animate_mana_cost_override_text.clone();
61 let is_perpetual = matches!(
62 sa.ir.duration,
63 Some(crate::spellability::AbilityDuration::Perpetual)
64 );
65 let resolve_ts = ctx.game.next_effect_timestamp();
66
67 let mut parsed_triggers: Vec<crate::trigger::Trigger> = Vec::new();
69 if let Some(ref trigs) = triggers_str {
70 let source_id = sa.source.unwrap_or(crate::ids::CardId(0));
71 let source_svars = ctx.game.card(source_id).svars.clone();
72 let mut next_trig_id = 1000u32;
73 for trig_name in trigs.split(',') {
74 let trig_name = trig_name.trim();
75 if trig_name.is_empty() {
76 continue;
77 }
78 if let Some(svar_text) = source_svars.get(trig_name) {
79 if let Some(mut trig) = parse_trigger(svar_text, &mut next_trig_id) {
80 trig.execute = trig.execute.clone();
81 parsed_triggers.push(trig);
82 }
83 }
84 }
85 }
86
87 let player_ids = ctx.game.player_order.clone();
89 let mut targets = Vec::new();
90 for &pid in &player_ids {
91 let zone_cards = ctx.game.cards_in_zone(ZoneType::Battlefield, pid).to_vec();
92 for cid in zone_cards {
93 if matches_valid_cards_for_sa(
94 ctx.game,
95 sa,
96 ctx.game.card(cid),
97 valid_selector.as_ref(),
98 &valid_filter,
99 ) {
100 targets.push(cid);
101 }
102 }
103 }
104
105 for card_id in targets {
106 if ctx.game.card(card_id).zone != ZoneType::Battlefield {
107 continue;
108 }
109
110 let effect_ts = if is_perpetual { Some(resolve_ts) } else { None };
111
112 if let Some(ref mc) = incorporate_cost {
113 let ts = resolve_ts;
114 perpetual_incorporate::PerpetualIncorporate {
115 timestamp: ts,
116 incorporate: ManaCost::parse(mc),
117 }
118 .apply_effect(ctx.game.card_mut(card_id));
119 }
120 if let Some(ref mc) = mana_cost_override {
121 if let Some(ts) = effect_ts {
122 perpetual_mana_cost::PerpetualManaCost {
123 timestamp: ts,
124 mana_cost: ManaCost::parse(mc),
125 }
126 .apply_effect(ctx.game.card_mut(card_id));
127 }
128 }
129
130 if ctx.game.card(card_id).animate_state.is_none() {
132 let original_type_line = ctx.game.card(card_id).type_line.clone();
133 let original_base_power = ctx.game.card(card_id).base_power;
134 let original_base_toughness = ctx.game.card(card_id).base_toughness;
135 let original_color = ctx.game.card(card_id).color;
136 let original_keywords = ctx.game.card(card_id).keywords.clone();
137 ctx.game
138 .card_mut(card_id)
139 .set_animate_state(Some(AnimateState {
140 original_type_line,
141 original_base_power,
142 original_base_toughness,
143 original_color,
144 original_keywords: Some(original_keywords),
145 }));
146 }
147
148 if remove_all_abilities {
150 ctx.game.card_mut(card_id).clear_all_keyword_sets();
151 }
152
153 if remove_creature_types {
155 ctx.game.card_mut(card_id).clear_subtypes();
156 }
157
158 if let Some(ref types) = types_str {
160 for t in types.split(',') {
161 let t = t.trim();
162 if !t.is_empty() {
163 if let Some(ts) = effect_ts {
164 perpetual_types::PerpetualTypes {
165 timestamp: ts,
166 add_types: vec![t.to_string()],
167 }
168 .apply_effect(ctx.game.card_mut(card_id));
169 } else {
170 ctx.game.card_mut(card_id).add_type(t);
171 }
172 }
173 }
174 }
175
176 let parsed_power = power_str
178 .as_deref()
179 .and_then(|p| p.trim().parse::<i32>().ok());
180 let parsed_toughness = toughness_str
181 .as_deref()
182 .and_then(|t| t.trim().parse::<i32>().ok());
183 if let Some(ts) = effect_ts {
184 if parsed_power.is_some() || parsed_toughness.is_some() {
185 perpetual_new_pt::PerpetualNewPt {
186 timestamp: ts,
187 power: parsed_power,
188 toughness: parsed_toughness,
189 }
190 .apply_effect(ctx.game.card_mut(card_id));
191 }
192 } else {
193 if let Some(val) = parsed_power {
194 ctx.game.card_mut(card_id).set_base_power(Some(val));
195 }
196 if let Some(val) = parsed_toughness {
197 ctx.game.card_mut(card_id).set_base_toughness(Some(val));
198 }
199 }
200
201 if let Some(ref kws) = keywords_str {
204 for kw in kws.split('&') {
205 let kw = kw.trim();
206 if !kw.is_empty() {
207 if let Some(ts) = effect_ts {
208 perpetual_keywords::PerpetualKeywords {
209 timestamp: ts,
210 add_keywords: vec![kw.to_string()],
211 remove_keywords: Vec::new(),
212 remove_all: false,
213 }
214 .apply_effect(ctx.game.card_mut(card_id));
215 } else {
216 ctx.game.card_mut(card_id).add_pump_keyword(kw);
217 }
218 }
219 }
220 }
221
222 if let Some(ref colors) = colors_str {
224 let mut new_color = ColorSet::COLORLESS;
225 for c in colors.split(',') {
226 let c = c.trim().to_lowercase();
227 match c.as_str() {
228 "white" | "w" => new_color = new_color.union(ColorSet::WHITE),
229 "blue" | "u" => new_color = new_color.union(ColorSet::BLUE),
230 "black" | "b" => new_color = new_color.union(ColorSet::BLACK),
231 "red" | "r" => new_color = new_color.union(ColorSet::RED),
232 "green" | "g" => new_color = new_color.union(ColorSet::GREEN),
233 _ => {}
234 }
235 }
236 if overwrite_colors {
237 if let Some(ts) = effect_ts {
238 perpetual_colors::PerpetualColors {
239 timestamp: ts,
240 colors: new_color,
241 overwrite: true,
242 }
243 .apply_effect(ctx.game.card_mut(card_id));
244 } else {
245 ctx.game.card_mut(card_id).set_color(new_color);
246 }
247 } else if let Some(ts) = effect_ts {
248 perpetual_colors::PerpetualColors {
249 timestamp: ts,
250 colors: new_color,
251 overwrite: false,
252 }
253 .apply_effect(ctx.game.card_mut(card_id));
254 } else {
255 let union = ctx.game.card(card_id).color.union(new_color);
256 ctx.game.card_mut(card_id).set_color(union);
257 }
258 }
259
260 if !parsed_triggers.is_empty() {
262 let source_id = sa.source.unwrap_or(crate::ids::CardId(0));
263 let source_svars = ctx.game.card(source_id).svars.clone();
264 if let Some(ts) = effect_ts {
265 let changes = CardTraitChanges {
266 triggers: parsed_triggers.clone(),
267 ..Default::default()
268 };
269 perpetual_abilities::PerpetualAbilities {
270 timestamp: ts,
271 changes,
272 }
273 .apply_effect(ctx.game.card_mut(card_id));
274 for trig in &parsed_triggers {
275 if !trig.execute.is_empty() {
276 if let Some(exec_svar) = source_svars.get(&trig.execute) {
277 ctx.game
278 .card_mut(card_id)
279 .set_s_var_if_absent(trig.execute.clone(), exec_svar.clone());
280 }
281 }
282 }
283 } else {
284 for trig in &parsed_triggers {
285 ctx.game.card_mut(card_id).add_trigger(trig.clone());
286 ctx.game.card_mut(card_id).increment_pump_trigger_count();
287 if !trig.execute.is_empty() {
288 if let Some(exec_svar) = source_svars.get(&trig.execute) {
289 ctx.game
290 .card_mut(card_id)
291 .set_s_var_if_absent(trig.execute.clone(), exec_svar.clone());
292 }
293 }
294 }
295 }
296 ctx.trigger_handler
297 .register_active_trigger(ctx.game, card_id);
298 }
299 }
300}