Skip to main content

manabrew_engine/ability/effects/
clash_effect.rs

1//! Clash effect — each player reveals top card, higher CMC wins.
2//!
3//! Ported 1:1 from Java's `ClashEffect.java`.
4//! Clash with an opponent: Each clashing player reveals the top card of their
5//! library, then puts it on top or bottom. Higher mana value wins.
6
7use forge_foundation::ZoneType;
8
9use super::EffectContext;
10use crate::ids::{CardId, PlayerId};
11use crate::parsing::keys;
12
13/// Struct form of this effect so it can participate in the
14/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
15/// `ClashEffect` class extending `SpellAbilityEffect`.
16#[manabrew_engine_macros::spell_effect(ClashEffect)]
17fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
18    let controller = sa.activating_player;
19
20    // Choose opponent to clash with
21    let opponent = if let Some(def) = sa.defined() {
22        super::resolve_defined_players(def, controller, ctx.game)
23            .into_iter()
24            .next()
25            .unwrap_or_else(|| ctx.game.opponent_of(controller))
26    } else {
27        ctx.game.opponent_of(controller)
28    };
29
30    // RememberClasher$
31    if sa.param_is_true(keys::REMEMBER_CLASHER) {
32        if let Some(sid) = sa.source {
33            ctx.game.card_mut(sid).add_remembered_player(opponent);
34        }
35    }
36
37    let p_lib = ctx
38        .game
39        .cards_in_zone(ZoneType::Library, controller)
40        .to_vec();
41    let o_lib = ctx.game.cards_in_zone(ZoneType::Library, opponent).to_vec();
42
43    if p_lib.is_empty() && o_lib.is_empty() {
44        return;
45    }
46
47    let p_card: Option<CardId> = p_lib.last().copied();
48    let o_card: Option<CardId> = o_lib.last().copied();
49
50    let p_cmc = p_card
51        .map(|cid| ctx.game.card(cid).mana_cost.cmc())
52        .unwrap_or(-1);
53    let o_cmc = o_card
54        .map(|cid| ctx.game.card(cid).mana_cost.cmc())
55        .unwrap_or(-1);
56
57    let mut revealed = Vec::new();
58    if let Some(cid) = p_card {
59        revealed.push(cid);
60    }
61    if let Some(cid) = o_card {
62        revealed.push(cid);
63    }
64
65    // Java CR 701.11: Both players choose order simultaneously; the clash
66    // winner is the activator iff their revealed CMC is strictly greater.
67    // Tie → no winner; `Otherwise` branch runs.
68    let activator_wins = p_cmc > o_cmc;
69
70    clash_move(ctx, controller, p_card);
71    clash_move(ctx, opponent, o_card);
72
73    // `WinSubAbility$ X` / `OtherwiseSubAbility$ Y` — SVar references resolved
74    // from the host. Mirrors Java `ClashEffect` which triggers the right
75    // sub-ability via the `BranchEffect`-like flow.
76    let sub_key = if activator_wins {
77        "WinSubAbility"
78    } else {
79        "OtherwiseSubAbility"
80    };
81    if let Some(sub_sa) = sa.get_additional_ability(sub_key).cloned() {
82        super::resolve_effect(ctx, &sub_sa);
83    }
84}
85
86/// Player chooses to put their clashed card on top or bottom of library.
87fn clash_move(ctx: &mut EffectContext, player: PlayerId, card: Option<CardId>) {
88    let Some(card_id) = card else { return };
89
90    // Ask player: top or bottom?
91    ctx.agents[player.index()].snapshot_state(ctx.game, ctx.mana_pools);
92    let put_on_top = ctx.agents[player.index()].confirm_action(
93        player,
94        Some("ClashTopOrBottom"),
95        &format!(
96            "Put {} on top or bottom of your library?",
97            ctx.game.card(card_id).card_name
98        ),
99        &["Top".to_string(), "Bottom".to_string()],
100        Some(card_id),
101        None,
102    );
103
104    if !put_on_top {
105        // Move to bottom (card is already on top — move to index 0)
106        ctx.game
107            .reorder_card_in_zone(ZoneType::Library, player, card_id, 0);
108    }
109    // If top, card stays where it is (already on top)
110}