class_name EffectResolver extends RefCounted ## EffectResolver - Executes parsed effects on game state ## Handles all 58+ effect types from the ability processor signal effect_completed(effect: Dictionary, targets: Array) signal targeting_required(effect: Dictionary, valid_targets: Array, count: int) signal choice_required(effect: Dictionary, options: Array) ## Reference to ConditionChecker for evaluating conditions var condition_checker: ConditionChecker = null ## Resolve an effect func resolve( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var effect_type = str(effect.get("type", "")).to_upper() match effect_type: # === CORE EFFECTS (Most Common) === "DAMAGE": _resolve_damage(effect, source, targets, game_state) "DRAW": _resolve_draw(effect, source, game_state) "BREAK": _resolve_break(effect, targets, game_state) "DULL": _resolve_dull(effect, targets, game_state) "ACTIVATE": _resolve_activate(effect, targets, game_state) "POWER_MOD": _resolve_power_mod(effect, source, targets, game_state) "SEARCH": _resolve_search(effect, source, game_state) "PLAY": _resolve_play(effect, source, targets, game_state) "RETURN": _resolve_return(effect, targets, game_state) "DISCARD": _resolve_discard(effect, source, targets, game_state) # === KEYWORD & ABILITY EFFECTS === "ABILITY_GRANT": _resolve_ability_grant(effect, source, targets, game_state) "KEYWORD": _resolve_keyword(effect, source, targets, game_state) "REMOVE_ABILITY": _resolve_remove_ability(effect, targets, game_state) # === ZONE MOVEMENT === "RETRIEVE": _resolve_retrieve(effect, source, targets, game_state) "REMOVE_FROM_GAME": _resolve_remove_from_game(effect, targets, game_state) "PUT_INTO_BREAK_ZONE": _resolve_put_into_break_zone(effect, targets, game_state) "ADD_TO_HAND": _resolve_add_to_hand(effect, source, targets, game_state) "PUT_ON_FIELD": _resolve_put_on_field(effect, source, targets, game_state) "PUT_INTO_DECK": _resolve_put_into_deck(effect, source, targets, game_state) "REVEAL_AND_ADD": _resolve_reveal_and_add(effect, source, game_state) # === CARD STATE EFFECTS === "FREEZE": _resolve_freeze(effect, targets, game_state) "UNFREEZE": _resolve_unfreeze(effect, targets, game_state) "ADD_COUNTER": _resolve_add_counter(effect, targets, game_state) "REMOVE_COUNTER": _resolve_remove_counter(effect, targets, game_state) "TRANSFORM": _resolve_transform(effect, source, targets, game_state) # === COMBAT EFFECTS === "MUTUAL_DAMAGE": _resolve_mutual_damage(effect, source, targets, game_state) "LETHAL": _resolve_lethal(effect, targets, game_state) "CANT_ATTACK": _resolve_cant_attack(effect, targets, game_state) "CANT_BLOCK": _resolve_cant_block(effect, targets, game_state) "MUST_ATTACK": _resolve_must_attack(effect, targets, game_state) "MUST_BLOCK": _resolve_must_block(effect, targets, game_state) "UNBLOCKABLE": _resolve_unblockable(effect, source, targets, game_state) "BRAVE": _resolve_brave(effect, source, targets, game_state) "HASTE": _resolve_haste(effect, source, targets, game_state) "FIRST_STRIKE": _resolve_first_strike(effect, source, targets, game_state) # === PREVENTION & PROTECTION === "PREVENT": _resolve_prevent(effect, source, targets, game_state) "PROTECTION": _resolve_protection(effect, source, targets, game_state) "DAMAGE_IMMUNITY": _resolve_damage_immunity(effect, targets, game_state) "BREAK_IMMUNITY": _resolve_break_immunity(effect, targets, game_state) "SELECTION_IMMUNITY": _resolve_selection_immunity(effect, targets, game_state) # === COST & RESOURCE EFFECTS === "REDUCE_COST": _resolve_reduce_cost(effect, source, game_state) "INCREASE_COST": _resolve_increase_cost(effect, targets, game_state) "GENERATE_CP": _resolve_generate_cp(effect, source, game_state) "PAY_COST": _resolve_pay_cost(effect, source, game_state) # === DECK MANIPULATION === "SHUFFLE": _resolve_shuffle(effect, source, game_state) "LOOK_AT_TOP": _resolve_look_at_top(effect, source, game_state) "REORDER_TOP": _resolve_reorder_top(effect, source, targets, game_state) "MILL": _resolve_mill(effect, source, game_state) # === CHOICE & MODAL EFFECTS === "CHOOSE": _resolve_choose(effect, source, targets, game_state) "CHOOSE_MODE": _resolve_choose_mode(effect, source, game_state) # === SPECIAL EFFECTS === "COPY": _resolve_copy(effect, source, targets, game_state) "CANCEL": _resolve_cancel(effect, targets, game_state) "TAKE_CONTROL": _resolve_take_control(effect, source, targets, game_state) "SWAP": _resolve_swap(effect, targets, game_state) "TAKE_EXTRA_TURN": _resolve_take_extra_turn(effect, source, game_state) "WIN_GAME": _resolve_win_game(effect, source, game_state) # === DAMAGE TO PLAYER === "DAMAGE_TO_OPPONENT": _resolve_damage_to_opponent(effect, source, game_state) "DAMAGE_TO_CONTROLLER": _resolve_damage_to_controller(effect, source, game_state) # === HEALING & RECOVERY === "HEAL": _resolve_heal(effect, source, targets, game_state) "REMOVE_DAMAGE": _resolve_remove_damage(effect, targets, game_state) # === CONTINUOUS EFFECTS (Handled by FieldEffectManager) === "DAMAGE_MODIFIER": # Continuous - registered with FieldEffectManager pass # === PROPERTY MODIFICATION === "MODIFY": _resolve_modify(effect, targets, game_state) # === PERMISSION / CONDITIONAL === "PERMISSION": _resolve_permission(effect, source, game_state) "CONDITIONAL_EFFECT": _resolve_conditional_effect(effect, source, targets, game_state) "CONDITIONAL": _resolve_conditional(effect, source, targets, game_state) "CHAINED_EFFECT": _resolve_chained_effect(effect, source, targets, game_state) "SCALING_EFFECT": _resolve_scaling_effect(effect, source, targets, game_state) "TRIGGER_EX_BURST": _resolve_trigger_ex_burst(effect, targets, game_state) # === FIELD EFFECT TYPES (registered, not resolved) === # These are registered with FieldEffectManager when card enters field "BLOCK_IMMUNITY", "RESTRICTION", "COST_REDUCTION", "COST_INCREASE", \ "TAUNT", "CONDITIONAL_FIELD", "GAIN_ELEMENTS", "HAS_ALL_JOBS", \ "DAMAGE_PREVENTION", "DAMAGE_PREVENTION_CONDITIONAL", "ENTERS_DULL", \ "PARTY_ANY_ELEMENT", "CAST_RESTRICTION", "CAST_RESTRICTION_CP_SOURCE", \ "CAST_RESTRICTION_CP_TYPE", "CAST_REQUIREMENT", "MULTI_ATTACK", \ "SUPPRESS_EX_BURST", "SUPPRESS_AUTO_TRIGGERS", "SUPPRESS_ACTION_ABILITIES", \ "ALLOW_MULTIPLE", "ALSO_NAMED", "ALSO_TYPE", "PLAY_RESTRICTION", \ "PRODUCE_CP", "PRODUCE_ANY_CP", "COST_ANY_ELEMENT", "ACTION_HASTE", \ "HASTE_LIKE", "MODAL": # These are continuous effects registered by FieldEffectManager pass # === DAMAGE SCALING (deal X damage for each Y) === "DAMAGE_SCALING": _resolve_damage_scaling(effect, source, targets, game_state) # === OPPONENT MAY PLAY === "OPPONENT_MAY", "OPPONENT_MAY_PLAY": _resolve_opponent_may(effect, source, game_state) # === UNKNOWN / UNPARSED === "UNKNOWN": push_warning("EffectResolver: Unknown effect type for: " + str(effect)) _: push_warning("EffectResolver: Unhandled effect type: " + effect_type) effect_completed.emit(effect, targets) ## Deal damage to targets func _resolve_damage( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var amount = _resolve_damage_amount(effect, source, game_state) for target in targets: if target is CardInstance and target.is_forward(): var broken = target.apply_damage(amount) if broken: var player = game_state.get_player(target.controller_index) if player: player.break_card(target) ## Resolve damage amount, handling special values like HIGHEST_CONTROLLED_POWER func _resolve_damage_amount(effect: Dictionary, source: CardInstance, game_state) -> int: var amount = effect.get("amount", 0) # Check for special dynamic amount values if amount is String: var amount_filter = effect.get("amount_filter", {}) match amount.to_upper(): "HIGHEST_CONTROLLED_POWER": return _get_highest_power(source.controller_index, game_state, amount_filter) "LOWEST_CONTROLLED_POWER": return _get_lowest_power(source.controller_index, game_state, amount_filter) "HIGHEST_OPPONENT_POWER": return _get_highest_power(1 - source.controller_index, game_state, amount_filter) "HIGHEST_CONTROLLED_COST": return _get_highest_cost(source.controller_index, game_state, amount_filter) _: push_warning("EffectResolver: Unknown damage amount type: " + amount) return 0 return int(amount) ## Draw cards func _resolve_draw( effect: Dictionary, source: CardInstance, game_state ) -> void: var amount = effect.get("amount", 1) var target = effect.get("target", {}) var target_owner = target.get("type", "CONTROLLER") var player_index = source.controller_index if target_owner == "OPPONENT": player_index = 1 - player_index var player = game_state.get_player(player_index) if player: player.draw_cards(amount) ## Break targets func _resolve_break( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: var player = game_state.get_player(target.controller_index) if player: player.break_card(target) ## Dull targets func _resolve_dull( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: target.dull() ## Activate targets func _resolve_activate( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: target.activate() ## Apply power modifier func _resolve_power_mod( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var amount = effect.get("amount", 0) var duration = effect.get("duration", "END_OF_TURN") # Determine targets var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: # Add to temporary power modifiers target.power_modifiers.append(amount) ## Search deck for cards func _resolve_search( effect: Dictionary, source: CardInstance, game_state ) -> void: # Search requires UI interaction - for now just log var count = effect.get("count", 1) var filter = effect.get("filter", {}) var destination = effect.get("destination", "HAND") print("EffectResolver: Search for %d cards matching %s -> %s" % [count, filter, destination]) # TODO: Implement search UI and card selection ## Play card from zone func _resolve_play( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var modifiers = effect.get("modifiers", {}) var enters_dull = modifiers.get("enters_dull", false) for target in targets: if target is CardInstance: var player = game_state.get_player(source.controller_index) if player: # Move card to field # This is simplified - full implementation needs zone management if target.is_forward(): player.hand.remove_card(target) player.field_forwards.add_card(target) target.entered_field() if enters_dull: target.dull() elif target.is_backup(): player.hand.remove_card(target) player.field_backups.add_card(target) target.entered_field() if enters_dull: target.dull() ## Return cards to hand func _resolve_return( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: var player = game_state.get_player(target.owner_index) if player: # Remove from field if target.is_forward() and player.field_forwards.has_card(target): player.field_forwards.remove_card(target) player.hand.add_card(target) elif target.is_backup() and player.field_backups.has_card(target): player.field_backups.remove_card(target) player.hand.add_card(target) ## Force discard func _resolve_discard( effect: Dictionary, source: CardInstance, game_state ) -> void: var amount = effect.get("amount", 1) var target = effect.get("target", {}) var target_owner = target.get("type", "OPPONENT") var player_index = source.controller_index if target_owner == "OPPONENT": player_index = 1 - player_index # Discard requires UI interaction for card selection print("EffectResolver: Player %d discards %d cards" % [player_index, amount]) # TODO: Implement discard UI ## Grant temporary ability func _resolve_ability_grant( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var ability_name = effect.get("ability", "") var duration = effect.get("duration", "END_OF_TURN") var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: target.temporary_abilities.append(ability_name) ## Apply prevention effect func _resolve_prevent( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var action = effect.get("action", "") var duration = effect.get("duration", "END_OF_TURN") # Prevention effects need state tracking on cards # For now, log what should happen print("EffectResolver: Prevent %s on targets until %s" % [action, duration]) # TODO: Implement prevention state tracking ## Freeze targets (prevent activation) func _resolve_freeze( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: target.set_frozen(true) ## Unfreeze targets func _resolve_unfreeze( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: target.set_frozen(false) # ============================================================================= # KEYWORD & ABILITY EFFECTS # ============================================================================= ## Grant keyword ability (for triggered/temporary grants, not FIELD) func _resolve_keyword( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var keyword = str(effect.get("keyword", "")).to_upper() var duration = effect.get("duration", "END_OF_TURN") var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: target.add_temporary_keyword(keyword, duration) ## Remove ability from targets func _resolve_remove_ability( effect: Dictionary, targets: Array, game_state ) -> void: var ability_name = effect.get("ability", "ALL") for target in targets: if target is CardInstance: if ability_name == "ALL": target.remove_all_abilities() else: target.remove_ability(ability_name) # ============================================================================= # ZONE MOVEMENT EFFECTS # ============================================================================= ## Retrieve card from break zone to hand func _resolve_retrieve( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: var owner_player = game_state.get_player(target.owner_index) if owner_player and owner_player.break_zone.has_card(target): owner_player.break_zone.remove_card(target) owner_player.hand.add_card(target) ## Remove cards from game (exile) func _resolve_remove_from_game( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: var owner_player = game_state.get_player(target.owner_index) if owner_player: owner_player.remove_card_from_game(target) ## Put cards into break zone func _resolve_put_into_break_zone( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: var owner_player = game_state.get_player(target.owner_index) if owner_player: owner_player.put_card_in_break_zone(target) ## Add cards to hand (from various zones) func _resolve_add_to_hand( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var destination_owner = effect.get("destination_owner", "OWNER") for target in targets: if target is CardInstance: var player_index = target.owner_index if destination_owner == "CONTROLLER": player_index = source.controller_index elif destination_owner == "OPPONENT": player_index = 1 - source.controller_index var player = game_state.get_player(player_index) if player: player.add_card_to_hand(target) ## Put card onto field from another zone func _resolve_put_on_field( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var enters_dull = effect.get("enters_dull", false) var enters_active = effect.get("enters_active", false) for target in targets: if target is CardInstance: var player = game_state.get_player(source.controller_index) if player: player.put_card_on_field(target, enters_dull) if enters_active: target.activate() ## Put cards into deck func _resolve_put_into_deck( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var position = effect.get("position", "BOTTOM") # TOP, BOTTOM, SHUFFLE for target in targets: if target is CardInstance: var owner_player = game_state.get_player(target.owner_index) if owner_player: owner_player.put_card_in_deck(target, position) ## Reveal top cards and add matching to hand func _resolve_reveal_and_add( effect: Dictionary, source: CardInstance, game_state ) -> void: var count = effect.get("count", 5) var filter = effect.get("filter", {}) var add_count = effect.get("add_count", 1) var player = game_state.get_player(source.controller_index) if player: player.reveal_top_and_add(count, filter, add_count) # ============================================================================= # CARD STATE EFFECTS # ============================================================================= ## Add counters to cards func _resolve_add_counter( effect: Dictionary, targets: Array, game_state ) -> void: var counter_type = effect.get("counter_type", "GENERIC") var amount = effect.get("amount", 1) for target in targets: if target is CardInstance: target.add_counters(counter_type, amount) ## Remove counters from cards func _resolve_remove_counter( effect: Dictionary, targets: Array, game_state ) -> void: var counter_type = effect.get("counter_type", "GENERIC") var amount = effect.get("amount", 1) for target in targets: if target is CardInstance: target.remove_counters(counter_type, amount) ## Transform card into another card func _resolve_transform( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var into = effect.get("into", {}) for target in targets: if target is CardInstance: target.transform(into) # ============================================================================= # COMBAT EFFECTS # ============================================================================= ## Deal damage equal to target's power to itself (mutual/reflected damage) func _resolve_mutual_damage( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance and target.is_forward(): var damage = target.get_power() var broken = target.apply_damage(damage) if broken: var player = game_state.get_player(target.controller_index) if player: player.break_card(target) ## Apply lethal effect (break regardless of power) func _resolve_lethal( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: var player = game_state.get_player(target.controller_index) if player: player.break_card(target) ## Prevent targets from attacking func _resolve_cant_attack( effect: Dictionary, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: target.add_restriction("CANT_ATTACK", duration) ## Prevent targets from blocking func _resolve_cant_block( effect: Dictionary, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: target.add_restriction("CANT_BLOCK", duration) ## Force targets to attack if able func _resolve_must_attack( effect: Dictionary, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: target.add_requirement("MUST_ATTACK", duration) ## Force targets to block if able func _resolve_must_block( effect: Dictionary, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: target.add_requirement("MUST_BLOCK", duration) ## Make attacker unblockable func _resolve_unblockable( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: target.add_temporary_keyword("UNBLOCKABLE", duration) ## Grant Brave func _resolve_brave( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: target.add_temporary_keyword("BRAVE", duration) ## Grant Haste func _resolve_haste( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: target.add_temporary_keyword("HASTE", duration) ## Grant First Strike func _resolve_first_strike( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: target.add_temporary_keyword("FIRST_STRIKE", duration) # ============================================================================= # PREVENTION & PROTECTION EFFECTS # ============================================================================= ## Protection from damage/effects func _resolve_protection( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var from = effect.get("from", "ALL") var duration = effect.get("duration", "END_OF_TURN") var affected = targets if targets.size() > 0 else [source] for target in affected: if target is CardInstance: target.add_protection(from, duration) ## Damage immunity func _resolve_damage_immunity( effect: Dictionary, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: target.add_protection("DAMAGE", duration) ## Break immunity func _resolve_break_immunity( effect: Dictionary, targets: Array, game_state ) -> void: var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: target.add_protection("BREAK", duration) ## Selection immunity (can't be chosen) func _resolve_selection_immunity( effect: Dictionary, targets: Array, game_state ) -> void: var from = effect.get("from", "OPPONENT") var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: target.add_protection("SELECTION_" + from, duration) # ============================================================================= # COST & RESOURCE EFFECTS # ============================================================================= ## Reduce cost of playing cards func _resolve_reduce_cost( effect: Dictionary, source: CardInstance, game_state ) -> void: var amount = effect.get("amount", 1) var filter = effect.get("filter", {}) var duration = effect.get("duration", "END_OF_TURN") var player = game_state.get_player(source.controller_index) if player: player.add_cost_modifier(-amount, filter, duration) ## Increase cost of playing cards func _resolve_increase_cost( effect: Dictionary, targets: Array, game_state ) -> void: var amount = effect.get("amount", 1) var filter = effect.get("filter", {}) var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: var player = game_state.get_player(target.controller_index) if player: player.add_cost_modifier(amount, filter, duration) ## Generate CP func _resolve_generate_cp( effect: Dictionary, source: CardInstance, game_state ) -> void: var element = effect.get("element", "ANY") var amount = effect.get("amount", 1) var player = game_state.get_player(source.controller_index) if player: player.add_cp(element, amount) ## Pay cost (as part of ability) func _resolve_pay_cost( effect: Dictionary, source: CardInstance, game_state ) -> void: var cost_type = effect.get("cost_type", "CP") var amount = effect.get("amount", 0) var player = game_state.get_player(source.controller_index) if player: match cost_type: "CP": player.spend_cp(amount) "DISCARD": pass # Handled by separate discard selection "DULL_SELF": source.dull() # ============================================================================= # DECK MANIPULATION EFFECTS # ============================================================================= ## Shuffle deck func _resolve_shuffle( effect: Dictionary, source: CardInstance, game_state ) -> void: var target_owner = effect.get("owner", "CONTROLLER") var player_index = source.controller_index if target_owner == "OPPONENT": player_index = 1 - player_index var player = game_state.get_player(player_index) if player: player.deck.shuffle() ## Look at top cards of deck func _resolve_look_at_top( effect: Dictionary, source: CardInstance, game_state ) -> void: var count = effect.get("count", 1) var player = game_state.get_player(source.controller_index) if player: var cards = player.deck.peek_top(count) # This emits a signal for UI to handle revealing game_state.emit_signal("cards_revealed", source.controller_index, cards) ## Reorder top cards of deck func _resolve_reorder_top( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var player = game_state.get_player(source.controller_index) if player: # targets contains the cards in the desired order player.deck.reorder_top(targets) ## Mill cards from deck to break zone func _resolve_mill( effect: Dictionary, source: CardInstance, game_state ) -> void: var count = effect.get("amount", 1) var target_owner = effect.get("owner", "OPPONENT") var player_index = source.controller_index if target_owner == "OPPONENT": player_index = 1 - player_index var player = game_state.get_player(player_index) if player: player.mill(count) # ============================================================================= # CHOICE & MODAL EFFECTS # ============================================================================= ## Handle choose effect (user selects targets) func _resolve_choose( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: # If targets already provided, process the chosen effect if targets.size() > 0: var chosen_effect = effect.get("effect", {}) if not chosen_effect.is_empty(): resolve(chosen_effect, source, targets, game_state) ## Handle modal choice (choose one of several effects) func _resolve_choose_mode( effect: Dictionary, source: CardInstance, game_state ) -> void: var modes = effect.get("modes", []) # Emit signal for UI to present choices choice_required.emit(effect, modes) # ============================================================================= # SPECIAL EFFECTS # ============================================================================= ## Copy an ability or card func _resolve_copy( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var copy_type = effect.get("copy_type", "ABILITY") for target in targets: if target is CardInstance: if copy_type == "ABILITY": source.copy_abilities_from(target) elif copy_type == "STATS": source.copy_stats_from(target) elif copy_type == "CARD": source.become_copy_of(target) ## Cancel an ability or summon on the stack func _resolve_cancel( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is Dictionary: # Stack item game_state.cancel_stack_item(target) ## Take control of target func _resolve_take_control( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var duration = effect.get("duration", "PERMANENT") for target in targets: if target is CardInstance: game_state.transfer_control(target, source.controller_index, duration) ## Swap two cards or values func _resolve_swap( effect: Dictionary, targets: Array, game_state ) -> void: var swap_type = effect.get("swap_type", "POSITION") if targets.size() >= 2: var card_a = targets[0] var card_b = targets[1] match swap_type: "POSITION": game_state.swap_positions(card_a, card_b) "POWER": var power_a = card_a.base_power var power_b = card_b.base_power card_a.set_base_power(power_b) card_b.set_base_power(power_a) "CONTROL": game_state.swap_control(card_a, card_b) ## Take an extra turn func _resolve_take_extra_turn( effect: Dictionary, source: CardInstance, game_state ) -> void: var player = game_state.get_player(source.controller_index) if player: game_state.add_extra_turn(source.controller_index) ## Win the game func _resolve_win_game( effect: Dictionary, source: CardInstance, game_state ) -> void: var condition = effect.get("condition", "UNCONDITIONAL") # Check if condition is met if condition == "UNCONDITIONAL": game_state.declare_winner(source.controller_index) # ============================================================================= # DAMAGE TO PLAYER EFFECTS # ============================================================================= ## Deal damage directly to opponent (damage zone) func _resolve_damage_to_opponent( effect: Dictionary, source: CardInstance, game_state ) -> void: var amount = effect.get("amount", 1) var opponent_index = 1 - source.controller_index var opponent = game_state.get_player(opponent_index) if opponent: opponent.take_damage(amount) ## Deal damage directly to controller func _resolve_damage_to_controller( effect: Dictionary, source: CardInstance, game_state ) -> void: var amount = effect.get("amount", 1) var player = game_state.get_player(source.controller_index) if player: player.take_damage(amount) # ============================================================================= # HEALING & RECOVERY EFFECTS # ============================================================================= ## Heal/restore damage func _resolve_heal( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var amount = effect.get("amount", 1) for target in targets: if target is CardInstance: target.heal_damage(amount) ## Remove all damage from targets func _resolve_remove_damage( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: target.remove_all_damage() # ============================================================================= # PROPERTY MODIFICATION EFFECTS # ============================================================================= ## Modify card properties (element, job, etc.) func _resolve_modify( effect: Dictionary, targets: Array, game_state ) -> void: var property = effect.get("property", "") var new_value = effect.get("new_element", effect.get("job", "")) var duration = effect.get("duration", "END_OF_TURN") for target in targets: if target is CardInstance: match property: "ELEMENT": target.set_temporary_element(new_value, duration) "JOB": target.set_temporary_job(new_value, duration) # ============================================================================= # PERMISSION & CONDITIONAL EFFECTS # ============================================================================= ## Grant permission to perform action func _resolve_permission( effect: Dictionary, source: CardInstance, game_state ) -> void: var action = effect.get("action", "") var duration = effect.get("duration", "END_OF_TURN") var player = game_state.get_player(source.controller_index) if player: match action: "CAST": # Allow casting from non-standard zone player.add_cast_permission(effect, duration) ## Resolve conditional effect wrapper (old format) func _resolve_conditional_effect( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var condition = effect.get("condition", "") # Check if condition is met using legacy check var condition_met = _check_condition_legacy(condition, effect, source, game_state) if condition_met: # Execute the wrapped effect var inner_effect = effect.get("effect", {}) if not inner_effect.is_empty(): resolve(inner_effect, source, targets, game_state) ## Resolve CONDITIONAL effect (new format with condition object) func _resolve_conditional( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var condition = effect.get("condition", {}) # Build context for condition evaluation var context = _build_condition_context(source, targets, game_state) # Use ConditionChecker if available var condition_met = false if condition_checker: condition_met = condition_checker.evaluate(condition, context) else: # Fallback to legacy check if no ConditionChecker condition_met = _check_condition_legacy(condition.get("type", ""), effect, source, game_state) if condition_met: # Execute then_effects var then_effects: Array = effect.get("then_effects", []) for sub_effect in then_effects: resolve(sub_effect, source, targets, game_state) else: # Execute else_effects (if any) var else_effects: Array = effect.get("else_effects", []) for sub_effect in else_effects: resolve(sub_effect, source, targets, game_state) ## Resolve CHAINED_EFFECT - "If you do so" patterns func _resolve_chained_effect( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var primary_effect = effect.get("primary_effect", {}) var chain_effects: Array = effect.get("chain_effects", []) if primary_effect.is_empty(): return # Execute primary effect and track if it succeeded var primary_result = _execute_with_tracking(primary_effect, source, targets, game_state) # Only execute chain effects if primary succeeded if primary_result.get("success", false): for chain_effect in chain_effects: resolve(chain_effect, source, targets, game_state) ## Resolve SCALING_EFFECT - "for each X" patterns func _resolve_scaling_effect( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var base_effect = effect.get("base_effect", {}) var scale_by = str(effect.get("scale_by", "")).to_upper() var scale_filter = effect.get("scale_filter", {}) var multiplier = effect.get("multiplier", 1) if base_effect.is_empty(): return # Determine the scale value (with optional filter) var scale_value = _get_scale_value(scale_by, source, game_state, scale_filter) # Create scaled effect var scaled_effect = base_effect.duplicate(true) # Apply scaling based on effect type if scaled_effect.has("amount"): scaled_effect["amount"] = scale_value * multiplier # Resolve the scaled effect resolve(scaled_effect, source, targets, game_state) ## Execute an effect and track success for chaining func _execute_with_tracking( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> Dictionary: var effect_type = str(effect.get("type", "")).to_upper() var result = {"success": false} # Different effects have different success criteria match effect_type: "DISCARD": # Discard succeeds if player had cards to discard var target_info = effect.get("target", {}) var player_index = source.controller_index if target_info.get("type", "") == "OPPONENT": player_index = 1 - player_index var player = game_state.get_player(player_index) if game_state else null if player and player.hand.size() > 0: resolve(effect, source, targets, game_state) result["success"] = true "DULL": # Dull succeeds if there were targets to dull if targets.size() > 0: resolve(effect, source, targets, game_state) result["success"] = true elif effect.get("target", {}).get("type") == "SELF" and source: resolve(effect, source, [source], game_state) result["success"] = true "PAY_COST": # Pay cost succeeds if cost could be paid # For now, assume success - actual cost checking would be more complex resolve(effect, source, targets, game_state) result["success"] = true "SEARCH": # Search generally succeeds (even if no cards found, the search happened) resolve(effect, source, targets, game_state) result["success"] = true "DRAW": # Draw succeeds if player has cards in deck var player = game_state.get_player(source.controller_index) if game_state else null if player and player.deck.size() > 0: resolve(effect, source, targets, game_state) result["success"] = true _: # Default: resolve and assume success resolve(effect, source, targets, game_state) result["success"] = true return result ## Get scale value based on scale_by type (with optional filter) 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() # 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) "MONSTERS_CONTROLLED", "MONSTERS": cards_to_count = _get_monsters_for_owner(owner, player_index, game_state) _: push_warning("EffectResolver: Unknown scale_by type: " + scale_by) return 0 # If no filter, just return count if 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 # ============================================================================= ## Get forwards for specified owner 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 [] "ANY", _: 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 ## Get backups for specified owner 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 [] "ANY", _: 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 ## Get all field cards for specified owner 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 ## Get hand cards for specified owner 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 [] "ANY", _: 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 ## Get break zone cards for specified owner 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 [] "ANY", _: 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 ## Get damage for specified owner 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 "ANY", _: var total = 0 for p in game_state.players: if p and "damage" in p: total += p.damage return total ## Get monster cards for specified owner func _get_monsters_for_owner(owner: String, player_index: int, game_state) -> Array: # Monsters are typically on the field like forwards var all_field = _get_field_cards_for_owner(owner, player_index, game_state) var monsters = [] for card in all_field: if card.is_monster(): monsters.append(card) return monsters # ============================================================================= # HIGHEST/LOWEST VALUE QUERIES # ============================================================================= ## Get highest power among forwards controlled by player (with optional filter) func _get_highest_power(player_index: int, game_state, filter: Dictionary = {}) -> int: var forwards = _get_forwards_for_owner("CONTROLLER", player_index, game_state) return CardFilter.get_highest_power(forwards, filter) ## Get lowest power among forwards controlled by player (with optional filter) func _get_lowest_power(player_index: int, game_state, filter: Dictionary = {}) -> int: var forwards = _get_forwards_for_owner("CONTROLLER", player_index, game_state) return CardFilter.get_lowest_power(forwards, filter) ## Get highest cost among forwards controlled by player (with optional filter) func _get_highest_cost(player_index: int, game_state, filter: Dictionary = {}) -> int: var forwards = _get_forwards_for_owner("CONTROLLER", player_index, game_state) var highest = 0 for card in forwards: if filter.is_empty() or CardFilter.matches_filter(card, filter): var cost = card.card_data.cost if card.card_data else 0 if cost > highest: highest = cost return highest ## Get lowest cost among forwards controlled by player (with optional filter) func _get_lowest_cost(player_index: int, game_state, filter: Dictionary = {}) -> int: var forwards = _get_forwards_for_owner("CONTROLLER", player_index, game_state) var lowest = -1 for card in forwards: if filter.is_empty() or CardFilter.matches_filter(card, filter): var cost = card.card_data.cost if card.card_data else 0 if lowest == -1 or cost < lowest: lowest = cost return lowest if lowest != -1 else 0 ## Get forward with highest cost controlled by player (for targeting) func _get_forward_with_highest_cost(player_index: int, game_state, filter: Dictionary = {}) -> CardInstance: var forwards = _get_forwards_for_owner("CONTROLLER", player_index, game_state) var highest_cost = -1 var highest_card: CardInstance = null for card in forwards: if filter.is_empty() or CardFilter.matches_filter(card, filter): var cost = card.card_data.cost if card.card_data else 0 if cost > highest_cost: highest_cost = cost highest_card = card return highest_card ## Get forward with lowest cost controlled by player (for targeting) func _get_forward_with_lowest_cost(player_index: int, game_state, filter: Dictionary = {}) -> CardInstance: var forwards = _get_forwards_for_owner("CONTROLLER", player_index, game_state) var lowest_cost = -1 var lowest_card: CardInstance = null for card in forwards: if filter.is_empty() or CardFilter.matches_filter(card, filter): var cost = card.card_data.cost if card.card_data else 0 if lowest_cost == -1 or cost < lowest_cost: lowest_cost = cost lowest_card = card return lowest_card ## Build context dictionary for ConditionChecker func _build_condition_context( source: CardInstance, targets: Array, game_state ) -> Dictionary: return { "source_card": source, "target_card": targets[0] if targets.size() > 0 else null, "targets": targets, "game_state": game_state, "player_id": source.controller_index if source else 0 } ## Check if a condition is met (legacy format) func _check_condition_legacy( condition: String, effect: Dictionary, source: CardInstance, game_state ) -> bool: match condition: "CONTROL_COUNT": var required = effect.get("count", 0) var player = game_state.get_player(source.controller_index) if player: var count = player.field_forwards.size() + player.field_backups.size() return count >= required "COST_COMPARISON": # Would need more context about what we're comparing return true _: # Unknown condition, assume true return true ## Trigger EX BURST on a card func _resolve_trigger_ex_burst( effect: Dictionary, targets: Array, game_state ) -> void: for target in targets: if target is CardInstance: # Signal AbilitySystem to trigger the card's EX BURST var tree = Engine.get_main_loop() if tree and tree.root and tree.root.has_node("AbilitySystem"): var ability_system = tree.root.get_node("AbilitySystem") ability_system.trigger_ex_burst_on_card(target) # ============================================================================= # DAMAGE SCALING EFFECTS # ============================================================================= ## Deal X damage for each Y you control func _resolve_damage_scaling( effect: Dictionary, source: CardInstance, targets: Array, game_state ) -> void: var damage_per = effect.get("damage_per", 1000) var count_filter = effect.get("count_filter", "") # Count matching cards var count = _count_matching_cards(count_filter, source, game_state) var total_damage = damage_per * count for target in targets: if target is CardInstance and target.is_forward(): var broken = target.apply_damage(total_damage) if broken: var player = game_state.get_player(target.controller_index) if player: player.break_card(target) ## Count cards matching a filter description func _count_matching_cards( filter_text: String, source: CardInstance, game_state ) -> int: var player = game_state.get_player(source.controller_index) if not player: return 0 var count = 0 var filter_lower = filter_text.to_lower() # Count forwards for card in player.field_forwards.get_cards(): if _card_matches_text_filter(card, filter_lower): count += 1 # Count backups for card in player.field_backups.get_cards(): if _card_matches_text_filter(card, filter_lower): count += 1 return count ## Check if card matches a text filter func _card_matches_text_filter(card: CardInstance, filter_text: String) -> bool: if not card or not card.card_data: return false # Check job if "job" in filter_text: var job_match = filter_text.find("job ") if job_match >= 0: var job_name = filter_text.substr(job_match + 4).split(" ")[0] if job_name.to_lower() in card.card_data.job.to_lower(): return true # Check card name if "card name" in filter_text: var name_start = filter_text.find("card name ") if name_start >= 0: var card_name = filter_text.substr(name_start + 10).split(" ")[0] if card_name.to_lower() in card.card_data.name.to_lower(): return true # Check category if "category" in filter_text: if card.card_data.category.to_lower() in filter_text: return true # Check element for element in card.get_elements(): if Enums.element_to_string(element).to_lower() in filter_text: return true # Check card type if card.is_forward() and "forward" in filter_text: return true if card.is_backup() and "backup" in filter_text: return true return false # ============================================================================= # OPPONENT MAY EFFECTS # ============================================================================= ## Handle "opponent may play X" effects func _resolve_opponent_may( effect: Dictionary, source: CardInstance, game_state ) -> void: var action = effect.get("action", "") # Signal that opponent has an optional action var opponent_index = 1 - source.controller_index var opponent = game_state.get_player(opponent_index) if opponent: # This would trigger UI for opponent to make a choice game_state.emit_signal("opponent_may_action", opponent_index, effect)