Skip to main content

manabrew_engine/ability/effects/
damage_each_effect.rs

1use forge_foundation::ZoneType;
2
3use super::{matches_valid_cards_for_sa, resolve_numeric_svar, EffectContext};
4use crate::card::card_damage_map::DamageTarget;
5use crate::ids::CardId;
6
7/// `SP$ EachDamage` — each matching creature/player deals damage.
8///
9/// Mirrors Java's `DamageEachEffect.java`.
10/// - `ValidCards$` — which creatures deal damage.
11/// - `NumDmg$` — how much damage each deals (default: power of the creature).
12/// - `DefinedPlayers$` — if set, damage is dealt to matching players.
13///
14/// # Card script examples
15/// ```text
16/// A:SP$ EachDamage | ValidCards$ Creature.YouCtrl | NumDmg$ X
17/// ```
18/// Struct form of this effect so it can participate in the
19/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
20/// `DamageEachEffect` class extending `SpellAbilityEffect`.
21#[manabrew_engine_macros::spell_effect(DamageEachEffect)]
22fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
23    let use_damage_map = ctx.game.pending_damage_map.is_some() || sa.ir.damage_map;
24    if sa.ir.damage_map {
25        ctx.game.ensure_pending_damage_maps();
26    }
27
28    let valid_cards = sa.ir.valid_cards_selector.as_ref();
29    let fixed_dmg = sa
30        .ir
31        .num_dmg_present
32        .then(|| {
33            let v = resolve_numeric_svar(ctx.game, sa, "NumDmg", -1);
34            if v == -1 {
35                None
36            } else {
37                Some(v)
38            }
39        })
40        .flatten();
41
42    let player_ids = ctx.game.player_order.clone();
43    let mut damagers: Vec<CardId> = Vec::new();
44
45    for &pid in &player_ids {
46        let zone_cards = ctx.game.cards_in_zone(ZoneType::Battlefield, pid).to_vec();
47        for cid in zone_cards {
48            if matches_valid_cards_for_sa(ctx.game, sa, ctx.game.card(cid), valid_cards, "Creature")
49            {
50                damagers.push(cid);
51            }
52        }
53    }
54
55    // Determine damage target: opponent by default
56    let target_player = sa
57        .target_chosen
58        .target_player
59        .unwrap_or_else(|| ctx.game.opponent_of(sa.activating_player));
60
61    for card_id in damagers {
62        if ctx.game.card(card_id).zone != ZoneType::Battlefield {
63            continue;
64        }
65        let dmg = fixed_dmg.unwrap_or_else(|| ctx.game.card(card_id).power().max(0));
66        if dmg <= 0 {
67            continue;
68        }
69
70        if use_damage_map {
71            if let Some(map) = ctx.game.pending_damage_map.as_mut() {
72                map.put(card_id, DamageTarget::Player(target_player), dmg);
73            }
74        } else {
75            // Deal damage to the target player
76            let dealt = ctx.game.deal_damage_to_player(target_player, dmg);
77            ctx.game.record_player_damage_assignment(
78                Some(card_id),
79                Some(target_player),
80                dealt,
81                false,
82            );
83
84            ctx.trigger_handler.run_trigger(
85                crate::trigger::TriggerType::DamageDone,
86                crate::event::RunParams {
87                    damage_source: Some(card_id),
88                    damage_target_player: Some(target_player),
89                    damage_amount: Some(dmg),
90                    is_combat_damage: Some(false),
91                    ..Default::default()
92                },
93                false,
94            );
95        }
96    }
97}