Skip to main content

manabrew_engine/ability/effects/
seek_effect.rs

1//! Seek effect — randomly find cards matching criteria from library.
2//!
3//! Ported 1:1 from Java's `SeekEffect.java`.
4//! Seek N [Type]: Randomly select N cards matching [Type] from your library
5//! and put them into your hand. (Arena digital mechanic — no player choice.)
6
7use forge_foundation::ZoneType;
8
9use super::{emit_zone_trigger, matches_change_type, EffectContext};
10use crate::ids::CardId;
11
12/// Struct form of this effect so it can participate in the
13/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
14/// `SeekEffect` class extending `SpellAbilityEffect`.
15#[manabrew_engine_macros::spell_effect(SeekEffect)]
16fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
17    let controller = sa.activating_player;
18    let seek_num = super::resolve_numeric_svar(ctx.game, sa, "Num", 1).max(0) as usize;
19    if seek_num == 0 {
20        return;
21    }
22
23    // Parse seek types — can be comma-separated
24    let types_str = sa.ir.types_text.as_deref().unwrap_or("Card").to_string();
25    let seek_types: Vec<&str> = types_str.split(',').map(str::trim).collect();
26
27    let players = if let Some(def) = sa.defined_player() {
28        super::resolve_defined_players(def, controller, ctx.game)
29    } else {
30        vec![controller]
31    };
32
33    for pid in players {
34        let mut sought: Vec<CardId> = Vec::new();
35
36        for seek_type in &seek_types {
37            // Get library cards matching the type
38            let pool: Vec<CardId> = ctx
39                .game
40                .cards_in_zone(ZoneType::Library, pid)
41                .to_vec()
42                .into_iter()
43                .filter(|&cid| {
44                    if *seek_type == "Card" {
45                        true
46                    } else {
47                        matches_change_type(ctx.game.card(cid), seek_type, &[])
48                    }
49                })
50                .collect();
51
52            if pool.is_empty() {
53                continue;
54            }
55
56            // Randomly select up to seek_num cards
57            let mut shuffled = pool;
58            ctx.rng.shuffle_cards(&mut shuffled);
59            let selected: Vec<CardId> = shuffled.into_iter().take(seek_num).collect();
60
61            // Move each to hand
62            for card_id in selected {
63                let old_zone = ctx.game.card(card_id).zone;
64                ctx.move_card(card_id, ZoneType::Hand, pid);
65                emit_zone_trigger(ctx.trigger_handler, card_id, old_zone, ZoneType::Hand);
66                sought.push(card_id);
67            }
68        }
69
70        // RememberFound$ / ImprintFound$
71        if !sought.is_empty() {
72            if sa.ir.remember_found {
73                if let Some(sid) = sa.source {
74                    for &cid in &sought {
75                        ctx.game.card_mut(sid).add_remembered_card(cid);
76                    }
77                }
78            }
79            if sa.ir.imprint_found {
80                if let Some(sid) = sa.source {
81                    for &cid in &sought {
82                        ctx.game.card_mut(sid).add_imprinted_card(cid);
83                    }
84                }
85            }
86        }
87    }
88}