Skip to main content

manabrew_engine/replacement/
replace_damage.rs

1//! Replacement logic for `Event$ DamageDone`.
2//!
3//! Mirrors Java `ReplaceDamage.java` in `forge/game/replacement/`.
4
5use crate::card::Card;
6use crate::game::GameState;
7use crate::ids::CardId;
8use crate::parsing::compare::compare_expr;
9
10use super::replacement_effect::ReplacementEffect;
11use super::replacement_handler::ReplacementEvent;
12use super::replacement_handler::{execute_replace_with_numeric_update, resolve_replace_value};
13use super::replacement_result::ReplacementResult;
14use super::replacement_type::ReplacementType;
15use crate::card_trait_base::CardTrait;
16
17/// Mirrors Java `ReplaceDamage.canReplace()`.
18pub fn can_replace(
19    effect: &ReplacementEffect,
20    event: &ReplacementEvent,
21    game: &GameState,
22    source_card: &Card,
23) -> bool {
24    if effect.event != ReplacementType::DamageDone {
25        return false;
26    }
27    let (damage_source, target_player, target_card, amount, is_combat) = match event {
28        ReplacementEvent::DamageToCard {
29            target,
30            amount,
31            source,
32            is_combat,
33        } => (*source, None, Some(*target), *amount, *is_combat),
34        ReplacementEvent::DamageToPlayer {
35            target,
36            amount,
37            source,
38            is_combat,
39        } => (*source, Some(*target), None, *amount, *is_combat),
40        _ => return false,
41    };
42    if amount <= 0 {
43        return false;
44    }
45    if let Some(valid_source) = effect.ir.valid_source_selector.as_ref() {
46        let Some(source_id) = damage_source else {
47            return false;
48        };
49        if !effect.matches_compiled_valid_card(valid_source, game.card(source_id), source_card) {
50            return false;
51        }
52    }
53    if let Some(valid_target) = effect.ir.valid_target_selector.as_ref() {
54        let target_matches = if let Some(target) = target_player {
55            crate::card::valid_filter::matches_valid(
56                &valid_target.as_raw(),
57                None,
58                Some(target),
59                source_card,
60                source_card.controller,
61            )
62        } else if let Some(target) = target_card {
63            effect.matches_compiled_valid_card(valid_target, game.card(target), source_card)
64        } else {
65            false
66        };
67        if !target_matches {
68            return false;
69        }
70    }
71    if let Some(wants_max_speed) = effect.ir.max_speed {
72        if wants_max_speed != (game.player(source_card.controller).speed == 4) {
73            return false;
74        }
75    }
76    if let Some(wants_combat) = effect.ir.is_combat {
77        if wants_combat != is_combat {
78            return false;
79        }
80    }
81    if let Some(damage_amount) = effect.ir.damage_amount_text.as_deref() {
82        let threshold = damage_amount.get(2..).unwrap_or("");
83        let rhs = resolve_replace_value(threshold, game, source_card.id, event)
84            .or_else(|| threshold.parse::<i32>().ok())
85            .unwrap_or(0);
86        let cmp = format!("{}{}", damage_amount.get(..2).unwrap_or("GE"), rhs);
87        if !compare_expr(amount, &cmp) {
88            return false;
89        }
90    }
91    true
92}
93
94/// Mirrors Java `ReplacementHandler.executeReplacement()` for DamageDone.
95pub fn execute(
96    effect: &ReplacementEffect,
97    event: &mut ReplacementEvent,
98    game: &GameState,
99    source_card_id: CardId,
100) -> ReplacementResult {
101    match event {
102        ReplacementEvent::DamageToCard { .. } | ReplacementEvent::DamageToPlayer { .. } => {}
103        _ => return ReplacementResult::NotReplaced,
104    }
105    if effect.prevents() {
106        match event {
107            ReplacementEvent::DamageToCard { amount, .. } => *amount = 0,
108            ReplacementEvent::DamageToPlayer { amount, .. } => *amount = 0,
109            _ => {}
110        }
111        return ReplacementResult::Prevented;
112    }
113    // Handle built-in replacement modes before SVar chain.
114    if let Some(replace) = effect.replace_with() {
115        match replace {
116            "DmgTwice" | "DoubleDamage" => {
117                match event {
118                    ReplacementEvent::DamageToCard { amount, .. } => *amount *= 2,
119                    ReplacementEvent::DamageToPlayer { amount, .. } => *amount *= 2,
120                    _ => {}
121                }
122                return ReplacementResult::Updated;
123            }
124            "DmgHalf" | "HalfDamage" => {
125                match event {
126                    ReplacementEvent::DamageToCard { amount, .. } => *amount = (*amount + 1) / 2,
127                    ReplacementEvent::DamageToPlayer { amount, .. } => *amount = (*amount + 1) / 2,
128                    _ => {}
129                }
130                return ReplacementResult::Updated;
131            }
132            "DmgPlus1" => {
133                match event {
134                    ReplacementEvent::DamageToCard { amount, .. } => *amount += 1,
135                    ReplacementEvent::DamageToPlayer { amount, .. } => *amount += 1,
136                    _ => {}
137                }
138                return ReplacementResult::Updated;
139            }
140            "DmgPlus2" => {
141                match event {
142                    ReplacementEvent::DamageToCard { amount, .. } => *amount += 2,
143                    ReplacementEvent::DamageToPlayer { amount, .. } => *amount += 2,
144                    _ => {}
145                }
146                return ReplacementResult::Updated;
147            }
148            _ => {}
149        }
150    }
151    if let Some(result) =
152        execute_replace_with_numeric_update(effect, event, game, source_card_id, "DamageAmount")
153    {
154        return result;
155    }
156    ReplacementResult::Replaced
157}