511 lines
15 KiB
GDScript
511 lines
15 KiB
GDScript
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 []
|