1use std::collections::BTreeMap;
2
3use crate::ability::ProducedMana;
4use crate::card::counter_type::parse_counter_type;
5use crate::card::valid_filter::CardTraitRequirementsIr;
6use crate::card::CounterType;
7use crate::parsing::amount::AmountExpr;
8use crate::parsing::{
9 keys, parse_semantic_param_value, split_param_list_value, CompiledSelector, Params,
10 ParsedParams, SemanticParamValue,
11};
12use crate::spellability::{AbilityDuration, ReplaceDyingCondition, SpellAbilityMode};
13use forge_foundation::ZoneType;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum EffectIr {
17 DamageAll(NumericAmountIr),
18 DealDamage(DealDamageIr),
19 Draw(NumericAmountIr),
20 GainLife(NumericAmountIr),
21 LifeSet(NumericAmountIr),
22 LoseLife(NumericAmountIr),
23 Mill(NumericAmountIr),
24 Poison(NumericAmountIr),
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum DayTimeValue {
29 Day,
30 Night,
31 Switch,
32}
33
34impl DayTimeValue {
35 pub fn parse(raw: &str) -> Option<Self> {
36 match raw {
37 "Day" => Some(Self::Day),
38 "Night" => Some(Self::Night),
39 "Switch" => Some(Self::Switch),
40 _ => None,
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum DebuffAllSuffixKeywords {
47 Walk,
48}
49
50impl DebuffAllSuffixKeywords {
51 pub fn parse(raw: &str) -> Option<Self> {
52 match raw {
53 "walk" => Some(Self::Walk),
54 _ => None,
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Default)]
60pub struct DebuffIr {
61 pub num_present: bool,
62 pub keywords: Vec<String>,
63 pub all_suffix_keywords: Option<DebuffAllSuffixKeywords>,
64}
65
66impl DebuffIr {
67 pub fn from_parsed(params: &ParsedParams<'_>) -> Self {
68 Self {
69 num_present: params.has(keys::NUM),
70 keywords: split_param_list_value(params.get(keys::KEYWORDS), " & "),
71 all_suffix_keywords: params
72 .get(keys::ALL_SUFFIX_KEYWORDS)
73 .and_then(DebuffAllSuffixKeywords::parse),
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Default)]
79pub struct SpellAbilityIr {
80 pub card_trait_requirements: CardTraitRequirementsIr,
81 pub effect: Option<EffectIr>,
82 pub mode_text: Option<String>,
83 pub discard_mode: Option<crate::ability::effects::discard_effect::DiscardMode>,
84 pub produced_ir: Option<ProducedMana>,
85 pub reflect_property: Option<String>,
86 pub color_or_type: Option<String>,
87 pub restrict_valid: Option<String>,
88 pub valid_filter_text: Option<String>,
89 pub valid_filter_selector: Option<CompiledSelector>,
90 pub adds_keywords: Option<String>,
91 pub adds_keywords_valid: Option<String>,
92 pub adds_counters: Option<String>,
93 pub adds_counters_valid: Option<String>,
94 pub triggers_when_spent: Option<String>,
95 pub valid_cards_text: Option<String>,
96 pub valid_cards_selector: Option<CompiledSelector>,
97 pub valid_card_text: Option<String>,
98 pub valid_card_selector: Option<CompiledSelector>,
99 pub valid_players_text: Option<String>,
100 pub valid_players_selector: Option<CompiledSelector>,
101 pub valid_player_text: Option<String>,
102 pub valid_player_selector: Option<CompiledSelector>,
103 pub valid_tgts_text: Option<String>,
104 pub valid_tgts_selector: Option<CompiledSelector>,
105 pub valid_target_text: Option<String>,
106 pub valid_target_selector: Option<CompiledSelector>,
107 pub all_valid_text: Option<String>,
108 pub all_valid_selector: Option<CompiledSelector>,
109 pub defined_name_text: Option<String>,
110 pub set_chosen_number: Option<String>,
111 pub starting_with: Option<String>,
112 pub repeat: Option<String>,
113 pub branch_condition_svar: Option<String>,
114 pub branch_condition_svar_compare: Option<String>,
115 pub true_sub_ability: Option<String>,
116 pub false_sub_ability: Option<String>,
117 pub play_cost_text: Option<String>,
118 pub unless_cost: Option<String>,
119 pub amount: Option<String>,
120 pub counter_num_text: Option<String>,
121 pub max_repeat: Option<String>,
122 pub precost_desc: Option<String>,
123 pub token_amount: Option<String>,
124 pub sac_valid: Option<String>,
125 pub defined: Option<DefinedExpr>,
126 pub defined_player: Option<DefinedExpr>,
127 pub defined_text: Option<String>,
128 pub defined_player_text: Option<String>,
129 pub controller_text: Option<String>,
130 pub placer_text: Option<String>,
131 pub effect_owner: Option<DefinedExpr>,
132 pub effect_owner_text: Option<String>,
133 pub origin_text: Option<String>,
134 pub origin_zone: Option<ZoneType>,
135 pub origin_zones: Vec<ZoneType>,
136 pub destination_text: Option<String>,
137 pub destination_zone: Option<ZoneType>,
138 pub destination_zone_2: Option<ZoneType>,
139 pub zone1: Option<ZoneType>,
140 pub zone2: Option<ZoneType>,
141 pub change_type: Option<String>,
142 pub change_type_selector: Option<CompiledSelector>,
143 pub primary_text: Option<String>,
144 pub secondary_text: Option<String>,
145 pub secondary_type_text: Option<String>,
146 pub library_position: Option<String>,
147 pub library_position_2: Option<String>,
148 pub library_position_alternative: Option<String>,
149 pub shuffle_raw: Option<String>,
150 pub no_shuffle: bool,
151 pub mandatory: bool,
152 pub toggle: bool,
153 pub can_repeat_modes: bool,
154 pub entwine: bool,
155 pub skip_untap: bool,
156 pub tapped: bool,
157 pub shuffle: bool,
158 pub optional: bool,
159 pub gain_control: bool,
160 pub gain_control_text: Option<String>,
161 pub forget_changed: bool,
162 pub adds_no_counter: bool,
163 pub no_regen: bool,
164 pub damage_map: bool,
165 pub remember_damaged_creature: bool,
166 pub num_dmg_present: bool,
167 pub boon: bool,
168 pub unique: bool,
169 pub exile_on_moved: bool,
170 pub forget_on_phased_in: bool,
171 pub forget_counter: bool,
172 pub radiance: bool,
173 pub chooser: Option<String>,
174 pub gains: Option<String>,
175 pub choices: Option<String>,
176 pub choices_selector: Option<CompiledSelector>,
177 pub debuff: DebuffIr,
178 pub for_each_text: Option<String>,
179 pub exclude: Option<String>,
180 pub object_text: Option<String>,
181 pub source_text: Option<String>,
182 pub attached_to: Option<String>,
183 pub attached_to_player_text: Option<String>,
184 pub attach_after_text: Option<String>,
185 pub destination_alternative: Option<String>,
186 pub select_prompt: Option<String>,
187 pub sub_ability_name: Option<String>,
188 pub name_text: Option<String>,
189 pub names_text: Option<String>,
190 pub choose_from_list_text: Option<String>,
191 pub choose_from_defined_cards: bool,
192 pub stack_id: Option<u32>,
193 pub token_script: Option<String>,
194 pub token_owner: Option<String>,
195 pub token_name_text: Option<String>,
196 pub token_power: Option<i32>,
197 pub token_toughness: Option<i32>,
198 pub token_types_text: Option<String>,
199 pub token_colors_text: Option<String>,
200 pub token_keywords_text: Option<String>,
201 pub token_until_text: Option<String>,
202 pub keyword_text: Option<String>,
203 pub with_counters_type_text: Option<String>,
204 pub with_counters_type: Option<CounterType>,
205 pub with_counters_amount_text: Option<String>,
206 pub change_num: usize,
207 pub hidden: bool,
208 pub skip_reorder: bool,
209 pub secretly: bool,
210 pub random_chosen: bool,
211 pub forget_other_remembered: bool,
212 pub remember_changed: bool,
213 pub imprint: bool,
214 pub face_down: bool,
215 pub exile_face_down: bool,
216 pub transformed: bool,
217 pub at_random_text: Option<String>,
218 pub at_random: bool,
219 pub reveal: bool,
220 pub reveal_true: bool,
221 pub reveal_text: Option<String>,
222 pub remember_drawn: bool,
223 pub remember_removed_cards: bool,
224 pub remember_destroyed: bool,
225 pub remember_abandoned: bool,
226 pub may_shuffle: bool,
227 pub remember_altered: bool,
228 pub alter_attribute_activate: bool,
229 pub alter_attribute_attributes: Vec<String>,
230 pub remember_amass: bool,
231 pub remember_put: bool,
232 pub remember_amount: bool,
233 pub max_from_effect: Option<i32>,
234 pub remember_flag: bool,
235 pub remember_chosen: bool,
236 pub imprint_chosen: bool,
237 pub remember_clasher: bool,
238 pub remember_cloaked: bool,
239 pub remember_discovered: bool,
240 pub remember_drafted: bool,
241 pub remember_exchanged: bool,
242 pub remember_investigating_players: bool,
243 pub remember_made: bool,
244 pub always_remember: bool,
245 pub remember_milled: bool,
246 pub change_zone_table: bool,
247 pub at_eot: Option<String>,
248 pub execute: Option<String>,
249 pub replace_with_text: Option<String>,
250 pub damage_amount_text: Option<String>,
251 pub counter_type_text: Option<String>,
252 pub counter_type: Option<CounterType>,
253 pub simple_counter_type_choice_path: bool,
254 pub triggered_counter_map: bool,
255 pub counter_map_values: Option<i32>,
256 pub modular: bool,
257 pub adapt: bool,
258 pub monstrosity: bool,
259 pub bloodthirst: bool,
260 pub renown: bool,
261 pub abilities: Option<String>,
262 pub replacement_effects: Option<String>,
263 pub static_abilities: Option<String>,
264 pub triggers: Option<String>,
265 pub imprint_cards: Option<String>,
266 pub remember_spell: Option<String>,
267 pub remember_lki: Option<String>,
268 pub remember_targets: bool,
269 pub forget_other_targets: bool,
270 pub remember_cost_mana: bool,
271 pub forget_on_moved_text: Option<String>,
272 pub forget_on_moved_zone: Option<ZoneType>,
273 pub replace_dying_defined: Option<DefinedExpr>,
274 pub replace_dying_defined_text: Option<String>,
275 pub replace_dying_valid: Option<String>,
276 pub replace_dying_zone_text: Option<String>,
277 pub replace_dying_zone: Option<ZoneType>,
278 pub remember_objects: Option<String>,
279 pub remember_objects_remembered: bool,
283 pub remember_objects_remembered_controller: bool,
286 pub remember_objects_remembered_lki: bool,
288 pub remember_objects_triggered_attacker_lki_copy: bool,
290 pub remember_number: bool,
291 pub remember_svar_amount: Option<String>,
292 pub remember_exiled: bool,
293 pub delayed_trigger_defined_player: Option<DefinedExpr>,
294 pub delayed_trigger_defined_player_text: Option<String>,
295 pub this_turn: bool,
296 pub condition_hellbent: bool,
297 pub condition_threshold: bool,
298 pub condition_metalcraft: bool,
299 pub condition_delirium: bool,
300 pub condition_revolt: bool,
301 pub condition_desert: bool,
302 pub condition_blessing: bool,
303 pub condition_kicked: bool,
304 pub condition_optional_paid: bool,
305 pub condition_optional_not_paid: bool,
306 pub condition_opponent_turn: bool,
307 pub condition_player_turn: Option<String>,
308 pub condition_cards_in_hand: Option<String>,
309 pub condition_phases: Option<String>,
310 pub condition_life_compare: Option<String>,
311 pub condition: Option<String>,
312 pub condition_check_svar: Option<String>,
313 pub condition_svar_compare: Option<String>,
314 pub condition_present: Option<String>,
315 pub condition_defined: Option<DefinedExpr>,
316 pub condition_defined_text: Option<String>,
317 pub condition_compare: Option<String>,
318 pub condition_zone_text: Option<String>,
319 pub condition_zone: Option<ZoneType>,
320 pub optional_present: bool,
321 pub remove_from_combat: bool,
322 pub num_att: Option<String>,
323 pub num_def: Option<String>,
324 pub types_text: Option<String>,
325 pub add_types: Option<String>,
326 pub set_color: Option<String>,
327 pub set_power: Option<String>,
328 pub set_toughness: Option<String>,
329 pub set_mana_cost: Option<String>,
330 pub clone_target: Option<String>,
331 pub kw: Option<String>,
332 pub pump_keywords: Option<String>,
333 pub add_keywords: Option<String>,
334 pub kw_choice: Option<String>,
335 pub up_to: bool,
336 pub upto: bool,
339 pub optional_decider: Option<String>,
342 pub replace_graveyard: Option<String>,
346 pub two_colors: bool,
347 pub or_colors: bool,
348 pub can_block_any: bool,
349 pub can_block_amount: Option<String>,
350 pub option_question: Option<String>,
351 pub perpetual_duration: bool,
352 pub pump_zone: Option<String>,
353 pub repeat_sub_ability: Option<String>,
354 pub repeat_check_svar: bool,
355 pub repeat_svar_compare: Option<String>,
356 pub repeat_defined: Option<String>,
357 pub repeat_present: Option<String>,
358 pub repeat_compare: Option<String>,
359 pub repeat_optional: bool,
360 pub repeat_optional_decider: Option<String>,
361 pub repeat_cards_text: Option<String>,
362 pub repeat_cards_selector: Option<CompiledSelector>,
363 pub repeat_players: Option<String>,
364 pub change_num_text: Option<String>,
365 pub change_valid: Option<String>,
366 pub zone: Option<ZoneType>,
367 pub valid_zone: Option<ZoneType>,
368 pub source_zone: Option<ZoneType>,
369 pub restrict_from_zone: Option<ZoneType>,
370 pub choice_zone: Option<ZoneType>,
371 pub with_total_cmc: Option<i32>,
372 pub with_total_power: Option<i32>,
373 pub mana_ability: bool,
374 pub type_filter: Option<String>,
375 pub valid_types_text: Option<String>,
376 pub remember_countered: bool,
377 pub remember_countered_cmc: bool,
378 pub remember_for_counter: bool,
379 pub lose_control: Option<crate::card::LoseControlCondition>,
380 pub untap_on_resolve: bool,
381 pub add_kws: Option<String>,
382 pub fallback_ability: Option<String>,
383 pub cumulative_upkeep: Option<String>,
384 pub echo: Option<String>,
385 pub exploit: bool,
386 pub strict_amount: bool,
387 pub base_power: bool,
388 pub remember_sacrificed: bool,
389 pub unimprint: bool,
390 pub drain_mana: bool,
391 pub remember_drained_mana: bool,
392 pub clear_remembered: bool,
393 pub etb: bool,
394 pub remember_tapped: bool,
395 pub untap_type: Option<String>,
396 pub untap_up_to: bool,
397 pub track_discarded: bool,
398 pub no_peek: bool,
399 pub no_reveal: bool,
400 pub remember_peeked: bool,
401 pub reveal_optional: bool,
402 pub remember_revealed: bool,
403 pub imprint_revealed: bool,
404 pub remember_found: bool,
405 pub imprint_found: bool,
406 pub peek_amount_text: Option<String>,
407 pub champion: bool,
408 pub with_noted_counters: bool,
409 pub exiled_with_effect_source: bool,
410 pub token_tapped: bool,
411 pub token_blocking_text: Option<String>,
412 pub remember_tokens: bool,
413 pub remember_original_tokens: bool,
414 pub imprint_tokens: bool,
415 pub remember_source: bool,
416 pub token_remembered: Option<String>,
417 pub cleanup_for_each: bool,
418 pub add_triggers_from_text: Option<String>,
419 pub at_eot_trig_text: Option<String>,
420 pub pump_duration_text: Option<String>,
421 pub searched: bool,
422 pub reorder: bool,
423 pub exactly: bool,
424 pub no_looking: bool,
425 pub remember_lki_flag: bool,
426 pub remember_searched: bool,
427 pub ninjutsu: bool,
428 pub unearth: bool,
429 pub attacking: bool,
430 pub cant_fizzle: bool,
431 pub cant_be_countered: bool,
432 pub unpayable: bool,
433 pub pw_ability: bool,
434 pub flash: bool,
435 pub split_second: bool,
436 pub random: bool,
437 pub random_target: bool,
438 pub rest_random_order: bool,
439 pub prompt_to_skip_optional_ability: bool,
440 pub prompt_if_only_possible_ability: bool,
441 pub skip_cancel_prompt: bool,
442 pub temp_remember: bool,
443 pub num_random_choices: Option<String>,
444 pub num_dmg_text: Option<String>,
445 pub num_cards_text: Option<String>,
446 pub sides: Option<i32>,
447 pub result_sub_abilities_text: Option<String>,
448 pub result_svar_text: Option<String>,
449 pub chosen_svar_text: Option<String>,
450 pub other_svar_text: Option<String>,
451 pub spellbook_text: Option<String>,
452 pub dungeon_text: Option<String>,
453 pub var_name_text: Option<String>,
454 pub animate_power: Option<i32>,
455 pub animate_toughness: Option<i32>,
456 pub animate_types_text: Option<String>,
457 pub animate_keywords_text: Option<String>,
458 pub animate_remove_keywords_text: Option<String>,
459 pub animate_triggers_text: Option<String>,
460 pub animate_colors_text: Option<String>,
461 pub overwrite_colors: bool,
462 pub animate_overwrite_types: bool,
463 pub animate_remove_creature_types: bool,
464 pub animate_remove_all_abilities: bool,
465 pub animate_incorporate_text: Option<String>,
466 pub animate_mana_cost_override_text: Option<String>,
467 pub sneak: bool,
468 pub attacking_text: Option<String>,
469 pub ninjutsu_text: Option<String>,
470 pub token_attacking_text: Option<String>,
471 pub without_mana_cost: bool,
472 pub cast_from_play_effect: bool,
473 pub store_vote_num: bool,
474 pub remember_voted_objects: bool,
475 pub remember_played: bool,
476 pub discard_valid_text: Option<String>,
477 pub discard_valid_selector: Option<CompiledSelector>,
478 pub reveal_valid_text: Option<String>,
479 pub reveal_valid_selector: Option<CompiledSelector>,
480 pub any_number: bool,
481 pub remember_discarded: bool,
482 pub allows_paying_with_shard: bool,
483 pub cant_be_copied_ability: bool,
484 pub mana_replacement: Option<String>,
485 pub remember_manifested: bool,
486 pub unless_payer_text: Option<String>,
487 pub unless_resolve_subs: Option<String>,
488 pub unless_switched: bool,
489 pub vote_message_text: Option<String>,
490 pub optional_decider_text: Option<String>,
491 pub stack_description_text: Option<String>,
492 pub sp_desc_text: Option<String>,
493 pub spell_description_text: Option<String>,
494 pub condition_description_text: Option<String>,
495 pub after_description_text: Option<String>,
496 pub announce_text: Option<String>,
497 pub x_max_limit_text: Option<String>,
498 pub announce_max_text: Option<String>,
499 pub optional_ability_prompt: Option<String>,
500 pub svar_name_text: Option<String>,
501 pub svar_type_text: Option<String>,
502 pub svar_expression_text: Option<String>,
503 pub change_color_word_text: Option<String>,
504 pub change_type_word_text: Option<String>,
505 pub forbidden_new_types_text: Option<String>,
506 pub cost_has_x: bool,
507 pub targeting_player: bool,
508 pub targeting_player_text: Option<String>,
509 pub effect_source: bool,
510 pub defined_magnet_text: Option<String>,
511 pub targets_with_defined_controller_text: Option<String>,
512 pub replace_dying_exiled_with: bool,
513 pub mode: Option<SpellAbilityMode>,
514 pub duration: Option<AbilityDuration>,
515 pub replace_dying_condition: Option<ReplaceDyingCondition>,
516 pub day_time_value: Option<DayTimeValue>,
517 pub choice_restriction_text: Option<String>,
518 pub phase_text: Option<String>,
519 pub step_text: Option<String>,
520 pub phase_in_or_out_text: Option<String>,
521 pub found_destination_zone: Option<ZoneType>,
522 pub revealed_destination_zone: Option<ZoneType>,
523 pub extra_phase_text: Option<String>,
524 pub card_state_name: Option<String>,
525 pub track_mana_spent: bool,
526 pub become_starting_player: bool,
527 pub tap_creatures_for_mana: bool,
528 pub remember_players_text: Option<String>,
529 pub no_call: bool,
530 pub flip_until_you_lose: bool,
531 pub different_names: bool,
532 pub different_cmc: bool,
533 pub different_power: bool,
534 pub share_land_type: bool,
535 pub imprint_last: bool,
536 pub foretold: bool,
537 pub foretold_cost: bool,
538 pub random_order: bool,
539 pub shuffle_changed_pile: bool,
540 pub warp: bool,
541 pub morph: bool,
542 pub morph_up: bool,
543 pub megamorph: bool,
544 pub mega: bool,
545 pub imprint_made: bool,
546 pub to_visit_your_attractions: bool,
547 pub remember_highest_player: bool,
548 pub use_highest_roll: bool,
549 pub use_difference_between_rolls: bool,
550 pub store_results: bool,
551 pub even_odd_results: bool,
552 pub different_results: bool,
553 pub max_rolls_results: bool,
554 pub note_doubles: bool,
555 pub subs_for_each: bool,
556 pub reroll_results: bool,
557 pub semantic_numeric_params: BTreeMap<String, NumericParamIr>,
558}
559
560impl SpellAbilityIr {
561 pub fn from_parsed(
562 api: Option<crate::ability::api_type::ApiType>,
563 params: &ParsedParams<'_>,
564 ) -> Self {
565 Self {
566 card_trait_requirements: CardTraitRequirementsIr::from_parsed(params),
567 effect: lower_effect_ir(api, params),
568 mode_text: params.get(keys::MODE).map(str::to_string),
569 discard_mode: params
570 .get(keys::MODE)
571 .and_then(crate::ability::effects::discard_effect::DiscardMode::parse),
572 produced_ir: params
573 .semantic_get(keys::PRODUCED)
574 .and_then(|param| match param.value {
575 SemanticParamValue::ProducedMana(produced) => {
576 Some(ProducedMana::from_semantic(&produced))
577 }
578 _ => None,
579 }),
580 reflect_property: params.get(keys::REFLECT_PROPERTY).map(str::to_string),
581 color_or_type: params.get(keys::COLOR_OR_TYPE).map(str::to_string),
582 restrict_valid: params.get(keys::RESTRICT_VALID).map(str::to_string),
583 valid_filter_text: params.get(keys::VALID).map(str::to_string),
584 valid_filter_selector: params.get(keys::VALID).map(CompiledSelector::parse),
585 adds_keywords: params.get(keys::ADDS_KEYWORDS).map(str::to_string),
586 adds_keywords_valid: params.get(keys::ADDS_KEYWORDS_VALID).map(str::to_string),
587 adds_counters: params.get(keys::ADDS_COUNTERS).map(str::to_string),
588 adds_counters_valid: params.get(keys::ADDS_COUNTERS_VALID).map(str::to_string),
589 triggers_when_spent: params.get(keys::TRIGGERS_WHEN_SPENT).map(str::to_string),
590 valid_cards_text: params.get(keys::VALID_CARDS).map(str::to_string),
591 valid_cards_selector: params.get(keys::VALID_CARDS).map(CompiledSelector::parse),
592 valid_card_text: params.get(keys::VALID_CARD).map(str::to_string),
593 valid_card_selector: params.get(keys::VALID_CARD).map(CompiledSelector::parse),
594 valid_players_text: params.get(keys::VALID_PLAYERS).map(str::to_string),
595 valid_players_selector: params.get(keys::VALID_PLAYERS).map(CompiledSelector::parse),
596 valid_player_text: params.get(keys::VALID_PLAYER).map(str::to_string),
597 valid_player_selector: params.get(keys::VALID_PLAYER).map(CompiledSelector::parse),
598 valid_tgts_text: params.get(keys::VALID_TGTS).map(str::to_string),
599 valid_tgts_selector: params.get(keys::VALID_TGTS).map(CompiledSelector::parse),
600 valid_target_text: params.get(keys::VALID_TARGET).map(str::to_string),
601 valid_target_selector: params.get(keys::VALID_TARGET).map(CompiledSelector::parse),
602 all_valid_text: params.get("AllValid").map(str::to_string),
603 all_valid_selector: params.get("AllValid").map(CompiledSelector::parse),
604 defined_name_text: params.get(keys::DEFINED_NAME).map(str::to_string),
605 set_chosen_number: params.get("SetChosenNumber").map(str::to_string),
606 starting_with: params.get("StartingWith").map(str::to_string),
607 repeat: params.get(keys::REPEAT).map(str::to_string),
608 branch_condition_svar: params.get(keys::BRANCH_CONDITION_SVAR).map(str::to_string),
609 branch_condition_svar_compare: params
610 .get(keys::BRANCH_CONDITION_SVAR_COMPARE)
611 .map(str::to_string),
612 true_sub_ability: params.get(keys::TRUE_SUB_ABILITY).map(str::to_string),
613 false_sub_ability: params.get(keys::FALSE_SUB_ABILITY).map(str::to_string),
614 play_cost_text: params.get(keys::PLAY_COST).map(str::to_string),
615 unless_cost: params.get(keys::UNLESS_COST).map(str::to_string),
616 amount: params.get(keys::AMOUNT).map(str::to_string),
617 counter_num_text: params.get("CounterNum").map(str::to_string),
618 max_repeat: params.get("MaxRepeat").map(str::to_string),
619 precost_desc: params.get(keys::PRECOST_DESC).map(str::to_string),
620 token_amount: params.get("TokenAmount").map(str::to_string),
621 sac_valid: params.get(keys::SAC_VALID).map(str::to_string),
622 defined: params.get(keys::DEFINED).map(DefinedExpr::parse),
623 defined_player: params.get(keys::DEFINED_PLAYER).map(DefinedExpr::parse),
624 defined_text: params.get(keys::DEFINED).map(str::to_string),
625 defined_player_text: params.get(keys::DEFINED_PLAYER).map(str::to_string),
626 controller_text: params.get(keys::CONTROLLER).map(str::to_string),
627 placer_text: params.get("Placer").map(str::to_string),
628 effect_owner: params.get(keys::EFFECT_OWNER).map(DefinedExpr::parse),
629 effect_owner_text: params.get(keys::EFFECT_OWNER).map(str::to_string),
630 origin_text: params.get(keys::ORIGIN).map(str::to_string),
631 origin_zone: parsed_zone_type(params.get(keys::ORIGIN)),
632 origin_zones: parsed_zone_types(params.get(keys::ORIGIN)),
633 destination_text: params
634 .get(keys::DESTINATION_ZONE)
635 .or_else(|| params.get(keys::DESTINATION))
636 .map(str::to_string),
637 destination_zone: parsed_zone_type(
638 params
639 .get(keys::DESTINATION_ZONE)
640 .or_else(|| params.get(keys::DESTINATION)),
641 ),
642 destination_zone_2: parsed_zone_type(params.get(keys::DESTINATION_ZONE_2)),
643 zone1: parsed_zone_type(params.get(keys::ZONE1)),
644 zone2: parsed_zone_type(params.get(keys::ZONE2)),
645 change_type: params.get(keys::CHANGE_TYPE).map(str::to_string),
646 change_type_selector: params.get(keys::CHANGE_TYPE).map(CompiledSelector::parse),
647 primary_text: params.get(keys::PRIMARY).map(str::to_string),
648 secondary_text: params.get(keys::SECONDARY).map(str::to_string),
649 secondary_type_text: params.get(keys::SECONDARY_TYPE).map(str::to_string),
650 library_position: params.get(keys::LIBRARY_POSITION).map(str::to_string),
651 library_position_2: params.get(keys::LIBRARY_POSITION_2).map(str::to_string),
652 library_position_alternative: params
653 .get(keys::LIBRARY_POSITION_ALTERNATIVE)
654 .map(str::to_string),
655 shuffle_raw: params.get(keys::SHUFFLE).map(str::to_string),
656 no_shuffle: parsed_true(params.get(keys::NO_SHUFFLE)),
657 mandatory: parsed_true(params.get(keys::MANDATORY)),
658 toggle: parsed_true(params.get(keys::TOGGLE)),
659 can_repeat_modes: params.has(keys::CAN_REPEAT_MODES),
660 entwine: params.has(keys::ENTWINE),
661 skip_untap: params.has(keys::SKIP_UNTAP),
662 tapped: parsed_true(params.get(keys::TAPPED)),
663 shuffle: parsed_true(params.get(keys::SHUFFLE)),
664 optional: params.has(keys::OPTIONAL),
665 gain_control: params.has(keys::GAIN_CONTROL),
666 gain_control_text: params.get(keys::GAIN_CONTROL).map(str::to_string),
667 forget_changed: parsed_true(params.get(keys::FORGET_CHANGED)),
668 adds_no_counter: parsed_true(params.get(keys::ADDS_NO_COUNTER)),
669 no_regen: params.has("NoRegen"),
670 damage_map: params.has(keys::DAMAGE_MAP),
671 remember_damaged_creature: parsed_true(params.get(keys::REMEMBER_DAMAGED_CREATURE)),
672 num_dmg_present: params.has(keys::NUM_DMG),
673 boon: params.has("Boon"),
674 unique: params.has("Unique"),
675 exile_on_moved: params.has("ExileOnMoved"),
676 forget_on_phased_in: params.has("ForgetOnPhasedIn"),
677 forget_counter: params.has("ForgetCounter"),
678 radiance: params.has("Radiance"),
679 chooser: params.get(keys::CHOOSER).map(str::to_string),
680 gains: params.get(keys::GAINS).map(str::to_string),
681 choices: params.get(keys::CHOICES).map(str::to_string),
682 choices_selector: params.get(keys::CHOICES).map(CompiledSelector::parse),
683 debuff: DebuffIr::from_parsed(params),
684 for_each_text: params.get(keys::FOR_EACH).map(str::to_string),
685 exclude: params.get("Exclude").map(str::to_string),
686 object_text: params.get(keys::OBJECT).map(str::to_string),
687 source_text: params.get(keys::SOURCE).map(str::to_string),
688 attached_to: params.get(keys::ATTACHED_TO).map(str::to_string),
689 attached_to_player_text: params.get(keys::ATTACHED_TO_PLAYER).map(str::to_string),
690 attach_after_text: params.get(keys::ATTACH_AFTER).map(str::to_string),
691 destination_alternative: params
692 .get(keys::DESTINATION_ALTERNATIVE)
693 .map(str::to_string),
694 select_prompt: params.get(keys::SELECT_PROMPT).map(str::to_string),
695 sub_ability_name: params.get(keys::SUB_ABILITY).map(str::to_string),
696 name_text: params.get(keys::NAME).map(str::to_string),
697 names_text: params.get(keys::NAMES).map(str::to_string),
698 choose_from_list_text: params.get(keys::CHOOSE_FROM_LIST).map(str::to_string),
699 choose_from_defined_cards: params.has(keys::CHOOSE_FROM_DEFINED_CARDS),
700 stack_id: params
701 .get(keys::STACK_ID)
702 .and_then(|value| value.parse().ok()),
703 token_script: params.get(keys::TOKEN_SCRIPT).map(str::to_string),
704 token_owner: params.get(keys::TOKEN_OWNER).map(str::to_string),
705 token_name_text: params.get(keys::TOKEN_NAME).map(str::to_string),
706 token_power: parsed_i32(params.get(keys::TOKEN_POWER)),
707 token_toughness: parsed_i32(params.get(keys::TOKEN_TOUGHNESS)),
708 token_types_text: params.get(keys::TOKEN_TYPES).map(str::to_string),
709 token_colors_text: params.get(keys::TOKEN_COLORS).map(str::to_string),
710 token_keywords_text: params.get(keys::TOKEN_KEYWORDS).map(str::to_string),
711 token_until_text: params.get("TokenUntil").map(str::to_string),
712 keyword_text: params.get("Keyword").map(str::to_string),
713 with_counters_type_text: params.get(keys::WITH_COUNTERS_TYPE).map(str::to_string),
714 with_counters_type: params.get(keys::WITH_COUNTERS_TYPE).map(parse_counter_type),
715 with_counters_amount_text: params.get(keys::WITH_COUNTERS_AMOUNT).map(str::to_string),
716 change_num: parsed_usize(params.get(keys::CHANGE_NUM)).unwrap_or(1),
717 hidden: parsed_true(params.get(keys::HIDDEN)),
718 skip_reorder: parsed_true(params.get("SkipReorder")),
719 secretly: parsed_true(params.get(keys::SECRETLY)),
720 random_chosen: parsed_true(params.get(keys::RANDOM_CHOSEN)),
721 forget_other_remembered: parsed_true(params.get(keys::FORGET_OTHER_REMEMBERED)),
722 remember_changed: params.has(keys::REMEMBER_CHANGED),
723 imprint: parsed_true(params.get(keys::IMPRINT)),
724 face_down: params.has(keys::FACE_DOWN),
725 exile_face_down: parsed_true(params.get(keys::EXILE_FACE_DOWN)),
726 transformed: parsed_true(params.get(keys::TRANSFORMED)),
727 at_random_text: params.get(keys::AT_RANDOM).map(str::to_string),
728 at_random: params.has(keys::AT_RANDOM),
729 reveal: !parsed_true(params.get(keys::NO_REVEAL)),
730 reveal_true: parsed_true(params.get(keys::REVEAL)),
731 reveal_text: params.get(keys::REVEAL).map(str::to_string),
732 remember_drawn: params.has("RememberDrawn"),
733 remember_removed_cards: parsed_true(params.get(keys::REMEMBER_REMOVED_CARDS)),
734 remember_destroyed: parsed_true(params.get("RememberDestroyed")),
735 remember_abandoned: params.get("RememberAbandoned").is_some(),
736 may_shuffle: params.has(keys::MAY_SHUFFLE),
737 remember_altered: parsed_true(params.get(keys::REMEMBER_ALTERED)),
738 alter_attribute_activate: parsed_bool_default(params.get(keys::ACTIVATE), true),
739 alter_attribute_attributes: split_param_list_value(params.get(keys::ATTRIBUTES), ","),
740 remember_amass: parsed_true(params.get(keys::REMEMBER_AMASS)),
741 remember_put: params.has("RememberPut"),
742 remember_amount: params.has("RememberAmount"),
743 max_from_effect: params
744 .get("MaxFromEffect")
745 .and_then(|value| value.parse().ok()),
746 remember_flag: params.has(keys::REMEMBER),
747 remember_chosen: parsed_true(params.get(keys::REMEMBER_CHOSEN)),
748 imprint_chosen: parsed_true(params.get(keys::IMPRINT_CHOSEN)),
749 remember_clasher: parsed_true(params.get(keys::REMEMBER_CLASHER)),
750 remember_cloaked: parsed_true(params.get(keys::REMEMBER_CLOAKED)),
751 remember_discovered: parsed_true(params.get(keys::REMEMBER_DISCOVERED)),
752 remember_drafted: parsed_true(params.get(keys::REMEMBER_DRAFTED)),
753 remember_exchanged: parsed_true(params.get(keys::REMEMBER_EXCHANGED)),
754 remember_investigating_players: parsed_true(
755 params.get(keys::REMEMBER_INVESTIGATING_PLAYERS),
756 ),
757 remember_made: parsed_true(params.get(keys::REMEMBER_MADE)),
758 always_remember: parsed_true(params.get("AlwaysRemember")),
759 remember_milled: params.has("RememberMilled"),
760 change_zone_table: params.has("ChangeZoneTable"),
761 at_eot: params.get(keys::AT_EOT).map(str::to_string),
762 execute: params.get(keys::EXECUTE).map(str::to_string),
763 replace_with_text: params.get(keys::REPLACE_WITH).map(str::to_string),
764 damage_amount_text: params.get(keys::DAMAGE_AMOUNT).map(str::to_string),
765 counter_type_text: params.get(keys::COUNTER_TYPE).map(str::to_string),
766 counter_type: params.get(keys::COUNTER_TYPE).map(parse_counter_type),
767 simple_counter_type_choice_path: params.get(keys::COUNTER_TYPE).is_some()
768 && ![
769 "EachExistingCounter",
770 "EachFromSource",
771 "UniqueType",
772 "CounterTypePerDefined",
773 "CounterTypes",
774 "ChooseDifferent",
775 "PutOnEachOther",
776 "PutOnDefined",
777 "TriggeredCounterMap",
778 "SharedKeywords",
779 ]
780 .iter()
781 .any(|key| params.has(key)),
782 triggered_counter_map: params.has("TriggeredCounterMap"),
783 counter_map_values: params
784 .get("CounterMapValues")
785 .and_then(|value| value.parse().ok()),
786 modular: parsed_true(params.get(keys::MODULAR)),
787 adapt: parsed_true(params.get(keys::ADAPT)),
788 monstrosity: parsed_true(params.get(keys::MONSTROSITY)),
789 bloodthirst: parsed_true(params.get("Bloodthirst")),
790 renown: parsed_true(params.get(keys::RENOWN)),
791 abilities: params.get("Abilities").map(str::to_string),
792 replacement_effects: params.get("ReplacementEffects").map(str::to_string),
793 static_abilities: params.get(keys::STATIC_ABILITIES).map(str::to_string),
794 triggers: params.get(keys::TRIGGERS).map(str::to_string),
795 imprint_cards: params.get("ImprintCards").map(str::to_string),
796 remember_spell: params.get("RememberSpell").map(str::to_string),
797 remember_lki: params.get("RememberLKI").map(str::to_string),
798 remember_targets: params.has("RememberTargets"),
799 forget_other_targets: params.has("ForgetOtherTargets"),
800 remember_cost_mana: params.has("RememberCostMana"),
801 forget_on_moved_text: params.get(keys::FORGET_ON_MOVED).map(str::to_string),
802 forget_on_moved_zone: parsed_zone_type(params.get(keys::FORGET_ON_MOVED)),
803 replace_dying_defined: params.get("ReplaceDyingDefined").map(DefinedExpr::parse),
804 replace_dying_defined_text: params.get("ReplaceDyingDefined").map(str::to_string),
805 replace_dying_valid: params.get("ReplaceDyingValid").map(str::to_string),
806 replace_dying_zone_text: params.get("ReplaceDyingZone").map(str::to_string),
807 replace_dying_zone: parsed_zone_type(params.get("ReplaceDyingZone")),
808 remember_objects: params.get(keys::REMEMBER_OBJECTS).map(str::to_string),
809 remember_objects_remembered: has_remember_objects_token(params, "Remembered"),
810 remember_objects_remembered_controller: has_remember_objects_token(
811 params,
812 "RememberedController",
813 ),
814 remember_objects_remembered_lki: has_remember_objects_token(params, "RememberedLKI"),
815 remember_objects_triggered_attacker_lki_copy: has_remember_objects_token(
816 params,
817 "TriggeredAttackerLKICopy",
818 ),
819 remember_number: params.has(keys::REMEMBER_NUMBER),
820 remember_svar_amount: params.get(keys::REMEMBER_SVAR_AMOUNT).map(str::to_string),
821 remember_exiled: params.has("RememberExiled"),
822 delayed_trigger_defined_player: params
823 .get(keys::DELAYED_TRIGGER_DEFINED_PLAYER)
824 .map(DefinedExpr::parse),
825 delayed_trigger_defined_player_text: params
826 .get(keys::DELAYED_TRIGGER_DEFINED_PLAYER)
827 .map(str::to_string),
828 this_turn: params.has("ThisTurn"),
829 condition_hellbent: parsed_true(params.get("ConditionHellbent")),
830 condition_threshold: parsed_true(params.get("ConditionThreshold")),
831 condition_metalcraft: parsed_true(params.get("ConditionMetalcraft")),
832 condition_delirium: parsed_true(params.get("ConditionDelirium")),
833 condition_revolt: parsed_true(params.get("ConditionRevolt")),
834 condition_desert: parsed_true(params.get("ConditionDesert")),
835 condition_blessing: parsed_true(params.get("ConditionBlessing")),
836 condition_kicked: parsed_true(params.get("ConditionKicked")),
837 condition_optional_paid: parsed_true(params.get("ConditionOptionalPaid")),
838 condition_optional_not_paid: parsed_true(params.get("ConditionOptionalNotPaid")),
839 condition_opponent_turn: parsed_true(params.get("ConditionOpponentTurn")),
840 condition_player_turn: params.get("ConditionPlayerTurn").map(str::to_string),
841 condition_cards_in_hand: params.get("ConditionCardsInHand").map(str::to_string),
842 condition_phases: params.get("ConditionPhases").map(str::to_string),
843 condition_life_compare: params.get("ConditionLifeCompare").map(str::to_string),
844 condition: params.get(keys::CONDITION).map(str::to_string),
845 condition_check_svar: params.get(keys::CONDITION_CHECK_SVAR).map(str::to_string),
846 condition_svar_compare: params.get("ConditionSVarCompare").map(str::to_string),
847 condition_present: params.get(keys::CONDITION_PRESENT).map(str::to_string),
848 condition_defined: params.get(keys::CONDITION_DEFINED).map(DefinedExpr::parse),
849 condition_defined_text: params.get(keys::CONDITION_DEFINED).map(str::to_string),
850 condition_compare: params.get(keys::CONDITION_COMPARE).map(str::to_string),
851 condition_zone_text: params.get(keys::CONDITION_ZONE).map(str::to_string),
852 condition_zone: parsed_zone_type(params.get(keys::CONDITION_ZONE)),
853 optional_present: params.has(keys::OPTIONAL),
854 remove_from_combat: parsed_true(params.get(keys::REMOVE_FROM_COMBAT)),
855 num_att: params.get(keys::NUM_ATT).map(str::to_string),
856 num_def: params.get(keys::NUM_DEF).map(str::to_string),
857 types_text: params
858 .get(keys::TYPES)
859 .or_else(|| params.get(keys::TYPE))
860 .map(str::to_string),
861 add_types: params.get(keys::ADD_TYPES).map(str::to_string),
862 set_color: params.get(keys::SET_COLOR).map(str::to_string),
863 set_power: params.get(keys::SET_POWER).map(str::to_string),
864 set_toughness: params.get(keys::SET_TOUGHNESS).map(str::to_string),
865 set_mana_cost: params.get(keys::SET_MANA_COST).map(str::to_string),
866 clone_target: params.get(keys::CLONE_TARGET).map(str::to_string),
867 kw: params.get(keys::KW).map(str::to_string),
868 pump_keywords: params.get(keys::PUMP_KEYWORDS).map(str::to_string),
869 add_keywords: params.get(keys::ADD_KEYWORDS).map(str::to_string),
870 kw_choice: params.get("KWChoice").map(str::to_string),
871 up_to: params.has("UpTo"),
872 upto: params.has(keys::UPTO),
873 optional_decider: params.get(keys::OPTIONAL_DECIDER).map(str::to_string),
874 replace_graveyard: params.get(keys::REPLACE_GRAVEYARD).map(str::to_string),
875 two_colors: params.has("TwoColors"),
876 or_colors: params.has("OrColors"),
877 can_block_any: parsed_true(params.get("CanBlockAny")),
878 can_block_amount: params.get("CanBlockAmount").map(str::to_string),
879 option_question: params.get("OptionQuestion").map(str::to_string),
880 perpetual_duration: matches!(
881 params.get(keys::DURATION).map(AbilityDuration::parse),
882 Some(AbilityDuration::Perpetual)
883 ),
884 pump_zone: params.get("PumpZone").map(str::to_string),
885 repeat_sub_ability: params.get(keys::REPEAT_SUB_ABILITY).map(str::to_string),
886 repeat_check_svar: params.has("RepeatCheckSVar"),
887 repeat_svar_compare: params.get("RepeatSVarCompare").map(str::to_string),
888 repeat_defined: params.get("RepeatDefined").map(str::to_string),
889 repeat_present: params.get("RepeatPresent").map(str::to_string),
890 repeat_compare: params.get("RepeatCompare").map(str::to_string),
891 repeat_optional: params.has("RepeatOptional"),
892 repeat_optional_decider: params.get("RepeatOptionalDecider").map(str::to_string),
893 repeat_cards_text: params.get(keys::REPEAT_CARDS).map(str::to_string),
894 repeat_cards_selector: params.get(keys::REPEAT_CARDS).map(CompiledSelector::parse),
895 repeat_players: params.get(keys::REPEAT_PLAYERS).map(str::to_string),
896 change_num_text: params.get(keys::CHANGE_NUM).map(str::to_string),
897 change_valid: params.get(keys::CHANGE_VALID).map(str::to_string),
898 zone: parsed_zone_type(params.get(keys::ZONE)),
899 valid_zone: parsed_zone_type(params.get(keys::VALID_ZONE)),
900 source_zone: parsed_zone_type(params.get("SourceZone")),
901 restrict_from_zone: parsed_zone_type(params.get(keys::RESTRICT_FROM_ZONE)),
902 choice_zone: parsed_zone_type(params.get(keys::CHOICE_ZONE)),
903 with_total_cmc: parsed_i32(params.get(keys::WITH_TOTAL_CMC)),
904 with_total_power: parsed_i32(params.get(keys::WITH_TOTAL_POWER)),
905 mana_ability: parsed_true(params.get(keys::MANA_ABILITY)),
906 type_filter: params.get(keys::TYPE).map(str::to_string),
907 valid_types_text: params.get(keys::VALID_TYPES).map(str::to_string),
908 remember_countered: params.has(keys::REMEMBER_COUNTERED),
909 remember_countered_cmc: params.has(keys::REMEMBER_COUNTERED_CMC),
910 remember_for_counter: params.has(keys::REMEMBER_FOR_COUNTER),
911 lose_control: params
912 .get(keys::LOSE_CONTROL)
913 .and_then(|raw| raw.parse::<crate::card::LoseControlCondition>().ok()),
914 untap_on_resolve: params.has(keys::UNTAP),
915 add_kws: params.get(keys::ADD_KWS).map(str::to_string),
916 fallback_ability: params.get("FallbackAbility").map(str::to_string),
917 cumulative_upkeep: params.get(keys::CUMULATIVE_UPKEEP).map(str::to_string),
918 echo: params.get("Echo").map(str::to_string),
919 exploit: parsed_true(params.get(keys::EXPLOIT)),
920 strict_amount: params.has(keys::STRICT_AMOUNT),
921 base_power: params.get("BasePower").is_some(),
922 remember_sacrificed: parsed_true(params.get(keys::REMEMBER_SACRIFICED)),
923 unimprint: parsed_true(params.get(keys::UNIMPRINT)),
924 drain_mana: parsed_true(params.get(keys::DRAIN_MANA)),
925 remember_drained_mana: parsed_true(params.get(keys::REMEMBER_DRAINED_MANA)),
926 clear_remembered: parsed_true(params.get("ClearRemembered")),
927 etb: params.has(keys::ETB),
928 remember_tapped: params.has(keys::REMEMBER_TAPPED),
929 untap_type: params.get("UntapType").map(str::to_string),
930 untap_up_to: parsed_true(params.get("UntapUpTo")),
931 track_discarded: parsed_true(params.get(keys::TRACK_DISCARDED)),
932 no_peek: parsed_true(params.get("NoPeek")),
933 no_reveal: parsed_true(params.get("NoReveal")),
934 remember_peeked: parsed_true(params.get("RememberPeeked")),
935 reveal_optional: parsed_true(params.get("RevealOptional")),
936 remember_revealed: parsed_true(params.get("RememberRevealed")),
937 imprint_revealed: parsed_true(params.get("ImprintRevealed")),
938 remember_found: parsed_true(params.get(keys::REMEMBER_FOUND)),
939 imprint_found: parsed_true(params.get(keys::IMPRINT_FOUND)),
940 peek_amount_text: params.get("PeekAmount").map(str::to_string),
941 champion: parsed_true(params.get(keys::CHAMPION)),
942 with_noted_counters: parsed_true(params.get(keys::WITH_NOTED_COUNTERS)),
943 exiled_with_effect_source: params.has("ExiledWithEffectSource"),
944 token_tapped: params.has(keys::TOKEN_TAPPED),
945 token_blocking_text: params.get(keys::TOKEN_BLOCKING).map(str::to_string),
946 remember_tokens: params.has(keys::REMEMBER_TOKENS),
947 remember_original_tokens: params.has(keys::REMEMBER_ORIGINAL_TOKENS),
948 imprint_tokens: params.has(keys::IMPRINT_TOKENS),
949 remember_source: params.has(keys::REMEMBER_SOURCE),
950 token_remembered: params.get(keys::TOKEN_REMEMBERED).map(str::to_string),
951 cleanup_for_each: params.has(keys::CLEANUP_FOR_EACH),
952 add_triggers_from_text: params.get(keys::ADD_TRIGGERS_FROM).map(str::to_string),
953 at_eot_trig_text: params.get(keys::AT_EOT_TRIG).map(str::to_string),
954 pump_duration_text: params.get(keys::PUMP_DURATION).map(str::to_string),
955 searched: parsed_true(params.get(keys::SEARCHED)),
956 reorder: parsed_true(params.get("Reorder")),
957 exactly: parsed_true(params.get(keys::EXACTLY)),
958 no_looking: parsed_true(params.get(keys::NO_LOOKING)),
959 remember_lki_flag: parsed_true(params.get("RememberLKI")),
960 remember_searched: parsed_true(params.get(keys::REMEMBER_SEARCHED)),
961 ninjutsu: parsed_true(params.get(keys::NINJUTSU)),
962 unearth: parsed_true(params.get(keys::UNEARTH)),
963 attacking: params.has(keys::ATTACKING),
964 cant_fizzle: params.has(keys::CANT_FIZZLE),
965 cant_be_countered: parsed_true(params.get("CantBeCountered")),
966 unpayable: parsed_true(params.get("Unpayable")),
967 pw_ability: parsed_true(params.get(keys::PLANESWALKER))
968 || parsed_true(params.get("PwAbility")),
969 flash: parsed_true(params.get("Flash")),
970 split_second: parsed_true(params.get("SplitSecond")),
971 random: parsed_true(params.get("Random")),
972 random_target: parsed_true(params.get(keys::RANDOM_TARGET)),
973 rest_random_order: parsed_true(params.get("RestRandomOrder")),
974 prompt_to_skip_optional_ability: params.has(keys::PROMPT_TO_SKIP_OPTIONAL_ABILITY),
975 prompt_if_only_possible_ability: parsed_true(params.get("PromptIfOnlyPossible")),
976 skip_cancel_prompt: parsed_true(params.get("SkipCancelPrompt")),
977 temp_remember: params.has("TempRemember"),
978 num_random_choices: params.get("NumRandomChoices").map(str::to_string),
979 num_dmg_text: params.get("NumDmg").map(str::to_string),
980 num_cards_text: params.get("NumCards").map(str::to_string),
981 sides: parsed_i32(params.get(keys::SIDES)),
982 result_sub_abilities_text: params.get(keys::RESULT_SUB_ABILITIES).map(str::to_string),
983 result_svar_text: params.get("ResultSVar").map(str::to_string),
984 chosen_svar_text: params.get("ChosenSVar").map(str::to_string),
985 other_svar_text: params.get("OtherSVar").map(str::to_string),
986 spellbook_text: params.get(keys::SPELLBOOK).map(str::to_string),
987 dungeon_text: params.get(keys::DUNGEON).map(str::to_string),
988 var_name_text: params.get(keys::VAR_NAME).map(str::to_string),
989 animate_power: parsed_i32(params.get(keys::POWER)),
990 animate_toughness: parsed_i32(params.get(keys::TOUGHNESS)),
991 animate_types_text: params.get(keys::TYPES).map(str::to_string),
992 animate_keywords_text: params.get(keys::KEYWORDS).map(str::to_string),
993 animate_remove_keywords_text: params.get("RemoveKeywords").map(str::to_string),
994 animate_triggers_text: params.get(keys::TRIGGERS).map(str::to_string),
995 animate_colors_text: params.get("Colors").map(str::to_string),
996 overwrite_colors: parsed_true(params.get(keys::OVERWRITE_COLORS)),
997 animate_overwrite_types: parsed_true(params.get("OverwriteTypes")),
998 animate_remove_creature_types: parsed_true(params.get(keys::REMOVE_CREATURE_TYPES)),
999 animate_remove_all_abilities: parsed_true(params.get(keys::REMOVE_ALL_ABILITIES)),
1000 animate_incorporate_text: params.get("Incorporate").map(str::to_string),
1001 animate_mana_cost_override_text: params.get("ManaCost").map(str::to_string),
1002 sneak: parsed_true(params.get(keys::SNEAK)),
1003 attacking_text: params.get(keys::ATTACKING).map(str::to_string),
1004 ninjutsu_text: params.get(keys::NINJUTSU).map(str::to_string),
1005 token_attacking_text: params.get(keys::TOKEN_ATTACKING).map(str::to_string),
1006 without_mana_cost: params.has("WithoutManaCost"),
1007 cast_from_play_effect: params.has("CastFromPlayEffect"),
1008 store_vote_num: parsed_true(params.get(keys::STORE_VOTE_NUM)),
1009 remember_voted_objects: parsed_true(params.get(keys::REMEMBER_VOTED_OBJECTS)),
1010 remember_played: parsed_true(params.get("RememberPlayed")),
1011 discard_valid_text: params.get("DiscardValid").map(str::to_string),
1012 discard_valid_selector: params.get("DiscardValid").map(CompiledSelector::parse),
1013 reveal_valid_text: params.get("RevealValid").map(str::to_string),
1014 reveal_valid_selector: params.get("RevealValid").map(CompiledSelector::parse),
1015 any_number: parsed_true(params.get("AnyNumber")),
1016 remember_discarded: parsed_true(params.get("RememberDiscarded")),
1017 allows_paying_with_shard: parsed_true(params.get("AllowsPayingWithShard")),
1018 cant_be_copied_ability: parsed_true(params.get("CantBeCopied")),
1019 mana_replacement: params.get(keys::MANA_REPLACEMENT).map(str::to_string),
1020 remember_manifested: parsed_true(params.get(keys::REMEMBER_MANIFESTED)),
1021 unless_payer_text: params.get(keys::UNLESS_PAYER).map(str::to_string),
1022 unless_resolve_subs: params.get("UnlessResolveSubs").map(str::to_string),
1023 unless_switched: params.has(keys::UNLESS_SWITCHED),
1024 vote_message_text: params.get(keys::VOTE_MESSAGE).map(str::to_string),
1025 optional_decider_text: params.get(keys::OPTIONAL_DECIDER).map(str::to_string),
1026 stack_description_text: params.get("StackDescription").map(str::to_string),
1027 sp_desc_text: params.get("SpDesc").map(str::to_string),
1028 spell_description_text: params.get("SpellDescription").map(str::to_string),
1029 condition_description_text: params.get("ConditionDescription").map(str::to_string),
1030 after_description_text: params.get("AfterDescription").map(str::to_string),
1031 announce_text: params.get("Announce").map(str::to_string),
1032 x_max_limit_text: params.get("XMaxLimit").map(str::to_string),
1033 announce_max_text: params.get("AnnounceMax").map(str::to_string),
1034 optional_ability_prompt: params
1035 .get(keys::OPTIONAL_ABILITY_PROMPT)
1036 .map(str::to_string),
1037 svar_name_text: params
1038 .get("SVar")
1039 .or_else(|| params.get(keys::SVAR_NAME))
1040 .map(str::to_string),
1041 svar_type_text: params.get("Type").map(str::to_string),
1042 svar_expression_text: params
1043 .get("Expression")
1044 .or_else(|| params.get(keys::SVAR_VALUE))
1045 .map(str::to_string),
1046 change_color_word_text: params.get(keys::CHANGE_COLOR_WORD).map(str::to_string),
1047 change_type_word_text: params.get(keys::CHANGE_TYPE_WORD).map(str::to_string),
1048 forbidden_new_types_text: params.get(keys::FORBIDDEN_NEW_TYPES).map(str::to_string),
1049 cost_has_x: params
1050 .get(keys::COST)
1051 .is_some_and(|cost| cost.contains('X')),
1052 targeting_player: params.has(keys::TARGETING_PLAYER),
1053 targeting_player_text: params.get(keys::TARGETING_PLAYER).map(str::to_string),
1054 effect_source: params.has(keys::EFFECT_SOURCE),
1055 defined_magnet_text: params.get(keys::DEFINED_MAGNET).map(str::to_string),
1056 targets_with_defined_controller_text: params
1057 .get("TargetsWithDefinedController")
1058 .map(str::to_string),
1059 replace_dying_exiled_with: params.has("ReplaceDyingExiledWith"),
1060 mode: params.get(keys::MODE).map(SpellAbilityMode::parse),
1061 duration: params.get(keys::DURATION).map(AbilityDuration::parse),
1062 replace_dying_condition: params
1063 .get("ReplaceDyingCondition")
1064 .map(ReplaceDyingCondition::parse),
1065 day_time_value: params.get(keys::VALUE).and_then(DayTimeValue::parse),
1066 choice_restriction_text: params.get("ChoiceRestriction").map(str::to_string),
1067 phase_text: params.get(keys::PHASE).map(str::to_string),
1068 step_text: params.get(keys::STEP).map(str::to_string),
1069 phase_in_or_out_text: params.get("PhaseInOrOut").map(str::to_string),
1070 found_destination_zone: parsed_zone_type(params.get("FoundDestination")),
1071 revealed_destination_zone: parsed_zone_type(params.get("RevealedDestination")),
1072 extra_phase_text: params.get("ExtraPhase").map(str::to_string),
1073 card_state_name: params.get("CardState").map(str::to_string),
1074 track_mana_spent: parsed_true(params.get("TrackManaSpent")),
1075 become_starting_player: params.has("BecomeStartingPlayer"),
1076 tap_creatures_for_mana: params.has("TapCreaturesForMana"),
1077 remember_players_text: params.get(keys::REMEMBER_PLAYERS).map(str::to_string),
1078 no_call: parsed_true(params.get("NoCall")),
1079 flip_until_you_lose: parsed_true(params.get("FlipUntilYouLose")),
1080 different_names: parsed_true(params.get(keys::DIFFERENT_NAMES)),
1081 different_cmc: parsed_true(params.get(keys::DIFFERENT_CMC)),
1082 different_power: parsed_true(params.get(keys::DIFFERENT_POWER)),
1083 share_land_type: parsed_true(params.get(keys::SHARE_LAND_TYPE)),
1084 imprint_last: parsed_true(params.get(keys::IMPRINT_LAST)),
1085 foretold: parsed_true(params.get(keys::FORETOLD)),
1086 foretold_cost: parsed_true(params.get(keys::FORETOLD_COST)),
1087 random_order: parsed_true(params.get(keys::RANDOM_ORDER)),
1088 shuffle_changed_pile: parsed_true(params.get(keys::SHUFFLE_CHANGED_PILE)),
1089 warp: params.has(keys::WARP),
1090 morph: parsed_true(params.get("Morph")),
1091 morph_up: parsed_true(params.get("MorphUp")),
1092 megamorph: parsed_true(params.get("Megamorph")),
1093 mega: parsed_true(params.get(keys::MEGA)),
1094 imprint_made: parsed_true(params.get(keys::IMPRINT_MADE)),
1095 to_visit_your_attractions: parsed_true(params.get("ToVisitYourAttractions")),
1096 remember_highest_player: parsed_true(params.get("RememberHighestPlayer")),
1097 use_highest_roll: parsed_true(params.get("UseHighestRoll")),
1098 use_difference_between_rolls: parsed_true(params.get("UseDifferenceBetweenRolls")),
1099 store_results: parsed_true(params.get("StoreResults")),
1100 even_odd_results: parsed_true(params.get("EvenOddResults")),
1101 different_results: parsed_true(params.get("DifferentResults")),
1102 max_rolls_results: parsed_true(params.get("MaxRollsResults")),
1103 note_doubles: parsed_true(params.get("NoteDoubles")),
1104 subs_for_each: parsed_true(params.get("SubsForEach")),
1105 reroll_results: parsed_true(params.get("RerollResults")),
1106 semantic_numeric_params: BTreeMap::new(),
1107 }
1108 }
1109
1110 pub fn compile_numeric_params_from_runtime(&mut self, params: &Params) {
1111 self.semantic_numeric_params = params
1112 .iter()
1113 .filter_map(|(key, value)| {
1114 NumericParamIr::from_semantic(&parse_semantic_param_value(key, value))
1115 .map(|compiled| (key.to_string(), compiled))
1116 })
1117 .collect();
1118 }
1119}
1120
1121#[derive(Debug, Clone, PartialEq, Eq)]
1122pub struct DefinedExpr {
1123 pub refs: Vec<DefinedRef>,
1124}
1125
1126impl DefinedExpr {
1127 pub fn parse(raw: &str) -> Self {
1128 Self {
1129 refs: raw
1130 .split(" & ")
1131 .map(str::trim)
1132 .filter(|part| !part.is_empty())
1133 .map(DefinedRef::parse)
1134 .collect(),
1135 }
1136 }
1137}
1138
1139#[derive(Debug, Clone, PartialEq, Eq, strum_macros::AsRefStr, strum_macros::EnumString)]
1140pub enum DefinedRef {
1141 #[strum(serialize = "Self", serialize = "CARDNAME")]
1142 SelfCard,
1143 You,
1144 Opponent,
1145 Player,
1146 Players,
1147 Targeted,
1148 TargetedCard,
1149 ThisTargetedCard,
1150 TargetedPlayer,
1151 ThisTargetedPlayer,
1152 TargetedOrController,
1153 TargetedController,
1154 ThisTargetedController,
1155 ParentTargetedController,
1156 TargetedOwner,
1157 ThisTargetedOwner,
1158 ParentTarget,
1159 ParentTargeted,
1160 TriggeredCard,
1161 #[strum(serialize = "TriggeredCardLKI")]
1162 TriggeredCardLki,
1163 #[strum(serialize = "TriggeredCardLKICopy")]
1164 TriggeredCardLkiCopy,
1165 TriggeredPlayer,
1166 TriggeredSource,
1167 TriggeredNewCard,
1168 #[strum(serialize = "TriggeredNewCardLKICopy")]
1169 TriggeredNewCardLkiCopy,
1170 TriggeredAttacker,
1171 TriggeredAttackers,
1172 TriggeredBlocker,
1173 TriggeredTarget,
1174 #[strum(serialize = "TriggeredTargetLKICopy")]
1175 TriggeredTargetLkiCopy,
1176 TriggeredTargets,
1177 TriggeredTargetController,
1178 TriggeredTargetsController,
1179 TriggeredAttackerController,
1180 TriggeredBlockerController,
1181 DefendingPlayer,
1182 TriggeredDefendingPlayer,
1183 ReplacedCard,
1184 #[strum(serialize = "ReplacedCardLKI")]
1185 ReplacedCardLki,
1186 Remembered,
1187 #[strum(serialize = "RememberedLKI")]
1188 RememberedLki,
1189 DelayTriggerRemembered,
1190 #[strum(serialize = "DelayTriggerRememberedLKI")]
1191 DelayTriggerRememberedLki,
1192 TriggerRemembered,
1193 Imprinted,
1194 ExiledWith,
1195 Explorer,
1196 Explored,
1197 Discarded,
1198 Sacrificed,
1199 #[strum(default)]
1200 Unsupported(String),
1201}
1202
1203impl DefinedRef {
1204 pub fn parse(raw: &str) -> Self {
1205 raw.parse()
1206 .unwrap_or_else(|_| Self::Unsupported(raw.to_string()))
1207 }
1208
1209 pub fn is_supported(&self) -> bool {
1210 !matches!(self, Self::Unsupported(_))
1211 }
1212
1213 pub fn as_legacy_str(&self) -> &str {
1214 match self {
1215 Self::SelfCard => "Self",
1216 Self::Unsupported(raw) => raw,
1217 other => other.as_ref(),
1218 }
1219 }
1220}
1221
1222#[derive(Debug, Clone, PartialEq, Eq)]
1223pub enum NumericParamIr {
1224 Integer(i32),
1225 Amount(AmountExpr),
1226 SVarReference(Vec<String>),
1227 Raw(String),
1228}
1229
1230impl NumericParamIr {
1231 fn from_semantic(value: &SemanticParamValue<'_>) -> Option<Self> {
1232 match value {
1233 SemanticParamValue::Integer(value) => Some(Self::Integer(*value)),
1234 SemanticParamValue::Amount(amount) => {
1235 Some(Self::Amount(AmountExpr::from_semantic(amount)))
1236 }
1237 SemanticParamValue::SVarReference(names) => Some(Self::SVarReference(
1238 names.iter().map(|name| (*name).to_string()).collect(),
1239 )),
1240 SemanticParamValue::Raw(raw)
1241 | SemanticParamValue::Expression(raw)
1242 | SemanticParamValue::Text(raw)
1243 | SemanticParamValue::Symbol(raw) => Some(Self::Raw((*raw).to_string())),
1244 _ => None,
1245 }
1246 }
1247}
1248
1249#[derive(Debug, Clone, PartialEq, Eq)]
1250pub struct DealDamageIr {
1251 pub amount: Option<AmountExpr>,
1252 pub valid_targets: Option<String>,
1253 pub damage_map: bool,
1254}
1255
1256impl DealDamageIr {
1257 pub fn from_parsed(params: &ParsedParams<'_>) -> Self {
1258 Self {
1259 amount: semantic_amount_expr(params, keys::NUM_DMG),
1260 valid_targets: params.get(keys::VALID_TGTS).map(str::to_string),
1261 damage_map: params.has(keys::DAMAGE_MAP),
1262 }
1263 }
1264}
1265
1266#[derive(Debug, Clone, PartialEq, Eq)]
1267pub struct NumericAmountIr {
1268 pub amount: Option<AmountExpr>,
1269}
1270
1271impl NumericAmountIr {
1272 pub fn from_parsed(params: &ParsedParams<'_>, key: &str) -> Self {
1273 Self {
1274 amount: semantic_amount_expr(params, key),
1275 }
1276 }
1277}
1278
1279fn semantic_amount_expr(params: &ParsedParams<'_>, key: &str) -> Option<AmountExpr> {
1280 let param = params.semantic_get(key)?;
1281 match param.value {
1282 SemanticParamValue::Amount(amount) => Some(AmountExpr::from_semantic(&amount)),
1283 SemanticParamValue::Integer(value) => Some(AmountExpr::Literal(value)),
1284 SemanticParamValue::Raw(raw)
1285 | SemanticParamValue::Expression(raw)
1286 | SemanticParamValue::Text(raw)
1287 | SemanticParamValue::Symbol(raw) => Some(AmountExpr::parse(raw)),
1288 _ => param
1289 .raw_value
1290 .is_empty()
1291 .then_some(AmountExpr::Raw(String::new())),
1292 }
1293}
1294
1295pub fn lower_effect_ir(
1296 api: Option<crate::ability::api_type::ApiType>,
1297 params: &ParsedParams<'_>,
1298) -> Option<EffectIr> {
1299 match api {
1300 Some(crate::ability::api_type::ApiType::DamageAll) => Some(EffectIr::DamageAll(
1301 NumericAmountIr::from_parsed(params, keys::NUM_DMG),
1302 )),
1303 Some(crate::ability::api_type::ApiType::DealDamage) => {
1304 Some(EffectIr::DealDamage(DealDamageIr::from_parsed(params)))
1305 }
1306 Some(crate::ability::api_type::ApiType::Draw) => Some(EffectIr::Draw(
1307 NumericAmountIr::from_parsed(params, keys::NUM_CARDS),
1308 )),
1309 Some(crate::ability::api_type::ApiType::GainLife) => Some(EffectIr::GainLife(
1310 NumericAmountIr::from_parsed(params, keys::LIFE_AMOUNT),
1311 )),
1312 Some(crate::ability::api_type::ApiType::SetLife) => Some(EffectIr::LifeSet(
1313 NumericAmountIr::from_parsed(params, keys::LIFE_AMOUNT),
1314 )),
1315 Some(crate::ability::api_type::ApiType::LoseLife) => Some(EffectIr::LoseLife(
1316 NumericAmountIr::from_parsed(params, keys::LIFE_AMOUNT),
1317 )),
1318 Some(crate::ability::api_type::ApiType::Mill) => Some(EffectIr::Mill(
1319 NumericAmountIr::from_parsed(params, keys::NUM_CARDS),
1320 )),
1321 Some(crate::ability::api_type::ApiType::Poison) => Some(EffectIr::Poison(
1322 NumericAmountIr::from_parsed(params, keys::NUM),
1323 )),
1324 _ => None,
1325 }
1326}
1327
1328fn parsed_zone_type(value: Option<&str>) -> Option<ZoneType> {
1329 let value = value?.trim();
1330 if value.eq_ignore_ascii_case("Deck") {
1331 Some(ZoneType::Library)
1332 } else {
1333 ZoneType::from_str_compat(value)
1334 }
1335}
1336
1337fn parsed_zone_types(value: Option<&str>) -> Vec<ZoneType> {
1338 value
1339 .map(|zones| {
1340 zones
1341 .split([',', ' '])
1342 .map(str::trim)
1343 .filter(|zone| !zone.is_empty())
1344 .filter_map(|zone| {
1345 if zone.eq_ignore_ascii_case("Deck") {
1346 Some(ZoneType::Library)
1347 } else {
1348 ZoneType::from_str_compat(zone)
1349 }
1350 })
1351 .collect()
1352 })
1353 .unwrap_or_default()
1354}
1355
1356fn parsed_i32(value: Option<&str>) -> Option<i32> {
1357 value?.parse().ok()
1358}
1359
1360fn parsed_usize(value: Option<&str>) -> Option<usize> {
1361 value?.parse().ok()
1362}
1363
1364fn parsed_bool_default(value: Option<&str>, default: bool) -> bool {
1365 value
1366 .map(|value| value.eq_ignore_ascii_case("True"))
1367 .unwrap_or(default)
1368}
1369
1370fn parsed_true(value: Option<&str>) -> bool {
1371 value.is_some_and(|value| value.eq_ignore_ascii_case("True"))
1372}
1373
1374fn has_remember_objects_token(params: &ParsedParams<'_>, token: &str) -> bool {
1375 params
1376 .get(keys::REMEMBER_OBJECTS)
1377 .is_some_and(|raw| raw.split(" & ").any(|t| t.trim() == token))
1378}
1379
1380#[cfg(test)]
1381mod tests {
1382 use super::*;
1383 use crate::parsing::ParsedParams;
1384
1385 #[test]
1386 fn planeswalker_param_marks_pw_ability() {
1387 let params = ParsedParams::parse("AB$ Discard | Planeswalker$ True");
1388 let ir = SpellAbilityIr::from_parsed(None, ¶ms);
1389 assert!(ir.pw_ability);
1390 }
1391}