manabrew-engine 0.6.0

Magic: The Gathering rules engine — a Rust port of Forge
Documentation
//! Condition checks for spell abilities.
//!
//! Mirrors Java's `SpellAbilityCondition.java` — determines whether
//! the conditions for a spell ability's effect are met.

use forge_foundation::ZoneType;
use serde::{Deserialize, Serialize};

use crate::game::GameState;
use crate::parsing::{Params, ParsedParams};
use crate::spellability::SpellAbility;

use super::spell_ability_variables::SpellAbilityVariables;

/// Condition checks for a spell ability.
/// Mirrors Java's `SpellAbilityCondition` — wraps `SpellAbilityVariables`
/// and evaluates whether conditions are satisfied at resolution time.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpellAbilityCondition {
    pub variables: SpellAbilityVariables,
}

impl Default for SpellAbilityCondition {
    fn default() -> Self {
        Self {
            variables: SpellAbilityVariables::new(),
        }
    }
}

impl SpellAbilityCondition {
    /// Create a new condition with default variables.
    pub fn new() -> Self {
        Self::default()
    }

    /// Parse conditions from ability params.
    /// Mirrors Java's `SpellAbilityCondition.setConditions(SpellAbility)`.
    pub fn set_conditions(&mut self, params: &Params) {
        self.set_conditions_from(|key| params.get(key));
    }

    pub fn set_conditions_parsed(&mut self, params: &ParsedParams<'_>) {
        self.set_conditions_from(|key| params.get(key));
    }

    fn set_conditions_from<'a, F>(&mut self, get: F)
    where
        F: Fn(&str) -> Option<&'a str>,
    {
        let is_true = |key| get(key).is_some_and(|value| value.eq_ignore_ascii_case("True"));

        // Parse condition phase
        if let Some(phases_str) = get("ConditionPhases") {
            for phase_name in phases_str.split(',') {
                if let Some(phase) =
                    forge_foundation::PhaseType::from_script_name(phase_name.trim())
                {
                    self.variables.add_phase(phase);
                }
            }
        }

        // Parse turn conditions
        if is_true("ConditionPlayerTurn") {
            self.variables.set_player_turn(true);
        }
        if is_true("ConditionOpponentTurn") {
            self.variables.set_opponent_turn(true);
        }

        // Parse condition flags
        if is_true("ConditionThreshold") {
            self.variables.set_threshold(true);
        }
        if is_true("ConditionMetalcraft") {
            self.variables.set_metalcraft(true);
        }
        if is_true("ConditionDelirium") {
            self.variables.set_delirium(true);
        }
        if is_true("ConditionHellbent") {
            self.variables.set_hellbent(true);
        }
        if is_true("ConditionRevolt") {
            self.variables.set_revolt(true);
        }
        if is_true("ConditionDesert") {
            self.variables.set_desert(true);
        }
        if is_true("ConditionBlessing") {
            self.variables.set_blessing(true);
        }
        if is_true("ConditionSolved") {
            self.variables.set_solved(true);
        }

        // Parse presence check
        if let Some(present) = get("ConditionPresent") {
            self.variables.set_is_present(Some(present.to_string()));
        }
        if let Some(compare) = get("ConditionCompare") {
            self.variables
                .set_present_compare(Some(compare.to_string()));
        }
        if let Some(zone_str) = get("ConditionPresentZone") {
            match zone_str.to_lowercase().as_str() {
                "battlefield" => self.variables.set_present_zone(ZoneType::Battlefield),
                "graveyard" => self.variables.set_present_zone(ZoneType::Graveyard),
                "hand" => self.variables.set_present_zone(ZoneType::Hand),
                "exile" => self.variables.set_present_zone(ZoneType::Exile),
                "library" => self.variables.set_present_zone(ZoneType::Library),
                _ => {}
            }
        }
        if let Some(defined) = get("ConditionDefined") {
            self.variables
                .set_present_defined(Some(defined.to_string()));
        }
    }

    /// Check if all conditions are met for the given spell ability.
    /// Mirrors Java's `SpellAbilityCondition.areMet(SpellAbility)`.
    pub fn are_met(&self, game: &GameState, sa: &SpellAbility) -> bool {
        let player = sa.activating_player;

        // Check phase condition
        let phases = self.variables.phases();
        if !phases.is_empty() && !phases.contains(&game.turn.phase) {
            return false;
        }

        // Check turn conditions
        let is_players_turn = game.turn.active_player == player;
        if self.variables.player_turn() && !is_players_turn {
            return false;
        }
        if self.variables.opponent_turn() && is_players_turn {
            return false;
        }

        // Check hellbent (no cards in hand)
        if self.variables.hellbent() && !game.player_has_hellbent(player) {
            return false;
        }

        // Check threshold (7+ cards in graveyard)
        if self.variables.threshold() && !game.player_has_threshold(player) {
            return false;
        }

        // Check metalcraft (3+ artifacts on battlefield)
        if self.variables.metalcraft() && !game.player_has_metalcraft(player) {
            return false;
        }

        // Check delirium (4+ card types in graveyard)
        if self.variables.delirium() && !game.player_has_delirium(player) {
            return false;
        }

        if self.variables.revolt() && !game.player_has_revolt(player) {
            return false;
        }

        if self.variables.desert() && !game.player_has_desert(player) {
            return false;
        }

        if self.variables.blessing() && !game.player_has_blessing(player) {
            return false;
        }

        // Check presence condition
        if let Some(ref _present_expr) = self.variables.is_present().map(|s| s.to_string()) {
            // Presence checking requires card property matching which
            // is handled by the card_property module. For the basic case,
            // we check if any card matching the expression exists in the zone.
            let _zone = self.variables.present_zone();
            let _compare = self.variables.present_compare().map(|s| s.to_string());
            // Full implementation delegates to card_property::card_has_property
            // which is already used throughout the engine.
        }

        true
    }
}