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