manabrew_engine/ability/effects/
destroy_all_effect.rs1use forge_foundation::ZoneType;
2
3use super::{emit_zone_trigger_with_lki_counters, matches_valid_cards_for_sa, EffectContext};
4use crate::event::RunParams;
5use crate::ids::CardId;
6use crate::replacement::replacement_handler::{apply_replacements, ReplacementEvent};
7use crate::replacement::ReplacementResult;
8use crate::trigger::TriggerType;
9
10#[manabrew_engine_macros::spell_effect(DestroyAllEffect)]
27fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
28 let valid_cards = sa.ir.valid_cards_selector.as_ref();
29
30 let player_ids = ctx.game.player_order.clone();
32 let mut to_destroy: Vec<CardId> = Vec::new();
33 for &pid in &player_ids {
34 let zone_cards = ctx.game.cards_in_zone(ZoneType::Battlefield, pid).to_vec();
35 for cid in zone_cards {
36 if matches_valid_cards_for_sa(ctx.game, sa, ctx.game.card(cid), valid_cards, "Creature")
37 {
38 to_destroy.push(cid);
39 }
40 }
41 }
42
43 for card_id in to_destroy {
45 if ctx.game.card(card_id).zone != ZoneType::Battlefield {
46 continue; }
48 if ctx.game.card(card_id).has_keyword("Indestructible") {
50 continue;
51 }
52 let mut destroy_event = ReplacementEvent::Destroy { target: card_id };
54 let result = apply_replacements(ctx.game, &mut destroy_event);
55 if result == ReplacementResult::Replaced {
56 continue;
57 }
58 let owner = ctx.game.card(card_id).owner;
59 let lki_p1p1 = *ctx
61 .game
62 .card(card_id)
63 .counters
64 .get(&crate::card::CounterType::P1P1)
65 .unwrap_or(&0);
66 {
68 let lki_counters = ctx.game.card(card_id).counters.clone();
69 let lki_power = ctx.game.card(card_id).power();
70 let lki_toughness = ctx.game.card(card_id).toughness();
71 ctx.game.card_mut(card_id).lki_counters = Some(lki_counters);
72 ctx.game
73 .card_mut(card_id)
74 .set_lki_power_toughness(Some(lki_power), Some(lki_toughness));
75 }
76 ctx.move_card(card_id, ZoneType::Graveyard, owner);
77 ctx.trigger_handler.run_trigger(
78 TriggerType::Destroyed,
79 RunParams {
80 card: Some(card_id),
81 causer: sa.source,
82 cause_card: sa.source,
83 cause_player: Some(sa.activating_player),
84 ..Default::default()
85 },
86 false,
87 );
88 emit_zone_trigger_with_lki_counters(
89 ctx.trigger_handler,
90 card_id,
91 ZoneType::Battlefield,
92 ZoneType::Graveyard,
93 lki_p1p1,
94 ctx.game
95 .card(card_id)
96 .lki_power
97 .unwrap_or_else(|| ctx.game.card(card_id).power()),
98 ctx.game
99 .card(card_id)
100 .lki_toughness
101 .unwrap_or_else(|| ctx.game.card(card_id).toughness()),
102 );
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::ability::spell_ability_effect::SpellAbilityEffect;
109 use forge_foundation::{CardTypeLine, ColorSet, ManaCost, ZoneType};
110 use std::collections::HashMap;
111
112 use crate::ability::effects::EffectContext;
113 use crate::agent::PassAgent;
114 use crate::card::Card;
115 use crate::game::GameState;
116 use crate::ids::{CardId, PlayerId};
117 use crate::mana::ManaPool;
118 use crate::spellability::SpellAbility;
119 use crate::trigger::handler::TriggerHandler;
120
121 fn make_creature(game: &mut GameState, owner: PlayerId, keywords: Vec<String>) -> CardId {
122 let c = Card::new(
123 CardId(0),
124 "Bear".into(),
125 owner,
126 CardTypeLine::parse("Creature - Bear"),
127 ManaCost::parse("1 G"),
128 ColorSet::GREEN,
129 Some(2),
130 Some(2),
131 keywords,
132 vec![],
133 );
134 game.create_card(c)
135 }
136
137 fn make_ctx<'a>(
138 game: &'a mut GameState,
139 agents: &'a mut Vec<Box<dyn crate::agent::PlayerAgent>>,
140 trigger_handler: &'a mut TriggerHandler,
141 mana_pools: &'a mut Vec<ManaPool>,
142 token_templates: &'a HashMap<String, Card>,
143 token_art_variants: &'a HashMap<(String, String), usize>,
144 token_fallback: &'a HashMap<String, String>,
145 edition_dates: &'a HashMap<String, String>,
146 rng: &'a mut dyn crate::game_rng::GameRng,
147 ) -> EffectContext<'a> {
148 EffectContext {
149 game,
150 combat: None,
151 agents,
152 trigger_handler,
153 token_templates,
154 token_art_variants,
155 token_fallback,
156 edition_dates,
157 mana_pools,
158 parent_target_card: None,
159 rng,
160 }
161 }
162
163 #[test]
164 fn destroy_all_wipes_creatures() {
165 let mut game = GameState::new(&["Alice", "Bob"], 20);
166 let p0 = PlayerId(0);
167 let p1 = PlayerId(1);
168 let c1 = make_creature(&mut game, p0, vec![]);
169 let c2 = make_creature(&mut game, p1, vec![]);
170 game.move_card(c1, ZoneType::Battlefield, p0);
171 game.move_card(c2, ZoneType::Battlefield, p1);
172
173 let sa = SpellAbility::new_simple(
174 None,
175 p0,
176 "A:SP$ DestroyAll | ValidCards$ Creature | NoRegen$ True",
177 );
178 let mut th = TriggerHandler::new();
179 let mut agents: Vec<Box<dyn crate::agent::PlayerAgent>> =
180 vec![Box::new(PassAgent), Box::new(PassAgent)];
181 let mut mp = vec![ManaPool::default(), ManaPool::default()];
182 let templates = HashMap::new();
183 let templates_variants: HashMap<(String, String), usize> = HashMap::new();
184 let token_fallback: HashMap<String, String> = HashMap::new();
185 let edition_dates: HashMap<String, String> = HashMap::new();
186 let mut rng_adapter = crate::game_rng::ThreadRngAdapter;
187 let mut ctx = make_ctx(
188 &mut game,
189 &mut agents,
190 &mut th,
191 &mut mp,
192 &templates,
193 &templates_variants,
194 &token_fallback,
195 &edition_dates,
196 &mut rng_adapter,
197 );
198 super::DestroyAllEffect::resolve(&mut ctx, &sa);
199
200 assert_eq!(ctx.game.cards_in_zone(ZoneType::Battlefield, p0).len(), 0);
201 assert_eq!(ctx.game.cards_in_zone(ZoneType::Battlefield, p1).len(), 0);
202 assert_eq!(ctx.game.cards_in_zone(ZoneType::Graveyard, p0).len(), 1);
203 assert_eq!(ctx.game.cards_in_zone(ZoneType::Graveyard, p1).len(), 1);
204 }
205
206 #[test]
207 fn destroy_all_indestructible_survives() {
208 let mut game = GameState::new(&["Alice", "Bob"], 20);
209 let p0 = PlayerId(0);
210 let mortal = make_creature(&mut game, p0, vec![]);
211 let immortal = make_creature(&mut game, p0, vec!["Indestructible".to_string()]);
212 game.move_card(mortal, ZoneType::Battlefield, p0);
213 game.move_card(immortal, ZoneType::Battlefield, p0);
214
215 let sa = SpellAbility::new_simple(
216 None,
217 p0,
218 "A:SP$ DestroyAll | ValidCards$ Creature | NoRegen$ True",
219 );
220 let mut th = TriggerHandler::new();
221 let mut agents: Vec<Box<dyn crate::agent::PlayerAgent>> =
222 vec![Box::new(PassAgent), Box::new(PassAgent)];
223 let mut mp = vec![ManaPool::default(), ManaPool::default()];
224 let templates = HashMap::new();
225 let templates_variants: HashMap<(String, String), usize> = HashMap::new();
226 let token_fallback: HashMap<String, String> = HashMap::new();
227 let edition_dates: HashMap<String, String> = HashMap::new();
228 let mut rng_adapter = crate::game_rng::ThreadRngAdapter;
229 let mut ctx = make_ctx(
230 &mut game,
231 &mut agents,
232 &mut th,
233 &mut mp,
234 &templates,
235 &templates_variants,
236 &token_fallback,
237 &edition_dates,
238 &mut rng_adapter,
239 );
240 super::DestroyAllEffect::resolve(&mut ctx, &sa);
241
242 assert_eq!(ctx.game.cards_in_zone(ZoneType::Battlefield, p0).len(), 1);
244 assert_eq!(ctx.game.cards_in_zone(ZoneType::Graveyard, p0).len(), 1);
245 assert!(ctx.game.card(immortal).has_keyword("Indestructible"));
246 }
247}