1pub mod activation_table;
2mod alt_costs;
3mod card_assembly;
4pub mod card_changed_words;
5pub mod card_clone_states;
6pub mod card_collection;
7pub mod card_collection_view;
8pub mod card_copy_service;
9pub mod card_damage_history;
10pub mod card_damage_map;
11pub mod card_factory;
12pub mod card_factory_util;
13pub mod card_lists;
14pub mod card_play_option;
15pub mod card_predicates;
16pub mod card_property;
17pub mod card_state;
18pub mod card_trait_changes;
19pub mod card_util;
20pub mod card_zone_table;
21pub mod counter_enum_type;
22pub mod counter_keyword_type;
23pub mod counter_type;
24pub mod damage_history;
25pub mod filter_constants;
26mod keyword_gen;
27pub mod perpetual;
28pub mod svar_cache;
29pub mod token;
30pub mod token_create_table;
31pub mod trait_card_trait_changes;
32pub mod valid_filter;
33use crate::card::activation_table::ActivationTable;
34use crate::core::HasSVars;
35pub use counter_type::CounterType;
36
37use crate::keyword::keyword_instance::Keyword as Kw;
39
40pub const KEYWORD_PLOTTED_PREFIX: &str = "Plotted:";
47
48pub const KEYWORD_WARP_EXILED: &str = "WarpExiled";
51
52use std::collections::{BTreeMap, HashMap, HashSet};
53
54use forge_carddb::CardRules;
55use forge_foundation::{CardTypeLine, ColorSet, CoreType, ManaCost, ZoneType};
56use serde::{Deserialize, Serialize};
57
58use crate::ability::activated::{parse_activated_ability, ActivatedAbility};
59use crate::card::perpetual::perpetual_record::PerpetualRecord;
60use crate::card::svar_cache::{ParsedSVar, ParsedSVarCache};
61use crate::cost::{parse_cost, Cost};
62use crate::game::GameState;
63use crate::ids::{CardId, PlayerId};
64use crate::parsing::{keys, parse_or_warn, Params, ParsedParams};
65use crate::replacement::{parse_replacement_effect, ReplacementEffect};
66use crate::spellability::{SpellAbility, TargetRestrictions};
67use crate::staticability::{parse_static_ability, StaticAbility};
68use crate::trigger::Trigger;
69
70fn colorless_color_set() -> ColorSet {
72 ColorSet::COLORLESS
73}
74
75fn parse_literal_target_count(expr: &str) -> Option<i32> {
76 if let Ok(n) = expr.trim().parse::<i32>() {
77 return Some(n);
78 }
79 expr.trim().strip_prefix('+')?.parse::<i32>().ok()
80}
81
82pub fn make_plotted_keyword(turn: u32) -> String {
83 format!("{KEYWORD_PLOTTED_PREFIX}{turn}")
84}
85
86pub fn parse_plotted_turn(kw: &str) -> Option<u32> {
88 kw.strip_prefix(KEYWORD_PLOTTED_PREFIX)
89 .and_then(|s| s.parse().ok())
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct CardOtherPart {
97 pub name: String,
98 #[serde(default)]
99 pub oracle_text: String,
100 #[serde(default)]
105 pub is_modal: bool,
106 pub type_line: CardTypeLine,
107 pub mana_cost: ManaCost,
108 pub color: ColorSet,
109 pub base_power: Option<i32>,
110 pub base_toughness: Option<i32>,
111 pub keywords: crate::keyword::keyword_collection::KeywordCollection,
112 pub abilities: Vec<String>,
113 pub triggers: Vec<Trigger>,
114 pub static_abilities: Vec<crate::staticability::StaticAbility>,
115 pub replacement_effects: Vec<crate::replacement::ReplacementEffect>,
116 pub svars: BTreeMap<String, String>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, Default)]
120pub struct CardActionSpellSpec {
121 pub ability_index: usize,
122 pub has_valid_tgts: bool,
123 pub cost_contains_x: bool,
124 #[serde(default)]
125 pub target_chain: Vec<CardActionTargetSpec>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CardActionTargetSpec {
130 pub target_restrictions: TargetRestrictions,
131 pub min_targets: Option<i32>,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum_macros::EnumString)]
144#[strum(ascii_case_insensitive)]
145pub enum LoseControlCondition {
146 #[strum(serialize = "EOT", serialize = "UntilEOT", serialize = "EndOfTurn")]
148 EndOfTurn,
149 #[strum(serialize = "Untap", serialize = "UntilUntap", serialize = "NextUntap")]
151 NextUntap,
152 EndOfCombat,
154 LeavesPlay,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct AnimateState {
161 pub original_type_line: CardTypeLine,
162 pub original_base_power: Option<i32>,
163 pub original_base_toughness: Option<i32>,
164 pub original_color: ColorSet,
165 #[serde(default)]
169 pub original_keywords: Option<crate::keyword::keyword_collection::KeywordCollection>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct CloneState {
175 #[serde(default)]
176 pub expires_at_cleanup: bool,
177 pub original_card_name: String,
178 #[serde(default)]
179 pub original_oracle_text: String,
180 pub original_type_line: CardTypeLine,
181 pub original_mana_cost: ManaCost,
182 pub original_color: ColorSet,
183 pub original_base_power: Option<i32>,
184 pub original_base_toughness: Option<i32>,
185 pub original_keywords: crate::keyword::keyword_collection::KeywordCollection,
186 pub original_abilities: Vec<String>,
187 pub original_activated_abilities: Vec<ActivatedAbility>,
188 pub original_triggers: Vec<Trigger>,
189 pub original_svars: BTreeMap<String, String>,
190 pub original_static_abilities: Vec<StaticAbility>,
191 pub original_replacement_effects: Vec<ReplacementEffect>,
192 #[serde(default)]
198 pub original_base_ability_count: usize,
199 #[serde(default)]
201 pub original_base_trigger_count: usize,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct Card {
208 pub id: CardId,
209 pub card_name: String,
212 pub full_name: String,
216 #[serde(default)]
217 pub oracle_text: String,
218
219 pub owner: PlayerId,
221 pub controller: PlayerId,
222
223 pub zone: ZoneType,
225
226 pub type_line: CardTypeLine,
228
229 pub mana_cost: ManaCost,
231
232 pub color: ColorSet,
234
235 #[serde(default = "colorless_color_set")]
240 pub color_identity: ColorSet,
241
242 pub base_power: Option<i32>,
244 pub base_toughness: Option<i32>,
245 pub initial_loyalty: Option<String>,
247 pub power_modifier: i32,
250 pub toughness_modifier: i32,
251 pub perpetual_power_modifier: i32,
254 pub perpetual_toughness_modifier: i32,
255 #[serde(default)]
257 pub perpetual: Vec<PerpetualRecord>,
258 pub static_set_power: Option<i32>,
262 pub static_set_toughness: Option<i32>,
263 pub static_power_modifier: i32,
266 pub static_toughness_modifier: i32,
267
268 pub tapped: bool,
270 #[serde(skip)]
274 pub last_mana_produced: Option<Vec<u16>>,
275 pub flipped: bool,
276 pub face_down: bool,
277 pub has_morph: bool,
279 pub discarded: bool,
281 pub unearthed: bool,
283 pub class_level: i32,
285 pub paired_with: Option<CardId>,
287 pub manifested: bool,
289 pub cloaked: bool,
291 pub foretold: bool,
293 pub melded_with: Vec<CardId>,
296 pub foretold_cost_by_effect: bool,
298 pub is_bestowed: bool,
300 pub summoning_sick: bool,
301 #[serde(default)]
302 pub came_under_control_since_last_upkeep: bool,
303 pub exerted: bool,
304 pub damage: i32,
305 pub cast_from: Option<ZoneType>,
311
312 pub counters: BTreeMap<CounterType, i32>,
314
315 pub keywords: crate::keyword::keyword_collection::KeywordCollection,
318 pub granted_keywords: crate::keyword::keyword_collection::KeywordCollection,
321 #[serde(default)]
324 pub granted_svars: BTreeMap<String, String>,
325 pub static_added_subtypes: Vec<String>,
331 #[serde(skip)]
332 pub static_type_line_base: Option<CardTypeLine>,
333 #[serde(skip)]
334 pub changed_type_line_base: Option<CardTypeLine>,
335 #[serde(skip)]
336 pub changed_base_power: Option<Option<i32>>,
337 #[serde(skip)]
338 pub changed_base_toughness: Option<Option<i32>>,
339 pub pump_keywords: crate::keyword::keyword_collection::KeywordCollection,
342 pub pump_trigger_count: usize,
345
346 pub abilities: Vec<String>,
348 #[serde(default)]
350 pub action_spell_specs: Vec<CardActionSpellSpec>,
351 #[serde(default)]
353 pub action_spell_cost: Option<Cost>,
354 #[serde(default)]
356 pub ai_phyrexian_payment: Option<String>,
357 #[serde(default)]
359 pub spree_min_mode_cost: Option<i32>,
360
361 pub activated_abilities: Vec<ActivatedAbility>,
363 pub base_ability_count: usize,
366 pub base_trigger_count: usize,
369 pub changed_card_traits:
372 std::collections::BTreeMap<(i64, i64), card_trait_changes::CardTraitChanges>,
373 pub changed_card_traits_by_text:
376 std::collections::BTreeMap<(i64, i64), card_trait_changes::CardTraitChanges>,
377
378 pub static_abilities: Vec<StaticAbility>,
381
382 pub has_deathtouch_damage: bool,
384 pub cant_attack_static: bool,
387 pub cant_block_static: bool,
390
391 #[serde(default)]
393 pub turn_in_zone: u32,
394 pub entered_battlefield_this_turn: bool,
395 pub attacked_this_turn: bool,
396 pub started_turn_tapped: bool,
399
400 pub triggers: Vec<Trigger>,
402 pub svars: BTreeMap<String, String>,
404 #[serde(skip, default)]
405 pub parsed_svar_cache: ParsedSVarCache,
406
407 pub is_commander: bool,
410 pub move_to_command_zone: bool,
413 pub commander_cast_count: u32,
415
416 pub is_token: bool,
418
419 pub cast_with_flashback: bool,
422 pub cast_with_harmonize: bool,
425
426 pub replacement_effects: Vec<ReplacementEffect>,
429
430 pub attached_to: Option<CardId>,
434 pub attached_to_player: Option<PlayerId>,
435 pub attached_this_turn: bool,
438 pub attachments: Vec<CardId>,
440
441 pub remembered_cards: Vec<CardId>,
444 pub remembered_players: Vec<PlayerId>,
446 pub imprinted_cards: Vec<CardId>,
448 pub gain_control_targets: Vec<CardId>,
450 pub until_leaves_battlefield: Vec<CardId>,
452 pub exiled_cards: Vec<CardId>,
454 pub paid_cost_exiled_cards: Vec<CardId>,
457 pub haunted_by: Vec<CardId>,
459 pub haunting: Option<CardId>,
461 pub chosen_map: HashMap<PlayerId, Vec<CardId>>,
463 pub remembered_cmc: Vec<i32>,
465 pub effect_source: Option<CardId>,
467 #[serde(default)]
468 pub clone_origin: Option<CardId>,
469 #[serde(default)]
470 pub copied_permanent: Option<CardId>,
471 #[serde(skip, default)]
475 pub cast_sa: Option<Box<SpellAbility>>,
476 #[serde(default)]
480 pub chosen_charm_modes: HashMap<String, i32>,
481 #[serde(skip, default)]
487 pub remembered_lki_cards: Vec<Card>,
488 #[serde(default)]
492 pub lose_control_condition: Option<LoseControlCondition>,
493 pub temp_effect_until_eot: bool,
495 pub temp_effect_host: Option<CardId>,
498 pub forget_on_moved_origin: Option<ZoneType>,
500 pub exile_when_no_remembered: bool,
502 pub exiled_by: Option<CardId>,
506
507 pub original_controller_eot: Option<PlayerId>,
509
510 pub is_transformed: bool,
513 pub other_part: Option<CardOtherPart>,
515
516 pub set_code: Option<String>,
518
519 pub card_number: Option<String>,
523
524 #[serde(default)]
525 pub paper_foil: bool,
526
527 pub phased_out: bool,
529
530 pub regeneration_shields: i32,
533
534 pub kicked: bool,
538 pub monstrous: bool,
541
542 pub chosen_colors: Vec<String>,
544 pub chosen_cards: Vec<CardId>,
546
547 pub animate_state: Option<AnimateState>,
549 pub clone_state: Option<CloneState>,
551
552 pub chosen_type: Option<String>,
555 pub chosen_type2: Option<String>,
557 pub noted_types: Vec<String>,
559 pub named_cards: Vec<String>,
561 pub chosen_number: Option<i32>,
563 pub chosen_player: Option<PlayerId>,
565 pub chosen_player_controller: Option<PlayerId>,
567 pub chosen_type_controller: Option<PlayerId>,
569 pub chosen_player_revealed: bool,
571 pub chosen_type_revealed: bool,
573 pub promised_gift: Option<PlayerId>,
575 pub attraction_lights: Vec<u32>,
577 pub sector: Option<String>,
579 pub chosen_sector: Option<String>,
581 pub sprocket: i32,
583 pub chosen_even_odd: Option<String>,
585 pub detained: bool,
587 pub attacking_player: Option<PlayerId>,
589 pub goaded_by: Option<PlayerId>,
591 pub damage_prevention: i32,
593 pub assigned_damage: i32,
595 pub must_block: bool,
597 pub encoded_cards: Vec<CardId>,
599 pub damage_sources_this_turn: Vec<CardId>,
602 pub total_damage_done_this_turn: i32,
606 pub lki_power: Option<i32>,
610 pub lki_toughness: Option<i32>,
613 pub lki_counters: Option<std::collections::BTreeMap<CounterType, i32>>,
616 #[serde(skip)]
619 pub damage_history: damage_history::DamageHistory,
620 pub must_block_cards: Vec<CardId>,
622 pub etb_counters_p1p1: i32,
624 pub colors_spent_to_cast: u16,
627 pub paying_mana_to_cast: Vec<u16>,
630 pub chosen_modes: Option<Vec<usize>>,
633 pub strive_extra_targets: u32,
635 pub became_target_this_turn: bool,
637 pub temp_controllers: Vec<PlayerId>,
639 pub may_look_at: Vec<PlayerId>,
641 pub may_play: Vec<PlayerId>,
643 pub can_block_additional: i32,
645 pub can_block_any: bool,
647 pub cant_have_keywords: HashSet<String>,
649 pub intensity: i32,
651 pub surveilled: bool,
653 pub milled: bool,
655 pub visited_this_turn: bool,
657 pub times_crewed_this_turn: u32,
659 pub is_crewed: bool,
661 pub ignore_legend_rule_flag: bool,
663 pub ability_activated_this_turn: u32,
665 pub ability_resolved_this_turn: u32,
667 #[serde(skip)]
669 pub number_turn_activations: ActivationTable,
670 #[serde(skip)]
672 pub number_game_activations: ActivationTable,
673 #[serde(skip)]
675 pub number_ability_resolved: ActivationTable,
676 pub planeswalker_abilities_activated: u32,
678 pub planeswalker_activation_limit_used: bool,
680 pub chosen_modes_turn: Option<u32>,
682 pub enlisted_this_combat: bool,
684 pub activations_this_game: std::collections::BTreeMap<usize, u32>,
686 pub is_renowned: bool,
689 pub zone_timestamp: u64,
693
694 #[serde(skip)]
697 trait_base_activated_abilities: Option<Vec<ActivatedAbility>>,
698 #[serde(skip)]
699 trait_base_triggers: Option<Vec<Trigger>>,
700 #[serde(skip)]
701 trait_base_replacement_effects: Option<Vec<ReplacementEffect>>,
702 #[serde(skip)]
703 trait_base_static_abilities: Option<Vec<StaticAbility>>,
704 #[serde(skip)]
705 trait_base_keywords: Option<crate::keyword::keyword_collection::KeywordCollection>,
706}
707
708pub type CardInstance = Card;
710
711impl Card {
712 pub fn new(
713 id: CardId,
714 card_name: String,
715 owner: PlayerId,
716 type_line: CardTypeLine,
717 mana_cost: ManaCost,
718 color: ColorSet,
719 base_power: Option<i32>,
720 base_toughness: Option<i32>,
721 keywords: Vec<String>,
722 abilities: Vec<String>,
723 ) -> Self {
724 let activated_abilities: Vec<ActivatedAbility> = abilities
726 .iter()
727 .enumerate()
728 .filter_map(|(i, raw)| {
729 parse_or_warn(parse_activated_ability(raw, i), "ActivatedAbility", raw)
730 })
731 .collect();
732
733 let replacement_effects: Vec<ReplacementEffect> = abilities
736 .iter()
737 .filter_map(|raw| {
738 parse_or_warn(parse_replacement_effect(raw), "ReplacementEffect", raw)
739 })
740 .collect();
741
742 let static_abilities: Vec<StaticAbility> = abilities
745 .iter()
746 .filter_map(|raw| parse_or_warn(parse_static_ability(raw), "StaticAbility", raw))
747 .collect();
748
749 let full_name = card_name.clone();
750 let color_identity = color;
751 let mut card = Card {
752 id,
753 card_name,
754 full_name,
755 oracle_text: String::new(),
756 owner,
757 controller: owner,
758 zone: ZoneType::None,
759 type_line,
760 mana_cost,
761 color,
762 color_identity,
763 base_power,
764 base_toughness,
765 initial_loyalty: None,
766 power_modifier: 0,
767 toughness_modifier: 0,
768 perpetual_power_modifier: 0,
769 perpetual_toughness_modifier: 0,
770 perpetual: Vec::new(),
771 static_set_power: None,
772 static_set_toughness: None,
773 static_power_modifier: 0,
774 static_toughness_modifier: 0,
775 tapped: false,
776 last_mana_produced: None,
777 flipped: false,
778 face_down: false,
779 has_morph: false,
780 discarded: false,
781 unearthed: false,
782 class_level: 1,
783 paired_with: None,
784 manifested: false,
785 cloaked: false,
786 foretold: false,
787 foretold_cost_by_effect: false,
788 melded_with: Vec::new(),
789 is_bestowed: false,
790 summoning_sick: true,
791 came_under_control_since_last_upkeep: false,
792 exerted: false,
793 damage: 0,
794 cast_from: None,
795 counters: BTreeMap::new(),
796 keywords: crate::keyword::keyword_collection::KeywordCollection::from_strings(
797 &keywords,
798 ),
799 granted_keywords: crate::keyword::keyword_collection::KeywordCollection::new(),
800 granted_svars: BTreeMap::new(),
801 static_added_subtypes: Vec::new(),
802 static_type_line_base: None,
803 changed_type_line_base: None,
804 changed_base_power: None,
805 changed_base_toughness: None,
806 pump_keywords: crate::keyword::keyword_collection::KeywordCollection::new(),
807 pump_trigger_count: 0,
808 abilities,
809 action_spell_specs: Vec::new(),
810 action_spell_cost: None,
811 ai_phyrexian_payment: None,
812 spree_min_mode_cost: None,
813 activated_abilities,
814 base_ability_count: 0,
815 base_trigger_count: 0,
816 changed_card_traits: std::collections::BTreeMap::new(),
817 changed_card_traits_by_text: std::collections::BTreeMap::new(),
818 static_abilities,
819 has_deathtouch_damage: false,
820 cant_attack_static: false,
821 cant_block_static: false,
822 turn_in_zone: 0,
823 entered_battlefield_this_turn: false,
824 attacked_this_turn: false,
825 started_turn_tapped: false,
826 triggers: Vec::new(),
827 svars: BTreeMap::new(),
828 parsed_svar_cache: ParsedSVarCache::default(),
829 is_commander: false,
830 move_to_command_zone: false,
831 commander_cast_count: 0,
832 is_token: false,
833 cast_with_flashback: false,
834 cast_with_harmonize: false,
835 replacement_effects,
836 attached_to: None,
837 attached_to_player: None,
838 attached_this_turn: false,
839 attachments: Vec::new(),
840 remembered_cards: Vec::new(),
841 remembered_players: Vec::new(),
842 imprinted_cards: Vec::new(),
843 gain_control_targets: Vec::new(),
844 until_leaves_battlefield: Vec::new(),
845 exiled_cards: Vec::new(),
846 paid_cost_exiled_cards: Vec::new(),
847 haunted_by: Vec::new(),
848 haunting: None,
849 chosen_map: HashMap::new(),
850 remembered_cmc: Vec::new(),
851 effect_source: None,
852 clone_origin: None,
853 copied_permanent: None,
854 cast_sa: None,
855 chosen_charm_modes: HashMap::new(),
856 remembered_lki_cards: Vec::new(),
857 lose_control_condition: None,
858 temp_effect_until_eot: false,
859 temp_effect_host: None,
860 forget_on_moved_origin: None,
861 exile_when_no_remembered: false,
862 exiled_by: None,
863 original_controller_eot: None,
864 is_transformed: false,
865 other_part: None,
866 set_code: None,
867 card_number: None,
868 paper_foil: false,
869 phased_out: false,
870 regeneration_shields: 0,
871 kicked: false,
872 monstrous: false,
873 chosen_colors: Vec::new(),
874 chosen_cards: Vec::new(),
875 animate_state: None,
876 clone_state: None,
877 chosen_type: None,
878 chosen_type2: None,
879 noted_types: Vec::new(),
880 named_cards: Vec::new(),
881 chosen_number: None,
882 chosen_player: None,
883 chosen_player_controller: None,
884 chosen_type_controller: None,
885 chosen_player_revealed: false,
886 chosen_type_revealed: false,
887 promised_gift: None,
888 attraction_lights: Vec::new(),
889 sector: None,
890 chosen_sector: None,
891 sprocket: 0,
892 chosen_even_odd: None,
893 detained: false,
894 attacking_player: None,
895 goaded_by: None,
896 damage_prevention: 0,
897 assigned_damage: 0,
898 must_block: false,
899 encoded_cards: Vec::new(),
900 damage_sources_this_turn: Vec::new(),
901 total_damage_done_this_turn: 0,
902 lki_power: None,
903 lki_toughness: None,
904 lki_counters: None,
905 damage_history: damage_history::DamageHistory::default(),
906 must_block_cards: Vec::new(),
907 etb_counters_p1p1: 0,
908 colors_spent_to_cast: 0,
909 paying_mana_to_cast: Vec::new(),
910 chosen_modes: None,
911 strive_extra_targets: 0,
912 became_target_this_turn: false,
913 temp_controllers: Vec::new(),
914 may_look_at: Vec::new(),
915 may_play: Vec::new(),
916 can_block_additional: 0,
917 can_block_any: false,
918 cant_have_keywords: HashSet::new(),
919 intensity: 0,
920 surveilled: false,
921 milled: false,
922 visited_this_turn: false,
923 times_crewed_this_turn: 0,
924 is_crewed: false,
925 ignore_legend_rule_flag: false,
926 ability_activated_this_turn: 0,
927 ability_resolved_this_turn: 0,
928 number_turn_activations: ActivationTable::default(),
929 number_game_activations: ActivationTable::default(),
930 number_ability_resolved: ActivationTable::default(),
931 planeswalker_abilities_activated: 0,
932 planeswalker_activation_limit_used: false,
933 chosen_modes_turn: None,
934 enlisted_this_combat: false,
935 activations_this_game: std::collections::BTreeMap::new(),
936 is_renowned: false,
937 zone_timestamp: 0,
938 trait_base_activated_abilities: None,
939 trait_base_triggers: None,
940 trait_base_replacement_effects: None,
941 trait_base_static_abilities: None,
942 trait_base_keywords: None,
943 };
944
945 card.generate_basic_land_mana_abilities();
947 card.generate_keyword_abilities();
948 card.generate_keyword_triggers();
949 crate::card::card_state::update_types(&mut card);
950 crate::card::card_state::update_keywords_cache(&mut card);
951 crate::card::card_state::calculate_perpetual_adjusted_mana_cost(&mut card);
952 card.refresh_action_specs();
953 card.base_ability_count = card.activated_abilities.len();
955 card.base_trigger_count = card.triggers.len();
956 card
957 }
958
959 pub fn clone_for_parity_snapshot(&self) -> Self {
960 let mut out = self.clone();
961 out.abilities.clear();
962 out.activated_abilities.clear();
963 out.triggers.clear();
964 for static_ability in &mut out.static_abilities {
965 *static_ability.base = crate::card_trait_base::CardTraitBase::default();
966 }
967 out.replacement_effects.clear();
968 out.cast_sa = None;
969 out.trait_base_activated_abilities = None;
970 out.trait_base_triggers = None;
971 out.trait_base_replacement_effects = None;
972 out.trait_base_static_abilities = None;
973 out.trait_base_keywords = None;
974 out
975 }
976
977 pub fn from_rules(rules: &CardRules, owner: PlayerId) -> Self {
991 card_factory::build_from_rules(rules, owner)
992 }
993
994 pub fn power(&self) -> i32 {
1002 let base = self
1003 .static_set_power
1004 .unwrap_or(self.base_power.unwrap_or(0));
1005 base + self.static_power_modifier
1006 + self.power_modifier
1007 + self.perpetual_power_modifier
1008 + self.counter_count(&CounterType::P1P1)
1009 - self.counter_count(&CounterType::M1M1)
1010 }
1011
1012 pub fn toughness(&self) -> i32 {
1014 let base = self
1015 .static_set_toughness
1016 .unwrap_or(self.base_toughness.unwrap_or(0));
1017 base + self.static_toughness_modifier
1018 + self.toughness_modifier
1019 + self.perpetual_toughness_modifier
1020 + self.counter_count(&CounterType::P1P1)
1021 - self.counter_count(&CounterType::M1M1)
1022 }
1023
1024 pub fn lethal_damage(&self) -> bool {
1025 self.damage >= self.toughness()
1026 }
1027
1028 pub fn can_be_dealt_damage(&self) -> bool {
1029 self.zone == ZoneType::Battlefield
1030 && (self.is_creature()
1031 || self.type_line.is_planeswalker()
1032 || self.type_line.core_types.contains(&CoreType::Battle))
1033 }
1034
1035 pub fn is_creature(&self) -> bool {
1036 !self.is_bestowed && self.type_line.is_creature()
1037 }
1038
1039 pub fn is_land(&self) -> bool {
1040 self.type_line.is_land()
1041 }
1042
1043 pub fn is_permanent(&self) -> bool {
1044 self.type_line.is_permanent()
1045 }
1046
1047 pub fn update_types(&mut self) {
1049 crate::card::card_state::update_types(self);
1050 }
1051
1052 pub fn update_types_for_view(&mut self) {
1053 crate::card::card_state::update_types_for_view(self);
1054 }
1055
1056 pub fn add_type(&mut self, ty: &str) {
1057 crate::card::card_state::add_type(self, ty);
1058 self.update_types();
1059 self.update_types_for_view();
1060 }
1061
1062 pub fn remove_type(&mut self, ty: &str) {
1063 crate::card::card_state::remove_type(self, ty);
1064 self.update_types();
1065 self.update_types_for_view();
1066 }
1067
1068 pub fn remove_card_types(&mut self) {
1069 crate::card::card_state::remove_card_types(self);
1070 self.update_types();
1071 self.update_types_for_view();
1072 }
1073
1074 pub fn set_type(&mut self, type_line: &str) {
1075 crate::card::card_state::set_type(self, type_line);
1076 self.update_types();
1077 self.update_types_for_view();
1078 }
1079
1080 pub fn set_type_line(&mut self, type_line: CardTypeLine) {
1081 self.type_line = type_line;
1082 self.update_types();
1083 self.update_types_for_view();
1084 }
1085
1086 pub fn add_color(&mut self, color: ColorSet) {
1087 crate::card::card_state::add_color(self, color);
1088 }
1089
1090 pub fn has_intrinsic_keyword(&self, keyword: &str) -> bool {
1091 crate::card::card_state::has_intrinsic_keyword(self, keyword)
1092 }
1093
1094 pub fn add_intrinsic_keyword(&mut self, keyword: &str) -> bool {
1095 let changed = crate::card::card_state::add_intrinsic_keyword(self, keyword);
1096 if changed {
1097 crate::card::card_state::update_keywords_cache(self);
1098 }
1099 changed
1100 }
1101
1102 pub fn add_intrinsic_keywords<'a>(
1103 &mut self,
1104 keywords: impl IntoIterator<Item = &'a str>,
1105 ) -> bool {
1106 let changed = crate::card::card_state::add_intrinsic_keywords(self, keywords);
1107 if changed {
1108 crate::card::card_state::update_keywords_cache(self);
1109 }
1110 changed
1111 }
1112
1113 pub fn remove_intrinsic_keyword(&mut self, keyword: &str) -> bool {
1114 let changed = crate::card::card_state::remove_intrinsic_keyword(self, keyword);
1115 if changed {
1116 crate::card::card_state::update_keywords_cache(self);
1117 }
1118 changed
1119 }
1120
1121 pub fn has_spell_ability(&self, sa: &SpellAbility) -> bool {
1122 crate::card::card_state::has_spell_ability(self, sa)
1123 }
1124
1125 pub fn add_spell_ability(&mut self, sa: &SpellAbility) -> bool {
1126 let added = crate::card::card_state::add_spell_ability(self, sa);
1127 self.refresh_action_specs();
1128 added
1129 }
1130
1131 pub fn has_trigger(&self, trigger_id: u32) -> bool {
1132 crate::card::card_state::has_trigger(self, trigger_id)
1133 }
1134
1135 pub fn add_trigger(&mut self, trig: Trigger) -> bool {
1136 crate::card::card_state::add_trigger(self, trig)
1137 }
1138
1139 pub fn clear_pump_triggers(&mut self) {
1140 let count = self.pump_trigger_count;
1141 if count == 0 {
1142 return;
1143 }
1144 let new_len = self
1145 .triggers
1146 .len()
1147 .saturating_sub(count)
1148 .max(self.base_trigger_count);
1149 self.triggers.truncate(new_len);
1150 self.pump_trigger_count = 0;
1151 }
1152
1153 pub fn copiable_triggers(&self) -> Vec<Trigger> {
1154 self.trait_base_triggers
1155 .clone()
1156 .unwrap_or_else(|| self.triggers.clone())
1157 }
1158
1159 pub fn copiable_replacement_effects(&self) -> Vec<ReplacementEffect> {
1160 self.trait_base_replacement_effects
1161 .clone()
1162 .unwrap_or_else(|| self.replacement_effects.clone())
1163 }
1164
1165 pub fn add_static_ability(&mut self, st_ab: StaticAbility) -> bool {
1166 crate::card::card_state::add_static_ability(self, st_ab)
1167 }
1168
1169 pub fn remove_static_ability(&mut self, mode: crate::staticability::StaticMode) -> bool {
1170 crate::card::card_state::remove_static_ability(self, mode)
1171 }
1172
1173 pub fn add_replacement_effect(&mut self, re: ReplacementEffect) -> bool {
1174 crate::card::card_state::add_replacement_effect(self, re)
1175 }
1176
1177 pub fn has_replacement_effect(&self) -> bool {
1178 crate::card::card_state::has_replacement_effect(self)
1179 }
1180
1181 pub fn has_s_var(&self, key: &str) -> bool {
1182 crate::card::card_state::has_s_var(self, key)
1183 }
1184
1185 pub fn get_s_var(&self, key: &str) -> Option<&str> {
1186 self.svars
1187 .get(key)
1188 .or_else(|| self.granted_svars.get(key))
1189 .map(String::as_str)
1190 }
1191
1192 pub fn parsed_s_var(&mut self, key: &str) -> Option<ParsedSVar> {
1193 let raw = self.get_s_var(key)?.to_string();
1194 Some(self.parsed_svar_cache.get_or_parse(key, &raw).clone())
1195 }
1196
1197 pub fn remove_s_var(&mut self, key: &str) {
1198 crate::card::card_state::remove_s_var(self, key);
1199 self.parsed_svar_cache.remove(key);
1200 self.refresh_action_specs_after_svar_change();
1201 }
1202
1203 pub fn set_s_var(&mut self, key: impl Into<String>, value: impl Into<String>) {
1204 let key = key.into();
1205 self.parsed_svar_cache.remove(&key);
1206 self.svars.insert(key, value.into());
1207 self.refresh_action_specs_after_svar_change();
1208 }
1209
1210 pub fn set_s_var_if_absent(&mut self, key: impl Into<String>, value: impl Into<String>) {
1211 let key = key.into();
1212 if self.svars.contains_key(&key) {
1213 return;
1214 }
1215 self.svars.insert(key, value.into());
1216 self.refresh_action_specs_after_svar_change();
1217 }
1218
1219 pub fn set_svars_map(&mut self, svars: BTreeMap<String, String>) {
1220 self.svars = svars;
1221 self.parsed_svar_cache.clear();
1222 self.refresh_action_specs();
1223 }
1224
1225 pub fn copy_from_card_state(&mut self, source: &Card) {
1226 crate::card::card_state::copy_from(source, self);
1227 }
1228
1229 pub fn copy_from(&mut self, source: &Card) {
1230 self.copy_from_card_state(source);
1231 }
1232
1233 pub fn add_abilities_from(&mut self, source: &Card) {
1234 crate::card::card_state::add_abilities_from(source, self);
1235 }
1236
1237 pub fn has_property(&self, property: &str) -> bool {
1238 crate::card::card_state::has_property(self, property)
1239 }
1240
1241 pub fn reset_original_host(&mut self) {
1242 crate::card::card_state::reset_original_host(self);
1243 }
1244
1245 pub fn update_changed_text(&mut self) {
1246 crate::card::card_state::update_changed_text(self);
1247 }
1248
1249 pub fn update_keywords_cache(&mut self) {
1250 crate::card::card_state::update_keywords_cache(self);
1251 }
1252
1253 pub fn change_text_intrinsic(&mut self) {
1254 crate::card::card_state::change_text_intrinsic(self);
1255 }
1256
1257 pub fn has_chapter(&self) -> bool {
1258 crate::card::card_state::has_chapter(self)
1259 }
1260
1261 pub fn get_final_chapter_nr(&self) -> i32 {
1262 crate::card::card_state::get_final_chapter_nr(self)
1263 }
1264
1265 pub fn sunburst_count(&self) -> i32 {
1269 use forge_foundation::mana::ManaAtom;
1270 let mut count = 0;
1271 for &bit in &[
1272 ManaAtom::WHITE,
1273 ManaAtom::BLUE,
1274 ManaAtom::BLACK,
1275 ManaAtom::RED,
1276 ManaAtom::GREEN,
1277 ] {
1278 if (self.colors_spent_to_cast & bit) != 0 {
1279 count += 1;
1280 }
1281 }
1282 count
1283 }
1284
1285 pub fn has_keyword(&self, kw: &str) -> bool {
1286 crate::card::card_state::has_keyword(self, kw)
1287 }
1288
1289 pub fn has_keyword_enum(&self, kw: Kw) -> bool {
1294 if self
1295 .cant_have_keywords
1296 .contains(&kw.display_name().to_ascii_lowercase())
1297 {
1298 return false;
1299 }
1300 self.keywords.contains_keyword(kw)
1301 || self.granted_keywords.contains_keyword(kw)
1302 || self.pump_keywords.contains_keyword(kw)
1303 }
1304
1305 pub fn has_haste(&self) -> bool {
1306 self.has_keyword_enum(crate::keyword::keyword_instance::Keyword::Haste)
1307 }
1308
1309 pub fn has_flying(&self) -> bool {
1310 self.has_keyword_enum(crate::keyword::keyword_instance::Keyword::Flying)
1311 }
1312
1313 pub fn has_reach(&self) -> bool {
1314 self.has_keyword_enum(Kw::Reach)
1315 }
1316
1317 pub fn has_first_strike(&self) -> bool {
1318 self.has_keyword_enum(Kw::FirstStrike)
1319 }
1320
1321 pub fn has_double_strike(&self) -> bool {
1322 self.has_keyword_enum(Kw::DoubleStrike)
1323 }
1324
1325 pub fn has_trample(&self) -> bool {
1326 self.has_keyword_enum(Kw::Trample)
1327 }
1328
1329 pub fn has_deathtouch(&self) -> bool {
1330 self.has_keyword_enum(Kw::Deathtouch)
1331 }
1332
1333 pub fn has_lifelink(&self) -> bool {
1334 self.has_keyword_enum(Kw::Lifelink)
1335 }
1336
1337 pub fn has_vigilance(&self) -> bool {
1338 self.has_keyword_enum(Kw::Vigilance)
1339 }
1340
1341 pub fn has_defender(&self) -> bool {
1342 self.has_keyword_enum(Kw::Defender)
1343 }
1344
1345 pub fn has_hexproof(&self) -> bool {
1346 self.has_keyword_enum(Kw::Hexproof)
1347 }
1348
1349 pub fn has_shroud(&self) -> bool {
1350 self.has_keyword_enum(Kw::Shroud)
1351 }
1352
1353 pub fn has_menace(&self) -> bool {
1354 self.has_keyword_enum(Kw::Menace)
1355 }
1356
1357 pub fn has_fear(&self) -> bool {
1358 self.has_keyword_enum(Kw::Fear)
1359 }
1360
1361 pub fn has_intimidate(&self) -> bool {
1362 self.has_keyword_enum(Kw::Intimidate)
1363 }
1364
1365 pub fn has_shadow(&self) -> bool {
1366 self.has_keyword_enum(Kw::Shadow)
1367 }
1368
1369 pub fn has_skulk(&self) -> bool {
1370 self.has_keyword_enum(Kw::Skulk)
1371 }
1372
1373 pub fn has_horsemanship(&self) -> bool {
1374 self.has_keyword_enum(Kw::Horsemanship)
1375 }
1376
1377 pub fn has_indestructible(&self) -> bool {
1378 self.has_keyword_enum(Kw::Indestructible)
1379 }
1380
1381 pub fn has_infect(&self) -> bool {
1382 self.has_keyword_enum(Kw::Infect)
1383 }
1384
1385 pub fn has_wither(&self) -> bool {
1386 self.has_keyword_enum(Kw::Wither)
1387 }
1388
1389 pub fn has_prowess(&self) -> bool {
1390 self.has_keyword_enum(Kw::Prowess)
1391 }
1392
1393 pub fn has_rebound(&self) -> bool {
1394 self.has_keyword_enum(Kw::Rebound)
1395 }
1396
1397 pub fn has_hexproof_from(&self, color: &str) -> bool {
1399 let target = format!("Hexproof from {color}");
1400 self.keywords.contains_string_ignore_case(&target)
1401 || self.granted_keywords.contains_string_ignore_case(&target)
1402 }
1403
1404 pub fn get_toxic_count(&self) -> Option<i32> {
1406 self.get_keyword_cost("Toxic").and_then(|s| s.parse().ok())
1407 }
1408
1409 pub fn has_storm(&self) -> bool {
1411 self.has_keyword_enum(Kw::Storm)
1412 }
1413
1414 pub fn has_cascade(&self) -> bool {
1415 self.has_keyword_enum(Kw::Cascade)
1416 }
1417
1418 pub fn mana_value(&self) -> i32 {
1420 self.mana_cost.cmc()
1421 }
1422
1423 pub fn has_protection_from(&self, quality: &str) -> bool {
1425 let target = format!("Protection from {quality}");
1426 self.keywords.contains_string_ignore_case(&target)
1427 || self.granted_keywords.contains_string_ignore_case(&target)
1428 }
1429
1430 pub fn get_protections(&self) -> Vec<String> {
1432 let mut prots = Vec::new();
1433 for kw in self
1434 .keywords
1435 .iter_strings()
1436 .chain(self.granted_keywords.iter_strings())
1437 {
1438 if let Some(from) = kw.strip_prefix("Protection from ") {
1439 prots.push(from.to_lowercase());
1440 }
1441 }
1442 prots
1443 }
1444
1445 pub fn is_protected_from(&self, source: &Card) -> bool {
1449 for prot in self.get_protections() {
1450 match prot.as_str() {
1451 "white" => {
1452 if source.color.has_white() {
1453 return true;
1454 }
1455 }
1456 "blue" => {
1457 if source.color.has_blue() {
1458 return true;
1459 }
1460 }
1461 "black" => {
1462 if source.color.has_black() {
1463 return true;
1464 }
1465 }
1466 "red" => {
1467 if source.color.has_red() {
1468 return true;
1469 }
1470 }
1471 "green" => {
1472 if source.color.has_green() {
1473 return true;
1474 }
1475 }
1476 "colorless" => {
1477 if source.color.is_colorless() {
1478 return true;
1479 }
1480 }
1481 "artifacts" => {
1482 if source.type_line.is_artifact() {
1483 return true;
1484 }
1485 }
1486 "creatures" => {
1487 if source.type_line.is_creature() {
1488 return true;
1489 }
1490 }
1491 "enchantments" => {
1492 if source.type_line.is_enchantment() {
1493 return true;
1494 }
1495 }
1496 _ => {}
1497 }
1498 }
1499 false
1500 }
1501
1502 pub fn can_attack(&self) -> bool {
1503 self.is_creature()
1504 && !self.tapped
1505 && !self.has_defender()
1506 && !self.cant_attack_static
1507 && !self.detained
1508 && (self.has_haste() || !self.summoning_sick)
1509 && self.zone == ZoneType::Battlefield
1510 }
1511
1512 pub fn can_block(&self) -> bool {
1513 self.is_creature()
1514 && !self.tapped
1515 && !self.cant_block_static
1516 && !self.detained
1517 && self.zone == ZoneType::Battlefield
1518 }
1519
1520 pub fn can_be_controlled_by(&self, player: PlayerId) -> bool {
1523 if player == self.controller {
1524 return true;
1525 }
1526 !self.has_keyword("Other players can't gain control of CARDNAME.")
1527 }
1528
1529 pub fn counter_count(&self, ct: &CounterType) -> i32 {
1530 *self.counters.get(ct).unwrap_or(&0)
1531 }
1532
1533 pub fn add_counter(&mut self, ct: &CounterType, count: i32) {
1534 let entry = self.counters.entry(ct.clone()).or_insert(0);
1535 *entry += count;
1536 }
1537
1538 pub fn remove_counter(&mut self, ct: &CounterType, count: i32) {
1539 let entry = self.counters.entry(ct.clone()).or_insert(0);
1540 *entry = (*entry - count).max(0);
1541 }
1542
1543 pub fn enter_battlefield(&mut self) {
1545 self.tapped = false;
1546 self.damage = 0;
1547 self.summoning_sick = true;
1548 self.came_under_control_since_last_upkeep = true;
1549 self.has_deathtouch_damage = false;
1550 self.entered_battlefield_this_turn = true;
1551 self.attacked_this_turn = false;
1552 self.damage_sources_this_turn.clear();
1553 }
1554
1555 pub fn clear_global_turn_state(&mut self) {
1557 self.entered_battlefield_this_turn = false;
1558 self.attacked_this_turn = false;
1559 self.attached_this_turn = false;
1560 self.has_deathtouch_damage = false;
1561 self.damage_sources_this_turn.clear();
1562 self.total_damage_done_this_turn = 0;
1563 }
1564
1565 pub fn new_turn(&mut self) {
1567 self.clear_global_turn_state();
1568 if self.zone == ZoneType::Battlefield {
1569 if let Ok(filter) = std::env::var("FORGE_CARD_TRACE") {
1570 if !filter.is_empty()
1571 && self.card_name.eq_ignore_ascii_case(&filter)
1572 && self.summoning_sick
1573 {
1574 eprintln!(
1575 "[card-trace] new_turn clears sickness on {}#{:?} (controller={:?})",
1576 self.card_name, self.id, self.controller,
1577 );
1578 }
1579 }
1580 self.summoning_sick = false;
1581 }
1582 }
1583
1584 pub fn add_remembered_card(&mut self, card_id: CardId) {
1586 if !self.remembered_cards.contains(&card_id) {
1587 self.remembered_cards.push(card_id);
1588 }
1589 }
1590
1591 pub fn add_remembered_cmc(&mut self, cmc: i32) {
1593 self.remembered_cmc.push(cmc);
1594 }
1595
1596 pub fn add_remembered_player(&mut self, player: PlayerId) {
1597 if !self.remembered_players.contains(&player) {
1598 self.remembered_players.push(player);
1599 }
1600 }
1601
1602 pub fn add_remembered_players<I>(&mut self, players: I)
1603 where
1604 I: IntoIterator<Item = PlayerId>,
1605 {
1606 for p in players {
1607 self.add_remembered_player(p);
1608 }
1609 }
1610
1611 pub fn has_remembered(&self) -> bool {
1612 !self.remembered_cards.is_empty()
1613 || !self.remembered_players.is_empty()
1614 || !self.remembered_cmc.is_empty()
1615 }
1616
1617 pub fn add_remembered(&mut self, card_id: CardId) {
1618 self.add_remembered_card(card_id);
1619 }
1620
1621 pub fn remove_remembered(&mut self, card_id: CardId) {
1622 self.remembered_cards.retain(|&c| c != card_id);
1623 }
1624
1625 pub fn clear_remembered(&mut self) {
1626 self.remembered_cards.clear();
1627 self.remembered_players.clear();
1628 self.remembered_cmc.clear();
1629 }
1630
1631 pub fn update_remembered(&mut self) {
1632 let mut seen = HashSet::new();
1633 self.remembered_cards.retain(|c| seen.insert(*c));
1634 }
1635
1636 pub fn has_imprinted_card(&self) -> bool {
1637 !self.imprinted_cards.is_empty()
1638 }
1639
1640 pub fn add_imprinted_card(&mut self, card_id: CardId) {
1641 if !self.imprinted_cards.contains(&card_id) {
1642 self.imprinted_cards.push(card_id);
1643 }
1644 }
1645
1646 pub fn add_imprinted_cards(&mut self, cards: impl IntoIterator<Item = CardId>) {
1647 for c in cards {
1648 self.add_imprinted_card(c);
1649 }
1650 }
1651
1652 pub fn remove_imprinted_card(&mut self, card_id: CardId) {
1653 self.imprinted_cards.retain(|&c| c != card_id);
1654 }
1655
1656 pub fn remove_imprinted_cards(&mut self, cards: impl IntoIterator<Item = CardId>) {
1657 for c in cards {
1658 self.remove_imprinted_card(c);
1659 }
1660 }
1661
1662 pub fn clear_imprinted_cards(&mut self) {
1663 self.imprinted_cards.clear();
1664 }
1665
1666 pub fn add_to_chosen_map(&mut self, player: PlayerId, chosen: Vec<CardId>) {
1667 self.chosen_map.insert(player, chosen);
1668 }
1669
1670 pub fn add_gain_control_target(&mut self, card_id: CardId) {
1671 if !self.gain_control_targets.contains(&card_id) {
1672 self.gain_control_targets.push(card_id);
1673 }
1674 }
1675
1676 pub fn remove_gain_control_targets(&mut self, card_id: CardId) {
1677 self.gain_control_targets.retain(|&c| c != card_id);
1678 }
1679
1680 pub fn has_gain_control_target(&self) -> bool {
1681 !self.gain_control_targets.is_empty()
1682 }
1683
1684 pub fn add_until_leaves_battlefield(&mut self, card_id: CardId) {
1685 if !self.until_leaves_battlefield.contains(&card_id) {
1686 self.until_leaves_battlefield.push(card_id);
1687 }
1688 }
1689
1690 pub fn remove_until_leaves_battlefield(&mut self, card_id: CardId) {
1691 self.until_leaves_battlefield.retain(|&c| c != card_id);
1692 }
1693
1694 pub fn clear_until_leaves_battlefield(&mut self) {
1695 self.until_leaves_battlefield.clear();
1696 }
1697
1698 pub fn has_exiled_card(&self) -> bool {
1699 !self.exiled_cards.is_empty()
1700 }
1701
1702 pub fn add_exiled_card(&mut self, card_id: CardId) {
1703 if !self.exiled_cards.contains(&card_id) {
1704 self.exiled_cards.push(card_id);
1705 }
1706 }
1707
1708 pub fn add_exiled_cards(&mut self, cards: impl IntoIterator<Item = CardId>) {
1709 for c in cards {
1710 self.add_exiled_card(c);
1711 }
1712 }
1713
1714 pub fn remove_exiled_card(&mut self, card_id: CardId) {
1715 self.exiled_cards.retain(|&c| c != card_id);
1716 }
1717
1718 pub fn remove_exiled_cards(&mut self, cards: impl IntoIterator<Item = CardId>) {
1719 for c in cards {
1720 self.remove_exiled_card(c);
1721 }
1722 }
1723
1724 pub fn clear_exiled_cards(&mut self) {
1725 self.exiled_cards.clear();
1726 }
1727
1728 pub fn add_haunted_by(&mut self, card_id: CardId) {
1729 if !self.haunted_by.contains(&card_id) {
1730 self.haunted_by.push(card_id);
1731 }
1732 }
1733
1734 pub fn remove_haunted_by(&mut self, card_id: CardId) {
1735 self.haunted_by.retain(|&c| c != card_id);
1736 }
1737
1738 pub fn has_encoded_card(&self) -> bool {
1739 !self.encoded_cards.is_empty()
1740 }
1741
1742 pub fn add_encoded_card(&mut self, card_id: CardId) {
1743 if !self.encoded_cards.contains(&card_id) {
1744 self.encoded_cards.push(card_id);
1745 }
1746 }
1747
1748 pub fn add_encoded_cards(&mut self, cards: impl IntoIterator<Item = CardId>) {
1749 for c in cards {
1750 self.add_encoded_card(c);
1751 }
1752 }
1753
1754 pub fn remove_encoded_card(&mut self, card_id: CardId) {
1755 self.encoded_cards.retain(|&c| c != card_id);
1756 }
1757
1758 pub fn clear_encoded_cards(&mut self) {
1759 self.encoded_cards.clear();
1760 }
1761
1762 pub fn has_merged_card(&self) -> bool {
1763 !self.melded_with.is_empty()
1764 }
1765
1766 pub fn add_merged_card(&mut self, card_id: CardId) {
1767 if !self.melded_with.contains(&card_id) {
1768 self.melded_with.push(card_id);
1769 }
1770 }
1771
1772 pub fn add_merged_card_to_top(&mut self, card_id: CardId) {
1773 if !self.melded_with.contains(&card_id) {
1774 self.melded_with.insert(0, card_id);
1775 }
1776 }
1777
1778 pub fn remove_merged_card(&mut self, card_id: CardId) {
1779 self.melded_with.retain(|&c| c != card_id);
1780 }
1781
1782 pub fn clear_merged_cards(&mut self) {
1783 self.melded_with.clear();
1784 }
1785
1786 pub fn remove_mutated_states(&mut self) {
1787 self.clear_merged_cards();
1788 }
1789
1790 pub fn rebuild_mutated_states(&mut self) {
1791 let mut seen = HashSet::new();
1792 self.melded_with.retain(|c| seen.insert(*c));
1793 }
1794
1795 pub fn move_merged_to_subgame(&mut self) {
1796 self.clear_merged_cards();
1797 }
1798
1799 pub fn entered_this_turn(&self) -> bool {
1800 self.entered_battlefield_this_turn
1801 }
1802
1803 pub fn entered_current_zone_this_turn(&self, turn_number: u32) -> bool {
1804 self.turn_in_zone == turn_number
1805 }
1806
1807 pub fn calculate_perpetual_adjusted_mana_cost(&mut self) {
1808 crate::card::card_state::calculate_perpetual_adjusted_mana_cost(self);
1809 }
1810
1811 pub fn has_chosen_player(&self) -> bool {
1812 self.chosen_player.is_some()
1813 }
1814
1815 pub fn reveal_chosen_player(&mut self) {
1816 self.chosen_player_revealed = true;
1817 }
1818
1819 pub fn has_promised_gift(&self) -> bool {
1820 self.promised_gift.is_some()
1821 }
1822
1823 pub fn has_chosen_number(&self) -> bool {
1824 self.chosen_number.is_some()
1825 }
1826
1827 pub fn clear_chosen_number(&mut self) {
1828 self.chosen_number = None;
1829 }
1830
1831 pub fn has_chosen_type(&self) -> bool {
1832 self.chosen_type
1833 .as_ref()
1834 .map(|s| !s.is_empty())
1835 .unwrap_or(false)
1836 }
1837
1838 pub fn reveal_chosen_type(&mut self) {
1839 self.chosen_type_revealed = true;
1840 }
1841
1842 pub fn has_chosen_type2(&self) -> bool {
1843 self.chosen_type2
1844 .as_ref()
1845 .map(|s| !s.is_empty())
1846 .unwrap_or(false)
1847 }
1848
1849 pub fn has_any_noted_type(&self) -> bool {
1850 !self.noted_types.is_empty()
1851 }
1852
1853 pub fn add_noted_type(&mut self, ty: &str) {
1854 self.noted_types.push(ty.to_string());
1855 }
1856
1857 pub fn has_chosen_color(&self) -> bool {
1858 !self.chosen_colors.is_empty()
1859 }
1860
1861 pub fn has_chosen_card(&self) -> bool {
1862 !self.chosen_cards.is_empty()
1863 }
1864
1865 pub fn assign_sector(&mut self, sector: &str) {
1866 self.sector = Some(sector.to_string());
1867 }
1868
1869 pub fn has_attraction_light(&self, light: i32) -> bool {
1870 light > 0 && self.attraction_lights.contains(&(light as u32))
1871 }
1872
1873 pub fn has_sector(&self) -> bool {
1874 self.sector.is_some()
1875 }
1876
1877 pub fn handle_changed_controller_sprocket_reset(&mut self) {
1878 if self.sprocket != 0 {
1879 self.sprocket = -1;
1880 }
1881 }
1882
1883 pub fn add_named_card(&mut self, name: &str) {
1884 self.named_cards.push(name.to_string());
1885 }
1886
1887 pub fn has_named_card(&self) -> bool {
1888 !self.named_cards.is_empty()
1889 }
1890
1891 pub fn has_chosen_even_odd(&self) -> bool {
1892 self.chosen_even_odd.is_some()
1893 }
1894
1895 pub fn has_no_abilities(&self) -> bool {
1896 self.abilities.is_empty()
1897 && self.activated_abilities.is_empty()
1898 && self.triggers.is_empty()
1899 && self.static_abilities.is_empty()
1900 && self.replacement_effects.is_empty()
1901 }
1902
1903 pub fn can_tap(&self) -> bool {
1904 !self.tapped
1905 }
1906
1907 pub fn tap(&mut self) -> bool {
1908 if !self.can_tap() {
1909 return false;
1910 }
1911 self.tapped = true;
1912 true
1913 }
1914
1915 pub fn set_tapped(&mut self, tapped: bool) {
1916 if tapped {
1917 self.tap();
1918 } else {
1919 self.untap();
1920 }
1921 }
1922
1923 pub fn set_owner(&mut self, owner: PlayerId) {
1924 self.owner = owner;
1925 }
1926
1927 pub fn set_controller(&mut self, controller: PlayerId) {
1928 if self.controller != controller {
1929 self.came_under_control_since_last_upkeep = true;
1930 }
1931 self.controller = controller;
1932 }
1933
1934 pub fn set_is_token(&mut self, is_token: bool) {
1935 self.is_token = is_token;
1936 }
1937
1938 pub fn set_effect_source(&mut self, source: Option<CardId>) {
1939 self.effect_source = source;
1940 }
1941
1942 pub fn set_temp_effect_host(&mut self, host: Option<CardId>) {
1943 self.temp_effect_host = host;
1944 }
1945
1946 pub fn set_temp_effect_until_eot(&mut self, until_eot: bool) {
1947 self.temp_effect_until_eot = until_eot;
1948 }
1949
1950 pub fn set_forget_on_moved_origin(&mut self, zone: Option<ZoneType>) {
1951 self.forget_on_moved_origin = zone;
1952 }
1953
1954 pub fn set_exile_when_no_remembered(&mut self, exile: bool) {
1955 self.exile_when_no_remembered = exile;
1956 }
1957
1958 pub fn set_flipped(&mut self, flipped: bool) {
1959 self.flipped = flipped;
1960 }
1961
1962 pub fn can_untap(&self) -> bool {
1963 self.tapped
1964 }
1965
1966 pub fn untap(&mut self) -> bool {
1967 if !self.can_untap() {
1968 return false;
1969 }
1970 self.tapped = false;
1971 true
1972 }
1973
1974 pub fn exert(&mut self) {
1975 self.exerted = true;
1976 }
1977
1978 pub fn clear_exerted(&mut self) {
1979 self.exerted = false;
1980 }
1981
1982 pub fn remove_exerted_by(&mut self, _player: PlayerId) {
1983 self.exerted = false;
1984 }
1985
1986 pub fn detain(&mut self) {
1987 self.detained = true;
1988 }
1989
1990 pub fn add_goad(&mut self, player: PlayerId) {
1991 self.goaded_by = Some(player);
1992 }
1993
1994 pub fn remove_goad(&mut self, player: PlayerId) {
1995 if self.goaded_by == Some(player) {
1996 self.goaded_by = None;
1997 }
1998 }
1999
2000 pub fn un_goad(&mut self) {
2001 self.goaded_by = None;
2002 }
2003
2004 pub fn remove_detained_by(&mut self, _player: PlayerId) {
2005 self.detained = false;
2006 }
2007
2008 pub fn update_ability_text_for_view(&mut self) {
2009 self.update_spell_abilities();
2010 }
2011 pub fn update_non_ability_text_for_view(&mut self) {
2012 self.update_changed_text();
2013 }
2014 pub fn update_mana_cost_for_view(&mut self) {
2015 let _ = self.mana_value();
2016 }
2017 pub fn update_p_tfor_view(&mut self) {
2018 let _ = (self.power(), self.toughness());
2019 }
2020 pub fn update_color_for_view(&mut self) {
2021 let _ = self.color;
2022 }
2023 pub fn update_attacking_for_view(&mut self) {
2024 let _ = self.attacking_player;
2025 }
2026 pub fn update_blocking_for_view(&mut self) {
2027 let _ = self.must_block;
2028 }
2029 pub fn update_state_for_view(&mut self) {
2030 let _ = (self.zone, self.tapped, self.face_down);
2031 }
2032 pub fn update_namefor_view(&mut self) {
2033 self.card_name = self.card_name.trim().to_string();
2034 }
2035 pub fn update_token_view(&mut self) {
2036 let _ = self.is_token;
2037 }
2038 pub fn update_was_destroyed(&mut self) {
2039 let _ = self.damage >= self.toughness();
2040 }
2041 pub fn update_rules_view(&mut self) {
2042 let _ = (&self.abilities, &self.keywords);
2043 }
2044 pub fn update_commander_view(&mut self) {
2045 let _ = self.is_commander;
2046 }
2047 pub fn update_card(&mut self) {
2048 self.update_namefor_view();
2049 self.update_types_for_view();
2050 self.update_color_for_view();
2051 self.update_mana_cost_for_view();
2052 self.update_p_tfor_view();
2053 self.update_rules_view();
2054 self.update_state_for_view();
2055 }
2056 pub fn dangerously_set_game(&mut self) {
2057 self.update_card();
2058 }
2059 pub fn visit(&mut self) {
2060 self.update_card();
2061 }
2062
2063 pub fn has_state(&self) -> bool {
2064 self.is_transformed || self.other_part.is_some()
2065 }
2066
2067 pub fn is_double_faced(&self) -> bool {
2070 self.other_part.is_some()
2071 }
2072
2073 pub fn change_to_state(&mut self) {
2074 self.transform();
2075 }
2076
2077 pub fn add_alternate_state(&mut self, other: CardOtherPart) {
2078 self.other_part = Some(other);
2079 }
2080
2081 pub fn clear_states(&mut self) {
2082 self.other_part = None;
2083 self.is_transformed = false;
2084 }
2085
2086 pub fn change_card_state(&mut self) {
2087 self.transform();
2088 }
2089
2090 pub fn has_alternate_state(&self) -> bool {
2091 self.other_part.is_some()
2092 }
2093
2094 pub fn manifest(&mut self) {
2095 self.manifested = true;
2096 self.turn_face_down();
2097 }
2098
2099 pub fn cloak(&mut self) {
2100 self.cloaked = true;
2101 self.turn_face_down();
2102 }
2103
2104 pub fn turn_face_down(&mut self) {
2105 self.face_down = true;
2106 }
2107
2108 pub fn turn_face_down_no_update(&mut self) {
2109 self.face_down = true;
2110 }
2111
2112 pub fn can_be_turned_face_up(&self) -> bool {
2113 self.face_down
2114 }
2115
2116 pub fn force_turn_face_up(&mut self) {
2117 self.face_down = false;
2118 }
2119
2120 pub fn turn_face_up(&mut self) {
2121 self.face_down = false;
2122 }
2123
2124 pub fn set_face_down(&mut self, face_down: bool) {
2125 if face_down {
2126 self.turn_face_down();
2127 } else {
2128 self.turn_face_up();
2129 }
2130 }
2131
2132 pub fn set_manifested(&mut self, manifested: bool) {
2133 self.manifested = manifested;
2134 }
2135
2136 pub fn set_cloaked(&mut self, cloaked: bool) {
2137 self.cloaked = cloaked;
2138 }
2139
2140 pub fn set_discarded(&mut self, discarded: bool) {
2141 self.discarded = discarded;
2142 }
2143
2144 pub fn set_unearthed(&mut self, unearthed: bool) {
2145 self.unearthed = unearthed;
2146 }
2147
2148 pub fn set_summoning_sick(&mut self, summoning_sick: bool) {
2149 self.summoning_sick = summoning_sick;
2150 }
2151
2152 pub fn set_foretold(&mut self, foretold: bool) {
2153 self.foretold = foretold;
2154 }
2155
2156 pub fn set_foretold_cost_by_effect(&mut self, by_effect: bool) {
2157 self.foretold_cost_by_effect = by_effect;
2158 }
2159
2160 pub fn set_transformed(&mut self, transformed: bool) {
2161 self.is_transformed = transformed;
2162 }
2163
2164 pub fn set_attacking_player(&mut self, player: PlayerId) {
2165 self.attacking_player = Some(player);
2166 }
2167
2168 pub fn clear_attacking_player(&mut self) {
2169 self.attacking_player = None;
2170 }
2171
2172 pub fn mark_attacked_this_turn(&mut self) {
2173 self.attacked_this_turn = true;
2174 }
2175
2176 pub fn add_etb_counters_p1p1(&mut self, amount: i32) {
2177 self.etb_counters_p1p1 += amount;
2178 }
2179
2180 pub fn increment_commander_cast_count(&mut self) {
2181 self.commander_cast_count += 1;
2182 }
2183
2184 pub fn set_kicked(&mut self, kicked: bool) {
2185 self.kicked = kicked;
2186 }
2187
2188 pub fn mark_enlisted_this_combat(&mut self) {
2189 self.enlisted_this_combat = true;
2190 }
2191
2192 pub fn add_enlisted_power(&mut self, amount: i32) {
2193 self.power_modifier += amount;
2194 }
2195
2196 pub fn add_damage_source_this_turn(&mut self, source: CardId) {
2197 self.damage_sources_this_turn.push(source);
2198 }
2199
2200 pub fn mark_deathtouch_damage(&mut self) {
2201 self.has_deathtouch_damage = true;
2202 }
2203
2204 pub fn clear_deathtouch_damage(&mut self) {
2205 self.has_deathtouch_damage = false;
2206 }
2207
2208 pub fn clear_damage(&mut self) {
2209 self.damage = 0;
2210 }
2211
2212 pub fn reset_turn_modifiers(&mut self) {
2213 self.power_modifier = 0;
2214 self.toughness_modifier = 0;
2215 }
2216
2217 pub fn reset_regeneration_shields(&mut self) {
2218 self.regeneration_shields = 0;
2219 }
2220
2221 pub fn clear_original_controller_eot(&mut self) {
2222 self.original_controller_eot = None;
2223 }
2224
2225 pub fn set_chosen_modes(&mut self, modes: Vec<usize>) {
2226 self.chosen_modes = Some(modes);
2227 }
2228
2229 pub fn set_chosen_cards(&mut self, cards: Vec<CardId>) {
2230 self.chosen_cards = cards;
2231 }
2232
2233 pub fn set_chosen_number(&mut self, number: Option<i32>) {
2234 self.chosen_number = number;
2235 }
2236
2237 pub fn set_chosen_player(
2238 &mut self,
2239 player: Option<PlayerId>,
2240 chooser: Option<PlayerId>,
2241 revealed: bool,
2242 ) {
2243 self.chosen_player = player;
2244 self.chosen_player_controller = chooser;
2245 self.chosen_player_revealed = revealed;
2246 }
2247
2248 pub fn set_chosen_type(
2249 &mut self,
2250 chosen_type: Option<String>,
2251 chooser: Option<PlayerId>,
2252 revealed: bool,
2253 ) {
2254 self.chosen_type = chosen_type;
2255 self.chosen_type_controller = chooser;
2256 self.chosen_type_revealed = revealed;
2257 }
2258
2259 pub fn set_strive_extra_targets(&mut self, value: u32) {
2260 self.strive_extra_targets = value;
2261 }
2262
2263 pub fn set_colors_spent_to_cast(&mut self, colors: u16) {
2264 self.colors_spent_to_cast = colors;
2265 }
2266
2267 pub fn set_paying_mana_to_cast(&mut self, paying_mana: Vec<u16>) {
2268 self.paying_mana_to_cast = paying_mana;
2269 }
2270
2271 pub fn set_promised_gift(&mut self, player: Option<PlayerId>) {
2272 self.promised_gift = player;
2273 }
2274
2275 pub fn set_lki_power_toughness(&mut self, power: Option<i32>, toughness: Option<i32>) {
2276 self.lki_power = power;
2277 self.lki_toughness = toughness;
2278 }
2279
2280 pub fn restore_animate_snapshot(
2281 &mut self,
2282 type_line: CardTypeLine,
2283 base_power: Option<i32>,
2284 base_toughness: Option<i32>,
2285 color: ColorSet,
2286 ) {
2287 self.set_type_line(type_line);
2288 self.base_power = base_power;
2289 self.base_toughness = base_toughness;
2290 self.color = color;
2291 }
2292
2293 pub fn capture_clone_state(&self) -> CloneState {
2294 CloneState {
2295 expires_at_cleanup: false,
2296 original_card_name: self.card_name.clone(),
2297 original_oracle_text: self.oracle_text.clone(),
2298 original_type_line: self.type_line.clone(),
2299 original_mana_cost: self.mana_cost.clone(),
2300 original_color: self.color,
2301 original_base_power: self.base_power,
2302 original_base_toughness: self.base_toughness,
2303 original_keywords: self.keywords.clone(),
2304 original_abilities: self.abilities.clone(),
2305 original_activated_abilities: self.activated_abilities.clone(),
2306 original_triggers: self.triggers.clone(),
2307 original_svars: self.svars.clone(),
2308 original_static_abilities: self.static_abilities.clone(),
2309 original_replacement_effects: self.replacement_effects.clone(),
2310 original_base_ability_count: self.base_ability_count,
2311 original_base_trigger_count: self.base_trigger_count,
2312 }
2313 }
2314
2315 pub fn restore_clone_snapshot(&mut self, state: CloneState) {
2316 self.card_name = state.original_card_name;
2317 self.oracle_text = state.original_oracle_text;
2318 self.type_line = state.original_type_line;
2319 self.mana_cost = state.original_mana_cost;
2320 self.color = state.original_color;
2321 self.base_power = state.original_base_power;
2322 self.base_toughness = state.original_base_toughness;
2323 self.keywords = state.original_keywords;
2324 self.abilities = state.original_abilities;
2325 self.activated_abilities = state.original_activated_abilities;
2326 self.triggers = state.original_triggers;
2327 self.svars = state.original_svars;
2328 self.static_abilities = state.original_static_abilities;
2329 self.replacement_effects = state.original_replacement_effects;
2330 self.base_ability_count = state.original_base_ability_count;
2331 self.base_trigger_count = state.original_base_trigger_count;
2332 self.parsed_svar_cache.clear();
2333 self.refresh_action_specs();
2334 self.ensure_crew_activated_ability();
2335 self.remove_clone_state();
2336 }
2337
2338 pub fn set_counters_map(&mut self, counters: BTreeMap<CounterType, i32>) {
2339 self.counters = counters;
2340 }
2341
2342 pub fn set_zone(&mut self, zone: ZoneType) {
2343 self.zone = zone;
2344 }
2345
2346 pub fn set_color(&mut self, color: ColorSet) {
2347 self.color = color;
2348 }
2349
2350 pub fn set_animate_state(&mut self, state: Option<AnimateState>) {
2351 self.animate_state = state;
2352 }
2353
2354 pub fn set_clone_state(&mut self, state: Option<CloneState>) {
2355 self.clone_state = state;
2356 }
2357
2358 pub fn set_exiled_by(&mut self, source: Option<CardId>) {
2359 self.exiled_by = source;
2360 }
2361
2362 pub fn set_attached_to(&mut self, target: Option<CardId>) {
2363 self.attached_to = target;
2364 }
2365
2366 pub fn set_original_controller_eot(&mut self, controller: Option<PlayerId>) {
2367 self.original_controller_eot = controller;
2368 }
2369
2370 pub fn set_class_level(&mut self, level: i32) {
2371 self.class_level = level;
2372 }
2373
2374 pub fn set_paired_with(&mut self, pair: Option<CardId>) {
2375 self.paired_with = pair;
2376 }
2377
2378 pub fn set_must_block(&mut self, must_block: bool) {
2379 self.must_block = must_block;
2380 }
2381
2382 pub fn set_detained(&mut self, detained: bool) {
2383 self.detained = detained;
2384 }
2385
2386 pub fn set_goaded_by(&mut self, player: Option<PlayerId>) {
2387 self.goaded_by = player;
2388 }
2389
2390 pub fn set_phased_out(&mut self, phased_out: bool) {
2391 self.phased_out = phased_out;
2392 }
2393
2394 pub fn set_base_power(&mut self, power: Option<i32>) {
2395 self.base_power = power;
2396 }
2397
2398 pub fn set_base_toughness(&mut self, toughness: Option<i32>) {
2399 self.base_toughness = toughness;
2400 }
2401
2402 pub fn set_base_pt(&mut self, power: Option<i32>, toughness: Option<i32>) {
2403 self.base_power = power;
2404 self.base_toughness = toughness;
2405 }
2406
2407 pub fn capture_changed_characteristics_baseline_if_needed(&mut self) {
2408 if self.changed_type_line_base.is_none() {
2409 self.changed_type_line_base = Some(self.type_line.clone());
2410 }
2411 if self.changed_base_power.is_none() {
2412 self.changed_base_power = Some(self.base_power);
2413 }
2414 if self.changed_base_toughness.is_none() {
2415 self.changed_base_toughness = Some(self.base_toughness);
2416 }
2417 }
2418
2419 pub fn restore_changed_characteristics_baseline(&mut self) {
2420 if let Some(type_line) = self.changed_type_line_base.take() {
2421 self.set_type_line(type_line);
2422 }
2423 if let Some(power) = self.changed_base_power.take() {
2424 self.base_power = power;
2425 }
2426 if let Some(toughness) = self.changed_base_toughness.take() {
2427 self.base_toughness = toughness;
2428 }
2429 }
2430
2431 pub fn set_static_set_pt(&mut self, power: Option<i32>, toughness: Option<i32>) {
2432 self.static_set_power = power;
2433 self.static_set_toughness = toughness;
2434 }
2435
2436 pub fn set_power_modifier(&mut self, amount: i32) {
2437 self.power_modifier = amount;
2438 }
2439
2440 pub fn set_toughness_modifier(&mut self, amount: i32) {
2441 self.toughness_modifier = amount;
2442 }
2443
2444 pub fn set_card_name(&mut self, name: impl Into<String>) {
2445 self.card_name = name.into();
2446 }
2447
2448 pub fn set_mana_cost(&mut self, mana_cost: ManaCost) {
2449 self.mana_cost = mana_cost;
2450 }
2451
2452 pub fn set_abilities(&mut self, abilities: Vec<String>) {
2453 self.abilities = abilities;
2454 self.update_spell_abilities();
2455 self.refresh_action_specs();
2456 }
2457
2458 pub fn set_static_abilities(&mut self, abilities: Vec<StaticAbility>) {
2459 self.static_abilities = abilities;
2460 }
2461
2462 pub fn set_triggers(&mut self, triggers: Vec<Trigger>) {
2463 self.triggers = triggers;
2464 self.base_trigger_count = self.triggers.len();
2465 }
2466
2467 pub fn set_replacement_effects(&mut self, effects: Vec<ReplacementEffect>) {
2468 self.replacement_effects = effects;
2469 }
2470
2471 pub fn set_renowned(&mut self, renowned: bool) {
2472 self.is_renowned = renowned;
2473 }
2474
2475 pub fn set_monstrous(&mut self, monstrous: bool) {
2476 self.monstrous = monstrous;
2477 }
2478
2479 pub fn clear_granted_keywords(&mut self) {
2480 self.granted_keywords.clear();
2481 }
2482
2483 pub fn clear_pump_keywords(&mut self) {
2484 self.pump_keywords.clear();
2485 }
2486
2487 pub fn increment_pump_trigger_count(&mut self) {
2488 self.pump_trigger_count += 1;
2489 }
2490
2491 pub fn add_remembered_cards<I>(&mut self, cards: I)
2492 where
2493 I: IntoIterator<Item = CardId>,
2494 {
2495 for card in cards {
2496 self.add_remembered_card(card);
2497 }
2498 }
2499
2500 pub fn clear_chosen_colors(&mut self) {
2501 self.chosen_colors.clear();
2502 }
2503
2504 pub fn add_chosen_color(&mut self, color: impl Into<String>) {
2505 self.chosen_colors.push(color.into());
2506 }
2507
2508 pub fn add_chosen_card(&mut self, card: CardId) {
2509 if !self.chosen_cards.contains(&card) {
2510 self.chosen_cards.push(card);
2511 }
2512 }
2513
2514 pub fn add_pump_keyword(&mut self, keyword: &str) {
2515 self.pump_keywords.add(keyword);
2516 }
2517
2518 pub fn add_granted_keyword(&mut self, keyword: &str) {
2519 self.granted_keywords.add(keyword);
2520 }
2521
2522 pub fn was_turned_face_up_this_turn(&self) -> bool {
2523 !self.face_down && (self.manifested || self.cloaked)
2524 }
2525
2526 pub fn can_transform(&self) -> bool {
2527 self.other_part.is_some()
2528 }
2529
2530 pub fn has_name_overwrite(&self) -> bool {
2531 false
2532 }
2533
2534 pub fn has_non_legendary_creature_names(&self) -> bool {
2535 false
2536 }
2537
2538 pub fn add_changed_name(&mut self, name: &str) {
2539 if !self.has_s_var("OriginalName") {
2540 self.set_s_var("OriginalName", self.card_name.clone());
2541 }
2542 self.card_name = name.to_string();
2543 }
2544
2545 pub fn remove_changed_name(&mut self) {
2546 if let Some(orig) = self.svars.get("OriginalName").cloned() {
2547 self.card_name = orig;
2548 }
2549 }
2550
2551 pub fn clear_changed_name(&mut self) {
2552 self.remove_s_var("OriginalName");
2553 }
2554
2555 pub fn add_devoured(&mut self, card_id: CardId) {
2556 self.add_remembered_card(card_id);
2557 self.set_s_var("Devoured", "True");
2558 }
2559 pub fn add_exploited(&mut self, card_id: CardId) {
2560 self.add_remembered_card(card_id);
2561 self.set_s_var("Exploited", "True");
2562 }
2563 pub fn add_delved(&mut self, card_id: CardId) {
2564 self.add_remembered_card(card_id);
2565 self.set_s_var("Delved", "True");
2566 }
2567 pub fn clear_delved(&mut self) {
2568 self.remove_s_var("Delved");
2569 }
2570 pub fn retain_paid_list(&mut self) {
2571 self.remembered_cards.retain(|_| true);
2572 }
2573 pub fn add_stored_rolls(&mut self, roll: i32) {
2574 self.add_remembered_cmc(roll);
2575 }
2576 pub fn replace_stored_roll(&mut self, from: i32, to: i32) {
2577 for roll in &mut self.remembered_cmc {
2578 if *roll == from {
2579 *roll = to;
2580 }
2581 }
2582 }
2583 pub fn add_flip_result(&mut self, heads: bool) {
2584 self.set_s_var("FlipResult", if heads { "Heads" } else { "Tails" });
2585 }
2586 pub fn clear_flip_result(&mut self) {
2587 self.remove_s_var("FlipResult");
2588 }
2589 pub fn add_blocked_this_turn(&mut self, card_id: CardId) {
2590 self.add_remembered_card(card_id);
2591 self.set_s_var("BlockedThisTurn", "True");
2592 }
2593 pub fn clear_blocked_this_turn(&mut self) {
2594 self.remove_s_var("BlockedThisTurn");
2595 }
2596 pub fn add_blocked_by_this_turn(&mut self, card_id: CardId) {
2597 self.add_remembered_card(card_id);
2598 self.set_s_var("BlockedByThisTurn", "True");
2599 }
2600 pub fn clear_blocked_by_this_turn(&mut self) {
2601 self.remove_s_var("BlockedByThisTurn");
2602 }
2603
2604 pub fn add_must_block_card(&mut self, card_id: CardId) {
2605 if !self.must_block_cards.contains(&card_id) {
2606 self.must_block_cards.push(card_id);
2607 }
2608 }
2609
2610 pub fn add_must_block_cards(&mut self, cards: impl IntoIterator<Item = CardId>) {
2611 for c in cards {
2612 self.add_must_block_card(c);
2613 }
2614 }
2615
2616 pub fn remove_must_block_cards(&mut self, cards: impl IntoIterator<Item = CardId>) {
2617 let remove: HashSet<CardId> = cards.into_iter().collect();
2618 self.must_block_cards.retain(|c| !remove.contains(c));
2619 }
2620
2621 pub fn clear_must_block_cards(&mut self) {
2622 self.must_block_cards.clear();
2623 }
2624
2625 pub fn has_second_strike(&self) -> bool {
2626 self.has_double_strike()
2627 }
2628
2629 pub fn has_suspend(&self) -> bool {
2630 self.has_keyword("Suspend")
2631 }
2632
2633 pub fn has_converge(&self) -> bool {
2634 self.has_keyword("Converge")
2635 }
2636
2637 pub fn can_receive_counters(&self, _counter: &CounterType) -> bool {
2638 true
2639 }
2640
2641 pub fn can_remove_counters(&self, counter: &CounterType) -> bool {
2642 self.counter_count(counter) > 0
2643 }
2644
2645 pub fn add_counter_internal(&mut self, counter: &CounterType, amount: i32) {
2646 self.add_counter(counter, amount);
2647 }
2648
2649 pub fn create_counter_static(&mut self) {
2650 self.put_etb_counters();
2651 }
2652
2653 pub fn subtract_counter(&mut self, counter: &CounterType, amount: i32) {
2654 self.remove_counter(counter, amount);
2655 }
2656
2657 pub fn clear_counters(&mut self) {
2658 self.counters.clear();
2659 }
2660
2661 pub fn sum_all_counters(&self) -> i32 {
2662 self.counters.values().sum()
2663 }
2664
2665 pub fn put_etb_counters(&mut self) {
2666 if self.etb_counters_p1p1 > 0 {
2667 self.add_counter(&CounterType::P1P1, self.etb_counters_p1p1);
2668 self.etb_counters_p1p1 = 0;
2669 }
2670 }
2671
2672 pub fn copy_changed_s_vars_from(&mut self, other: &Card) {
2673 self.set_svars_map(other.svars.clone());
2674 }
2675
2676 pub fn add_changed_s_vars(&mut self, key: &str, value: &str) {
2677 self.set_s_var(key, value);
2678 }
2679
2680 pub fn remove_changed_s_vars(&mut self, key: &str) {
2681 self.remove_s_var(key);
2682 }
2683
2684 pub fn add_changed_mana_cost(&mut self, mana_cost: &str) {
2685 if !self.has_s_var("OriginalManaCost") {
2686 self.set_s_var("OriginalManaCost", self.mana_cost.to_string());
2687 }
2688 self.mana_cost = ManaCost::parse(mana_cost);
2689 self.calculate_perpetual_adjusted_mana_cost();
2690 self.update_mana_cost_for_view();
2691 }
2692 pub fn remove_changed_mana_cost(&mut self, _timestamp: i64, _static_id: i64) -> bool {
2693 let Some(original) = self.svars.get("OriginalManaCost").cloned() else {
2694 return false;
2695 };
2696 let before = self.mana_cost.clone();
2697 self.mana_cost = ManaCost::parse(&original);
2698 self.calculate_perpetual_adjusted_mana_cost();
2699 self.update_mana_cost_for_view();
2700 self.remove_s_var("OriginalManaCost");
2701 self.mana_cost != before
2702 }
2703
2704 pub fn cleanup_exiled_with(&mut self) {
2705 self.exiled_by = None;
2706 }
2707
2708 pub fn has_paper_foil(&self) -> bool {
2709 self.paper_foil
2710 }
2711
2712 pub fn has_marked_color(&self) -> bool {
2713 !self.color.is_colorless()
2714 }
2715
2716 pub fn can_produce_color_mana(
2717 &self,
2718 game: &GameState,
2719 colors: &std::collections::HashSet<String>,
2720 ) -> bool {
2721 crate::card::card_util::card_can_produce_color_mana(game, self.id, colors)
2722 }
2723
2724 pub fn can_produce_same_mana_type_with(&self, game: &GameState, other: &Card) -> bool {
2725 crate::card::card_util::card_can_produce_same_mana_type_with(game, self.id, other.id)
2726 }
2727
2728 pub fn has_remove_intrinsic(&self) -> bool {
2729 false
2730 }
2731
2732 pub fn update_spell_abilities(&mut self) {
2733 self.activated_abilities.clear();
2734 for (i, raw) in self.abilities.iter().enumerate() {
2735 if let Some(parsed) = crate::ability::activated::parse_activated_ability(raw, i) {
2736 self.activated_abilities.push(parsed);
2737 }
2738 }
2739 }
2740
2741 pub fn refresh_action_specs(&mut self) {
2742 let mut spell_specs = Vec::new();
2743 let mut spell_cost = None;
2744 let mut ai_phyrexian_payment = None;
2745 let mut spree_min_mode_cost = None;
2746
2747 for (ability_index, raw) in self.abilities.iter().enumerate() {
2748 let parsed = ParsedParams::parse(raw);
2749 if ai_phyrexian_payment.is_none() {
2750 ai_phyrexian_payment = parsed.get(keys::AI_PHYREXIAN_PAYMENT).map(str::to_string);
2751 }
2752 let Some(sp_kind) = parsed.get(keys::SP) else {
2753 continue;
2754 };
2755 let cost_contains_x = parsed
2756 .get(keys::COST)
2757 .is_some_and(|cost| cost.contains('X'));
2758 if spell_cost.is_none() {
2759 spell_cost = parsed.get(keys::COST).map(parse_cost);
2760 }
2761 spell_specs.push(CardActionSpellSpec {
2762 ability_index,
2763 has_valid_tgts: parsed.has(keys::VALID_TGTS),
2764 cost_contains_x,
2765 target_chain: self.collect_action_target_chain(raw),
2766 });
2767
2768 if sp_kind.eq_ignore_ascii_case("Charm") {
2769 if let Some(choices) = parsed.get(keys::CHOICES) {
2770 let min_mode_cost = choices
2771 .split(',')
2772 .filter_map(|name| {
2773 self.svars.get(name.trim()).and_then(|svar_val| {
2774 ParsedParams::parse(svar_val)
2775 .get(keys::MODE_COST)
2776 .map(|cost| forge_foundation::ManaCost::parse(cost).cmc())
2777 })
2778 })
2779 .min();
2780 spree_min_mode_cost = spree_min_mode_cost.or(min_mode_cost);
2781 }
2782 }
2783 }
2784
2785 self.action_spell_specs = spell_specs;
2786 self.action_spell_cost = spell_cost;
2787 self.ai_phyrexian_payment = ai_phyrexian_payment;
2788 self.spree_min_mode_cost = spree_min_mode_cost;
2789 }
2790
2791 fn refresh_action_specs_after_svar_change(&mut self) {
2792 if self.action_spell_specs.is_empty() {
2793 return;
2794 }
2795 self.refresh_action_specs();
2796 }
2797
2798 fn collect_action_target_chain(&self, ability_text: &str) -> Vec<CardActionTargetSpec> {
2799 let mut specs = Vec::new();
2800 let mut current = Some(ability_text.to_string());
2801
2802 while let Some(text) = current {
2803 let parsed = ParsedParams::parse(&text);
2804 let params = Params::from_parsed(&parsed);
2805 if let Some(target_restrictions) = TargetRestrictions::new_from_parsed(&parsed, ¶ms)
2806 {
2807 specs.push(CardActionTargetSpec {
2808 min_targets: parse_literal_target_count(&target_restrictions.min_targets),
2809 target_restrictions,
2810 });
2811 }
2812
2813 current = parsed
2814 .get(keys::SUB_ABILITY)
2815 .and_then(|name| self.svars.get(name.trim()))
2816 .cloned();
2817 }
2818
2819 specs
2820 }
2821
2822 pub fn inc_shield_count(&mut self) {
2823 self.damage_prevention += 1;
2824 }
2825
2826 pub fn dec_shield_count(&mut self) {
2827 self.damage_prevention = (self.damage_prevention - 1).max(0);
2828 }
2829
2830 pub fn reset_shield_count(&mut self) {
2831 self.damage_prevention = 0;
2832 }
2833
2834 pub fn add_regenerated_this_turn(&mut self) {
2835 self.regeneration_shields += 1;
2836 }
2837
2838 pub fn can_be_shielded(&self) -> bool {
2839 self.is_permanent()
2840 }
2841
2842 pub fn add_untap_command(&mut self) {
2843 self.set_s_var("_cmd_untap", "1");
2844 }
2845 pub fn add_unattach_command(&mut self) {
2846 self.set_s_var("_cmd_unattach", "1");
2847 }
2848 pub fn add_faceup_command(&mut self) {
2849 self.set_s_var("_cmd_faceup", "1");
2850 }
2851 pub fn add_facedown_command(&mut self) {
2852 self.set_s_var("_cmd_facedown", "1");
2853 }
2854 pub fn add_change_controller_command(&mut self) {
2855 self.set_s_var("_cmd_change_controller", "1");
2856 }
2857 pub fn add_phase_out_command(&mut self) {
2858 self.set_s_var("_cmd_phase_out", "1");
2859 }
2860 pub fn add_leaves_play_command(&mut self) {
2861 self.set_s_var("_cmd_leaves_play", "1");
2862 }
2863 pub fn add_static_command_list(&mut self) {
2864 self.set_s_var("_cmd_static", "1");
2865 }
2866 pub fn run_leaves_play_commands(&mut self) {
2867 if self.has_s_var("_cmd_leaves_play") {
2868 self.cleanup_exiled_with();
2869 self.remove_s_var("_cmd_leaves_play");
2870 }
2871 }
2872 pub fn run_untap_commands(&mut self) {
2873 if self.has_s_var("_cmd_untap") {
2874 self.untap();
2875 self.remove_s_var("_cmd_untap");
2876 }
2877 }
2878 pub fn run_unattach_commands(&mut self) {
2879 if self.has_s_var("_cmd_unattach") {
2880 self.unattach_from_entity();
2881 self.remove_s_var("_cmd_unattach");
2882 }
2883 }
2884 pub fn run_faceup_commands(&mut self) {
2885 if self.has_s_var("_cmd_faceup") {
2886 self.turn_face_up();
2887 self.remove_s_var("_cmd_faceup");
2888 }
2889 }
2890 pub fn run_facedown_commands(&mut self) {
2891 if self.has_s_var("_cmd_facedown") {
2892 self.turn_face_down();
2893 self.remove_s_var("_cmd_facedown");
2894 }
2895 }
2896 pub fn run_change_controller_commands(&mut self) {
2897 if self.has_s_var("_cmd_change_controller") {
2898 self.clear_temp_controllers();
2899 self.remove_s_var("_cmd_change_controller");
2900 }
2901 }
2902 pub fn run_phase_out_commands(&mut self) {
2903 if self.has_s_var("_cmd_phase_out") {
2904 self.phase();
2905 self.remove_s_var("_cmd_phase_out");
2906 }
2907 }
2908
2909 pub fn has_sickness(&self) -> bool {
2910 self.summoning_sick
2911 }
2912
2913 pub fn has_become_target_this_turn(&self) -> bool {
2914 self.became_target_this_turn
2915 }
2916
2917 pub fn add_target_from_this_turn(&mut self) {
2918 self.became_target_this_turn = true;
2919 }
2920
2921 pub fn has_started_the_turn_untapped(&self) -> bool {
2922 !self.started_turn_tapped
2923 }
2924
2925 pub fn came_under_control_since_last_upkeep(&self) -> bool {
2926 self.came_under_control_since_last_upkeep
2927 }
2928
2929 pub fn add_temp_controller(&mut self, player: PlayerId) {
2930 self.temp_controllers.push(player);
2931 }
2932
2933 pub fn remove_temp_controller(&mut self, player: PlayerId) {
2934 self.temp_controllers.retain(|&p| p != player);
2935 }
2936
2937 pub fn clear_temp_controllers(&mut self) {
2938 self.temp_controllers.clear();
2939 }
2940
2941 pub fn clear_controllers(&mut self) {
2942 self.controller = self.owner;
2943 self.clear_temp_controllers();
2944 }
2945
2946 pub fn may_player_look(&self, player: PlayerId) -> bool {
2947 self.may_look_at.contains(&player)
2948 }
2949
2950 pub fn add_may_look_face_down_exile(&mut self, player: PlayerId) {
2951 if !self.may_look_at.contains(&player) {
2952 self.may_look_at.push(player);
2953 }
2954 }
2955
2956 pub fn add_may_look_at(&mut self, player: PlayerId) {
2957 if !self.may_look_at.contains(&player) {
2958 self.may_look_at.push(player);
2959 }
2960 }
2961
2962 pub fn remove_may_look_at(&mut self, player: PlayerId) {
2963 self.may_look_at.retain(|&p| p != player);
2964 }
2965
2966 pub fn add_may_look_temp(&mut self, player: PlayerId) {
2967 self.add_may_look_at(player);
2968 }
2969
2970 pub fn remove_may_look_temp(&mut self, player: PlayerId) {
2971 self.remove_may_look_at(player);
2972 }
2973
2974 pub fn update_may_look(&mut self) {
2975 let mut seen = HashSet::new();
2976 self.may_look_at.retain(|p| seen.insert(*p));
2977 }
2978 pub fn update_may_play(&mut self) {
2979 let mut seen = HashSet::new();
2980 self.may_play.retain(|p| seen.insert(*p));
2981 }
2982
2983 pub fn may_play(&self, player: PlayerId) -> bool {
2984 self.may_play.contains(&player)
2985 }
2986
2987 pub fn remove_may_play(&mut self, player: PlayerId) {
2988 self.may_play.retain(|&p| p != player);
2989 }
2990
2991 pub fn reset_may_play_turn(&mut self) {
2992 self.may_play.clear();
2993 }
2994
2995 pub fn remove_attached_to(&mut self) {
2996 self.attached_to = None;
2997 }
2998
2999 pub fn attach_to_entity(&mut self, host: CardId) {
3000 self.attached_to = Some(host);
3001 }
3002
3003 pub fn add_attachment(&mut self, card_id: CardId) {
3004 if !self.attachments.contains(&card_id) {
3005 self.attachments.push(card_id);
3006 }
3007 }
3008
3009 pub fn remove_attachment(&mut self, card_id: CardId) {
3010 self.attachments.retain(|&id| id != card_id);
3011 }
3012
3013 pub fn unattach_from_entity(&mut self) {
3014 self.attached_to = None;
3015 }
3016
3017 pub fn clear_intrinsic_keywords(&mut self) {
3018 self.keywords.clear();
3019 }
3020
3021 pub fn clear_all_keyword_sets(&mut self) {
3022 self.keywords.clear();
3023 self.pump_keywords.clear();
3024 self.granted_keywords.clear();
3025 }
3026
3027 pub fn clear_subtypes(&mut self) {
3028 self.type_line.subtypes.clear();
3029 }
3030
3031 pub fn clear_changed_card_types(&mut self) {
3032 self.update_types();
3033 }
3034 pub fn clear_changed_card_colors(&mut self) {
3035 self.color = ColorSet::COLORLESS;
3036 }
3037 pub fn add_changed_card_types_by_text(&mut self) {
3038 self.update_types();
3039 }
3040 pub fn remove_changed_card_types_by_text(&mut self) {
3041 self.update_types();
3042 }
3043 pub fn add_changed_card_types(&mut self) {
3044 self.update_types();
3045 }
3046 pub fn remove_changed_card_types(&mut self) {
3047 self.update_types();
3048 }
3049 pub fn update_type_cache(&mut self) {
3050 self.type_line = CardTypeLine::parse(&self.type_line.to_string());
3051 }
3052 pub fn has_changed_card_colors(&self) -> bool {
3053 !self.color.is_colorless()
3054 }
3055 pub fn add_color_by_text(&mut self, color: ColorSet) {
3056 self.add_color(color);
3057 }
3058 pub fn remove_color_by_text(&mut self) {
3059 self.remove_color();
3060 }
3061 pub fn remove_color(&mut self) {
3062 self.color = ColorSet::COLORLESS;
3063 }
3064 pub fn add_clone_state(&mut self) {
3065 self.set_s_var("CloneState", "True");
3066 }
3067 pub fn remove_clone_state(&mut self) {
3068 self.remove_s_var("CloneState");
3069 }
3070 pub fn remove_clone_states(&mut self) {
3071 self.remove_s_var("CloneState");
3072 }
3073 pub fn add_new_pt_by_text(&mut self, p: i32, t: i32) {
3074 self.base_power = Some(p);
3075 self.base_toughness = Some(t);
3076 }
3077 pub fn remove_new_p_tby_text(&mut self) {
3078 self.clear_new_pt();
3079 }
3080 pub fn add_new_pt(&mut self, p: i32, t: i32) {
3081 self.base_power = Some(p);
3082 self.base_toughness = Some(t);
3083 }
3084 pub fn remove_new_pt(&mut self) {
3085 self.clear_new_pt();
3086 }
3087 pub fn clear_new_pt(&mut self) {
3088 self.base_power = None;
3089 self.base_toughness = None;
3090 }
3091 pub fn toughness_assigns_damage(&self) -> bool {
3092 self.has_keyword("CARDNAME assigns combat damage equal to its toughness")
3093 }
3094 pub fn assign_no_combat_damage(&self) -> bool {
3095 self.has_keyword("CARDNAME assigns no combat damage")
3096 }
3097 pub fn add_pt_boost(&mut self, p: i32, t: i32) {
3098 self.power_modifier += p;
3099 self.toughness_modifier += t;
3100 }
3101 pub fn remove_pt_boost(&mut self, p: i32, t: i32) {
3102 self.power_modifier -= p;
3103 self.toughness_modifier -= t;
3104 }
3105 pub fn add_draft_action(&mut self) {
3106 self.set_s_var("DraftAction", "True");
3107 }
3108 pub fn add_intensity(&mut self, v: i32) {
3109 self.intensity += v;
3110 }
3111 pub fn has_intensity(&self) -> bool {
3112 self.intensity > 0
3113 }
3114 pub fn has_perpetual(&self) -> bool {
3115 !self.perpetual.is_empty()
3116 }
3117 pub fn get_perpetual(&self) -> &[PerpetualRecord] {
3118 &self.perpetual
3119 }
3120 pub fn add_perpetual(&mut self, p: PerpetualRecord) {
3121 self.apply_perpetual_record(p, true);
3122 }
3123 pub fn remove_perpetual(&mut self, timestamp: i64) -> bool {
3124 if let Some(idx) = self
3125 .perpetual
3126 .iter()
3127 .position(|p| p.timestamp() == timestamp)
3128 {
3129 self.perpetual.remove(idx);
3130 true
3131 } else {
3132 false
3133 }
3134 }
3135 pub fn set_perpetual(&mut self, old_card: &Card, apply_effects: bool) {
3136 self.perpetual = old_card.perpetual.clone();
3137 if apply_effects {
3138 for p in self.perpetual.clone() {
3139 self.apply_perpetual_record(p, false);
3140 }
3141 }
3142 }
3143 pub fn set_perpetual_from(&mut self, old_card: &Card) {
3144 self.set_perpetual(old_card, true);
3145 }
3146 pub fn apply_perpetual_record(&mut self, p: PerpetualRecord, remember: bool) {
3147 if remember {
3148 self.perpetual.push(p.clone());
3149 }
3150 p.apply_effect(self);
3151 }
3152 pub fn add_trigger_for_static_ability(&mut self, trig: Trigger) {
3153 self.add_trigger(trig);
3154 }
3155 pub fn visit_keywords(&self) -> Vec<String> {
3156 self.keywords.as_string_list()
3157 }
3158 pub fn update_keywords(&mut self) {
3159 self.update_keywords_cache();
3160 }
3161 pub fn add_changed_card_keywords(&mut self, kw: &str) {
3162 self.add_intrinsic_keyword(kw);
3163 }
3164 pub fn add_keyword_for_static_ability(&mut self, kw: &str) {
3165 self.granted_keywords.add(kw);
3166 }
3167 pub fn add_changed_card_keywords_by_text(&mut self, kw: &str) {
3168 self.add_intrinsic_keyword(kw);
3169 }
3170 pub fn add_changed_card_keywords_internal(&mut self, kw: &str) {
3171 self.add_intrinsic_keyword(kw);
3172 }
3173 pub fn remove_changed_card_keywords(&mut self, kw: &str) {
3174 self.remove_intrinsic_keyword(kw);
3175 }
3176 pub fn remove_changed_card_keywords_by_text(&mut self, kw: &str) {
3177 self.remove_intrinsic_keyword(kw);
3178 }
3179 pub fn clear_changed_card_keywords(&mut self) {
3180 self.keywords.clear();
3181 }
3182 pub fn clear_static_changed_card_keywords(&mut self) {
3183 self.granted_keywords.clear();
3184 }
3185 pub fn add_hidden_extrinsic_keywords(&mut self, kw: &str) {
3186 self.granted_keywords.add(kw);
3187 }
3188 pub fn remove_hidden_extrinsic_keywords(&mut self, kw: &str) {
3189 self.granted_keywords.remove(kw);
3190 }
3191 pub fn remove_hidden_extrinsic_keyword(&mut self, kw: &str) {
3192 self.granted_keywords.remove(kw);
3193 }
3194 pub fn has_start_of_keyword(&self, prefix: &str) -> bool {
3195 self.keywords.iter_strings().any(|k| k.starts_with(prefix))
3196 }
3197 pub fn has_start_of_un_hidden_keyword(&self, prefix: &str) -> bool {
3198 self.has_start_of_keyword(prefix)
3199 }
3200 pub fn has_any_keyword(&self) -> bool {
3201 !self.keywords.as_string_list().is_empty()
3202 || !self.granted_keywords.as_string_list().is_empty()
3203 || !self.pump_keywords.as_string_list().is_empty()
3204 }
3205 pub fn add_cant_have_keyword(&mut self, kw: &str) {
3206 self.cant_have_keywords.insert(kw.to_ascii_lowercase());
3207 }
3208 pub fn remove_cant_have_keyword(&mut self, kw: &str) {
3209 self.cant_have_keywords.remove(&kw.to_ascii_lowercase());
3210 }
3211 pub fn add_changed_text_color_word(&mut self, from: &str, to: &str) {
3212 self.set_s_var(format!("TextColor:{from}"), to);
3213 }
3214 pub fn remove_changed_text_color_word(&mut self, from: &str) {
3215 self.remove_s_var(&format!("TextColor:{from}"));
3216 }
3217 pub fn add_changed_text_type_word(&mut self, from: &str, to: &str) {
3218 self.set_s_var(format!("TextType:{from}"), to);
3219 }
3220 pub fn remove_changed_text_type_word(&mut self, from: &str) {
3221 self.remove_s_var(&format!("TextType:{from}"));
3222 }
3223 pub fn copy_changed_text_from(&mut self, other: &Card) {
3224 for (k, v) in &other.svars {
3225 if k.starts_with("TextColor:") || k.starts_with("TextType:") {
3226 self.svars.insert(k.clone(), v.clone());
3227 }
3228 }
3229 }
3230 pub fn has_playable_land_face(&self) -> bool {
3231 self.is_land()
3232 || self
3233 .other_part
3234 .as_ref()
3235 .map(|p| p.type_line.is_land())
3236 .unwrap_or(false)
3237 }
3238 pub fn phase(&mut self) {
3239 self.phased_out = !self.phased_out;
3240 }
3241 pub fn associated_with_color(&self, game: &GameState, color: &str) -> bool {
3242 let mut colors = HashSet::new();
3243 colors.insert(color.to_string());
3244 forge_foundation::Color::from_name(&color.to_ascii_lowercase())
3245 .map(|parsed| self.color.has_any_color(parsed.mask()))
3246 .unwrap_or(false)
3247 || self.can_produce_color_mana(game, &colors)
3248 }
3249 pub fn has_no_name(&self) -> bool {
3250 self.card_name.trim().is_empty()
3251 }
3252 pub fn shares_name_with(&self, other: &Card) -> bool {
3253 self.card_name.eq_ignore_ascii_case(&other.card_name)
3254 }
3255 pub fn has_creature_type(&self, creature_type: &str) -> bool {
3256 if !self.is_creature() && !self.type_line.core_types.contains(&CoreType::Kindred) {
3257 return false;
3258 }
3259 if self.type_line.has_subtype(creature_type) {
3260 return true;
3261 }
3262 self.has_keyword("Changeling") && crate::game::TypeRegistry::is_creature_type(creature_type)
3263 }
3264 pub fn has_subtype(&self, subtype: &str) -> bool {
3265 self.type_line.has_subtype(subtype) || self.has_creature_type(subtype)
3266 }
3267 pub fn shares_color_with(&self, other: &Card) -> bool {
3268 (self.color.has_white() && other.color.has_white())
3269 || (self.color.has_blue() && other.color.has_blue())
3270 || (self.color.has_black() && other.color.has_black())
3271 || (self.color.has_red() && other.color.has_red())
3272 || (self.color.has_green() && other.color.has_green())
3273 || (self.color.is_colorless() && other.color.is_colorless())
3274 }
3275 pub fn shares_cmc_with(&self, other: &Card) -> bool {
3276 self.mana_value() == other.mana_value()
3277 }
3278 pub fn shares_creature_type_with(&self, other: &Card) -> bool {
3279 crate::game::TypeRegistry::creature_types()
3280 .iter()
3281 .any(|creature_type| {
3282 self.has_creature_type(creature_type) && other.has_creature_type(creature_type)
3283 })
3284 }
3285 pub fn shares_land_type_with(&self, other: &Card) -> bool {
3286 self.shares_creature_type_with(other) && self.is_land() && other.is_land()
3287 }
3288 pub fn shares_permanent_type_with(&self, other: &Card) -> bool {
3289 (self.is_creature() && other.is_creature())
3290 || (self.is_land() && other.is_land())
3291 || (self.type_line.is_artifact() && other.type_line.is_artifact())
3292 || (self.type_line.is_enchantment() && other.type_line.is_enchantment())
3293 || (self.type_line.is_planeswalker() && other.type_line.is_planeswalker())
3294 }
3295 pub fn shares_card_type_with(&self, other: &Card) -> bool {
3296 self.shares_permanent_type_with(other)
3297 }
3298 pub fn shares_all_card_types_with(&self, other: &Card) -> bool {
3299 self.type_line.core_types == other.type_line.core_types
3300 }
3301 pub fn shares_controller_with(&self, other: &Card) -> bool {
3302 self.controller == other.controller
3303 }
3304 pub fn has_a_basic_land_type(&self) -> bool {
3305 self.type_line.has_subtype("Plains")
3306 || self.type_line.has_subtype("Island")
3307 || self.type_line.has_subtype("Swamp")
3308 || self.type_line.has_subtype("Mountain")
3309 || self.type_line.has_subtype("Forest")
3310 }
3311 pub fn has_a_non_basic_land_type(&self) -> bool {
3312 self.is_land() && !self.has_a_basic_land_type()
3313 }
3314 pub fn has_dealt_damage_to_opponent_this_turn(&self) -> bool {
3315 self.total_damage_done_this_turn > 0
3316 }
3317 pub fn has_been_dealt_deathtouch_damage(&self) -> bool {
3318 self.has_deathtouch_damage
3319 }
3320 pub fn has_been_dealt_excess_damage_this_turn(&self) -> bool {
3321 self.damage > self.toughness()
3322 }
3323 pub fn log_excess_damage(&mut self) {
3324 self.set_s_var("ExcessDamageLogged", "True");
3325 }
3326 pub fn add_assigned_damage(&mut self, amount: i32) {
3327 self.assigned_damage += amount;
3328 }
3329 pub fn clear_assigned_damage(&mut self) {
3330 self.assigned_damage = 0;
3331 }
3332 pub fn can_damage_prevented(&self) -> bool {
3333 !self.has_keyword("Damage can't be prevented")
3334 }
3335 pub fn static_replace_damage(&self, amount: i32) -> i32 {
3336 amount
3337 }
3338 pub fn add_damage_after_prevention(&mut self, amount: i32) -> i32 {
3339 let dealt = if self.can_be_dealt_damage() {
3340 amount.max(0)
3341 } else {
3342 0
3343 };
3344 if dealt <= 0 {
3345 return 0;
3346 }
3347 if self.type_line.is_planeswalker() {
3348 self.remove_counter(&CounterType::Loyalty, dealt);
3349 }
3350 if self.type_line.core_types.contains(&CoreType::Battle) {
3351 self.remove_counter(&CounterType::Named("DEFENSE".to_string()), dealt);
3352 }
3353 if self.is_creature() {
3354 self.damage += dealt;
3355 }
3356 dealt
3357 }
3358 pub fn border_color(&self) -> &'static str {
3359 if self.color.is_colorless() {
3360 "Colorless"
3361 } else if self.color.has_white() {
3362 "White"
3363 } else if self.color.has_blue() {
3364 "Blue"
3365 } else if self.color.has_black() {
3366 "Black"
3367 } else if self.color.has_red() {
3368 "Red"
3369 } else {
3370 "Green"
3371 }
3372 }
3373 pub fn was_discarded(&self) -> bool {
3374 self.discarded
3375 }
3376 pub fn was_surveilled(&self) -> bool {
3377 self.surveilled
3378 }
3379 pub fn was_milled(&self) -> bool {
3380 self.milled
3381 }
3382 pub fn clear_ring_bearer(&mut self) {
3383 self.remove_s_var("RingBearer");
3384 }
3385 pub fn add_saddled_by_this_turn(&mut self, card: CardId) {
3386 self.set_s_var("SaddledBy", format!("{}", card.0));
3387 }
3388 pub fn reset_saddled(&mut self) {
3389 self.remove_s_var("SaddledBy");
3390 }
3391 pub fn can_specialize(&self) -> bool {
3392 self.has_keyword("Specialize")
3393 }
3394 pub fn can_crew(&self) -> bool {
3395 self.is_permanent()
3396 }
3397 pub fn reset_times_crewed_this_turn(&mut self) {
3398 self.times_crewed_this_turn = 0;
3399 }
3400 pub fn becomes_crewed(&mut self) {
3401 self.is_crewed = true;
3402 self.times_crewed_this_turn += 1;
3403 }
3404 pub fn reset_crewed(&mut self) {
3405 self.is_crewed = false;
3406 }
3407 pub fn add_crewed_by_this_turn(&mut self, _card: CardId) {
3408 self.times_crewed_this_turn += 1;
3409 }
3410 pub fn visit_attraction(&mut self) {
3411 self.visited_this_turn = true;
3412 }
3413 pub fn was_visited_this_turn(&self) -> bool {
3414 self.visited_this_turn
3415 }
3416 pub fn animate_bestow(&mut self) {
3417 self.is_bestowed = false;
3418 }
3419 pub fn unanimate_bestow(&mut self) {
3420 self.is_bestowed = true;
3421 }
3422 pub fn equals_with_game_timestamp(&self, other: &Card) -> bool {
3423 self.id == other.id && self.zone_timestamp == other.zone_timestamp
3424 }
3425 pub fn update_world_timestamp(&mut self) {
3426 self.zone_timestamp = self.zone_timestamp.saturating_add(1);
3427 }
3428 pub fn can_be_discarded_by(&self, _player: PlayerId) -> bool {
3429 true
3430 }
3431 pub fn can_be_destroyed(&self) -> bool {
3432 !self.has_indestructible()
3433 }
3434 pub fn can_be_targeted_by(&self, _player: PlayerId) -> bool {
3435 true
3436 }
3437 pub fn cant_be_attached_msg(&self) -> Option<String> {
3438 None
3439 }
3440 pub fn can_be_sacrificed_by(&self, _player: PlayerId) -> bool {
3441 true
3442 }
3443 pub fn can_exiled_by(&self, _player: PlayerId) -> bool {
3444 true
3445 }
3446 pub fn update_static_abilities(&mut self) {
3447 self.recompute_changed_card_traits();
3448 }
3449 pub fn update_triggers(&mut self) {
3450 self.recompute_changed_card_traits();
3451 }
3452 pub fn update_replacement_effects(&mut self) {
3453 self.recompute_changed_card_traits();
3454 }
3455 pub fn was_cast(&self) -> bool {
3456 self.cast_from.is_some()
3460 }
3461 pub fn on_end_of_combat(&mut self) {
3462 self.assigned_damage = 0;
3463 }
3464 pub fn on_cleanup_phase(&mut self) {
3465 self.became_target_this_turn = false;
3466 self.visited_this_turn = false;
3467 self.damage_prevention = 0;
3468 }
3469 pub fn has_etb_trigger(&self) -> bool {
3470 self.triggers.iter().any(|t| {
3471 t.kind == crate::trigger::TriggerType::ChangesZone
3472 && t.destination_zone() == Some(ZoneType::Battlefield)
3473 })
3474 }
3475 pub fn has_etb_replacement(&self) -> bool {
3476 self.has_replacement_effect()
3477 }
3478 pub fn can_move_to_command_zone(&self) -> bool {
3479 self.is_commander && self.move_to_command_zone
3480 }
3481 pub fn from_paper_card(&mut self) {
3482 self.is_token = false;
3483 }
3484 pub fn cleanup_copied_changes_from(&mut self) {
3485 self.clear_changed_card_traits();
3486 }
3487 pub fn activated_this_turn(&self) -> bool {
3488 self.ability_activated_this_turn > 0
3489 }
3490 pub fn add_ability_activated(&mut self) {
3491 self.ability_activated_this_turn += 1;
3492 }
3493 pub fn add_ability_activated_for(
3494 &mut self,
3495 ability: Option<&crate::spellability::SpellAbility>,
3496 ) {
3497 self.add_ability_activated_for_with_limit_increase(ability, false);
3498 }
3499 pub fn add_ability_activated_for_with_limit_increase(
3500 &mut self,
3501 ability: Option<&crate::spellability::SpellAbility>,
3502 loyalty_limit_increase: bool,
3503 ) {
3504 if let Some(ability) = ability {
3505 self.number_turn_activations.add(ability);
3506 self.number_game_activations.add(ability);
3507 if ability.ir.pw_ability {
3508 self.add_planeswalker_ability_activated(loyalty_limit_increase);
3509 }
3510 }
3511 self.add_ability_activated();
3512 }
3513 pub fn add_ability_resolved(&mut self) {
3514 self.ability_resolved_this_turn += 1;
3515 }
3516 pub fn add_ability_resolved_for(
3517 &mut self,
3518 ability: Option<&crate::spellability::SpellAbility>,
3519 ) {
3520 if let Some(ability) = ability {
3521 self.number_ability_resolved.add(ability);
3522 }
3523 self.add_ability_resolved();
3524 }
3525 pub fn get_ability_activated_this_turn(
3526 &self,
3527 ability: Option<&crate::spellability::SpellAbility>,
3528 ) -> u32 {
3529 ability
3530 .map(|ability| self.number_turn_activations.get(ability) as u32)
3531 .unwrap_or(0)
3532 }
3533 pub fn get_ability_activated_this_game(
3534 &self,
3535 ability: Option<&crate::spellability::SpellAbility>,
3536 ) -> u32 {
3537 ability
3538 .map(|ability| self.number_game_activations.get(ability) as u32)
3539 .unwrap_or(0)
3540 }
3541 pub fn get_ability_resolved_this_turn(
3542 &self,
3543 ability: Option<&crate::spellability::SpellAbility>,
3544 ) -> u32 {
3545 ability
3546 .map(|ability| self.number_ability_resolved.get(ability) as u32)
3547 .unwrap_or(0)
3548 }
3549 pub fn get_ability_resolved_this_turn_activators(
3550 &self,
3551 ability: Option<&crate::spellability::SpellAbility>,
3552 ) -> Vec<crate::ids::PlayerId> {
3553 ability
3554 .map(|ability| self.number_ability_resolved.get_activators(ability))
3555 .unwrap_or_default()
3556 }
3557 pub fn reset_ability_resolved_this_turn(&mut self) {
3558 self.ability_resolved_this_turn = 0;
3559 self.number_ability_resolved.clear();
3560 }
3561 pub fn add_chosen_modes(&mut self, modes: Vec<usize>, turn: u32) {
3562 self.chosen_modes = Some(modes);
3563 self.chosen_modes_turn = Some(turn);
3564 }
3565 pub fn reset_chosen_mode_turn(&mut self) {
3566 self.chosen_modes_turn = None;
3567 self.chosen_modes = None;
3568 }
3569 pub fn add_planeswalker_ability_activated(&mut self, loyalty_limit_increase: bool) {
3570 self.planeswalker_abilities_activated += 1;
3571 if self.planeswalker_abilities_activated == 2 && loyalty_limit_increase {
3572 self.planeswalker_activation_limit_used = true;
3573 }
3574 }
3575 pub fn planeswalker_activation_limit_used(&self) -> bool {
3576 self.planeswalker_activation_limit_used
3577 }
3578 pub fn reset_activations_per_turn(&mut self) {
3579 self.ability_activated_this_turn = 0;
3580 self.number_turn_activations.clear();
3581 self.planeswalker_abilities_activated = 0;
3582 self.planeswalker_activation_limit_used = false;
3583 }
3584 pub fn add_can_block_additional(&mut self, n: i32) {
3585 self.can_block_additional += n;
3586 }
3587 pub fn remove_can_block_additional(&mut self, n: i32) {
3588 self.can_block_additional = (self.can_block_additional - n).max(0);
3589 }
3590 pub fn can_block_additional(&self) -> i32 {
3591 self.can_block_additional
3592 }
3593 pub fn add_can_block_any(&mut self) {
3594 self.can_block_any = true;
3595 }
3596 pub fn remove_can_block_any(&mut self) {
3597 self.can_block_any = false;
3598 }
3599 pub fn can_block_any(&self) -> bool {
3600 self.can_block_any
3601 }
3602 pub fn ignore_legend_rule(&self) -> bool {
3603 self.ignore_legend_rule_flag
3604 }
3605 pub fn attack_vigilance(&self) -> bool {
3606 self.has_vigilance()
3607 }
3608 pub fn unlock_room(&mut self) {
3609 self.set_s_var("RoomLocked", "False");
3610 }
3611 pub fn lock_room(&mut self) {
3612 self.set_s_var("RoomLocked", "True");
3613 }
3614 pub fn update_rooms(&mut self) {
3615 if !self.has_s_var("RoomLocked") {
3616 self.set_s_var("RoomLocked", "False");
3617 }
3618 }
3619
3620 pub fn is_modal(&self) -> bool {
3627 self.other_part.as_ref().is_some_and(|other| other.is_modal)
3628 }
3629
3630 pub fn transform(&mut self) {
3631 if let Some(other) = self.other_part.as_mut() {
3632 std::mem::swap(&mut self.card_name, &mut other.name);
3633 std::mem::swap(&mut self.oracle_text, &mut other.oracle_text);
3634 std::mem::swap(&mut self.type_line, &mut other.type_line);
3635 std::mem::swap(&mut self.mana_cost, &mut other.mana_cost);
3636 std::mem::swap(&mut self.color, &mut other.color);
3637 std::mem::swap(&mut self.base_power, &mut other.base_power);
3638 std::mem::swap(&mut self.base_toughness, &mut other.base_toughness);
3639 std::mem::swap(&mut self.keywords, &mut other.keywords);
3640 std::mem::swap(&mut self.abilities, &mut other.abilities);
3641 std::mem::swap(&mut self.triggers, &mut other.triggers);
3642 std::mem::swap(&mut self.static_abilities, &mut other.static_abilities);
3643 std::mem::swap(
3644 &mut self.replacement_effects,
3645 &mut other.replacement_effects,
3646 );
3647 std::mem::swap(&mut self.svars, &mut other.svars);
3648
3649 self.power_modifier = 0;
3651 self.toughness_modifier = 0;
3652 self.damage = 0;
3653 self.granted_keywords.clear();
3654
3655 self.activated_abilities = self
3657 .abilities
3658 .iter()
3659 .enumerate()
3660 .filter_map(|(i, raw)| {
3661 parse_or_warn(parse_activated_ability(raw, i), "ActivatedAbility", raw)
3662 })
3663 .collect();
3664 self.base_ability_count = self.activated_abilities.len();
3665 self.base_trigger_count = self.triggers.len();
3666 self.parsed_svar_cache.clear();
3667 self.refresh_action_specs();
3668
3669 let mut res = std::mem::take(&mut self.replacement_effects);
3671 for re in &mut res {
3672 re.set_host_card(self);
3673 }
3674 self.replacement_effects = res;
3675
3676 self.is_transformed = !self.is_transformed;
3677
3678 self.reset_changed_card_traits_baseline();
3681 self.recompute_changed_card_traits();
3682 }
3683 }
3684
3685 fn activated_to_spell_abilities(&self, list: &[ActivatedAbility]) -> Vec<SpellAbility> {
3686 list.iter()
3687 .map(|ab| {
3688 let mut sa = crate::spellability::build_spell_ability_from_host_card(
3689 self,
3690 &ab.ability_text,
3691 self.controller,
3692 );
3693 sa.is_activated = true;
3694 sa
3695 })
3696 .collect()
3697 }
3698
3699 fn spell_to_activated_abilities(list: &[SpellAbility]) -> Vec<ActivatedAbility> {
3700 list.iter()
3701 .enumerate()
3702 .filter_map(|(i, sa)| parse_activated_ability(&sa.ability_text, i))
3703 .collect()
3704 }
3705
3706 fn capture_changed_card_traits_baseline_if_needed(&mut self) {
3707 if self.trait_base_activated_abilities.is_none() {
3708 self.trait_base_activated_abilities = Some(self.activated_abilities.clone());
3709 self.trait_base_triggers = Some(self.triggers.clone());
3710 self.trait_base_replacement_effects = Some(self.replacement_effects.clone());
3711 self.trait_base_static_abilities = Some(self.static_abilities.clone());
3712 self.trait_base_keywords = Some(self.keywords.clone());
3713 }
3714 }
3715
3716 fn reset_changed_card_traits_baseline(&mut self) {
3717 self.trait_base_activated_abilities = Some(self.activated_abilities.clone());
3718 self.trait_base_triggers = Some(self.triggers.clone());
3719 self.trait_base_replacement_effects = Some(self.replacement_effects.clone());
3720 self.trait_base_static_abilities = Some(self.static_abilities.clone());
3721 self.trait_base_keywords = Some(self.keywords.clone());
3722 }
3723
3724 pub(crate) fn reset_changed_card_traits_baseline_to_current(&mut self) {
3725 self.reset_changed_card_traits_baseline();
3726 self.recompute_changed_card_traits();
3727 }
3728
3729 fn recompute_changed_card_traits(&mut self) {
3730 let Some(base_activated) = self.trait_base_activated_abilities.clone() else {
3731 return;
3732 };
3733 let Some(base_triggers) = self.trait_base_triggers.clone() else {
3734 return;
3735 };
3736 let Some(base_replacements) = self.trait_base_replacement_effects.clone() else {
3737 return;
3738 };
3739 let Some(base_static) = self.trait_base_static_abilities.clone() else {
3740 return;
3741 };
3742 let Some(base_keywords) = self.trait_base_keywords.clone() else {
3743 return;
3744 };
3745
3746 let mut spell_abilities = self.activated_to_spell_abilities(&base_activated);
3747 let mut triggers = base_triggers;
3748 let mut replacements = base_replacements;
3749 let mut static_abilities = base_static;
3750 let mut keywords = base_keywords;
3751
3752 for layer in self.changed_card_traits_by_text.values() {
3753 spell_abilities = crate::card::card_state::apply_spell_ability(layer, spell_abilities);
3754 triggers = crate::card::card_state::apply_trigger(layer, triggers);
3755 replacements = crate::card::card_state::apply_replacement_effect(layer, replacements);
3756 static_abilities =
3757 crate::card::card_state::apply_static_ability(layer, static_abilities);
3758 keywords = crate::card::card_state::apply_keywords(layer, keywords);
3759 }
3760 for layer in self.changed_card_traits.values() {
3761 spell_abilities = crate::card::card_state::apply_spell_ability(layer, spell_abilities);
3762 triggers = crate::card::card_state::apply_trigger(layer, triggers);
3763 replacements = crate::card::card_state::apply_replacement_effect(layer, replacements);
3764 static_abilities =
3765 crate::card::card_state::apply_static_ability(layer, static_abilities);
3766 keywords = crate::card::card_state::apply_keywords(layer, keywords);
3767 }
3768
3769 self.activated_abilities = Self::spell_to_activated_abilities(&spell_abilities);
3770 self.triggers = triggers;
3771 self.replacement_effects = replacements;
3772 self.static_abilities = static_abilities;
3773 self.keywords = keywords;
3774 }
3775
3776 pub fn add_changed_card_traits(
3778 &mut self,
3779 layer: card_trait_changes::CardTraitChanges,
3780 timestamp: i64,
3781 static_id: i64,
3782 ) {
3783 self.capture_changed_card_traits_baseline_if_needed();
3784 self.changed_card_traits
3785 .insert((timestamp, static_id), layer);
3786 self.recompute_changed_card_traits();
3787 }
3788
3789 pub fn add_changed_card_traits_by_text(
3791 &mut self,
3792 layer: card_trait_changes::CardTraitChanges,
3793 timestamp: i64,
3794 static_id: i64,
3795 ) {
3796 self.capture_changed_card_traits_baseline_if_needed();
3797 self.changed_card_traits_by_text
3798 .insert((timestamp, static_id), layer);
3799 self.recompute_changed_card_traits();
3800 }
3801
3802 pub fn remove_changed_card_traits(&mut self, timestamp: i64, static_id: i64) -> bool {
3804 if self
3805 .changed_card_traits
3806 .remove(&(timestamp, static_id))
3807 .is_none()
3808 {
3809 return false;
3810 }
3811 if self.changed_card_traits.is_empty() && self.changed_card_traits_by_text.is_empty() {
3812 if let Some(v) = self.trait_base_activated_abilities.take() {
3813 self.activated_abilities = v;
3814 }
3815 if let Some(v) = self.trait_base_triggers.take() {
3816 self.triggers = v;
3817 }
3818 if let Some(v) = self.trait_base_replacement_effects.take() {
3819 self.replacement_effects = v;
3820 }
3821 if let Some(v) = self.trait_base_static_abilities.take() {
3822 self.static_abilities = v;
3823 }
3824 if let Some(v) = self.trait_base_keywords.take() {
3825 self.keywords = v;
3826 }
3827 return true;
3828 }
3829
3830 self.recompute_changed_card_traits();
3831 true
3832 }
3833
3834 pub fn remove_changed_card_traits_by_text(&mut self, timestamp: i64, static_id: i64) -> bool {
3836 if self
3837 .changed_card_traits_by_text
3838 .remove(&(timestamp, static_id))
3839 .is_none()
3840 {
3841 return false;
3842 }
3843 if self.changed_card_traits.is_empty() && self.changed_card_traits_by_text.is_empty() {
3844 if let Some(v) = self.trait_base_activated_abilities.take() {
3845 self.activated_abilities = v;
3846 }
3847 if let Some(v) = self.trait_base_triggers.take() {
3848 self.triggers = v;
3849 }
3850 if let Some(v) = self.trait_base_replacement_effects.take() {
3851 self.replacement_effects = v;
3852 }
3853 if let Some(v) = self.trait_base_static_abilities.take() {
3854 self.static_abilities = v;
3855 }
3856 if let Some(v) = self.trait_base_keywords.take() {
3857 self.keywords = v;
3858 }
3859 return true;
3860 }
3861
3862 self.recompute_changed_card_traits();
3863 true
3864 }
3865
3866 pub fn clear_changed_card_traits(&mut self) {
3868 self.changed_card_traits.clear();
3869 self.changed_card_traits_by_text.clear();
3870 if let Some(v) = self.trait_base_activated_abilities.take() {
3871 self.activated_abilities = v;
3872 }
3873 if let Some(v) = self.trait_base_triggers.take() {
3874 self.triggers = v;
3875 }
3876 if let Some(v) = self.trait_base_replacement_effects.take() {
3877 self.replacement_effects = v;
3878 }
3879 if let Some(v) = self.trait_base_static_abilities.take() {
3880 self.static_abilities = v;
3881 }
3882 if let Some(v) = self.trait_base_keywords.take() {
3883 self.keywords = v;
3884 }
3885 }
3886
3887 pub fn clear_static_layer_changed_card_traits(&mut self) {
3892 let before = self.changed_card_traits.len();
3893 self.changed_card_traits
3894 .retain(|(_, static_id), _| *static_id >= 0);
3895 if self.changed_card_traits.len() == before {
3896 return;
3897 }
3898 if self.changed_card_traits.is_empty() && self.changed_card_traits_by_text.is_empty() {
3899 if let Some(v) = self.trait_base_activated_abilities.take() {
3900 self.activated_abilities = v;
3901 }
3902 if let Some(v) = self.trait_base_triggers.take() {
3903 self.triggers = v;
3904 }
3905 if let Some(v) = self.trait_base_replacement_effects.take() {
3906 self.replacement_effects = v;
3907 }
3908 if let Some(v) = self.trait_base_static_abilities.take() {
3909 self.static_abilities = v;
3910 }
3911 if let Some(v) = self.trait_base_keywords.take() {
3912 self.keywords = v;
3913 }
3914 return;
3915 }
3916
3917 self.recompute_changed_card_traits();
3918 }
3919
3920 pub fn remove_changed_state(&mut self) {
3921 self.clear_changed_card_traits();
3922 }
3923}
3924
3925impl HasSVars for Card {
3926 fn get_svar(&self, name: &str) -> Option<&str> {
3927 self.get_s_var(name)
3928 }
3929
3930 fn set_svar(&mut self, name: String, value: String) {
3931 self.set_s_var(name, value);
3932 }
3933
3934 fn set_svars(&mut self, new_svars: std::collections::HashMap<String, String>) {
3935 self.set_svars_map(new_svars.into_iter().collect());
3936 }
3937
3938 fn get_svars(&self) -> &std::collections::HashMap<String, String> {
3939 panic!("Card::get_svars is not supported yet; use get_s_var/has_s_var parity accessors");
3940 }
3941
3942 fn remove_svar(&mut self, var: &str) {
3943 self.remove_s_var(var);
3944 }
3945}
3946
3947#[cfg(test)]
3948mod tests {
3949 use super::*;
3950 use std::collections::HashSet;
3951
3952 use forge_carddb::parse_card_script;
3953 use forge_foundation::ManaCost;
3954
3955 #[test]
3956 fn card_power_toughness() {
3957 let mut card = Card::new(
3958 CardId(0),
3959 "Test".to_string(),
3960 PlayerId(0),
3961 CardTypeLine::parse("Creature Bear"),
3962 ManaCost::parse("1 G"),
3963 ColorSet::GREEN,
3964 Some(2),
3965 Some(2),
3966 vec![],
3967 vec![],
3968 );
3969 assert_eq!(card.power(), 2);
3970 assert_eq!(card.toughness(), 2);
3971
3972 card.add_counter(&CounterType::P1P1, 1);
3973 assert_eq!(card.power(), 3);
3974 assert_eq!(card.toughness(), 3);
3975 }
3976
3977 #[test]
3978 fn can_attack() {
3979 let mut card = Card::new(
3980 CardId(0),
3981 "Test".to_string(),
3982 PlayerId(0),
3983 CardTypeLine::parse("Creature Bear"),
3984 ManaCost::parse("1 G"),
3985 ColorSet::GREEN,
3986 Some(2),
3987 Some(2),
3988 vec![],
3989 vec![],
3990 );
3991 card.zone = ZoneType::Battlefield;
3992 assert!(!card.can_attack()); card.summoning_sick = false;
3995 assert!(card.can_attack());
3996
3997 card.tapped = true;
3998 assert!(!card.can_attack()); }
4000
4001 #[test]
4002 fn haste_bypasses_summoning_sickness() {
4003 let mut card = Card::new(
4004 CardId(0),
4005 "Test".to_string(),
4006 PlayerId(0),
4007 CardTypeLine::parse("Creature Bear"),
4008 ManaCost::parse("1 G"),
4009 ColorSet::GREEN,
4010 Some(2),
4011 Some(2),
4012 vec!["Haste".to_string()],
4013 vec![],
4014 );
4015 card.zone = ZoneType::Battlefield;
4016 assert!(card.can_attack()); }
4018
4019 #[test]
4020 fn keyword_helpers() {
4021 let card = Card::new(
4022 CardId(0),
4023 "Test".to_string(),
4024 PlayerId(0),
4025 CardTypeLine::parse("Creature Bear"),
4026 ManaCost::parse("1 G"),
4027 ColorSet::GREEN,
4028 Some(2),
4029 Some(2),
4030 vec![
4031 "Hexproof".to_string(),
4032 "Menace".to_string(),
4033 "Indestructible".to_string(),
4034 ],
4035 vec![],
4036 );
4037 assert!(card.has_hexproof());
4038 assert!(card.has_menace());
4039 assert!(card.has_indestructible());
4040 assert!(!card.has_shroud());
4041 assert!(!card.has_fear());
4042 assert!(!card.has_shadow());
4043 }
4044
4045 #[test]
4046 fn protection_from_color() {
4047 let knight = Card::new(
4048 CardId(0),
4049 "White Knight".to_string(),
4050 PlayerId(0),
4051 CardTypeLine::parse("Creature Knight"),
4052 ManaCost::parse("W W"),
4053 ColorSet::WHITE,
4054 Some(2),
4055 Some(2),
4056 vec!["Protection from black".to_string()],
4057 vec![],
4058 );
4059 let black_source = Card::new(
4060 CardId(1),
4061 "Doom Blade".to_string(),
4062 PlayerId(1),
4063 CardTypeLine::parse("Instant"),
4064 ManaCost::parse("1 B"),
4065 ColorSet::BLACK,
4066 None,
4067 None,
4068 vec![],
4069 vec![],
4070 );
4071 let green_source = Card::new(
4072 CardId(2),
4073 "Giant Growth".to_string(),
4074 PlayerId(1),
4075 CardTypeLine::parse("Instant"),
4076 ManaCost::parse("G"),
4077 ColorSet::GREEN,
4078 None,
4079 None,
4080 vec![],
4081 vec![],
4082 );
4083 assert!(knight.is_protected_from(&black_source));
4084 assert!(!knight.is_protected_from(&green_source));
4085 assert!(knight.has_protection_from("black"));
4086 assert!(!knight.has_protection_from("red"));
4087 }
4088
4089 #[test]
4090 fn ward_and_toxic_parsing() {
4091 let ward_card = Card::new(
4092 CardId(0),
4093 "Ward Bear".to_string(),
4094 PlayerId(0),
4095 CardTypeLine::parse("Creature Bear"),
4096 ManaCost::parse("1 U"),
4097 ColorSet::BLUE,
4098 Some(2),
4099 Some(2),
4100 vec!["Ward:2".to_string()],
4101 vec![],
4102 );
4103 assert_eq!(ward_card.get_ward_cost(), Some("2".to_string()));
4104
4105 let toxic_card = Card::new(
4106 CardId(1),
4107 "Toxic Elf".to_string(),
4108 PlayerId(0),
4109 CardTypeLine::parse("Creature Elf"),
4110 ManaCost::parse("G"),
4111 ColorSet::GREEN,
4112 Some(1),
4113 Some(1),
4114 vec!["Toxic:1".to_string()],
4115 vec![],
4116 );
4117 assert_eq!(toxic_card.get_toxic_count(), Some(1));
4118
4119 let plain = Card::new(
4121 CardId(2),
4122 "Bear".to_string(),
4123 PlayerId(0),
4124 CardTypeLine::parse("Creature Bear"),
4125 ManaCost::parse("1 G"),
4126 ColorSet::GREEN,
4127 Some(2),
4128 Some(2),
4129 vec![],
4130 vec![],
4131 );
4132 assert_eq!(plain.get_ward_cost(), None);
4133 assert_eq!(plain.get_toxic_count(), None);
4134 }
4135
4136 #[test]
4137 fn from_rules_copies_attraction_lights() {
4138 let rules = parse_card_script(
4139 "Name:Balloon Stand\nTypes:Artifact Attraction\nLights: 2 4 6\nOracle:Test.",
4140 )
4141 .expect("card script should parse");
4142 let card = Card::from_rules(&rules, PlayerId(0));
4143 assert_eq!(card.attraction_lights, vec![2, 4, 6]);
4144 assert!(card.has_attraction_light(4));
4145 assert!(!card.has_attraction_light(3));
4146 }
4147
4148 #[test]
4149 fn can_produce_color_mana_uses_mana_abilities_and_reflection() {
4150 let mut game = GameState::new(&["Alice", "Bob"], 20);
4151 let p0 = PlayerId(0);
4152
4153 let white_land = Card::new(
4154 CardId(0),
4155 "White Source".to_string(),
4156 p0,
4157 CardTypeLine::parse("Land"),
4158 ManaCost::parse(""),
4159 ColorSet::COLORLESS,
4160 None,
4161 None,
4162 vec![],
4163 vec!["AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.".to_string()],
4164 );
4165 let reflecting_pool = Card::new(
4166 CardId(1),
4167 "Reflecting Pool".to_string(),
4168 p0,
4169 CardTypeLine::parse("Land"),
4170 ManaCost::parse(""),
4171 ColorSet::COLORLESS,
4172 None,
4173 None,
4174 vec![],
4175 vec!["AB$ ManaReflected | Cost$ T | Valid$ Land.YouCtrl | ReflectProperty$ Produce | ColorOrType$ Type | Produced$ W | SpellDescription$ Add one mana of any type that a land you control could produce.".to_string()],
4176 );
4177
4178 let white_id = game.create_card(white_land);
4179 let pool_id = game.create_card(reflecting_pool);
4180 game.move_card(white_id, ZoneType::Battlefield, p0);
4181 game.move_card(pool_id, ZoneType::Battlefield, p0);
4182
4183 let mut white = HashSet::new();
4184 white.insert("white".to_string());
4185 assert!(game.card(white_id).can_produce_color_mana(&game, &white));
4186 assert!(game.card(pool_id).can_produce_color_mana(&game, &white));
4187 }
4188
4189 #[test]
4190 fn can_produce_same_mana_type_with_uses_mana_ability_overlap() {
4191 let mut game = GameState::new(&["Alice", "Bob"], 20);
4192 let p0 = PlayerId(0);
4193
4194 let island = Card::new(
4195 CardId(0),
4196 "Island Source".to_string(),
4197 p0,
4198 CardTypeLine::parse("Land"),
4199 ManaCost::parse(""),
4200 ColorSet::COLORLESS,
4201 None,
4202 None,
4203 vec![],
4204 vec!["AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}.".to_string()],
4205 );
4206 let prism = Card::new(
4207 CardId(1),
4208 "Prism".to_string(),
4209 p0,
4210 CardTypeLine::parse("Artifact"),
4211 ManaCost::parse("2"),
4212 ColorSet::COLORLESS,
4213 None,
4214 None,
4215 vec![],
4216 vec!["AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}.".to_string()],
4217 );
4218
4219 let island_id = game.create_card(island);
4220 let prism_id = game.create_card(prism);
4221 game.move_card(island_id, ZoneType::Battlefield, p0);
4222 game.move_card(prism_id, ZoneType::Battlefield, p0);
4223
4224 assert!(game
4225 .card(prism_id)
4226 .can_produce_same_mana_type_with(&game, game.card(island_id)));
4227 }
4228}