Skip to main content

manabrew_engine/ability/effects/
meld_effect.rs

1//! Meld effect — exile two named cards, return melded as one creature.
2//!
3//! Ported 1:1 from Java's `MeldEffect.java`.
4//! Meld: Exile two specific named permanents you control and own,
5//! then return the primary card to the battlefield transformed (melded)
6//! with the secondary card linked to it.
7
8use forge_foundation::ZoneType;
9
10use super::{emit_zone_trigger, EffectContext};
11use crate::ids::CardId;
12use crate::parsing::keys;
13
14/// Struct form of this effect so it can participate in the
15/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
16/// `MeldEffect` class extending `SpellAbilityEffect`.
17#[manabrew_engine_macros::spell_effect(MeldEffect)]
18fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
19    let Some(host_card_id) = sa.source else {
20        return;
21    };
22    let controller = sa.activating_player;
23
24    let prim_name = sa.ir.primary_text.as_deref().unwrap_or_default();
25    let sec_name = sa.ir.secondary_text.as_deref().unwrap_or_default();
26    let sec_type = sa.ir.secondary_type_text.as_deref().unwrap_or("Creature");
27
28    // Find a permanent you control and own named "Secondary" matching SecondaryType
29    let candidates: Vec<CardId> = ctx
30        .game
31        .cards
32        .iter()
33        .filter(|c| {
34            c.zone == ZoneType::Battlefield
35                && c.controller == controller
36                && c.owner == controller
37                && c.card_name == sec_name
38                && super::matches_change_type(c, sec_type, &[])
39        })
40        .map(|c| c.id)
41        .collect();
42
43    if candidates.is_empty() {
44        return;
45    }
46
47    // Choose which secondary to meld with (if multiple copies)
48    let secondary_id = if candidates.len() == 1 {
49        candidates[0]
50    } else {
51        ctx.agents[controller.index()].snapshot_state(ctx.game, ctx.mana_pools);
52        ctx.agents[controller.index()]
53            .choose_single_card_for_zone_change(
54                ctx.game,
55                controller,
56                &candidates,
57                "Choose a card to meld",
58                false,
59            )
60            .unwrap_or(candidates[0])
61    };
62
63    // Exile both cards
64    let cards_to_exile = vec![host_card_id, secondary_id];
65    let mut exiled = Vec::new();
66
67    for &card_id in &cards_to_exile {
68        // canExiledBy check
69        if ctx
70            .game
71            .card(card_id)
72            .keywords
73            .contains_string_ignore_case("CantBeExiled")
74        {
75            continue;
76        }
77        let old_zone = ctx.game.card(card_id).zone;
78        ctx.move_card(card_id, ZoneType::Exile, controller);
79        emit_zone_trigger(ctx.trigger_handler, card_id, old_zone, ZoneType::Exile);
80        exiled.push(card_id);
81    }
82
83    // Both must have been exiled
84    if exiled.len() < 2 {
85        return;
86    }
87
88    let primary = exiled[0];
89    let secondary = exiled[1];
90
91    // Verify names are still correct in exile
92    if ctx.game.card(primary).card_name != prim_name
93        || ctx.game.card(secondary).card_name != sec_name
94    {
95        return;
96    }
97
98    // Verify neither is a token and both are still in exile
99    for &c in &[primary, secondary] {
100        if ctx.game.card(c).is_token || ctx.game.card(c).zone != ZoneType::Exile {
101            return;
102        }
103    }
104
105    // Transform primary to meld state
106    ctx.game.card_mut(primary).set_transformed(true);
107    if let Some(other_name) = ctx
108        .game
109        .card(primary)
110        .other_part
111        .as_ref()
112        .map(|o| o.name.clone())
113    {
114        ctx.game.card_mut(primary).set_card_name(other_name);
115    }
116
117    // Link secondary to primary via melded_with
118    ctx.game.card_mut(primary).melded_with.push(secondary);
119
120    // Tapped$
121    if sa.is_tapped() {
122        ctx.game.card_mut(primary).set_tapped(true);
123    }
124
125    // Move primary to battlefield (melded form)
126    let old_zone = ctx.game.card(primary).zone;
127    ctx.game
128        .move_card(primary, ZoneType::Battlefield, controller);
129    ctx.trigger_handler
130        .register_active_trigger(ctx.game, primary);
131    emit_zone_trigger(
132        ctx.trigger_handler,
133        primary,
134        old_zone,
135        ZoneType::Battlefield,
136    );
137
138    let _ = super::add_to_combat(ctx, sa, primary, keys::ATTACKING);
139}