Skip to main content

manabrew_engine/ability/effects/
branch_effect.rs

1use super::EffectContext;
2use crate::parsing::compare::compare_expr;
3use crate::parsing::keys;
4use crate::spellability::SpellAbility;
5
6/// `DB$ Branch` — resolve one of two sub-abilities based on a condition SVar.
7/// Mirrors Java `BranchEffect`.
8/// Struct form of this effect so it can participate in the
9/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
10/// `BranchEffect` class extending `SpellAbilityEffect`.
11#[manabrew_engine_macros::spell_effect(BranchEffect)]
12fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
13    let take_true_branch = evaluate_branch_condition(ctx, sa);
14    let key = if take_true_branch {
15        keys::TRUE_SUB_ABILITY
16    } else {
17        keys::FALSE_SUB_ABILITY
18    };
19
20    let sub_svar_name = match key {
21        keys::TRUE_SUB_ABILITY => sa.ir.true_sub_ability.as_deref(),
22        keys::FALSE_SUB_ABILITY => sa.ir.false_sub_ability.as_deref(),
23        _ => None,
24    };
25    let Some(sub_svar_name) = sub_svar_name else {
26        return;
27    };
28    let Some(source_id) = sa.source else {
29        return;
30    };
31    let Some(sub_text) = ctx
32        .game
33        .card(source_id)
34        .get_s_var(sub_svar_name)
35        .map(str::to_string)
36    else {
37        return;
38    };
39
40    let mut sub_sa = crate::spellability::build_spell_ability(
41        ctx.game,
42        source_id,
43        &sub_text,
44        sa.activating_player,
45    );
46    sub_sa.target_chosen = sa.target_chosen.clone();
47    sub_sa.trigger_source = sa.trigger_source;
48    sub_sa.trigger_index = sa.trigger_index;
49    sub_sa.trigger_remembered_amount = sa.trigger_remembered_amount;
50    sub_sa.x_mana_cost_paid = sa.x_mana_cost_paid;
51    sub_sa.kicked = sa.kicked;
52    sub_sa.kick_count = sa.kick_count;
53    sub_sa.buyback_paid = sa.buyback_paid;
54    sub_sa.overloaded = sa.overloaded;
55    sub_sa.replicate_count = sa.replicate_count;
56    sub_sa.is_copy = sa.is_copy;
57
58    // Walk the full sub-ability chain, just like Java's AbilityUtils.resolve()
59    // which follows getSubAbility() after each node. Without this, linked effects
60    // (e.g. DBShuffle after DBChangeZoneAll2 in Celestial Reunion) would be skipped.
61    let mut cur_opt: Option<SpellAbility> = Some(sub_sa);
62    while let Some(cur_sa) = cur_opt {
63        super::resolve_effect(ctx, &cur_sa);
64        cur_opt = cur_sa.sub_ability.map(|b| *b);
65        if ctx.game.game_over {
66            break;
67        }
68    }
69}
70
71fn evaluate_branch_condition(ctx: &EffectContext, sa: &SpellAbility) -> bool {
72    let Some(condition_svar) = sa.ir.branch_condition_svar.as_deref() else {
73        return true;
74    };
75    let Some(source_id) = sa.source else {
76        return false;
77    };
78    let Some(expr) = ctx.game.card(source_id).get_s_var(condition_svar) else {
79        return false;
80    };
81
82    if let Some(valid_filter) = expr.strip_prefix("Remembered$Valid ") {
83        let remembered = ctx.game.card(source_id).remembered_cards.clone();
84        if remembered.is_empty() {
85            return false;
86        }
87        if valid_filter.eq_ignore_ascii_case("Card.ChosenType") {
88            let Some(chosen_type) = ctx.game.card(source_id).chosen_type.clone() else {
89                return false;
90            };
91            return remembered
92                .iter()
93                .copied()
94                .any(|cid| ctx.game.card(cid).type_line.has_subtype(&chosen_type));
95        }
96        return remembered.iter().copied().any(|cid| {
97            super::matches_valid_cards_for_sa(ctx.game, sa, ctx.game.card(cid), None, valid_filter)
98        });
99    }
100
101    let branch_compare = sa
102        .ir
103        .branch_condition_svar_compare
104        .as_deref()
105        .unwrap_or("GE1");
106    let (operator, operand) = branch_compare.split_at(branch_compare.len().min(2));
107    let svar_value = super::resolve_numeric_value(ctx.game, sa, condition_svar, 0);
108    let operand_value = super::resolve_numeric_value(ctx.game, sa, operand, 0);
109
110    compare_expr(svar_value, &format!("{operator}{operand_value}"))
111}