1use std::collections::HashSet;
2
3use forge_foundation::ZoneType;
4
5use crate::agent::{notify_all_agents, GameLogEvent, PlayerAgent};
6use crate::card::valid_filter;
7use crate::event::RunParams;
8use crate::game::GameState;
9use crate::ids::{CardId, PlayerId};
10use crate::mana::ManaPool;
11use crate::parsing::compare::compare_expr;
12use crate::spellability::{build_spell_ability, StackEntry};
13use crate::trigger::TriggerType;
14use crate::trigger::{parse_trigger, Trigger};
15
16#[derive(Debug, Clone)]
20struct ActiveTrigger {
21 card_id: CardId,
22 trigger_index: usize,
23}
24
25#[derive(Debug, Clone)]
27struct TriggerWaiting {
28 mode: TriggerType,
29 params: RunParams,
30 trigger_refs: Option<Vec<(CardId, usize)>>,
31}
32
33#[derive(Debug, Clone)]
35pub struct DelayedTrigger {
36 pub mode: TriggerType,
37 pub trigger_mode: Box<dyn crate::trigger::TriggerBehavior>,
38 pub params: crate::parsing::Params,
39 pub execute_svar: String,
40 pub controller: PlayerId,
41 pub source_card: CardId,
42 pub created_turn: u32,
44 pub created_phase: forge_foundation::PhaseType,
46 pub target_card: Option<CardId>,
49 pub remembered_amount: i32,
51 #[allow(dead_code)]
55 pub remembered_cards: Vec<CardId>,
56 pub remembered_players: Vec<PlayerId>,
63 pub remembered_lki_cards: Vec<CardId>,
65 pub sort_after_active: bool,
71 pub trigger_order: Option<u32>,
75}
76
77impl DelayedTrigger {
78 pub fn as_trigger(&self, _game: &crate::game::GameState) -> crate::trigger::Trigger {
81 let Self {
82 params: delayed_params,
83 ..
84 } = self;
85 let mut base =
86 crate::game_loop::trigger_replacement_base::TriggerReplacementBase::default();
87 base.set_host_card_id(self.source_card);
88 crate::trigger::Trigger {
89 id: self.trigger_order.unwrap_or(u32::MAX),
90 base,
91 kind: self.mode,
92 mode: dyn_clone::clone_box(&*self.trigger_mode),
93 ir: crate::trigger::trigger_ir::TriggerIr::from_params(delayed_params),
94 execute: self.execute_svar.clone(),
95 optional: false,
96 description: String::new(),
97 static_trigger: false,
98 trigger_remembered: self
99 .remembered_cards
100 .iter()
101 .copied()
102 .map(crate::event::AbilityValue::Card)
103 .collect(),
104 spawning_ability: None,
105 }
106 }
107}
108
109#[derive(Debug, Clone)]
111pub struct PendingTrigger {
112 pub entry: StackEntry,
113 pub ability_triggered: Option<RunParams>,
114 pub optional: bool,
116 pub decider: PlayerId,
118 pub description: String,
120}
121
122#[derive(Debug, Clone)]
123pub struct TriggerPushLog {
124 pub source_name: String,
125 pub player_name: String,
126 pub optional: bool,
127 pub trigger_api: String,
128}
129
130type MatchedTrigger = (PendingTrigger, PlayerId, u64, u8, u32);
131
132#[allow(dead_code)]
136#[derive(Debug, Clone)]
137pub struct TriggerHandler {
138 active_triggers: Vec<ActiveTrigger>,
139 waiting_triggers: Vec<TriggerWaiting>,
140 delayed_triggers: Vec<DelayedTrigger>,
141 this_turn_delayed_triggers: Vec<DelayedTrigger>,
142 player_defined_delayed_triggers: Vec<(PlayerId, DelayedTrigger)>,
143 suppressed_modes: HashSet<TriggerType>,
144 all_suppressed: bool,
145 next_trigger_id: u32,
146 pre_matched_triggers: Vec<MatchedTrigger>,
151}
152
153impl TriggerHandler {
154 pub fn new() -> Self {
155 TriggerHandler {
156 active_triggers: Vec::new(),
157 waiting_triggers: Vec::new(),
158 delayed_triggers: Vec::new(),
159 this_turn_delayed_triggers: Vec::new(),
160 player_defined_delayed_triggers: Vec::new(),
161 suppressed_modes: HashSet::new(),
162 all_suppressed: false,
163 next_trigger_id: 0,
164 pre_matched_triggers: Vec::new(),
165 }
166 }
167
168 pub fn run_trigger(&mut self, mode: TriggerType, params: RunParams, hold: bool) {
173 if self.is_trigger_suppressed(mode) {
174 return;
175 }
176
177 let urgent = !hold
181 && matches!(
182 mode,
183 TriggerType::Always | TriggerType::TapsForMana | TriggerType::ManaAdded
184 );
185 if urgent {
186 self.waiting_triggers
187 .insert(0, self.build_waiting_trigger(mode, params.clone()));
188 } else {
189 self.waiting_triggers
190 .push(self.build_waiting_trigger(mode, params.clone()));
191 }
192
193 if mode == TriggerType::SpellCast {
194 self.waiting_triggers
195 .push(self.build_waiting_trigger(TriggerType::SpellAbilityCast, params.clone()));
196 self.waiting_triggers
197 .push(self.build_waiting_trigger(TriggerType::SpellCastOrCopy, params.clone()));
198 }
199 if mode == TriggerType::AbilityCast {
200 self.waiting_triggers
201 .push(self.build_waiting_trigger(TriggerType::SpellAbilityCast, params.clone()));
202 }
203 if mode == TriggerType::SpellCopied {
204 self.waiting_triggers
205 .push(self.build_waiting_trigger(TriggerType::SpellCopy, params.clone()));
206 self.waiting_triggers
207 .push(self.build_waiting_trigger(TriggerType::SpellAbilityCopy, params.clone()));
208 self.waiting_triggers
209 .push(self.build_waiting_trigger(TriggerType::SpellCastOrCopy, params.clone()));
210 }
211 }
212
213 pub fn run_trigger_with_game(
216 &mut self,
217 game: &GameState,
218 mode: TriggerType,
219 params: RunParams,
220 hold: bool,
221 ) -> Vec<PendingTrigger> {
222 self.run_trigger(mode, params, hold);
223 let resolve_now = mode == TriggerType::Always
224 || (!hold
225 && !game.stack.is_frozen()
226 && !matches!(mode, TriggerType::TapsForMana | TriggerType::ManaAdded));
227 if resolve_now {
228 self.run_waiting_triggers(game)
229 } else {
230 Vec::new()
231 }
232 }
233
234 pub fn parse_trigger(&mut self, raw: &str) -> Option<Trigger> {
236 parse_trigger(raw, &mut self.next_trigger_id)
237 }
238
239 pub fn collect_trigger_for_waiting(&mut self, mode: TriggerType, params: RunParams) {
241 self.waiting_triggers
242 .push(self.build_waiting_trigger(mode, params));
243 }
244
245 pub fn waiting_trigger_count(&self) -> usize {
247 self.waiting_triggers.len()
248 }
249
250 pub fn pre_matched_trigger_count(&self) -> usize {
251 self.pre_matched_triggers.len()
252 }
253
254 pub fn has_number_drawn_triggers(&self, game: &GameState) -> bool {
265 self.active_triggers.iter().any(|at| {
266 let card = game.card(at.card_id);
267 if at.trigger_index >= card.triggers.len() {
268 return false;
269 }
270 {
271 let t = &card.triggers[at.trigger_index];
272 t.kind == TriggerType::Drawn && t.mode.drawn_number().is_some()
273 }
274 })
275 }
276
277 pub fn flush_waiting_triggers(&mut self, game: &GameState) {
278 if self.waiting_triggers.is_empty() && self.delayed_triggers.is_empty() {
279 return;
280 }
281 let matched = self.match_waiting_triggers(game);
282 self.pre_matched_triggers.extend(matched);
283 }
284
285 pub fn run_waiting_triggers(&mut self, game: &GameState) -> Vec<PendingTrigger> {
289 let mut entries: Vec<MatchedTrigger> = std::mem::take(&mut self.pre_matched_triggers);
291
292 if self.waiting_triggers.is_empty() && self.delayed_triggers.is_empty() {
293 if entries.is_empty() {
294 return Vec::new();
295 }
296 let active_player = game.active_player();
298 entries.sort_by_key(|(_, controller, ts, trigger_bucket, trigger_order)| {
299 (
300 if *controller == active_player { 0u8 } else { 1 },
301 if *trigger_bucket == 2 { 1u8 } else { 0 },
302 *ts,
303 *trigger_bucket,
304 *trigger_order,
305 )
306 });
307 return entries
308 .into_iter()
309 .map(|(pending, _, _, _, _)| pending)
310 .collect();
311 }
312
313 entries.extend(self.match_waiting_triggers(game));
315
316 entries.extend(self.fire_immediate_delayed_triggers(game));
320
321 let active_player = game.active_player();
326 entries.sort_by_key(|(_, controller, ts, trigger_bucket, trigger_order)| {
327 (
328 if *controller == active_player { 0u8 } else { 1 },
329 if *trigger_bucket == 2 { 1u8 } else { 0 },
330 *ts,
331 *trigger_bucket,
332 *trigger_order,
333 )
334 });
335
336 entries
337 .into_iter()
338 .map(|(pending, _, _, _, _)| pending)
339 .collect()
340 }
341
342 pub fn process_waiting_triggers(
343 &mut self,
344 mana_pools: &[ManaPool],
345 game: &mut GameState,
346 agents: &mut [Box<dyn PlayerAgent>],
347 ) -> Vec<TriggerPushLog> {
348 let pending = self.run_waiting_triggers(game);
349 self.process_pending_triggers(mana_pools, game, agents, pending)
350 }
351
352 pub fn process_pending_triggers(
353 &mut self,
354 mana_pools: &[ManaPool],
355 game: &mut GameState,
356 agents: &mut [Box<dyn PlayerAgent>],
357 pending: Vec<PendingTrigger>,
358 ) -> Vec<TriggerPushLog> {
359 let mut push_logs = Vec::new();
360 for mut pt in pending {
361 let source_name = pt
362 .entry
363 .spell_ability
364 .source
365 .and_then(|id| game.cards.get(id.index()).map(|c| c.card_name.clone()))
366 .unwrap_or_else(|| "Triggered ability".to_string());
367 let player_name = game
368 .player(pt.entry.spell_ability.activating_player)
369 .name
370 .clone();
371
372 if pt.entry.spell_ability.api == Some(crate::ability::api_type::ApiType::Charm)
373 && !crate::ability::effects::charm_effect::make_choices_precast(
374 game,
375 agents,
376 &mut pt.entry.spell_ability,
377 )
378 {
379 continue;
380 }
381
382 let setup_result = pt
383 .entry
384 .spell_ability
385 .setup_targets(game, agents, mana_pools);
386 if !setup_result {
387 continue;
388 }
389 if let Some(source_id) = pt.entry.spell_ability.source {
390 crate::ability::effects::emit_targeting_triggers_for_sa(
391 self,
392 game,
393 source_id,
394 &pt.entry.spell_ability,
395 );
396 }
397
398 if pt.optional {
399 pt.entry.optional_trigger_decider = Some(pt.decider);
400 pt.entry.optional_trigger_description = Some(pt.description.clone());
401 pt.entry.optional_trigger_source_name = Some(source_name.clone());
402 }
403
404 if let Some(source_id) = pt.entry.spell_ability.trigger_source {
405 if let Some(trig_idx) = pt.entry.spell_ability.trigger_index {
406 if let Some(trigger) = game
407 .cards
408 .get(source_id.index())
409 .and_then(|c| c.triggers.get(trig_idx))
410 .cloned()
411 {
412 trigger.trigger_run(game, source_id);
413 }
414 }
415 }
416
417 let trigger_mode = pt
418 .entry
419 .spell_ability
420 .trigger_source
421 .and_then(|source_id| {
422 pt.entry.spell_ability.trigger_index.and_then(|idx| {
423 game.cards
424 .get(source_id.index())
425 .and_then(|c| c.triggers.get(idx))
426 .map(|t| t.kind.name().to_string())
427 })
428 })
429 .unwrap_or_else(|| "DelayedOrUnknown".to_string());
430 let trigger_api = pt
431 .entry
432 .spell_ability
433 .api
434 .map(|a| a.name().to_string())
435 .unwrap_or_else(|| "Unknown".to_string());
436
437 let trigger_msg = if pt.description.is_empty() {
438 format!(
439 "Trigger fired: mode={trigger_mode} | api={trigger_api} | source={source_name}"
440 )
441 } else {
442 format!(
443 "Trigger fired: mode={} | api={} | source={} | {}",
444 trigger_mode, trigger_api, source_name, pt.description
445 )
446 };
447 let mut event = GameLogEvent::stack(trigger_msg)
448 .with_player(pt.entry.spell_ability.activating_player);
449 if let Some(source_id) = pt.entry.spell_ability.source {
450 event = event.with_source_card(source_id);
451 }
452 if let Some(target_id) = pt.entry.spell_ability.target_chosen.target_card {
453 event = event.with_target_card(target_id);
454 }
455 notify_all_agents(agents, event);
456
457 let is_optional = pt.optional;
458 let pushed_entry = pt.entry.clone();
459 game.stack.push(pt.entry);
460 if pushed_entry.spell_ability.is_trigger {
461 let source_card = pushed_entry.spell_ability.source;
462 self.run_trigger(
463 TriggerType::SpellAbilityCast,
464 RunParams {
465 card: source_card,
466 spell_card: source_card,
467 player: Some(pushed_entry.spell_ability.activating_player),
468 activator: Some(pushed_entry.spell_ability.activating_player),
469 spell_controller: Some(pushed_entry.spell_ability.activating_player),
470 spell_ability: Some(pushed_entry.spell_ability.clone()),
471 source_sa: Some(pushed_entry.spell_ability.clone()),
472 cause: Some(pushed_entry.spell_ability.clone()),
473 cause_card: source_card,
474 ..Default::default()
475 },
476 true,
477 );
478 if let Some(mut ability_triggered) = pt.ability_triggered.clone() {
479 ability_triggered.spell_ability = Some(pushed_entry.spell_ability.clone());
480 ability_triggered.source_sa = Some(pushed_entry.spell_ability.clone());
481 if ability_triggered.cause_card.is_none() {
482 ability_triggered.cause_card = source_card;
483 }
484 self.run_trigger(TriggerType::AbilityTriggered, ability_triggered, false);
485 }
486 }
487 push_logs.push(TriggerPushLog {
488 source_name,
489 player_name,
490 optional: is_optional,
491 trigger_api,
492 });
493 }
494 push_logs
495 }
496
497 fn match_waiting_triggers(&mut self, game: &GameState) -> Vec<MatchedTrigger> {
501 let waiting = std::mem::take(&mut self.waiting_triggers);
502 let mut entries: Vec<MatchedTrigger> = Vec::new();
503
504 for event in &waiting {
505 let mut trigger_refs: Vec<(CardId, usize, usize)> =
506 if let Some(stored_refs) = &event.trigger_refs {
507 stored_refs
508 .iter()
509 .enumerate()
510 .map(|(idx, (card_id, trigger_index))| (*card_id, *trigger_index, idx))
511 .collect()
512 } else {
513 self.active_triggers
514 .iter()
515 .enumerate()
516 .map(|(idx, active)| (active.card_id, active.trigger_index, idx))
517 .collect()
518 };
519 if event.trigger_refs.is_none() {
520 for (card_id, trigger_index) in self.ltb_trigger_refs_for_event(game, event) {
521 if !trigger_refs.iter().any(|(existing_card, existing_idx, _)| {
522 *existing_card == card_id && *existing_idx == trigger_index
523 }) {
524 trigger_refs.push((card_id, trigger_index, usize::MAX));
525 }
526 }
527 }
528 trigger_refs.sort_by_key(|&(card_id, trigger_index, idx)| {
529 let card = game.card(card_id);
530 let is_static = card
531 .triggers
532 .get(trigger_index)
533 .map(|trigger| !trigger.is_static())
534 .unwrap_or(true);
535 (is_static, idx)
536 });
537
538 let event_entries_start = entries.len();
539
540 for (card_id, trigger_index, _) in trigger_refs {
542 let card = game.card(card_id);
543 if trigger_index >= card.triggers.len() {
544 continue;
545 }
546 let trigger = &card.triggers[trigger_index];
547 let host_controller = card.controller;
548 if crate::staticability::static_ability_disable_triggers::is_disabled(
549 game,
550 card_id,
551 trigger,
552 &event.params,
553 ) {
554 continue;
555 }
556
557 let can_run = self.can_run_trigger(
558 game,
559 card_id,
560 trigger_index,
561 host_controller,
562 &event.mode,
563 &event.params,
564 );
565 if can_run {
566 let sa = trigger.build_triggered_spell_ability(
567 game,
568 card_id,
569 host_controller,
570 trigger_index,
571 &event.params,
572 );
573
574 let entry = StackEntry {
575 id: 0,
576 spell_ability: sa,
577 is_pending_cast: false,
578 is_creature_spell: false,
579 is_permanent_spell: false,
580 cast_from_zone: None,
581 optional_trigger_decider: None,
582 optional_trigger_description: None,
583 optional_trigger_source_name: None,
584 };
585 let trigger_cost_optional = entry
590 .spell_ability
591 .pay_costs
592 .as_ref()
593 .map(|c| !c.mandatory && !c.is_zero_cost())
594 .unwrap_or(false);
595 let effect_optional_decider =
596 entry.spell_ability.ir.optional_decider_text.is_some();
597 let description_lower = trigger.description.to_ascii_lowercase();
598 let description_implies_optional = description_lower.starts_with("you may")
599 && !entry.spell_ability.ir.optional
600 && !effect_optional_decider;
601 let pending = PendingTrigger {
602 ability_triggered: Some(
603 crate::trigger::trigger_ability_triggered::build_run_params(
604 trigger,
605 &entry.spell_ability,
606 &event.params,
607 game,
608 ),
609 ),
610 entry,
611 optional: (trigger.optional && !effect_optional_decider)
612 || trigger_cost_optional
613 || description_implies_optional,
614 decider: host_controller,
615 description: trigger.description.clone(),
616 };
617 let source_ts = card.zone_timestamp;
618 entries.push((pending, host_controller, source_ts, 1, trigger.id));
619 let extra = crate::staticability::static_ability_panharmonicon::extra_triggers(
620 game,
621 card_id,
622 trigger,
623 &event.params,
624 );
625 for _ in 0..extra {
626 let sa2 = trigger.build_triggered_spell_ability(
627 game,
628 card_id,
629 host_controller,
630 trigger_index,
631 &event.params,
632 );
633 let extra_entry = StackEntry {
634 id: 0,
635 spell_ability: sa2,
636 is_pending_cast: false,
637 is_creature_spell: false,
638 is_permanent_spell: false,
639 cast_from_zone: None,
640 optional_trigger_decider: None,
641 optional_trigger_description: None,
642 optional_trigger_source_name: None,
643 };
644 let trigger_cost_optional = extra_entry
645 .spell_ability
646 .pay_costs
647 .as_ref()
648 .map(|c| !c.mandatory && !c.is_zero_cost())
649 .unwrap_or(false);
650 let effect_optional_decider =
651 extra_entry.spell_ability.ir.optional_decider_text.is_some();
652 entries.push((
653 PendingTrigger {
654 ability_triggered: Some(
655 crate::trigger::trigger_ability_triggered::build_run_params(
656 trigger,
657 &extra_entry.spell_ability,
658 &event.params,
659 game,
660 ),
661 ),
662 entry: extra_entry,
663 optional: (trigger.optional && !effect_optional_decider)
664 || trigger_cost_optional,
665 decider: host_controller,
666 description: trigger.description.clone(),
667 },
668 host_controller,
669 source_ts,
670 1,
671 trigger.id,
672 ));
673 }
674 }
675 }
676
677 let delayed_insert_at = event_entries_start;
688 let mut delayed_insert_offset = 0usize;
689 let mut fired_indices = Vec::new();
690 for (idx, delayed) in self.delayed_triggers.iter().enumerate() {
691 let TriggerWaiting {
692 params: event_payload,
693 ..
694 } = &event;
695 if delayed.mode != event.mode {
696 continue;
697 }
698 if delayed.mode == TriggerType::Phase
699 && delayed.created_turn == game.turn.turn_number
700 && event_payload.phase == Some(delayed.created_phase)
701 {
702 continue;
703 }
704 let tmp_trigger = delayed.as_trigger(game);
705 if !delayed
706 .trigger_mode
707 .perform_test(&tmp_trigger, event_payload, game)
708 {
709 continue;
710 }
711 let mut sa = build_spell_ability(
712 game,
713 delayed.source_card,
714 &delayed.execute_svar,
715 delayed.controller,
716 );
717 sa.is_trigger = true;
718 sa.trigger_source = Some(delayed.source_card);
719 sa.trigger_source_zone_timestamp =
720 Some(game.card(delayed.source_card).zone_timestamp);
721 sa.trigger_remembered_amount = delayed.remembered_amount;
722 sa.trigger_remembered.extend(
726 delayed
727 .remembered_cards
728 .iter()
729 .copied()
730 .map(crate::event::AbilityValue::Card),
731 );
732 sa.trigger_remembered.extend(
736 delayed
737 .remembered_players
738 .iter()
739 .copied()
740 .map(crate::event::AbilityValue::Player),
741 );
742 if !delayed.remembered_lki_cards.is_empty() {
743 sa.trigger_remembered.extend(
744 delayed
745 .remembered_lki_cards
746 .iter()
747 .copied()
748 .map(crate::event::AbilityValue::Card),
749 );
750 sa.trigger_objects.insert(
751 crate::ability::AbilityKey::RememberedLKI,
752 delayed
753 .remembered_lki_cards
754 .iter()
755 .map(|card_id| card_id.0.to_string())
756 .collect::<Vec<_>>()
757 .join(",")
758 .into(),
759 );
760 }
761
762 let entry = StackEntry {
763 id: 0,
764 spell_ability: sa,
765 is_pending_cast: false,
766 is_creature_spell: false,
767 is_permanent_spell: false,
768 cast_from_zone: None,
769 optional_trigger_decider: None,
770 optional_trigger_description: None,
771 optional_trigger_source_name: None,
772 };
773 let pending = PendingTrigger {
774 ability_triggered: Some(
775 crate::trigger::trigger_ability_triggered::build_run_params(
776 &tmp_trigger,
777 &entry.spell_ability,
778 event_payload,
779 game,
780 ),
781 ),
782 entry,
783 optional: false,
784 decider: delayed.controller,
785 description: String::new(),
786 };
787 let delayed_ts = if delayed.mode == TriggerType::Phase {
788 0
789 } else {
790 game.card(delayed.source_card).zone_timestamp
791 };
792 let delayed_bucket = if delayed.sort_after_active { 2 } else { 0 };
793 let delayed_order = delayed.trigger_order.unwrap_or(0);
794 let extra_delayed =
803 crate::staticability::static_ability_panharmonicon::extra_triggers(
804 game,
805 delayed.source_card,
806 &tmp_trigger,
807 event_payload,
808 );
809 if delayed.sort_after_active {
810 entries.push((
812 pending.clone(),
813 delayed.controller,
814 delayed_ts,
815 delayed_bucket,
816 delayed_order,
817 ));
818 for _ in 0..extra_delayed {
819 entries.push((
820 pending.clone(),
821 delayed.controller,
822 delayed_ts,
823 delayed_bucket,
824 delayed_order,
825 ));
826 }
827 } else {
828 entries.insert(
829 delayed_insert_at + delayed_insert_offset,
830 (
831 pending.clone(),
832 delayed.controller,
833 delayed_ts,
834 delayed_bucket,
835 delayed_order,
836 ),
837 );
838 delayed_insert_offset += 1;
839 for _ in 0..extra_delayed {
840 entries.insert(
841 delayed_insert_at + delayed_insert_offset,
842 (
843 pending.clone(),
844 delayed.controller,
845 delayed_ts,
846 delayed_bucket,
847 delayed_order,
848 ),
849 );
850 delayed_insert_offset += 1;
851 }
852 }
853 fired_indices.push(idx);
854 }
855
856 for idx in fired_indices.into_iter().rev() {
858 self.delayed_triggers.remove(idx);
859 }
860 }
861
862 entries
863 }
864
865 fn build_waiting_trigger(&self, mode: TriggerType, params: RunParams) -> TriggerWaiting {
866 let snapshot_active = matches!(
874 mode,
875 TriggerType::ChangesZone
876 | TriggerType::ChangesZoneAll
877 | TriggerType::Drawn
878 | TriggerType::SpellCast
879 | TriggerType::SpellAbilityCast
880 | TriggerType::SpellCastAll
881 | TriggerType::SpellCastOnce
882 | TriggerType::SpellCastOfType
883 | TriggerType::SpellCopied
884 | TriggerType::SpellCopy
885 | TriggerType::SpellAbilityCopy
886 | TriggerType::SpellCastOrCopy
887 | TriggerType::AbilityCast
888 );
889 let trigger_refs = if snapshot_active {
890 let snap: Vec<(CardId, usize)> = self
891 .active_triggers
892 .iter()
893 .map(|active| (active.card_id, active.trigger_index))
894 .collect();
895 Some(snap)
896 } else {
897 None
898 };
899
900 TriggerWaiting {
901 mode,
902 params,
903 trigger_refs,
904 }
905 }
906
907 fn fire_immediate_delayed_triggers(&mut self, game: &GameState) -> Vec<MatchedTrigger> {
911 let mut entries = Vec::new();
912 let mut fired_indices = Vec::new();
913 for (idx, delayed) in self.delayed_triggers.iter().enumerate() {
914 if delayed.mode != TriggerType::Immediate {
915 continue;
916 }
917 let svar_text = game
919 .card(delayed.source_card)
920 .svars
921 .get(&delayed.execute_svar)
922 .cloned();
923 if let Some(text) = svar_text {
924 let mut sa =
925 build_spell_ability(game, delayed.source_card, &text, delayed.controller);
926 sa.is_trigger = true;
927 sa.trigger_source = Some(delayed.source_card);
928 sa.trigger_source_zone_timestamp =
929 Some(game.card(delayed.source_card).zone_timestamp);
930 if !delayed.remembered_lki_cards.is_empty() {
931 sa.trigger_remembered.extend(
932 delayed
933 .remembered_lki_cards
934 .iter()
935 .copied()
936 .map(crate::event::AbilityValue::Card),
937 );
938 sa.trigger_objects.insert(
939 crate::ability::AbilityKey::RememberedLKI,
940 delayed
941 .remembered_lki_cards
942 .iter()
943 .map(|card_id| card_id.0.to_string())
944 .collect::<Vec<_>>()
945 .join(",")
946 .into(),
947 );
948 }
949
950 let entry = StackEntry {
951 id: 0,
952 spell_ability: sa,
953 is_pending_cast: false,
954 is_creature_spell: false,
955 is_permanent_spell: false,
956 cast_from_zone: None,
957 optional_trigger_decider: None,
958 optional_trigger_description: None,
959 optional_trigger_source_name: None,
960 };
961 let pending = PendingTrigger {
962 ability_triggered: Some(
963 crate::trigger::trigger_ability_triggered::build_run_params(
964 &delayed.as_trigger(game),
965 &entry.spell_ability,
966 &RunParams::default(),
967 game,
968 ),
969 ),
970 entry,
971 optional: false,
972 decider: delayed.controller,
973 description: String::new(),
974 };
975 let ts = if delayed.mode == TriggerType::Phase {
976 0
977 } else {
978 game.card(delayed.source_card).zone_timestamp
979 };
980 let trigger_bucket = if delayed.sort_after_active { 2 } else { 0 };
981 entries.push((
982 pending,
983 delayed.controller,
984 ts,
985 trigger_bucket,
986 delayed.trigger_order.unwrap_or(0),
987 ));
988 fired_indices.push(idx);
989 }
990 }
991 for idx in fired_indices.into_iter().rev() {
992 self.delayed_triggers.remove(idx);
993 }
994 entries
995 }
996
997 pub fn register_delayed_trigger(&mut self, mut delayed: DelayedTrigger) {
1000 self.assign_delayed_trigger_order(&mut delayed);
1001 self.delayed_triggers.push(delayed);
1002 }
1003
1004 pub fn clear_delayed_trigger(&mut self) {
1005 self.delayed_triggers.clear();
1006 self.this_turn_delayed_triggers.clear();
1007 }
1008
1009 pub fn register_this_turn_delayed_trigger(&mut self, mut delayed: DelayedTrigger) {
1010 self.assign_delayed_trigger_order(&mut delayed);
1011 self.this_turn_delayed_triggers.push(delayed.clone());
1012 self.delayed_triggers.push(delayed);
1013 }
1014
1015 pub fn clear_this_turn_delayed_trigger(&mut self) {
1016 if self.this_turn_delayed_triggers.is_empty() {
1017 return;
1018 }
1019 self.delayed_triggers.retain(|d| {
1020 !self.this_turn_delayed_triggers.iter().any(|x| {
1021 x.source_card == d.source_card
1022 && x.controller == d.controller
1023 && x.mode == d.mode
1024 && x.execute_svar == d.execute_svar
1025 })
1026 });
1027 self.this_turn_delayed_triggers.clear();
1028 }
1029
1030 pub fn register_player_defined_delayed_trigger(
1031 &mut self,
1032 player: PlayerId,
1033 mut delayed: DelayedTrigger,
1034 ) {
1035 self.assign_delayed_trigger_order(&mut delayed);
1036 self.player_defined_delayed_triggers.push((player, delayed));
1037 }
1038
1039 pub fn clear_player_defined_delayed_trigger(&mut self) {
1040 self.player_defined_delayed_triggers.clear();
1041 }
1042
1043 pub fn handle_player_defined_del_triggers(&mut self, player: PlayerId) {
1044 let mut to_activate = Vec::new();
1045 self.player_defined_delayed_triggers.retain(|(p, delayed)| {
1046 if *p == player {
1047 to_activate.push(delayed.clone());
1048 false
1049 } else {
1050 true
1051 }
1052 });
1053 for delayed in to_activate {
1054 self.delayed_triggers.push(delayed);
1055 }
1056 }
1057
1058 fn assign_delayed_trigger_order(&mut self, delayed: &mut DelayedTrigger) {
1059 if delayed.trigger_order.is_none() {
1060 delayed.trigger_order = Some(self.next_trigger_id);
1061 self.next_trigger_id += 1;
1062 }
1063 }
1064
1065 pub fn reset_active_triggers(&mut self, game: &GameState) {
1068 self.active_triggers.clear();
1069 for (i, card) in game.cards.iter().enumerate() {
1070 let card_id = CardId(i as u32);
1071 for (trig_idx, _) in card.triggers.iter().enumerate() {
1072 self.register_one_trigger(game, card_id, trig_idx);
1073 }
1074 }
1075 }
1076
1077 pub fn register_active_trigger(&mut self, game: &GameState, card_id: CardId) {
1080 let card = game.card(card_id);
1081 for (trig_idx, _) in card.triggers.iter().enumerate() {
1082 self.register_one_trigger(game, card_id, trig_idx);
1083 }
1084 }
1085
1086 pub fn clear_active_triggers(&mut self) {
1088 self.active_triggers.clear();
1089 }
1090
1091 pub fn register_active_ltb_trigger(&mut self, game: &GameState, card_id: CardId) {
1094 let card = game.card(card_id);
1095 for (trig_idx, trigger) in card.triggers.iter().enumerate() {
1096 if self.looks_back_in_time(trigger) {
1097 self.register_one_trigger(game, card_id, trig_idx);
1098 }
1099 }
1100 }
1101
1102 pub fn register_one_trigger(
1104 &mut self,
1105 game: &GameState,
1106 card_id: CardId,
1107 trigger_index: usize,
1108 ) {
1109 let card = game.card(card_id);
1110 if trigger_index >= card.triggers.len() {
1111 return;
1112 }
1113 let trigger = &card.triggers[trigger_index];
1114 if !trigger.get_active_zone().contains(&card.zone) {
1115 return;
1116 }
1117 if trigger.kind == TriggerType::Always && game.stack.has_state_trigger_id(trigger.id) {
1122 return;
1123 }
1124 let already_registered = self
1125 .active_triggers
1126 .iter()
1127 .any(|at| at.card_id == card_id && at.trigger_index == trigger_index);
1128 if !already_registered {
1129 if std::env::var("FORGE_TRIGGER_TRACE").is_ok() {
1130 if let Some(trigger) = game.card(card_id).triggers.get(trigger_index) {
1131 if trigger.kind == TriggerType::BecomesTarget {
1132 eprintln!(
1133 "[trigger-trace] REGISTER active BecomesTarget trigger host={}#{:?} idx={} desc={} zones={:?}",
1134 game.card(card_id).card_name,
1135 card_id,
1136 trigger_index,
1137 trigger.description,
1138 trigger.get_active_zone()
1139 );
1140 }
1141 }
1142 }
1143 self.active_triggers.push(ActiveTrigger {
1144 card_id,
1145 trigger_index,
1146 });
1147 }
1148 }
1149
1150 pub fn unregister_active_triggers(&mut self, card_id: CardId) {
1152 self.active_triggers.retain(|at| at.card_id != card_id);
1153 }
1154
1155 fn looks_back_in_time(&self, trigger: &Trigger) -> bool {
1156 if matches!(
1157 trigger.kind,
1158 TriggerType::Exploited
1159 | TriggerType::Destroyed
1160 | TriggerType::Sacrificed
1161 | TriggerType::SacrificedOnce
1162 ) {
1163 return true;
1164 }
1165 if matches!(
1166 trigger.kind,
1167 TriggerType::ChangesZone | TriggerType::ChangesZoneAll
1168 ) {
1169 return trigger
1170 .ir
1171 .origin_zones
1172 .contains(&forge_foundation::ZoneType::Battlefield)
1173 || trigger.ir.destination_zones.iter().any(|zone| {
1174 matches!(
1175 zone,
1176 forge_foundation::ZoneType::Library | forge_foundation::ZoneType::Hand
1177 )
1178 });
1179 }
1180 false
1181 }
1182
1183 fn ltb_trigger_refs_for_event(
1184 &self,
1185 game: &GameState,
1186 event: &TriggerWaiting,
1187 ) -> Vec<(CardId, usize)> {
1188 let TriggerWaiting {
1189 params: event_payload,
1190 ..
1191 } = event;
1192 match event.mode {
1193 TriggerType::ChangesZone => {
1194 let destination = event_payload.destination;
1195 if event_payload.origin != Some(forge_foundation::ZoneType::Battlefield)
1196 && !matches!(
1197 destination,
1198 Some(
1199 forge_foundation::ZoneType::Library | forge_foundation::ZoneType::Hand
1200 )
1201 )
1202 {
1203 return Vec::new();
1204 }
1205 let Some(card_id) = event_payload.card_lki.or(event_payload.card) else {
1206 return Vec::new();
1207 };
1208 let mut refs: Vec<(CardId, usize)> = game
1209 .card(card_id)
1210 .triggers
1211 .iter()
1212 .enumerate()
1213 .filter_map(|(trigger_index, trigger)| {
1214 self.looks_back_in_time(trigger)
1215 .then_some((card_id, trigger_index))
1216 })
1217 .collect();
1218
1219 if event_payload.origin == Some(forge_foundation::ZoneType::Battlefield) {
1227 for <b_id in &game.pre_sba_battlefield {
1228 if ltb_id == card_id {
1229 continue;
1230 }
1231 let ltb_card = game.card(ltb_id);
1232 if ltb_card.zone == forge_foundation::ZoneType::Battlefield {
1233 continue;
1235 }
1236 for (trigger_index, trigger) in ltb_card.triggers.iter().enumerate() {
1237 if self.looks_back_in_time(trigger) {
1238 refs.push((ltb_id, trigger_index));
1239 }
1240 }
1241 }
1242 }
1243
1244 refs
1245 }
1246 TriggerType::ChangesZoneAll => event
1247 .params
1248 .change_zone_table
1249 .as_ref()
1250 .map(|table| {
1251 table
1252 .last_state_battlefield()
1253 .iter()
1254 .flat_map(|&card_id| {
1255 game.card(card_id).triggers.iter().enumerate().filter_map(
1256 move |(trigger_index, trigger)| {
1257 self.looks_back_in_time(trigger)
1258 .then_some((card_id, trigger_index))
1259 },
1260 )
1261 })
1262 .collect()
1263 })
1264 .unwrap_or_default(),
1265 _ => Vec::new(),
1266 }
1267 }
1268
1269 pub fn clear_waiting_triggers(&mut self) {
1270 self.waiting_triggers.clear();
1271 self.pre_matched_triggers.clear();
1272 }
1273
1274 pub fn on_player_lost(&mut self, player: PlayerId) {
1275 self.delayed_triggers.retain(|d| d.controller != player);
1276 self.this_turn_delayed_triggers
1277 .retain(|d| d.controller != player);
1278 self.player_defined_delayed_triggers
1279 .retain(|(p, _)| *p != player);
1280 }
1281
1282 pub fn has_source_chapter_pending(&self, game: &GameState, source: CardId) -> bool {
1283 if self
1284 .pre_matched_triggers
1285 .iter()
1286 .any(|(pending, _, _, _, _)| {
1287 pending.entry.spell_ability.is_trigger
1288 && pending.entry.spell_ability.source == Some(source)
1289 && pending
1290 .entry
1291 .spell_ability
1292 .source_trigger_id
1293 .is_some_and(|trigger_id| {
1294 game.card(source)
1295 .triggers
1296 .iter()
1297 .any(|trigger| trigger.id == trigger_id && trigger.is_chapter())
1298 })
1299 })
1300 {
1301 return true;
1302 }
1303
1304 for event in &self.waiting_triggers {
1305 let refs: Vec<(CardId, usize)> = event.trigger_refs.clone().unwrap_or_else(|| {
1306 self.active_triggers
1307 .iter()
1308 .map(|active| (active.card_id, active.trigger_index))
1309 .collect()
1310 });
1311 for (card_id, trigger_index) in refs {
1312 if card_id != source {
1313 continue;
1314 }
1315 let Some(trigger) = game.card(card_id).triggers.get(trigger_index) else {
1316 continue;
1317 };
1318 if !trigger.is_chapter() {
1319 continue;
1320 }
1321 if self.can_run_trigger(
1322 game,
1323 card_id,
1324 trigger_index,
1325 game.card(card_id).controller,
1326 &event.mode,
1327 &event.params,
1328 ) {
1329 return true;
1330 }
1331 }
1332 }
1333
1334 false
1335 }
1336
1337 fn is_trigger_disabled_by_static(
1341 game: &GameState,
1342 host_card: CardId,
1343 trigger_index: usize,
1344 params: &RunParams,
1345 ) -> bool {
1346 let trigger = &game.card(host_card).triggers[trigger_index];
1347
1348 if trigger.kind != TriggerType::ChangesZone {
1350 return false;
1351 }
1352
1353 let cause_card_id = match params.card {
1355 Some(cid) => cid,
1356 None => return false,
1357 };
1358
1359 let is_ltb = params.origin == Some(ZoneType::Battlefield);
1368 for card in game.cards.iter() {
1369 let on_battlefield = card.zone == ZoneType::Battlefield;
1370 let was_on_battlefield = is_ltb && game.pre_sba_battlefield.contains(&card.id);
1371 if !on_battlefield && !was_on_battlefield {
1372 continue;
1373 }
1374 for sa in &card.static_abilities {
1375 if !sa.check_mode(&crate::staticability::StaticMode::DisableTriggers) {
1376 continue;
1377 }
1378
1379 if let Some(valid_cause) = sa.ir.valid_cause.as_ref() {
1381 let context = valid_filter::MatchContext::from_source(card)
1382 .with_game(game)
1383 .with_triggering(cause_card_id.into(), params.player);
1384 if !valid_filter::matches_valid_card_selector_with_context(
1385 valid_cause,
1386 game.card(cause_card_id),
1387 context,
1388 ) {
1389 continue;
1390 }
1391 }
1392
1393 if let Some(valid_mode) = sa.ir.valid_mode.as_deref() {
1395 let modes: Vec<&str> = valid_mode.split(',').collect();
1396 if !modes.contains(&"ChangesZone") {
1397 continue;
1398 }
1399 }
1400
1401 if !sa.ir.origin_zones.is_empty()
1403 && !params
1404 .origin
1405 .is_some_and(|zone| sa.ir.origin_zones.contains(&zone))
1406 {
1407 continue;
1408 }
1409
1410 if !sa.ir.destination_zones.is_empty()
1412 && !params
1413 .destination
1414 .is_some_and(|zone| sa.ir.destination_zones.contains(&zone))
1415 {
1416 continue;
1417 }
1418
1419 return true;
1421 }
1422 }
1423 false
1424 }
1425
1426 fn can_run_trigger(
1429 &self,
1430 game: &GameState,
1431 host_card: CardId,
1432 trigger_index: usize,
1433 host_controller: PlayerId,
1434 mode: &TriggerType,
1435 params: &RunParams,
1436 ) -> bool {
1437 let _perf_scope =
1438 crate::perf::ParamsLookupScopeGuard::enter(crate::perf::ParamsLookupScope::Trigger);
1439 let card = game.card(host_card);
1440 if trigger_index >= card.triggers.len() {
1441 return false;
1442 }
1443 let trigger = &card.triggers[trigger_index];
1444
1445 let trigger_type = trigger.mode.trigger_type();
1447
1448 let normalize = |t: TriggerType| -> TriggerType {
1449 match t {
1450 TriggerType::DungeonCompleted => TriggerType::CompletedDungeon,
1451 TriggerType::RoomEntered => TriggerType::EnteredRoom,
1452 TriggerType::Airbend
1453 | TriggerType::Earthbend
1454 | TriggerType::Firebend
1455 | TriggerType::Waterbend
1456 | TriggerType::ElementalBend => TriggerType::Elementalbend,
1457 TriggerType::TakesInitiative => TriggerType::TakeInitiative,
1458 TriggerType::PhaseIn => TriggerType::PhasedIn,
1459 TriggerType::PhaseOut => TriggerType::PhasedOut,
1460 TriggerType::PlaneswalkedFrom | TriggerType::PlaneswalkedTo => {
1461 TriggerType::Planeswalk
1462 }
1463 TriggerType::CrankContraption => TriggerType::CrankAdvanced,
1464 TriggerType::Explores => TriggerType::Explored,
1465 TriggerType::Unattach => TriggerType::Unattached,
1466 TriggerType::Saddled | TriggerType::Stationed => TriggerType::Crewed,
1467 other => other,
1468 }
1469 };
1470 let mode_matches = normalize(trigger_type) == normalize(*mode);
1471 if !mode_matches {
1472 return false;
1473 }
1474
1475 if self.is_trigger_suppressed(*mode) {
1477 return false;
1478 }
1479
1480 if !trigger.phases_check(game, host_card, params.phase) {
1482 return false;
1483 }
1484 if !trigger.requirements_check(game, host_card) {
1485 return false;
1486 }
1487 if !trigger.check_activation_limit(game, host_card) {
1488 return false;
1489 }
1490
1491 if matches!(
1494 *mode,
1495 TriggerType::ChangesZone | TriggerType::ChangesZoneAll
1496 ) && Self::is_trigger_disabled_by_static(game, host_card, trigger_index, params)
1497 {
1498 return false;
1499 }
1500
1501 let zone_for_active_check = if *mode == TriggerType::ChangesZone
1505 && params.card == Some(host_card)
1506 && params.origin == Some(ZoneType::Battlefield)
1507 && params.destination != Some(ZoneType::Battlefield)
1508 {
1509 ZoneType::Battlefield
1511 } else if *mode == TriggerType::ChangesZone
1512 && params.card == Some(host_card)
1513 && params.destination == Some(ZoneType::Battlefield)
1514 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1515 && card.zone != ZoneType::Battlefield
1516 {
1517 ZoneType::Battlefield
1520 } else if *mode == TriggerType::ChangesZone
1521 && params.origin == Some(ZoneType::Battlefield)
1522 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1523 && card.zone != ZoneType::Battlefield
1524 && self.looks_back_in_time(trigger)
1525 && game.pre_sba_battlefield.contains(&host_card)
1526 {
1527 ZoneType::Battlefield
1533 } else if (*mode == TriggerType::DamageDone || *mode == TriggerType::DamageDoneOnce)
1534 && params.damage_target_card == Some(host_card)
1535 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1536 && card.zone != ZoneType::Battlefield
1537 {
1538 ZoneType::Battlefield
1543 } else if *mode == TriggerType::Exploited
1544 && params.card == Some(host_card)
1545 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1546 && card.zone != ZoneType::Battlefield
1547 {
1548 ZoneType::Battlefield
1551 } else if *mode == TriggerType::Sacrificed
1552 && params.card == Some(host_card)
1553 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1554 && card.zone != ZoneType::Battlefield
1555 {
1556 ZoneType::Battlefield
1559 } else {
1560 card.zone
1561 };
1562 if !trigger.get_active_zone().contains(&zone_for_active_check) {
1563 return false;
1564 }
1565
1566 if card.phased_out {
1569 return false;
1570 }
1571
1572 if !trigger.mode.perform_test(trigger, params, game) {
1574 return false;
1575 }
1576 if !trigger.meets_requirements_on_triggered_objects(game, params, host_card) {
1577 return false;
1578 }
1579
1580 if let Some(cond) = trigger.ir.activator_this_turn_cast.as_deref() {
1585 let caster = params.spell_controller.unwrap_or(host_controller);
1586 let count = game.player(caster).spells_cast_this_turn;
1587 if !compare_expr(count, cond.trim()) {
1588 return false;
1589 }
1590 }
1591
1592 true
1593 }
1594
1595 pub fn suppress_mode(&mut self, mode: TriggerType) {
1596 self.suppressed_modes.insert(mode);
1597 }
1598
1599 pub fn set_suppress_all_triggers(&mut self, suppress: bool) {
1600 self.all_suppressed = suppress;
1601 }
1602
1603 pub fn get_active_trigger(
1606 &self,
1607 game: &GameState,
1608 mode: TriggerType,
1609 params: &RunParams,
1610 ) -> Vec<Trigger> {
1611 let mut result = Vec::new();
1612 for at in &self.active_triggers {
1613 let card = game.card(at.card_id);
1614 if at.trigger_index >= card.triggers.len() {
1615 continue;
1616 }
1617 let host_controller = card.controller;
1618 if self.can_run_trigger(
1619 game,
1620 at.card_id,
1621 at.trigger_index,
1622 host_controller,
1623 &mode,
1624 params,
1625 ) {
1626 result.push(card.triggers[at.trigger_index].clone());
1627 }
1628 }
1629 result
1630 }
1631
1632 pub fn is_trigger_suppressed(&self, mode: TriggerType) -> bool {
1633 self.all_suppressed || self.suppressed_modes.contains(&mode)
1634 }
1635
1636 pub fn clear_suppression(&mut self, mode: TriggerType) {
1637 self.suppressed_modes.remove(&mode);
1638 }
1639}
1640
1641impl Default for TriggerHandler {
1642 fn default() -> Self {
1643 Self::new()
1644 }
1645}
1646
1647#[cfg(test)]
1648mod tests {
1649 use super::*;
1650 use crate::card::Card;
1651 use crate::event::RunParams;
1652 use crate::ids::PlayerId;
1653 use crate::trigger::TriggerType;
1654 use forge_carddb::parse_card_script;
1655 use forge_foundation::PhaseType;
1656
1657 #[test]
1658 fn maralen_style_draw_phase_trigger_is_active() {
1659 let rules = parse_card_script(
1660 "Name:Maralen of the Mornsong\nManaCost:1 B B\nTypes:Legendary Creature Elf Wizard\nPT:2/3\nT:Mode$ Phase | Phase$ Draw | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigDrain | TriggerDescription$ At the beginning of each player's draw step, that player loses 3 life, searches their library for a card, puts it into their hand, then shuffles.\nSVar:TrigDrain:DB$ LoseLife | Defined$ TriggeredPlayer | LifeAmount$ 3",
1661 )
1662 .expect("card script should parse");
1663
1664 let mut game = GameState::new(&["Alice", "Bob"], 20);
1665 let mut maralen = Card::from_rules(&rules, PlayerId(0));
1666 maralen.id = crate::ids::CardId(0);
1667 maralen.zone = forge_foundation::ZoneType::Battlefield;
1668 game.cards.push(maralen);
1669 game.add_card_to_zone(
1670 forge_foundation::ZoneType::Battlefield,
1671 PlayerId(0),
1672 crate::ids::CardId(0),
1673 );
1674
1675 let mut handler = TriggerHandler::new();
1676 handler.register_active_trigger(&game, crate::ids::CardId(0));
1677
1678 let active = handler.get_active_trigger(
1679 &game,
1680 TriggerType::Phase,
1681 &RunParams {
1682 phase: Some(PhaseType::Draw),
1683 player: Some(PlayerId(0)),
1684 ..Default::default()
1685 },
1686 );
1687
1688 assert_eq!(game.card(crate::ids::CardId(0)).triggers.len(), 1);
1689 assert_eq!(active.len(), 1);
1690
1691 let pending = handler.run_trigger_with_game(
1692 &game,
1693 TriggerType::Phase,
1694 RunParams {
1695 phase: Some(PhaseType::Draw),
1696 player: Some(PlayerId(0)),
1697 ..Default::default()
1698 },
1699 false,
1700 );
1701 assert_eq!(pending.len(), 1);
1702 assert_eq!(
1703 pending[0]
1704 .entry
1705 .spell_ability
1706 .get_triggering_player(crate::ability::AbilityKey::TriggeredPlayer),
1707 Some(PlayerId(0))
1708 );
1709 }
1710}