543 lines
15 KiB
GDScript
543 lines
15 KiB
GDScript
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 = ""
|