manabrew-engine 0.6.0

Magic: The Gathering rules engine — a Rust port of Forge
Documentation
//! ControlExchange effect — swap control of two permanents.
//!
//! Ported 1:1 from Java's `ControlExchangeEffect.java`.
//! Exchange control of two target/defined permanents.

use forge_foundation::ZoneType;

use super::EffectContext;
use crate::ability::ability_ir::DefinedRef;
use crate::ids::CardId;
use crate::parsing::keys;

/// Struct form of this effect so it can participate in the
/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
/// `ControlExchangeEffect` class extending `SpellAbilityEffect`.
#[manabrew_engine_macros::spell_effect(ControlExchangeEffect)]
fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
    let mut object1: Option<CardId> = None;
    let mut object2: Option<CardId> = None;

    // Get targets
    if sa.uses_targeting() {
        object1 = sa.target_chosen.target_card;
    }

    // Get defined cards
    if let Some(defined) = sa.defined() {
        if matches!(sa.defined_ref(), Some(DefinedRef::SelfCard)) {
            object2 = sa.source;
        } else if let Some(uid_str) = defined.strip_prefix("CardUID_") {
            object2 = uid_str.parse::<u32>().ok().map(crate::ids::CardId);
        }
    } else if object1.is_some() {
        // Second target if two targets
        // For simplicity, use target_player's permanent — full impl would handle multi-target
        object2 = sa.source;
    }

    let (Some(card1), Some(card2)) = (object1, object2) else {
        return;
    };

    // Verify both on battlefield and not phased out
    let c1 = ctx.game.card(card1);
    let c2 = ctx.game.card(card2);
    if c1.zone != ZoneType::Battlefield || c2.zone != ZoneType::Battlefield {
        return;
    }
    if c1.phased_out || c2.phased_out {
        return;
    }

    // Optional$
    if sa.is_optional() {
        let controller = sa.activating_player;
        let name1 = c1.card_name.clone();
        let name2 = c2.card_name.clone();
        ctx.agents[controller.index()].snapshot_state(ctx.game, ctx.mana_pools);
        let confirm = ctx.agents[controller.index()].confirm_action(
            controller,
            Some("ControlExchange"),
            &format!("Exchange control of {} and {}?", name1, name2),
            &[],
            None,
            None,
        );
        if !confirm {
            return;
        }
    }

    // Swap controllers
    let player1 = ctx.game.card(card1).controller;
    let player2 = ctx.game.card(card2).controller;

    ctx.game.card_mut(card1).set_controller(player2);
    ctx.game.card_mut(card2).set_controller(player1);

    // RememberExchanged$
    if sa.param_is_true(keys::REMEMBER_EXCHANGED) {
        if let Some(sid) = sa.source {
            ctx.game.card_mut(sid).add_remembered_card(card1);
            ctx.game.card_mut(sid).add_remembered_card(card2);
        }
    }
}