manabrew_engine/ability/effects/
airbend_effect.rs1use forge_foundation::{CardTypeLine, ColorSet, ManaCost, ZoneType};
7
8use super::EffectContext;
9use crate::ability::ability_ir::DefinedRef;
10use crate::card::Card;
11use crate::ids::CardId;
12use crate::staticability::parse_static_ability;
13
14#[manabrew_engine_macros::spell_effect(AirbendEffect)]
18fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
19 let parent_target = ctx.parent_target_card.or(sa.target_chosen.target_card);
20 let primary_def = sa.ir.defined.as_ref().and_then(|d| d.refs.first());
21 let targets: Vec<CardId> = match primary_def {
22 None => {
23 if let Some(target) = sa.target_chosen.target_card {
24 vec![target]
25 } else if let Some(source) = sa.source {
26 vec![source]
27 } else {
28 return;
29 }
30 }
31 Some(DefinedRef::SelfCard) => sa.source.into_iter().collect(),
32 Some(DefinedRef::Remembered) => sa
33 .source
34 .map(|sid| ctx.game.card(sid).remembered_cards.clone())
35 .unwrap_or_default(),
36 Some(DefinedRef::Targeted) => sa.target_chosen.target_card.into_iter().collect(),
37 Some(DefinedRef::Unsupported(raw)) if raw.starts_with("Valid ") => {
38 let filter = raw.strip_prefix("Valid ").unwrap_or("").trim();
43 let stripped: String = filter
44 .split('.')
45 .map(|seg| {
46 seg.split('+')
47 .filter(|term| !term.eq_ignore_ascii_case("NotDefinedTargeted"))
48 .collect::<Vec<_>>()
49 .join("+")
50 })
51 .filter(|seg| !seg.is_empty())
52 .collect::<Vec<_>>()
53 .join(".");
54 let mut out = Vec::new();
55 for &pid in &ctx.game.player_order {
56 for &cid in ctx.game.cards_in_zone(ZoneType::Battlefield, pid) {
57 if filter.contains("NotDefinedTargeted")
58 && parent_target.is_some_and(|t| t == cid)
59 {
60 continue;
61 }
62 if super::matches_change_type(ctx.game.card(cid), &stripped, &[]) {
63 out.push(cid);
64 }
65 }
66 }
67 out
68 }
69 _ => sa.source.into_iter().collect(),
70 };
71
72 let host_id = sa.source;
73 let host_image = host_id.map(|id| ctx.game.card(id).card_name.clone());
74
75 let mut exiled_so_far: Vec<CardId> = Vec::new();
81
82 for card_id in targets {
83 if ctx.game.card(card_id).zone == ZoneType::None {
84 continue;
85 }
86
87 let old_zone = ctx.game.card(card_id).zone;
89 let owner = ctx.game.card(card_id).owner;
90 ctx.move_card(card_id, ZoneType::Exile, owner);
91 super::emit_zone_trigger(ctx.trigger_handler, card_id, old_zone, ZoneType::Exile);
92
93 if ctx.game.card(card_id).is_token {
94 continue;
95 }
96 if ctx.game.card(card_id).zone != ZoneType::Exile {
97 continue;
98 }
99
100 exiled_so_far.push(card_id);
103
104 let card_name_for_effect = ctx.game.card(card_id).card_name.clone();
108 let host_name = host_image.clone().unwrap_or_else(|| "Airbend".to_string());
109 let effect_name = format!("{} ({})", host_name, card_name_for_effect);
110
111 let mut effect = Card::new(
112 CardId(0),
113 effect_name,
114 owner,
115 CardTypeLine::parse("Effect"),
116 ManaCost::parse("0"),
117 ColorSet::COLORLESS,
118 None,
119 None,
120 vec![],
121 vec![],
122 );
123 effect.set_controller(owner);
124 effect.set_effect_source(host_id);
125 let static_text = "S$ Mode$ Continuous | MayPlay$ True | MayPlayAltManaCost$ 2 \
126 | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand \
127 | AffectedZone$ Exile | Description$ You may cast the card.";
128 if let Some(static_ab) = parse_static_ability(static_text) {
129 effect.set_static_abilities(vec![static_ab]);
130 }
131 effect.add_remembered_cards(exiled_so_far.iter().copied());
132 effect.set_forget_on_moved_origin(Some(ZoneType::Exile));
133
134 let effect_id = ctx.game.create_card(effect);
135 ctx.move_card(effect_id, ZoneType::Command, owner);
136 }
137}