abstract vessel/systems

This commit is contained in:
geoffsee
2025-07-03 11:30:39 -04:00
parent 5711d84698
commit f0935f2b54
10 changed files with 77 additions and 227 deletions

View File

@@ -1,15 +1,16 @@
#![allow(clippy::type_complexity)]
pub mod player;
pub mod systems;
pub mod yacht_systems;
mod marine;
use marine::*;
// Re-export components from the components crate
pub use components::{
setup_instrument_cluster, update_instrument_displays, update_yacht_data, YachtData,
setup_instrument_cluster, update_instrument_displays, update_vessel_data, VesselData,
SpeedGauge, DepthGauge, CompassGauge, EngineStatus, NavigationDisplay,
InstrumentCluster, GpsIndicator, RadarIndicator, AisIndicator, SystemDisplay
};
pub use player::{get_yacht_systems, setup_instrument_cluster_system, PlayerPlugin};
pub use yacht_systems::{create_yacht_systems, AisSystem, GpsSystem, RadarSystem, SystemInteraction, SystemStatus, YachtSystem};
pub use player::{get_vessel_systems, setup_instrument_cluster_system, PlayerPlugin};
pub use vessel_systems::{create_vessel_systems, AisSystem, GpsSystem, RadarSystem, SystemInteraction, SystemStatus, VesselSystem};

View File

@@ -0,0 +1 @@
pub mod vessel_systems;

View File

@@ -1,12 +1,12 @@
//! Concrete implementations of yacht systems using the SystemManager abstraction
//! Concrete implementations of vessel systems using the SystemManager abstraction
//!
//! This module provides implementations of the YachtSystem trait for GPS, Radar, and AIS systems,
//! This module provides implementations of the VesselSystem trait for GPS, Radar, and AIS systems,
//! bridging the existing functionality with the new higher-level abstraction.
use bevy::prelude::*;
use components::YachtData;
use components::VesselData;
/// Status of a yacht system
/// Status of a vessel system
#[derive(Debug, Clone, PartialEq)]
pub enum SystemStatus {
Active,
@@ -25,11 +25,11 @@ pub enum SystemInteraction {
}
/// Common trait for all yacht systems
pub trait YachtSystem: Send + Sync {
pub trait VesselSystem: Send + Sync {
fn id(&self) -> &'static str;
fn display_name(&self) -> &'static str;
fn update(&mut self, yacht_data: &YachtData, time: &Time);
fn render_display(&self, yacht_data: &YachtData) -> String;
fn update(&mut self, yacht_data: &VesselData, time: &Time);
fn render_display(&self, yacht_data: &VesselData) -> String;
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool;
fn status(&self) -> SystemStatus;
}
@@ -51,7 +51,7 @@ impl GpsSystem {
}
}
impl YachtSystem for GpsSystem {
impl VesselSystem for GpsSystem {
fn id(&self) -> &'static str {
"gps"
}
@@ -60,14 +60,14 @@ impl YachtSystem for GpsSystem {
"GPS Navigation"
}
fn update(&mut self, _yacht_data: &YachtData, time: &Time) {
fn update(&mut self, _yacht_data: &VesselData, time: &Time) {
// Simulate satellite connection variations
let t = time.elapsed_secs();
self.satellites_connected = (12.0 + (t * 0.1).sin() * 2.0).max(8.0) as u8;
self.hdop = 0.8 + (t * 0.05).sin() * 0.2;
}
fn render_display(&self, yacht_data: &YachtData) -> String {
fn render_display(&self, yacht_data: &VesselData) -> String {
format!(
"GPS NAVIGATION SYSTEM\n\n\
Position: 43°38'19.5\"N 1°26'58.3\"W\n\
@@ -140,7 +140,7 @@ impl RadarSystem {
}
}
impl YachtSystem for RadarSystem {
impl VesselSystem for RadarSystem {
fn id(&self) -> &'static str {
"radar"
}
@@ -149,12 +149,12 @@ impl YachtSystem for RadarSystem {
"Radar System"
}
fn update(&mut self, _yacht_data: &YachtData, time: &Time) {
fn update(&mut self, _yacht_data: &VesselData, time: &Time) {
// Update radar sweep angle
self.sweep_angle = (time.elapsed_secs() * 60.0) % 360.0;
}
fn render_display(&self, _yacht_data: &YachtData) -> String {
fn render_display(&self, _yacht_data: &VesselData) -> String {
format!(
"RADAR SYSTEM - {:.0} NM RANGE\n\n\
Status: {}\n\
@@ -257,7 +257,7 @@ impl AisSystem {
}
}
impl YachtSystem for AisSystem {
impl VesselSystem for AisSystem {
fn id(&self) -> &'static str {
"ais"
}
@@ -266,12 +266,12 @@ impl YachtSystem for AisSystem {
"AIS System"
}
fn update(&mut self, _yacht_data: &YachtData, _time: &Time) {
fn update(&mut self, _yacht_data: &VesselData, _time: &Time) {
// AIS system is relatively static, but we could simulate
// vessel movements or signal strength variations here
}
fn render_display(&self, _yacht_data: &YachtData) -> String {
fn render_display(&self, _yacht_data: &VesselData) -> String {
format!(
"AIS - AUTOMATIC IDENTIFICATION SYSTEM\n\n\
Status: {}\n\
@@ -345,7 +345,7 @@ impl YachtSystem for AisSystem {
}
/// Helper function to create and register all yacht systems
pub fn create_yacht_systems() -> Vec<Box<dyn YachtSystem>> {
pub fn create_vessel_systems() -> Vec<Box<dyn VesselSystem>> {
vec![
Box::new(GpsSystem::new()),
Box::new(RadarSystem::new()),
@@ -364,7 +364,7 @@ mod tests {
assert_eq!(gps.display_name(), "GPS Navigation");
assert_eq!(gps.status(), SystemStatus::Active);
let yacht_data = YachtData::default();
let yacht_data = VesselData::default();
let display = gps.render_display(&yacht_data);
assert!(display.contains("GPS NAVIGATION SYSTEM"));
assert!(display.contains("Satellites: 12 connected"));
@@ -378,7 +378,7 @@ mod tests {
// Test configuration
assert!(radar.handle_interaction(SystemInteraction::Configure("range".to_string(), "24".to_string())));
let display = radar.render_display(&YachtData::default());
let display = radar.render_display(&VesselData::default());
assert!(display.contains("24 NM RANGE"));
}
@@ -395,7 +395,7 @@ mod tests {
#[test]
fn test_create_yacht_systems() {
let systems = create_yacht_systems();
let systems = create_vessel_systems();
assert_eq!(systems.len(), 3);
let ids: Vec<&str> = systems.iter().map(|s| s.id()).collect();

View File

@@ -1,27 +1,26 @@
use bevy::prelude::*;
use components::{setup_instrument_cluster, YachtData, update_yacht_data, update_instrument_displays};
use super::yacht_systems::{create_yacht_systems, YachtSystem};
use components::{setup_instrument_cluster, VesselData, update_vessel_data, update_instrument_displays};
use super::vessel_systems::{create_vessel_systems, VesselSystem};
pub struct PlayerPlugin;
/// This plugin handles the futuristic yacht instrument cluster
/// The main app should handle state management and system registration
/// bind domain to bevy
impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<YachtData>()
app.init_resource::<VesselData>()
.add_systems(
Update,
(update_yacht_data, update_instrument_displays)
(update_vessel_data, update_instrument_displays)
);
}
}
/// Setup function for instrument cluster - to be called by the main app
/// Setup function called by the main app
pub fn setup_instrument_cluster_system() -> impl Fn(Commands) {
setup_instrument_cluster
}
/// Initialize yacht systems - returns the systems for registration
pub fn get_yacht_systems() -> Vec<Box<dyn YachtSystem>> {
create_yacht_systems()
/// Initialize marine systems - returns the systems for registration
pub fn get_vessel_systems() -> Vec<Box<dyn VesselSystem>> {
create_vessel_systems()
}

View File

@@ -1,149 +0,0 @@
use bevy::prelude::*;
use components::{YachtData, GpsIndicator, RadarIndicator, AisIndicator, SystemDisplay};
/// Resource to track which system is currently selected
#[derive(Resource, Default)]
pub struct SelectedSystem {
pub current: Option<SystemType>,
}
/// Types of navigation systems available
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SystemType {
Gps,
Radar,
Ais,
}
/// Handles user interactions with system indicator buttons
pub fn handle_system_interactions(
mut selected_system: ResMut<SelectedSystem>,
mut interaction_query: Query<
(&Interaction, &mut BackgroundColor, Option<&GpsIndicator>, Option<&RadarIndicator>, Option<&AisIndicator>),
(Changed<Interaction>, With<Button>),
>,
) {
for (interaction, mut background_color, gps, radar, ais) in &mut interaction_query {
match *interaction {
Interaction::Pressed => {
if gps.is_some() {
selected_system.current = Some(SystemType::Gps);
*background_color = BackgroundColor(Color::linear_rgb(0.0, 0.3, 0.5));
} else if radar.is_some() {
selected_system.current = Some(SystemType::Radar);
*background_color = BackgroundColor(Color::linear_rgb(0.0, 0.3, 0.5));
} else if ais.is_some() {
selected_system.current = Some(SystemType::Ais);
*background_color = BackgroundColor(Color::linear_rgb(0.0, 0.3, 0.5));
}
}
Interaction::Hovered => {
*background_color = BackgroundColor(Color::linear_rgb(0.15, 0.15, 0.2));
}
Interaction::None => {
*background_color = BackgroundColor(Color::linear_rgb(0.1, 0.1, 0.15));
}
}
}
}
/// Updates the system display area with detailed information about the selected system
pub fn update_system_display(
selected_system: Res<SelectedSystem>,
mut display_query: Query<&mut Text, With<SystemDisplay>>,
yacht_data: Res<YachtData>,
time: Res<Time>,
) {
if let Ok(mut text) = display_query.single_mut() {
match selected_system.current {
Some(SystemType::Gps) => {
text.0 = format!(
"GPS NAVIGATION SYSTEM\n\n\
Position: 43°38'19.5\"N 1°26'58.3\"W\n\
Heading: {:.0}°\n\
Speed: {:.1} knots\n\
Course Over Ground: {:.0}°\n\
Satellites: 12 connected\n\
HDOP: 0.8 (Excellent)\n\
\n\
Next Waypoint: MONACO HARBOR\n\
Distance: 127.3 NM\n\
ETA: 10h 12m",
yacht_data.heading,
yacht_data.speed,
yacht_data.heading + 5.0
);
}
Some(SystemType::Radar) => {
let sweep_angle = (time.elapsed_secs() * 60.0) % 360.0;
text.0 = format!(
"RADAR SYSTEM - 12 NM RANGE\n\n\
Status: ACTIVE\n\
Sweep: {:.0}°\n\
Gain: AUTO\n\
Sea Clutter: -15 dB\n\
Rain Clutter: OFF\n\
\n\
CONTACTS DETECTED:\n\
• Vessel 1: 2.3 NM @ 045° (15 kts)\n\
• Vessel 2: 5.7 NM @ 180° (8 kts)\n\
• Land Mass: 8.2 NM @ 270°\n\
• Buoy: 1.1 NM @ 315°",
sweep_angle
);
}
Some(SystemType::Ais) => {
text.0 = format!(
"AIS - AUTOMATIC IDENTIFICATION SYSTEM\n\n\
Status: RECEIVING\n\
Own Ship MMSI: 123456789\n\
\n\
NEARBY VESSELS:\n\
\n\
🛥️ M/Y SERENITY\n\
MMSI: 987654321\n\
Distance: 2.1 NM @ 045°\n\
Speed: 12.5 kts\n\
Course: 180°\n\
\n\
🚢 CARGO VESSEL ATLANTIS\n\
MMSI: 456789123\n\
Distance: 5.8 NM @ 270°\n\
Speed: 18.2 kts\n\
Course: 090°\n\
\n\
⛵ S/Y WIND DANCER\n\
MMSI: 789123456\n\
Distance: 1.3 NM @ 135°\n\
Speed: 6.8 kts\n\
Course: 225°"
);
}
None => {
text.0 = "".to_string();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_type_enum() {
let gps = SystemType::Gps;
let radar = SystemType::Radar;
let ais = SystemType::Ais;
assert_ne!(gps, radar);
assert_ne!(radar, ais);
assert_ne!(ais, gps);
}
#[test]
fn test_selected_system_default() {
let selected_system = SelectedSystem::default();
assert_eq!(selected_system.current, None);
}
}