class_name ConditionChecker extends RefCounted ## Centralized condition evaluation for all ability types ## Handles conditions like "If you control X", "If you have received Y damage", etc. ## Main evaluation entry point ## Returns true if condition is met, false otherwise func evaluate(condition: Dictionary, context: Dictionary) -> bool: if condition.is_empty(): return true # Empty condition = unconditional var condition_type = condition.get("type", "") match condition_type: "CONTROL_CARD": return _check_control_card(condition, context) "CONTROL_COUNT": return _check_control_count(condition, context) "DAMAGE_RECEIVED": return _check_damage_received(condition, context) "BREAK_ZONE_COUNT": return _check_break_zone_count(condition, context) "CARD_IN_ZONE": return _check_card_in_zone(condition, context) "FORWARD_STATE": return _check_forward_state(condition, context) "COST_COMPARISON": return _check_cost_comparison(condition, context) "POWER_COMPARISON": return _check_power_comparison(condition, context) "ELEMENT_MATCH": return _check_element_match(condition, context) "CARD_TYPE_MATCH": return _check_card_type_match(condition, context) "JOB_MATCH": return _check_job_match(condition, context) "CATEGORY_MATCH": return _check_category_match(condition, context) "AND": return _check_and(condition, context) "OR": return _check_or(condition, context) "NOT": return _check_not(condition, context) _: push_warning("ConditionChecker: Unknown condition type '%s'" % condition_type) return false # ============================================================================= # CONTROL CONDITIONS # ============================================================================= func _check_control_card(condition: Dictionary, context: Dictionary) -> bool: var card_name = condition.get("card_name", "") var player = context.get("player_id", 0) var game_state = context.get("game_state") if not game_state: return false # Check all field cards for the player var field_cards = _get_field_cards(game_state, player) for card in field_cards: if card and card.card_data and card.card_data.name == card_name: return true return false func _check_control_count(condition: Dictionary, context: Dictionary) -> bool: var card_type = condition.get("card_type", "") var element = condition.get("element", "") var job = condition.get("job", "") var category = condition.get("category", "") var comparison = condition.get("comparison", "GTE") var value = condition.get("value", 1) var player = context.get("player_id", 0) var game_state = context.get("game_state") if not game_state: return false var count = 0 var field_cards = _get_field_cards(game_state, player) for card in field_cards: if not card or not card.card_data: continue var matches = true # Check card type filter if card_type != "" and not _matches_card_type(card, card_type): matches = false # Check element filter if element != "" and not _matches_element(card, element): matches = false # Check job filter if job != "" and not _matches_job(card, job): matches = false # Check category filter if category != "" and not _matches_category(card, category): matches = false if matches: count += 1 return _compare(count, comparison, value) # ============================================================================= # DAMAGE CONDITIONS # ============================================================================= func _check_damage_received(condition: Dictionary, context: Dictionary) -> bool: var comparison = condition.get("comparison", "GTE") var value = condition.get("value", 1) var player = context.get("player_id", 0) var game_state = context.get("game_state") if not game_state: return false var damage = _get_player_damage(game_state, player) return _compare(damage, comparison, value) # ============================================================================= # ZONE CONDITIONS # ============================================================================= func _check_break_zone_count(condition: Dictionary, context: Dictionary) -> bool: var card_name = condition.get("card_name", "") var card_names: Array = condition.get("card_names", []) if card_name != "" and card_name not in card_names: card_names.append(card_name) var comparison = condition.get("comparison", "GTE") var value = condition.get("value", 1) var player = context.get("player_id", 0) var game_state = context.get("game_state") if not game_state: return false var count = 0 var break_zone = _get_break_zone(game_state, player) for card in break_zone: if not card or not card.card_data: continue # If no specific names, count all if card_names.is_empty(): count += 1 elif card.card_data.name in card_names: count += 1 return _compare(count, comparison, value) func _check_card_in_zone(condition: Dictionary, context: Dictionary) -> bool: var zone = condition.get("zone", "") # "HAND", "DECK", "BREAK_ZONE", "REMOVED" var card_name = condition.get("card_name", "") var card_type = condition.get("card_type", "") var player = context.get("player_id", 0) var game_state = context.get("game_state") if not game_state: return false var zone_cards: Array = [] match zone: "HAND": zone_cards = _get_hand(game_state, player) "DECK": zone_cards = _get_deck(game_state, player) "BREAK_ZONE": zone_cards = _get_break_zone(game_state, player) "REMOVED": zone_cards = _get_removed_zone(game_state, player) "FIELD": zone_cards = _get_field_cards(game_state, player) for card in zone_cards: if not card or not card.card_data: continue var matches = true if card_name != "" and card.card_data.name != card_name: matches = false if card_type != "" and not _matches_card_type(card, card_type): matches = false if matches: return true return false # ============================================================================= # CARD STATE CONDITIONS # ============================================================================= func _check_forward_state(condition: Dictionary, context: Dictionary) -> bool: var state = condition.get("state", "") # "DULL", "ACTIVE", "DAMAGED" var check_self = condition.get("check_self", false) var target = context.get("target_card") if not check_self else context.get("source_card") if not target: return false match state: "DULL": return target.is_dull if target.has_method("get") or "is_dull" in target else false "ACTIVE": return not target.is_dull if "is_dull" in target else false "DAMAGED": if "current_power" in target and target.card_data: return target.current_power < target.card_data.power "FROZEN": return target.is_frozen if "is_frozen" in target else false return false func _check_cost_comparison(condition: Dictionary, context: Dictionary) -> bool: var comparison = condition.get("comparison", "LTE") var value = condition.get("value", 0) var compare_to = condition.get("compare_to", "") # "SELF_COST", "VALUE", or empty for value var target = context.get("target_card") var source = context.get("source_card") if not target or not target.card_data: return false var target_cost = target.card_data.cost var compare_value = value if compare_to == "SELF_COST" and source and source.card_data: compare_value = source.card_data.cost return _compare(target_cost, comparison, compare_value) func _check_power_comparison(condition: Dictionary, context: Dictionary) -> bool: var comparison = condition.get("comparison", "LTE") var value = condition.get("value", 0) var compare_to = condition.get("compare_to", "") # "SELF_POWER", "VALUE" var target = context.get("target_card") var source = context.get("source_card") if not target: return false var target_power = target.current_power if "current_power" in target else 0 var compare_value = value if compare_to == "SELF_POWER" and source: compare_value = source.current_power if "current_power" in source else 0 return _compare(target_power, comparison, compare_value) # ============================================================================= # CARD ATTRIBUTE CONDITIONS # ============================================================================= func _check_element_match(condition: Dictionary, context: Dictionary) -> bool: var element = condition.get("element", "") var check_self = condition.get("check_self", false) var target = context.get("target_card") if not check_self else context.get("source_card") if not target or not target.card_data: return false return _matches_element(target, element) func _check_card_type_match(condition: Dictionary, context: Dictionary) -> bool: var card_type = condition.get("card_type", "") var check_self = condition.get("check_self", false) var target = context.get("target_card") if not check_self else context.get("source_card") if not target: return false return _matches_card_type(target, card_type) func _check_job_match(condition: Dictionary, context: Dictionary) -> bool: var job = condition.get("job", "") var check_self = condition.get("check_self", false) var target = context.get("target_card") if not check_self else context.get("source_card") if not target or not target.card_data: return false return _matches_job(target, job) func _check_category_match(condition: Dictionary, context: Dictionary) -> bool: var category = condition.get("category", "") var check_self = condition.get("check_self", false) var target = context.get("target_card") if not check_self else context.get("source_card") if not target or not target.card_data: return false return _matches_category(target, category) # ============================================================================= # LOGICAL OPERATORS # ============================================================================= func _check_and(condition: Dictionary, context: Dictionary) -> bool: var conditions: Array = condition.get("conditions", []) for sub_condition in conditions: if not evaluate(sub_condition, context): return false return true func _check_or(condition: Dictionary, context: Dictionary) -> bool: var conditions: Array = condition.get("conditions", []) for sub_condition in conditions: if evaluate(sub_condition, context): return true return false func _check_not(condition: Dictionary, context: Dictionary) -> bool: var inner: Dictionary = condition.get("condition", {}) return not evaluate(inner, context) # ============================================================================= # HELPER FUNCTIONS # ============================================================================= func _compare(actual: int, comparison: String, expected: int) -> bool: match comparison: "EQ": return actual == expected "NEQ": return actual != expected "GT": return actual > expected "GTE": return actual >= expected "LT": return actual < expected "LTE": return actual <= expected return false func _matches_card_type(card, card_type: String) -> bool: if not card or not card.card_data: return false var type_upper = card_type.to_upper() var card_type_value = card.card_data.type # Handle string or enum type if card_type_value is String: return card_type_value.to_upper() == type_upper # Handle Enums.CardType enum match type_upper: "FORWARD": return card_type_value == Enums.CardType.FORWARD "BACKUP": return card_type_value == Enums.CardType.BACKUP "SUMMON": return card_type_value == Enums.CardType.SUMMON "MONSTER": return card_type_value == Enums.CardType.MONSTER return false func _matches_element(card, element: String) -> bool: if not card or not card.card_data: return false var element_upper = element.to_upper() var card_element = card.card_data.element if card_element is String: return card_element.to_upper() == element_upper # Handle Enums.Element enum match element_upper: "FIRE": return card_element == Enums.Element.FIRE "ICE": return card_element == Enums.Element.ICE "WIND": return card_element == Enums.Element.WIND "EARTH": return card_element == Enums.Element.EARTH "LIGHTNING": return card_element == Enums.Element.LIGHTNING "WATER": return card_element == Enums.Element.WATER "LIGHT": return card_element == Enums.Element.LIGHT "DARK": return card_element == Enums.Element.DARK return false func _matches_job(card, job: String) -> bool: if not card or not card.card_data: return false var card_job = card.card_data.get("job", "") if card_job is String: return card_job.to_lower() == job.to_lower() return false func _matches_category(card, category: String) -> bool: if not card or not card.card_data: return false var card_categories = card.card_data.get("categories", []) if card_categories is Array: for cat in card_categories: if cat is String and cat.to_lower() == category.to_lower(): return true return false # ============================================================================= # GAME STATE ACCESSORS # These abstract away the game state interface for flexibility # ============================================================================= func _get_field_cards(game_state, player: int) -> Array: if game_state.has_method("get_field_cards"): return game_state.get_field_cards(player) elif game_state.has_method("get_player_field"): return game_state.get_player_field(player) elif "players" in game_state and player < game_state.players.size(): var p = game_state.players[player] if "field" in p: return p.field return [] func _get_player_damage(game_state, player: int) -> int: if game_state.has_method("get_player_damage"): return game_state.get_player_damage(player) elif "players" in game_state and player < game_state.players.size(): var p = game_state.players[player] if "damage" in p: return p.damage return 0 func _get_break_zone(game_state, player: int) -> Array: if game_state.has_method("get_break_zone"): return game_state.get_break_zone(player) elif "players" in game_state and player < game_state.players.size(): var p = game_state.players[player] if "break_zone" in p: return p.break_zone return [] func _get_hand(game_state, player: int) -> Array: if game_state.has_method("get_hand"): return game_state.get_hand(player) elif "players" in game_state and player < game_state.players.size(): var p = game_state.players[player] if "hand" in p: return p.hand return [] func _get_deck(game_state, player: int) -> Array: if game_state.has_method("get_deck"): return game_state.get_deck(player) elif "players" in game_state and player < game_state.players.size(): var p = game_state.players[player] if "deck" in p: return p.deck return [] func _get_removed_zone(game_state, player: int) -> Array: if game_state.has_method("get_removed_zone"): return game_state.get_removed_zone(player) elif "players" in game_state and player < game_state.players.size(): var p = game_state.players[player] if "removed_zone" in p: return p.removed_zone return []