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