Skip to main content

manabrew_engine/ability/effects/
control_exchange_effect.rs

1//! ControlExchange effect — swap control of two permanents.
2//!
3//! Ported 1:1 from Java's `ControlExchangeEffect.java`.
4//! Exchange control of two target/defined permanents.
5
6use forge_foundation::ZoneType;
7
8use super::EffectContext;
9use crate::ability::ability_ir::DefinedRef;
10use crate::ids::CardId;
11use crate::parsing::keys;
12
13/// Struct form of this effect so it can participate in the
14/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
15/// `ControlExchangeEffect` class extending `SpellAbilityEffect`.
16#[manabrew_engine_macros::spell_effect(ControlExchangeEffect)]
17fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
18    let mut object1: Option<CardId> = None;
19    let mut object2: Option<CardId> = None;
20
21    // Get targets
22    if sa.uses_targeting() {
23        object1 = sa.target_chosen.target_card;
24    }
25
26    // Get defined cards
27    if let Some(defined) = sa.defined() {
28        if matches!(sa.defined_ref(), Some(DefinedRef::SelfCard)) {
29            object2 = sa.source;
30        } else if let Some(uid_str) = defined.strip_prefix("CardUID_") {
31            object2 = uid_str.parse::<u32>().ok().map(crate::ids::CardId);
32        }
33    } else if object1.is_some() {
34        // Second target if two targets
35        // For simplicity, use target_player's permanent — full impl would handle multi-target
36        object2 = sa.source;
37    }
38
39    let (Some(card1), Some(card2)) = (object1, object2) else {
40        return;
41    };
42
43    // Verify both on battlefield and not phased out
44    let c1 = ctx.game.card(card1);
45    let c2 = ctx.game.card(card2);
46    if c1.zone != ZoneType::Battlefield || c2.zone != ZoneType::Battlefield {
47        return;
48    }
49    if c1.phased_out || c2.phased_out {
50        return;
51    }
52
53    // Optional$
54    if sa.is_optional() {
55        let controller = sa.activating_player;
56        let name1 = c1.card_name.clone();
57        let name2 = c2.card_name.clone();
58        ctx.agents[controller.index()].snapshot_state(ctx.game, ctx.mana_pools);
59        let confirm = ctx.agents[controller.index()].confirm_action(
60            controller,
61            Some("ControlExchange"),
62            &format!("Exchange control of {name1} and {name2}?"),
63            &[],
64            None,
65            None,
66        );
67        if !confirm {
68            return;
69        }
70    }
71
72    // Swap controllers
73    let player1 = ctx.game.card(card1).controller;
74    let player2 = ctx.game.card(card2).controller;
75
76    ctx.game.card_mut(card1).set_controller(player2);
77    ctx.game.card_mut(card2).set_controller(player1);
78
79    // RememberExchanged$
80    if sa.param_is_true(keys::REMEMBER_EXCHANGED) {
81        if let Some(sid) = sa.source {
82            ctx.game.card_mut(sid).add_remembered_card(card1);
83            ctx.game.card_mut(sid).add_remembered_card(card2);
84        }
85    }
86}