//! Flight state and controls for in-system gameplay. //! //! Handles the transition from docked to active flight mode, including //! ship controls, camera transitions, and flight state management. //! //! Navigation is point-and-click tactical mode: select targets and click //! "Approach" to auto-navigate. No WASD controls. use bevy::prelude::*; use crate::camera::{CameraState, CameraMode, OrbitFocusGoal}; use crate::gameplay::movement::components::{Velocity, MoveTarget}; use crate::gameplay::galaxy::Identifiable; use super::{DockedState, UndockEvent}; use super::scene::{Docked, PlayerShip}; // UI removed - no longer needed // use super::flight_ui::setup_flight_ui; /// Flight state component attached to the player ship when actively flying. #[derive(Component, Debug, Clone, Default)] pub struct FlightState { pub is_flying: bool, pub current_speed: f32, } /// Event fired when the player docks at a station. #[derive(Event, Debug, Clone)] pub struct DockEvent { pub ship: Entity, pub station: Entity, } /// Plugin for flight controls and state management. pub struct FlightControlsPlugin; impl Plugin for FlightControlsPlugin { fn build(&self, app: &mut App) { app.add_event::() .add_systems( Update, ( handle_undock, handle_docking, flight_input_system, ).run_if(in_state(crate::state::AppState::InGame)), ); } } /// Handle undock event: transition from docked to flight mode. fn handle_undock( mut commands: Commands, mut events: EventReader, mut docked_state: ResMut, mut camera_state: ResMut, mut focus_goal: ResMut, player_query: Query, With)>, // docked_ui_query removed - UI no longer needed ) { for event in events.read() { bevy::log::info!("Handling undock from station {:?}", event.station_entity); // Find player ship let Ok(player_entity) = player_query.single() else { bevy::log::warn!("No docked player ship found"); continue; }; // Remove docked component commands.entity(player_entity).remove::(); // Add flight state with initial velocity away from station let initial_speed = 10.0; // Start with slow drift commands.entity(player_entity).insert(( FlightState { is_flying: true, current_speed: initial_speed, }, Velocity(Vec3::new(0.0, 0.0, -1.0) * initial_speed), MoveTarget(Vec3::new(0.0, 0.0, -50.0)), // Initial target away from station super::SelectedTarget { entity: Entity::PLACEHOLDER, kind: super::TargetKind::Manual, name: "None".to_string(), }, )); // Update docked state resource docked_state.undock(); // Transition camera to follow mode: a tactical iso view tracking the // ship. Arm a focus goal so the distance eases from the docked framing // to the follow distance instead of hard-snapping. The orbit target is // left to `track_camera_target`, which locks it to the ship. camera_state.mode = CameraMode::Follow; camera_state.target_entity = Some(player_entity); camera_state.follow_distance = 45.0; // Higher for tactical view camera_state.follow_height = 35.0; // Isometric angle focus_goal.arm( None, Some(camera_state.follow_distance), Some(crate::camera::tactical_rotation()), ); // UI removed - gameplay only // setup_flight_ui(commands.reborrow()); // Despawn docked UI (commented out - UI being removed) // for entity in docked_ui_query.iter() { // commands.entity(entity).despawn(); // } bevy::log::info!("Transitioned to flight mode"); } } /// Handle docking event: transition from flight to docked mode. fn handle_docking( mut commands: Commands, mut events: EventReader, mut docked_state: ResMut, mut camera_state: ResMut, mut focus_goal: ResMut, identifiable_query: Query<&Identifiable>, // flight_ui_query removed - UI no longer needed ) { for event in events.read() { bevy::log::info!("Handling docking at target {:?}", event.station); let Ok(identifiable) = identifiable_query.get(event.station) else { bevy::log::warn!("Docking target has no Identifiable component"); continue; }; // Add docked component commands.entity(event.ship).insert(Docked { station_entity: event.station, }); // Remove flight state commands.entity(event.ship) .remove::(); // Update docked state resource docked_state.dock_at(event.station); // Transition camera back to the docked tactical framing (Orbit on the // station), easing the distance in. camera_state.mode = CameraMode::Orbit; camera_state.target_entity = Some(event.station); focus_goal.arm( None, Some(crate::camera::DOCKED_FRAMING_DISTANCE), Some(crate::camera::tactical_rotation()), ); // UI removed - no longer needed // Despawn flight HUD // for entity in flight_ui_query.iter() { // commands.entity(entity).despawn(); // } // Respawn docked UI // super::ui::setup_docked_ui(commands.reborrow()); bevy::log::info!("Docked at {}", identifiable.display_name); } } /// Flight input system: point-and-click navigation only. /// /// This system handles updating the flight state based on movement. /// Actual navigation is handled by click-to-move (setting MoveTarget) /// and the kinematic systems (steer_to_target, integrate_velocity). /// /// This is a minimal system - the real action happens in the movement systems. fn flight_input_system( mut player_query: Query<(&Velocity, &mut FlightState), With>, docked_state: Res, ) { // Only process flight input when not docked if docked_state.is_docked { return; } let Ok((velocity, mut flight_state)) = player_query.single_mut() else { return; }; // Update current speed from actual velocity flight_state.current_speed = velocity.0.length(); }