Files
Space-Game/apps/game/src/gameplay/in_system/mod.rs
francy51 7b9b892bfb feat(game): restore in-system HUD with reactive station↔flight panel swap (U1)
Basic UI Shell: bring back the in-game flight HUD and station info panel,
and implement the missing "station mode panel swap."

Implementation:
- New `in_system/hud.rs` with a reactive, idempotent `sync_hud_panels`
  system: it shows the station info panel while the player is `Docked` and
  the flight HUD while in `FlightState`, spawning/despawning only when the
  on-screen panel disagrees with the player's state. This replaces the old
  fragile event-driven spawn/despawn scattered through the transition systems.
- Re-enabled the docked info panel (`ui.rs`) and flight HUD (`flight_ui.rs`),
  restructured both as small corner-anchored overlays with NO full-screen
  root so they only block camera orbit input when the cursor is directly
  over a panel (orbit_camera_control suppresses input over any UI node).
- Made the docked panel info-only (actions live in the contextual action
  panel, which adapts to docked/flight modes).
- Fixed the broken undock chain: `start_undocking`/`start_travel` now take a
  real `now_ms` (was hardcoded `0.0`, completing instantly) and a new
  `complete_operations` bridge maps `OperationCompletedEvent::Undocking`
  back to `UndockEvent` so `handle_undock` actually runs.
- Wired `ActionType::Dock` to fire a `DockEvent` for a targeted station /
  habitable planet so the flight→docked swap is reachable too.
- `handle_docking` now also clears Velocity/MoveTarget/SelectedTarget so the
  docked ship stops cleanly.
- Fixed deprecated Bevy 0.16 APIs (single_mut, EventWriter::write,
  despawn) and removed now-dead code, dropping total warnings 183→137.

Compiles clean (`cargo check`); no new warnings in touched files.
2026-06-17 19:43:24 -04:00

191 lines
7.0 KiB
Rust

//! 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::<DockedState>()
.init_resource::<ActiveSystem>()
.add_event::<UndockEvent>()
.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<ActionTriggeredEvent>,
player_query: Query<Entity, With<scene::PlayerShip>>,
selected_target_query: Query<&SelectedTarget, With<scene::PlayerShip>>,
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<operations::OperationCompletedEvent>,
docked: Res<DockedState>,
mut undock_events: EventWriter<UndockEvent>,
) {
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<crate::gameplay::galaxy::Station>, Without<Targetable>)>,
belt_query: Query<(Entity, &crate::gameplay::galaxy::Identifiable), (With<crate::gameplay::galaxy::AsteroidBelt>, Without<Targetable>)>,
) {
// 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(),
});
}
}