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={} | api={} | source={}",
440 trigger_mode, trigger_api, source_name
441 )
442 } else {
443 format!(
444 "Trigger fired: mode={} | api={} | source={} | {}",
445 trigger_mode, trigger_api, source_name, pt.description
446 )
447 };
448 let mut event = GameLogEvent::stack(trigger_msg)
449 .with_player(pt.entry.spell_ability.activating_player);
450 if let Some(source_id) = pt.entry.spell_ability.source {
451 event = event.with_source_card(source_id);
452 }
453 if let Some(target_id) = pt.entry.spell_ability.target_chosen.target_card {
454 event = event.with_target_card(target_id);
455 }
456 notify_all_agents(agents, event);
457
458 let is_optional = pt.optional;
459 let pushed_entry = pt.entry.clone();
460 game.stack.push(pt.entry);
461 if pushed_entry.spell_ability.is_trigger {
462 let source_card = pushed_entry.spell_ability.source;
463 self.run_trigger(
464 TriggerType::SpellAbilityCast,
465 RunParams {
466 card: source_card,
467 spell_card: source_card,
468 player: Some(pushed_entry.spell_ability.activating_player),
469 activator: Some(pushed_entry.spell_ability.activating_player),
470 spell_controller: Some(pushed_entry.spell_ability.activating_player),
471 spell_ability: Some(pushed_entry.spell_ability.clone()),
472 source_sa: Some(pushed_entry.spell_ability.clone()),
473 cause: Some(pushed_entry.spell_ability.clone()),
474 cause_card: source_card,
475 ..Default::default()
476 },
477 true,
478 );
479 if let Some(mut ability_triggered) = pt.ability_triggered.clone() {
480 ability_triggered.spell_ability = Some(pushed_entry.spell_ability.clone());
481 ability_triggered.source_sa = Some(pushed_entry.spell_ability.clone());
482 if ability_triggered.cause_card.is_none() {
483 ability_triggered.cause_card = source_card;
484 }
485 self.run_trigger(TriggerType::AbilityTriggered, ability_triggered, false);
486 }
487 }
488 push_logs.push(TriggerPushLog {
489 source_name,
490 player_name,
491 optional: is_optional,
492 trigger_api,
493 });
494 }
495 push_logs
496 }
497
498 fn match_waiting_triggers(&mut self, game: &GameState) -> Vec<MatchedTrigger> {
502 let waiting = std::mem::take(&mut self.waiting_triggers);
503 let mut entries: Vec<MatchedTrigger> = Vec::new();
504
505 for event in &waiting {
506 let mut trigger_refs: Vec<(CardId, usize, usize)> =
507 if let Some(stored_refs) = &event.trigger_refs {
508 stored_refs
509 .iter()
510 .enumerate()
511 .map(|(idx, (card_id, trigger_index))| (*card_id, *trigger_index, idx))
512 .collect()
513 } else {
514 self.active_triggers
515 .iter()
516 .enumerate()
517 .map(|(idx, active)| (active.card_id, active.trigger_index, idx))
518 .collect()
519 };
520 if event.trigger_refs.is_none() {
521 for (card_id, trigger_index) in self.ltb_trigger_refs_for_event(game, event) {
522 if !trigger_refs.iter().any(|(existing_card, existing_idx, _)| {
523 *existing_card == card_id && *existing_idx == trigger_index
524 }) {
525 trigger_refs.push((card_id, trigger_index, usize::MAX));
526 }
527 }
528 }
529 trigger_refs.sort_by_key(|&(card_id, trigger_index, idx)| {
530 let card = game.card(card_id);
531 let is_static = card
532 .triggers
533 .get(trigger_index)
534 .map(|trigger| !trigger.is_static())
535 .unwrap_or(true);
536 (is_static, idx)
537 });
538
539 let event_entries_start = entries.len();
540
541 for (card_id, trigger_index, _) in trigger_refs {
543 let card = game.card(card_id);
544 if trigger_index >= card.triggers.len() {
545 continue;
546 }
547 let trigger = &card.triggers[trigger_index];
548 let host_controller = card.controller;
549 if crate::staticability::static_ability_disable_triggers::is_disabled(
550 game,
551 card_id,
552 trigger,
553 &event.params,
554 ) {
555 continue;
556 }
557
558 let can_run = self.can_run_trigger(
559 game,
560 card_id,
561 trigger_index,
562 host_controller,
563 &event.mode,
564 &event.params,
565 );
566 if can_run {
567 let sa = trigger.build_triggered_spell_ability(
568 game,
569 card_id,
570 host_controller,
571 trigger_index,
572 &event.params,
573 );
574
575 let entry = StackEntry {
576 id: 0,
577 spell_ability: sa,
578 is_pending_cast: false,
579 is_creature_spell: false,
580 is_permanent_spell: false,
581 cast_from_zone: None,
582 optional_trigger_decider: None,
583 optional_trigger_description: None,
584 optional_trigger_source_name: None,
585 };
586 let trigger_cost_optional = entry
591 .spell_ability
592 .pay_costs
593 .as_ref()
594 .map(|c| !c.mandatory && !c.is_zero_cost())
595 .unwrap_or(false);
596 let effect_optional_decider =
597 entry.spell_ability.ir.optional_decider_text.is_some();
598 let description_lower = trigger.description.to_ascii_lowercase();
599 let description_implies_optional = description_lower.starts_with("you may")
600 && !entry.spell_ability.ir.optional
601 && !effect_optional_decider;
602 let pending = PendingTrigger {
603 ability_triggered: Some(
604 crate::trigger::trigger_ability_triggered::build_run_params(
605 trigger,
606 &entry.spell_ability,
607 &event.params,
608 game,
609 ),
610 ),
611 entry,
612 optional: (trigger.optional && !effect_optional_decider)
613 || trigger_cost_optional
614 || description_implies_optional,
615 decider: host_controller,
616 description: trigger.description.clone(),
617 };
618 let source_ts = card.zone_timestamp;
619 entries.push((pending, host_controller, source_ts, 1, trigger.id));
620 let extra = crate::staticability::static_ability_panharmonicon::extra_triggers(
621 game,
622 card_id,
623 trigger,
624 &event.params,
625 );
626 for _ in 0..extra {
627 let sa2 = trigger.build_triggered_spell_ability(
628 game,
629 card_id,
630 host_controller,
631 trigger_index,
632 &event.params,
633 );
634 let extra_entry = StackEntry {
635 id: 0,
636 spell_ability: sa2,
637 is_pending_cast: false,
638 is_creature_spell: false,
639 is_permanent_spell: false,
640 cast_from_zone: None,
641 optional_trigger_decider: None,
642 optional_trigger_description: None,
643 optional_trigger_source_name: None,
644 };
645 let trigger_cost_optional = extra_entry
646 .spell_ability
647 .pay_costs
648 .as_ref()
649 .map(|c| !c.mandatory && !c.is_zero_cost())
650 .unwrap_or(false);
651 let effect_optional_decider =
652 extra_entry.spell_ability.ir.optional_decider_text.is_some();
653 entries.push((
654 PendingTrigger {
655 ability_triggered: Some(
656 crate::trigger::trigger_ability_triggered::build_run_params(
657 trigger,
658 &extra_entry.spell_ability,
659 &event.params,
660 game,
661 ),
662 ),
663 entry: extra_entry,
664 optional: (trigger.optional && !effect_optional_decider)
665 || trigger_cost_optional,
666 decider: host_controller,
667 description: trigger.description.clone(),
668 },
669 host_controller,
670 source_ts,
671 1,
672 trigger.id,
673 ));
674 }
675 }
676 }
677
678 let delayed_insert_at = event_entries_start;
689 let mut delayed_insert_offset = 0usize;
690 let mut fired_indices = Vec::new();
691 for (idx, delayed) in self.delayed_triggers.iter().enumerate() {
692 let TriggerWaiting {
693 params: event_payload,
694 ..
695 } = &event;
696 if delayed.mode != event.mode {
697 continue;
698 }
699 if delayed.mode == TriggerType::Phase
700 && delayed.created_turn == game.turn.turn_number
701 && event_payload.phase == Some(delayed.created_phase)
702 {
703 continue;
704 }
705 let tmp_trigger = delayed.as_trigger(game);
706 if !delayed
707 .trigger_mode
708 .perform_test(&tmp_trigger, event_payload, game)
709 {
710 continue;
711 }
712 let mut sa = build_spell_ability(
713 game,
714 delayed.source_card,
715 &delayed.execute_svar,
716 delayed.controller,
717 );
718 sa.is_trigger = true;
719 sa.trigger_source = Some(delayed.source_card);
720 sa.trigger_source_zone_timestamp =
721 Some(game.card(delayed.source_card).zone_timestamp);
722 sa.trigger_remembered_amount = delayed.remembered_amount;
723 sa.trigger_remembered.extend(
727 delayed
728 .remembered_cards
729 .iter()
730 .copied()
731 .map(crate::event::AbilityValue::Card),
732 );
733 sa.trigger_remembered.extend(
737 delayed
738 .remembered_players
739 .iter()
740 .copied()
741 .map(crate::event::AbilityValue::Player),
742 );
743 if !delayed.remembered_lki_cards.is_empty() {
744 sa.trigger_remembered.extend(
745 delayed
746 .remembered_lki_cards
747 .iter()
748 .copied()
749 .map(crate::event::AbilityValue::Card),
750 );
751 sa.trigger_objects.insert(
752 crate::ability::AbilityKey::RememberedLKI,
753 delayed
754 .remembered_lki_cards
755 .iter()
756 .map(|card_id| card_id.0.to_string())
757 .collect::<Vec<_>>()
758 .join(",")
759 .into(),
760 );
761 }
762
763 let entry = StackEntry {
764 id: 0,
765 spell_ability: sa,
766 is_pending_cast: false,
767 is_creature_spell: false,
768 is_permanent_spell: false,
769 cast_from_zone: None,
770 optional_trigger_decider: None,
771 optional_trigger_description: None,
772 optional_trigger_source_name: None,
773 };
774 let pending = PendingTrigger {
775 ability_triggered: Some(
776 crate::trigger::trigger_ability_triggered::build_run_params(
777 &tmp_trigger,
778 &entry.spell_ability,
779 event_payload,
780 game,
781 ),
782 ),
783 entry,
784 optional: false,
785 decider: delayed.controller,
786 description: String::new(),
787 };
788 let delayed_ts = if delayed.mode == TriggerType::Phase {
789 0
790 } else {
791 game.card(delayed.source_card).zone_timestamp
792 };
793 let delayed_bucket = if delayed.sort_after_active { 2 } else { 0 };
794 let delayed_order = delayed.trigger_order.unwrap_or(0);
795 let extra_delayed =
804 crate::staticability::static_ability_panharmonicon::extra_triggers(
805 game,
806 delayed.source_card,
807 &tmp_trigger,
808 event_payload,
809 );
810 if delayed.sort_after_active {
811 entries.push((
813 pending.clone(),
814 delayed.controller,
815 delayed_ts,
816 delayed_bucket,
817 delayed_order,
818 ));
819 for _ in 0..extra_delayed {
820 entries.push((
821 pending.clone(),
822 delayed.controller,
823 delayed_ts,
824 delayed_bucket,
825 delayed_order,
826 ));
827 }
828 } else {
829 entries.insert(
830 delayed_insert_at + delayed_insert_offset,
831 (
832 pending.clone(),
833 delayed.controller,
834 delayed_ts,
835 delayed_bucket,
836 delayed_order,
837 ),
838 );
839 delayed_insert_offset += 1;
840 for _ in 0..extra_delayed {
841 entries.insert(
842 delayed_insert_at + delayed_insert_offset,
843 (
844 pending.clone(),
845 delayed.controller,
846 delayed_ts,
847 delayed_bucket,
848 delayed_order,
849 ),
850 );
851 delayed_insert_offset += 1;
852 }
853 }
854 fired_indices.push(idx);
855 }
856
857 for idx in fired_indices.into_iter().rev() {
859 self.delayed_triggers.remove(idx);
860 }
861 }
862
863 entries
864 }
865
866 fn build_waiting_trigger(&self, mode: TriggerType, params: RunParams) -> TriggerWaiting {
867 let snapshot_active = matches!(
875 mode,
876 TriggerType::ChangesZone
877 | TriggerType::ChangesZoneAll
878 | TriggerType::Drawn
879 | TriggerType::SpellCast
880 | TriggerType::SpellAbilityCast
881 | TriggerType::SpellCastAll
882 | TriggerType::SpellCastOnce
883 | TriggerType::SpellCastOfType
884 | TriggerType::SpellCopied
885 | TriggerType::SpellCopy
886 | TriggerType::SpellAbilityCopy
887 | TriggerType::SpellCastOrCopy
888 | TriggerType::AbilityCast
889 );
890 let trigger_refs = if snapshot_active {
891 let snap: Vec<(CardId, usize)> = self
892 .active_triggers
893 .iter()
894 .map(|active| (active.card_id, active.trigger_index))
895 .collect();
896 Some(snap)
897 } else {
898 None
899 };
900
901 TriggerWaiting {
902 mode,
903 params,
904 trigger_refs,
905 }
906 }
907
908 fn fire_immediate_delayed_triggers(&mut self, game: &GameState) -> Vec<MatchedTrigger> {
912 let mut entries = Vec::new();
913 let mut fired_indices = Vec::new();
914 for (idx, delayed) in self.delayed_triggers.iter().enumerate() {
915 if delayed.mode != TriggerType::Immediate {
916 continue;
917 }
918 let svar_text = game
920 .card(delayed.source_card)
921 .svars
922 .get(&delayed.execute_svar)
923 .cloned();
924 if let Some(text) = svar_text {
925 let mut sa =
926 build_spell_ability(game, delayed.source_card, &text, delayed.controller);
927 sa.is_trigger = true;
928 sa.trigger_source = Some(delayed.source_card);
929 sa.trigger_source_zone_timestamp =
930 Some(game.card(delayed.source_card).zone_timestamp);
931 if !delayed.remembered_lki_cards.is_empty() {
932 sa.trigger_remembered.extend(
933 delayed
934 .remembered_lki_cards
935 .iter()
936 .copied()
937 .map(crate::event::AbilityValue::Card),
938 );
939 sa.trigger_objects.insert(
940 crate::ability::AbilityKey::RememberedLKI,
941 delayed
942 .remembered_lki_cards
943 .iter()
944 .map(|card_id| card_id.0.to_string())
945 .collect::<Vec<_>>()
946 .join(",")
947 .into(),
948 );
949 }
950
951 let entry = StackEntry {
952 id: 0,
953 spell_ability: sa,
954 is_pending_cast: false,
955 is_creature_spell: false,
956 is_permanent_spell: false,
957 cast_from_zone: None,
958 optional_trigger_decider: None,
959 optional_trigger_description: None,
960 optional_trigger_source_name: None,
961 };
962 let pending = PendingTrigger {
963 ability_triggered: Some(
964 crate::trigger::trigger_ability_triggered::build_run_params(
965 &delayed.as_trigger(game),
966 &entry.spell_ability,
967 &RunParams::default(),
968 game,
969 ),
970 ),
971 entry,
972 optional: false,
973 decider: delayed.controller,
974 description: String::new(),
975 };
976 let ts = if delayed.mode == TriggerType::Phase {
977 0
978 } else {
979 game.card(delayed.source_card).zone_timestamp
980 };
981 let trigger_bucket = if delayed.sort_after_active { 2 } else { 0 };
982 entries.push((
983 pending,
984 delayed.controller,
985 ts,
986 trigger_bucket,
987 delayed.trigger_order.unwrap_or(0),
988 ));
989 fired_indices.push(idx);
990 }
991 }
992 for idx in fired_indices.into_iter().rev() {
993 self.delayed_triggers.remove(idx);
994 }
995 entries
996 }
997
998 pub fn register_delayed_trigger(&mut self, mut delayed: DelayedTrigger) {
1001 self.assign_delayed_trigger_order(&mut delayed);
1002 self.delayed_triggers.push(delayed);
1003 }
1004
1005 pub fn clear_delayed_trigger(&mut self) {
1006 self.delayed_triggers.clear();
1007 self.this_turn_delayed_triggers.clear();
1008 }
1009
1010 pub fn register_this_turn_delayed_trigger(&mut self, mut delayed: DelayedTrigger) {
1011 self.assign_delayed_trigger_order(&mut delayed);
1012 self.this_turn_delayed_triggers.push(delayed.clone());
1013 self.delayed_triggers.push(delayed);
1014 }
1015
1016 pub fn clear_this_turn_delayed_trigger(&mut self) {
1017 if self.this_turn_delayed_triggers.is_empty() {
1018 return;
1019 }
1020 self.delayed_triggers.retain(|d| {
1021 !self.this_turn_delayed_triggers.iter().any(|x| {
1022 x.source_card == d.source_card
1023 && x.controller == d.controller
1024 && x.mode == d.mode
1025 && x.execute_svar == d.execute_svar
1026 })
1027 });
1028 self.this_turn_delayed_triggers.clear();
1029 }
1030
1031 pub fn register_player_defined_delayed_trigger(
1032 &mut self,
1033 player: PlayerId,
1034 mut delayed: DelayedTrigger,
1035 ) {
1036 self.assign_delayed_trigger_order(&mut delayed);
1037 self.player_defined_delayed_triggers.push((player, delayed));
1038 }
1039
1040 pub fn clear_player_defined_delayed_trigger(&mut self) {
1041 self.player_defined_delayed_triggers.clear();
1042 }
1043
1044 pub fn handle_player_defined_del_triggers(&mut self, player: PlayerId) {
1045 let mut to_activate = Vec::new();
1046 self.player_defined_delayed_triggers.retain(|(p, delayed)| {
1047 if *p == player {
1048 to_activate.push(delayed.clone());
1049 false
1050 } else {
1051 true
1052 }
1053 });
1054 for delayed in to_activate {
1055 self.delayed_triggers.push(delayed);
1056 }
1057 }
1058
1059 fn assign_delayed_trigger_order(&mut self, delayed: &mut DelayedTrigger) {
1060 if delayed.trigger_order.is_none() {
1061 delayed.trigger_order = Some(self.next_trigger_id);
1062 self.next_trigger_id += 1;
1063 }
1064 }
1065
1066 pub fn reset_active_triggers(&mut self, game: &GameState) {
1069 self.active_triggers.clear();
1070 for (i, card) in game.cards.iter().enumerate() {
1071 let card_id = CardId(i as u32);
1072 for (trig_idx, _) in card.triggers.iter().enumerate() {
1073 self.register_one_trigger(game, card_id, trig_idx);
1074 }
1075 }
1076 }
1077
1078 pub fn register_active_trigger(&mut self, game: &GameState, card_id: CardId) {
1081 let card = game.card(card_id);
1082 for (trig_idx, _) in card.triggers.iter().enumerate() {
1083 self.register_one_trigger(game, card_id, trig_idx);
1084 }
1085 }
1086
1087 pub fn clear_active_triggers(&mut self) {
1089 self.active_triggers.clear();
1090 }
1091
1092 pub fn register_active_ltb_trigger(&mut self, game: &GameState, card_id: CardId) {
1095 let card = game.card(card_id);
1096 for (trig_idx, trigger) in card.triggers.iter().enumerate() {
1097 if self.looks_back_in_time(trigger) {
1098 self.register_one_trigger(game, card_id, trig_idx);
1099 }
1100 }
1101 }
1102
1103 pub fn register_one_trigger(
1105 &mut self,
1106 game: &GameState,
1107 card_id: CardId,
1108 trigger_index: usize,
1109 ) {
1110 let card = game.card(card_id);
1111 if trigger_index >= card.triggers.len() {
1112 return;
1113 }
1114 let trigger = &card.triggers[trigger_index];
1115 if !trigger.get_active_zone().contains(&card.zone) {
1116 return;
1117 }
1118 if trigger.kind == TriggerType::Always && game.stack.has_state_trigger_id(trigger.id) {
1123 return;
1124 }
1125 let already_registered = self
1126 .active_triggers
1127 .iter()
1128 .any(|at| at.card_id == card_id && at.trigger_index == trigger_index);
1129 if !already_registered {
1130 if std::env::var("FORGE_TRIGGER_TRACE").is_ok() {
1131 if let Some(trigger) = game.card(card_id).triggers.get(trigger_index) {
1132 if trigger.kind == TriggerType::BecomesTarget {
1133 eprintln!(
1134 "[trigger-trace] REGISTER active BecomesTarget trigger host={}#{:?} idx={} desc={} zones={:?}",
1135 game.card(card_id).card_name,
1136 card_id,
1137 trigger_index,
1138 trigger.description,
1139 trigger.get_active_zone()
1140 );
1141 }
1142 }
1143 }
1144 self.active_triggers.push(ActiveTrigger {
1145 card_id,
1146 trigger_index,
1147 });
1148 }
1149 }
1150
1151 pub fn unregister_active_triggers(&mut self, card_id: CardId) {
1153 self.active_triggers.retain(|at| at.card_id != card_id);
1154 }
1155
1156 fn looks_back_in_time(&self, trigger: &Trigger) -> bool {
1157 if matches!(
1158 trigger.kind,
1159 TriggerType::Exploited
1160 | TriggerType::Destroyed
1161 | TriggerType::Sacrificed
1162 | TriggerType::SacrificedOnce
1163 ) {
1164 return true;
1165 }
1166 if matches!(
1167 trigger.kind,
1168 TriggerType::ChangesZone | TriggerType::ChangesZoneAll
1169 ) {
1170 return trigger
1171 .ir
1172 .origin_zones
1173 .contains(&forge_foundation::ZoneType::Battlefield)
1174 || trigger.ir.destination_zones.iter().any(|zone| {
1175 matches!(
1176 zone,
1177 forge_foundation::ZoneType::Library | forge_foundation::ZoneType::Hand
1178 )
1179 });
1180 }
1181 false
1182 }
1183
1184 fn ltb_trigger_refs_for_event(
1185 &self,
1186 game: &GameState,
1187 event: &TriggerWaiting,
1188 ) -> Vec<(CardId, usize)> {
1189 let TriggerWaiting {
1190 params: event_payload,
1191 ..
1192 } = event;
1193 match event.mode {
1194 TriggerType::ChangesZone => {
1195 let destination = event_payload.destination;
1196 if event_payload.origin != Some(forge_foundation::ZoneType::Battlefield)
1197 && !matches!(
1198 destination,
1199 Some(
1200 forge_foundation::ZoneType::Library | forge_foundation::ZoneType::Hand
1201 )
1202 )
1203 {
1204 return Vec::new();
1205 }
1206 let Some(card_id) = event_payload.card_lki.or(event_payload.card) else {
1207 return Vec::new();
1208 };
1209 let mut refs: Vec<(CardId, usize)> = game
1210 .card(card_id)
1211 .triggers
1212 .iter()
1213 .enumerate()
1214 .filter_map(|(trigger_index, trigger)| {
1215 self.looks_back_in_time(trigger)
1216 .then_some((card_id, trigger_index))
1217 })
1218 .collect();
1219
1220 if event_payload.origin == Some(forge_foundation::ZoneType::Battlefield) {
1228 for <b_id in &game.pre_sba_battlefield {
1229 if ltb_id == card_id {
1230 continue;
1231 }
1232 let ltb_card = game.card(ltb_id);
1233 if ltb_card.zone == forge_foundation::ZoneType::Battlefield {
1234 continue;
1236 }
1237 for (trigger_index, trigger) in ltb_card.triggers.iter().enumerate() {
1238 if self.looks_back_in_time(trigger) {
1239 refs.push((ltb_id, trigger_index));
1240 }
1241 }
1242 }
1243 }
1244
1245 refs
1246 }
1247 TriggerType::ChangesZoneAll => event
1248 .params
1249 .change_zone_table
1250 .as_ref()
1251 .map(|table| {
1252 table
1253 .last_state_battlefield()
1254 .iter()
1255 .flat_map(|&card_id| {
1256 game.card(card_id).triggers.iter().enumerate().filter_map(
1257 move |(trigger_index, trigger)| {
1258 self.looks_back_in_time(trigger)
1259 .then_some((card_id, trigger_index))
1260 },
1261 )
1262 })
1263 .collect()
1264 })
1265 .unwrap_or_default(),
1266 _ => Vec::new(),
1267 }
1268 }
1269
1270 pub fn clear_waiting_triggers(&mut self) {
1271 self.waiting_triggers.clear();
1272 self.pre_matched_triggers.clear();
1273 }
1274
1275 pub fn on_player_lost(&mut self, player: PlayerId) {
1276 self.delayed_triggers.retain(|d| d.controller != player);
1277 self.this_turn_delayed_triggers
1278 .retain(|d| d.controller != player);
1279 self.player_defined_delayed_triggers
1280 .retain(|(p, _)| *p != player);
1281 }
1282
1283 pub fn has_source_chapter_pending(&self, game: &GameState, source: CardId) -> bool {
1284 if self
1285 .pre_matched_triggers
1286 .iter()
1287 .any(|(pending, _, _, _, _)| {
1288 pending.entry.spell_ability.is_trigger
1289 && pending.entry.spell_ability.source == Some(source)
1290 && pending
1291 .entry
1292 .spell_ability
1293 .source_trigger_id
1294 .is_some_and(|trigger_id| {
1295 game.card(source)
1296 .triggers
1297 .iter()
1298 .any(|trigger| trigger.id == trigger_id && trigger.is_chapter())
1299 })
1300 })
1301 {
1302 return true;
1303 }
1304
1305 for event in &self.waiting_triggers {
1306 let refs: Vec<(CardId, usize)> = event.trigger_refs.clone().unwrap_or_else(|| {
1307 self.active_triggers
1308 .iter()
1309 .map(|active| (active.card_id, active.trigger_index))
1310 .collect()
1311 });
1312 for (card_id, trigger_index) in refs {
1313 if card_id != source {
1314 continue;
1315 }
1316 let Some(trigger) = game.card(card_id).triggers.get(trigger_index) else {
1317 continue;
1318 };
1319 if !trigger.is_chapter() {
1320 continue;
1321 }
1322 if self.can_run_trigger(
1323 game,
1324 card_id,
1325 trigger_index,
1326 game.card(card_id).controller,
1327 &event.mode,
1328 &event.params,
1329 ) {
1330 return true;
1331 }
1332 }
1333 }
1334
1335 false
1336 }
1337
1338 fn is_trigger_disabled_by_static(
1342 game: &GameState,
1343 host_card: CardId,
1344 trigger_index: usize,
1345 params: &RunParams,
1346 ) -> bool {
1347 let trigger = &game.card(host_card).triggers[trigger_index];
1348
1349 if trigger.kind != TriggerType::ChangesZone {
1351 return false;
1352 }
1353
1354 let cause_card_id = match params.card {
1356 Some(cid) => cid,
1357 None => return false,
1358 };
1359
1360 let is_ltb = params.origin == Some(ZoneType::Battlefield);
1369 for card in game.cards.iter() {
1370 let on_battlefield = card.zone == ZoneType::Battlefield;
1371 let was_on_battlefield = is_ltb && game.pre_sba_battlefield.contains(&card.id);
1372 if !on_battlefield && !was_on_battlefield {
1373 continue;
1374 }
1375 for sa in &card.static_abilities {
1376 if !sa.check_mode(&crate::staticability::StaticMode::DisableTriggers) {
1377 continue;
1378 }
1379
1380 if let Some(valid_cause) = sa.ir.valid_cause.as_ref() {
1382 let context = valid_filter::MatchContext::from_source(card)
1383 .with_game(game)
1384 .with_triggering(cause_card_id.into(), params.player);
1385 if !valid_filter::matches_valid_card_selector_with_context(
1386 valid_cause,
1387 game.card(cause_card_id),
1388 context,
1389 ) {
1390 continue;
1391 }
1392 }
1393
1394 if let Some(valid_mode) = sa.ir.valid_mode.as_deref() {
1396 let modes: Vec<&str> = valid_mode.split(',').collect();
1397 if !modes.contains(&"ChangesZone") {
1398 continue;
1399 }
1400 }
1401
1402 if !sa.ir.origin_zones.is_empty()
1404 && !params
1405 .origin
1406 .is_some_and(|zone| sa.ir.origin_zones.contains(&zone))
1407 {
1408 continue;
1409 }
1410
1411 if !sa.ir.destination_zones.is_empty()
1413 && !params
1414 .destination
1415 .is_some_and(|zone| sa.ir.destination_zones.contains(&zone))
1416 {
1417 continue;
1418 }
1419
1420 return true;
1422 }
1423 }
1424 false
1425 }
1426
1427 fn can_run_trigger(
1430 &self,
1431 game: &GameState,
1432 host_card: CardId,
1433 trigger_index: usize,
1434 host_controller: PlayerId,
1435 mode: &TriggerType,
1436 params: &RunParams,
1437 ) -> bool {
1438 let _perf_scope =
1439 crate::perf::ParamsLookupScopeGuard::enter(crate::perf::ParamsLookupScope::Trigger);
1440 let card = game.card(host_card);
1441 if trigger_index >= card.triggers.len() {
1442 return false;
1443 }
1444 let trigger = &card.triggers[trigger_index];
1445
1446 let trigger_type = trigger.mode.trigger_type();
1448
1449 let normalize = |t: TriggerType| -> TriggerType {
1450 match t {
1451 TriggerType::DungeonCompleted => TriggerType::CompletedDungeon,
1452 TriggerType::RoomEntered => TriggerType::EnteredRoom,
1453 TriggerType::Airbend
1454 | TriggerType::Earthbend
1455 | TriggerType::Firebend
1456 | TriggerType::Waterbend
1457 | TriggerType::ElementalBend => TriggerType::Elementalbend,
1458 TriggerType::TakesInitiative => TriggerType::TakeInitiative,
1459 TriggerType::PhaseIn => TriggerType::PhasedIn,
1460 TriggerType::PhaseOut => TriggerType::PhasedOut,
1461 TriggerType::PlaneswalkedFrom | TriggerType::PlaneswalkedTo => {
1462 TriggerType::Planeswalk
1463 }
1464 TriggerType::CrankContraption => TriggerType::CrankAdvanced,
1465 TriggerType::Explores => TriggerType::Explored,
1466 TriggerType::Unattach => TriggerType::Unattached,
1467 TriggerType::Saddled | TriggerType::Stationed => TriggerType::Crewed,
1468 other => other,
1469 }
1470 };
1471 let mode_matches = normalize(trigger_type) == normalize(*mode);
1472 if !mode_matches {
1473 return false;
1474 }
1475
1476 if self.is_trigger_suppressed(*mode) {
1478 return false;
1479 }
1480
1481 if !trigger.phases_check(game, host_card, params.phase) {
1483 return false;
1484 }
1485 if !trigger.requirements_check(game, host_card) {
1486 return false;
1487 }
1488 if !trigger.check_activation_limit(game, host_card) {
1489 return false;
1490 }
1491
1492 if matches!(
1495 *mode,
1496 TriggerType::ChangesZone | TriggerType::ChangesZoneAll
1497 ) && Self::is_trigger_disabled_by_static(game, host_card, trigger_index, params)
1498 {
1499 return false;
1500 }
1501
1502 let zone_for_active_check = if *mode == TriggerType::ChangesZone
1506 && params.card == Some(host_card)
1507 && params.origin == Some(ZoneType::Battlefield)
1508 && params.destination != Some(ZoneType::Battlefield)
1509 {
1510 ZoneType::Battlefield
1512 } else if *mode == TriggerType::ChangesZone
1513 && params.card == Some(host_card)
1514 && params.destination == Some(ZoneType::Battlefield)
1515 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1516 && card.zone != ZoneType::Battlefield
1517 {
1518 ZoneType::Battlefield
1521 } else if *mode == TriggerType::ChangesZone
1522 && params.origin == Some(ZoneType::Battlefield)
1523 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1524 && card.zone != ZoneType::Battlefield
1525 && self.looks_back_in_time(trigger)
1526 && game.pre_sba_battlefield.contains(&host_card)
1527 {
1528 ZoneType::Battlefield
1534 } else if (*mode == TriggerType::DamageDone || *mode == TriggerType::DamageDoneOnce)
1535 && params.damage_target_card == Some(host_card)
1536 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1537 && card.zone != ZoneType::Battlefield
1538 {
1539 ZoneType::Battlefield
1544 } else if *mode == TriggerType::Exploited
1545 && params.card == Some(host_card)
1546 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1547 && card.zone != ZoneType::Battlefield
1548 {
1549 ZoneType::Battlefield
1552 } else if *mode == TriggerType::Sacrificed
1553 && params.card == Some(host_card)
1554 && trigger.get_active_zone().contains(&ZoneType::Battlefield)
1555 && card.zone != ZoneType::Battlefield
1556 {
1557 ZoneType::Battlefield
1560 } else {
1561 card.zone
1562 };
1563 if !trigger.get_active_zone().contains(&zone_for_active_check) {
1564 return false;
1565 }
1566
1567 if card.phased_out {
1570 return false;
1571 }
1572
1573 if !trigger.mode.perform_test(trigger, params, game) {
1575 return false;
1576 }
1577 if !trigger.meets_requirements_on_triggered_objects(game, params, host_card) {
1578 return false;
1579 }
1580
1581 if let Some(cond) = trigger.ir.activator_this_turn_cast.as_deref() {
1586 let caster = params.spell_controller.unwrap_or(host_controller);
1587 let count = game.player(caster).spells_cast_this_turn;
1588 if !compare_expr(count, cond.trim()) {
1589 return false;
1590 }
1591 }
1592
1593 true
1594 }
1595
1596 pub fn suppress_mode(&mut self, mode: TriggerType) {
1597 self.suppressed_modes.insert(mode);
1598 }
1599
1600 pub fn set_suppress_all_triggers(&mut self, suppress: bool) {
1601 self.all_suppressed = suppress;
1602 }
1603
1604 pub fn get_active_trigger(
1607 &self,
1608 game: &GameState,
1609 mode: TriggerType,
1610 params: &RunParams,
1611 ) -> Vec<Trigger> {
1612 let mut result = Vec::new();
1613 for at in &self.active_triggers {
1614 let card = game.card(at.card_id);
1615 if at.trigger_index >= card.triggers.len() {
1616 continue;
1617 }
1618 let host_controller = card.controller;
1619 if self.can_run_trigger(
1620 game,
1621 at.card_id,
1622 at.trigger_index,
1623 host_controller,
1624 &mode,
1625 params,
1626 ) {
1627 result.push(card.triggers[at.trigger_index].clone());
1628 }
1629 }
1630 result
1631 }
1632
1633 pub fn is_trigger_suppressed(&self, mode: TriggerType) -> bool {
1634 self.all_suppressed || self.suppressed_modes.contains(&mode)
1635 }
1636
1637 pub fn clear_suppression(&mut self, mode: TriggerType) {
1638 self.suppressed_modes.remove(&mode);
1639 }
1640}
1641
1642impl Default for TriggerHandler {
1643 fn default() -> Self {
1644 Self::new()
1645 }
1646}
1647
1648#[cfg(test)]
1649mod tests {
1650 use super::*;
1651 use crate::card::Card;
1652 use crate::event::RunParams;
1653 use crate::ids::PlayerId;
1654 use crate::trigger::TriggerType;
1655 use forge_carddb::parse_card_script;
1656 use forge_foundation::PhaseType;
1657
1658 #[test]
1659 fn maralen_style_draw_phase_trigger_is_active() {
1660 let rules = parse_card_script(
1661 "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",
1662 )
1663 .expect("card script should parse");
1664
1665 let mut game = GameState::new(&["Alice", "Bob"], 20);
1666 let mut maralen = Card::from_rules(&rules, PlayerId(0));
1667 maralen.id = crate::ids::CardId(0);
1668 maralen.zone = forge_foundation::ZoneType::Battlefield;
1669 game.cards.push(maralen);
1670 game.add_card_to_zone(
1671 forge_foundation::ZoneType::Battlefield,
1672 PlayerId(0),
1673 crate::ids::CardId(0),
1674 );
1675
1676 let mut handler = TriggerHandler::new();
1677 handler.register_active_trigger(&game, crate::ids::CardId(0));
1678
1679 let active = handler.get_active_trigger(
1680 &game,
1681 TriggerType::Phase,
1682 &RunParams {
1683 phase: Some(PhaseType::Draw),
1684 player: Some(PlayerId(0)),
1685 ..Default::default()
1686 },
1687 );
1688
1689 assert_eq!(game.card(crate::ids::CardId(0)).triggers.len(), 1);
1690 assert_eq!(active.len(), 1);
1691
1692 let pending = handler.run_trigger_with_game(
1693 &game,
1694 TriggerType::Phase,
1695 RunParams {
1696 phase: Some(PhaseType::Draw),
1697 player: Some(PlayerId(0)),
1698 ..Default::default()
1699 },
1700 false,
1701 );
1702 assert_eq!(pending.len(), 1);
1703 assert_eq!(
1704 pending[0]
1705 .entry
1706 .spell_ability
1707 .get_triggering_player(crate::ability::AbilityKey::TriggeredPlayer),
1708 Some(PlayerId(0))
1709 );
1710 }
1711}