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