class_name CardInstance extends RefCounted ## CardInstance - Runtime instance of a card in the game ## Represents a specific card in play with its current state # Reference to card definition var card_data: CardDatabase.CardData # Unique instance ID var instance_id: int = 0 # Current state var state: Enums.CardState = Enums.CardState.ACTIVE var current_power: int = 0 var damage_received: int = 0 # Owner and controller var owner_index: int = 0 # Player who owns this card (0 or 1) var controller_index: int = 0 # Player who currently controls this card # Current zone var zone_type: Enums.ZoneType = Enums.ZoneType.DECK # Temporary effects (cleared at end of turn) var power_modifiers: Array[int] = [] var temporary_abilities: Array = [] var temporary_keywords: Dictionary = {} # keyword -> duration var restrictions: Dictionary = {} # restriction_type -> duration var requirements: Dictionary = {} # requirement_type -> duration var protections: Dictionary = {} # protection_type -> duration # Counters var counters: Dictionary = {} # counter_type -> count # Special states var is_frozen: bool = false var base_power_override: int = -1 # -1 means use card_data.power # Turn tracking var turns_on_field: int = 0 var attacked_this_turn: bool = false # Static counter for unique IDs static var _next_id: int = 1 func _init(data: CardDatabase.CardData = null, owner: int = 0) -> void: card_data = data owner_index = owner controller_index = owner instance_id = _next_id _next_id += 1 if data: current_power = data.power ## Get the card's current power (base + modifiers + field effects) func get_power() -> int: var total = current_power for mod in power_modifiers: total += mod # Add field effect modifiers from AbilitySystem var tree = Engine.get_main_loop() if tree and tree.root and tree.root.has_node("AbilitySystem"): var ability_system = tree.root.get_node("AbilitySystem") total += ability_system.get_field_power_modifier(self) return max(0, total) ## Check if this is a Forward func is_forward() -> bool: return card_data and card_data.type == Enums.CardType.FORWARD ## Check if this is a Backup func is_backup() -> bool: return card_data and card_data.type == Enums.CardType.BACKUP ## Check if this is a Summon func is_summon() -> bool: return card_data and card_data.type == Enums.CardType.SUMMON ## Check if the card is active (not dull) func is_active() -> bool: return state == Enums.CardState.ACTIVE ## Check if the card is dull func is_dull() -> bool: return state == Enums.CardState.DULL ## Dull this card func dull() -> void: state = Enums.CardState.DULL ## Activate this card func activate() -> void: # Frozen cards can't activate during Active Phase (but can be activated by effects) state = Enums.CardState.ACTIVE ## Attempt to activate during Active Phase (respects frozen) func activate_during_active_phase() -> bool: if is_frozen: is_frozen = false # Frozen wears off but card stays dull return false state = Enums.CardState.ACTIVE return true ## Check if this card can attack func can_attack() -> bool: if not is_forward(): return false if is_dull(): return false if attacked_this_turn: return false if has_restriction("CANT_ATTACK"): return false # Must have been on field since start of turn (or have Haste) if turns_on_field < 1 and not has_haste(): return false return true ## Check if this card must attack (if able) func must_attack() -> bool: return has_requirement("MUST_ATTACK") ## Check if this card can block func can_block() -> bool: if not is_forward(): return false if is_dull(): return false if has_restriction("CANT_BLOCK"): return false return true ## Check if this card must block (if able) func must_block() -> bool: return has_requirement("MUST_BLOCK") ## Check if this card can use dull abilities func can_use_dull_ability() -> bool: # Must have been on field since start of turn (or have Haste) # Monsters are exception if card_data.type == Enums.CardType.MONSTER: return true if turns_on_field < 1 and not has_haste(): return false return true ## Check if card has Haste func has_haste() -> bool: if not card_data: return false # Check explicit has_haste field first if card_data.has_haste: return true # Fallback: search ability text for backwards compatibility for ability in card_data.abilities: if ability.type == Enums.AbilityType.FIELD: if "haste" in ability.effect.to_lower(): return true # Check field-granted keywords if _has_field_keyword("HASTE"): return true return false ## Check if card has Brave (from abilities) func has_brave() -> bool: if not card_data: return false for ability in card_data.abilities: if ability.type == Enums.AbilityType.FIELD: if "brave" in ability.effect.to_lower(): return true # Check field-granted keywords if _has_field_keyword("BRAVE"): return true return false ## Check if card has First Strike func has_first_strike() -> bool: if not card_data: return false for ability in card_data.abilities: if ability.type == Enums.AbilityType.FIELD: if "first strike" in ability.effect.to_lower(): return true # Check field-granted keywords if _has_field_keyword("FIRST_STRIKE"): return true return false ## Check for field-granted keyword from AbilitySystem func _has_field_keyword(keyword: String) -> bool: var tree = Engine.get_main_loop() if tree and tree.root and tree.root.has_node("AbilitySystem"): var ability_system = tree.root.get_node("AbilitySystem") return ability_system.has_field_keyword(self, keyword) return false ## Get primary element func get_element() -> Enums.Element: if card_data: return card_data.get_primary_element() return Enums.Element.FIRE ## Get all elements func get_elements() -> Array[Enums.Element]: if card_data: return card_data.elements return [] ## Check if card is Light or Dark element func is_light_or_dark() -> bool: for element in get_elements(): if Enums.is_light_or_dark(element): return true return false ## Apply damage to this Forward func apply_damage(amount: int) -> bool: if not is_forward(): return false damage_received += amount # Check if broken (damage >= power) return damage_received >= get_power() ## Reset temporary effects at end of turn func end_turn_cleanup() -> void: power_modifiers.clear() temporary_abilities.clear() damage_received = 0 attacked_this_turn = false ## Called when card enters field func entered_field() -> void: turns_on_field = 0 attacked_this_turn = false damage_received = 0 ## Called at start of each turn func start_turn() -> void: turns_on_field += 1 ## Get a display string for this card func get_display_name() -> String: if card_data: return card_data.name return "Unknown Card" func _to_string() -> String: return "[CardInstance: %s (%s)]" % [get_display_name(), instance_id] # ============================================================================= # KEYWORD MANAGEMENT # ============================================================================= ## Add a temporary keyword func add_temporary_keyword(keyword: String, duration: String = "END_OF_TURN") -> void: temporary_keywords[keyword.to_upper()] = duration ## Check if card has a keyword (from card data, temp, or field effects) func has_keyword(keyword: String) -> bool: var kw_upper = keyword.to_upper() # Check temporary keywords if temporary_keywords.has(kw_upper): return true # Check field-granted keywords if _has_field_keyword(kw_upper): return true # Check card's base keywords if card_data: match kw_upper: "HASTE": return card_data.has_haste "BRAVE": for ability in card_data.abilities: if ability.type == Enums.AbilityType.FIELD and "brave" in ability.effect.to_lower(): return true "FIRST_STRIKE": for ability in card_data.abilities: if ability.type == Enums.AbilityType.FIELD and "first strike" in ability.effect.to_lower(): return true return false ## Remove all abilities func remove_all_abilities() -> void: temporary_abilities.clear() temporary_keywords.clear() ## Remove a specific ability func remove_ability(ability_name: String) -> void: var name_upper = ability_name.to_upper() temporary_abilities.erase(name_upper) temporary_keywords.erase(name_upper) # ============================================================================= # RESTRICTIONS & REQUIREMENTS # ============================================================================= ## Add a restriction (can't attack, can't block, etc.) func add_restriction(restriction_type: String, duration: String = "END_OF_TURN") -> void: restrictions[restriction_type.to_upper()] = duration ## Check if card has a restriction func has_restriction(restriction_type: String) -> bool: return restrictions.has(restriction_type.to_upper()) ## Add a requirement (must attack, must block, etc.) func add_requirement(requirement_type: String, duration: String = "END_OF_TURN") -> void: requirements[requirement_type.to_upper()] = duration ## Check if card has a requirement func has_requirement(requirement_type: String) -> bool: return requirements.has(requirement_type.to_upper()) # ============================================================================= # PROTECTION # ============================================================================= ## Add protection from damage/effects func add_protection(protection_type: String, duration: String = "END_OF_TURN") -> void: protections[protection_type.to_upper()] = duration ## Check if card has protection from something func has_protection(protection_type: String) -> bool: var pt_upper = protection_type.to_upper() # Check local protections if protections.has(pt_upper): return true # Check for ALL protection if protections.has("ALL"): return true # Check field-granted protection var tree = Engine.get_main_loop() if tree and tree.root and tree.root.has_node("AbilitySystem"): var ability_system = tree.root.get_node("AbilitySystem") return ability_system.has_field_protection(self, protection_type) return false # ============================================================================= # FROZEN STATE # ============================================================================= ## Set frozen state func set_frozen(frozen: bool) -> void: is_frozen = frozen ## Check if frozen (doesn't activate during Active Phase) func is_card_frozen() -> bool: return is_frozen # ============================================================================= # COUNTERS # ============================================================================= ## Add counters func add_counters(counter_type: String, amount: int = 1) -> void: var ct = counter_type.to_upper() counters[ct] = counters.get(ct, 0) + amount ## Remove counters func remove_counters(counter_type: String, amount: int = 1) -> void: var ct = counter_type.to_upper() counters[ct] = max(0, counters.get(ct, 0) - amount) if counters[ct] == 0: counters.erase(ct) ## Get counter count func get_counter_count(counter_type: String) -> int: return counters.get(counter_type.to_upper(), 0) # ============================================================================= # POWER MANIPULATION # ============================================================================= ## Set base power (for swap/transform effects) func set_base_power(new_power: int) -> void: base_power_override = new_power ## Get base power (respecting override) func get_base_power() -> int: if base_power_override >= 0: return base_power_override return card_data.power if card_data else 0 # ============================================================================= # DAMAGE MANIPULATION # ============================================================================= ## Heal damage func heal_damage(amount: int) -> void: damage_received = max(0, damage_received - amount) ## Remove all damage func remove_all_damage() -> void: damage_received = 0 # ============================================================================= # COPY & TRANSFORM # ============================================================================= ## Copy abilities from another card func copy_abilities_from(other: CardInstance) -> void: if other and other.card_data: for ability in other.card_data.abilities: temporary_abilities.append(ability) ## Copy stats from another card func copy_stats_from(other: CardInstance) -> void: if other: base_power_override = other.get_base_power() ## Become a copy of another card func become_copy_of(other: CardInstance) -> void: if other: copy_stats_from(other) copy_abilities_from(other) ## Transform into something else func transform(into: Dictionary) -> void: if into.has("power"): base_power_override = int(into.power) if into.has("name"): # Transform name handling would require additional infrastructure pass # ============================================================================= # PROPERTY MODIFICATION # ============================================================================= # Temporary element/job storage var _temp_element: String = "" var _temp_element_duration: String = "" var _temp_job: String = "" var _temp_job_duration: String = "" ## Set temporary element func set_temporary_element(element: String, duration: String = "END_OF_TURN") -> void: _temp_element = element.to_upper() _temp_element_duration = duration ## Set temporary job func set_temporary_job(job: String, duration: String = "END_OF_TURN") -> void: _temp_job = job _temp_job_duration = duration ## Get current elements (including temporary) func get_current_elements() -> Array: if _temp_element != "": var element = Enums.element_from_string(_temp_element) return [element] return get_elements() ## Get current job (including temporary) func get_current_job() -> String: if _temp_job != "": return _temp_job return card_data.job if card_data else "" # ============================================================================= # CLEANUP # ============================================================================= ## Reset temporary effects at end of turn (extended) func end_turn_cleanup() -> void: power_modifiers.clear() temporary_abilities.clear() damage_received = 0 attacked_this_turn = false # Clear END_OF_TURN duration effects _clear_duration_effects("END_OF_TURN") ## Clear effects with specific duration func _clear_duration_effects(duration: String) -> void: for key in temporary_keywords.keys(): if temporary_keywords[key] == duration: temporary_keywords.erase(key) for key in restrictions.keys(): if restrictions[key] == duration: restrictions.erase(key) for key in requirements.keys(): if requirements[key] == duration: requirements.erase(key) for key in protections.keys(): if protections[key] == duration: protections.erase(key) # Clear temporary element/job if _temp_element_duration == duration: _temp_element = "" _temp_element_duration = "" if _temp_job_duration == duration: _temp_job = "" _temp_job_duration = ""