//! In-system gameplay module. //! //! Handles the system-scale view where the player is docked at a station //! within their selected starting system. This is the entry point to actual //! gameplay after onboarding. mod actions; mod docked; mod flight; mod flight_ui; mod hud; mod operations; mod scene; mod target; mod ui; use bevy::ecs::system::SystemParam; use bevy::prelude::*; use crate::state::AppState; pub use docked::{DockedState, UndockEvent}; pub use flight::{FlightState, FlightControlsPlugin}; pub use scene::ActiveSystem; pub use target::{Targetable, TargetKind, SelectedTarget, TargetSelectionPlugin}; pub use actions::{ContextualActionPlugin, ActionType, ActionTriggeredEvent, ActionUi}; pub use operations::TimedOperationPlugin; pub struct InSystemPlugin; impl Plugin for InSystemPlugin { fn build(&self, app: &mut App) { app.init_resource::() .init_resource::() .add_event::() .add_plugins(FlightControlsPlugin) .add_plugins(TargetSelectionPlugin) .add_plugins(ContextualActionPlugin) .add_plugins(TimedOperationPlugin) .add_systems( OnEnter(AppState::InGame), (scene::setup_in_system_view, add_targetable_to_pois).chain(), ) .add_systems( OnExit(AppState::InGame), (hud::despawn_in_system_hud, scene::despawn_in_system_scene).chain(), ) .add_systems( Update, ( hud::sync_hud_panels, ui::refresh_docked_ui, flight_ui::update_flight_ui, complete_operations, handle_action_triggered, ) .chain() .run_if(in_state(AppState::InGame)), ); } } /// Bundles the resources/event-writers [`handle_action_triggered`] dispatches /// through, keeping its parameter list under the system-argument threshold. #[derive(SystemParam)] struct ActionDispatch<'w> { time: Res<'w, Time>, durations: Res<'w, operations::OperationDurations>, started: EventWriter<'w, operations::OperationStartedEvent>, dock: EventWriter<'w, flight::DockEvent>, } /// Handle action triggered events from the contextual action buttons. /// /// - **Undock** starts a timed operation; on completion [`complete_operations`] /// fires the [`UndockEvent`] that drives the docked → flight transition. /// - **Dock** fires a [`flight::DockEvent`] directly (instant) for the selected /// station / habitable planet so the flight → docked transition (and the HUD /// swap back to the station panel) is reachable. fn handle_action_triggered( mut commands: Commands, mut events: EventReader, player_query: Query>, selected_target_query: Query<&SelectedTarget, With>, mut dispatch: ActionDispatch, ) { let Ok(player_entity) = player_query.single() else { return; }; let now_ms = dispatch.time.elapsed_secs_f64() * 1000.0; for event in events.read() { bevy::log::info!("Action triggered: {:?}", event.action_type); match event.action_type { ActionType::Undock => { operations::start_undocking( &mut commands, player_entity, &dispatch.durations, &mut dispatch.started, now_ms, ); } ActionType::Approach => { if let Ok(selected) = selected_target_query.single() { operations::start_travel( &mut commands, player_entity, selected.name.clone(), &dispatch.durations, &mut dispatch.started, now_ms, ); } } ActionType::Dock => { if let Ok(selected) = selected_target_query.single() { match selected.kind { TargetKind::Station | TargetKind::HabitablePlanet => { bevy::log::info!("Docking at {}", selected.name); dispatch.dock.write(flight::DockEvent { ship: player_entity, station: selected.entity, }); } kind => { bevy::log::info!("Cannot dock at {:?} target", kind); } } } } ActionType::StartMining => { bevy::log::info!("Start mining action triggered"); } _ => { bevy::log::info!("Other action: {:?}", event.action_type); } } } } /// Bridge completed timed operations back into the docked ↔ flight state /// machine. Today only undocking is wired — when the timed undock completes it /// fires the [`UndockEvent`] consumed by [`flight::handle_undock`]. Other /// operation kinds are logged for future work. fn complete_operations( mut completed: EventReader, docked: Res, mut undock_events: EventWriter, ) { for event in completed.read() { bevy::log::info!( "Operation completed: {:?} on entity {:?}", event.kind, event.entity ); if let operations::OperationKind::Undocking = event.kind { undock_events.write(UndockEvent { station_entity: docked.station_entity.unwrap_or(Entity::PLACEHOLDER), }); } } } /// Add Targetable components to spawned POIs (stations and asteroid belts) /// so they can be selected by the target selection system. fn add_targetable_to_pois( mut commands: Commands, station_query: Query<(Entity, &crate::gameplay::galaxy::Identifiable), (With, Without)>, belt_query: Query<(Entity, &crate::gameplay::galaxy::Identifiable), (With, Without)>, ) { // Add Targetable to stations for (entity, identifiable) in station_query.iter() { bevy::log::debug!("Adding Targetable component to station: {}", identifiable.display_name); commands.entity(entity).insert(Targetable { kind: TargetKind::Station, name: identifiable.display_name.clone(), }); } // Add Targetable to asteroid belts for (entity, identifiable) in belt_query.iter() { bevy::log::debug!("Adding Targetable component to asteroid belt: {}", identifiable.display_name); commands.entity(entity).insert(Targetable { kind: TargetKind::AsteroidBelt, name: identifiable.display_name.clone(), }); } }