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 let battlefield = game
154 .cards_in_zone(ZoneType::Battlefield, game.active_player())
155 .to_vec();
156 self.add_saga_lore_counters(game, agents, &battlefield);
157 if game
158 .cards_in_zone(ZoneType::Battlefield, game.active_player())
159 .iter()
160 .any(|&card_id| {
161 game.card(card_id)
162 .type_line
163 .subtypes
164 .iter()
165 .any(|s| s.eq_ignore_ascii_case("Attraction"))
166 })
167 {
168 crate::ability::effects::roll_dice_effect::roll_to_visit_attractions(
169 game,
170 &mut self.trigger_handler,
171 &mut *self.game_rng,
172 agents,
173 &mut self.mana_pools,
174 game.active_player(),
175 );
176 }
177 self.apply_turn_event(
178 game,
179 agents,
180 TurnEvent::PriorityWindow {
181 is_main_phase: true,
182 },
183 );
184 TurnMachineState::Combat
185 }
186 TurnMachineState::Combat => {
187 let active = game.active_player();
189 if game.player(active).skip_next_combat {
190 game.player_clear_skip_combat(active);
191 TurnMachineState::Main2
192 } else {
193 self.apply_turn_event(game, agents, TurnEvent::CombatStep);
194 if game.extra_combat_phases > 0 {
196 game.extra_combat_phases -= 1;
197 TurnMachineState::Combat } else {
199 TurnMachineState::Main2
200 }
201 }
202 }
203 TurnMachineState::Main2 => {
204 self.apply_turn_event(
205 game,
206 agents,
207 TurnEvent::EnterPhase {
208 phase: PhaseType::Main2,
209 emit_phase_trigger: true,
210 },
211 );
212 self.apply_turn_event(
213 game,
214 agents,
215 TurnEvent::PriorityWindow {
216 is_main_phase: true,
217 },
218 );
219 TurnMachineState::EndOfTurn
220 }
221 TurnMachineState::EndOfTurn => {
222 self.apply_turn_event(
223 game,
224 agents,
225 TurnEvent::EnterPhase {
226 phase: PhaseType::EndOfTurn,
227 emit_phase_trigger: true,
228 },
229 );
230 self.apply_turn_event(
231 game,
232 agents,
233 TurnEvent::PriorityWindow {
234 is_main_phase: false,
235 },
236 );
237 TurnMachineState::Cleanup
238 }
239 TurnMachineState::Cleanup => {
240 self.apply_turn_event(
241 game,
242 agents,
243 TurnEvent::EnterPhase {
244 phase: PhaseType::Cleanup,
245 emit_phase_trigger: false,
246 },
247 );
248 self.apply_turn_event(game, agents, TurnEvent::CleanupStep);
249 self.apply_turn_event(game, agents, TurnEvent::AdvanceTurn);
250 TurnMachineState::Done
251 }
252 TurnMachineState::Done => TurnMachineState::Done,
253 };
254 }
255 }
256
257 fn apply_turn_event(
258 &mut self,
259 game: &mut GameState,
260 agents: &mut [Box<dyn PlayerAgent>],
261 event: TurnEvent,
262 ) {
263 if game.game_over {
264 return;
265 }
266 match event {
267 TurnEvent::EnterPhase {
268 phase,
269 emit_phase_trigger,
270 } => {
271 game.stack.clear_undo_stack();
273
274 {
276 use crate::replacement::replacement_handler::{
277 apply_replacements, ReplacementEvent,
278 };
279 use crate::replacement::ReplacementResult;
280 let active = game.active_player();
281 let mut event = ReplacementEvent::BeginPhase {
282 player: active,
283 phase,
284 };
285 let result = apply_replacements(game, &mut event);
286 if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced
287 {
288 return;
289 }
290 }
291
292 game.copy_last_state();
295 self.set_phase(game, agents, phase);
296 if emit_phase_trigger {
297 self.emit_phase_trigger(game, phase);
298 }
299 if phase == PhaseType::Upkeep {
302 self.process_suspend_upkeep(game, agents);
303 }
304 }
305 TurnEvent::PriorityWindow { is_main_phase } => {
306 self.step_with_priority(game, agents, is_main_phase);
307 }
308 TurnEvent::UntapStep => {
309 self.with_shared_state_mutation(game, agents, |this, game, agents| {
310 this.step_untap(game, agents);
311 });
312 }
313 TurnEvent::DrawStep => {
314 self.with_shared_state_mutation(game, agents, |this, game, agents| {
315 this.step_draw(game, agents);
316 });
317 }
318 TurnEvent::CombatStep => {
319 self.step_combat(game, agents);
320 }
321 TurnEvent::CleanupStep => {
322 self.with_shared_state_mutation(game, agents, |this, game, agents| {
323 this.step_cleanup(game, agents);
324 });
325 }
326 TurnEvent::AdvanceTurn => {
327 self.with_shared_state_mutation(game, agents, |_this, game, agents| {
328 game.stack.on_next_turn();
330 game.reset_zone_turn_tracking();
332 game.reset_card_turn_tracking();
333 let player_order = game.player_order.clone();
334 let extra_turn_player = game.extra_turns.front().map(|et| et.player);
335 if let Some((player, _skip)) =
336 game.turn.advance_turn(&mut game.extra_turns, &player_order)
337 {
338 game.player_set_skip_untap(player);
339 }
340 if let Some(player) = extra_turn_player {
341 if agents[player.index()]
342 .get_pass_until()
343 .is_some_and(|target| target.player != player)
344 {
345 agents[player.index()].clear_pass_until();
346 }
347 }
348 });
349 }
350 }
351 }
352
353 pub fn step_untap(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
354 let active = game.active_player();
355 let cards_snapshot = game.cards.clone();
356
357 crate::phase::untap::do_phasing(game, active);
359
360 let cards: Vec<crate::ids::CardId> =
362 game.cards_in_zone(ZoneType::Battlefield, active).to_vec();
363 for cid in cards {
364 if game.card(cid).exerted {
365 game.card_mut(cid).clear_exerted();
366 } else {
367 let optional_keyword =
368 "You may choose not to untap CARDNAME during your untap step.";
369 let should_untap = if game.card(cid).has_keyword(optional_keyword) {
370 let question = format!("Untap {}?", game.card(cid).card_name);
371 let source_name = game.card(cid).card_name.clone();
372 agents[active.index()].choose_binary(
373 active,
374 &question,
375 crate::agent::BinaryChoiceKind::UntapOrLeaveTapped,
376 Some(true),
377 Some(cid),
378 None,
379 )
380 } else {
381 true
382 };
383 if should_untap {
384 game.untap_during_untap_step(cid, active);
385 }
386 }
387 }
388
389 let other_battlefield_cards: Vec<_> = game
393 .player_order
394 .iter()
395 .copied()
396 .filter(|&pid| pid != active)
397 .flat_map(|pid| {
398 game.cards_in_zone(ZoneType::Battlefield, pid)
399 .iter()
400 .copied()
401 })
402 .collect();
403 for cid in other_battlefield_cards {
404 let card = game.card(cid);
405 if !card.tapped {
406 continue;
407 }
408 if crate::staticability::static_ability_untap_other_player::untap(
409 &cards_snapshot,
410 card,
411 active,
412 ) {
413 game.untap_during_untap_step(cid, active);
414 }
415 }
416
417 self.pool_mut(active).reset_pool();
418 }
419
420 pub fn step_draw(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
421 let active = game.active_player();
422 if game.turn.turn_number > 1 {
424 use crate::replacement::replacement_handler::{
425 apply_replacements_with_agents, ReplacementEvent,
426 };
427 use crate::replacement::ReplacementResult;
428
429 let mut draw_count = 1;
437 let mut draw_cards_event = ReplacementEvent::DrawCards {
438 player: active,
439 count: draw_count,
440 };
441 let result = apply_replacements_with_agents(game, agents, &mut draw_cards_event);
442 if result == ReplacementResult::Skipped || result == ReplacementResult::Replaced {
443 return;
444 }
445 if let ReplacementEvent::DrawCards { count, .. } = draw_cards_event {
446 draw_count = count;
447 }
448
449 for draw_index in 0..draw_count {
450 if let Some(card_id) =
451 game.player_draw_one_internal(active, draw_index == 0, Some(agents))
452 {
453 let drawn_snapshot = game.player(active).drawn_this_turn;
454 self.trigger_handler.run_trigger(
456 TriggerType::Drawn,
457 RunParams {
458 card: Some(card_id),
459 player: Some(active),
460 drawn_this_turn_snapshot: Some(drawn_snapshot),
461 ..Default::default()
462 },
463 false,
464 );
465 if self.trigger_handler.has_number_drawn_triggers(game) {
468 self.trigger_handler.flush_waiting_triggers(game);
469 }
470 }
471 }
472 }
473 }
474
475 pub fn step_with_priority(
476 &mut self,
477 game: &mut GameState,
478 agents: &mut [Box<dyn PlayerAgent>],
479 is_main_phase: bool,
480 ) {
481 let _perf_scope =
482 crate::perf::ParamsLookupScopeGuard::enter(crate::perf::ParamsLookupScope::Priority);
483 self.game_log.log(
484 GameLogEntryType::Info,
485 2,
486 format!(
487 "Priority window opened ({}, stack depth: {})",
488 if is_main_phase {
489 "main-phase speed"
490 } else {
491 "instant speed only"
492 },
493 game.stack.len()
494 ),
495 );
496 self.with_shared_state_mutation(game, agents, |_this, game, _agents| {
497 game.turn.priority_player = game.active_player();
498 });
499 loop {
500 if game.game_over {
501 return;
502 }
503
504 self.priority_round(game, agents, is_main_phase);
509
510 if std::env::var("FORGE_STACK_TRACE").is_ok() {
511 let names: Vec<String> = game
512 .stack
513 .iter()
514 .map(|entry| {
515 entry
516 .spell_ability
517 .source
518 .map(|cid| game.card(cid).card_name.clone())
519 .unwrap_or_else(|| "<effect>".to_string())
520 })
521 .collect();
522 eprintln!(
523 "[stack-trace] AFTER priority_round phase={:?} active={:?} priority={:?} depth={} {:?}",
524 game.turn.phase,
525 game.active_player(),
526 game.turn.priority_player,
527 game.stack.len(),
528 names
529 );
530 }
531
532 if game.game_over {
533 return;
534 }
535
536 if game.stack.is_empty() {
538 break;
539 }
540
541 self.with_shared_state_mutation(game, agents, |this, game, agents| {
543 this.resolve_stack(game, agents);
544 });
545 }
546 }
547 pub fn step_main_phase(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
548 self.step_with_priority(game, agents, true);
549 }
550
551 pub fn step_cleanup(&mut self, game: &mut GameState, agents: &mut [Box<dyn PlayerAgent>]) {
552 let mut is_repeat = false;
557 loop {
558 if is_repeat {
561 self.notify_phase_changed(game, agents);
562 }
563
564 let active = game.active_player();
565
566 let hand_size = game.zone(ZoneType::Hand, active).len() as i32;
570 let max = game.player(active).max_hand_size;
571 let unlimited = game.player(active).unlimited_hand_size;
572 if !unlimited && hand_size > max {
573 let to_discard = (hand_size - max) as usize;
574 let hand: Vec<CardId> = game.cards_in_zone(ZoneType::Hand, active).to_vec();
575 agents[active.index()].snapshot_state(game, &self.mana_pools);
576 self.game_log.log(
577 GameLogEntryType::PriorityWaiting,
578 2,
579 format!(
580 "Waiting for {} discard decision (choose {})",
581 game.player(active).name,
582 to_discard
583 ),
584 );
585 let chosen = agents[active.index()].choose_discard(active, &hand, to_discard);
586 self.game_log.log(
587 GameLogEntryType::PriorityResponse,
588 2,
589 format!(
590 "{} selected {} card(s) to discard",
591 game.player(active).name,
592 chosen.len().min(to_discard)
593 ),
594 );
595 for card_id in chosen.iter().take(to_discard) {
596 if game.card(*card_id).zone == ZoneType::Hand {
597 game.discard_card(
598 *card_id,
599 active,
600 None,
601 Some(agents),
602 &mut self.trigger_handler,
603 );
604 }
605 }
606 }
607
608 self.cleanup_damage_and_eot(game);
612
613 self.trigger_handler.flush_waiting_triggers(game);
614 if game.stack.is_empty() && self.trigger_handler.pre_matched_trigger_count() == 0 {
615 break;
616 }
617 self.step_with_priority(game, agents, false);
618 is_repeat = true;
619 }
620
621 game.prevent_all_combat_damage = false;
625
626 game.end_turn_requested = false;
628 game.end_combat_requested = false;
629 game.extra_combat_phases = 0;
630 game.stack.reset_max_distinct_sources();
631
632 let active = game.active_player();
634 self.pool_mut(active).reset_pool();
635 self.trigger_handler.clear_this_turn_delayed_trigger();
636 }
637
638 fn cleanup_damage_and_eot(&mut self, game: &mut GameState) {
642 let temp_effect_ids: Vec<CardId> = game
645 .cards
646 .iter()
647 .filter(|c| c.zone == ZoneType::Command && c.temp_effect_until_eot)
648 .map(|c| c.id)
649 .collect();
650 for effect_id in temp_effect_ids {
651 if game.card(effect_id).zone == ZoneType::Command {
652 let controller = game.card(effect_id).controller;
653 game.remove_card_from_zone(ZoneType::Command, controller, effect_id);
654 game.card_mut(effect_id).set_zone(ZoneType::None);
655 }
656 }
657
658 let stolen: Vec<(CardId, crate::ids::PlayerId)> = game
660 .cards
661 .iter()
662 .filter(|c| c.zone == ZoneType::Battlefield && c.original_controller_eot.is_some())
663 .map(|c| (c.id, c.original_controller_eot.unwrap()))
664 .collect();
665 for (card_id, original) in stolen {
666 let current = game.card(card_id).controller;
667 game.card_mut(card_id).clear_original_controller_eot();
668 game.card_mut(card_id).clear_granted_keywords();
670 if current != original {
671 game.change_controller(card_id, original);
672 }
673 }
674
675 let player_ids = game.player_order.clone();
677 for player in player_ids {
678 game.player_cleanup_turn_state(player);
679 crate::player::expire_end_of_turn_keywords(game, player);
680 }
681
682 for i in 0..game.cards.len() {
683 let card_id = game.cards[i].id;
684 crate::ability::effects::change_text_effect::run(game, card_id);
685 }
686
687 for i in 0..game.cards.len() {
689 if game.cards[i].zone == ZoneType::Battlefield {
690 if let Some(state) = game.cards[i].animate_state.take() {
692 game.cards[i].restore_animate_snapshot(
693 state.original_type_line,
694 state.original_base_power,
695 state.original_base_toughness,
696 state.original_color,
697 );
698 game.cards[i].clear_damage();
699 }
700 if game.cards[i]
701 .clone_state
702 .as_ref()
703 .is_some_and(|state| state.expires_at_cleanup)
704 {
705 if let Some(state) = game.cards[i].clone_state.take() {
706 game.cards[i].restore_clone_snapshot(state);
707 }
708 }
709
710 game.cards[i].reset_turn_modifiers();
711 game.cards[i].cant_have_keywords.clear();
712 game.cards[i].clear_pump_keywords();
713 game.cards[i].clear_pump_triggers();
714 game.cards[i].clear_deathtouch_damage();
715 game.cards[i].reset_regeneration_shields();
716 game.cards[i].reset_shield_count();
717 game.cards[i].reset_crewed();
718
719 if game.cards[i].is_creature() {
720 let keep_damage =
721 crate::staticability::static_ability_no_cleanup_damage::damage_not_removed(
722 &game.cards,
723 &game.cards[i],
724 );
725 if !keep_damage {
726 game.cards[i].clear_damage();
727 }
728 game.cards[i].reset_turn_modifiers();
729 game.cards[i].clear_deathtouch_damage();
730 game.cards[i].reset_regeneration_shields();
731 game.cards[i].reset_shield_count();
732 game.cards[i].damage_history.new_turn();
733 }
734 game.cards[i].cant_have_keywords.clear();
742 game.cards[i].clear_pump_keywords();
743 game.cards[i].clear_pump_triggers();
744 } else {
745 game.cards[i].cant_have_keywords.clear();
746 game.cards[i].clear_pump_keywords();
747 game.cards[i].clear_pump_triggers();
748 }
749 }
750 }
751 pub(crate) fn emit_phase_trigger(&mut self, game: &mut GameState, phase: PhaseType) {
752 let active = game.active_player();
753 self.trigger_handler.run_trigger(
754 TriggerType::Phase,
755 RunParams {
756 phase: Some(phase),
757 player: Some(active),
758 ..Default::default()
759 },
760 false,
761 );
762 self.trigger_handler.run_trigger(
763 TriggerType::Always,
764 RunParams {
765 phase: Some(phase),
766 player: Some(active),
767 ..Default::default()
768 },
769 false,
770 );
771 }
772
773 fn clear_came_under_control_since_last_upkeep(&mut self, game: &mut GameState) {
774 let active = game.active_player();
775 let turn_number = game.turn.turn_number;
776 for card in &mut game.cards {
777 if card.controller == active
778 && card.zone == ZoneType::Battlefield
779 && card.turn_in_zone < turn_number
780 {
781 card.came_under_control_since_last_upkeep = false;
782 }
783 }
784 }
785
786 pub(crate) fn fire_combat_damage_triggers(&mut self, events: &[combat::CombatDamageEvent]) {
790 use crate::card::card_damage_map::{CardDamageMap, DamageTarget};
791 use crate::ids::{CardId, PlayerId};
792 use std::collections::HashMap;
793
794 for event in events {
796 self.trigger_handler.run_trigger(
797 TriggerType::DamageDone,
798 RunParams {
799 damage_source: Some(event.source),
800 damage_target_player: event.target_player,
801 damage_target_card: event.target_card,
802 damage_amount: Some(event.amount),
803 is_combat_damage: Some(event.is_combat),
804 ..Default::default()
805 },
806 false,
807 );
808 if let Some(player) = event.lifelink_player {
809 if event.lifelink_amount > 0 {
810 self.trigger_handler.run_trigger(
811 TriggerType::LifeGained,
812 RunParams {
813 player: Some(player),
814 life_amount: Some(event.lifelink_amount),
815 source_card: Some(event.source),
816 ..Default::default()
817 },
818 false,
819 );
820 }
821 }
822 }
823
824 #[derive(Hash, PartialEq, Eq, Clone, Copy)]
828 enum Target {
829 Card(CardId),
830 Player(PlayerId),
831 }
832 let mut by_target: HashMap<Target, i32> = HashMap::new();
833 let mut maps_by_target: HashMap<Target, CardDamageMap> = HashMap::new();
834 for event in events {
835 if event.amount <= 0 {
836 continue;
837 }
838 let target = if let Some(cid) = event.target_card {
839 Target::Card(cid)
840 } else if let Some(pid) = event.target_player {
841 Target::Player(pid)
842 } else {
843 continue;
844 };
845 *by_target.entry(target).or_insert(0) += event.amount;
846 let damage_target = match target {
847 Target::Card(cid) => DamageTarget::Card(cid),
848 Target::Player(pid) => DamageTarget::Player(pid),
849 };
850 maps_by_target.entry(target).or_default().put(
851 event.source,
852 damage_target,
853 event.amount,
854 );
855 }
856 for (target, total) in by_target {
857 if total <= 0 {
858 continue;
859 }
860 let params = match target {
861 Target::Card(cid) => RunParams {
862 damage_target_card: Some(cid),
863 damage_amount: Some(total),
864 damage_map: maps_by_target.get(&target).cloned(),
865 is_combat_damage: Some(true),
866 ..Default::default()
867 },
868 Target::Player(pid) => RunParams {
869 damage_target_player: Some(pid),
870 damage_amount: Some(total),
871 damage_map: maps_by_target.get(&target).cloned(),
872 is_combat_damage: Some(true),
873 ..Default::default()
874 },
875 };
876 self.trigger_handler
877 .run_trigger(TriggerType::DamageDoneOnce, params, false);
878 }
879 }
880
881 pub(crate) fn process_suspend_upkeep(
886 &mut self,
887 game: &mut GameState,
888 agents: &mut [Box<dyn PlayerAgent>],
889 ) {
890 let active = game.active_player();
891 let exile: Vec<CardId> = game.cards_in_zone(ZoneType::Exile, active).to_vec();
892 for card_id in exile {
893 let card = game.card(card_id);
894 if card.get_suspend_cost().is_none() {
896 continue;
897 }
898 let time_counters = *card
899 .counters
900 .get(&crate::card::CounterType::Time)
901 .unwrap_or(&0);
902 if time_counters <= 0 {
903 continue;
904 }
905 game.card_mut(card_id)
907 .remove_counter(&crate::card::CounterType::Time, 1);
908
909 self.trigger_handler.run_trigger(
911 TriggerType::CounterRemoved,
912 RunParams {
913 card: Some(card_id),
914 player: Some(active),
915 ..Default::default()
916 },
917 false,
918 );
919
920 let remaining = *game
921 .card(card_id)
922 .counters
923 .get(&crate::card::CounterType::Time)
924 .unwrap_or(&0);
925 if remaining <= 0 {
926 let card_name = game.card(card_id).card_name.clone();
928 let is_creature = game.card(card_id).is_creature();
929 let is_permanent = game.card(card_id).is_permanent();
930 game.player_record_spell_cast(active, card_id);
932
933 self.trigger_handler.run_trigger(
935 TriggerType::SpellCast,
936 RunParams {
937 spell_card: Some(card_id),
938 spell_controller: Some(active),
939 ..Default::default()
940 },
941 false,
942 );
943
944 let mut sa =
946 crate::spellability::build_spell_ability_for_card_cast(game, card_id, active);
947 sa.alt_cost = Some(crate::spellability::AlternativeCost::Suspend);
948 sa.setup_targets(game, agents, &self.mana_pools);
949
950 let entry = StackEntry {
951 id: 0,
952 spell_ability: sa,
953 is_pending_cast: false,
954 is_creature_spell: is_creature,
955 is_permanent_spell: is_permanent,
956 cast_from_zone: Some(ZoneType::Exile),
957 optional_trigger_decider: None,
958 optional_trigger_description: None,
959 optional_trigger_source_name: None,
960 };
961
962 game.stack.push(entry);
963 self.log_stack_push(&card_name, &game.player(active).name);
964 self.move_card_with_runtime(game, card_id, ZoneType::Stack, active, agents);
965 crate::agent::notify_all_agents(
966 agents,
967 crate::agent::GameLogEvent::stack(format!(
968 "Suspend: casting {} for free!",
969 card_name
970 ))
971 .with_player(active)
972 .with_card(card_id),
973 );
974
975 if is_creature && !game.card(card_id).has_haste() {
977 game.card_mut(card_id).granted_keywords.add("Haste");
978 }
979 }
980 }
981 }
982}