Files
Space-Game/apps/game/src/gameplay/starting_base/mod.rs
francy51 30b6678569 feat(camera): comprehensive camera system (C1)
Rebuild the camera around a single persistent MainCamera and the
Camera System design-doc model: two framing modes (Orbit = inspection/
docked tactical, Follow = track the ship) plus Cinematic as a boolean
overlay (free rotation + HUD hide + live gameplay), not a third mode.

Critical fixes (gap analysis A1-A3):
- A1: scenes now *reconfigure* the one startup camera instead of spawning
  a second one; despawn_in_system_scene no longer destroys MainCamera, so
  the session never loses its camera. .single_mut() on MainCamera now
  succeeds during flight.
- A2: Cinematic is a real overlay — toggle (KeyC), free-look rotation in
  any framing, HUD hidden while active, gameplay keeps running.
- A3: removed dead FollowCamera component + setup_follow_camera; tracking
  is unified in track_camera_target.

Gaps B1-B6:
- B1: adopted doc model (Cinematic overlay, not exclusive mode).
- B2: canonical isometric tactical_rotation() baseline.
- B3: smooth reframing via OrbitFocusGoal exponential-damp tween.
- B4: non-zero CameraState defaults.
- B5: consolidated the three orbit-control impls into one (dropped the
  starting_base local control + its Euler variant).
- B6: track_camera_target keeps OrbitCamera.target synced to the focus
  entity every frame.

Docked view now frames the actual station at a tactical iso distance.
cargo check + clippy clean for all newly-authored code; net -10 lines
(more dead code removed than added). 42 tests pass.
2026-06-16 20:05:31 -04:00

100 lines
2.7 KiB
Rust

//! Starting base selection scene.
//!
//! After character creation, the player chooses a starting base from generated
//! outer-galaxy systems. The choice is kept in a resource until persistence is
//! wired into the game client.
mod scene;
mod ui;
use bevy::prelude::*;
use crate::gameplay::galaxy::{StartingBaseCandidate, orbits};
use crate::state::AppState;
// Public exports for POI spawning
pub use scene::{SpawnedPoiSystem, StartingBasePoi};
pub struct StartingBasePlugin;
impl Plugin for StartingBasePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<StartingBaseDraft>()
.init_resource::<scene::SpawnedPoiSystem>()
.add_systems(
OnEnter(AppState::StartingBaseSelection),
(scene::setup_starting_base_scene, ui::setup_starting_base_ui).chain(),
)
.add_systems(
OnExit(AppState::StartingBaseSelection),
(despawn_starting_base_ui, scene::despawn_pois_on_exit).chain(),
)
.add_systems(
Update,
(
escape_to_character_creation,
ui::candidate_button_handler,
scene::focus_starting_base_camera,
scene::animate_starting_base_selection,
ui::scroll_starting_base_panels,
ui::refresh_starting_base_ui,
ui::action_button_handler,
scene::spawn_selected_pois,
orbits::advance_orbital_paths,
)
.chain()
.run_if(in_state(AppState::StartingBaseSelection)),
);
}
}
#[derive(Resource, Default)]
pub struct StartingBaseDraft {
pub candidates: Vec<StartingBaseCandidate>,
pub selected_index: Option<usize>,
}
#[derive(Resource, Debug, Clone)]
pub struct StartingBaseSelection {
pub system_id: String,
pub system_name: String,
pub faction: &'static str,
pub security: f32,
}
#[derive(Resource, Debug, Clone, Copy)]
pub struct StartingBaseFocusRequest {
pub candidate_index: usize,
}
#[derive(Component)]
pub struct StartingBaseSpawned;
#[derive(Component)]
pub struct StartingBaseInputBlocker;
#[derive(Component, Clone, Copy)]
pub enum StartingBaseButton {
Candidate(usize),
Confirm,
Back,
}
fn despawn_starting_base_ui(
mut commands: Commands,
query: Query<Entity, With<StartingBaseSpawned>>,
) {
for entity in &query {
commands.entity(entity).despawn();
}
}
fn escape_to_character_creation(
keys: Res<ButtonInput<KeyCode>>,
mut next_state: ResMut<NextState<AppState>>,
) {
if keys.just_pressed(KeyCode::Escape) {
next_state.set(AppState::CharacterCreation);
}
}