Skip to main content

manabrew_engine/ability/effects/
counters_put_or_remove_effect.rs

1use forge_foundation::ZoneType;
2
3use super::{resolve_numeric_svar, EffectContext};
4use crate::agent::BinaryChoiceKind;
5use crate::parsing::keys;
6
7/// `SP$ CountersPutOrRemove` — choose add/remove on target card counters.
8///
9/// Mirrors Java `CountersPutOrRemoveEffect.java` binary choice semantics.
10/// Struct form of this effect so it can participate in the
11/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
12/// `CountersPutOrRemoveEffect` class extending `SpellAbilityEffect`.
13#[manabrew_engine_macros::spell_effect(CountersPutOrRemoveEffect)]
14fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
15    let Some(source_id) = sa.source else { return };
16    let controller = sa.activating_player;
17    let source_name = ctx.game.card(source_id).card_name.clone();
18
19    let amount = resolve_numeric_svar(ctx.game, sa, keys::COUNTER_NUM, 1);
20    if amount <= 0 {
21        return;
22    }
23
24    let target_id = sa.target_chosen.target_card.or(sa.source);
25    let Some(target_id) = target_id else { return };
26    if ctx.game.card(target_id).zone != ZoneType::Battlefield {
27        return;
28    }
29
30    let counter_type = sa.ir.counter_type.clone().or_else(|| {
31        // Sort keys for deterministic fallback (HashMap iteration is random).
32        let mut keys: Vec<_> = ctx.game.card(target_id).counters.keys().cloned().collect();
33        keys.sort_by(|a, b| format!("{:?}", a).cmp(&format!("{:?}", b)));
34        keys.into_iter().next()
35    });
36    let Some(counter_type) = counter_type else {
37        return;
38    };
39
40    let can_add =
41        !crate::staticability::static_ability_cant_put_counter::any_cant_put_counter_on_card(
42            &ctx.game.cards,
43            ctx.game.card(target_id),
44            &counter_type,
45        );
46    let can_remove = ctx.game.card(target_id).counter_count(&counter_type) > 0;
47    if !can_add && !can_remove {
48        return;
49    }
50
51    let put_counter = if can_add && can_remove {
52        let prompt = format!(
53            "Add or remove {:?} counter on {}?",
54            counter_type,
55            ctx.game.card(target_id).card_name
56        );
57        ctx.agents[controller.index()].choose_binary(
58            controller,
59            &prompt,
60            BinaryChoiceKind::AddOrRemove,
61            None,
62            Some(source_id),
63            sa.api,
64        )
65    } else {
66        can_add
67    };
68
69    if put_counter {
70        ctx.game
71            .card_mut(target_id)
72            .add_counter(&counter_type, amount);
73        ctx.trigger_handler.run_trigger(
74            crate::trigger::TriggerType::CounterAdded,
75            crate::event::RunParams {
76                card: Some(target_id),
77                counter_type: Some(format!("{:?}", counter_type)),
78                counter_amount: Some(amount),
79                cause_player: Some(controller),
80                ..Default::default()
81            },
82            false,
83        );
84    } else {
85        ctx.game
86            .card_mut(target_id)
87            .remove_counter(&counter_type, amount);
88        ctx.trigger_handler.run_trigger(
89            crate::trigger::TriggerType::CounterRemoved,
90            crate::event::RunParams {
91                card: Some(target_id),
92                counter_type: Some(format!("{:?}", counter_type)),
93                counter_amount: Some(amount),
94                cause_player: Some(controller),
95                ..Default::default()
96            },
97            false,
98        );
99        if sa.ir.remember_removed_cards {
100            ctx.game.card_mut(source_id).add_remembered_card(target_id);
101        }
102    }
103}