Skip to main content

manabrew_engine/spellability/
spell_ability_condition.rs

1//! Condition checks for spell abilities.
2//!
3//! Mirrors Java's `SpellAbilityCondition.java` — determines whether
4//! the conditions for a spell ability's effect are met.
5
6use forge_foundation::ZoneType;
7use serde::{Deserialize, Serialize};
8
9use crate::game::GameState;
10use crate::parsing::{Params, ParsedParams};
11use crate::spellability::SpellAbility;
12
13use super::spell_ability_variables::SpellAbilityVariables;
14
15/// Condition checks for a spell ability.
16/// Mirrors Java's `SpellAbilityCondition` — wraps `SpellAbilityVariables`
17/// and evaluates whether conditions are satisfied at resolution time.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct SpellAbilityCondition {
20    pub variables: SpellAbilityVariables,
21}
22
23impl Default for SpellAbilityCondition {
24    fn default() -> Self {
25        Self {
26            variables: SpellAbilityVariables::new(),
27        }
28    }
29}
30
31impl SpellAbilityCondition {
32    /// Create a new condition with default variables.
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Parse conditions from ability params.
38    /// Mirrors Java's `SpellAbilityCondition.setConditions(SpellAbility)`.
39    pub fn set_conditions(&mut self, params: &Params) {
40        self.set_conditions_from(|key| params.get(key));
41    }
42
43    pub fn set_conditions_parsed(&mut self, params: &ParsedParams<'_>) {
44        self.set_conditions_from(|key| params.get(key));
45    }
46
47    fn set_conditions_from<'a, F>(&mut self, get: F)
48    where
49        F: Fn(&str) -> Option<&'a str>,
50    {
51        let is_true = |key| get(key).is_some_and(|value| value.eq_ignore_ascii_case("True"));
52
53        // Parse condition phase
54        if let Some(phases_str) = get("ConditionPhases") {
55            for phase_name in phases_str.split(',') {
56                if let Some(phase) =
57                    forge_foundation::PhaseType::from_script_name(phase_name.trim())
58                {
59                    self.variables.add_phase(phase);
60                }
61            }
62        }
63
64        // Parse turn conditions
65        if is_true("ConditionPlayerTurn") {
66            self.variables.set_player_turn(true);
67        }
68        if is_true("ConditionOpponentTurn") {
69            self.variables.set_opponent_turn(true);
70        }
71
72        // Parse condition flags
73        if is_true("ConditionThreshold") {
74            self.variables.set_threshold(true);
75        }
76        if is_true("ConditionMetalcraft") {
77            self.variables.set_metalcraft(true);
78        }
79        if is_true("ConditionDelirium") {
80            self.variables.set_delirium(true);
81        }
82        if is_true("ConditionHellbent") {
83            self.variables.set_hellbent(true);
84        }
85        if is_true("ConditionRevolt") {
86            self.variables.set_revolt(true);
87        }
88        if is_true("ConditionDesert") {
89            self.variables.set_desert(true);
90        }
91        if is_true("ConditionBlessing") {
92            self.variables.set_blessing(true);
93        }
94        if is_true("ConditionSolved") {
95            self.variables.set_solved(true);
96        }
97
98        // Parse presence check
99        if let Some(present) = get("ConditionPresent") {
100            self.variables.set_is_present(Some(present.to_string()));
101        }
102        if let Some(compare) = get("ConditionCompare") {
103            self.variables
104                .set_present_compare(Some(compare.to_string()));
105        }
106        if let Some(zone_str) = get("ConditionPresentZone") {
107            match zone_str.to_lowercase().as_str() {
108                "battlefield" => self.variables.set_present_zone(ZoneType::Battlefield),
109                "graveyard" => self.variables.set_present_zone(ZoneType::Graveyard),
110                "hand" => self.variables.set_present_zone(ZoneType::Hand),
111                "exile" => self.variables.set_present_zone(ZoneType::Exile),
112                "library" => self.variables.set_present_zone(ZoneType::Library),
113                _ => {}
114            }
115        }
116        if let Some(defined) = get("ConditionDefined") {
117            self.variables
118                .set_present_defined(Some(defined.to_string()));
119        }
120    }
121
122    /// Check if all conditions are met for the given spell ability.
123    /// Mirrors Java's `SpellAbilityCondition.areMet(SpellAbility)`.
124    pub fn are_met(&self, game: &GameState, sa: &SpellAbility) -> bool {
125        let player = sa.activating_player;
126
127        // Check phase condition
128        let phases = self.variables.phases();
129        if !phases.is_empty() && !phases.contains(&game.turn.phase) {
130            return false;
131        }
132
133        // Check turn conditions
134        let is_players_turn = game.turn.active_player == player;
135        if self.variables.player_turn() && !is_players_turn {
136            return false;
137        }
138        if self.variables.opponent_turn() && is_players_turn {
139            return false;
140        }
141
142        // Check hellbent (no cards in hand)
143        if self.variables.hellbent() && !game.player_has_hellbent(player) {
144            return false;
145        }
146
147        // Check threshold (7+ cards in graveyard)
148        if self.variables.threshold() && !game.player_has_threshold(player) {
149            return false;
150        }
151
152        // Check metalcraft (3+ artifacts on battlefield)
153        if self.variables.metalcraft() && !game.player_has_metalcraft(player) {
154            return false;
155        }
156
157        // Check delirium (4+ card types in graveyard)
158        if self.variables.delirium() && !game.player_has_delirium(player) {
159            return false;
160        }
161
162        if self.variables.revolt() && !game.player_has_revolt(player) {
163            return false;
164        }
165
166        if self.variables.desert() && !game.player_has_desert(player) {
167            return false;
168        }
169
170        if self.variables.blessing() && !game.player_has_blessing(player) {
171            return false;
172        }
173
174        // Check presence condition
175        if let Some(ref _present_expr) = self.variables.is_present().map(|s| s.to_string()) {
176            // Presence checking requires card property matching which
177            // is handled by the card_property module. For the basic case,
178            // we check if any card matching the expression exists in the zone.
179            let _zone = self.variables.present_zone();
180            let _compare = self.variables.present_compare().map(|s| s.to_string());
181            // Full implementation delegates to card_property::card_has_property
182            // which is already used throughout the engine.
183        }
184
185        true
186    }
187}