Skip to main content

manabrew_engine/card/
mod.rs

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
37/// Type alias for the Keyword enum, used by keyword helper methods.
38use crate::keyword::keyword_instance::Keyword as Kw;
39
40// ── Keyword marker constants ──────────────────────────────────────────
41// These are synthetic keywords injected at runtime to track card state.
42// Using constants avoids magic strings scattered across the codebase.
43
44/// Prefix for the Plotted marker. Full keyword is `"Plotted:{turn}"`.
45/// The turn number prevents casting on the same turn the card was plotted.
46pub const KEYWORD_PLOTTED_PREFIX: &str = "Plotted:";
47
48/// Marker for cards exiled via Warp's end-of-turn trigger.
49/// These cards can be cast from exile on a later turn for their normal mana cost.
50pub 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
70/// Build the full `"Plotted:{turn}"` keyword string.
71fn 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
86/// Extract the turn number from a `"Plotted:{turn}"` keyword, if present.
87pub 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/// Stores alternate-face characteristics for double-faced cards (DFCs).
93/// The `transform()` method swaps `Card` fields with these values.
94/// Mirrors Java's `CardState` stored as the "backside" state.
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct CardOtherPart {
97    pub name: String,
98    /// True when the card's split type is `Modal` (MDFC). Only a modal back face
99    /// may be played from hand; transform/meld backs may not. Mirrors
100    /// `Card.isModal()` (`getRules().getSplitType() == CardSplitType.Modal`).
101    /// Invariant across `transform()`, so it is not swapped there.
102    #[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/// When a `SP$ GainControl` steals a permanent, the revert trigger is stored
133/// here. Fires during the appropriate phase or event handler, at which point
134/// the card's `original_controller_eot` is restored.
135///
136/// Mirrors the subset of Java `ControlGainEffect.LoseControl$` variants that
137/// schedule a `GameCommand`. Java also has variants we intentionally skip
138/// here (`StaticCommandCheck` driven by an SVar comparator, `UntilSourceUnattached`,
139/// `UntilTheEndOfYourNextTurn`) — they require either a scheduler that scans
140/// every tick or a turn-owner counter that the engine doesn't maintain yet.
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum_macros::EnumString)]
142#[strum(ascii_case_insensitive)]
143pub enum LoseControlCondition {
144    /// Revert at the end of the current turn (default EOT branch).
145    #[strum(serialize = "EOT", serialize = "UntilEOT", serialize = "EndOfTurn")]
146    EndOfTurn,
147    /// Revert the next time this card untaps.
148    #[strum(serialize = "Untap", serialize = "UntilUntap", serialize = "NextUntap")]
149    NextUntap,
150    /// Revert at end of combat (Threaten-style steal-and-swing).
151    EndOfCombat,
152    /// Revert when the card leaves the battlefield.
153    LeavesPlay,
154}
155
156/// Saved pre-animate state for AnimateEffect, restored at cleanup.
157#[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    /// Snapshot of intrinsic keywords before animate added any. Restored
164    /// when the card leaves the battlefield (CR 400.7) so granted keywords
165    /// (e.g. Animate `Keywords$ Haste`) do not persist into the new object.
166    #[serde(default)]
167    pub original_keywords: Option<crate::keyword::keyword_collection::KeywordCollection>,
168}
169
170/// Saved pre-clone copiable characteristics.
171#[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    /// Intrinsic ability count *before* the clone overwrote it. The static
189    /// layer truncates `activated_abilities` to `base_ability_count` each
190    /// pass, so reverting `activated_abilities` without also restoring this
191    /// would let the layer trim the recovered abilities right back to the
192    /// clone's count.
193    #[serde(default)]
194    pub original_base_ability_count: usize,
195    /// Intrinsic trigger count *before* the clone, for the same reason.
196    #[serde(default)]
197    pub original_base_trigger_count: usize,
198}
199
200/// A card instance in a game. This is the mutable game-state representation,
201/// as opposed to CardRules which is the immutable definition.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct Card {
204    pub id: CardId,
205    /// The card's active face name (front face for single/split cards, active face for DFC).
206    /// On the battlefield, this is the name that's displayed.
207    pub card_name: String,
208    /// The full combined name for split/room cards (e.g. "Walk-In Closet // Forgotten Cellar").
209    /// For non-split cards, this equals `card_name`. Used for hand/graveyard display and
210    /// database lookups.
211    pub full_name: String,
212
213    // Ownership and control
214    pub owner: PlayerId,
215    pub controller: PlayerId,
216
217    // Current zone
218    pub zone: ZoneType,
219
220    // Type line (can be modified by effects)
221    pub type_line: CardTypeLine,
222
223    // Mana cost (can be modified)
224    pub mana_cost: ManaCost,
225
226    // Color (can be modified)
227    pub color: ColorSet,
228
229    /// Immutable color identity from the card's rules (CR 903.4): mana cost
230    /// colors plus any mana symbols found in the oracle text (outside reminder
231    /// text). Used for commander color-identity checks and Combo ColorIdentity
232    /// mana productions. Mirrors Java `CardRules.getColorIdentity()`.
233    #[serde(default = "colorless_color_set")]
234    pub color_identity: ColorSet,
235
236    // Power/Toughness (base values, can be modified)
237    pub base_power: Option<i32>,
238    pub base_toughness: Option<i32>,
239    /// Printed starting loyalty for planeswalkers.
240    pub initial_loyalty: Option<String>,
241    /// Temporary P/T modifications from spells/abilities resolving this turn
242    /// (e.g. Giant Growth).  Reset when leaving the battlefield.
243    pub power_modifier: i32,
244    pub toughness_modifier: i32,
245    /// Perpetual P/T modifications — persist across zone changes (never reset).
246    /// Applied by `PumpAll` / `Pump` effects with `Duration$ Perpetual`.
247    pub perpetual_power_modifier: i32,
248    pub perpetual_toughness_modifier: i32,
249    /// Java-parity storage of all perpetual effect records applied to this card.
250    #[serde(default)]
251    pub perpetual: Vec<PerpetualRecord>,
252    /// Layer 7b override: set by `SetPower$` / `SetToughness$` continuous effects.
253    /// `None` means use `base_power` / `base_toughness` as normal.
254    /// Reset to `None` each time [`layer::apply_continuous_effects`] runs.
255    pub static_set_power: Option<i32>,
256    pub static_set_toughness: Option<i32>,
257    /// Layer 7c bonus: accumulated from `AddPower$` / `AddToughness$` anthems.
258    /// Reset to 0 each time [`layer::apply_continuous_effects`] runs.
259    pub static_power_modifier: i32,
260    pub static_toughness_modifier: i32,
261
262    // Combat/state
263    pub tapped: bool,
264    /// Mana atoms produced the last time this land was tapped for mana.
265    /// Used for mana rollback — when untapping, remove exactly this mana from pool.
266    /// Covers base production + aura triggers + static doublers + any other source.
267    #[serde(skip)]
268    pub last_mana_produced: Option<Vec<u16>>,
269    pub flipped: bool,
270    pub face_down: bool,
271    /// True if this card has Morph or Megamorph and can be cast face-down for {3}.
272    pub has_morph: bool,
273    /// True if this card was discarded (CR 400.7k, for TrackDiscarded$ effects).
274    pub discarded: bool,
275    /// True if this card was unearthed (should be exiled at EOT or if leaving battlefield).
276    pub unearthed: bool,
277    /// Class enchantment level (1 = base, 2+ = leveled up).
278    pub class_level: i32,
279    /// Soulbond: paired creature (if any).
280    pub paired_with: Option<CardId>,
281    /// True if this card was manifested (face-down as 2/2 creature).
282    pub manifested: bool,
283    /// True if this card was cloaked (face-down with ward {2}).
284    pub cloaked: bool,
285    /// True if this card was foretold (exiled face-down via Foretell).
286    pub foretold: bool,
287    /// Other card(s) melded/merged with this one. When this card changes zones,
288    /// all melded parts move together (CR 712.4).
289    pub melded_with: Vec<CardId>,
290    /// True if foretold cost was set by an effect (not the card's own Foretell ability).
291    pub foretold_cost_by_effect: bool,
292    /// True if this card is currently bestowed (attached as an Aura via Bestow).
293    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    /// Zone the card was cast from (mirrors Java `Card.castFrom`). `Some` only
300    /// while the card represents a spell that was actually cast — set during
301    /// cast resolution, cleared on every zone change so the next "object" the
302    /// card becomes (CR 400.7) starts with no cast history. Used by
303    /// `wasCast`/`wasCastByYou` valid filters (e.g. Sunderflock's ETB).
304    pub cast_from: Option<ZoneType>,
305
306    // Counters
307    pub counters: BTreeMap<CounterType, i32>,
308
309    // Keywords intrinsic to this card (from its card definition).
310    // Now stored as a `KeywordCollection` for structured typed lookups.
311    pub keywords: crate::keyword::keyword_collection::KeywordCollection,
312    /// Keywords granted by continuous static effects (Layer 6).
313    /// Reset and recomputed each time [`layer::apply_continuous_effects`] runs.
314    pub granted_keywords: crate::keyword::keyword_collection::KeywordCollection,
315    /// SVars supplied by granted text (e.g. AddTrigger$/AddAbility$).
316    /// Reset and recomputed each time [`layer::apply_continuous_effects`] runs.
317    #[serde(default)]
318    pub granted_svars: BTreeMap<String, String>,
319    /// Type tokens added by continuous static effects (Layer 4, `AddType$`).
320    /// Reset and recomputed each time [`layer::apply_continuous_effects`] runs.
321    /// The listed strings may be supertypes, core card types, or subtypes;
322    /// keeping a separate list lets us revert on reset without losing the
323    /// card's intrinsic type line.
324    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    /// Keywords granted temporarily by pump effects (`KW$` parameter) until end of turn.
334    /// Cleared during step_cleanup alongside power_modifier / toughness_modifier.
335    pub pump_keywords: crate::keyword::keyword_collection::KeywordCollection,
336    /// Number of triggers added temporarily by `DB$ Animate | Triggers$` effects.
337    /// At cleanup, this many triggers are popped from the end of the `triggers` vec.
338    pub pump_trigger_count: usize,
339
340    // Abilities (raw strings from card definition)
341    pub abilities: Vec<String>,
342    /// Prebound SP$ ability metadata used by action-space filters.
343    #[serde(default)]
344    pub action_spell_specs: Vec<CardActionSpellSpec>,
345    /// Prebound first SP$ Cost used by action-space non-mana cost checks.
346    #[serde(default)]
347    pub action_spell_cost: Option<Cost>,
348    /// Prebound AIPhyrexianPayment$ policy from printed ability text.
349    #[serde(default)]
350    pub ai_phyrexian_payment: Option<String>,
351    /// Prebound minimum Spree mode cost from Choices$ -> SVar ModeCost$.
352    #[serde(default)]
353    pub spree_min_mode_cost: Option<i32>,
354
355    // Parsed activated abilities (from AB$ lines in abilities)
356    pub activated_abilities: Vec<ActivatedAbility>,
357    /// Number of base activated abilities (before continuous effects add more via AddAbility$).
358    /// Used by `apply_continuous_effects` to truncate granted abilities on reset.
359    pub base_ability_count: usize,
360    /// Number of base triggers (before continuous effects add more via AddTrigger$).
361    /// Used by `apply_continuous_effects` to truncate granted triggers on reset.
362    pub base_trigger_count: usize,
363    /// Applied card-trait mutation layers keyed by (timestamp, static_id).
364    /// Mirrors Java `changedCardTraits` table.
365    pub changed_card_traits:
366        std::collections::BTreeMap<(i64, i64), card_trait_changes::CardTraitChanges>,
367    /// Text-layer trait changes keyed by (timestamp, static_id).
368    /// Mirrors Java `changedCardTraitsByText` table.
369    pub changed_card_traits_by_text:
370        std::collections::BTreeMap<(i64, i64), card_trait_changes::CardTraitChanges>,
371
372    /// Parsed static abilities (from S$ lines in abilities).
373    /// Mirrors Java Forge `Card.getStaticAbilities()`.
374    pub static_abilities: Vec<StaticAbility>,
375
376    // Combat tracking
377    pub has_deathtouch_damage: bool,
378    /// Set by `Mode$ CantAttack` static effects. Reset each time
379    /// [`layer::apply_continuous_effects`] runs.
380    pub cant_attack_static: bool,
381    /// Set by `Mode$ CantBlock` static effects. Reset each time
382    /// [`layer::apply_continuous_effects`] runs.
383    pub cant_block_static: bool,
384
385    // Turn tracking
386    #[serde(default)]
387    pub turn_in_zone: u32,
388    pub entered_battlefield_this_turn: bool,
389    pub attacked_this_turn: bool,
390    /// Snapshot of whether this permanent was tapped at the start of its
391    /// controller's current turn (before untap step).
392    pub started_turn_tapped: bool,
393
394    // Triggers — mirrors Java Card.getTriggers()
395    pub triggers: Vec<Trigger>,
396    // SVars — mirrors Java Card.getSVars()
397    pub svars: BTreeMap<String, String>,
398    #[serde(skip, default)]
399    pub parsed_svar_cache: ParsedSVarCache,
400
401    // Commander tracking
402    /// True if this card is designated as a commander.
403    pub is_commander: bool,
404    /// True if this commander entered graveyard or exile since the last SBA check
405    /// and may still be moved to the command zone.
406    pub move_to_command_zone: bool,
407    /// How many times this commander has been cast from the command zone (for tax).
408    pub commander_cast_count: u32,
409
410    /// True if this permanent is a token or a copy-token (ceases to exist on zone change).
411    pub is_token: bool,
412
413    /// Set when the card is cast from graveyard via Flashback. Used by the
414    /// flashback replacement effect to exile the card when it leaves the stack.
415    pub cast_with_flashback: bool,
416    /// Set when the card is cast from graveyard via Harmonize. Used by the
417    /// Harmonize replacement effect to exile the card when it leaves the stack.
418    pub cast_with_harmonize: bool,
419
420    // Replacement effects — parsed from R$ lines in card abilities.
421    // Mirrors Java `Card.getReplacementEffects()`.
422    pub replacement_effects: Vec<ReplacementEffect>,
423
424    // Attachment tracking (Auras / Equipment).
425    // Mirrors Java `Card.getAttachedTo()` / `Card.getAttachedCards()`.
426    /// The permanent this card is currently attached to (for Auras/Equipment).
427    pub attached_to: Option<CardId>,
428    pub attached_to_player: Option<PlayerId>,
429    /// Whether this equipment was attached/moved this turn (AI memory to prevent ping-ponging).
430    /// Cleared at start of each turn. Mirrors Java `AiCardMemory.MemorySet.ATTACHED_THIS_TURN`.
431    pub attached_this_turn: bool,
432    /// Cards currently attached to this permanent (inverse of `attached_to`).
433    pub attachments: Vec<CardId>,
434
435    // Memory for "Remember" and "Imprint" parameters
436    /// Cards remembered by this card (for RememberCountered, etc.)
437    pub remembered_cards: Vec<CardId>,
438    /// Players remembered by this card (for Player.IsRemembered checks).
439    pub remembered_players: Vec<PlayerId>,
440    /// Cards imprinted on this card (for Imprint mechanic, e.g. Chrome Mox).
441    pub imprinted_cards: Vec<CardId>,
442    /// Cards associated via gain-control effects.
443    pub gain_control_targets: Vec<CardId>,
444    /// Cards linked by "until leaves battlefield" tracking.
445    pub until_leaves_battlefield: Vec<CardId>,
446    /// Cards exiled by this card/effect.
447    pub exiled_cards: Vec<CardId>,
448    /// Cards exiled specifically to pay this card's current activation/cast cost.
449    /// This is reset at the start of each cost payment attempt.
450    pub paid_cost_exiled_cards: Vec<CardId>,
451    /// Cards haunting this card.
452    pub haunted_by: Vec<CardId>,
453    /// Card currently haunted by this card.
454    pub haunting: Option<CardId>,
455    /// Per-player chosen card map.
456    pub chosen_map: HashMap<PlayerId, Vec<CardId>>,
457    /// CMC values remembered by this card
458    pub remembered_cmc: Vec<i32>,
459    /// Source card that created this effect card (for Card.EffectSource checks).
460    pub effect_source: Option<CardId>,
461    #[serde(default)]
462    pub clone_origin: Option<CardId>,
463    #[serde(default)]
464    pub copied_permanent: Option<CardId>,
465    /// The spell ability used to cast this card instance onto the stack.
466    /// Mirrors Java `Card.getCastSA()`. Populated when the card hits the stack,
467    /// cleared when it leaves the battlefield.
468    #[serde(skip, default)]
469    pub cast_sa: Option<Box<SpellAbility>>,
470    /// For `SP$ Charm`: last turn each mode (keyed by its SVar name) was chosen
471    /// on this card instance. Feeds `ChoiceRestriction$` filtering.
472    /// Mirrors the per-card mode history Java keeps on `Card`.
473    #[serde(default)]
474    pub chosen_charm_modes: HashMap<String, i32>,
475    /// LKI (last-known-information) snapshots of cards remembered by this
476    /// card via `RememberLKI$`. Each entry is a frozen copy taken at remember
477    /// time via `CardCopyService::get_lki_copy`. Callers that care about
478    /// "what was this creature when it died" query this list instead of
479    /// `remembered_cards` (which stores live IDs and drifts).
480    #[serde(skip, default)]
481    pub remembered_lki_cards: Vec<Card>,
482    /// When set, the card's `original_controller_eot` must be restored on the
483    /// trigger described here. Mirrors the Java `ControlGainEffect` set of
484    /// `LoseControl$` variants that register distinct GameCommands.
485    #[serde(default)]
486    pub lose_control_condition: Option<LoseControlCondition>,
487    /// True if this temporary effect expires at end of turn cleanup.
488    pub temp_effect_until_eot: bool,
489    /// Host card this temporary effect is linked to; when host leaves the
490    /// battlefield, this effect expires.
491    pub temp_effect_host: Option<CardId>,
492    /// Forget remembered cards when they move from this origin zone.
493    pub forget_on_moved_origin: Option<ZoneType>,
494    /// Exile this effect when remembered cards become empty after forget logic.
495    pub exile_when_no_remembered: bool,
496    /// When this card is in exile, the card that caused it to be exiled here.
497    /// Used for `Duration$ UntilHostLeavesPlay` effects (e.g. Deputy of Detention):
498    /// when `exiled_by` leaves the battlefield, this card returns to its owner's battlefield.
499    pub exiled_by: Option<CardId>,
500
501    /// Original controller to restore at end of turn (for `LoseControl$ EOT`).
502    pub original_controller_eot: Option<PlayerId>,
503
504    // Double-faced card (DFC) state
505    /// True if this card is currently showing its back face.
506    pub is_transformed: bool,
507    /// Back-face characteristics for DFC cards. `None` for single-faced cards.
508    pub other_part: Option<CardOtherPart>,
509
510    /// Optional set code (e.g., "M21") for specific printings.
511    pub set_code: Option<String>,
512
513    /// Optional collector number within a set (e.g., "1", "42").
514    /// For tokens, this is the token's collector number in the token set
515    /// (e.g., collector "1" in set "THOU" for Adorned Pouncer token).
516    pub card_number: Option<String>,
517
518    #[serde(default)]
519    pub paper_foil: bool,
520
521    // Phase-out state (issue #22, Phases effect).
522    pub phased_out: bool,
523
524    // Regeneration shields (issue #22, Regenerate effect).
525    // Decremented instead of destroying; resets at end of turn.
526    pub regeneration_shields: i32,
527
528    /// Whether this permanent was kicked when cast.
529    /// Mirrors Java `Card.isKicked()`. Stored on the card so triggers
530    /// with `ValidCard$ Card.Self+kicked` can check it after resolution.
531    pub kicked: bool,
532    /// Whether this permanent has become monstrous.
533    /// Mirrors Java `Card.isMonstrous()`. Resets when the permanent changes zones.
534    pub monstrous: bool,
535
536    /// Colors chosen by ChooseColorEffect (stored for later reference by other effects).
537    pub chosen_colors: Vec<String>,
538    /// Cards chosen by ChooseCardEffect (stored for later reference by other effects).
539    pub chosen_cards: Vec<CardId>,
540
541    /// Saved state for AnimateEffect — restored during step_cleanup.
542    pub animate_state: Option<AnimateState>,
543    /// Saved state for temporary Clone effects — restored during step_cleanup.
544    pub clone_state: Option<CloneState>,
545
546    // ── Issue #53: High-priority effect fields ──────────────────────────
547    /// Type chosen by ChooseType effect (e.g. "Goblin", "Artifact").
548    pub chosen_type: Option<String>,
549    /// Secondary chosen type used by a subset of cards (e.g. Illusionary Terrain).
550    pub chosen_type2: Option<String>,
551    /// Noted types tracked by effects that accumulate type names.
552    pub noted_types: Vec<String>,
553    /// Card names chosen by NameCard effect.
554    pub named_cards: Vec<String>,
555    /// Number chosen by ChooseNumber effect.
556    pub chosen_number: Option<i32>,
557    /// Player chosen by ChoosePlayer effect.
558    pub chosen_player: Option<PlayerId>,
559    /// Controller who made the chosen-player choice.
560    pub chosen_player_controller: Option<PlayerId>,
561    /// Controller who made the chosen-type choice.
562    pub chosen_type_controller: Option<PlayerId>,
563    /// Whether the chosen player has been revealed.
564    pub chosen_player_revealed: bool,
565    /// Whether the chosen type has been revealed.
566    pub chosen_type_revealed: bool,
567    /// Opponent chosen for PromiseGift cost.
568    pub promised_gift: Option<PlayerId>,
569    /// Attraction lights printed on the card face.
570    pub attraction_lights: Vec<u32>,
571    /// Attraction sector assignment.
572    pub sector: Option<String>,
573    /// Chosen sector before assignment effects resolve.
574    pub chosen_sector: Option<String>,
575    /// Contraption sprocket assignment.
576    pub sprocket: i32,
577    /// Chosen even/odd marker.
578    pub chosen_even_odd: Option<String>,
579    /// True if detained — can't attack, block, or activate abilities. Clears at controller's next turn.
580    pub detained: bool,
581    /// Set during combat to the player this creature is attacking; None if not attacking.
582    pub attacking_player: Option<PlayerId>,
583    /// Player who goaded this creature. Goaded creature must attack but can't attack goader.
584    pub goaded_by: Option<PlayerId>,
585    /// Damage prevention shields (decremented when damage would be dealt). Resets at EOT.
586    pub damage_prevention: i32,
587    /// Damage assigned in current combat assignment step.
588    pub assigned_damage: i32,
589    /// True if this creature must block if able.
590    pub must_block: bool,
591    /// Spell cards encoded/ciphered onto this creature.
592    pub encoded_cards: Vec<CardId>,
593    /// Cards that dealt damage to this creature this turn (for DamagedBy trigger filters).
594    /// Mirrors Java `CardDamageHistory.getDamageReceivedThisTurn()`.
595    pub damage_sources_this_turn: Vec<CardId>,
596    /// Total damage dealt by this card this turn (for Count$TotalDamageDoneByThisTurn).
597    /// Mirrors Java `Card.getTotalDamageDoneBy()` via `DamageHistory.getDamageDoneThisTurn()`.
598    /// Reset each turn in `new_turn()`.
599    pub total_damage_done_this_turn: i32,
600    /// Last-known information: power when this card last left the battlefield.
601    /// Mirrors Java's LKI system for `TriggeredCard$CardPower`.
602    /// `None` means LKI was never captured; `Some(0)` means power was 0.
603    pub lki_power: Option<i32>,
604    /// Last-known information: toughness when this card last left the battlefield.
605    /// `None` means LKI was never captured; `Some(0)` means toughness was 0.
606    pub lki_toughness: Option<i32>,
607    /// Last-known information: counters when this card last left the battlefield.
608    /// Used by `TriggeredCard$CardCounters.TYPE` (e.g. Servant of the Scale death trigger).
609    pub lki_counters: Option<std::collections::BTreeMap<CounterType, i32>>,
610    /// Damage history tracking (attacks, blocks, damage dealt).
611    /// Mirrors Java `CardDamageHistory`.
612    #[serde(skip)]
613    pub damage_history: damage_history::DamageHistory,
614    /// Specific cards this creature must block (set by effects like Lure variants).
615    pub must_block_cards: Vec<CardId>,
616    /// +1/+1 counters to add on ETB (from mana that adds counters, e.g. Guildmages' Forum).
617    pub etb_counters_p1p1: i32,
618    /// Bitmask of colors of mana spent to cast this spell (for Sunburst/Converge).
619    /// Uses ManaAtom bit flags (W=1, U=2, B=4, R=8, G=16).
620    pub colors_spent_to_cast: u16,
621    /// Exact mana atoms spent to cast this spell, in payment order.
622    /// Mirrors Java's castSA.getPayingMana() use sites such as Adamant.
623    pub paying_mana_to_cast: Vec<u16>,
624    /// Pre-selected charm/mode indices (for Spree — modes chosen before payment).
625    /// If `Some`, charm_effect should use these instead of asking the player again.
626    pub chosen_modes: Option<Vec<usize>>,
627    /// Number of extra targets paid for via Strive (0 = no extra targets).
628    pub strive_extra_targets: u32,
629    /// Tracks if this card became a target this turn.
630    pub became_target_this_turn: bool,
631    /// Temporary controllers layered on this card.
632    pub temp_controllers: Vec<PlayerId>,
633    /// Players that may look at this card.
634    pub may_look_at: Vec<PlayerId>,
635    /// Players that may play this card.
636    pub may_play: Vec<PlayerId>,
637    /// Additional blockers this creature can declare.
638    pub can_block_additional: i32,
639    /// Whether this creature can block any number of creatures.
640    pub can_block_any: bool,
641    /// Keywords this card is prevented from having.
642    pub cant_have_keywords: HashSet<String>,
643    /// Intensity marker value.
644    pub intensity: i32,
645    /// Card was surveilled this turn.
646    pub surveilled: bool,
647    /// Card was milled this turn.
648    pub milled: bool,
649    /// Attraction visited this turn.
650    pub visited_this_turn: bool,
651    /// Number of times this permanent has crewed this turn.
652    pub times_crewed_this_turn: u32,
653    /// Whether this permanent is currently crewed.
654    pub is_crewed: bool,
655    /// Whether this card should ignore legend rule checks.
656    pub ignore_legend_rule_flag: bool,
657    /// Ability activation counts this turn.
658    pub ability_activated_this_turn: u32,
659    /// Ability resolution counts this turn.
660    pub ability_resolved_this_turn: u32,
661    /// Java parity: per-ability activation tracking this turn.
662    #[serde(skip)]
663    pub number_turn_activations: ActivationTable,
664    /// Java parity: per-ability activation tracking this game.
665    #[serde(skip)]
666    pub number_game_activations: ActivationTable,
667    /// Java parity: per-ability resolution tracking this turn.
668    #[serde(skip)]
669    pub number_ability_resolved: ActivationTable,
670    /// Planeswalker activation count this turn.
671    pub planeswalker_abilities_activated: u32,
672    /// Whether a static effect's increased planeswalker activation limit was used this turn.
673    pub planeswalker_activation_limit_used: bool,
674    /// Chosen mode count tracking turn marker.
675    pub chosen_modes_turn: Option<u32>,
676    /// Set when this creature enlisted another creature in the current combat.
677    pub enlisted_this_combat: bool,
678    /// Per-ability activation count this game (for PowerUp once-per-game restriction).
679    pub activations_this_game: std::collections::BTreeMap<usize, u32>,
680    /// True once Renown has triggered (creature dealt combat damage to a player).
681    /// Mirrors Java `Card.isRenowned()`.
682    pub is_renowned: bool,
683    /// Monotonically increasing timestamp set each time the card enters a zone.
684    /// Used to order same-player triggers by zone entry order, matching
685    /// Java's `Zone.cardList` insertion order used by `forEachCardInGame`.
686    pub zone_timestamp: u64,
687
688    /// Baseline snapshots used to recompute live lists when trait-change layers
689    /// are removed/cleared.
690    #[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
702/// Transitional alias for downstream code still importing `CardInstance`.
703pub 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        // Parse activated abilities from raw ability strings.
719        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        // Parse replacement effects from R$ lines in card abilities.
728        // Mirrors Java Card constructor calling ReplacementHandler registration.
729        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        // Parse static abilities from S$ lines.
737        // Mirrors Java Forge Card constructor calling StaticAbility.create().
738        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        // Generate intrinsic abilities from card properties (mirrors Java CardFactoryUtil)
939        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        // Record base ability count so continuous effects can truncate granted abilities.
947        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    /// Construct a `Card` from a `CardRules` definition.
971    /// This is the single entry point for creating game-ready cards from the
972    /// card database. Mirrors Java's `CardFactory.readCard()` + `CardFactoryUtil`.
973    ///
974    /// Handles:
975    /// - Base stats (name, mana cost, type line, color, P/T, keywords, abilities)
976    /// - Trigger parsing (T: lines) including SpellCastOrCopy → SpellCopied duplication
977    /// - Static ability parsing (S: lines) with alternative cost keyword conversion
978    /// - Replacement effect parsing (R: lines)
979    /// - SVars
980    /// - Double-faced card back face setup
981    /// - Intrinsic mana abilities (basic land subtypes)
982    /// - Keyword-generated abilities and triggers (Cycling, Prowess, Bushido)
983    pub fn from_rules(rules: &CardRules, owner: PlayerId) -> Self {
984        card_factory::build_from_rules(rules, owner)
985    }
986
987    /// Effective power, accounting for all layer effects and counters.
988    ///
989    /// Calculation order (CR 613):
990    /// - Layer 7b: `static_set_power` overrides `base_power` if set.
991    /// - Layer 7c: `static_power_modifier` (anthem bonuses) is added.
992    /// - Temporary: `power_modifier` (from spells like Giant Growth) is added.
993    /// - Layer 7d: +1/+1 and -1/-1 counters are factored in.
994    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    /// Effective toughness, accounting for all layer effects and counters.
1006    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    // CardState-style adapters wired on Card for Java-parity call sites.
1041    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    /// Check whether this card has a keyword — intrinsically, granted by a
1255    /// continuous static effect (Layer 6), or temporarily from a pump effect.
1256    /// Count distinct colors of mana spent to cast this spell (for Sunburst/Converge).
1257    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    /// Check for a keyword using the typed Keyword enum.
1279    /// Checks the structured `keyword_collection` first (O(1) HashMap lookup),
1280    /// then falls back to string matching on granted/pump keywords.
1281    /// Mirrors Java's `Card.hasKeyword(Keyword)`.
1282    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    /// Check "Hexproof from <color>" variants (e.g. "Hexproof from blue").
1387    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    /// Get Toxic count (e.g. "Toxic:1" → Some(1)).
1394    pub fn get_toxic_count(&self) -> Option<i32> {
1395        self.get_keyword_cost("Toxic").and_then(|s| s.parse().ok())
1396    }
1397
1398    /// Whether this card has the Storm keyword.
1399    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    /// Converted mana cost (mana value).
1408    pub fn mana_value(&self) -> i32 {
1409        self.mana_cost.cmc()
1410    }
1411
1412    /// Check "Protection from <quality>" (e.g. "Protection from red").
1413    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    /// Get all "Protection from X" values this card has.
1420    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    /// Check if this card is protected from a source card.
1435    /// Protection from <color> checks source's color.
1436    /// Protection from <type> checks source's type (e.g. "artifacts", "creatures").
1437    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    /// Check if this card can be controlled by the given player
1510    /// (e.g., checks for "Other players can't gain control of CARDNAME.")
1511    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    /// Reset state when entering the battlefield.
1533    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    /// Reset per-turn state at start of turn.
1545    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    /// Reset controller-specific state at the start of that player's turn.
1555    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    /// Add a remembered card (for RememberCountered, etc.)
1574    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    /// Add a remembered CMC value
1581    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    /// Whether this card is double-faced (has a back side).
2057    /// Mirrors Java `Card.isDoubleFaced()`.
2058    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, &params)
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        // Mirrors Java `Card.wasCast()`: true iff `castFrom` was set during
3444        // cast resolution. Sneak Attack and other "put onto battlefield"
3445        // effects don't go through the cast pipeline and leave this `None`.
3446        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    /// Transform this double-faced card to its other face.
3608    /// Swaps all face-dependent characteristics with `other_part`.
3609    /// No-op if `other_part` is `None`.
3610    /// Mirrors Java's `CardUtil.applyState(card, CardStateName.Backside)`.
3611    /// Mirror of `Card.isModal()`: true only for MDFC (modal) cards, whose back
3612    /// face may be played from hand. Transform / meld backs are not modal.
3613    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            // Reset per-face transient state
3636            self.power_modifier = 0;
3637            self.toughness_modifier = 0;
3638            self.damage = 0;
3639            self.granted_keywords.clear();
3640
3641            // Re-parse activated abilities from new face's abilities
3642            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            // Re-bind replacement hosts so SVar lookups hit the active face.
3656            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            // Face characteristics changed; reset trait-change baseline and
3665            // re-apply active trait-change layers against the new face.
3666            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    /// Java parity: `addChangedCardTraits`.
3763    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    /// Java parity: `addChangedCardTraitsByText`.
3776    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    /// Java parity: `removeChangedCardTraits`.
3789    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    /// Java parity: `removeChangedCardTraitsByText`.
3821    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    /// Java parity: `clearChangedCardTraits`.
3853    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    /// Clear continuous static-ability trait changes from the previous layer pass.
3874    ///
3875    /// Static layer effects use negative static IDs so they can be recomputed
3876    /// each pass without disturbing perpetual/card-state trait changes.
3877    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()); // summoning sick
3979
3980        card.summoning_sick = false;
3981        assert!(card.can_attack());
3982
3983        card.tapped = true;
3984        assert!(!card.can_attack()); // tapped
3985    }
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()); // haste means no summoning sickness check
4003    }
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        // No ward/toxic
4106        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}