682 lines
21 KiB
GDScript
682 lines
21 KiB
GDScript
class_name FieldEffectManager
|
|
extends RefCounted
|
|
|
|
## FieldEffectManager - Manages continuous FIELD abilities
|
|
## Tracks active field effects and calculates their impact on the game state
|
|
|
|
# Active field abilities by source card instance_id
|
|
var _active_abilities: Dictionary = {} # instance_id -> Array of abilities
|
|
|
|
|
|
## Register field abilities when a card enters the field
|
|
func register_field_abilities(card: CardInstance, abilities: Array) -> void:
|
|
var field_abilities: Array = []
|
|
|
|
for ability in abilities:
|
|
var parsed = ability.get("parsed", {})
|
|
if parsed.get("type") == "FIELD":
|
|
field_abilities.append({
|
|
"ability": ability,
|
|
"source": card
|
|
})
|
|
|
|
if not field_abilities.is_empty():
|
|
_active_abilities[card.instance_id] = field_abilities
|
|
|
|
|
|
## Unregister field abilities when a card leaves the field
|
|
func unregister_field_abilities(card: CardInstance) -> void:
|
|
_active_abilities.erase(card.instance_id)
|
|
|
|
|
|
## Get total power modifier for a card from all active field effects
|
|
func get_power_modifiers(card: CardInstance, game_state) -> int:
|
|
var total_modifier: int = 0
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "POWER_MOD":
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
total_modifier += effect.get("amount", 0)
|
|
|
|
return total_modifier
|
|
|
|
|
|
## Check if a card has a keyword granted by field effects
|
|
func has_keyword(card: CardInstance, keyword: String, game_state) -> bool:
|
|
var keyword_upper = keyword.to_upper()
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "KEYWORD":
|
|
var granted_keyword = str(effect.get("keyword", "")).to_upper()
|
|
if granted_keyword == keyword_upper:
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
## Get all keywords granted to a card by field effects
|
|
func get_granted_keywords(card: CardInstance, game_state) -> Array:
|
|
var keywords: Array = []
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "KEYWORD":
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
var keyword = effect.get("keyword", "")
|
|
if keyword and keyword not in keywords:
|
|
keywords.append(keyword)
|
|
|
|
return keywords
|
|
|
|
|
|
## Check if a card has protection from something via field effects
|
|
func has_protection(card: CardInstance, protection_type: String, game_state) -> bool:
|
|
var protection_upper = protection_type.to_upper()
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "PROTECTION":
|
|
var from = str(effect.get("from", "")).to_upper()
|
|
if from == protection_upper or from == "ALL":
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
## Check if a card is affected by a damage modifier
|
|
func get_damage_modifier(card: CardInstance, game_state) -> int:
|
|
var total_modifier: int = 0
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "DAMAGE_MODIFIER":
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
total_modifier += effect.get("amount", 0)
|
|
|
|
return total_modifier
|
|
|
|
|
|
## Check if a card matches an effect's target specification
|
|
func _card_matches_effect_target(
|
|
card: CardInstance,
|
|
effect: Dictionary,
|
|
source: CardInstance,
|
|
game_state
|
|
) -> bool:
|
|
var target = effect.get("target", {})
|
|
if target.is_empty():
|
|
# No target specified, assume applies to source only
|
|
return card == source
|
|
|
|
var target_type = str(target.get("type", "")).to_upper()
|
|
|
|
# Check owner
|
|
var owner = str(target.get("owner", "ANY")).to_upper()
|
|
match owner:
|
|
"CONTROLLER":
|
|
if card.controller_index != source.controller_index:
|
|
return false
|
|
"OPPONENT":
|
|
if card.controller_index == source.controller_index:
|
|
return false
|
|
# "ANY" matches all
|
|
|
|
# Check if applies to self
|
|
if target_type == "SELF":
|
|
return card == source
|
|
|
|
# Check if applies to all matching
|
|
if target_type == "ALL":
|
|
return _matches_filter(card, target.get("filter", {}), source)
|
|
|
|
# Default check filter
|
|
return _matches_filter(card, target.get("filter", {}), source)
|
|
|
|
|
|
## Check if a card matches a filter (duplicated from TargetSelector for independence)
|
|
func _matches_filter(
|
|
card: CardInstance,
|
|
filter: Dictionary,
|
|
source: CardInstance
|
|
) -> bool:
|
|
if filter.is_empty():
|
|
return true
|
|
|
|
# Card type filter
|
|
if filter.has("card_type"):
|
|
var type_str = str(filter.card_type).to_upper()
|
|
match type_str:
|
|
"FORWARD":
|
|
if not card.is_forward():
|
|
return false
|
|
"BACKUP":
|
|
if not card.is_backup():
|
|
return false
|
|
"SUMMON":
|
|
if not card.is_summon():
|
|
return false
|
|
"CHARACTER":
|
|
if not (card.is_forward() or card.is_backup()):
|
|
return false
|
|
|
|
# Element filter
|
|
if filter.has("element"):
|
|
var element_str = str(filter.element).to_upper()
|
|
var element = Enums.element_from_string(element_str)
|
|
if element not in card.get_elements():
|
|
return false
|
|
|
|
# Cost filters
|
|
if filter.has("cost_min") and card.card_data.cost < int(filter.cost_min):
|
|
return false
|
|
if filter.has("cost_max") and card.card_data.cost > int(filter.cost_max):
|
|
return false
|
|
if filter.has("cost") and card.card_data.cost != int(filter.cost):
|
|
return false
|
|
|
|
# Power filters
|
|
if filter.has("power_min") and card.get_power() < int(filter.power_min):
|
|
return false
|
|
if filter.has("power_max") and card.get_power() > int(filter.power_max):
|
|
return false
|
|
|
|
# Name filter
|
|
if filter.has("name") and card.card_data.name != filter.name:
|
|
return false
|
|
|
|
# Category filter
|
|
if filter.has("category") and card.card_data.category != filter.category:
|
|
return false
|
|
|
|
# Job filter
|
|
if filter.has("job") and card.card_data.job != filter.job:
|
|
return false
|
|
|
|
# Exclude self
|
|
if filter.get("exclude_self", false) and card == source:
|
|
return false
|
|
|
|
return true
|
|
|
|
|
|
## Get count of active field abilities
|
|
func get_active_ability_count() -> int:
|
|
var count = 0
|
|
for instance_id in _active_abilities:
|
|
count += _active_abilities[instance_id].size()
|
|
return count
|
|
|
|
|
|
## Clear all active abilities (for game reset)
|
|
func clear_all() -> void:
|
|
_active_abilities.clear()
|
|
|
|
|
|
# =============================================================================
|
|
# BLOCK IMMUNITY CHECKS
|
|
# =============================================================================
|
|
|
|
## Check if a card has block immunity (can't be blocked by certain cards)
|
|
func has_block_immunity(card: CardInstance, potential_blocker: CardInstance, game_state) -> bool:
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var source = ability_data.source
|
|
if source != card:
|
|
continue
|
|
|
|
var ability = ability_data.ability
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "BLOCK_IMMUNITY":
|
|
var condition = effect.get("condition", {})
|
|
if _blocker_matches_immunity_condition(potential_blocker, condition, card):
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
## Check if blocker matches the immunity condition
|
|
func _blocker_matches_immunity_condition(
|
|
blocker: CardInstance,
|
|
condition: Dictionary,
|
|
attacker: CardInstance
|
|
) -> bool:
|
|
if condition.is_empty():
|
|
return true # Unconditional block immunity
|
|
|
|
var comparison = condition.get("comparison", "")
|
|
var attribute = condition.get("attribute", "")
|
|
var value = condition.get("value", 0)
|
|
var compare_to = condition.get("compare_to", "")
|
|
|
|
var blocker_value = 0
|
|
match attribute:
|
|
"cost":
|
|
blocker_value = blocker.card_data.cost if blocker.card_data else 0
|
|
"power":
|
|
blocker_value = blocker.get_power()
|
|
|
|
var compare_value = value
|
|
if compare_to == "SELF_POWER":
|
|
compare_value = attacker.get_power()
|
|
|
|
match comparison:
|
|
"GTE":
|
|
return blocker_value >= compare_value
|
|
"GT":
|
|
return blocker_value > compare_value
|
|
"LTE":
|
|
return blocker_value <= compare_value
|
|
"LT":
|
|
return blocker_value < compare_value
|
|
"EQ":
|
|
return blocker_value == compare_value
|
|
|
|
return false
|
|
|
|
|
|
# =============================================================================
|
|
# ATTACK RESTRICTION CHECKS
|
|
# =============================================================================
|
|
|
|
## Check if a card has attack restrictions
|
|
func has_attack_restriction(card: CardInstance, game_state) -> bool:
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "RESTRICTION":
|
|
var restriction = effect.get("restriction", "")
|
|
if restriction in ["CANNOT_ATTACK", "CANNOT_ATTACK_OR_BLOCK"]:
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
## Check if a card has block restrictions
|
|
func has_block_restriction(card: CardInstance, game_state) -> bool:
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "RESTRICTION":
|
|
var restriction = effect.get("restriction", "")
|
|
if restriction in ["CANNOT_BLOCK", "CANNOT_ATTACK_OR_BLOCK"]:
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
# =============================================================================
|
|
# TAUNT CHECKS (Must be targeted if possible)
|
|
# =============================================================================
|
|
|
|
## Get cards that must be targeted by opponent's abilities if possible
|
|
func get_taunt_targets(player_index: int, game_state) -> Array:
|
|
var taunt_cards: Array = []
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "TAUNT":
|
|
var target = effect.get("target", {})
|
|
if target.get("type") == "SELF":
|
|
if source.controller_index == player_index:
|
|
taunt_cards.append(source)
|
|
|
|
return taunt_cards
|
|
|
|
|
|
# =============================================================================
|
|
# COST MODIFICATION
|
|
# =============================================================================
|
|
|
|
## Get cost modifier for playing a card
|
|
func get_cost_modifier(
|
|
card_to_play: CardInstance,
|
|
playing_player: int,
|
|
game_state
|
|
) -> int:
|
|
var total_modifier = 0
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
var effect_type = effect.get("type", "")
|
|
|
|
if effect_type == "COST_REDUCTION":
|
|
if _cost_effect_applies(effect, card_to_play, playing_player, source, game_state):
|
|
total_modifier -= effect.get("amount", 0)
|
|
|
|
elif effect_type == "COST_REDUCTION_SCALING":
|
|
if _cost_effect_applies(effect, card_to_play, playing_player, source, game_state):
|
|
var reduction = _calculate_scaling_cost_reduction(effect, source, game_state)
|
|
total_modifier -= reduction
|
|
|
|
elif effect_type == "COST_INCREASE":
|
|
if _cost_effect_applies(effect, card_to_play, playing_player, source, game_state):
|
|
total_modifier += effect.get("amount", 0)
|
|
|
|
return total_modifier
|
|
|
|
|
|
## Check if a cost modification effect applies to a card being played
|
|
func _cost_effect_applies(
|
|
effect: Dictionary,
|
|
card: CardInstance,
|
|
player: int,
|
|
source: CardInstance,
|
|
game_state
|
|
) -> bool:
|
|
var for_player = effect.get("for_player", "CONTROLLER")
|
|
|
|
# Check if effect applies to this player
|
|
match for_player:
|
|
"CONTROLLER":
|
|
if player != source.controller_index:
|
|
return false
|
|
"OPPONENT":
|
|
if player == source.controller_index:
|
|
return false
|
|
|
|
# Check card filter
|
|
var card_filter = effect.get("card_filter", "")
|
|
if card_filter and not _card_matches_name_filter(card, card_filter):
|
|
return false
|
|
|
|
# Check condition
|
|
var condition = effect.get("condition", {})
|
|
if not condition.is_empty():
|
|
if not _cost_condition_met(condition, source, game_state):
|
|
return false
|
|
|
|
return true
|
|
|
|
|
|
## Check if a card name matches a filter
|
|
func _card_matches_name_filter(card: CardInstance, filter_text: String) -> bool:
|
|
if not card or not card.card_data:
|
|
return false
|
|
|
|
var filter_lower = filter_text.to_lower()
|
|
var card_name = card.card_data.name.to_lower()
|
|
|
|
# Direct name match
|
|
if card_name in filter_lower or filter_lower in card_name:
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
## Check if a cost condition is met
|
|
func _cost_condition_met(condition: Dictionary, source: CardInstance, game_state) -> bool:
|
|
if condition.has("control_card_name"):
|
|
var name_to_find = condition.control_card_name.to_lower()
|
|
var player = game_state.get_player(source.controller_index)
|
|
if player:
|
|
for card in player.field_forwards.get_cards():
|
|
if name_to_find in card.card_data.name.to_lower():
|
|
return true
|
|
for card in player.field_backups.get_cards():
|
|
if name_to_find in card.card_data.name.to_lower():
|
|
return true
|
|
return false
|
|
|
|
if condition.has("control_category"):
|
|
var category = condition.control_category.to_lower()
|
|
var player = game_state.get_player(source.controller_index)
|
|
if player:
|
|
for card in player.field_forwards.get_cards():
|
|
if category in card.card_data.category.to_lower():
|
|
return true
|
|
for card in player.field_backups.get_cards():
|
|
if category in card.card_data.category.to_lower():
|
|
return true
|
|
return false
|
|
|
|
return true
|
|
|
|
|
|
# =============================================================================
|
|
# SCALING COST REDUCTION
|
|
# =============================================================================
|
|
|
|
## Calculate cost reduction for a COST_REDUCTION_SCALING effect
|
|
func _calculate_scaling_cost_reduction(
|
|
effect: Dictionary,
|
|
source: CardInstance,
|
|
game_state
|
|
) -> int:
|
|
var reduction_per = effect.get("reduction_per", 1)
|
|
var scale_by = str(effect.get("scale_by", "")).to_upper()
|
|
var scale_filter = effect.get("scale_filter", {})
|
|
|
|
# Get scale value using similar logic to EffectResolver
|
|
var scale_value = _get_scale_value(scale_by, source, game_state, scale_filter)
|
|
|
|
return scale_value * reduction_per
|
|
|
|
|
|
## Get scale value based on scale_by type (with optional filter)
|
|
## Mirrors the logic in EffectResolver for consistency
|
|
func _get_scale_value(
|
|
scale_by: String,
|
|
source: CardInstance,
|
|
game_state,
|
|
scale_filter: Dictionary = {}
|
|
) -> int:
|
|
if not source or not game_state:
|
|
return 0
|
|
|
|
var player_index = source.controller_index
|
|
var player = game_state.get_player(player_index)
|
|
if not player:
|
|
return 0
|
|
|
|
# Determine owner from filter (default to CONTROLLER)
|
|
var owner = scale_filter.get("owner", "CONTROLLER").to_upper() if scale_filter else "CONTROLLER"
|
|
|
|
# Get cards based on scale_by and owner
|
|
var cards_to_count: Array = []
|
|
|
|
match scale_by:
|
|
"DAMAGE_RECEIVED":
|
|
# Special case - not card-based
|
|
return _get_damage_for_owner(owner, player_index, game_state)
|
|
"FORWARDS_CONTROLLED", "FORWARDS":
|
|
cards_to_count = _get_forwards_for_owner(owner, player_index, game_state)
|
|
"BACKUPS_CONTROLLED", "BACKUPS":
|
|
cards_to_count = _get_backups_for_owner(owner, player_index, game_state)
|
|
"FIELD_CARDS_CONTROLLED", "FIELD_CARDS":
|
|
cards_to_count = _get_field_cards_for_owner(owner, player_index, game_state)
|
|
"CARDS_IN_HAND":
|
|
cards_to_count = _get_hand_for_owner(owner, player_index, game_state)
|
|
"CARDS_IN_BREAK_ZONE":
|
|
cards_to_count = _get_break_zone_for_owner(owner, player_index, game_state)
|
|
"OPPONENT_FORWARDS":
|
|
cards_to_count = _get_forwards_for_owner("OPPONENT", player_index, game_state)
|
|
"OPPONENT_BACKUPS":
|
|
cards_to_count = _get_backups_for_owner("OPPONENT", player_index, game_state)
|
|
_:
|
|
push_warning("FieldEffectManager: Unknown scale_by type: " + scale_by)
|
|
return 0
|
|
|
|
# If no filter, just return count
|
|
if not scale_filter or scale_filter.is_empty() or (scale_filter.size() == 1 and scale_filter.has("owner")):
|
|
return cards_to_count.size()
|
|
|
|
# Apply filter and count matching cards using CardFilter utility
|
|
return CardFilter.count_matching(cards_to_count, scale_filter)
|
|
|
|
|
|
# =============================================================================
|
|
# OWNER-BASED ACCESS HELPERS FOR SCALING
|
|
# =============================================================================
|
|
|
|
func _get_forwards_for_owner(owner: String, player_index: int, game_state) -> Array:
|
|
match owner.to_upper():
|
|
"CONTROLLER":
|
|
var player = game_state.get_player(player_index)
|
|
return player.field_forwards.get_cards() if player and player.field_forwards else []
|
|
"OPPONENT":
|
|
var opponent = game_state.get_player(1 - player_index)
|
|
return opponent.field_forwards.get_cards() if opponent and opponent.field_forwards else []
|
|
_:
|
|
var all_cards = []
|
|
for p in game_state.players:
|
|
if p and p.field_forwards:
|
|
all_cards.append_array(p.field_forwards.get_cards())
|
|
return all_cards
|
|
|
|
|
|
func _get_backups_for_owner(owner: String, player_index: int, game_state) -> Array:
|
|
match owner.to_upper():
|
|
"CONTROLLER":
|
|
var player = game_state.get_player(player_index)
|
|
return player.field_backups.get_cards() if player and player.field_backups else []
|
|
"OPPONENT":
|
|
var opponent = game_state.get_player(1 - player_index)
|
|
return opponent.field_backups.get_cards() if opponent and opponent.field_backups else []
|
|
_:
|
|
var all_cards = []
|
|
for p in game_state.players:
|
|
if p and p.field_backups:
|
|
all_cards.append_array(p.field_backups.get_cards())
|
|
return all_cards
|
|
|
|
|
|
func _get_field_cards_for_owner(owner: String, player_index: int, game_state) -> Array:
|
|
var cards = []
|
|
cards.append_array(_get_forwards_for_owner(owner, player_index, game_state))
|
|
cards.append_array(_get_backups_for_owner(owner, player_index, game_state))
|
|
return cards
|
|
|
|
|
|
func _get_hand_for_owner(owner: String, player_index: int, game_state) -> Array:
|
|
match owner.to_upper():
|
|
"CONTROLLER":
|
|
var player = game_state.get_player(player_index)
|
|
return player.hand.get_cards() if player and player.hand else []
|
|
"OPPONENT":
|
|
var opponent = game_state.get_player(1 - player_index)
|
|
return opponent.hand.get_cards() if opponent and opponent.hand else []
|
|
_:
|
|
var all_cards = []
|
|
for p in game_state.players:
|
|
if p and p.hand:
|
|
all_cards.append_array(p.hand.get_cards())
|
|
return all_cards
|
|
|
|
|
|
func _get_break_zone_for_owner(owner: String, player_index: int, game_state) -> Array:
|
|
match owner.to_upper():
|
|
"CONTROLLER":
|
|
var player = game_state.get_player(player_index)
|
|
return player.break_zone.get_cards() if player and player.break_zone else []
|
|
"OPPONENT":
|
|
var opponent = game_state.get_player(1 - player_index)
|
|
return opponent.break_zone.get_cards() if opponent and opponent.break_zone else []
|
|
_:
|
|
var all_cards = []
|
|
for p in game_state.players:
|
|
if p and p.break_zone:
|
|
all_cards.append_array(p.break_zone.get_cards())
|
|
return all_cards
|
|
|
|
|
|
func _get_damage_for_owner(owner: String, player_index: int, game_state) -> int:
|
|
match owner.to_upper():
|
|
"CONTROLLER":
|
|
var player = game_state.get_player(player_index)
|
|
return player.damage if player and "damage" in player else 0
|
|
"OPPONENT":
|
|
var opponent = game_state.get_player(1 - player_index)
|
|
return opponent.damage if opponent and "damage" in opponent else 0
|
|
_:
|
|
var total = 0
|
|
for p in game_state.players:
|
|
if p and "damage" in p:
|
|
total += p.damage
|
|
return total
|
|
|
|
|
|
# =============================================================================
|
|
# MULTI-ATTACK CHECKS
|
|
# =============================================================================
|
|
|
|
## Get maximum attacks allowed for a card this turn
|
|
func get_max_attacks(card: CardInstance, game_state) -> int:
|
|
var max_attacks = 1 # Default is 1 attack per turn
|
|
|
|
for instance_id in _active_abilities:
|
|
var abilities = _active_abilities[instance_id]
|
|
for ability_data in abilities:
|
|
var ability = ability_data.ability
|
|
var source = ability_data.source
|
|
var parsed = ability.get("parsed", {})
|
|
|
|
for effect in parsed.get("effects", []):
|
|
if effect.get("type") == "MULTI_ATTACK":
|
|
if _card_matches_effect_target(card, effect, source, game_state):
|
|
var attack_count = effect.get("attack_count", 1)
|
|
if attack_count > max_attacks:
|
|
max_attacks = attack_count
|
|
|
|
return max_attacks
|