manabrew_engine/ability/effects/
discard_effect.rs1use forge_foundation::ZoneType;
2use serde::{Deserialize, Serialize};
3
4use super::EffectContext;
5use crate::ability::spell_ability_effect::get_target_players;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
10pub enum DiscardMode {
11 #[default]
12 TgtChoose,
13 YouChoose,
14 RevealTgtChoose,
15 RevealYouChoose,
16 Random,
17 Hand,
18}
19
20impl DiscardMode {
21 pub fn parse(raw: &str) -> Option<Self> {
22 match raw {
23 "TgtChoose" => Some(Self::TgtChoose),
24 "YouChoose" => Some(Self::YouChoose),
25 "RevealTgtChoose" => Some(Self::RevealTgtChoose),
26 "RevealYouChoose" => Some(Self::RevealYouChoose),
27 "Random" => Some(Self::Random),
28 "Hand" => Some(Self::Hand),
29 _ => None,
30 }
31 }
32
33 fn chooser_style_optional(self) -> bool {
34 matches!(
35 self,
36 Self::TgtChoose | Self::YouChoose | Self::RevealYouChoose | Self::RevealTgtChoose
37 )
38 }
39
40 fn reveals_hand(self) -> bool {
41 matches!(self, Self::RevealTgtChoose | Self::RevealYouChoose)
42 }
43
44 fn chooser_is_activator(self) -> bool {
45 matches!(self, Self::YouChoose | Self::RevealYouChoose)
46 }
47}
48
49#[manabrew_engine_macros::spell_effect(DiscardEffect)]
53fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
54 let num = super::resolve_numeric_svar(ctx.game, sa, crate::parsing::keys::NUM_CARDS, 1).max(0)
55 as usize;
56 let mode = sa.ir.discard_mode.unwrap_or_default();
57
58 let any_number = sa.ir.any_number;
62
63 let target_players = get_target_players(ctx.game, sa);
64 let first_target = target_players.first().copied();
65
66 for target_player in target_players.iter().copied() {
67 let mut hand: Vec<_> = ctx
68 .game
69 .cards_in_zone(ZoneType::Hand, target_player)
70 .to_vec();
71 if let Some(valid_filter) = sa.ir.discard_valid_text.as_deref() {
72 let valid_selector = sa.ir.discard_valid_selector.as_ref();
73 hand.retain(|&card_id| {
74 super::matches_valid_cards_for_sa(
75 ctx.game,
76 sa,
77 ctx.game.card(card_id),
78 valid_selector,
79 valid_filter,
80 )
81 });
82 }
83
84 let chooser_style_optional = mode.chooser_style_optional();
87
88 let chooser = if mode.chooser_is_activator() {
92 sa.activating_player
93 } else if mode == DiscardMode::RevealTgtChoose {
94 first_target.unwrap_or(target_player)
95 } else {
96 target_player
97 };
98
99 if mode.reveals_hand() && !hand.is_empty() {
102 let source_name = sa.source.map(|cid| ctx.game.card(cid).card_name.clone());
103 for agent in ctx.agents.iter_mut() {
104 agent.reveal_cards(
105 ctx.game,
106 target_player,
107 &hand,
108 ZoneType::Hand,
109 target_player,
110 source_name.as_deref(),
111 );
112 }
113 }
114
115 if sa.ir.optional && !any_number && !chooser_style_optional {
116 let accepted = ctx.agents[target_player.index()].confirm_action(
117 target_player,
118 None,
119 "Do you want to discard?",
120 &[],
121 sa.source,
122 Some(crate::ability::api_type::ApiType::Discard),
123 );
124 if !accepted {
125 continue;
126 }
127 }
128
129 if mode == DiscardMode::Hand {
132 let remember_discarded = sa.ir.remember_discarded;
133 for card_id in hand.iter().copied() {
134 if ctx.game.card(card_id).zone == ZoneType::Hand {
135 if remember_discarded {
136 if let Some(sid) = sa.source {
137 ctx.game.card_mut(sid).add_remembered_card(card_id);
138 }
139 }
140 ctx.game.discard_card(
141 card_id,
142 target_player,
143 Some(sa),
144 Some(ctx.agents),
145 ctx.trigger_handler,
146 );
147 }
148 }
149 continue;
150 }
151
152 let to_discard = match mode {
153 DiscardMode::Random => {
154 ctx.agents[target_player.index()].choose_random_discard(target_player, &hand, num)
155 }
156 _ if any_number => ctx.agents[chooser.index()].choose_discard_any_number(
157 target_player,
158 &hand,
159 0,
160 hand.len(),
161 ),
162 _ if sa.ir.optional && chooser_style_optional => ctx.agents[chooser.index()]
163 .choose_discard_any_number(target_player, &hand, 0, num.min(hand.len())),
164 _ if chooser_style_optional => {
165 ctx.agents[chooser.index()].choose_discard(target_player, &hand, num)
166 }
167 _ => ctx.agents[target_player.index()].choose_discard(target_player, &hand, num),
168 };
169
170 let remember_discarded = sa.ir.remember_discarded;
174
175 let to_discard = if to_discard.len() > 1 {
176 let reordered = ctx.agents[target_player.index()].choose_reorder_library(
177 ctx.game,
178 target_player,
179 &to_discard,
180 );
181 if reordered.len() == to_discard.len() {
182 reordered
183 } else {
184 to_discard
185 }
186 } else {
187 to_discard
188 };
189
190 for card_id in to_discard {
191 if ctx.game.card(card_id).zone == ZoneType::Hand {
192 if remember_discarded {
193 if let Some(sid) = sa.source {
194 ctx.game.card_mut(sid).add_remembered_card(card_id);
195 }
196 }
197 ctx.game.discard_card(
198 card_id,
199 target_player,
200 Some(sa),
201 Some(ctx.agents),
202 ctx.trigger_handler,
203 );
204 }
205 }
206 }
207}