1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use forge_foundation::ZoneType;
use super::{emit_zone_trigger, EffectContext};
use crate::ability::ability_ir::DefinedRef;
use crate::card::CounterType;
use crate::event::RunParams;
use crate::parsing::keys;
use crate::replacement::replacement_handler::{apply_replacements, ReplacementEvent};
use crate::replacement::ReplacementResult;
use crate::trigger::TriggerType;
/// `SP$ Explore` — target creature explores.
///
/// Mirrors Java's `ExploreEffect.java`.
/// Explore: Reveal the top card of your library. If it's a land, put it into your hand.
/// Otherwise, put a +1/+1 counter on this creature, then you may put the card into
/// your graveyard.
///
/// # Card script examples
/// ```text
/// A:SP$ Explore | Defined$ Self
/// A:SP$ Explore | Defined$ Targeted | Num$ 2
/// ```
/// Struct form of this effect so it can participate in the
/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
/// `ExploreEffect` class extending `SpellAbilityEffect`.
#[manabrew_engine_macros::spell_effect(ExploreEffect)]
fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
let controller = sa.activating_player;
// Determine the exploring creature
let explorer = sa
.target_chosen
.target_card
.or_else(|| match sa.defined_ref() {
Some(DefinedRef::SelfCard) => sa.source,
Some(DefinedRef::ParentTarget) => ctx.parent_target_card,
_ => sa.source,
});
let explorer_id = match explorer {
Some(id) if ctx.game.card(id).zone == ZoneType::Battlefield => id,
_ => return,
};
// Run Explore replacement effects before exploring.
let mut event = ReplacementEvent::Explore { card: explorer_id };
let result = apply_replacements(ctx.game, &mut event);
if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
return;
}
// Parse Num parameter for multiple explores (e.g. Jadelight Ranger explores twice).
// Mirrors Java's `AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa)`.
let amount = super::resolve_numeric_svar(ctx.game, sa, keys::NUM, 1).max(1);
for _ in 0..amount {
// Re-check explorer is still on battlefield (may have been removed by a trigger)
if ctx.game.card(explorer_id).zone != ZoneType::Battlefield {
return;
}
ctx.game.player_record_explore(controller, 1);
// Check if library has cards
let lib = ctx.game.cards_in_zone(ZoneType::Library, controller);
if lib.is_empty() {
// Explorer still gets the +1/+1 counter per rules
if !crate::staticability::static_ability_cant_put_counter::any_cant_put_counter_on_card(
&ctx.game.cards,
ctx.game.card(explorer_id),
&CounterType::P1P1,
) {
ctx.add_counter(explorer_id, &CounterType::P1P1, 1, sa, RunParams::default());
}
continue;
}
// Reveal top card
let top_card = *lib.last().unwrap();
let is_land = ctx.game.card(top_card).is_land();
if is_land {
// Land → put into hand
let owner = ctx.game.card(top_card).owner;
ctx.move_card(top_card, ZoneType::Hand, owner);
emit_zone_trigger(
ctx.trigger_handler,
top_card,
ZoneType::Library,
ZoneType::Hand,
);
} else {
// Nonland → put +1/+1 counter on explorer
if !crate::staticability::static_ability_cant_put_counter::any_cant_put_counter_on_card(
&ctx.game.cards,
ctx.game.card(explorer_id),
&CounterType::P1P1,
) {
ctx.add_counter(explorer_id, &CounterType::P1P1, 1, sa, RunParams::default());
}
// Player may put revealed card into graveyard (otherwise it stays on top).
// Java's ExploreEffect calls controller.confirmAction() which in the
// harness DeterministicController uses a random boolean (pickBool).
// Use confirm_action here to match that RNG-consuming path.
let card_name = ctx.game.card(top_card).card_name.clone();
let _explorer_name = ctx.game.card(explorer_id).card_name.clone();
let msg = format!("Put {} into your graveyard?", card_name);
let put_in_gy = ctx.agents[controller.index()].confirm_action(
controller,
None,
&msg,
&[],
sa.source,
Some(crate::ability::api_type::ApiType::Explore),
);
if put_in_gy {
let owner = ctx.game.card(top_card).owner;
ctx.move_card(top_card, ZoneType::Graveyard, owner);
emit_zone_trigger(
ctx.trigger_handler,
top_card,
ZoneType::Library,
ZoneType::Graveyard,
);
}
}
ctx.trigger_handler.run_trigger(
TriggerType::Explored,
RunParams {
card: Some(explorer_id),
explored: Some(top_card),
..Default::default()
},
false,
);
}
}