manabrew_engine/ability/effects/
counter_effect.rs1use forge_foundation::ZoneType;
2
3use super::{emit_zone_trigger, EffectContext};
4use crate::event::RunParams;
5use crate::replacement::replacement_handler::{apply_replacements, ReplacementEvent};
6use crate::replacement::ReplacementResult;
7use crate::spellability::SpellAbility;
8use crate::trigger::TriggerType;
9
10pub fn build_spell_ability(sa: &mut crate::spellability::SpellAbility) {
16 if sa.uses_targeting() {
17 if let Some(ref mut tr) = sa.target_restrictions {
18 tr.tgt_zone = vec![ZoneType::Stack];
19 }
20 }
21}
22
23pub fn check_for_condition_would_destroy(
30 game: &crate::game::GameState,
31 sa: &SpellAbility,
32 target_stack_id: u32,
33) -> bool {
34 let entry = match game.stack.find_by_id(target_stack_id) {
35 Some(e) => e,
36 None => return false,
37 };
38
39 let targeted_sa = &entry.spell_ability;
40 let controller = sa.activating_player;
41
42 if let Some(api) = targeted_sa.api {
44 match api {
45 crate::ability::api_type::ApiType::Destroy
46 | crate::ability::api_type::ApiType::DestroyAll => {
47 if let Some(target_card) = targeted_sa.target_chosen.target_card {
49 return game.card(target_card).controller == controller;
50 }
51 if let Some(valid) = targeted_sa.ir.valid_cards_selector.as_ref() {
53 let our_permanents =
54 game.cards_in_zone(forge_foundation::ZoneType::Battlefield, controller);
55 return our_permanents.iter().any(|&cid| {
56 crate::ability::ability_utils::matches_valid_cards_selector_opt(
57 Some(valid),
58 game.card(cid),
59 targeted_sa.activating_player,
60 )
61 });
62 }
63 return true; }
65 _ => {}
66 }
67 }
68
69 false
70}
71
72#[manabrew_engine_macros::spell_effect(CounterEffect)]
82fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
83 let entry_id = match counter_target_stack_entry_id(ctx, sa) {
84 Some(id) => id,
85 None => return, };
87
88 let dest_zone = sa.ir.destination_zone.unwrap_or(ZoneType::Graveyard);
90
91 if let Some(entry) = ctx.game.stack.find_by_id(entry_id) {
94 if let Some(source_card) = entry.spell_ability.source {
95 if sa.ir.remember_for_counter {
96 if let Some(source_id) = sa.source {
97 ctx.game
98 .card_mut(source_id)
99 .add_remembered_card(source_card);
100 }
101 }
102 if sa.ir.remember_countered_cmc {
103 let cmc = ctx.game.card(source_card).mana_cost.cmc();
104 if let Some(source_id) = sa.source {
105 ctx.game.card_mut(source_id).add_remembered_cmc(cmc);
106 }
107 }
108 let mut event = ReplacementEvent::Counter { card: source_card };
109 let result = apply_replacements(ctx.game, &mut event);
110 if result == ReplacementResult::Replaced {
111 return;
112 }
113 }
114 }
115
116 if let Some(entry) = ctx.game.stack.remove_by_id(entry_id) {
118 let countered_sa = &entry.spell_ability;
119 if let Some(source_card) = countered_sa.source {
120 let owner = ctx.game.card(source_card).owner;
123
124 if sa.ir.remember_countered {
126 ctx.game
127 .card_mut(sa.source.unwrap())
128 .add_remembered_card(source_card);
129 }
130
131 if !countered_sa.is_activated && !countered_sa.is_trigger {
132 ctx.move_card(source_card, dest_zone, owner);
133 emit_zone_trigger(ctx.trigger_handler, source_card, ZoneType::Stack, dest_zone);
134 }
135
136 ctx.trigger_handler.run_trigger(
138 TriggerType::Countered,
139 RunParams {
140 card: Some(source_card),
141 spell_ability: Some(countered_sa.clone()),
142 cause: Some(sa.clone()),
143 ..Default::default()
144 },
145 false,
146 );
147 }
148 }
149}
150
151fn counter_target_stack_entry_id(ctx: &EffectContext, sa: &SpellAbility) -> Option<u32> {
152 if let Some(id) = sa.target_chosen.target_stack_entry {
153 return Some(id);
154 }
155
156 let defined = sa.defined()?;
157 let defined_spells =
158 crate::ability::ability_utils::get_defined_spell_abilities(defined, sa, ctx.game);
159 let mut candidate_sources: Vec<crate::ids::CardId> = defined_spells
160 .iter()
161 .filter_map(|defined_sa| defined_sa.source)
162 .collect();
163 if defined == "TriggeredSpellAbility" || defined == "TriggeredSourceSA" {
164 if let Some(source) = sa
165 .trigger_objects
166 .get(&crate::ability::AbilityKey::Source)
167 .and_then(|value| value.parse::<u32>().ok())
168 .map(crate::ids::CardId)
169 {
170 if !candidate_sources.contains(&source) {
171 candidate_sources.push(source);
172 }
173 }
174 }
175
176 for defined_sa in defined_spells {
177 if let Some(source) = defined_sa.source {
178 let stack_entries: Vec<_> = ctx.game.stack.iter().collect();
179 for entry in stack_entries.iter().rev() {
180 if entry.spell_ability.source == Some(source)
181 && entry.spell_ability.ability_text == defined_sa.ability_text
182 {
183 return Some(entry.id);
184 }
185 }
186 for entry in stack_entries.iter().rev() {
187 if entry.spell_ability.source == Some(source) {
188 return Some(entry.id);
189 }
190 }
191 }
192 }
193 for source in candidate_sources {
194 let stack_entries: Vec<_> = ctx.game.stack.iter().collect();
195 for entry in stack_entries.iter().rev() {
196 if entry.spell_ability.source == Some(source) {
197 return Some(entry.id);
198 }
199 }
200 }
201
202 None
203}