First Strike Damage
Overview
The First Strike Damage step is a conditional sub-step within the Combat Damage step that occurs when at least one creature with first strike or double strike is involved in combat. During this step, only creatures with first strike or double strike deal combat damage, while other creatures wait until the regular Combat Damage step.
This document outlines the implementation details, edge cases, and testing strategies for the First Strike Damage step in our Commander engine.
Core Concepts
First Strike Damage Flow
The First Strike Damage step follows this general flow:
- Check if any attacking or blocking creatures have first strike or double strike
- If yes, create a dedicated First Strike Damage step before the regular Combat Damage step
- Only creatures with first strike or double strike assign and deal damage in this step
- State-based actions are checked
- Triggers from first strike damage are put on the stack
- Priority passes to players in turn order
- Once all players pass priority and the stack is empty, the game proceeds to the regular Combat Damage step
First Strike vs. Double Strike
The key difference between first strike and double strike:
- Creatures with first strike deal damage only during the First Strike Damage step
- Creatures with double strike deal damage in both the First Strike Damage step and the regular Combat Damage step
Implementation Design
Data Structures
#![allow(unused)] fn main() { // Components for first strike and double strike abilities #[derive(Component)] struct FirstStrike; #[derive(Component)] struct DoubleStrike; // System resource for tracking the first strike step struct FirstStrikeDamageSystem { triggers_processed: bool, } }
First Strike Checking System
#![allow(unused)] fn main() { fn check_for_first_strike_step( mut turn_manager: ResMut<TurnManager>, first_strike_query: Query<Entity, Or<(With<FirstStrike>, With<DoubleStrike>)>>, combat_participants: Query<Entity, Or<(With<Attacking>, With<Blocking>)>> ) { // Only check when transitioning from Declare Blockers to Combat Damage if !matches!(turn_manager.current_phase, Phase::Combat(CombatStep::DeclareBlockers)) { return; } // Check if any creature in combat has first strike or double strike let has_first_strike = combat_participants.iter().any(|entity| { first_strike_query.contains(entity) }); // If there are creatures with first strike, we'll use the FirstStrike step if has_first_strike { turn_manager.next_phase = Some(Phase::Combat(CombatStep::FirstStrike)); } else { turn_manager.next_phase = Some(Phase::Combat(CombatStep::CombatDamage)); } } }
First Strike Damage System
#![allow(unused)] fn main() { fn first_strike_damage_system( mut commands: Commands, mut combat_system: ResMut<CombatSystem>, turn_manager: Res<TurnManager>, creature_query: Query<(Entity, &Creature, &Health, Option<&FirstStrike>, Option<&DoubleStrike>)>, attacker_query: Query<(Entity, &Attacking)>, blocker_query: Query<(Entity, &Blocking)>, player_query: Query<(Entity, &Player, &Health)>, mut commander_damage: Query<&mut CommanderDamage>, // Other system parameters ) { // Only run during first strike damage step if !matches!(turn_manager.current_phase, Phase::Combat(CombatStep::FirstStrike)) { return; } // Find creatures with first strike or double strike that are in combat let first_strike_creatures = creature_query .iter() .filter(|(entity, _, _, first_strike, double_strike)| { (first_strike.is_some() || double_strike.is_some()) && (attacker_query.contains(*entity) || blocker_query.contains(*entity)) }) .collect::<Vec<_>>(); // Assign and deal damage for (entity, creature, _, _, _) in first_strike_creatures { // Determine damage amount and recipients let power = creature.power; if let Ok((_, attacking)) = attacker_query.get(entity) { // Attacking creature deals damage assign_attacker_damage( entity, power, attacking.defending, &blocker_query, &mut combat_system ); } else if let Ok((_, blocking)) = blocker_query.get(entity) { // Blocking creature deals damage assign_blocker_damage( entity, power, &blocking.blocked_attackers, &mut combat_system ); } } // Deal all assigned damage simultaneously process_damage_assignments(&mut commands, &combat_system, &mut commander_damage); // Check for state-based actions check_state_based_actions(&mut commands, &player_query, &creature_query); // Record that first strike damage has been processed combat_system.first_strike_round_completed = true; } }
Special Cases and Edge Scenarios
Gaining/Losing First Strike During Combat
Creatures might gain or lose first strike abilities during combat:
#![allow(unused)] fn main() { fn handle_first_strike_changes( mut first_strike_system: ResMut<FirstStrikeDamageSystem>, turn_manager: Res<TurnManager>, added_first_strike: Query<Entity, Added<FirstStrike>>, removed_first_strike: Query<Entity, Without<FirstStrike>>, combat_participants: Query<Entity, Or<(With<Attacking>, With<Blocking>)>>, was_in_combat: Local<HashSet<Entity>> ) { // Check if any creature in combat gained or lost first strike let gained_first_strike = added_first_strike.iter() .filter(|&entity| combat_participants.contains(entity)) .count() > 0; let lost_first_strike = removed_first_strike.iter() .filter(|&entity| was_in_combat.contains(&entity)) .count() > 0; // If changes occurred during declare blockers step, we might need to adjust // whether a first strike step will occur if matches!(turn_manager.current_phase, Phase::Combat(CombatStep::DeclareBlockers)) { if gained_first_strike { first_strike_system.needed = true; } } // Update our tracking of combat participants was_in_combat.clear(); for entity in combat_participants.iter() { was_in_combat.insert(entity); } } }
Removing Creatures During First Strike
If a creature is destroyed by first strike damage, it doesn't deal regular combat damage:
#![allow(unused)] fn main() { fn track_destroyed_by_first_strike( combat_system: Res<CombatSystem>, mut destroyed_entities: ResMut<DestroyedEntities>, damage_events: Query<&DamageEvent>, creature_query: Query<&Health> ) { // Only track during first strike damage if !combat_system.first_strike_round_completed { return; } // Find creatures that received lethal damage during first strike for damage_event in damage_events.iter() { if let Ok(health) = creature_query.get(damage_event.target) { if health.current <= 0 { destroyed_entities.insert(damage_event.target); } } } } }
Fast Effect Windows
Players get priority after first strike damage, allowing them to cast spells before regular damage:
#![allow(unused)] fn main() { fn handle_first_strike_priority( mut turn_manager: ResMut<TurnManager>, stack: Res<Stack>, combat_system: Res<CombatSystem> ) { // Only handle during first strike step if !matches!(turn_manager.current_phase, Phase::Combat(CombatStep::FirstStrike)) { return; } // If first strike damage has been dealt and the stack is empty, // we can proceed to regular combat damage if combat_system.first_strike_round_completed && stack.is_empty() { turn_manager.advance_phase(); } } }
Testing Strategy
Unit Tests
#![allow(unused)] fn main() { #[test] fn test_first_strike_damage() { // Set up test world let mut app = App::new(); app.add_plugins(MinimalPlugins) .add_systems(Update, first_strike_damage_system); // Create attacker with first strike let attacker = app.world.spawn(( Creature { power: 3, toughness: 3 }, Attacking { defending: player_entity }, FirstStrike, )).id(); // Create blocker without first strike let blocker = app.world.spawn(( Creature { power: 2, toughness: 2 }, Blocking { blocked_attackers: vec![attacker] }, Health { current: 2, maximum: 2 }, )).id(); // Process first strike damage app.world.resource_mut::<TurnManager>().current_phase = Phase::Combat(CombatStep::FirstStrike); app.update(); // Verify blocker received damage and was destroyed let health = app.world.get::<Health>(blocker).unwrap(); assert_eq!(health.current, 0); // Received 3 damage from first strike attacker // Verify no damage was dealt to attacker yet (since blocker doesn't have first strike) let attacker_health = app.world.get::<Health>(attacker).unwrap(); assert_eq!(attacker_health.current, attacker_health.maximum); } #[test] fn test_double_strike_damage() { // Test that double strike creatures deal damage in both steps // ... } #[test] fn test_first_strike_detection() { // Test that the system correctly detects when a first strike step is needed // ... } }
Integration Tests
#![allow(unused)] fn main() { #[test] fn test_first_strike_vs_regular_damage() { // Test complete sequence with both first strike and regular damage // ... } #[test] fn test_gaining_first_strike_mid_combat() { // Test gaining first strike during combat // ... } #[test] fn test_losing_first_strike_mid_combat() { // Test losing first strike during combat // ... } }
Edge Case Tests
#![allow(unused)] fn main() { #[test] fn test_first_strike_with_triggered_abilities() { // Test interaction between first strike damage and triggered abilities // ... } #[test] fn test_first_strike_with_damage_prevention() { // Test first strike damage with damage prevention effects // ... } #[test] fn test_first_strike_with_damage_redirection() { // Test first strike damage with redirection effects // ... } }
Performance Considerations
-
Conditional Step Creation: Only create the First Strike Damage step when needed.
-
Efficient Entity Filtering: Optimize filtering of entities with first strike/double strike.
-
Damage Assignment Optimization: Reuse damage assignment logic between first strike and regular damage steps.
-
State Tracking: Efficiently track state between first strike and regular damage steps.
Conclusion
The First Strike Damage step is a specialized combat sub-step that adds strategic depth to the combat system. By correctly implementing first strike and double strike mechanics, we ensure that these powerful combat abilities function as expected according to the Magic: The Gathering rules. The implementation must handle all edge cases, including mid-combat changes to first strike status, while maintaining good performance and seamless integration with the rest of the combat system.