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