Skip to main content

manabrew_engine/combat/
attack_cost.rs

1//! Attack cost computation (Propaganda, Ghostly Prison, etc.).
2//!
3//! Mirrors Java Forge's `CombatUtil.getAttackCost()` — scans battlefield for
4//! `CantAttackUnless` static abilities and accumulates the mana cost an
5//! attacker must pay to attack a given defender.
6
7use forge_foundation::ZoneType;
8
9use crate::card::{valid_filter, Card};
10use crate::combat::DefenderId;
11use crate::staticability::StaticMode;
12
13/// Compute the total generic mana cost required for `attacker` to attack `defender`.
14///
15/// Returns the accumulated cost as a generic mana amount, or 0 if no cost.
16/// Scans all battlefield permanents for `Mode$ CantAttackUnless` statics.
17///
18/// Card script example (Propaganda):
19/// ```text
20/// S:Mode$ CantAttackUnless | ValidCard$ Creature | Target$ You | Cost$ 2
21/// ```
22pub fn get_attack_cost(cards: &[Card], attacker: &Card, defender: DefenderId) -> i32 {
23    let mut total_cost = 0;
24
25    for source in cards.iter().filter(|c| c.zone == ZoneType::Battlefield) {
26        for sa in &source.static_abilities {
27            if !sa.check_mode(&StaticMode::CantAttackUnless) {
28                continue;
29            }
30
31            // Check ValidCard$ matches the attacker
32            if !valid_filter::matches_valid_card_selector_opt(
33                sa.ir.valid_card.as_ref(),
34                attacker,
35                source,
36            ) {
37                continue;
38            }
39
40            // Check Target$ — who is being defended
41            if let Some(target_param) = sa.ir.target_text.as_deref() {
42                match target_param {
43                    "You" => {
44                        // Only applies when attacking the source's controller
45                        let defends_controller = match defender {
46                            DefenderId::Player(pid) => pid == source.controller,
47                            DefenderId::Permanent(cid) => {
48                                cards[cid.index()].controller == source.controller
49                            }
50                        };
51                        if !defends_controller {
52                            continue;
53                        }
54                    }
55                    _ => {
56                        // Applies to any attack
57                    }
58                }
59            }
60
61            // Parse Cost$ parameter as generic mana amount
62            if let Some(cost_str) = sa.ir.cost.as_deref() {
63                if let Ok(cost) = cost_str.trim().parse::<i32>() {
64                    total_cost += cost;
65                }
66            }
67        }
68    }
69
70    total_cost
71}