Skip to main content

manabrew_engine/replacement/
replace_draw.rs

1//! Replacement logic for `Event$ Draw`.
2//!
3//! Mirrors Java `ReplaceDraw.java` in `forge/game/replacement/`.
4
5use crate::card::Card;
6use crate::game::GameState;
7use crate::ids::CardId;
8use forge_foundation::ZoneType;
9
10use super::replacement_effect::ReplacementEffect;
11use super::replacement_handler::ReplacementEvent;
12use super::replacement_result::ReplacementResult;
13use super::replacement_type::ReplacementType;
14use crate::card_trait_base::CardTrait;
15
16/// Mirrors Java `ReplaceDraw.canReplace()`.
17pub fn can_replace(
18    effect: &ReplacementEffect,
19    event: &ReplacementEvent,
20    game: &GameState,
21    source_card: &Card,
22) -> bool {
23    if effect.event != ReplacementType::Draw {
24        return false;
25    }
26    let player = match event {
27        ReplacementEvent::Draw { player, .. } => *player,
28        _ => return false,
29    };
30    if let Some(valid) = effect.ir.valid_player_selector.as_ref() {
31        if !effect.matches_compiled_valid_player(valid, player, source_card) {
32            return false;
33        }
34    }
35    // NotFirstCardInDrawStep$ True: only replace draws that are NOT the first in the draw step.
36    // Used by Alhammarret's Archive to skip its first draw in the draw step.
37    if effect.ir.not_first_card_in_draw_step {
38        if let ReplacementEvent::Draw {
39            is_first_in_draw_step,
40            ..
41        } = event
42        {
43            if *is_first_in_draw_step {
44                return false;
45            }
46        }
47    }
48    // Dredge: check that the player's library has enough cards to mill.
49    // Mirrors Java's CheckSVar$ DredgeCheckLib | SVarCompare$ GE{N}.
50    if let Some(amount) = effect.ir.dredge_amount {
51        let lib_size = game.cards_in_zone(ZoneType::Library, player).len();
52        if lib_size < amount {
53            return false;
54        }
55    }
56    true
57}
58
59/// Mirrors Java `ReplacementHandler.executeReplacement()` for Draw.
60pub fn execute(
61    effect: &ReplacementEffect,
62    _event: &mut ReplacementEvent,
63    game: &mut GameState,
64    source_card_id: CardId,
65) -> ReplacementResult {
66    if effect.prevents() || effect.has_skip() {
67        return ReplacementResult::Skipped;
68    }
69    // ReplaceWith$ DrawTwo — draw an extra card (Alhammarret's Archive).
70    if let Some(replace) = effect.replace_with() {
71        if replace == "DrawTwo" || replace == "DrawExtra" {
72            if let ReplacementEvent::Draw { extra_draws, .. } = _event {
73                *extra_draws += 1;
74                return ReplacementResult::Updated;
75            }
76        }
77    }
78    // Dredge: mill N cards from library, return this card from graveyard to hand.
79    // Mirrors Java's overriding ability: DB$ Mill + DB$ ChangeZone.
80    if let Some(amount) = effect.ir.dredge_amount {
81        let player = match _event {
82            ReplacementEvent::Draw { player, .. } => *player,
83            _ => return ReplacementResult::Replaced,
84        };
85        // Mill N cards from library top (Rust stores top at end of vec)
86        let lib = game.cards_in_zone(ZoneType::Library, player);
87        let lib_len = lib.len();
88        let start = lib_len.saturating_sub(amount);
89        let mill_cards: Vec<crate::ids::CardId> = lib[start..].iter().rev().copied().collect();
90        for cid in mill_cards {
91            let owner = game.card(cid).owner;
92            game.move_card(cid, ZoneType::Graveyard, owner);
93        }
94        // Return the Dredge card from graveyard to hand
95        if game.card(source_card_id).zone == ZoneType::Graveyard {
96            game.move_card(source_card_id, ZoneType::Hand, player);
97        }
98        return ReplacementResult::Replaced;
99    }
100    ReplacementResult::Replaced
101}