1use super::*;
2
3impl GameLoop {
4 pub(crate) fn run_turn_state_machine(
5 &mut self,
6 game: &mut GameState,
7 agents: &mut [Box<dyn PlayerAgent>],
8 ) {
9 let _perf_scope =
10 crate::perf::ParamsLookupScopeGuard::enter(crate::perf::ParamsLookupScope::Phase);
11 let active = game.active_player();
13 {
14 use crate::replacement::replacement_handler::{apply_replacements, ReplacementEvent};
15 use crate::replacement::ReplacementResult;
16 let mut event = ReplacementEvent::BeginTurn { player: active };
17 let result = apply_replacements(game, &mut event);
18 if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
19 return;
20 }
21 }
22
23 let active = game.active_player();
25 crate::player::expire_until_your_next_turn_keywords(game, active);
26 self.trigger_handler
27 .handle_player_defined_del_triggers(active);
28 self.trigger_handler.run_trigger(
29 TriggerType::TurnBegin,
30 RunParams {
31 player: Some(active),
32 ..Default::default()
33 },
34 false,
35 );
36
37 let mut state = TurnMachineState::Untap;
38 while !game.game_over && state != TurnMachineState::Done {
39 if self.is_aborted() {
40 game.game_over = true;
41 break;
42 }
43 if game.end_turn_requested
45 && state != TurnMachineState::Cleanup
46 && state != TurnMachineState::Done
47 {
48 game.end_turn_requested = false;
49 self.combat.clear_with_cards(&mut game.cards);
50 state = TurnMachineState::Cleanup;
51 continue;
52 }
53 state = match state {
54 TurnMachineState::Untap => {
55 let active = game.active_player();
57 if game.player(active).skip_next_untap {
58 game.player_clear_skip_untap(active);
59 TurnMachineState::Upkeep
60 } else {
61 self.apply_turn_event(
62 game,
63 agents,
64 TurnEvent::EnterPhase {
65 phase: PhaseType::Untap,
66 emit_phase_trigger: false,
67 },
68 );
69 self.apply_turn_event(game, agents, TurnEvent::UntapStep);
70 TurnMachineState::Upkeep
71 }
72 }
73 TurnMachineState::Upkeep => {
74 self.apply_turn_event(
75 game,
76 agents,
77 TurnEvent::EnterPhase {
78 phase: PhaseType::Upkeep,
79 emit_phase_trigger: true,
80 },
81 );
82 self.apply_turn_event(
83 game,
84 agents,
85 TurnEvent::PriorityWindow {
86 is_main_phase: false,
87 },
88 );
89 self.clear_came_under_control_since_last_upkeep(game);
90 TurnMachineState::Draw
95 }
96 TurnMachineState::Draw => {
97 let active = game.active_player();
99 if game.player(active).skip_next_draw
100 || crate::replacement::replacement_handler::would_phase_be_skipped(
101 game,
102 active,
103 PhaseType::Draw,
104 )
105 {
106 game.player_clear_skip_draw(active);
107 TurnMachineState::Main1
108 } else {
109 let skip_draw_phase = game.turn.turn_number == 1 && game.players.len() == 2;
113 if skip_draw_phase {
114 self.apply_turn_event(
115 game,
116 agents,
117 TurnEvent::EnterPhase {
118 phase: PhaseType::Draw,
119 emit_phase_trigger: false,
120 },
121 );
122 TurnMachineState::Main1
123 } else {
124 self.apply_turn_event(
125 game,
126 agents,
127 TurnEvent::EnterPhase {
128 phase: PhaseType::Draw,
129 emit_phase_trigger: true,
130 },
131 );
132 self.apply_turn_event(game, agents, TurnEvent::DrawStep);
133 self.apply_turn_event(
134 game,
135 agents,
136 TurnEvent::PriorityWindow {
137 is_main_phase: false,
138 },
139 );
140 TurnMachineState::Main1
141 }
142 }
143 }
144 TurnMachineState::Main1 => {
145 self.apply_turn_event(
146 game,
147 agents,
148 TurnEvent::EnterPhase {
149 phase: PhaseType::Main1,
150 emit_phase_trigger: true,
151 },
152 );
153 if game
154 .cards_in_zone(ZoneType::Battlefield, game.active_player())
155 .iter()
156 .any(|&card_id| {
157 game.card(card_id)
158 .type_line
159 .subtypes
160 .iter()
161 .any(|s| s.eq_ignore_ascii_case("Attraction"))
162 })
163 {
164 crate::ability::effects::roll_dice_effect::roll_to_visit_attractions(
165 game,
166 &mut self.trigger_handler,
167 &mut *self.game_rng,
168 agents,
169 &mut self.mana_pools,
170 game.active_player(),
171 );
172 }
173 self.apply_turn_event(
174 game,
175 agents,
176 TurnEvent::PriorityWindow {
177 is_main_phase: true,
178 },
179 );
180 TurnMachineState::Combat
181 }
182 TurnMachineState::Combat => {
183 let active = game.active_player();
185 if game.player(active).skip_next_combat {
186 game.player_clear_skip_combat(active);
187 TurnMachineState::Main2
188 } else {
189 self.apply_turn_event(game, agents, TurnEvent::CombatStep);
190 if game.extra_combat_phases > 0 {
192 game.extra_combat_phases -= 1;
193 TurnMachineState::Combat } else {
195 TurnMachineState::Main2
196 }
197 }
198 }
199 TurnMachineState::Main2 => {
200 self.apply_turn_event(
201 game,
202 agents,
203 TurnEvent::EnterPhase {
204 phase: PhaseType::Main2,
205 emit_phase_trigger: true,
206 },
207 );
208 self.apply_turn_event(
209 game,
210 agents,
211 TurnEvent::PriorityWindow {
212 is_main_phase: true,
213 },
214 );
215 TurnMachineState::EndOfTurn
216 }
217 TurnMachineState::EndOfTurn => {
218 self.apply_turn_event(
219 game,
220 agents,
221 TurnEvent::EnterPhase {
222 phase: PhaseType::EndOfTurn,
223 emit_phase_trigger: true,
224 },
225 );
226 self.apply_turn_event(
227 game,
228 agents,
229 TurnEvent::PriorityWindow {
230 is_main_phase: false,
231 },
232 );
233 TurnMachineState::Cleanup
234 }
235 TurnMachineState::Cleanup => {
236 self.apply_turn_event(
237 game,
238 agents,
239 TurnEvent::EnterPhase {
240 phase: PhaseType::Cleanup,
241 emit_phase_trigger: false,
242 },
243 );
244 self.apply_turn_event(game, agents, TurnEvent::CleanupStep);
245 self.apply_turn_event(game, agents, TurnEvent::AdvanceTurn);
246 TurnMachineState::Done
247 }
248 TurnMachineState::Done => TurnMachineState::Done,
249 };
250 }
251 }
252
253 fn apply_turn_event(
254 &mut self,
255 game: &mut GameState,
256 agents: &mut [Box<dyn PlayerAgent>],
257 event: TurnEvent,
258 ) {
259 if game.game_over {
260 return;
261 }
262 match event {
263 TurnEvent::EnterPhase {
264 phase,
265 emit_phase_trigger,
266 } => {
267 game.stack.clear_undo_stack();
269
270 {
272 use crate::replacement::replacement_handler::{
273 apply_replacements, ReplacementEvent,
274 };
275 use crate::replacement::ReplacementResult;
276 let active = game.active_player();
277 let mut event = ReplacementEvent::BeginPhase {
278 player: active,
279 phase,
280 };
281 let result = apply_replacements(game, &mut event);
282 if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced
283 {
284 return;
285 }
286 }
287
288 game.copy_last_state();
291 self.set_phase(game, agents, phase);
292 if emit_phase_trigger {
293 self.emit_phase_trigger(game, phase);
294 }
295 if phase == PhaseType::Upkeep {
298 self.process_suspend_upkeep(game, agents);
299 }
300 }
301 TurnEvent::PriorityWindow { is_main_phase } => {
302 self.step_with_priority(game, agents, is_main_phase);
303 }
304 TurnEvent::UntapStep => {
305 self.with_shared_state_mutation(game, agents, |this, game, agents| {
306 this.step_untap(game, agents);
307 });
308 }
309 TurnEvent::DrawStep => {
310 self.with_shared_state_mutation(game, agents, |this, game, agents| {
311 this.step_draw(game, agents);
312 });
313 }
314 TurnEvent::CombatStep => {
315 self.step_combat(game, agents);
316 }
317 TurnEvent::CleanupStep => {
318 self.with_shared_state_mutation(game, agents, |this, game, agents| {
319 this.step_cleanup(game, agents);
320 });
321 }
322 TurnEvent::AdvanceTurn => {
323 self.with_shared_state_mutation(game, agents, |_this, game, agents| {
324 game.stack.on_next_turn();
326 game.reset_zone_turn_tracking();
328 game.reset_card_turn_tracking();
329 let player_order = game.player_order.clone();
330 let extra_turn_player = game.extra_turns.front().map(|et| et.player);
331 if let Some((player, _skip)) =
332 game.turn.advance_turn(&mut game.extra_turns, &player_order)
333 {
334 game.player_set_skip_untap(player);
335 }
336 if let Some(player) = extra_turn_player {
337 if agents[player.index()]
338 .get_pass_until()
339 .is_some_and(|target| target.player != player)
340 {
341 agents[player.index()].clear_pass_until();
342 }
343 }
344 });
345 }
346 }
347 }
348
349 pub fn step_untap(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
350 let active = game.active_player();
351 let cards_snapshot = game.cards.clone();
352
353 crate::phase::untap::do_phasing(game, active);
355
356 let cards: Vec<crate::ids::CardId> =
358 game.cards_in_zone(ZoneType::Battlefield, active).to_vec();
359 for cid in cards {
360 if game.card(cid).exerted {
361 game.card_mut(cid).clear_exerted();
362 } else {
363 let optional_keyword =
364 "You may choose not to untap CARDNAME during your untap step.";
365 let should_untap = if game.card(cid).has_keyword(optional_keyword) {
366 let question = format!("Untap {}?", game.card(cid).card_name);
367 let source_name = game.card(cid).card_name.clone();
368 agents[active.index()].choose_binary(
369 active,
370 &question,
371 crate::agent::BinaryChoiceKind::UntapOrLeaveTapped,
372 Some(true),
373 Some(cid),
374 None,
375 )
376 } else {
377 true
378 };
379 if should_untap {
380 game.untap_during_untap_step(cid, active);
381 }
382 }
383 }
384
385 let other_battlefield_cards: Vec<_> = game
389 .player_order
390 .iter()
391 .copied()
392 .filter(|&pid| pid != active)
393 .flat_map(|pid| {
394 game.cards_in_zone(ZoneType::Battlefield, pid)
395 .iter()
396 .copied()
397 })
398 .collect();
399 for cid in other_battlefield_cards {
400 let card = game.card(cid);
401 if !card.tapped {
402 continue;
403 }
404 if crate::staticability::static_ability_untap_other_player::untap(
405 &cards_snapshot,
406 card,
407 active,
408 ) {
409 game.untap_during_untap_step(cid, active);
410 }
411 }
412
413 self.pool_mut(active).reset_pool();
414 }
415
416 pub fn step_draw(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
417 let active = game.active_player();
418 if game.turn.turn_number > 1 {
420 use crate::replacement::replacement_handler::{
421 apply_replacements_with_agents, ReplacementEvent,
422 };
423 use crate::replacement::ReplacementResult;
424
425 let mut draw_count = 1;
433 let mut draw_cards_event = ReplacementEvent::DrawCards {
434 player: active,
435 count: draw_count,
436 };
437 let result = apply_replacements_with_agents(game, agents, &mut draw_cards_event);
438 if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
439 return;
440 }
441 if let ReplacementEvent::DrawCards { count, .. } = draw_cards_event {
442 draw_count = count;
443 }
444
445 for draw_index in 0..draw_count {
446 if let Some(card_id) =
447 game.player_draw_one_internal(active, draw_index == 0, Some(agents))
448 {
449 let drawn_snapshot = game.player(active).drawn_this_turn;
450 self.trigger_handler.run_trigger(
452 TriggerType::Drawn,
453 RunParams {
454 card: Some(card_id),
455 player: Some(active),
456 drawn_this_turn_snapshot: Some(drawn_snapshot),
457 ..Default::default()
458 },
459 false,
460 );
461 if self.trigger_handler.has_number_drawn_triggers(game) {
464 self.trigger_handler.flush_waiting_triggers(game);
465 }
466 }
467 }
468 }
469 }
470
471 pub fn step_with_priority(
472 &mut self,
473 game: &mut GameState,
474 agents: &mut [Box<dyn PlayerAgent>],
475 is_main_phase: bool,
476 ) {
477 let _perf_scope =
478 crate::perf::ParamsLookupScopeGuard::enter(crate::perf::ParamsLookupScope::Priority);
479 self.game_log.log(
480 GameLogEntryType::Info,
481 2,
482 format!(
483 "Priority window opened ({}, stack depth: {})",
484 if is_main_phase {
485 "main-phase speed"
486 } else {
487 "instant speed only"
488 },
489 game.stack.len()
490 ),
491 );
492 self.with_shared_state_mutation(game, agents, |_this, game, _agents| {
493 game.turn.priority_player = game.active_player();
494 });
495 loop {
496 if game.game_over {
497 return;
498 }
499
500 self.priority_round(game, agents, is_main_phase);
505
506 if std::env::var("FORGE_STACK_TRACE").is_ok() {
507 let names: Vec<String> = game
508 .stack
509 .iter()
510 .map(|entry| {
511 entry
512 .spell_ability
513 .source
514 .map(|cid| game.card(cid).card_name.clone())
515 .unwrap_or_else(|| "<effect>".to_string())
516 })
517 .collect();
518 eprintln!(
519 "[stack-trace] AFTER priority_round phase={:?} active={:?} priority={:?} depth={} {:?}",
520 game.turn.phase,
521 game.active_player(),
522 game.turn.priority_player,
523 game.stack.len(),
524 names
525 );
526 }
527
528 if game.game_over {
529 return;
530 }
531
532 if game.stack.is_empty() {
534 break;
535 }
536
537 self.with_shared_state_mutation(game, agents, |this, game, agents| {
539 this.resolve_stack(game, agents);
540 });
541 }
542 }
543 pub fn step_main_phase(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
544 self.step_with_priority(game, agents, true);
545 }
546
547 pub fn step_cleanup(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
548 let mut is_repeat = false;
553 loop {
554 if is_repeat {
557 self.notify_phase_changed(game, agents);
558 }
559
560 let active = game.active_player();
561
562 let hand_size = game.zone(ZoneType::Hand, active).len() as i32;
566 let max = game.player(active).max_hand_size;
567 let unlimited = game.player(active).unlimited_hand_size;
568 if !unlimited && hand_size > max {
569 let to_discard = (hand_size - max) as usize;
570 let hand: Vec<CardId> = game.cards_in_zone(ZoneType::Hand, active).to_vec();
571 agents[active.index()].snapshot_state(game, &self.mana_pools);
572 self.game_log.log(
573 GameLogEntryType::PriorityWaiting,
574 2,
575 format!(
576 "Waiting for {} discard decision (choose {})",
577 game.player(active).name,
578 to_discard
579 ),
580 );
581 let chosen = agents[active.index()].choose_discard(active, &hand, to_discard);
582 self.game_log.log(
583 GameLogEntryType::PriorityResponse,
584 2,
585 format!(
586 "{} selected {} card(s) to discard",
587 game.player(active).name,
588 chosen.len().min(to_discard)
589 ),
590 );
591 for card_id in chosen.iter().take(to_discard) {
592 if game.card(*card_id).zone == ZoneType::Hand {
593 game.discard_card(
594 *card_id,
595 active,
596 None,
597 Some(agents),
598 &mut self.trigger_handler,
599 );
600 }
601 }
602 }
603
604 self.cleanup_damage_and_eot(game);
608
609 self.trigger_handler.flush_waiting_triggers(game);
610 if game.stack.is_empty() && self.trigger_handler.pre_matched_trigger_count() == 0 {
611 break;
612 }
613 self.step_with_priority(game, agents, false);
614 is_repeat = true;
615 }
616
617 game.prevent_all_combat_damage = false;
621
622 game.end_turn_requested = false;
624 game.end_combat_requested = false;
625 game.extra_combat_phases = 0;
626 game.stack.reset_max_distinct_sources();
627
628 let active = game.active_player();
630 self.pool_mut(active).reset_pool();
631 self.trigger_handler.clear_this_turn_delayed_trigger();
632 }
633
634 fn cleanup_damage_and_eot(&mut self, game: &mut GameState) {
638 let temp_effect_ids: Vec<CardId> = game
641 .cards
642 .iter()
643 .filter(|c| c.zone == ZoneType::Command && c.temp_effect_until_eot)
644 .map(|c| c.id)
645 .collect();
646 for effect_id in temp_effect_ids {
647 if game.card(effect_id).zone == ZoneType::Command {
648 let controller = game.card(effect_id).controller;
649 game.remove_card_from_zone(ZoneType::Command, controller, effect_id);
650 game.card_mut(effect_id).set_zone(ZoneType::None);
651 }
652 }
653
654 let stolen: Vec<(CardId, crate::ids::PlayerId)> = game
656 .cards
657 .iter()
658 .filter(|c| c.zone == ZoneType::Battlefield && c.original_controller_eot.is_some())
659 .map(|c| (c.id, c.original_controller_eot.unwrap()))
660 .collect();
661 for (card_id, original) in stolen {
662 let current = game.card(card_id).controller;
663 game.card_mut(card_id).clear_original_controller_eot();
664 game.card_mut(card_id).clear_granted_keywords();
666 if current != original {
667 game.change_controller(card_id, original);
668 }
669 }
670
671 let player_ids = game.player_order.clone();
673 for player in player_ids {
674 game.player_cleanup_turn_state(player);
675 crate::player::expire_end_of_turn_keywords(game, player);
676 }
677
678 for i in 0..game.cards.len() {
679 let card_id = game.cards[i].id;
680 crate::ability::effects::change_text_effect::run(game, card_id);
681 }
682
683 for i in 0..game.cards.len() {
685 if game.cards[i].zone == ZoneType::Battlefield {
686 if let Some(state) = game.cards[i].animate_state.take() {
688 game.cards[i].restore_animate_snapshot(
689 state.original_type_line,
690 state.original_base_power,
691 state.original_base_toughness,
692 state.original_color,
693 );
694 game.cards[i].clear_damage();
695 }
696 if game.cards[i]
697 .clone_state
698 .as_ref()
699 .is_some_and(|state| state.expires_at_cleanup)
700 {
701 if let Some(state) = game.cards[i].clone_state.take() {
702 game.cards[i].restore_clone_snapshot(state);
703 }
704 }
705
706 game.cards[i].reset_turn_modifiers();
707 game.cards[i].cant_have_keywords.clear();
708 game.cards[i].clear_pump_keywords();
709 game.cards[i].clear_pump_triggers();
710 game.cards[i].clear_deathtouch_damage();
711 game.cards[i].reset_regeneration_shields();
712 game.cards[i].reset_shield_count();
713 game.cards[i].reset_crewed();
714
715 if game.cards[i].is_creature() {
716 let keep_damage =
717 crate::staticability::static_ability_no_cleanup_damage::damage_not_removed(
718 &game.cards,
719 &game.cards[i],
720 );
721 if !keep_damage {
722 game.cards[i].clear_damage();
723 }
724 game.cards[i].reset_turn_modifiers();
725 game.cards[i].clear_deathtouch_damage();
726 game.cards[i].reset_regeneration_shields();
727 game.cards[i].reset_shield_count();
728 game.cards[i].damage_history.new_turn();
729 }
730 game.cards[i].cant_have_keywords.clear();
738 game.cards[i].clear_pump_keywords();
739 game.cards[i].clear_pump_triggers();
740 } else {
741 game.cards[i].cant_have_keywords.clear();
742 game.cards[i].clear_pump_keywords();
743 game.cards[i].clear_pump_triggers();
744 }
745 }
746 }
747 pub(crate) fn emit_phase_trigger(&mut self, game: &mut GameState, phase: PhaseType) {
748 let active = game.active_player();
749 self.trigger_handler.run_trigger(
750 TriggerType::Phase,
751 RunParams {
752 phase: Some(phase),
753 player: Some(active),
754 ..Default::default()
755 },
756 false,
757 );
758 self.trigger_handler.run_trigger(
759 TriggerType::Always,
760 RunParams {
761 phase: Some(phase),
762 player: Some(active),
763 ..Default::default()
764 },
765 false,
766 );
767 }
768
769 fn clear_came_under_control_since_last_upkeep(&mut self, game: &mut GameState) {
770 let active = game.active_player();
771 let turn_number = game.turn.turn_number;
772 for card in &mut game.cards {
773 if card.controller == active
774 && card.zone == ZoneType::Battlefield
775 && card.turn_in_zone < turn_number
776 {
777 card.came_under_control_since_last_upkeep = false;
778 }
779 }
780 }
781
782 pub(crate) fn fire_combat_damage_triggers(&mut self, events: &[combat::CombatDamageEvent]) {
786 use crate::card::card_damage_map::{CardDamageMap, DamageTarget};
787 use crate::ids::{CardId, PlayerId};
788 use std::collections::HashMap;
789
790 for event in events {
792 self.trigger_handler.run_trigger(
793 TriggerType::DamageDone,
794 RunParams {
795 damage_source: Some(event.source),
796 damage_target_player: event.target_player,
797 damage_target_card: event.target_card,
798 damage_amount: Some(event.amount),
799 is_combat_damage: Some(event.is_combat),
800 ..Default::default()
801 },
802 false,
803 );
804 if let Some(player) = event.lifelink_player {
805 if event.lifelink_amount > 0 {
806 self.trigger_handler.run_trigger(
807 TriggerType::LifeGained,
808 RunParams {
809 player: Some(player),
810 life_amount: Some(event.lifelink_amount),
811 source_card: Some(event.source),
812 ..Default::default()
813 },
814 false,
815 );
816 }
817 }
818 }
819
820 #[derive(Hash, PartialEq, Eq, Clone, Copy)]
824 enum Target {
825 Card(CardId),
826 Player(PlayerId),
827 }
828 let mut by_target: HashMap<Target, i32> = HashMap::new();
829 let mut maps_by_target: HashMap<Target, CardDamageMap> = HashMap::new();
830 for event in events {
831 if event.amount <= 0 {
832 continue;
833 }
834 let target = if let Some(cid) = event.target_card {
835 Target::Card(cid)
836 } else if let Some(pid) = event.target_player {
837 Target::Player(pid)
838 } else {
839 continue;
840 };
841 *by_target.entry(target).or_insert(0) += event.amount;
842 let damage_target = match target {
843 Target::Card(cid) => DamageTarget::Card(cid),
844 Target::Player(pid) => DamageTarget::Player(pid),
845 };
846 maps_by_target.entry(target).or_default().put(
847 event.source,
848 damage_target,
849 event.amount,
850 );
851 }
852 for (target, total) in by_target {
853 if total <= 0 {
854 continue;
855 }
856 let params = match target {
857 Target::Card(cid) => RunParams {
858 damage_target_card: Some(cid),
859 damage_amount: Some(total),
860 damage_map: maps_by_target.get(&target).cloned(),
861 is_combat_damage: Some(true),
862 ..Default::default()
863 },
864 Target::Player(pid) => RunParams {
865 damage_target_player: Some(pid),
866 damage_amount: Some(total),
867 damage_map: maps_by_target.get(&target).cloned(),
868 is_combat_damage: Some(true),
869 ..Default::default()
870 },
871 };
872 self.trigger_handler
873 .run_trigger(TriggerType::DamageDoneOnce, params, false);
874 }
875 }
876
877 pub(crate) fn process_suspend_upkeep(
882 &mut self,
883 game: &mut GameState,
884 agents: &mut [Box<dyn PlayerAgent>],
885 ) {
886 let active = game.active_player();
887 let exile: Vec<CardId> = game.cards_in_zone(ZoneType::Exile, active).to_vec();
888 for card_id in exile {
889 let card = game.card(card_id);
890 if card.get_suspend_cost().is_none() {
892 continue;
893 }
894 let time_counters = *card
895 .counters
896 .get(&crate::card::CounterType::Time)
897 .unwrap_or(&0);
898 if time_counters <= 0 {
899 continue;
900 }
901 game.card_mut(card_id)
903 .remove_counter(&crate::card::CounterType::Time, 1);
904
905 self.trigger_handler.run_trigger(
907 TriggerType::CounterRemoved,
908 RunParams {
909 card: Some(card_id),
910 player: Some(active),
911 ..Default::default()
912 },
913 false,
914 );
915
916 let remaining = *game
917 .card(card_id)
918 .counters
919 .get(&crate::card::CounterType::Time)
920 .unwrap_or(&0);
921 if remaining <= 0 {
922 let card_name = game.card(card_id).card_name.clone();
924 let is_creature = game.card(card_id).is_creature();
925 let is_permanent = game.card(card_id).is_permanent();
926 game.player_record_spell_cast(active, card_id);
928
929 self.trigger_handler.run_trigger(
931 TriggerType::SpellCast,
932 RunParams {
933 spell_card: Some(card_id),
934 spell_controller: Some(active),
935 ..Default::default()
936 },
937 false,
938 );
939
940 let mut sa =
942 crate::spellability::build_spell_ability_for_card_cast(game, card_id, active);
943 sa.alt_cost = Some(crate::spellability::AlternativeCost::Suspend);
944 sa.setup_targets(game, agents, &self.mana_pools);
945
946 let entry = StackEntry {
947 id: 0,
948 spell_ability: sa,
949 is_pending_cast: false,
950 is_creature_spell: is_creature,
951 is_permanent_spell: is_permanent,
952 cast_from_zone: Some(ZoneType::Exile),
953 optional_trigger_decider: None,
954 optional_trigger_description: None,
955 optional_trigger_source_name: None,
956 };
957
958 game.stack.push(entry);
959 self.log_stack_push(&card_name, &game.player(active).name);
960 self.move_card_with_runtime(game, card_id, ZoneType::Stack, active, agents);
961 crate::agent::notify_all_agents(
962 agents,
963 crate::agent::GameLogEvent::stack(format!(
964 "Suspend: casting {} for free!",
965 card_name
966 ))
967 .with_player(active)
968 .with_card(card_id),
969 );
970
971 if is_creature && !game.card(card_id).has_haste() {
973 game.card_mut(card_id).granted_keywords.add("Haste");
974 }
975 }
976 }
977 }
978}