|
1 | 1 | # Priority System |
2 | 2 |
|
3 | | -This document details the implementation of the priority system in Rummage, which determines which player can take actions at any given time during a game of Magic: The Gathering. |
| 3 | +This document describes the implementation of Magic: The Gathering's priority system in Rummage. |
4 | 4 |
|
5 | 5 | ## Overview |
6 | 6 |
|
7 | | -The priority system is a fundamental part of Magic: The Gathering's turn structure. It determines when players can cast spells, activate abilities, and take other game actions. Understanding and correctly implementing the priority system is essential for proper game flow. |
| 7 | +The priority system determines when players can take actions during a game of Magic: The Gathering. It's a fundamental component that controls the game's flow and ensures players have appropriate opportunities to act. |
8 | 8 |
|
9 | 9 | ## Core Priority Rules |
10 | 10 |
|
11 | | -The basic rules of priority in MTG are: |
| 11 | +In Magic: The Gathering, priority follows these key rules: |
12 | 12 |
|
13 | | -1. The active player receives priority first in each step and phase |
14 | | -2. When a player has priority, they may: |
15 | | - - Cast a spell |
16 | | - - Activate an ability |
17 | | - - Take a special action |
18 | | - - Pass priority |
19 | | -3. When a player passes priority, the next player in turn order receives priority |
20 | | -4. When all players pass priority in succession: |
21 | | - - If the stack is empty, the current step or phase ends |
22 | | - - If the stack has objects, the top object on the stack resolves, then the active player gets priority again |
| 13 | +1. The active player receives priority first in each step and phase. |
| 14 | +2. When a player has priority, they may cast spells, activate abilities, or pass. |
| 15 | +3. When a player passes priority, the next player in turn order receives priority. |
| 16 | +4. When all players pass priority in succession with an empty stack, the current step or phase ends. |
| 17 | +5. When all players pass priority in succession with objects on the stack, the top object on the stack resolves, then the active player receives priority. |
23 | 18 |
|
24 | | -## Implementation |
| 19 | +## Implementation Details |
25 | 20 |
|
26 | | -In Rummage, the priority system is implemented as follows: |
| 21 | +The priority system in Rummage is implemented through a combination of resources and systems: |
27 | 22 |
|
28 | 23 | ```rust |
29 | | -#[derive(Resource)] |
30 | | -pub struct PrioritySystem { |
31 | | - // The player who currently has priority |
| 24 | +#[derive(Resource, Debug, Clone)] |
| 25 | +pub struct PriorityManager { |
32 | 26 | pub current_player: Entity, |
33 | | - |
34 | | - // Set of players who have passed priority in succession |
35 | | - pub passed_players: HashSet<Entity>, |
36 | | - |
37 | | - // Whether the priority system is currently active |
38 | | - pub active: bool, |
39 | | -} |
40 | | - |
41 | | -#[derive(Event)] |
42 | | -pub struct PriorityEvent { |
43 | | - pub player: Entity, |
44 | | - pub action: PriorityAction, |
| 27 | + pub all_passed: bool, |
| 28 | + pub stack_empty_when_passed: bool, |
| 29 | + pub last_to_act: Option<Entity>, |
| 30 | + pub player_order: Vec<Entity>, |
45 | 31 | } |
46 | 32 |
|
47 | | -#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
48 | | -pub enum PriorityAction { |
49 | | - Receive, // Player receives priority |
50 | | - Pass, // Player passes priority |
51 | | - TakeAction, // Player takes an action (cast spell, activate ability, etc.) |
52 | | -} |
53 | | -``` |
54 | | - |
55 | | -## Priority Flow |
56 | | - |
57 | | -The flow of priority follows this pattern: |
58 | | - |
59 | | -1. **Phase/Step Start**: At the beginning of each phase or step, the active player receives priority |
60 | | -2. **Action Taken**: If a player takes an action, all players who have passed are reset, and priority returns to the active player |
61 | | -3. **Passing**: When a player passes, the next player in turn order receives priority |
62 | | -4. **Resolution**: When all players pass in succession, either the top of the stack resolves or the phase/step ends |
63 | | - |
64 | | -### Priority System Implementation |
65 | | - |
66 | | -```rust |
67 | | -pub fn handle_priority_system( |
68 | | - mut commands: Commands, |
69 | | - mut priority: ResMut<PrioritySystem>, |
70 | | - mut priority_events: EventReader<PriorityEvent>, |
71 | | - game_state: Res<GameState>, |
72 | | - stack: Res<Stack>, |
73 | | -) { |
74 | | - for event in priority_events.iter() { |
75 | | - match event.action { |
76 | | - PriorityAction::Pass => { |
77 | | - // Record that this player passed |
78 | | - priority.passed_players.insert(event.player); |
| 33 | +impl PriorityManager { |
| 34 | + pub fn new(starting_player: Entity, player_order: Vec<Entity>) -> Self { |
| 35 | + PriorityManager { |
| 36 | + current_player: starting_player, |
| 37 | + all_passed: false, |
| 38 | + stack_empty_when_passed: true, |
| 39 | + last_to_act: None, |
| 40 | + player_order, |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + pub fn pass_priority(&mut self, stack: &Stack) -> PriorityResult { |
| 45 | + let current_index = self.player_order |
| 46 | + .iter() |
| 47 | + .position(|&p| p == self.current_player) |
| 48 | + .expect("Current player not found in player order"); |
| 49 | + |
| 50 | + let next_index = (current_index + 1) % self.player_order.len(); |
| 51 | + let next_player = self.player_order[next_index]; |
| 52 | + |
| 53 | + // Record if this is the last player to act |
| 54 | + if let Some(last_player) = self.last_to_act { |
| 55 | + if last_player == self.current_player { |
| 56 | + // All players have passed priority |
| 57 | + self.all_passed = true; |
| 58 | + self.stack_empty_when_passed = stack.is_empty(); |
79 | 59 |
|
80 | | - // Check if all players have passed |
81 | | - if priority.passed_players.len() == game_state.players.len() { |
82 | | - // All players have passed |
83 | | - if !stack.items.is_empty() { |
84 | | - // Resolve top of stack |
85 | | - commands.add(resolve_stack_command()); |
86 | | - |
87 | | - // Reset passed players |
88 | | - priority.passed_players.clear(); |
89 | | - |
90 | | - // Active player gets priority again |
91 | | - priority.current_player = game_state.active_player; |
92 | | - } else { |
93 | | - // Stack is empty, end current phase/step |
94 | | - commands.add(advance_phase_command()); |
95 | | - |
96 | | - // Priority will be set by the phase transition system |
97 | | - priority.active = false; |
98 | | - } |
| 60 | + if stack.is_empty() { |
| 61 | + return PriorityResult::EndPhase; |
99 | 62 | } else { |
100 | | - // Not all players have passed, give priority to next player |
101 | | - let next_player = get_next_player(event.player, &game_state); |
102 | | - priority.current_player = next_player; |
| 63 | + return PriorityResult::ResolveStack; |
103 | 64 | } |
104 | | - }, |
105 | | - PriorityAction::TakeAction => { |
106 | | - // Player took an action, reset passed players |
107 | | - priority.passed_players.clear(); |
108 | | - |
109 | | - // Active player gets priority again |
110 | | - priority.current_player = game_state.active_player; |
111 | | - }, |
112 | | - PriorityAction::Receive => { |
113 | | - // Player receives priority (usually at beginning of phase/step) |
114 | | - priority.current_player = event.player; |
115 | | - priority.active = true; |
116 | 65 | } |
| 66 | + } else { |
| 67 | + // First player to pass |
| 68 | + self.last_to_act = Some(self.current_player); |
117 | 69 | } |
| 70 | + |
| 71 | + // Pass to next player |
| 72 | + self.current_player = next_player; |
| 73 | + PriorityResult::Continue |
| 74 | + } |
| 75 | + |
| 76 | + pub fn reset_for_new_phase(&mut self, active_player: Entity) { |
| 77 | + self.current_player = active_player; |
| 78 | + self.all_passed = false; |
| 79 | + self.stack_empty_when_passed = true; |
| 80 | + self.last_to_act = None; |
| 81 | + } |
| 82 | + |
| 83 | + pub fn reset_after_stack_resolution(&mut self, active_player: Entity) { |
| 84 | + self.current_player = active_player; |
| 85 | + self.all_passed = false; |
| 86 | + self.last_to_act = None; |
118 | 87 | } |
119 | 88 | } |
120 | | -``` |
121 | | - |
122 | | -## Special Priority Rules |
123 | | - |
124 | | -### No Priority Phases |
125 | 89 |
|
126 | | -Some steps do not normally grant players priority: |
127 | | - |
128 | | -- **Untap Step**: No player receives priority during this step |
129 | | -- **Cleanup Step**: No player receives priority unless a triggered ability triggers |
130 | | - |
131 | | -```rust |
132 | | -pub fn should_grant_priority(phase: Phase, step: Step) -> bool { |
133 | | - match (phase, step) { |
134 | | - (Phase::Beginning, Step::Untap) => false, |
135 | | - (Phase::Ending, Step::Cleanup) => false, |
136 | | - _ => true, |
137 | | - } |
| 90 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 91 | +pub enum PriorityResult { |
| 92 | + Continue, // Continue with priority passing |
| 93 | + EndPhase, // End the current phase |
| 94 | + ResolveStack, // Resolve the top of the stack |
138 | 95 | } |
139 | 96 | ``` |
140 | 97 |
|
141 | | -### Triggered Abilities During No-Priority Steps |
| 98 | +## Priority Systems |
| 99 | + |
| 100 | +The following systems manage priority in the game: |
142 | 101 |
|
143 | | -If a triggered ability triggers during a step where players don't normally receive priority, players will receive priority: |
| 102 | +1. **Initialize Priority System**: Sets up priority at the beginning of a phase |
| 103 | +2. **Handle Priority Actions System**: Processes actions from the player with priority |
| 104 | +3. **Pass Priority System**: Handles the passing of priority between players |
| 105 | +4. **Stack Resolution System**: Resolves stack objects when all players pass |
144 | 106 |
|
145 | 107 | ```rust |
146 | | -pub fn handle_cleanup_triggers( |
| 108 | +pub fn handle_priority_actions( |
147 | 109 | mut commands: Commands, |
148 | | - mut priority: ResMut<PrioritySystem>, |
| 110 | + priority: Res<PriorityManager>, |
| 111 | + mut action_events: EventReader<PlayerAction>, |
| 112 | + mut pass_events: EventWriter<PassPriorityEvent>, |
| 113 | + mut stack: ResMut<Stack>, |
149 | 114 | game_state: Res<GameState>, |
150 | | - triggers: Res<TriggeredAbilities>, |
151 | 115 | ) { |
152 | | - // If we're in cleanup step and there are triggers |
153 | | - if game_state.current_phase == Phase::Ending && |
154 | | - game_state.current_step == Step::Cleanup && |
155 | | - !triggers.pending.is_empty() { |
| 116 | + for action in action_events.read() { |
| 117 | + if action.player != priority.current_player { |
| 118 | + // Only the player with priority can act |
| 119 | + continue; |
| 120 | + } |
156 | 121 |
|
157 | | - // Grant priority to active player |
158 | | - priority.active = true; |
159 | | - priority.current_player = game_state.active_player; |
160 | | - priority.passed_players.clear(); |
| 122 | + match &action.action_type { |
| 123 | + ActionType::CastSpell { card, targets } => { |
| 124 | + // Handle casting a spell |
| 125 | + // ... |
| 126 | + }, |
| 127 | + ActionType::ActivateAbility { source, ability_id, targets } => { |
| 128 | + // Handle activating an ability |
| 129 | + // ... |
| 130 | + }, |
| 131 | + ActionType::PlayLand { card } => { |
| 132 | + // Handle playing a land |
| 133 | + // ... |
| 134 | + }, |
| 135 | + ActionType::Pass => { |
| 136 | + // Player passes priority |
| 137 | + pass_events.send(PassPriorityEvent { |
| 138 | + player: action.player, |
| 139 | + phase: game_state.current_phase.clone(), |
| 140 | + }); |
| 141 | + }, |
| 142 | + // Other action types... |
| 143 | + } |
161 | 144 | } |
162 | 145 | } |
163 | 146 | ``` |
164 | 147 |
|
165 | | -## APNAP Order |
| 148 | +## Special Phase Rules |
166 | 149 |
|
167 | | -When multiple players would receive priority simultaneously (such as for triggered abilities), they are processed in APNAP (Active Player, Non-Active Player) order: |
| 150 | +Priority is handled differently in certain phases and steps: |
| 151 | + |
| 152 | +1. **Untap Step**: No player receives priority |
| 153 | +2. **Cleanup Step**: No player receives priority unless a triggered ability triggers |
| 154 | +3. **Combat Damage Step**: Players receive priority after combat damage is dealt |
168 | 155 |
|
169 | 156 | ```rust |
170 | | -pub fn get_players_in_apnap_order(game_state: &GameState) -> Vec<Entity> { |
171 | | - let mut players = Vec::new(); |
172 | | - |
173 | | - // Start with active player |
174 | | - let mut current = game_state.active_player; |
175 | | - players.push(current); |
176 | | - |
177 | | - // Add remaining players in turn order |
178 | | - for _ in 1..game_state.players.len() { |
179 | | - current = get_next_player(current, game_state); |
180 | | - players.push(current); |
| 157 | +pub fn should_receive_priority(phase: &Phase) -> bool { |
| 158 | + match phase { |
| 159 | + Phase::Beginning(BeginningStep::Untap) => false, |
| 160 | + Phase::Ending(EndingStep::Cleanup) => false, |
| 161 | + _ => true, |
181 | 162 | } |
182 | | - |
183 | | - players |
184 | 163 | } |
185 | 164 | ``` |
186 | 165 |
|
187 | 166 | ## Integration with Other Systems |
188 | 167 |
|
189 | | -The priority system integrates with: |
190 | | - |
191 | | -1. **Turn Structure**: Phase and step transitions affect priority |
192 | | -2. **Stack System**: Stack resolution and priority are tightly coupled |
193 | | -3. **Action System**: Player actions affect priority flow |
194 | | -4. **UI System**: The UI must indicate which player has priority |
| 168 | +The priority system integrates closely with several other game systems: |
195 | 169 |
|
196 | | -## Implementation Status |
| 170 | +- **Turn Structure**: Controls when phases begin and end based on priority passing |
| 171 | +- **Stack**: Determines when objects on the stack resolve |
| 172 | +- **Triggered Abilities**: Manages when triggered abilities are put on the stack |
| 173 | +- **State-Based Actions**: Checks whenever a player would receive priority |
197 | 174 |
|
198 | | -The priority system implementation currently: |
| 175 | +## Format-Specific Extensions |
199 | 176 |
|
200 | | -- ✅ Handles basic priority passing |
201 | | -- ✅ Integrates with stack resolution |
202 | | -- ✅ Implements APNAP order |
203 | | -- ✅ Handles special steps without priority |
204 | | -- ✅ Supports triggered abilities during cleanup |
205 | | -- 🔄 Implementing special actions that don't use the stack |
206 | | -- 🔄 Handling priority with split second spells |
| 177 | +For Commander-specific priority implementation details, see [Commander Priority System](../../formats/commander/turns_and_phases/priority_system.md). |
207 | 178 |
|
208 | | ---- |
| 179 | +## Related Documentation |
209 | 180 |
|
210 | | -Next: [Turn Phases](phases.md) |
| 181 | +- [Turn Structure](index.md): How turns are structured |
| 182 | +- [Stack](../stack/index.md): How the stack handles spell and ability resolution |
| 183 | +- [Commander Priority System](../../formats/commander/turns_and_phases/priority_system.md): Commander-specific priority rules |
0 commit comments