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 self.get_triggers()
475 .iter()
476 .filter_map(|trigger| trigger.get_chapter())
477 .max()
478 .unwrap_or(0)
479 }
480 pub fn get_manifest_up(&self) -> Option<SpellAbility> {
481 self.manifest_up.clone()
482 }
483 pub fn get_cloak_up(&self) -> Option<SpellAbility> {
484 self.cloak_up.clone()
485 }
486 pub fn get_ability_for_trigger(&self, svar: String) -> Option<SpellAbility> {
487 self.ability_for_trigger.get(&svar).cloned()
488 }
489}
490
491impl HasSVars for CardState {
492 fn get_svar(&self, name: &str) -> Option<&str> {
493 self.s_vars.get(name).map(String::as_str)
494 }
495
496 fn set_svar(&mut self, name: String, value: String) {
497 self.s_vars.insert(name, value);
498 }
499
500 fn set_svars(&mut self, new_svars: HashMap<String, String>) {
501 self.s_vars = new_svars;
502 }
503
504 fn get_svars(&self) -> &HashMap<String, String> {
505 &self.s_vars
506 }
507
508 fn remove_svar(&mut self, var: &str) {
509 self.s_vars.remove(var);
510 }
511}
512
513impl GameObject for CardState {
514 fn is_valid_single(
515 &self,
516 restriction: &str,
517 _source_controller: PlayerId,
518 _source: &Card,
519 _spell_ability: &CardTraitBase,
520 ) -> bool {
521 self.has_property(restriction, _source_controller, _source, _spell_ability)
522 }
523
524 fn has_property(
525 &self,
526 property: &str,
527 source_controller: PlayerId,
528 _source: &Card,
529 _spell_ability: &CardTraitBase,
530 ) -> bool {
531 match property {
532 "Card" | "card" => true,
533 "Permanent" => self.r#type.is_permanent(),
534 "Creature" => self.r#type.is_creature(),
535 "Land" => self.r#type.is_land(),
536 "Artifact" => self.r#type.is_artifact(),
537 "Enchantment" => self.r#type.is_enchantment(),
538 "Planeswalker" => self.r#type.is_planeswalker(),
539 "Instant" => self.r#type.is_instant(),
540 "Sorcery" => self.r#type.is_sorcery(),
541 "Basic" => self.r#type.is_basic(),
542 "Legendary" => self.r#type.is_legendary(),
543 "YouCtrl" => self.card.controller == source_controller,
544 "OppCtrl" => self.card.controller != source_controller,
545 _ => false,
546 }
547 }
548}
549
550impl HasName for CardState {
551 fn get_name(&self) -> &str {
552 &self.name
553 }
554}
555
556impl ITranslatable for CardState {
557 fn get_translation_key(&self) -> String {
558 self.flavor_name
559 .clone()
560 .unwrap_or_else(|| self.name.clone())
561 }
562
563 fn get_untranslated_type(&self) -> String {
564 self.r#type.to_string()
565 }
566
567 fn get_translated_name(&self) -> String {
568 self.get_translation_key()
569 }
570}
571
572pub fn update_types(card: &mut Card) {
573 card.type_line = CardTypeLine::parse(&card.type_line.to_string());
574}
575
576pub fn update_types_for_view(card: &mut Card) {
577 let _ = card.type_line.to_string();
578}
579
580pub fn add_type(card: &mut Card, ty: &str) {
581 card.type_line.add_type(ty);
582}
583
584pub fn remove_type(card: &mut Card, ty: &str) {
585 card.type_line
586 .supertypes
587 .retain(|st| !st.name().eq_ignore_ascii_case(ty));
588 card.type_line
589 .core_types
590 .retain(|ct| !ct.name().eq_ignore_ascii_case(ty));
591 card.type_line
592 .subtypes
593 .retain(|s| !s.eq_ignore_ascii_case(ty));
594}
595
596pub fn remove_card_types(card: &mut Card) {
597 card.type_line.core_types.clear();
598 card.type_line.subtypes.clear();
599}
600
601pub fn calculate_perpetual_adjusted_mana_cost(card: &mut Card) {
602 card.update_mana_cost_for_view();
603}
604
605pub fn add_color(card: &mut Card, color: ColorSet) {
606 card.color = card.color.union(color);
607}
608
609pub fn has_keyword(card: &Card, keyword: &str) -> bool {
610 if card
611 .cant_have_keywords
612 .contains(&keyword.to_ascii_lowercase())
613 {
614 return false;
615 }
616 card.keywords.contains_string_ignore_case(keyword)
617 || card.granted_keywords.contains_string_ignore_case(keyword)
618 || card.pump_keywords.contains_string_ignore_case(keyword)
619}
620
621pub fn has_intrinsic_keyword(card: &Card, keyword: &str) -> bool {
622 card.keywords.contains_string_ignore_case(keyword)
623}
624
625pub fn update_keywords_cache(card: &mut Card) {
626 let mut seen = HashSet::new();
630 let keywords = card.keywords.as_string_list();
631 card.keywords.clear();
632 for kw in keywords {
633 let parsed = crate::keyword::keyword_collection::parse_keyword_string(&kw).0;
634 let stackable = !parsed.is_multiple_redundant();
635 let key = kw.to_ascii_lowercase();
636 if stackable || seen.insert(key) {
637 card.keywords.add(&kw);
638 }
639 }
640}
641
642pub fn add_intrinsic_keyword(card: &mut Card, keyword: &str) -> bool {
643 if keyword.trim().is_empty() {
644 return false;
645 }
646 card.keywords.add(keyword)
647}
648
649pub fn add_intrinsic_keywords<'a>(
650 card: &mut Card,
651 keywords: impl IntoIterator<Item = &'a str>,
652) -> bool {
653 let mut changed = false;
654 for kw in keywords {
655 changed |= add_intrinsic_keyword(card, kw);
656 }
657 changed
658}
659
660pub fn remove_intrinsic_keyword(card: &mut Card, keyword: &str) -> bool {
661 card.keywords.remove(keyword)
662}
663
664pub fn apply_spell_ability(
665 layer: &crate::card::card_trait_changes::CardTraitChanges,
666 list: Vec<SpellAbility>,
667) -> Vec<SpellAbility> {
668 layer.apply_spell_ability(list)
669}
670
671pub fn apply_trigger(
672 layer: &crate::card::card_trait_changes::CardTraitChanges,
673 list: Vec<Trigger>,
674) -> Vec<Trigger> {
675 layer.apply_trigger(list)
676}
677
678pub fn apply_replacement_effect(
679 layer: &crate::card::card_trait_changes::CardTraitChanges,
680 list: Vec<ReplacementEffect>,
681) -> Vec<ReplacementEffect> {
682 layer.apply_replacement_effect(list)
683}
684
685pub fn apply_static_ability(
686 layer: &crate::card::card_trait_changes::CardTraitChanges,
687 list: Vec<StaticAbility>,
688) -> Vec<StaticAbility> {
689 layer.apply_static_ability(list)
690}
691
692pub fn apply_keywords(
693 layer: &crate::card::card_trait_changes::CardTraitChanges,
694 mut list: crate::keyword::keyword_collection::KeywordCollection,
695) -> crate::keyword::keyword_collection::KeywordCollection {
696 if layer.remove_all {
697 list.clear();
698 }
699 list
700}
701
702pub fn copy(from: &Card, to: &mut Card) {
703 copy_from(from, to);
704}
705
706pub fn has_spell_ability(card: &Card, sa: &SpellAbility) -> bool {
707 card.activated_abilities
708 .iter()
709 .any(|ab| ab.ability_text == sa.ability_text)
710}
711
712pub fn add_spell_ability(card: &mut Card, sa: &SpellAbility) -> bool {
713 card.abilities.push(sa.ability_text.clone());
714 if let Some(parsed) = parse_activated_ability(&sa.ability_text, card.activated_abilities.len())
715 {
716 card.activated_abilities.push(parsed);
717 return true;
718 }
719 false
720}
721
722pub fn has_trigger(card: &Card, trigger_id: u32) -> bool {
723 card.triggers.iter().any(|t| t.id == trigger_id)
724}
725
726pub fn add_trigger(card: &mut Card, mut trig: Trigger) -> bool {
727 trig.bind_host_card_id(card.id);
728 card.triggers.push(trig);
729 true
730}
731
732pub fn add_static_ability(card: &mut Card, st_ab: StaticAbility) -> bool {
733 card.static_abilities.push(st_ab);
734 true
735}
736
737pub fn remove_static_ability(card: &mut Card, mode: crate::staticability::StaticMode) -> bool {
738 let before = card.static_abilities.len();
739 card.static_abilities.retain(|sa| !sa.check_mode(&mode));
740 before != card.static_abilities.len()
741}
742
743pub fn add_replacement_effect(card: &mut Card, mut re: ReplacementEffect) -> bool {
744 re.set_host_card(card);
745 card.replacement_effects.push(re);
746 true
747}
748
749pub fn has_replacement_effect(card: &Card) -> bool {
750 !card.replacement_effects.is_empty()
751}
752
753pub fn has_s_var(card: &Card, key: &str) -> bool {
754 card.svars.contains_key(key) || card.granted_svars.contains_key(key)
755}
756
757pub fn remove_s_var(card: &mut Card, key: &str) {
758 card.svars.remove(key);
759}
760
761pub fn copy_from(source: &Card, target: &mut Card) {
762 target.changed_card_traits = source.changed_card_traits.clone();
763 target.changed_card_traits_by_text = source.changed_card_traits_by_text.clone();
764
765 target
766 .svars
767 .retain(|k, _| !k.starts_with("TextColor:") && !k.starts_with("TextType:"));
768 target.copy_changed_text_from(source);
769
770 target.update_type_cache();
771}
772
773pub fn add_abilities_from(source: &Card, target: &mut Card) {
774 card_copy_service::copy_copiable_characteristics(source, target);
776
777 target
778 .activated_abilities
779 .extend(source.activated_abilities.iter().cloned());
780 target.triggers.extend(source.triggers.iter().cloned());
781 target
782 .replacement_effects
783 .extend(source.replacement_effects.iter().cloned());
784 target
785 .static_abilities
786 .extend(source.static_abilities.iter().cloned());
787}
788
789pub fn has_property(card: &Card, property: &str) -> bool {
790 card_property::card_has_property(card, property, card.controller)
791}
792
793pub fn reset_original_host(card: &mut Card) {
794 card.effect_source = None;
795}
796
797pub fn update_changed_text(card: &mut Card) {
798 card.update_rules_view();
799}
800
801pub fn change_text_intrinsic(card: &mut Card) {
802 card.update_changed_text();
803}
804
805pub fn has_chapter(card: &Card) -> bool {
806 card.triggers.iter().any(Trigger::is_chapter)
807}
808
809pub fn get_final_chapter_nr(card: &Card) -> i32 {
810 card.triggers
811 .iter()
812 .filter_map(Trigger::get_chapter)
813 .max()
814 .unwrap_or(0)
815}
816
817pub fn set_type(card: &mut Card, type_line: &str) {
818 card.type_line = CardTypeLine::parse(type_line);
819}