Skip to main content

manabrew_engine/ability/effects/
manifest_effect.rs

1//! Manifest effect — put cards onto the battlefield face-down as 2/2 creatures.
2//!
3//! Ported from Java's `ManifestEffect.java` + `ManifestBaseEffect.java`.
4//!
5//! Manifest: Take the top card of a player's library (or chosen cards),
6//! turn it face-down, and put it onto the battlefield as a 2/2 creature.
7//! The card can be turned face-up by paying its mana cost if it's a creature.
8
9use forge_foundation::ZoneType;
10
11use super::manifest_base_effect::parse_manifest_params;
12use super::{emit_zone_trigger, EffectContext};
13use crate::ids::{CardId, PlayerId};
14use crate::spellability::SpellAbility;
15
16/// Struct form of this effect so it can participate in the
17/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
18/// `ManifestEffect` class extending `SpellAbilityEffect`.
19#[manabrew_engine_macros::spell_effect(ManifestEffect)]
20fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
21    let manifest_params = parse_manifest_params(ctx, sa);
22    let amount = manifest_params.amount;
23    let controller = sa.activating_player;
24
25    // DefinedPlayer$ → SA-aware resolution (Targeted preserves empty lists).
26    let players = if let Some(def) = sa.defined_player() {
27        crate::ability::ability_utils::resolve_defined_players_with_sa(
28            def, sa, controller, ctx.game,
29        )
30    } else {
31        vec![controller]
32    };
33
34    for pid in players {
35        manifest_for_player(ctx, sa, pid, amount);
36    }
37}
38
39/// Manifest N cards for a given player.
40fn manifest_for_player(
41    ctx: &mut EffectContext,
42    sa: &SpellAbility,
43    player: PlayerId,
44    amount: usize,
45) {
46    let defined = sa.defined().unwrap_or("TopOfLibrary");
47
48    // Determine source cards
49    let cards_to_manifest: Vec<CardId> = if defined == "TopOfLibrary" || defined.is_empty() {
50        // Default: top N cards of library
51        let lib = ctx.game.cards_in_zone(ZoneType::Library, player).to_vec();
52        lib.into_iter().rev().take(amount).collect()
53    } else if let Some(zone) = sa.ir.choice_zone {
54        // Player chooses from a specific zone
55        let zone_cards = ctx.game.cards_in_zone(zone, player).to_vec();
56        if zone_cards.is_empty() {
57            return;
58        }
59        // Let player choose
60        ctx.agents[player.index()].snapshot_state(ctx.game, ctx.mana_pools);
61        ctx.agents[player.index()].choose_cards_for_zone_change(
62            ctx.game,
63            player,
64            &zone_cards,
65            amount.min(zone_cards.len()),
66            amount.min(zone_cards.len()),
67            "Choose cards to manifest",
68        )
69    } else {
70        // Targeted or self
71        sa.target_chosen.target_card.into_iter().collect()
72    };
73
74    // Manifest each card one at a time (CR 701.34d)
75    for card_id in cards_to_manifest {
76        manifest_single_card(ctx, sa, card_id, player);
77    }
78}
79
80/// Manifest a single card: turn face-down, put on battlefield as 2/2.
81fn manifest_single_card(
82    ctx: &mut EffectContext,
83    sa: &SpellAbility,
84    card_id: CardId,
85    player: PlayerId,
86) {
87    let old_zone = ctx.game.card(card_id).zone;
88
89    // Turn face down
90    ctx.game.card_mut(card_id).set_face_down(true);
91    ctx.game.card_mut(card_id).set_manifested(true);
92
93    // Set as 2/2 creature while face-down
94    ctx.game.card_mut(card_id).set_base_pt(Some(2), Some(2));
95
96    // Move to battlefield under the player's control
97    ctx.game.card_mut(card_id).set_controller(player);
98    ctx.move_card(card_id, ZoneType::Battlefield, player);
99
100    ctx.trigger_handler
101        .register_active_trigger(ctx.game, card_id);
102
103    // RememberManifested$
104    if sa.ir.remember_manifested {
105        if let Some(source_id) = sa.source {
106            ctx.game.card_mut(source_id).add_remembered_card(card_id);
107        }
108    }
109
110    emit_zone_trigger(
111        ctx.trigger_handler,
112        card_id,
113        old_zone,
114        ZoneType::Battlefield,
115    );
116}