Skip to main content

manabrew_engine/ability/effects/
attach_effect.rs

1use forge_foundation::ZoneType;
2
3use super::EffectContext;
4use crate::agent::types::GameEntity;
5use crate::player::player_controller::PlayerController;
6use crate::replacement::replacement_handler::{apply_replacements, ReplacementEvent};
7use crate::replacement::ReplacementResult;
8
9/// SP$ Attach / AB$ Attach — attach source Equipment/Aura to target creature.
10///
11/// Mirrors Java's `AttachEffect.resolve()`.
12/// Struct form of this effect so it can participate in the
13/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
14/// `AttachEffect` class extending `SpellAbilityEffect`.
15#[manabrew_engine_macros::spell_effect(AttachEffect)]
16fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
17    // Source is the card being attached (the Equipment or Aura)
18    let aura_id = match sa.source {
19        Some(s) => s,
20        None => return,
21    };
22    let chooser = sa
23        .chooser()
24        .and_then(|defined| {
25            crate::ability::ability_utils::resolve_defined_players_with_sa(
26                defined,
27                sa,
28                sa.activating_player,
29                ctx.game,
30            )
31            .into_iter()
32            .next()
33        })
34        .unwrap_or(sa.activating_player);
35
36    let mut candidates: Vec<GameEntity> = Vec::new();
37    if let Some(target_card) = sa.target_chosen.target_card {
38        candidates.push(GameEntity::Card(target_card));
39    }
40    if let Some(target_player) = sa.target_chosen.target_player {
41        candidates.push(GameEntity::Player(target_player));
42    }
43    if candidates.is_empty() {
44        if let Some(defined) = sa.defined() {
45            candidates.extend(
46                crate::ability::ability_utils::get_defined_cards(
47                    ctx.game,
48                    Some(aura_id),
49                    defined,
50                    Some(sa.activating_player),
51                )
52                .into_iter()
53                .map(GameEntity::Card),
54            );
55            candidates.extend(
56                crate::ability::ability_utils::resolve_defined_players_with_sa(
57                    defined,
58                    sa,
59                    sa.activating_player,
60                    ctx.game,
61                )
62                .into_iter()
63                .map(GameEntity::Player),
64            );
65        }
66    }
67    if candidates.is_empty() {
68        return;
69    }
70    let chosen = {
71        let agent = ctx.agents[chooser.index()].as_mut();
72        let mut controller = PlayerController::new(ctx.game, chooser, agent);
73        controller.snapshot_state(ctx.mana_pools);
74        controller.choose_single_entity_for_effect(&candidates)
75    };
76    let chosen = match chosen {
77        Some(chosen) => chosen,
78        None => return,
79    };
80    let target = match chosen {
81        GameEntity::Card(c) => c,
82        GameEntity::Player(_) => return,
83    };
84
85    // Both must be on the battlefield
86    if ctx.game.card(aura_id).zone != ZoneType::Battlefield
87        || ctx.game.card(target).zone != ZoneType::Battlefield
88    {
89        return;
90    }
91    if crate::staticability::static_ability_cant_attach::cant_attach(
92        &ctx.game.cards,
93        ctx.game.card(aura_id),
94        ctx.game.card(target),
95        false,
96    ) {
97        return;
98    }
99
100    // Run Attached replacement effects before attaching.
101    let mut event = ReplacementEvent::Attached {
102        card: aura_id,
103        target,
104    };
105    let result = apply_replacements(ctx.game, &mut event);
106    if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
107        return;
108    }
109
110    ctx.game.attach_to(aura_id, target);
111
112    // Fire Attached trigger
113    ctx.trigger_handler.run_trigger(
114        crate::trigger::TriggerType::Attached,
115        crate::event::RunParams {
116            card: Some(aura_id),
117            ..Default::default()
118        },
119        false,
120    );
121}