Skip to main content

manabrew_engine/ability/effects/
delayed_trigger_effect.rs

1use crate::ability::effects::{evaluate_svar, resolve_defined_player, EffectContext};
2use crate::parsing::Params;
3use crate::spellability::SpellAbility;
4use crate::trigger::trigger::parse_trigger;
5
6/// Mirrors Java's `DelayedTriggerEffect`.
7/// Struct form of this effect so it can participate in the
8/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
9/// `DelayedTriggerEffect` class extending `SpellAbilityEffect`.
10#[manabrew_engine_macros::spell_effect(DelayedTriggerEffect)]
11fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
12    let Some(source_id) = sa.source else {
13        return;
14    };
15
16    let mut next_id = 0;
17    let Some(parsed) = parse_trigger(&sa.ability_text, &mut next_id) else {
18        return;
19    };
20    let trigger_params = Params::from_raw(&sa.ability_text);
21    let mode = parsed.kind;
22
23    let execute_svar = if let Some(exec) = sa.ir.execute.as_deref() {
24        if let Some(svar_text) = ctx.game.card(source_id).get_s_var(exec) {
25            svar_text.to_string()
26        } else {
27            exec.to_string()
28        }
29    } else {
30        return;
31    };
32
33    let mut remembered_amount = 0;
34    if sa.ir.remember_number {
35        remembered_amount += ctx
36            .game
37            .card(source_id)
38            .remembered_cmc
39            .iter()
40            .copied()
41            .sum::<i32>();
42    }
43    if let Some(svar_name) = sa.ir.remember_svar_amount.as_deref() {
44        if let Some(expr) = ctx.game.card(source_id).get_s_var(svar_name) {
45            remembered_amount += evaluate_svar(expr, sa);
46        }
47    }
48
49    let controller = if let Some(def_player) = sa.ir.delayed_trigger_defined_player_text.as_deref()
50    {
51        resolve_defined_player(def_player, sa.activating_player, ctx.game)
52            .unwrap_or(sa.activating_player)
53    } else {
54        sa.activating_player
55    };
56    let mut remembered_lki_cards = Vec::new();
57    if sa.ir.remember_objects_remembered_lki {
58        remembered_lki_cards = ctx.game.card(source_id).remembered_cards.clone();
59    }
60    // `RememberObjects$ Remembered` — snapshot the source card's current
61    // remembered_cards into the delayed trigger so the executed ability sees
62    // them later via `SpellAbility::trigger_remembered`. Ashling uses this to
63    // track the token copy it created for its end-step sacrifice clause.
64    let mut remembered_cards: Vec<crate::ids::CardId> = if sa.ir.remember_objects_remembered {
65        ctx.game.card(source_id).remembered_cards.clone()
66    } else {
67        Vec::new()
68    };
69    // `RememberObjects$ RememberedController` — snapshot the controllers of
70    // the source's remembered cards. Arcane Denial uses this to remember the
71    // controller of the countered spell so its delayed "may draw up to two
72    // cards" trigger fires for the right player. Mirrors Java's
73    // `DelayedTriggerEffect.resolve` registration of remembered objects.
74    let mut remembered_players: Vec<crate::ids::PlayerId> = Vec::new();
75    if sa.ir.remember_objects_remembered_controller {
76        for cid in ctx.game.card(source_id).remembered_cards.clone() {
77            let controller = ctx.game.card(cid).controller;
78            if !remembered_players.contains(&controller) {
79                remembered_players.push(controller);
80            }
81        }
82    }
83
84    // `RememberObjects$ TriggeredAttackerLKICopy` — snapshot the attacker
85    // that fired the parent trigger so the delayed trigger's effect can
86    // phase it out / operate on it at a later phase. Teferi's Veil uses
87    // this to remember each attacker and phase them out at end of combat.
88    // The attacker id is populated into both `remembered_cards` (so
89    // `Defined$ DelayTriggerRememberedLKI` resolves via `trigger_remembered`)
90    // and `remembered_lki_cards` (for the trigger_objects string lookup).
91    if sa.ir.remember_objects_triggered_attacker_lki_copy {
92        // The Attacker triggering object is stored as `AbilityValue::Card`
93        // (since the kaalia parity refactor in trigger_attacks.rs); query it
94        // through the typed accessor. The string-based `get_triggering_object`
95        // returns `None` for non-String variants, which would silently drop
96        // the LKI snapshot and break Teferi's Veil-style phase-out triggers.
97        if let Some(cid) = sa.get_triggering_card(crate::ability::AbilityKey::Attacker) {
98            remembered_lki_cards.push(cid);
99            if !remembered_cards.contains(&cid) {
100                remembered_cards.push(cid);
101            }
102        }
103    }
104
105    let delayed = crate::trigger::handler::DelayedTrigger {
106        mode,
107        trigger_mode: parsed.mode,
108        params: trigger_params,
109        execute_svar,
110        controller,
111        source_card: source_id,
112        created_turn: ctx.game.turn.turn_number,
113        created_phase: ctx.game.turn.phase,
114        target_card: None,
115        remembered_amount,
116        remembered_cards,
117        remembered_players,
118        remembered_lki_cards,
119        sort_after_active: false,
120        trigger_order: None,
121    };
122    if sa.ir.this_turn {
123        ctx.trigger_handler
124            .register_this_turn_delayed_trigger(delayed);
125    } else if sa.ir.delayed_trigger_defined_player.is_some() {
126        ctx.trigger_handler
127            .register_player_defined_delayed_trigger(controller, delayed);
128    } else {
129        ctx.trigger_handler.register_delayed_trigger(delayed);
130    }
131}
132
133/// End-of-turn / next-turn registration callback.
134pub fn run(ctx: &mut EffectContext, sa: &SpellAbility) {
135    use crate::ability::spell_ability_effect::SpellAbilityEffect;
136    DelayedTriggerEffect::resolve(ctx, sa);
137}