Skip to main content

manabrew_engine/ability/effects/
zone_exchange_effect.rs

1//! ZoneExchange effect — swap a card between two zones.
2//!
3//! Ported from Java's `ZoneExchangeEffect.java`.
4//! Exchange a card in one zone with a card in another zone.
5//! Example: swap a permanent on battlefield with a card in hand.
6
7use forge_foundation::ZoneType;
8
9use super::EffectContext;
10use crate::ids::CardId;
11
12/// Struct form of this effect so it can participate in the
13/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
14/// `ZoneExchangeEffect` class extending `SpellAbilityEffect`.
15#[manabrew_engine_macros::spell_effect(ZoneExchangeEffect)]
16fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
17    let controller = sa.activating_player;
18
19    // Determine the two zones
20    let zone1 = sa.ir.zone1.unwrap_or(ZoneType::Battlefield);
21    let zone2 = sa.ir.zone2.unwrap_or(ZoneType::Hand);
22
23    // Object 1: defined card or source
24    let object1 = if let Some(def) = sa.ir.object_text.as_deref() {
25        if def == "Self" {
26            sa.source
27        } else {
28            sa.source
29                .and_then(|sid| ctx.game.card(sid).remembered_cards.first().copied())
30        }
31    } else {
32        sa.source
33    };
34
35    let obj1 = match object1 {
36        Some(id) => id,
37        None => return,
38    };
39
40    // Verify object1 is in zone1
41    if ctx.game.card(obj1).zone != zone1 {
42        return;
43    }
44    if ctx.game.card(obj1).owner != controller {
45        return;
46    }
47
48    // Get candidates for object2 from zone2
49    let candidates: Vec<CardId> = ctx
50        .game
51        .cards
52        .iter()
53        .filter(|c| c.zone == zone2 && c.owner == controller)
54        .map(|c| c.id)
55        .collect();
56
57    if candidates.is_empty() {
58        return;
59    }
60
61    // Agent chooses object2
62    ctx.agents[controller.index()].snapshot_state(ctx.game, ctx.mana_pools);
63    let obj2 = ctx.agents[controller.index()]
64        .choose_single_card_for_zone_change(
65            ctx.game,
66            controller,
67            &candidates,
68            "Choose a card to exchange",
69            false,
70        )
71        .unwrap_or(candidates[0]);
72
73    // Verify object2 is still in zone2
74    if ctx.game.card(obj2).zone != zone2 {
75        return;
76    }
77
78    // Perform the exchange: move obj1 to zone2, obj2 to zone1
79    let old1 = ctx.game.card(obj1).zone;
80    let old2 = ctx.game.card(obj2).zone;
81
82    ctx.move_card(obj1, zone2, controller);
83    ctx.move_card(obj2, zone1, controller);
84
85    super::emit_zone_trigger(ctx.trigger_handler, obj1, old1, zone2);
86    super::emit_zone_trigger(ctx.trigger_handler, obj2, old2, zone1);
87}