Skip to main content

manabrew_engine/ability/effects/
change_combatants_effect.rs

1//! ChangeCombatants effect — modify combat participants.
2//!
3//! Ported from Java's `ChangeCombatantsEffect.java`.
4//! Add or remove creatures from combat.
5
6use forge_foundation::ZoneType;
7
8use super::EffectContext;
9use crate::ability::api_type::ApiType;
10use crate::parsing::keys;
11
12/// Struct form of this effect so it can participate in the
13/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
14/// `ChangeCombatantsEffect` class extending `SpellAbilityEffect`.
15#[manabrew_engine_macros::spell_effect(ChangeCombatantsEffect)]
16fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
17    let targets: Vec<crate::ids::CardId> = if sa.uses_targeting() {
18        sa.target_chosen.target_card.into_iter().collect()
19    } else {
20        sa.source.into_iter().collect()
21    };
22
23    for card_id in targets {
24        if ctx.game.card(card_id).zone != ZoneType::Battlefield {
25            continue;
26        }
27        if sa.ir.optional_present {
28            let card_name = ctx.game.card(card_id).card_name.clone();
29            let accepted = ctx.agents[sa.activating_player.index()].confirm_action(
30                sa.activating_player,
31                Some("ChangeCombatants"),
32                &format!("Reselect the defender of {card_name}?"),
33                &[],
34                Some(card_id),
35                Some(ApiType::ChangeCombatants),
36            );
37            if !accepted {
38                continue;
39            }
40        }
41
42        let original_defender = ctx
43            .combat
44            .as_deref()
45            .and_then(|combat| {
46                combat
47                    .attackers
48                    .iter()
49                    .find(|(attacker, _)| *attacker == card_id)
50            })
51            .map(|(_, defender)| *defender);
52
53        if sa.param_is_true(keys::REMOVE_FROM_COMBAT) {
54            if let Some(combat) = ctx.combat.as_deref_mut() {
55                combat.remove_from_combat(card_id, ctx.game);
56            } else {
57                ctx.game.card_mut(card_id).clear_attacking_player();
58            }
59        }
60        if super::add_to_combat(ctx, sa, card_id, keys::ATTACKING) {
61            let new_defender_player = ctx
62                .combat
63                .as_deref()
64                .and_then(|combat| {
65                    combat
66                        .attackers
67                        .iter()
68                        .find(|(attacker, _)| *attacker == card_id)
69                })
70                .map(|(_, defender)| defender.controlling_player(ctx.game));
71
72            for entry in ctx.game.stack.iter_mut() {
73                if !entry.spell_ability.is_trigger {
74                    continue;
75                }
76                if entry.spell_ability.source != Some(card_id) {
77                    continue;
78                }
79                if !entry
80                    .spell_ability
81                    .trigger_objects
82                    .contains_key(&crate::ability::AbilityKey::Attacker)
83                {
84                    continue;
85                }
86
87                if let Some(original) = original_defender {
88                    match original {
89                        crate::combat::DefenderId::Player(pid) => {
90                            entry.set_triggering_object(
91                                crate::ability::AbilityKey::OriginalDefender,
92                                &pid.0.to_string(),
93                            );
94                        }
95                        crate::combat::DefenderId::Permanent(cid) => {
96                            entry.set_triggering_object(
97                                crate::ability::AbilityKey::OriginalDefender,
98                                &cid.0.to_string(),
99                            );
100                        }
101                    }
102                }
103                if let Some(defending_player) = new_defender_player {
104                    entry.update_triggering_object(
105                        "DefendingPlayer",
106                        &defending_player.0.to_string(),
107                    );
108                }
109            }
110        }
111    }
112}