Skip to main content

manabrew_engine/staticability/
static_ability_infect_damage.rs

1use forge_foundation::ZoneType;
2
3use crate::card::{valid_filter, Card};
4use crate::game::GameState;
5use crate::ids::PlayerId;
6use crate::parsing::compare::compare_expr;
7use crate::parsing::CompiledSelector;
8use crate::staticability::StaticMode;
9
10pub fn is_infect_damage(
11    game: &GameState,
12    cards: &[Card],
13    target: PlayerId,
14    source_controller: PlayerId,
15) -> bool {
16    is_infect_damage_with_life_override(game, cards, target, source_controller, None)
17}
18
19pub fn is_infect_damage_with_life_override(
20    game: &GameState,
21    cards: &[Card],
22    target: PlayerId,
23    _source_controller: PlayerId,
24    target_life_override: Option<i32>,
25) -> bool {
26    for source in cards.iter().filter(|c| c.zone == ZoneType::Battlefield) {
27        for st_ab in source
28            .static_abilities
29            .iter()
30            .filter(|sa| sa.check_mode(&StaticMode::InfectDamage))
31        {
32            let life_override = if source.controller == target {
33                target_life_override
34            } else {
35                None
36            };
37            if !condition_matches(game, source, st_ab, life_override) {
38                continue;
39            }
40            let valid = st_ab.ir.valid_target.as_ref();
41            // ValidTarget is evaluated relative to the static ability source
42            // (e.g. Phyrexian Unlife's controller), not the damage source.
43            if matches_valid_player(valid, target, source.controller) {
44                return true;
45            }
46        }
47    }
48    false
49}
50
51fn condition_matches(
52    game: &GameState,
53    source: &Card,
54    st_ab: &crate::staticability::StaticAbility,
55    life_override: Option<i32>,
56) -> bool {
57    let Some(check_svar) = st_ab.ir.check_svar_text.as_deref() else {
58        return true;
59    };
60    let Some(compare) = st_ab.ir.svar_compare_text.as_deref() else {
61        return true;
62    };
63    let Some(expr) = source.svars.get(check_svar) else {
64        return true;
65    };
66    // Only support the pattern needed by Phyrexian Unlife.
67    let value = if expr == "Count$YourLifeTotal" {
68        life_override.unwrap_or_else(|| game.player(source.controller).life)
69    } else {
70        return true;
71    };
72    compare_expr(value, compare)
73}
74
75fn matches_valid_player(
76    valid: Option<&CompiledSelector>,
77    player: PlayerId,
78    source_controller: PlayerId,
79) -> bool {
80    valid_filter::matches_valid_player_selector_opt(valid, player, source_controller)
81}