Skip to main content

manabrew_engine/replacement/
replace_add_counter.rs

1//! Replacement logic for `Event$ AddCounter`.
2//!
3//! Mirrors Java `ReplaceAddCounter.java` in `forge/game/replacement/`.
4
5use crate::card::Card;
6use crate::game::GameState;
7use crate::ids::CardId;
8
9use super::replacement_effect::ReplacementEffect;
10use super::replacement_handler::ReplacementEvent;
11use super::replacement_result::ReplacementResult;
12use super::replacement_type::ReplacementType;
13use crate::card_trait_base::CardTrait;
14
15/// Check if the effect has a `ValidCounterType$` that matches the given counter type.
16///
17/// If no `ValidCounterType$` param is present, the effect applies to any counter
18/// type, so return `true`. Otherwise, check if the given counter type name
19/// matches the param value.
20///
21/// Mirrors Java `ReplaceAddCounter.hasAnyInCounterMap()`.
22pub fn has_any_in_counter_map(effect: &ReplacementEffect, counter_type: Option<&str>) -> bool {
23    match effect.ir.valid_counter_type_text.as_deref() {
24        None => true, // No restriction — matches any counter type
25        Some(valid) => match counter_type {
26            None => false, // Effect requires a specific type but none given
27            Some(ct) => valid.split(',').any(|v| v.trim().eq_ignore_ascii_case(ct)),
28        },
29    }
30}
31
32/// Check if this effect's event type matches the given event for AddCounter purposes.
33///
34/// Returns `true` if event is `AddCounter`, or if event is `Moved` and the
35/// effect handles counter-on-move (has a `CounterMap` interaction).
36///
37/// Mirrors Java `ReplaceAddCounter.modeCheck()`.
38pub fn mode_check(effect: &ReplacementEffect, event: &ReplacementType) -> bool {
39    match event {
40        ReplacementType::AddCounter => true,
41        ReplacementType::Moved => effect.ir.counter_map,
42        _ => false,
43    }
44}
45
46/// Mirrors Java `ReplaceAddCounter.canReplace()`.
47pub fn can_replace(
48    effect: &ReplacementEffect,
49    event: &ReplacementEvent,
50    game: &GameState,
51    source_card: &Card,
52) -> bool {
53    if effect.event != ReplacementType::AddCounter {
54        return false;
55    }
56    let (target, is_effect) = match event {
57        ReplacementEvent::AddCounter {
58            target, is_effect, ..
59        } => (*target, *is_effect),
60        _ => return false,
61    };
62    // EffectOnly$ True: only apply to counters placed by effects, not ETB keywords/game rules
63    if effect.ir.effect_only && !is_effect {
64        return false;
65    }
66    let target_card = &game.cards[target.index()];
67    if let Some(valid) = effect.ir.valid_card_selector.as_ref() {
68        if !effect.matches_compiled_valid_card(valid, target_card, source_card) {
69            return false;
70        }
71    }
72    // Check ValidCounterType$ — e.g. Hardened Scales only applies to P1P1 counters.
73    if let Some(valid_ct) = effect.ir.valid_counter_type_text.as_deref() {
74        let counter_type = match event {
75            ReplacementEvent::AddCounter { counter_type, .. } => counter_type,
76            _ => return false,
77        };
78        let expected = crate::ability::effects::parse_counter_type(valid_ct);
79        if *counter_type != expected {
80            return false;
81        }
82    }
83    true
84}
85
86/// Mirrors Java `ReplacementHandler.executeReplacement()` for AddCounter.
87pub fn execute(
88    effect: &ReplacementEffect,
89    event: &mut ReplacementEvent,
90    _game: &GameState,
91    _source_card_id: CardId,
92) -> ReplacementResult {
93    let count = match event {
94        ReplacementEvent::AddCounter { count, .. } => count,
95        _ => return ReplacementResult::NotReplaced,
96    };
97    if let Some(replace) = effect.replace_with() {
98        match replace {
99            "AddOneMoreCounter" | "AddOneMoreCounters" => {
100                *count += 1;
101                return ReplacementResult::Updated;
102            }
103            "AddTwiceCounters" | "DoubleCounters" => {
104                *count *= 2;
105                return ReplacementResult::Updated;
106            }
107            _ => {
108                // Try SVar chain (DB$ ReplaceCounter)
109                if let Some(result) =
110                    super::replacement_handler::execute_replace_with_numeric_update(
111                        effect,
112                        event,
113                        _game,
114                        _source_card_id,
115                        "CounterNum",
116                    )
117                {
118                    return result;
119                }
120                eprintln!(
121                    "[WARN] Unknown replacement mode in AddCounter event: {:?}",
122                    replace
123                );
124            }
125        }
126    }
127    ReplacementResult::Replaced
128}