1use std::collections::{HashMap, HashSet};
7
8use forge_foundation::{CardStateName, CardTypeLine, ColorSet, ManaCost};
9
10use crate::ability::activated::parse_activated_ability;
11use crate::card::trait_card_trait_changes::CardTraitChanges as ICardTraitChanges;
12use crate::card::{card_copy_service, card_property, Card};
13use crate::core::HasSVars;
14use crate::game_object::GameObject;
15use crate::ids::PlayerId;
16use crate::keyword::keyword_collection::KeywordCollection;
17use crate::keyword::keyword_interface::KeywordInterface;
18use crate::keyword::trait_keywords_change::KeywordsChange as IKeywordsChange;
19use crate::replacement::ReplacementEffect;
20use crate::spellability::SpellAbility;
21use crate::staticability::StaticAbility;
22use crate::trigger::Trigger;
23use crate::util::{HasName, ITranslatable};
24
25use crate::card_trait_base::CardTraitBase;
26
27pub type CardType = CardTypeLine;
28pub type CardTypeView = CardTypeLine;
29pub type FCollection<T> = Vec<T>;
30pub type FCollectionView<T> = Vec<T>;
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
33pub enum CardRarity {
34 #[default]
35 Unknown,
36}
37
38pub struct CardEdition;
39impl CardEdition {
40 pub const UNKNOWN_CODE: &'static str = "UNKNOWN";
41}
42
43#[derive(Debug, Clone, Default)]
44pub struct LandTraitChanges;
45
46impl LandTraitChanges {
47 pub fn new() -> Self {
48 Self
49 }
50}
51
52impl ICardTraitChanges for LandTraitChanges {
53 fn change_text(&mut self) {}
54 fn copy(&self, _host: crate::ids::CardId, _lki: bool) -> Box<dyn ICardTraitChanges> {
55 Box::new(self.clone())
56 }
57}
58
59impl IKeywordsChange for LandTraitChanges {
60 fn apply_keywords(&self, _list: &mut KeywordCollection) {}
61 fn copy(&self) -> Box<dyn IKeywordsChange> {
62 Box::new(self.clone())
63 }
64}
65
66#[derive(Debug, Clone)]
68pub struct CardState {
69 pub state_name: CardStateName,
70 pub name: String,
71 pub r#type: CardType,
72 pub changed_type: Option<CardTypeView>,
73 pub mana_cost: ManaCost,
74 pub perpetual_adjusted_mana_cost: Option<ManaCost>,
75 pub color: ColorSet,
76 pub oracle_text: String,
77 pub functional_variant_name: Option<String>,
78 pub flavor_name: Option<String>,
79 pub base_power: i32,
80 pub base_toughness: i32,
81 pub base_power_string: Option<String>,
82 pub base_toughness_string: Option<String>,
83 pub base_loyalty: String,
84 pub base_defense: String,
85 pub intrinsic_keywords: KeywordCollection,
86 pub attraction_lights: Option<HashSet<i32>>,
87 pub abilities: FCollection<SpellAbility>,
88 pub triggers: FCollection<Trigger>,
89 pub replacement_effects: FCollection<ReplacementEffect>,
90 pub static_abilities: FCollection<StaticAbility>,
91 pub image_key: String,
92 pub s_vars: HashMap<String, String>,
93 pub ability_for_trigger: HashMap<String, SpellAbility>,
94 pub cached_keywords: KeywordCollection,
95 pub rarity: CardRarity,
96 pub set_code: String,
97 pub card: Card,
98 pub land_ability: Option<SpellAbility>,
99 pub aura_ability: Option<SpellAbility>,
100 pub permanent_ability: Option<SpellAbility>,
101 pub loyalty_rep: Option<ReplacementEffect>,
102 pub defense_rep: Option<ReplacementEffect>,
103 pub saga_rep: Option<ReplacementEffect>,
104 pub adventure_rep: Option<ReplacementEffect>,
105 pub omen_rep: Option<ReplacementEffect>,
106 pub manifest_up: Option<SpellAbility>,
107 pub cloak_up: Option<SpellAbility>,
108 pub land_trait_changes: LandTraitChanges,
109}
110
111impl CardState {
112 pub fn new(card: Card, name: CardStateName) -> Self {
113 Self {
114 state_name: name,
115 name: String::new(),
116 r#type: CardTypeLine::default(),
117 changed_type: None,
118 mana_cost: ManaCost::no_cost(),
119 perpetual_adjusted_mana_cost: None,
120 color: ColorSet::COLORLESS,
121 oracle_text: String::new(),
122 functional_variant_name: None,
123 flavor_name: None,
124 base_power: 0,
125 base_toughness: 0,
126 base_power_string: None,
127 base_toughness_string: None,
128 base_loyalty: String::new(),
129 base_defense: String::new(),
130 intrinsic_keywords: KeywordCollection::new(),
131 attraction_lights: None,
132 abilities: Vec::new(),
133 triggers: Vec::new(),
134 replacement_effects: Vec::new(),
135 static_abilities: Vec::new(),
136 image_key: String::new(),
137 s_vars: HashMap::new(),
138 ability_for_trigger: HashMap::new(),
139 cached_keywords: KeywordCollection::new(),
140 rarity: CardRarity::Unknown,
141 set_code: CardEdition::UNKNOWN_CODE.to_string(),
142 card,
143 land_ability: None,
144 aura_ability: None,
145 permanent_ability: None,
146 loyalty_rep: None,
147 defense_rep: None,
148 saga_rep: None,
149 adventure_rep: None,
150 omen_rep: None,
151 manifest_up: None,
152 cloak_up: None,
153 land_trait_changes: LandTraitChanges::new(),
154 }
155 }
156
157 pub fn get_card(&self) -> &Card {
158 &self.card
159 }
160 pub fn get_name(&self) -> &str {
161 &self.name
162 }
163 pub fn set_name(&mut self, name: String) {
164 self.name = name;
165 }
166 pub fn get_state_name(&self) -> CardStateName {
167 self.state_name
168 }
169 pub fn get_type_with_changes(&self) -> &CardTypeView {
170 self.changed_type.as_ref().unwrap_or(&self.r#type)
171 }
172 pub fn update_types(&mut self) {
173 self.changed_type = Some(self.r#type.clone());
174 }
175 pub fn update_types_for_view(&self) {}
176 pub fn get_type(&self) -> &CardTypeView {
177 &self.r#type
178 }
179 pub fn add_type(&mut self, type_value: String) {
180 self.r#type.add_type(&type_value);
181 }
182 pub fn add_types<I: IntoIterator<Item = String>>(&mut self, type_values: I) {
183 for t in type_values {
184 self.r#type.add_type(&t);
185 }
186 }
187 pub fn set_type(&mut self, type_value: CardType) {
188 self.r#type = type_value;
189 }
190 pub fn remove_card_types_with_sanitize(&mut self, _sanitize: bool) {
191 self.r#type.core_types.clear();
192 self.r#type.subtypes.clear();
193 }
194 pub fn get_mana_cost(&self) -> &ManaCost {
195 &self.mana_cost
196 }
197 pub fn set_mana_cost(&mut self, mana_cost: ManaCost) {
198 self.mana_cost = mana_cost;
199 }
200 pub fn calculate_perpetual_adjusted_mana_cost_for_state(&mut self) {}
201 pub fn get_perpetual_adjusted_mana_cost(&self) -> &ManaCost {
202 self.perpetual_adjusted_mana_cost
203 .as_ref()
204 .unwrap_or(&self.mana_cost)
205 }
206 pub fn get_color(&self) -> ColorSet {
207 self.color
208 }
209 pub fn add_color(&mut self, color: ColorSet) {
210 self.color = self.color.union(color);
211 }
212 pub fn set_color(&mut self, color: ColorSet) {
213 self.color = color;
214 }
215 pub fn get_oracle_text(&self) -> &str {
216 &self.oracle_text
217 }
218 pub fn set_oracle_text(&mut self, oracle_text: String) {
219 self.oracle_text = oracle_text;
220 }
221 pub fn get_functional_variant_name(&self) -> Option<&str> {
222 self.functional_variant_name.as_deref()
223 }
224 pub fn set_functional_variant_name(&mut self, functional_variant_name: Option<String>) {
225 self.functional_variant_name = functional_variant_name.filter(|s| !s.is_empty());
226 }
227 pub fn get_flavor_name(&self) -> Option<&str> {
228 self.flavor_name.as_deref()
229 }
230 pub fn set_flavor_name(&mut self, flavor_name: Option<String>) {
231 self.flavor_name = flavor_name;
232 }
233 pub fn get_base_power(&self) -> i32 {
234 self.base_power
235 }
236 pub fn set_base_power(&mut self, base_power: i32) {
237 self.base_power = base_power;
238 }
239 pub fn get_base_toughness(&self) -> i32 {
240 self.base_toughness
241 }
242 pub fn set_base_toughness(&mut self, base_toughness: i32) {
243 self.base_toughness = base_toughness;
244 }
245 pub fn get_base_power_string(&self) -> Option<&str> {
246 self.base_power_string.as_deref()
247 }
248 pub fn get_base_toughness_string(&self) -> Option<&str> {
249 self.base_toughness_string.as_deref()
250 }
251 pub fn set_base_power_string(&mut self, value: Option<String>) {
252 self.base_power_string = value;
253 }
254 pub fn set_base_toughness_string(&mut self, value: Option<String>) {
255 self.base_toughness_string = value;
256 }
257 pub fn get_base_loyalty(&self) -> &str {
258 &self.base_loyalty
259 }
260 pub fn set_base_loyalty(&mut self, value: String) {
261 self.base_loyalty = value;
262 }
263 pub fn get_base_defense(&self) -> &str {
264 &self.base_defense
265 }
266 pub fn set_base_defense(&mut self, value: String) {
267 self.base_defense = value;
268 }
269 pub fn get_attraction_lights(&self) -> Option<&HashSet<i32>> {
270 self.attraction_lights.as_ref()
271 }
272 pub fn set_attraction_lights(&mut self, attraction_lights: Option<HashSet<i32>>) {
273 self.attraction_lights = attraction_lights;
274 }
275 pub fn get_cached_keywords(
276 &self,
277 ) -> Vec<&crate::keyword::keyword_instance::KeywordInstanceData> {
278 self.cached_keywords.get_values()
279 }
280 pub fn set_cached_keywords(&mut self, collection: KeywordCollection) {
281 self.cached_keywords = collection;
282 }
283 pub fn has_keyword_enum(&self, key: crate::keyword::keyword_instance::Keyword) -> bool {
284 self.cached_keywords.contains_keyword(key)
285 }
286 pub fn get_intrinsic_keywords(
287 &self,
288 ) -> Vec<&crate::keyword::keyword_instance::KeywordInstanceData> {
289 self.intrinsic_keywords.get_values()
290 }
291 pub fn has_intrinsic_keyword_state(&self, keyword: &str) -> bool {
292 self.intrinsic_keywords.contains(keyword)
293 }
294 pub fn set_intrinsic_keywords(
295 &mut self,
296 _intrinsic_keywords: Vec<KeywordInterface>,
297 _lki: bool,
298 ) {
299 }
300 pub fn update_keywords_cache_for_state(&mut self) {
301 self.cached_keywords = self.intrinsic_keywords.clone();
302 }
303 pub fn add_intrinsic_keyword_with_init_traits(
304 &mut self,
305 keyword: String,
306 _init_traits: bool,
307 ) -> Option<KeywordInterface> {
308 if self.intrinsic_keywords.add(&keyword) {
309 Some(KeywordInterface::new(
310 crate::keyword::Keyword::smart_value_of(&keyword),
311 keyword,
312 ))
313 } else {
314 None
315 }
316 }
317 pub fn add_intrinsic_keywords_for_state(
318 &mut self,
319 keywords: Vec<String>,
320 init_traits: bool,
321 ) -> bool {
322 let mut changed = false;
323 for k in keywords {
324 changed |= self
325 .add_intrinsic_keyword_with_init_traits(k, init_traits)
326 .is_some();
327 }
328 changed
329 }
330 pub fn remove_intrinsic_keyword_for_state(&mut self, keyword: &str) -> bool {
331 self.intrinsic_keywords.remove(keyword)
332 }
333 pub fn get_spell_abilities(&self) -> FCollectionView<SpellAbility> {
334 crate::perf::increment(crate::perf::Metric::CardStateCollectionClones, 1);
335 self.abilities.clone()
336 }
337 pub fn get_mana_abilities(&self) -> FCollectionView<SpellAbility> {
338 crate::perf::increment(crate::perf::Metric::CardStateCollectionClones, 1);
339 self.abilities
340 .clone()
341 .into_iter()
342 .filter(|sa| sa.is_mana_ability)
343 .collect()
344 }
345 pub fn get_non_mana_abilities(&self) -> FCollectionView<SpellAbility> {
346 crate::perf::increment(crate::perf::Metric::CardStateCollectionClones, 1);
347 self.abilities
348 .clone()
349 .into_iter()
350 .filter(|sa| !sa.is_mana_ability)
351 .collect()
352 }
353 pub fn update_spell_abilities(&self, _new_col: &mut FCollection<SpellAbility>) {}
354 pub fn get_land_trait_changes(&self) -> &LandTraitChanges {
355 &self.land_trait_changes
356 }
357 pub fn get_intrinsic_spell_abilities(&self) -> Vec<SpellAbility> {
358 crate::perf::increment(crate::perf::Metric::CardStateCollectionClones, 1);
359 self.abilities
360 .clone()
361 .into_iter()
362 .filter(|sa| sa.is_activated || sa.is_trigger || sa.is_spell)
363 .collect()
364 }
365 pub fn get_first_ability(&self) -> Option<SpellAbility> {
366 self.get_intrinsic_spell_abilities().into_iter().next()
367 }
368 pub fn get_first_spell_ability(&self) -> Option<SpellAbility> {
369 self.get_non_mana_abilities().into_iter().next()
370 }
371 pub fn get_first_spell_ability_with_fallback(&self) -> Option<SpellAbility> {
372 self.get_first_spell_ability()
373 .or_else(|| self.get_first_ability())
374 }
375 pub fn get_aura_spell(&self) -> Option<SpellAbility> {
376 self.aura_ability.clone()
377 }
378 pub fn has_spell_ability_id(&self, id: i32) -> bool {
379 self.get_spell_abilities()
380 .iter()
381 .any(|sa| sa.source_trigger_id == Some(id as u32))
382 }
383 pub fn add_spell_ability_state(&mut self, ability: SpellAbility) -> bool {
384 self.abilities.push(ability);
385 true
386 }
387 pub fn get_triggers(&self) -> FCollectionView<Trigger> {
388 crate::perf::increment(crate::perf::Metric::CardStateCollectionClones, 1);
389 self.triggers.clone()
390 }
391 pub fn has_trigger_id(&self, id: i32) -> bool {
392 self.get_triggers().iter().any(|t| t.id == id as u32)
393 }
394 pub fn add_trigger_state(&mut self, trigger: Trigger) -> bool {
395 self.triggers.push(trigger);
396 true
397 }
398 pub fn get_static_abilities(&self) -> FCollectionView<StaticAbility> {
399 crate::perf::increment(crate::perf::Metric::CardStateCollectionClones, 1);
400 self.static_abilities.clone()
401 }
402 pub fn add_static_ability_state(&mut self, static_ability: StaticAbility) -> bool {
403 self.static_abilities.push(static_ability);
404 true
405 }
406 pub fn remove_static_ability_state(&mut self, _static_ability: StaticAbility) -> bool {
407 false
408 }
409 pub fn get_replacement_effects(&self) -> FCollectionView<ReplacementEffect> {
410 crate::perf::increment(crate::perf::Metric::CardStateCollectionClones, 1);
411 self.replacement_effects.clone()
412 }
413 pub fn add_replacement_effect_state(&mut self, replacement_effect: ReplacementEffect) -> bool {
414 self.replacement_effects.push(replacement_effect);
415 true
416 }
417 pub fn has_replacement_effect_id(&self, id: i32) -> bool {
418 self.get_replacement_effect(id).is_some()
419 }
420 pub fn get_replacement_effect(&self, id: i32) -> Option<ReplacementEffect> {
421 let _ = id;
422 None
423 }
424 pub fn get_foil(&self) -> i32 {
425 self.get_svar("Foil")
426 .and_then(|s| s.parse().ok())
427 .unwrap_or(0)
428 }
429 pub fn copy_from_state(&mut self, _source: &CardState, _lki: bool) {}
430 pub fn copy_from_with_trait_base(
431 &mut self,
432 _source: &CardState,
433 _lki: bool,
434 _ctb: &CardTraitBase,
435 ) {
436 }
437 pub fn add_abilities_from_state(&mut self, _source: &CardState, _lki: bool) {}
438 pub fn copy(&self, host: Card, name: CardStateName, _lki: bool) -> CardState {
439 CardState::new(host, name)
440 }
441 pub fn get_rarity(&self) -> CardRarity {
442 self.rarity
443 }
444 pub fn set_rarity(&mut self, rarity: CardRarity) {
445 self.rarity = rarity;
446 }
447 pub fn get_set_code(&self) -> &str {
448 &self.set_code
449 }
450 pub fn set_set_code(&mut self, set_code: String) {
451 self.set_code = set_code;
452 }
453 pub fn get_image_key(&self) -> &str {
454 &self.image_key
455 }
456 pub fn set_image_key(&mut self, image_key: String) {
457 self.image_key = image_key;
458 }
459 pub fn get_traits(&self) -> Vec<CardTraitBase> {
460 Vec::new()
461 }
462 pub fn reset_original_host(&mut self, _old_host: Card) {}
463 pub fn update_changed_text(&mut self) {}
464 pub fn change_text_intrinsic(
465 &mut self,
466 _color_map: HashMap<String, String>,
467 _type_map: HashMap<String, String>,
468 ) {
469 }
470 pub fn has_chapter(&self) -> bool {
471 self.get_triggers().iter().any(|t| t.ir.chapter.is_some())
472 }
473 pub fn get_final_chapter_nr(&self) -> i32 {
474 0
475 }
476 pub fn get_manifest_up(&self) -> Option<SpellAbility> {
477 self.manifest_up.clone()
478 }
479 pub fn get_cloak_up(&self) -> Option<SpellAbility> {
480 self.cloak_up.clone()
481 }
482 pub fn get_ability_for_trigger(&self, svar: String) -> Option<SpellAbility> {
483 self.ability_for_trigger.get(&svar).cloned()
484 }
485}
486
487impl HasSVars for CardState {
488 fn get_svar(&self, name: &str) -> Option<&str> {
489 self.s_vars.get(name).map(String::as_str)
490 }
491
492 fn set_svar(&mut self, name: String, value: String) {
493 self.s_vars.insert(name, value);
494 }
495
496 fn set_svars(&mut self, new_svars: HashMap<String, String>) {
497 self.s_vars = new_svars;
498 }
499
500 fn get_svars(&self) -> &HashMap<String, String> {
501 &self.s_vars
502 }
503
504 fn remove_svar(&mut self, var: &str) {
505 self.s_vars.remove(var);
506 }
507}
508
509impl GameObject for CardState {
510 fn is_valid_single(
511 &self,
512 restriction: &str,
513 _source_controller: PlayerId,
514 _source: &Card,
515 _spell_ability: &CardTraitBase,
516 ) -> bool {
517 self.has_property(restriction, _source_controller, _source, _spell_ability)
518 }
519
520 fn has_property(
521 &self,
522 property: &str,
523 source_controller: PlayerId,
524 _source: &Card,
525 _spell_ability: &CardTraitBase,
526 ) -> bool {
527 match property {
528 "Card" | "card" => true,
529 "Permanent" => self.r#type.is_permanent(),
530 "Creature" => self.r#type.is_creature(),
531 "Land" => self.r#type.is_land(),
532 "Artifact" => self.r#type.is_artifact(),
533 "Enchantment" => self.r#type.is_enchantment(),
534 "Planeswalker" => self.r#type.is_planeswalker(),
535 "Instant" => self.r#type.is_instant(),
536 "Sorcery" => self.r#type.is_sorcery(),
537 "Basic" => self.r#type.is_basic(),
538 "Legendary" => self.r#type.is_legendary(),
539 "YouCtrl" => self.card.controller == source_controller,
540 "OppCtrl" => self.card.controller != source_controller,
541 _ => false,
542 }
543 }
544}
545
546impl HasName for CardState {
547 fn get_name(&self) -> &str {
548 &self.name
549 }
550}
551
552impl ITranslatable for CardState {
553 fn get_translation_key(&self) -> String {
554 self.flavor_name
555 .clone()
556 .unwrap_or_else(|| self.name.clone())
557 }
558
559 fn get_untranslated_type(&self) -> String {
560 self.r#type.to_string()
561 }
562
563 fn get_translated_name(&self) -> String {
564 self.get_translation_key()
565 }
566}
567
568pub fn update_types(card: &mut Card) {
569 card.type_line = CardTypeLine::parse(&card.type_line.to_string());
570}
571
572pub fn update_types_for_view(card: &mut Card) {
573 let _ = card.type_line.to_string();
574}
575
576pub fn add_type(card: &mut Card, ty: &str) {
577 card.type_line.add_type(ty);
578}
579
580pub fn remove_type(card: &mut Card, ty: &str) {
581 card.type_line
582 .supertypes
583 .retain(|st| !st.name().eq_ignore_ascii_case(ty));
584 card.type_line
585 .core_types
586 .retain(|ct| !ct.name().eq_ignore_ascii_case(ty));
587 card.type_line
588 .subtypes
589 .retain(|s| !s.eq_ignore_ascii_case(ty));
590}
591
592pub fn remove_card_types(card: &mut Card) {
593 card.type_line.core_types.clear();
594 card.type_line.subtypes.clear();
595}
596
597pub fn calculate_perpetual_adjusted_mana_cost(card: &mut Card) {
598 card.update_mana_cost_for_view();
599}
600
601pub fn add_color(card: &mut Card, color: ColorSet) {
602 card.color = card.color.union(color);
603}
604
605pub fn has_keyword(card: &Card, keyword: &str) -> bool {
606 if card
607 .cant_have_keywords
608 .contains(&keyword.to_ascii_lowercase())
609 {
610 return false;
611 }
612 card.keywords.contains_string_ignore_case(keyword)
613 || card.granted_keywords.contains_string_ignore_case(keyword)
614 || card.pump_keywords.contains_string_ignore_case(keyword)
615}
616
617pub fn has_intrinsic_keyword(card: &Card, keyword: &str) -> bool {
618 card.keywords.contains_string_ignore_case(keyword)
619}
620
621pub fn update_keywords_cache(card: &mut Card) {
622 let mut seen = HashSet::new();
626 let keywords = card.keywords.as_string_list();
627 card.keywords.clear();
628 for kw in keywords {
629 let parsed = crate::keyword::keyword_collection::parse_keyword_string(&kw).0;
630 let stackable = !parsed.is_multiple_redundant();
631 let key = kw.to_ascii_lowercase();
632 if stackable || seen.insert(key) {
633 card.keywords.add(&kw);
634 }
635 }
636}
637
638pub fn add_intrinsic_keyword(card: &mut Card, keyword: &str) -> bool {
639 if keyword.trim().is_empty() {
640 return false;
641 }
642 card.keywords.add(keyword)
643}
644
645pub fn add_intrinsic_keywords<'a>(
646 card: &mut Card,
647 keywords: impl IntoIterator<Item = &'a str>,
648) -> bool {
649 let mut changed = false;
650 for kw in keywords {
651 changed |= add_intrinsic_keyword(card, kw);
652 }
653 changed
654}
655
656pub fn remove_intrinsic_keyword(card: &mut Card, keyword: &str) -> bool {
657 card.keywords.remove(keyword)
658}
659
660pub fn apply_spell_ability(
661 layer: &crate::card::card_trait_changes::CardTraitChanges,
662 list: Vec<SpellAbility>,
663) -> Vec<SpellAbility> {
664 layer.apply_spell_ability(list)
665}
666
667pub fn apply_trigger(
668 layer: &crate::card::card_trait_changes::CardTraitChanges,
669 list: Vec<Trigger>,
670) -> Vec<Trigger> {
671 layer.apply_trigger(list)
672}
673
674pub fn apply_replacement_effect(
675 layer: &crate::card::card_trait_changes::CardTraitChanges,
676 list: Vec<ReplacementEffect>,
677) -> Vec<ReplacementEffect> {
678 layer.apply_replacement_effect(list)
679}
680
681pub fn apply_static_ability(
682 layer: &crate::card::card_trait_changes::CardTraitChanges,
683 list: Vec<StaticAbility>,
684) -> Vec<StaticAbility> {
685 layer.apply_static_ability(list)
686}
687
688pub fn apply_keywords(
689 layer: &crate::card::card_trait_changes::CardTraitChanges,
690 mut list: crate::keyword::keyword_collection::KeywordCollection,
691) -> crate::keyword::keyword_collection::KeywordCollection {
692 if layer.remove_all {
693 list.clear();
694 }
695 list
696}
697
698pub fn copy(from: &Card, to: &mut Card) {
699 copy_from(from, to);
700}
701
702pub fn has_spell_ability(card: &Card, sa: &SpellAbility) -> bool {
703 card.activated_abilities
704 .iter()
705 .any(|ab| ab.ability_text == sa.ability_text)
706}
707
708pub fn add_spell_ability(card: &mut Card, sa: &SpellAbility) -> bool {
709 card.abilities.push(sa.ability_text.clone());
710 if let Some(parsed) = parse_activated_ability(&sa.ability_text, card.activated_abilities.len())
711 {
712 card.activated_abilities.push(parsed);
713 return true;
714 }
715 false
716}
717
718pub fn has_trigger(card: &Card, trigger_id: u32) -> bool {
719 card.triggers.iter().any(|t| t.id == trigger_id)
720}
721
722pub fn add_trigger(card: &mut Card, mut trig: Trigger) -> bool {
723 trig.bind_host_card_id(card.id);
724 card.triggers.push(trig);
725 true
726}
727
728pub fn add_static_ability(card: &mut Card, st_ab: StaticAbility) -> bool {
729 card.static_abilities.push(st_ab);
730 true
731}
732
733pub fn remove_static_ability(card: &mut Card, mode: crate::staticability::StaticMode) -> bool {
734 let before = card.static_abilities.len();
735 card.static_abilities.retain(|sa| !sa.check_mode(&mode));
736 before != card.static_abilities.len()
737}
738
739pub fn add_replacement_effect(card: &mut Card, mut re: ReplacementEffect) -> bool {
740 re.set_host_card(card);
741 card.replacement_effects.push(re);
742 true
743}
744
745pub fn has_replacement_effect(card: &Card) -> bool {
746 !card.replacement_effects.is_empty()
747}
748
749pub fn has_s_var(card: &Card, key: &str) -> bool {
750 card.svars.contains_key(key) || card.granted_svars.contains_key(key)
751}
752
753pub fn remove_s_var(card: &mut Card, key: &str) {
754 card.svars.remove(key);
755}
756
757pub fn copy_from(source: &Card, target: &mut Card) {
758 target.changed_card_traits = source.changed_card_traits.clone();
759 target.changed_card_traits_by_text = source.changed_card_traits_by_text.clone();
760
761 target
762 .svars
763 .retain(|k, _| !k.starts_with("TextColor:") && !k.starts_with("TextType:"));
764 target.copy_changed_text_from(source);
765
766 target.update_type_cache();
767}
768
769pub fn add_abilities_from(source: &Card, target: &mut Card) {
770 card_copy_service::copy_copiable_characteristics(source, target);
772
773 target
774 .activated_abilities
775 .extend(source.activated_abilities.iter().cloned());
776 target.triggers.extend(source.triggers.iter().cloned());
777 target
778 .replacement_effects
779 .extend(source.replacement_effects.iter().cloned());
780 target
781 .static_abilities
782 .extend(source.static_abilities.iter().cloned());
783}
784
785pub fn has_property(card: &Card, property: &str) -> bool {
786 card_property::card_has_property(card, property, card.controller)
787}
788
789pub fn reset_original_host(card: &mut Card) {
790 card.effect_source = None;
791}
792
793pub fn update_changed_text(card: &mut Card) {
794 card.update_rules_view();
795}
796
797pub fn change_text_intrinsic(card: &mut Card) {
798 card.update_changed_text();
799}
800
801pub fn has_chapter(card: &Card) -> bool {
802 card.triggers
803 .iter()
804 .any(|t| t.ir.chapter.is_some() || t.description.to_ascii_lowercase().contains("chapter"))
805}
806
807pub fn set_type(card: &mut Card, type_line: &str) {
808 card.type_line = CardTypeLine::parse(type_line);
809}