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.
This commit is contained in:
2026-06-17 19:43:24 -04:00
parent 408bdb6dd7
commit 7b9b892bfb
6 changed files with 241 additions and 216 deletions

View File

@@ -8,11 +8,13 @@ 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;
@@ -22,7 +24,7 @@ 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, OperationKind, ActiveOperation};
pub use operations::TimedOperationPlugin;
pub struct InSystemPlugin;
@@ -37,29 +39,19 @@ impl Plugin for InSystemPlugin {
.add_plugins(TimedOperationPlugin)
.add_systems(
OnEnter(AppState::InGame),
(
scene::setup_in_system_view,
// UI removed - no longer needed
// ui::setup_docked_ui,
add_targetable_to_pois,
).chain(),
(scene::setup_in_system_view, add_targetable_to_pois).chain(),
)
.add_systems(
OnExit(AppState::InGame),
(
// UI removed - no longer needed
// ui::despawn_docked_ui,
// flight_ui::despawn_flight_ui,
scene::despawn_in_system_scene,
).chain(),
(hud::despawn_in_system_hud, scene::despawn_in_system_scene).chain(),
)
.add_systems(
Update,
(
// UI removed - no longer needed
// ui::refresh_docked_ui,
// ui::undock_button_handler,
// flight_ui::update_flight_ui,
hud::sync_hud_panels,
ui::refresh_docked_ui,
flight_ui::update_flight_ui,
complete_operations,
handle_action_triggered,
)
.chain()
@@ -68,38 +60,77 @@ impl Plugin for InSystemPlugin {
}
}
/// Handle action triggered events from the action buttons.
/// 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>>,
durations: Res<operations::OperationDurations>,
mut operation_events: EventWriter<operations::OperationStartedEvent>,
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, &durations, &mut operation_events);
operations::start_undocking(
&mut commands,
player_entity,
&dispatch.durations,
&mut dispatch.started,
now_ms,
);
}
ActionType::Approach => {
// Get selected target
if let Ok(selected) = selected_target_query.single() {
operations::start_travel(&mut commands, player_entity, selected.name.clone(), &durations, &mut operation_events);
operations::start_travel(
&mut commands,
player_entity,
selected.name.clone(),
&dispatch.durations,
&mut dispatch.started,
now_ms,
);
}
}
ActionType::Dock => {
// TODO: Implement docking operation
bevy::log::info!("Dock action triggered");
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 => {
// TODO: Implement mining operation
bevy::log::info!("Start mining action triggered");
}
_ => {
@@ -109,6 +140,29 @@ fn handle_action_triggered(
}
}
/// 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(