Skip to main content

manabrew_engine/ability/effects/
fight_effect.rs

1use forge_foundation::ZoneType;
2
3use super::EffectContext;
4use crate::ability::ability_ir::DefinedRef;
5use crate::card::card_damage_map::DamageTarget;
6use crate::event::RunParams;
7use crate::trigger::TriggerType;
8
9/// SP$/DB$ Fight — two creatures deal damage to each other equal to their power.
10///
11/// When `Defined$ ParentTarget` is set (the common pattern for DB$ Fight used by
12/// cards like Prey Upon), the first fighter is the parent SA's chosen target card
13/// (`ctx.parent_target_card`) and this SA's `target_chosen.target_card` is the
14/// second fighter. For a direct SP$ Fight without Defined$, `sa.source` is used
15/// as the first fighter.
16///
17/// Mirrors Java's `FightEffect.resolve()`.
18/// Struct form of this effect so it can participate in the
19/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
20/// `FightEffect` class extending `SpellAbilityEffect`.
21#[manabrew_engine_macros::spell_effect(FightEffect)]
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    // The explicitly targeted card is always the "other" fighter (opponent's creature).
29    let target = match sa.target_chosen.target_card {
30        Some(c) => c,
31        None => return,
32    };
33
34    // Determine the "source" fighter based on Defined$ ParentTarget vs direct source.
35    // Prey Upon: `DB$ Fight | Defined$ ParentTarget` — controlled creature from parent SA.
36    let is_defined_parent = matches!(sa.defined_ref(), Some(DefinedRef::ParentTarget));
37    let source = if is_defined_parent {
38        match ctx.parent_target_card {
39            Some(c) => c,
40            None => return,
41        }
42    } else {
43        // Direct SP$ Fight: the source card itself should be a creature (rare pattern).
44        match sa.source {
45            Some(s) => s,
46            None => return,
47        }
48    };
49
50    // Both must be creatures on the battlefield
51    if ctx.game.card(source).zone != ZoneType::Battlefield
52        || !ctx.game.card(source).is_creature()
53        || ctx.game.card(target).zone != ZoneType::Battlefield
54        || !ctx.game.card(target).is_creature()
55    {
56        return;
57    }
58
59    if sa.ir.optional {
60        let decider = sa
61            .source
62            .map(|cid| ctx.game.card(cid).controller)
63            .unwrap_or(sa.activating_player);
64        ctx.agents[decider.index()].snapshot_state(ctx.game, ctx.mana_pools);
65        if !ctx.agents[decider.index()].confirm_action(
66            decider,
67            Some("Fight"),
68            "Would you like those creatures to fight?",
69            &[],
70            sa.source,
71            sa.api,
72        ) {
73            return;
74        }
75    }
76
77    let source_power = ctx.game.card(source).power();
78    let target_power = ctx.game.card(target).power();
79
80    // Track damage sources for DamagedBy trigger filters
81    if !ctx
82        .game
83        .card(target)
84        .damage_sources_this_turn
85        .contains(&source)
86    {
87        ctx.game
88            .card_mut(target)
89            .damage_sources_this_turn
90            .push(source);
91    }
92    if !ctx
93        .game
94        .card(source)
95        .damage_sources_this_turn
96        .contains(&target)
97    {
98        ctx.game
99            .card_mut(source)
100            .damage_sources_this_turn
101            .push(target);
102    }
103    // Deal damage simultaneously
104    if use_damage_map {
105        if let Some(map) = ctx.game.pending_damage_map.as_mut() {
106            map.put(source, DamageTarget::Card(target), source_power);
107            map.put(target, DamageTarget::Card(source), target_power);
108        }
109    } else {
110        ctx.game.deal_damage_to_card(target, source_power);
111        ctx.game.deal_damage_to_card(source, target_power);
112    }
113
114    // Fire per-fighter and batched fight triggers (matches Java FightEffect).
115    ctx.trigger_handler.run_trigger(
116        TriggerType::Fight,
117        RunParams {
118            card: Some(source),
119            card2: Some(target),
120            ..Default::default()
121        },
122        false,
123    );
124    ctx.trigger_handler.run_trigger(
125        TriggerType::Fight,
126        RunParams {
127            card: Some(target),
128            card2: Some(source),
129            ..Default::default()
130        },
131        false,
132    );
133    ctx.trigger_handler.run_trigger(
134        TriggerType::FightOnce,
135        RunParams {
136            card: Some(source),
137            card2: Some(target),
138            ..Default::default()
139        },
140        false,
141    );
142
143    let _ = crate::ability::spell_ability_effect::replace_dying(ctx.game, sa);
144}