Skip to main content

manabrew_engine/ability/effects/
learn_effect.rs

1//! Learn effect — reveal a Lesson from outside the game, or discard to draw.
2//!
3//! Ported from Java's `LearnEffect.java` + `Player.learnLesson()`.
4//! Learn: You may reveal a Lesson card you own from outside the game and put
5//! it into your hand, or discard a card to draw a card.
6
7use forge_foundation::ZoneType;
8
9use super::{emit_zone_trigger, EffectContext};
10use crate::event::RunParams;
11use crate::ids::PlayerId;
12use crate::replacement::replacement_handler::{apply_replacements, ReplacementEvent};
13use crate::replacement::ReplacementResult;
14use crate::spellability::SpellAbility;
15use crate::trigger::TriggerType;
16
17/// Struct form of this effect so it can participate in the
18/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
19/// `LearnEffect` class extending `SpellAbilityEffect`.
20#[manabrew_engine_macros::spell_effect(LearnEffect)]
21fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
22    let controller = sa.activating_player;
23
24    // Run Learn replacement effects before learning.
25    let mut event = ReplacementEvent::Learn { player: controller };
26    let result = apply_replacements(ctx.game, &mut event);
27    if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
28        return;
29    }
30
31    let players = if let Some(def) = sa.defined_player() {
32        super::resolve_defined_players(def, controller, ctx.game)
33    } else {
34        vec![controller]
35    };
36
37    for pid in players {
38        learn_lesson(ctx, sa, pid);
39    }
40}
41
42/// Implement the Learn choice: get Lesson from sideboard OR discard+draw.
43fn learn_lesson(ctx: &mut EffectContext, _sa: &SpellAbility, player: PlayerId) {
44    // Collect options: Lesson cards from sideboard + cards in hand
45    let sideboard_lessons: Vec<crate::ids::CardId> = ctx
46        .game
47        .cards_in_zone(ZoneType::Sideboard, player)
48        .to_vec()
49        .into_iter()
50        .filter(|&cid| {
51            ctx.game
52                .card(cid)
53                .type_line
54                .subtypes
55                .iter()
56                .any(|s| s.eq_ignore_ascii_case("Lesson"))
57        })
58        .collect();
59
60    let hand_cards: Vec<crate::ids::CardId> =
61        ctx.game.cards_in_zone(ZoneType::Hand, player).to_vec();
62
63    // Combine all options
64    let mut all_options = sideboard_lessons.clone();
65    all_options.extend(&hand_cards);
66
67    if all_options.is_empty() {
68        return;
69    }
70
71    // Player chooses one card (optional — can decline)
72    ctx.agents[player.index()].snapshot_state(ctx.game, ctx.mana_pools);
73    let chosen = ctx.agents[player.index()].choose_single_card_for_zone_change(
74        ctx.game,
75        player,
76        &all_options,
77        "Learn: choose a Lesson from sideboard, or a card from hand to discard",
78        true, // optional
79    );
80
81    let Some(card_id) = chosen else { return };
82
83    let card_zone = ctx.game.card(card_id).zone;
84
85    if card_zone == ZoneType::Sideboard {
86        // Lesson from sideboard → hand
87        let old_zone = ctx.game.card(card_id).zone;
88        ctx.move_card(card_id, ZoneType::Hand, player);
89        emit_zone_trigger(ctx.trigger_handler, card_id, old_zone, ZoneType::Hand);
90    } else if card_zone == ZoneType::Hand {
91        // Discard from hand, then draw 1
92        let old_zone = ctx.game.card(card_id).zone;
93        ctx.game.player_record_discard(player, 1);
94        ctx.game.card_mut(card_id).set_discarded(true);
95        ctx.game
96            .move_card(card_id, ZoneType::Graveyard, ctx.game.card(card_id).owner);
97
98        ctx.trigger_handler.run_trigger(
99            TriggerType::Discarded,
100            RunParams {
101                card: Some(card_id),
102                player: Some(player),
103                origin: Some(old_zone),
104                destination: Some(ZoneType::Graveyard),
105                ..Default::default()
106            },
107            false,
108        );
109        emit_zone_trigger(ctx.trigger_handler, card_id, old_zone, ZoneType::Graveyard);
110
111        // Draw 1 card
112        let lib = ctx.game.cards_in_zone(ZoneType::Library, player).to_vec();
113        if let Some(&top) = lib.last() {
114            let dz = ctx.game.card(top).zone;
115            ctx.move_card(top, ZoneType::Hand, player);
116            ctx.trigger_handler.run_trigger(
117                TriggerType::Drawn,
118                RunParams {
119                    card: Some(top),
120                    player: Some(player),
121                    ..Default::default()
122                },
123                false,
124            );
125            emit_zone_trigger(ctx.trigger_handler, top, dz, ZoneType::Hand);
126        }
127    }
128}