1use crate::ability::api_type::ApiType;
7use crate::ability::spell_ability_effect::SpellAbilityEffect;
8use crate::ids::{CardId, PlayerId};
9use crate::spellability::SpellAbility;
10
11use super::condition::{check_condition, check_condition_present};
12use super::cost_payment::resolve_effect_with_unless_cost;
13use super::effect_context::EffectContext;
14
15use super::*;
19
20macro_rules! effect_dispatch {
24 ( $( $api:path => $handler:path $( [build = $build:path] )? ),* $(,)? ) => {
25 pub const IMPLEMENTED_API_TYPES: &[ApiType] = &[ $( $api ),* ];
28
29 pub(crate) fn build_spell_ability_for_api(api_type: ApiType, sa: &mut SpellAbility) {
31 match api_type {
32 $( $api => effect_dispatch!(@build sa, $handler $(, $build)?), )*
33 _ => {}
34 }
35 }
36
37 pub(super) fn resolve_effect_once(ctx: &mut EffectContext, sa: &SpellAbility) {
41 let api_type = match sa.api {
42 Some(api) => api,
43 None => {
44 return;
47 }
48 };
49 match api_type {
50 $( $api => <$handler as SpellAbilityEffect>::resolve(ctx, sa), )*
51 _ => {
52 let err = crate::ability::IllegalAbilityException::new(
53 format!("Unimplemented effect API type: {:?}", api_type),
54 );
55 eprintln!("{}", err);
56 }
57 }
58 }
59 };
60
61 (@build $sa:ident, $handler:path, $build:path) => {
62 $build($sa)
63 };
64
65 (@build $sa:ident, $handler:path) => {
66 <$handler as SpellAbilityEffect>::build_spell_ability($sa)
67 };
68}
69
70effect_dispatch! {
71 ApiType::DealDamage => damage_deal_effect::DamageDealEffect,
72 ApiType::Branch => branch_effect::BranchEffect,
73 ApiType::GainLife => life_gain_effect::LifeGainEffect,
74 ApiType::LoseLife => life_lose_effect::LifeLoseEffect,
75 ApiType::PutCounter => counters_put_effect::CountersPutEffect,
76 ApiType::RemoveCounter => counters_remove_effect::CountersRemoveEffect,
77 ApiType::Poison => poison_effect::PoisonEffect,
78 ApiType::Pump => pump_effect::PumpEffect,
79 ApiType::Destroy => destroy_effect::DestroyEffect,
80 ApiType::Draw => draw_effect::DrawEffect,
81 ApiType::ChangeZoneAll => change_zone_all_effect::ChangeZoneAllEffect [build = change_zone_all_effect::build_spell_ability],
82 ApiType::ChangeZone => change_zone_effect::ChangeZoneEffect [build = change_zone_effect::build_spell_ability],
83 ApiType::SacrificeAll => sacrifice_all_effect::SacrificeAllEffect,
84 ApiType::Sacrifice => sacrifice_effect::SacrificeEffect,
85 ApiType::CopyPermanent => copy_permanent_effect::CopyPermanentEffect,
86 ApiType::Token => token_effect::TokenEffect,
87 ApiType::Amass => amass_effect::AmassEffect,
88 ApiType::Manifest => manifest_effect::ManifestEffect,
89 ApiType::ManifestDread => manifest_dread_effect::ManifestDreadEffect,
90 ApiType::Cloak => cloak_effect::CloakEffect,
91 ApiType::Investigate => investigate_effect::InvestigateEffect,
92 ApiType::Incubate => incubate_effect::IncubateEffect,
93 ApiType::Seek => seek_effect::SeekEffect,
94 ApiType::Learn => learn_effect::LearnEffect,
95 ApiType::Discover => discover_effect::DiscoverEffect,
96 ApiType::Meld => meld_effect::MeldEffect,
97 ApiType::ExchangeControl => control_exchange_effect::ControlExchangeEffect,
98 ApiType::ControlPlayer => control_player_effect::ControlPlayerEffect,
99 ApiType::Clash => clash_effect::ClashEffect,
100 ApiType::Vote => vote_effect::VoteEffect,
101 ApiType::VillainousChoice => villainous_choice_effect::VillainousChoiceEffect,
102 ApiType::Ascend => ascend_effect::AscendEffect,
103 ApiType::DayTime => day_time_effect::DayTimeEffect,
104 ApiType::Haunt => haunt_effect::HauntEffect,
105 ApiType::Unattach => unattach_effect::UnattachEffect,
106 ApiType::FlipOntoBattlefield => flip_onto_battlefield_effect::FlipOntoBattlefieldEffect,
107 ApiType::ClassLevelUp => class_level_up_effect::ClassLevelUpEffect,
108 ApiType::Venture => venture_effect::VentureEffect,
109 ApiType::RingTemptsYou => ring_tempts_you_effect::RingTemptsYouEffect,
110 ApiType::Heist => heist_effect::HeistEffect,
111 ApiType::ImmediateTrigger => immediate_trigger_effect::ImmediateTriggerEffect,
112 ApiType::StoreSVar => store_s_var_effect::StoreSVarEffect,
113 ApiType::ChangeTargets => change_targets_effect::ChangeTargetsEffect [build = change_targets_effect::build_spell_ability],
114 ApiType::ChangeText => change_text_effect::ChangeTextEffect,
115 ApiType::ChangeX => change_x_effect::ChangeXEffect,
116 ApiType::CountersMove => counters_move_effect::CountersMoveEffect,
117 ApiType::MultiplyCounter => counters_multiply_effect::CountersMultiplyEffect,
118 ApiType::CountersNote => counters_note_effect::CountersNoteEffect,
119 ApiType::RemoveCounterAll => counters_remove_all_effect::CountersRemoveAllEffect,
120 ApiType::ReorderZone => reorder_zone_effect::ReorderZoneEffect,
121 ApiType::Repeat => repeat_effect::RepeatEffect,
122 ApiType::ReplaceEffect => replace_effect::ReplaceEffect,
123 ApiType::BidLife => bid_life_effect::BidLifeEffect,
124 ApiType::Block => block_effect::BlockEffect,
125 ApiType::Bond => bond_effect::BondEffect,
126 ApiType::ChooseCardName => choose_card_name_effect::ChooseCardNameEffect,
127 ApiType::ChooseGeneric => choose_generic_effect::ChooseGenericEffect,
128 ApiType::ControlSpell => control_spell_effect::ControlSpellEffect [build = control_spell_effect::build_spell_ability],
129 ApiType::DamagePrevent => damage_prevent_effect::DamagePreventEffect,
130 ApiType::ExchangeLifeVariant => life_exchange_variant_effect::LifeExchangeVariantEffect,
131 ApiType::ReplaceDamage => replace_damage_effect::ReplaceDamageEffect,
132 ApiType::ReplaceMana => replace_mana_effect::ReplaceManaEffect,
133 ApiType::ReplaceCounter => replace_counter_effect::ReplaceCounterEffect,
134 ApiType::ReplaceToken => replace_token_effect::ReplaceTokenEffect,
135 ApiType::ReplaceSplitDamage => replace_split_damage_effect::ReplaceSplitDamageEffect,
136 ApiType::ExchangeTextBox => text_box_exchange_effect::TextBoxExchangeEffect,
137 ApiType::SwitchBlock => switch_block_effect::SwitchBlockEffect,
138 ApiType::ChangeCombatants => change_combatants_effect::ChangeCombatantsEffect,
139 ApiType::Mana => mana_effect::ManaEffect [build = mana_effect::build_spell_ability],
140 ApiType::ManaReflected => mana_reflected_effect::ManaReflectedEffect [build = mana_reflected_effect::build_spell_ability],
141 ApiType::Mill => mill_effect::MillEffect,
142 ApiType::Scry => scry_effect::ScryEffect,
143 ApiType::Surveil => surveil_effect::SurveilEffect,
144 ApiType::Dig => dig_effect::DigEffect,
145 ApiType::DigMultiple => dig_multiple_effect::DigMultipleEffect,
146 ApiType::RearrangeTopOfLibrary => rearrange_top_of_library_effect::RearrangeTopOfLibraryEffect,
147 ApiType::Reveal => reveal_effect::RevealEffect,
148 ApiType::RevealHand => reveal_hand_effect::RevealHandEffect,
149 ApiType::LookAt => look_at_effect::LookAtEffect,
150 ApiType::Charm => charm_effect::CharmEffect,
151 ApiType::CompanionChoose => charm_effect::CharmEffect,
153 ApiType::GenericChoice => choose_generic_effect::ChooseGenericEffect,
154 ApiType::Plot => plot_effect::PlotEffect,
155 ApiType::PeekAndReveal => peek_and_reveal_effect::PeekAndRevealEffect,
156 ApiType::SetState => set_state_effect::SetStateEffect,
157 ApiType::Cleanup => cleanup_effect::CleanupEffect,
158 ApiType::Counter => counter_effect::CounterEffect [build = counter_effect::build_spell_ability],
159 ApiType::GainControl => control_gain_effect::ControlGainEffect,
160 ApiType::Fight => fight_effect::FightEffect,
161 ApiType::Discard => discard_effect::DiscardEffect,
162 ApiType::Attach => attach_effect::AttachEffect,
163 ApiType::DestroyAll => destroy_all_effect::DestroyAllEffect,
164 ApiType::DamageAll => damage_all_effect::DamageAllEffect,
165 ApiType::PumpAll => pump_all_effect::PumpAllEffect,
166 ApiType::TapAll => tap_all_effect::TapAllEffect,
167 ApiType::TapOrUntapAll => tap_or_untap_all_effect::TapOrUntapAllEffect,
168 ApiType::UntapAll => untap_all_effect::UntapAllEffect,
169 ApiType::Tap => tap_effect::TapEffect,
170 ApiType::TapOrUntap => tap_or_untap_effect::TapOrUntapEffect,
171 ApiType::Untap => untap_effect::UntapEffect,
172 ApiType::SetLife => life_set_effect::LifeSetEffect,
173 ApiType::ExchangeLife => life_exchange_effect::LifeExchangeEffect,
174 ApiType::WinsGame => game_win_effect::GameWinEffect,
175 ApiType::LosesGame => game_loss_effect::GameLossEffect,
176 ApiType::GameDrawn => game_draw_effect::GameDrawEffect,
177 ApiType::AddTurn => add_turn_effect::AddTurnEffect,
178 ApiType::ActivateAbility => activate_ability_effect::ActivateAbilityEffect,
179 ApiType::Fog => fog_effect::FogEffect,
180 ApiType::ReverseTurnOrder => reverse_turn_order_effect::ReverseTurnOrderEffect,
181 ApiType::EndCombatPhase => end_combat_phase_effect::EndCombatPhaseEffect,
182 ApiType::EndTurn => end_turn_effect::EndTurnEffect,
183 ApiType::ExchangePower => power_exchange_effect::PowerExchangeEffect,
184 ApiType::BecomeMonarch => become_monarch_effect::BecomeMonarchEffect,
185 ApiType::TakeInitiative => take_initiative_effect::TakeInitiativeEffect,
186 ApiType::SkipTurn => skip_turn_effect::SkipTurnEffect,
187 ApiType::SkipPhase => skip_phase_effect::SkipPhaseEffect,
188 ApiType::AddPhase => add_phase_effect::AddPhaseEffect,
189 ApiType::Phases => phases_effect::PhasesEffect,
190 ApiType::Regenerate => regenerate_effect::RegenerateEffect,
191 ApiType::Play => play_effect::PlayEffect,
192 ApiType::Animate => animate_effect::AnimateEffect,
193 ApiType::AnimateAll => animate_all_effect::AnimateAllEffect,
194 ApiType::Balance => balance_effect::BalanceEffect,
195 ApiType::ChooseCard => choose_card_effect::ChooseCardEffect,
196 ApiType::ChooseColor => choose_color_effect::ChooseColorEffect,
197 ApiType::ChooseDirection => choose_direction_effect::ChooseDirectionEffect,
198 ApiType::ChooseEvenOdd => choose_even_odd_effect::ChooseEvenOddEffect,
199 ApiType::Clone => clone_effect::CloneEffect,
200 ApiType::Connive => connive_effect::ConniveEffect,
201 ApiType::GainControlVariant => control_gain_variant_effect::ControlGainVariantEffect,
202 ApiType::RepeatEach => repeat_each_effect::RepeatEachEffect,
203 ApiType::Shuffle => shuffle_effect::ShuffleEffect,
204 ApiType::PutCounterAll => counters_put_all_effect::CountersPutAllEffect,
205 ApiType::AddOrRemoveCounter => counters_put_or_remove_effect::CountersPutOrRemoveEffect,
206 ApiType::EachDamage => damage_each_effect::DamageEachEffect,
207 ApiType::Effect => effect_effect::EffectEffect,
208 ApiType::DelayedTrigger => delayed_trigger_effect::DelayedTriggerEffect,
209 ApiType::DrainMana => drain_mana_effect::DrainManaEffect,
210 ApiType::RemoveFromCombat => remove_from_combat_effect::RemoveFromCombatEffect,
211 ApiType::Detain => detain_effect::DetainEffect,
212 ApiType::Goad => goad_effect::GoadEffect,
213 ApiType::ChoosePlayer => choose_player_effect::ChoosePlayerEffect,
214 ApiType::ChooseSource => choose_source_effect::ChooseSourceEffect,
215 ApiType::ChooseType => choose_type_effect::ChooseTypeEffect,
216 ApiType::NameCard => name_card_effect::NameCardEffect,
217 ApiType::ChooseNumber => choose_number_effect::ChooseNumberEffect,
218 ApiType::DigUntil => dig_until_effect::DigUntilEffect,
219 ApiType::FlipACoin => flip_coin_effect::FlipCoinEffect,
220 ApiType::Explore => explore_effect::ExploreEffect,
221 ApiType::RollDice => roll_dice_effect::RollDiceEffect,
222 ApiType::Protection => protect_effect::ProtectEffect,
223 ApiType::ProtectionAll => protect_all_effect::ProtectAllEffect,
224 ApiType::PreventDamage => prevent_damage_effect::PreventDamageEffect,
225 ApiType::Proliferate => counters_proliferate_effect::CountersProliferateEffect,
226 ApiType::MoveCounter => move_counter_effect::MoveCounterEffect,
227 ApiType::TimeTravel => time_travel_effect::TimeTravelEffect,
228 ApiType::MustBlock => must_block_effect::MustBlockEffect,
229 ApiType::CopySpellAbility => copy_spell_ability_effect::CopySpellAbilityEffect [build = copy_spell_ability_effect::build_spell_ability],
230 ApiType::TwoPiles => two_piles_effect::TwoPilesEffect,
231 ApiType::Encode => encode_effect::EncodeEffect,
232
233 ApiType::ExchangeControlVariant => control_gain_variant_effect::ControlGainVariantEffect,
235 ApiType::ExchangeZone => change_zone_effect::ChangeZoneEffect,
236 ApiType::Regeneration => regenerate_effect::RegenerateEffect,
237
238 ApiType::Abandon => abandon_effect::AbandonEffect,
240 ApiType::AdvanceCrank => advance_crank_effect::AdvanceCrankEffect,
241 ApiType::Airbend => airbend_effect::AirbendEffect,
242 ApiType::AlterAttribute => alter_attribute_effect::AlterAttributeEffect,
243 ApiType::AssembleContraption => assemble_contraption_effect::AssembleContraptionEffect,
244 ApiType::AssignGroup => assign_group_effect::AssignGroupEffect,
245 ApiType::BecomesBlocked => becomes_blocked_effect::BecomesBlockedEffect,
246 ApiType::BlankLine => blank_line_effect::BlankLineEffect,
247 ApiType::Blight => blight_effect::BlightEffect,
248 ApiType::Camouflage => camouflage_effect::CamouflageEffect,
249 ApiType::ChangeSpeed => change_speed_effect::ChangeSpeedEffect,
250 ApiType::ChaosEnsues => chaos_ensues_effect::ChaosEnsuesEffect,
251 ApiType::ChooseSector => choose_sector_effect::ChooseSectorEffect,
252 ApiType::ClaimThePrize => claim_the_prize_effect::ClaimThePrizeEffect,
253 ApiType::DamageResolve => damage_resolve_effect::DamageResolveEffect,
254 ApiType::Debuff => debuff_effect::DebuffEffect,
255 ApiType::Draft => draft_effect::DraftEffect,
256 ApiType::Earthbend => earthbend_effect::EarthbendEffect [build = earthbend_effect::build_spell_ability],
257 ApiType::Endure => endure_effect::EndureEffect,
258 ApiType::GainOwnership => ownership_gain_effect::OwnershipGainEffect,
259 ApiType::Intensify => intensify_effect::IntensifyEffect,
260 ApiType::LosePerpetual => lose_perpetual_effect::LosePerpetualEffect,
261 ApiType::MakeCard => make_card_effect::MakeCardEffect,
262 ApiType::MultiplePiles => multiple_piles_effect::MultiplePilesEffect,
263 ApiType::Mutate => mutate_effect::MutateEffect,
264 ApiType::OpenAttraction => open_attraction_effect::OpenAttractionEffect,
265 ApiType::PermanentCreature => permanent_creature_effect::PermanentCreatureEffect,
266 ApiType::PermanentNoncreature => permanent_noncreature_effect::PermanentNoncreatureEffect,
267 ApiType::Planeswalk => planeswalk_effect::PlaneswalkEffect,
268 ApiType::Radiation => radiation_effect::RadiationEffect,
269 ApiType::InternalRadiation => internal_radiation_effect::InternalRadiationEffect,
270 ApiType::ZoneExchange => zone_exchange_effect::ZoneExchangeEffect,
271 ApiType::RemoveFromGame => remove_from_game_effect::RemoveFromGameEffect,
272 ApiType::RemoveFromMatch => remove_from_match_effect::RemoveFromMatchEffect,
273 ApiType::RestartGame => restart_game_effect::RestartGameEffect,
274 ApiType::RollPlanarDice => roll_planar_dice_effect::RollPlanarDiceEffect,
275 ApiType::RunChaos => run_chaos_effect::RunChaosEffect,
276 ApiType::SetInMotion => set_in_motion_effect::SetInMotionEffect,
277 ApiType::Subgame => subgame_effect::SubgameEffect,
278 ApiType::UnlockDoor => unlock_door_effect::UnlockDoorEffect,
279 ApiType::ChangeZoneResolve => change_zone_resolve_effect::ChangeZoneResolveEffect,
280 ApiType::PlayLandVariant => play_land_variant_effect::PlayLandVariantEffect,
281}
282
283pub fn resolve_effect(ctx: &mut EffectContext, sa: &SpellAbility) {
286 if !check_condition(ctx.game, sa) {
288 return;
289 }
290
291 let source_id = match sa.source {
293 Some(id) => id,
294 None => return, };
296 if !check_condition_present(ctx.game, sa, sa.activating_player, source_id) {
297 return;
298 }
299
300 let repeat_count = if let Some(repeat_val) = sa.ir.repeat.as_deref() {
303 match repeat_val {
304 "KickerNum" => sa.kick_count as i32,
305 _ => 1,
306 }
307 } else {
308 1
309 };
310
311 for _ in 0..repeat_count {
312 if let Some(unless_cost) = sa
313 .ir
314 .unless_cost
315 .as_deref()
316 .map(|s: &str| s.trim())
317 .filter(|s| !s.is_empty())
318 {
319 resolve_effect_with_unless_cost(ctx, sa, unless_cost);
320 } else {
321 resolve_effect_once(ctx, sa);
322 }
323 }
324}
325
326pub fn resolve_effect_chain_with_parent(
330 ctx: &mut EffectContext,
331 initial: SpellAbility,
332 initial_parent_target_card: Option<CardId>,
333 initial_parent_target_player: Option<PlayerId>,
334) {
335 let mut current = Some(initial);
336 let mut parent_target_card = initial_parent_target_card;
337 let mut parent_target_player = initial_parent_target_player;
338 let mut is_first = true;
339
340 while let Some(sa) = current {
341 let mut sa_with_ctx;
342 let needs_ctx_clone = !is_first
343 && ((parent_target_card.is_some() && sa.target_chosen.target_card.is_none())
344 || (parent_target_player.is_some() && sa.target_chosen.target_player.is_none()));
345 let sa_ref = if needs_ctx_clone {
346 sa_with_ctx = sa.clone();
347 if sa_with_ctx.target_chosen.target_card.is_none() {
348 sa_with_ctx.target_chosen.target_card = parent_target_card;
349 }
350 if sa_with_ctx.target_chosen.target_player.is_none() {
351 sa_with_ctx.target_chosen.target_player = parent_target_player;
352 }
353 &sa_with_ctx
354 } else {
355 &sa
356 };
357
358 resolve_effect(ctx, sa_ref);
359 parent_target_card = sa_ref.target_chosen.target_card.or(parent_target_card);
360 parent_target_player = sa_ref.target_chosen.target_player.or(parent_target_player);
361 current = if sub_ability_handled_internally(sa_ref) {
362 None
363 } else {
364 sa.sub_ability.map(|b| *b)
365 };
366 is_first = false;
367 if ctx.game.game_over {
368 break;
369 }
370 }
371}
372
373pub fn resolve_effect_chain(ctx: &mut EffectContext, initial: SpellAbility) {
374 resolve_effect_chain_with_parent(ctx, initial, None, None);
375}
376
377pub(crate) fn sub_ability_handled_internally(sa: &SpellAbility) -> bool {
378 sa.ir.unless_cost.is_some()
379}