Files
FFCardGame/scripts/game/abilities/TriggerMatcher.gd
2026-02-02 16:28:53 -05:00

234 lines
6.2 KiB
GDScript

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