From 008f9cc24a69fed867bda84ec2af8d42d026565d Mon Sep 17 00:00:00 2001
From: Harald Hoyer <harald@hoyer.xyz>
Date: Wed, 16 Apr 2025 08:41:17 +0200
Subject: [PATCH] feat: add Captured and TractorBeam components, enhance enemy
 behavior with capture mechanics

---
 src/components.rs |  21 +++-
 src/constants.rs  |   7 ++
 src/enemy.rs      | 283 ++++++++++++++++++++++++++++++++++++----------
 src/main.rs       |  67 ++++-------
 src/player.rs     |  95 +++++++++++++++-
 5 files changed, 366 insertions(+), 107 deletions(-)

diff --git a/src/components.rs b/src/components.rs
index adc5021..974f1fa 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -27,6 +27,24 @@ pub struct Invincible {
     pub timer: Timer,
 }
 
+// New component to mark a player as captured by a Boss enemy
+#[derive(Component, Clone)] // Added Clone derive
+pub struct Captured {
+    // Reference to the capturing boss entity
+    pub boss_entity: Entity,
+    // Timer for how long the player remains captured
+    pub timer: Timer,
+}
+
+// New component for the tractor beam visual effect
+#[derive(Component)]
+pub struct TractorBeam {
+    pub target: Entity,  // The entity being targeted (usually player)
+    pub timer: Timer,    // How long the beam lasts
+    pub width: f32,      // Visual width of the beam
+    pub active: bool,    // Whether the beam is currently active
+}
+
 #[derive(Component)]
 pub struct FormationTarget {
     pub position: Vec3,
@@ -38,7 +56,8 @@ pub enum AttackPattern {
     SwoopDive,  // Original pattern: dive towards center, then off screen
     DirectDive, // Dive straight down
     Kamikaze(Vec3), // Dive towards a specific target location (e.g., player's last known position) - Needs target Vec3
-                    // Add more patterns later (e.g., FigureEight, Looping)
+    CaptureBeam, // New pattern: Boss dives and attempts to capture the player with a tractor beam
+                 // Add more patterns later (e.g., FigureEight, Looping)
 }
 
 #[derive(Component, Clone, PartialEq, Debug)] // Added Debug derive
diff --git a/src/constants.rs b/src/constants.rs
index 2b5bd8e..4c3ec09 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -1,4 +1,5 @@
 use bevy::math::Vec2;
+use bevy::prelude::*;
 
 // --- Constants ---
 pub const WINDOW_WIDTH: f32 = 600.0;
@@ -31,3 +32,9 @@ pub const BULLET_ENEMY_COLLISION_THRESHOLD: f32 = (BULLET_SIZE.x + ENEMY_SIZE.x)
 pub const PLAYER_ENEMY_COLLISION_THRESHOLD: f32 = (PLAYER_SIZE.x + ENEMY_SIZE.x) * 0.5;
 // 35.0
 pub const ENEMY_BULLET_PLAYER_COLLISION_THRESHOLD: f32 = (ENEMY_BULLET_SIZE.x + PLAYER_SIZE.x) * 0.5;
+
+// Tractor beam constants
+pub const TRACTOR_BEAM_WIDTH: f32 = 20.0;
+pub const TRACTOR_BEAM_DURATION: f32 = 3.0;
+pub const TRACTOR_BEAM_COLOR: Color = Color::rgba(0.5, 0.0, 0.8, 0.6);
+pub const CAPTURE_DURATION: f32 = 10.0; // How long the player stays captured
diff --git a/src/enemy.rs b/src/enemy.rs
index 8cda903..a497e15 100644
--- a/src/enemy.rs
+++ b/src/enemy.rs
@@ -1,9 +1,10 @@
 use bevy::prelude::*;
 use std::time::Duration;
 
-use crate::components::{Enemy, EnemyBullet, EnemyState, EnemyType, FormationTarget};
+use crate::components::{Enemy, EnemyBullet, EnemyState, EnemyType, FormationTarget, TractorBeam, Captured};
 use crate::constants::{ // Only keeping used constants
     ENEMY_BULLET_SIZE, ENEMY_SIZE, ENEMY_SPEED, WINDOW_HEIGHT, WINDOW_WIDTH,
+    TRACTOR_BEAM_WIDTH, TRACTOR_BEAM_DURATION, TRACTOR_BEAM_COLOR, CAPTURE_DURATION,
 };
 use crate::resources::{
     AttackDiveTimer, CurrentStage, EnemySpawnTimer, FormationState,
@@ -49,8 +50,15 @@ pub fn spawn_enemies(
         let spawn_x = (fastrand::f32() - 0.5) * (WINDOW_WIDTH - ENEMY_SIZE.x);
         let spawn_y = WINDOW_HEIGHT / 2.0 + ENEMY_SIZE.y / 2.0; // Spawn slightly above screen
 
-        // Determine enemy type (can be randomized or based on stage config later)
-        let enemy_type = EnemyType::Grunt;
+        // Determine enemy type - now with a chance to spawn Boss enemies
+        // Higher stages have a slightly higher boss chance
+        let boss_chance = 0.05 + (stage.number as f32 * 0.01).min(0.15);
+        let enemy_type = if fastrand::f32() < boss_chance {
+            println!("Spawning a Boss enemy!");
+            EnemyType::Boss
+        } else {
+            EnemyType::Grunt
+        };
 
         // Determine sprite color based on type
         let sprite_color = match enemy_type {
@@ -112,6 +120,7 @@ pub fn move_enemies(
     mut commands: Commands,
     stage: Res<CurrentStage>,
     stage_configs: Res<StageConfigurations>, // Add stage configurations
+    has_beam_query: Query<&TractorBeam>,
 ) {
     // Get current stage config for speed multiplier
     let config_index = (stage.number as usize - 1) % stage_configs.stages.len();
@@ -129,6 +138,7 @@ pub fn move_enemies(
         if *state == EnemyState::Entering {
             let current_pos = transform.translation;
             let target_pos = target.position;
+            // Using target_pos which is already a Vec3, not a reference
             let direction = target_pos - current_pos;
             let distance = direction.length();
 
@@ -150,56 +160,121 @@ pub fn move_enemies(
     }
 
     // --- Handle Attacking Enemies ---
-    // Note: attack_speed calculated above using multiplier
-    for (entity, mut transform, state, _enemy) in attacking_query.iter_mut() {
+    for (entity, mut transform, state, enemy) in attacking_query.iter_mut() {
+        // Check what state the enemy is in
+        if let EnemyState::Attacking(attack_pattern) = state {
+            // Apply different movement based on enemy type
+            match enemy.enemy_type {
+                EnemyType::Grunt => {
+                    // Basic enemies follow their attack pattern
+                    match attack_pattern {
+                        // ... existing patterns ...
+                        AttackPattern::SwoopDive => {
+                            // ... existing code ...
+                            let vertical_movement = attack_speed * time.delta_seconds();
+                            let horizontal_speed_factor = 0.5;
+                            let horizontal_movement = if transform.translation.x < 0.0 {
+                                attack_speed * horizontal_speed_factor * time.delta_seconds()
+                            } else if transform.translation.x > 0.0 {
+                                -attack_speed * horizontal_speed_factor * time.delta_seconds()
+                            } else { 0.0 };
 
-        // Match on the specific attack pattern using matches! and then get the pattern
-        if matches!(state, EnemyState::Attacking(_)) {
-             if let EnemyState::Attacking(pattern) = state { // Get the pattern safely now
-            let delta_seconds = time.delta_seconds();
+                            transform.translation.y -= vertical_movement;
+                            transform.translation.x += horizontal_movement;
 
-            match pattern {
-                AttackPattern::SwoopDive => {
-                    // Original Swooping Dive Logic
-                    let vertical_movement = attack_speed * delta_seconds;
-                    let horizontal_speed_factor = 0.5;
-                    let horizontal_movement = if transform.translation.x < 0.0 {
-                        attack_speed * horizontal_speed_factor * delta_seconds
-                    } else if transform.translation.x > 0.0 {
-                        -attack_speed * horizontal_speed_factor * delta_seconds
-                    } else { 0.0 };
+                            // Prevent overshooting center
+                            if (transform.translation.x > 0.0 && transform.translation.x + horizontal_movement < 0.0) ||
+                               (transform.translation.x < 0.0 && transform.translation.x + horizontal_movement > 0.0) {
+                                transform.translation.x = 0.0;
+                            }
+                        }
+                        AttackPattern::DirectDive => {
+                            transform.translation.y -= attack_speed * time.delta_seconds();
+                        }
+                        AttackPattern::Kamikaze(target) => {
+                            // Copy the target value rather than dereferencing
+                            // since target should actually be a Vec3 in this context
+                            let target_pos = *target; // Dereference here
+                            let direction = target_pos - transform.translation;
+                            let distance = direction.length();
+                            let kamikaze_threshold = attack_speed * time.delta_seconds() * 1.1; // Threshold to stop near target
 
-                    transform.translation.y -= vertical_movement;
-                    transform.translation.x += horizontal_movement;
-
-                    // Prevent overshooting center
-                    if (transform.translation.x > 0.0 && transform.translation.x + horizontal_movement < 0.0) ||
-                       (transform.translation.x < 0.0 && transform.translation.x + horizontal_movement > 0.0) {
-                        transform.translation.x = 0.0;
+                            if distance > kamikaze_threshold {
+                                let move_delta = direction.normalize() * attack_speed * time.delta_seconds();
+                                transform.translation += move_delta;
+                            } else {
+                                // Optionally stop or continue past target - for now, just stop moving towards it
+                                // Could also despawn here if desired upon reaching target
+                            }
+                        }
+                        // New CaptureBeam pattern - Bosses behave differently
+                        AttackPattern::CaptureBeam => {
+                            // For Grunt enemies, just do a direct dive (fallback)
+                            transform.translation.y -= attack_speed * time.delta_seconds();
+                        }
                     }
                 }
-                AttackPattern::DirectDive => {
-                    // Move straight down
-                    transform.translation.y -= attack_speed * delta_seconds;
-                }
-                AttackPattern::Kamikaze(target_pos) => {
-                    // Move towards the target position
-                    let direction = *target_pos - transform.translation;
-                    let distance = direction.length();
-                    let kamikaze_threshold = attack_speed * delta_seconds * 1.1; // Threshold to stop near target
-
-                    if distance > kamikaze_threshold {
-                        let move_delta = direction.normalize() * attack_speed * delta_seconds;
-                        transform.translation += move_delta;
-                    } else {
-                        // Optionally stop or continue past target - for now, just stop moving towards it
-                        // Could also despawn here if desired upon reaching target
+                EnemyType::Boss => {
+                    // Boss has special behavior, especially for CaptureBeam
+                    match attack_pattern {
+                        AttackPattern::CaptureBeam => {
+                            // Boss moves down to a position above the player area
+                            let target_y = -WINDOW_HEIGHT / 4.0; // Position at lower quarter of the screen
+                            
+                            if transform.translation.y > target_y {
+                                // Move down to position
+                                transform.translation.y -= attack_speed * 0.8 * time.delta_seconds();
+                            } else {
+                                // Once in position, stay there briefly before activating beam
+                                // Check if this boss already has a TractorBeam component
+                                if has_beam_query.get(entity).is_err() {
+                                    // Spawn tractor beam component on this boss
+                                    commands.entity(entity).insert(TractorBeam {
+                                        target: Entity::PLACEHOLDER, // Will be filled in by the boss_capture_attack
+                                        timer: Timer::new(Duration::from_secs_f32(TRACTOR_BEAM_DURATION), TimerMode::Once),
+                                        width: TRACTOR_BEAM_WIDTH,
+                                        active: false,
+                                    });
+                                }
+                            }
+                        }
+                        AttackPattern::SwoopDive => {
+                            // ... existing code for swoop dive ...
+                            let center_x = 0.0;
+                            let bottom_y = -WINDOW_HEIGHT / 2.0 - ENEMY_SIZE.y;
+            
+                            // First move towards center-bottom
+                            let target = Vec3::new(center_x, bottom_y, 0.0);
+                            // target is directly created as Vec3, not a reference
+                            let direction = target - transform.translation;
+                            
+                            // Normalize and move
+                            if direction.length() > 0.0 {
+                                let normalized_dir = direction.normalize();
+                                transform.translation += normalized_dir * attack_speed * time.delta_seconds();
+                            }
+                        }
+                        AttackPattern::DirectDive => {
+                            transform.translation.y -= attack_speed * time.delta_seconds();
+                        }
+                        AttackPattern::Kamikaze(target) => {
+                            // Convert the target to a value type
+                            let target_pos = *target; // Dereference here
+                            let direction = target_pos - transform.translation;
+                            
+                            // If very close to target, just move straight down
+                            if direction.length() < 50.0 {
+                                transform.translation.y -= attack_speed * time.delta_seconds();
+                            } else {
+                                // Move toward target
+                                let normalized_dir = direction.normalize();
+                                transform.translation += normalized_dir * attack_speed * time.delta_seconds();
+                            }
+                        }
                     }
                 }
-                // Add cases for other patterns here
-             } // Close inner if let
-            } // Closes match enemy.enemy_type
-        } // Closes if *state == EnemyState::Attacking
+            }
+        }
 
         // Despawn if off screen (This should be inside the loop)
         if transform.translation.y < -WINDOW_HEIGHT / 2.0 - ENEMY_SIZE.y {
@@ -259,13 +334,12 @@ pub fn check_formation_complete(
     }
 }
 
-use crate::components::AttackPattern; // Import the new enum
-use crate::components::Player; // Import Player for Kamikaze target
+use crate::components::{AttackPattern, Player}; // Import the new enum and Player
 
 pub fn trigger_attack_dives(
     mut timer: ResMut<AttackDiveTimer>,
     time: Res<Time>,
-    mut enemy_query: Query<(Entity, &mut EnemyState), With<Enemy>>, // Renamed for clarity
+    mut enemy_query: Query<(Entity, &mut EnemyState, &Enemy)>, // Added Enemy component to check type
     formation_state: Res<FormationState>,
     stage: Res<CurrentStage>, // Need current stage
     stage_configs: Res<StageConfigurations>, // Need stage configs
@@ -280,22 +354,37 @@ pub fn trigger_attack_dives(
         let current_config = &stage_configs.stages[config_index];
 
         // Find all enemies currently in formation
-        let mut available_enemies: Vec<Entity> = Vec::new();
-        for (entity, state) in enemy_query.iter() {
-            // Check the state correctly
+        let mut available_enemies: Vec<(Entity, EnemyType)> = Vec::new();
+        for (entity, state, enemy) in enemy_query.iter() {
+            // Check the state correctly and store enemy type
             if matches!(state, EnemyState::InFormation) {
-                available_enemies.push(entity);
+                available_enemies.push((entity, enemy.enemy_type));
             }
         }
 
         // If there are enemies available, pick one randomly
         if !available_enemies.is_empty() && !current_config.attack_patterns.is_empty() {
             let random_index = fastrand::usize(..available_enemies.len());
-            let chosen_entity = available_enemies[random_index];
+            let (chosen_entity, enemy_type) = available_enemies[random_index];
 
-            // Select a random attack pattern for this stage
-            let pattern_index = fastrand::usize(..current_config.attack_patterns.len());
-            let mut selected_pattern = current_config.attack_patterns[pattern_index]; // Copy the pattern
+            // Select an attack pattern based on enemy type
+            let mut selected_pattern = match enemy_type {
+                // For Boss enemies, occasionally use the CaptureBeam pattern
+                EnemyType::Boss => {
+                    if fastrand::f32() < 0.4 { // 40% chance for Boss to use CaptureBeam
+                        AttackPattern::CaptureBeam
+                    } else {
+                        // Otherwise use a random pattern from the stage config
+                        let pattern_index = fastrand::usize(..current_config.attack_patterns.len());
+                        current_config.attack_patterns[pattern_index]
+                    }
+                },
+                // Regular enemies use patterns from the stage config
+                EnemyType::Grunt => {
+                    let pattern_index = fastrand::usize(..current_config.attack_patterns.len());
+                    current_config.attack_patterns[pattern_index]
+                }
+            };
 
             // If Kamikaze, get player position (if player exists)
             if let AttackPattern::Kamikaze(_) = selected_pattern {
@@ -308,9 +397,8 @@ pub fn trigger_attack_dives(
                 }
             }
 
-
             // Get the chosen enemy's state mutably and change it
-            if let Ok((_, mut state)) = enemy_query.get_mut(chosen_entity) {
+            if let Ok((_, mut state, _)) = enemy_query.get_mut(chosen_entity) {
                 println!("Enemy {:?} starting attack dive with pattern {:?}!", chosen_entity, selected_pattern);
                 *state = EnemyState::Attacking(selected_pattern); // Set state with pattern
                 // Timer duration is handled elsewhere (e.g., check_formation_complete)
@@ -360,4 +448,81 @@ pub fn enemy_shoot(
 // New run condition: Check if the formation is complete
 pub fn is_formation_complete(formation_state: Res<FormationState>) -> bool {
     formation_state.formation_complete
+}
+
+// New system to handle the tractor beam attack from Boss enemies
+pub fn boss_capture_attack(
+    mut commands: Commands,
+    time: Res<Time>,
+    mut boss_query: Query<(Entity, &Transform, &mut TractorBeam)>,
+    player_query: Query<(Entity, &Transform), (With<Player>, Without<Captured>)>, // Only target non-captured players
+    has_beam_query: Query<&TractorBeam>,
+) {
+    for (boss_entity, boss_transform, mut tractor_beam) in boss_query.iter_mut() {
+        // Tick the beam timer
+        tractor_beam.timer.tick(time.delta());
+        
+        // If player exists and beam is not active yet, set player as target
+        if !tractor_beam.active && tractor_beam.target == Entity::PLACEHOLDER {
+            if let Ok((player_entity, _)) = player_query.get_single() {
+                tractor_beam.target = player_entity;
+                tractor_beam.active = true;
+                println!("Boss {:?} activated tractor beam targeting player!", boss_entity);
+                
+                // Create visual beam effect (using a simple sprite for now)
+                let beam_height = boss_transform.translation.y - (-WINDOW_HEIGHT / 2.0); // Height from boss to bottom of screen
+                
+                // Spawn the beam as a child of the boss
+                commands.entity(boss_entity).with_children(|parent| {
+                    parent.spawn(SpriteBundle {
+                        sprite: Sprite {
+                            color: TRACTOR_BEAM_COLOR,
+                            custom_size: Some(Vec2::new(tractor_beam.width, beam_height)),
+                            ..default()
+                        },
+                        transform: Transform::from_xyz(0.0, -beam_height/2.0, 0.0),
+                        ..default()
+                    });
+                });
+            }
+        }
+        
+        // If beam is active, check if player is in beam's path
+        if tractor_beam.active {
+            if let Ok((player_entity, player_transform)) = player_query.get_single() {
+                // Check if player is roughly under the boss
+                if (player_transform.translation.x - boss_transform.translation.x).abs() < tractor_beam.width / 2.0 {
+                    // Player is in the beam! Capture them
+                    println!("Player captured by boss {:?}!", boss_entity);
+                    
+                    // Add Captured component to player
+                    commands.entity(player_entity).insert(Captured {
+                        boss_entity,
+                        timer: Timer::new(Duration::from_secs_f32(CAPTURE_DURATION), TimerMode::Once),
+                    });
+                    
+                    // Boss returns to formation with captured player
+                    commands.entity(boss_entity).remove::<TractorBeam>();
+                    
+                    // TODO: Implement logic for boss to return to formation
+                    // For now, just despawn the boss to simplify
+                    commands.entity(boss_entity).despawn_recursive();
+                    break;
+                }
+            }
+        }
+        
+        // If beam timer finishes and player wasn't captured, end the beam attack
+        if tractor_beam.timer.finished() {
+            println!("Boss {:?} tractor beam expired", boss_entity);
+            commands.entity(boss_entity).remove::<TractorBeam>();
+            
+            // If we had a proper visual beam, we'd despawn it here
+            // For now, just make sure we clean up by despawning all children
+            commands.entity(boss_entity).despawn_descendants();
+            
+            // For simplicity, after beam attack the boss flies off-screen
+            // In a more complete implementation, it might return to formation
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 8a1ba75..e074663 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,11 +21,11 @@ use game_state::{
 };
 use player::{
     check_player_enemy_collisions, manage_invincibility, move_player, player_shoot,
-    respawn_player,
+    respawn_player, handle_captured_player,
 };
 use enemy::{
     check_formation_complete, enemy_shoot, is_formation_complete, move_enemies, spawn_enemies,
-    trigger_attack_dives,
+    trigger_attack_dives, boss_capture_attack,
 };
 use bullet::{
     check_bullet_collisions, check_enemy_bullet_player_collisions, move_bullets,
@@ -36,75 +36,67 @@ use systems::{player_exists, player_vulnerable, setup, should_respawn_player, up
 
 fn main() {
     App::new()
-        .init_state::<AppState>() // Initialize the AppState
-        .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
+        .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.1)))
         .add_plugins(DefaultPlugins.set(WindowPlugin {
             primary_window: Some(Window {
-                title: "Galaga :: Stage: 1 Lives: 3 Score: 0".into(), // Initial title
+                title: "BGLGA".into(),
                 resolution: (WINDOW_WIDTH, WINDOW_HEIGHT).into(),
+                resizable: false,
                 ..default()
             }),
             ..default()
         }))
-        // Add Resources
-        .insert_resource(PlayerLives {
-            count: STARTING_LIVES,
+        // Add states
+        .init_state::<AppState>() // Changed from add_state to init_state
+        // Initialize game resources
+        .insert_resource(EnemySpawnTimer {
+            timer: Timer::new(Duration::from_secs_f32(1.0), TimerMode::Once),
         })
+        .insert_resource(PlayerLives { count: STARTING_LIVES })
         .insert_resource(PlayerRespawnTimer {
             timer: Timer::new(
                 Duration::from_secs_f32(PLAYER_RESPAWN_DELAY),
                 TimerMode::Once,
             ),
         })
-        .insert_resource(EnemySpawnTimer {
-            timer: Timer::new(Duration::from_secs_f32(0.5), TimerMode::Repeating),
-        })
         .insert_resource(Score { value: 0 })
         .insert_resource(CurrentStage {
             number: 1,
             waiting_for_clear: false,
         })
         .insert_resource(FormationState {
-            next_slot_index: 0,
-            total_spawned_this_stage: 0,
             formation_complete: false,
-        })
-        .insert_resource(StageConfigurations::default()) // Add stage configurations
-        .insert_resource(AttackDiveTimer {
-            timer: { // Correctly assign the block expression to the timer field
-                let mut timer = Timer::new(Duration::from_secs_f32(3.0), TimerMode::Repeating); // Default duration, will be overwritten by stage config
-                timer.pause(); // Start paused
-                timer
-            }
+            total_spawned_this_stage: 0,
+            next_slot_index: 0,
         })
         .insert_resource(AttackDiveTimer {
-            timer: {
-                let mut timer = Timer::new(Duration::from_secs_f32(3.0), TimerMode::Repeating);
-                timer.pause(); // Start paused
-                timer
-            },
+            timer: Timer::new(Duration::from_secs_f32(3.0), TimerMode::Once),
         })
-        // Add Systems
+        .insert_resource(StageConfigurations::default()) // Use default stages for now
+        // Add startup systems
         .add_systems(Startup, setup)
-        // Systems running only when Playing
+        // Core game systems
         .add_systems(
             Update,
             (
-                // Player systems
-                move_player,
+                update_window_title,
+                // Enemy and player systems
+                spawn_enemies,
+                move_player.run_if(player_exists),
                 player_shoot.run_if(player_exists),
                 check_player_enemy_collisions.run_if(player_vulnerable),
                 respawn_player.run_if(should_respawn_player),
                 manage_invincibility,
+                handle_captured_player, // New system for handling captured player
                 // Bullet systems
                 move_bullets,
                 check_bullet_collisions,
                 move_enemy_bullets,
                 check_enemy_bullet_player_collisions.run_if(player_vulnerable),
                 // Enemy systems
-                spawn_enemies,
                 move_enemies,
                 enemy_shoot, // Consider run_if attacking state? (Handled internally for now)
+                boss_capture_attack, // New system for boss tractor beam
             )
                 .run_if(in_state(AppState::Playing)),
         )
@@ -119,18 +111,9 @@ fn main() {
                 .chain() // Ensure these run in order if needed, check_formation first
                 .run_if(in_state(AppState::Playing)),
         )
-        // Systems running regardless of state (or managing state transitions)
-        .add_systems(
-            Update,
-            (
-                update_window_title, // Keep title updated
-                // TODO: Add system to check for restart input in GameOver state
-                bevy::window::close_on_esc, // Allow closing anytime
-            ),
-        )
-        // Systems for entering/exiting states
+        // UI and state management systems
         .add_systems(OnEnter(AppState::GameOver), setup_game_over_ui)
+        .add_systems(OnExit(AppState::Playing), cleanup_game_entities)
         .add_systems(OnExit(AppState::GameOver), cleanup_game_over_ui)
-        .add_systems(OnExit(AppState::Playing), cleanup_game_entities) // Cleanup when leaving Playing
         .run();
 }
diff --git a/src/player.rs b/src/player.rs
index 3554e8a..acb1b96 100644
--- a/src/player.rs
+++ b/src/player.rs
@@ -1,7 +1,7 @@
 use bevy::prelude::*;
 use std::time::Duration;
 
-use crate::components::{Bullet, Enemy, Invincible, Player};
+use crate::components::{Bullet, Enemy, Invincible, Player, Captured};
 use crate::constants::{
     BULLET_SIZE, PLAYER_ENEMY_COLLISION_THRESHOLD, PLAYER_INVINCIBILITY_DURATION, PLAYER_SIZE,
     PLAYER_SPEED, WINDOW_HEIGHT, WINDOW_WIDTH,
@@ -42,7 +42,7 @@ pub fn spawn_player_ship(commands: &mut Commands) {
 
 pub fn move_player(
     keyboard_input: Res<ButtonInput<KeyCode>>,
-    mut query: Query<(&mut Transform, &Player)>,
+    mut query: Query<(&mut Transform, &Player), Without<Captured>>, // Don't move captured players with controls
     time: Res<Time>,
 ) {
     // Using get_single_mut handles the case where player might not exist yet (or was just destroyed)
@@ -66,9 +66,94 @@ pub fn move_player(
     }
 }
 
+// New system to handle captured player movement
+pub fn handle_captured_player(
+    mut commands: Commands,
+    time: Res<Time>,
+    player_query: Query<(Entity, &Transform, &Captured)>,
+    mut player_mut_query: Query<(&mut Transform, &mut Captured)>,
+    enemy_query: Query<&Transform, With<Enemy>>,
+    mut lives: ResMut<PlayerLives>,
+    mut respawn_timer: ResMut<PlayerRespawnTimer>,
+    mut next_state: ResMut<NextState<AppState>>,
+) {
+    // First, collect data from all captured players
+    let mut to_process = Vec::new();
+    
+    for (entity, transform, captured) in player_query.iter() {
+        // Check if the boss exists
+        let boss_exists = enemy_query.get(captured.boss_entity).is_ok();
+        let boss_pos = if boss_exists {
+            enemy_query.get(captured.boss_entity).map(|t| t.translation).ok()
+        } else {
+            None
+        };
+        
+        // Create a copy of the timer to check if it would finish
+        let mut timer_copy = captured.timer.clone();
+        timer_copy.tick(time.delta());
+        
+        to_process.push((entity, transform.translation, boss_pos, timer_copy.finished()));
+    }
+    
+    // Now process each player separately
+    for (entity, current_pos, boss_pos_opt, timer_would_finish) in to_process {
+        if let Ok((mut transform, mut captured)) = player_mut_query.get_mut(entity) {
+            // Tick the real timer
+            captured.timer.tick(time.delta());
+            
+            match boss_pos_opt {
+                Some(boss_pos) => {
+                    // Boss exists, update player position
+                    let target_pos = boss_pos - Vec3::new(0.0, PLAYER_SIZE.y + 10.0, 0.0);
+                    transform.translation = current_pos.lerp(target_pos, 0.2);
+                },
+                None => {
+                    // Boss is gone, release player but lose a life
+                    println!("Boss is gone, releasing captured player!");
+                    commands.entity(entity).remove::<Captured>();
+                    lose_life_and_respawn(&mut commands, &mut lives, &mut respawn_timer, &mut next_state, entity);
+                }
+            }
+            
+            // If capture duration expires, player escapes but loses a life
+            if timer_would_finish || captured.timer.finished() {
+                println!("Player escaped from capture after timer expired!");
+                commands.entity(entity).remove::<Captured>();
+                lose_life_and_respawn(&mut commands, &mut lives, &mut respawn_timer, &mut next_state, entity);
+            }
+        }
+    }
+}
+
+// Helper function for player life loss and respawn logic
+fn lose_life_and_respawn(
+    commands: &mut Commands,
+    lives: &mut ResMut<PlayerLives>,
+    respawn_timer: &mut ResMut<PlayerRespawnTimer>,
+    next_state: &mut ResMut<NextState<AppState>>,
+    player_entity: Entity,
+) {
+    // Lose a life
+    lives.count = lives.count.saturating_sub(1);
+    println!("Lives remaining: {}", lives.count);
+    
+    // Destroy player
+    commands.entity(player_entity).despawn();
+    
+    if lives.count > 0 {
+        respawn_timer.timer.reset();
+        respawn_timer.timer.unpause();
+        println!("Respawn timer started.");
+    } else {
+        println!("GAME OVER!");
+        next_state.set(AppState::GameOver);
+    }
+}
+
 pub fn player_shoot(
     keyboard_input: Res<ButtonInput<KeyCode>>,
-    mut query: Query<(&Transform, &mut Player)>, // Should only run if player exists due to run_if
+    mut query: Query<(&Transform, &mut Player), Without<Captured>>, // Only non-captured players can shoot
     mut commands: Commands,
     time: Res<Time>,
 ) {
@@ -107,7 +192,7 @@ pub fn check_player_enemy_collisions(
     mut respawn_timer: ResMut<PlayerRespawnTimer>,
     mut next_state: ResMut<NextState<AppState>>, // Resource to change state
     // Query player without Invincible component - relies on run_if condition too
-    player_query: Query<(Entity, &Transform), (With<Player>, Without<Invincible>)>,
+    player_query: Query<(Entity, &Transform), (With<Player>, Without<Invincible>, Without<Captured>)>, // Don't check collisions for captured players
     enemy_query: Query<(Entity, &Transform), With<Enemy>>,
 ) {
     // This system only runs if player exists and is not invincible, due to run_if
@@ -133,7 +218,7 @@ pub fn check_player_enemy_collisions(
                     println!("Respawn timer started.");
                 } else {
                     println!("GAME OVER!");
-                    next_state.set(AppState::GameOver); // Transition to GameOver state
+                    next_state.set(AppState::GameOver); // Updated for newer Bevy states API
                 }
                 // Important: Break after handling one collision per frame for the player
                 break;