class_name TriggerMatcher extends RefCounted ## TriggerMatcher - Matches game events to ability triggers ## Scans all cards on field for abilities that trigger from the given event ## Reference to ConditionChecker for evaluating trigger conditions var condition_checker: ConditionChecker = null ## Find all abilities that should trigger for a given event func find_triggered_abilities( event_type: String, event_data: Dictionary, game_state, all_abilities: Dictionary ) -> Array: var triggered = [] # Check abilities on all cards in play for player in game_state.players: # Check forwards for card in player.field_forwards.get_cards(): var card_abilities = all_abilities.get(card.card_data.id, []) triggered.append_array(_check_card_abilities(card, card_abilities, event_type, event_data, game_state)) # Check backups for card in player.field_backups.get_cards(): var card_abilities = all_abilities.get(card.card_data.id, []) triggered.append_array(_check_card_abilities(card, card_abilities, event_type, event_data, game_state)) return triggered ## Check all abilities on a card for triggers func _check_card_abilities( card: CardInstance, abilities: Array, event_type: String, event_data: Dictionary, game_state ) -> Array: var triggered = [] for ability in abilities: if _matches_trigger(ability, event_type, event_data, card, game_state): triggered.append({ "source": card, "ability": ability, "event_data": event_data }) return triggered ## Check if an ability's trigger matches the event func _matches_trigger( ability: Dictionary, event_type: String, event_data: Dictionary, source_card: CardInstance, game_state ) -> bool: var parsed = ability.get("parsed", {}) if parsed.is_empty(): return false # Only AUTO abilities have triggers if parsed.get("type") != "AUTO": return false var trigger = parsed.get("trigger", {}) if trigger.is_empty(): return false # Check event type matches var trigger_event = trigger.get("event", "") if not _event_matches(trigger_event, event_type): return false # Check source filter var trigger_source = trigger.get("source", "ANY") if not _source_matches(trigger_source, event_data, source_card, game_state): return false # Check additional trigger filters if trigger.has("source_filter"): var filter = trigger.source_filter var event_card = event_data.get("card") if event_card and not _matches_card_filter(event_card, filter): return false # Check trigger condition (if present) var trigger_condition = trigger.get("condition", {}) if not trigger_condition.is_empty() and condition_checker: var context = { "source_card": source_card, "target_card": event_data.get("card"), "game_state": game_state, "player_id": source_card.controller_index if source_card else 0, "event_data": event_data } if not condition_checker.evaluate(trigger_condition, context): return false return true ## Check if event type matches trigger event func _event_matches(trigger_event: String, actual_event: String) -> bool: # Direct match if trigger_event == actual_event: return true # Handle variations match trigger_event: "ENTERS_FIELD": return actual_event in ["ENTERS_FIELD", "CARD_PLAYED"] "LEAVES_FIELD": return actual_event in ["LEAVES_FIELD", "FORWARD_BROKEN", "CARD_BROKEN"] "DEALS_DAMAGE": return actual_event in ["DEALS_DAMAGE", "DEALS_DAMAGE_TO_OPPONENT", "DEALS_DAMAGE_TO_FORWARD"] "DEALS_DAMAGE_TO_OPPONENT": return actual_event == "DEALS_DAMAGE_TO_OPPONENT" "DEALS_DAMAGE_TO_FORWARD": return actual_event == "DEALS_DAMAGE_TO_FORWARD" "BLOCKS_OR_IS_BLOCKED": return actual_event in ["BLOCKS", "IS_BLOCKED"] return false ## Check if source matches trigger requirements func _source_matches( trigger_source: String, event_data: Dictionary, source_card: CardInstance, game_state ) -> bool: var event_card = event_data.get("card") match trigger_source: "SELF": # Trigger source must be this card return event_card == source_card "CONTROLLER": # Trigger source must be controlled by same player if event_card: return event_card.controller_index == source_card.controller_index var event_player = event_data.get("player", -1) return event_player == source_card.controller_index "OPPONENT": # Trigger source must be controlled by opponent if event_card: return event_card.controller_index != source_card.controller_index var event_player = event_data.get("player", -1) return event_player != source_card.controller_index and event_player >= 0 "ANY", _: # Any source triggers return true return true ## Check if a card matches a filter func _matches_card_filter(card: CardInstance, filter: Dictionary) -> 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 # 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"): if card.card_data.cost < filter.cost_min: return false if filter.has("cost_max"): if card.card_data.cost > filter.cost_max: return false if filter.has("cost"): if card.card_data.cost != filter.cost: return false # Power filters if filter.has("power_min"): if card.get_power() < filter.power_min: return false if filter.has("power_max"): if card.get_power() > filter.power_max: return false # State filters if filter.has("is_dull"): if card.is_dull() != filter.is_dull: return false if filter.has("is_active"): if card.is_active() != filter.is_active: return false # Name filter if filter.has("name"): if card.card_data.name != filter.name: return false # Category filter if filter.has("category"): if card.card_data.category != filter.category: return false # Job filter if filter.has("job"): if card.card_data.job != filter.job: return false return true