mirror of
https://github.com/seemueller-io/yachtpit.git
synced 2025-09-08 22:46:45 +00:00
Modularized existing vessel systems into separate modules (AIS, GPS, Radar) and restructured the project for improved maintainability. Updated references and documentation accordingly.
This commit is contained in:
107
crates/systems/src/ais/ais_system.rs
Normal file
107
crates/systems/src/ais/ais_system.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use bevy::prelude::Time;
|
||||||
|
use components::VesselData;
|
||||||
|
use crate::{SystemInteraction, SystemStatus, VesselSystem};
|
||||||
|
|
||||||
|
/// AIS (Automatic Identification System) implementation
|
||||||
|
pub struct AisSystem {
|
||||||
|
status: SystemStatus,
|
||||||
|
own_mmsi: u32,
|
||||||
|
receiving: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AisSystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
status: SystemStatus::Active,
|
||||||
|
own_mmsi: 123456789,
|
||||||
|
receiving: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VesselSystem for AisSystem {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"ais"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_name(&self) -> &'static str {
|
||||||
|
"AIS System"
|
||||||
|
}
|
||||||
|
|
||||||
|
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: &VesselData) -> String {
|
||||||
|
format!(
|
||||||
|
"AIS - AUTOMATIC IDENTIFICATION SYSTEM\n\n\
|
||||||
|
Status: {}\n\
|
||||||
|
Own Ship MMSI: {}\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°",
|
||||||
|
if self.receiving { "RECEIVING" } else { "STANDBY" },
|
||||||
|
self.own_mmsi
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
||||||
|
match interaction {
|
||||||
|
SystemInteraction::Select => {
|
||||||
|
self.status = SystemStatus::Active;
|
||||||
|
self.receiving = true;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
SystemInteraction::Configure(key, value) => {
|
||||||
|
match key.as_str() {
|
||||||
|
"mmsi" => {
|
||||||
|
if let Ok(mmsi) = value.parse::<u32>() {
|
||||||
|
self.own_mmsi = mmsi;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemInteraction::Toggle => {
|
||||||
|
self.receiving = !self.receiving;
|
||||||
|
self.status = if self.receiving {
|
||||||
|
SystemStatus::Active
|
||||||
|
} else {
|
||||||
|
SystemStatus::Inactive
|
||||||
|
};
|
||||||
|
true
|
||||||
|
}
|
||||||
|
SystemInteraction::Reset => {
|
||||||
|
self.own_mmsi = 123456789;
|
||||||
|
self.receiving = true;
|
||||||
|
self.status = SystemStatus::Active;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self) -> SystemStatus {
|
||||||
|
self.status.clone()
|
||||||
|
}
|
||||||
|
}
|
1
crates/systems/src/ais/mod.rs
Normal file
1
crates/systems/src/ais/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod ais_system;
|
86
crates/systems/src/gps/gps_system.rs
Normal file
86
crates/systems/src/gps/gps_system.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
use bevy::prelude::Time;
|
||||||
|
use components::VesselData;
|
||||||
|
use crate::{SystemInteraction, SystemStatus, VesselSystem};
|
||||||
|
|
||||||
|
/// GPS Navigation System implementation
|
||||||
|
pub struct GpsSystem {
|
||||||
|
status: SystemStatus,
|
||||||
|
satellites_connected: u8,
|
||||||
|
hdop: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GpsSystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
status: SystemStatus::Active,
|
||||||
|
satellites_connected: 12,
|
||||||
|
hdop: 0.8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VesselSystem for GpsSystem {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"gps"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_name(&self) -> &'static str {
|
||||||
|
"GPS Navigation"
|
||||||
|
}
|
||||||
|
|
||||||
|
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: &VesselData) -> String {
|
||||||
|
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: {} connected\n\
|
||||||
|
HDOP: {:.1} ({})\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,
|
||||||
|
self.satellites_connected,
|
||||||
|
self.hdop,
|
||||||
|
if self.hdop < 1.0 { "Excellent" } else if self.hdop < 2.0 { "Good" } else { "Fair" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
||||||
|
match interaction {
|
||||||
|
SystemInteraction::Select => {
|
||||||
|
self.status = SystemStatus::Active;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
SystemInteraction::Reset => {
|
||||||
|
self.satellites_connected = 12;
|
||||||
|
self.hdop = 0.8;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
SystemInteraction::Toggle => {
|
||||||
|
self.status = match self.status {
|
||||||
|
SystemStatus::Active => SystemStatus::Inactive,
|
||||||
|
SystemStatus::Inactive => SystemStatus::Active,
|
||||||
|
_ => SystemStatus::Active,
|
||||||
|
};
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self) -> SystemStatus {
|
||||||
|
self.status.clone()
|
||||||
|
}
|
||||||
|
}
|
1
crates/systems/src/gps/mod.rs
Normal file
1
crates/systems/src/gps/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod gps_system;
|
@@ -1,9 +1,10 @@
|
|||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
|
|
||||||
pub mod player;
|
mod world;
|
||||||
mod marine;
|
mod vessel;
|
||||||
use marine::*;
|
mod ais;
|
||||||
|
mod gps;
|
||||||
|
mod radar;
|
||||||
|
|
||||||
// Re-export components from the components crate
|
// Re-export components from the components crate
|
||||||
pub use components::{
|
pub use components::{
|
||||||
@@ -12,5 +13,6 @@ pub use components::{
|
|||||||
InstrumentCluster, GpsIndicator, RadarIndicator, AisIndicator, SystemDisplay
|
InstrumentCluster, GpsIndicator, RadarIndicator, AisIndicator, SystemDisplay
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use player::{get_vessel_systems, setup_instrument_cluster_system, PlayerPlugin};
|
|
||||||
pub use vessel_systems::{create_vessel_systems, AisSystem, GpsSystem, RadarSystem, SystemInteraction, SystemStatus, VesselSystem};
|
pub use world::player::{get_vessel_systems, setup_instrument_cluster_system, PlayerPlugin};
|
||||||
|
pub use vessel::vessel_systems::{create_vessel_systems, AisSystem, GpsSystem, RadarSystem, SystemInteraction, SystemStatus, VesselSystem};
|
||||||
|
@@ -1,406 +0,0 @@
|
|||||||
//! Concrete implementations of vessel systems using the SystemManager abstraction
|
|
||||||
//!
|
|
||||||
//! 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::VesselData;
|
|
||||||
|
|
||||||
/// Status of a vessel system
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum SystemStatus {
|
|
||||||
Active,
|
|
||||||
Inactive,
|
|
||||||
Error(String),
|
|
||||||
Maintenance,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Interaction types for yacht systems
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum SystemInteraction {
|
|
||||||
Select,
|
|
||||||
Toggle,
|
|
||||||
Reset,
|
|
||||||
Configure(String, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Common trait for all yacht systems
|
|
||||||
pub trait VesselSystem: Send + Sync {
|
|
||||||
fn id(&self) -> &'static str;
|
|
||||||
fn display_name(&self) -> &'static str;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// GPS Navigation System implementation
|
|
||||||
pub struct GpsSystem {
|
|
||||||
status: SystemStatus,
|
|
||||||
satellites_connected: u8,
|
|
||||||
hdop: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GpsSystem {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
status: SystemStatus::Active,
|
|
||||||
satellites_connected: 12,
|
|
||||||
hdop: 0.8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VesselSystem for GpsSystem {
|
|
||||||
fn id(&self) -> &'static str {
|
|
||||||
"gps"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_name(&self) -> &'static str {
|
|
||||||
"GPS Navigation"
|
|
||||||
}
|
|
||||||
|
|
||||||
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: &VesselData) -> String {
|
|
||||||
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: {} connected\n\
|
|
||||||
HDOP: {:.1} ({})\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,
|
|
||||||
self.satellites_connected,
|
|
||||||
self.hdop,
|
|
||||||
if self.hdop < 1.0 { "Excellent" } else if self.hdop < 2.0 { "Good" } else { "Fair" }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
|
||||||
match interaction {
|
|
||||||
SystemInteraction::Select => {
|
|
||||||
self.status = SystemStatus::Active;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
SystemInteraction::Reset => {
|
|
||||||
self.satellites_connected = 12;
|
|
||||||
self.hdop = 0.8;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
SystemInteraction::Toggle => {
|
|
||||||
self.status = match self.status {
|
|
||||||
SystemStatus::Active => SystemStatus::Inactive,
|
|
||||||
SystemStatus::Inactive => SystemStatus::Active,
|
|
||||||
_ => SystemStatus::Active,
|
|
||||||
};
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn status(&self) -> SystemStatus {
|
|
||||||
self.status.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Radar System implementation
|
|
||||||
pub struct RadarSystem {
|
|
||||||
status: SystemStatus,
|
|
||||||
range_nm: f32,
|
|
||||||
gain: String,
|
|
||||||
sea_clutter_db: i8,
|
|
||||||
rain_clutter: bool,
|
|
||||||
sweep_angle: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RadarSystem {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
status: SystemStatus::Active,
|
|
||||||
range_nm: 12.0,
|
|
||||||
gain: "AUTO".to_string(),
|
|
||||||
sea_clutter_db: -15,
|
|
||||||
rain_clutter: false,
|
|
||||||
sweep_angle: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VesselSystem for RadarSystem {
|
|
||||||
fn id(&self) -> &'static str {
|
|
||||||
"radar"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_name(&self) -> &'static str {
|
|
||||||
"Radar System"
|
|
||||||
}
|
|
||||||
|
|
||||||
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: &VesselData) -> String {
|
|
||||||
format!(
|
|
||||||
"RADAR SYSTEM - {:.0} NM RANGE\n\n\
|
|
||||||
Status: {}\n\
|
|
||||||
Sweep: {:.0}°\n\
|
|
||||||
Gain: {}\n\
|
|
||||||
Sea Clutter: {} dB\n\
|
|
||||||
Rain Clutter: {}\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°",
|
|
||||||
self.range_nm,
|
|
||||||
match self.status {
|
|
||||||
SystemStatus::Active => "ACTIVE",
|
|
||||||
SystemStatus::Inactive => "STANDBY",
|
|
||||||
SystemStatus::Error(_) => "ERROR",
|
|
||||||
SystemStatus::Maintenance => "MAINTENANCE",
|
|
||||||
},
|
|
||||||
self.sweep_angle,
|
|
||||||
self.gain,
|
|
||||||
self.sea_clutter_db,
|
|
||||||
if self.rain_clutter { "ON" } else { "OFF" }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
|
||||||
match interaction {
|
|
||||||
SystemInteraction::Select => {
|
|
||||||
self.status = SystemStatus::Active;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
SystemInteraction::Configure(key, value) => {
|
|
||||||
match key.as_str() {
|
|
||||||
"range" => {
|
|
||||||
if let Ok(range) = value.parse::<f32>() {
|
|
||||||
self.range_nm = range.clamp(1.0, 48.0);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"gain" => {
|
|
||||||
self.gain = value;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
"sea_clutter" => {
|
|
||||||
if let Ok(db) = value.parse::<i8>() {
|
|
||||||
self.sea_clutter_db = db.clamp(-30, 0);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"rain_clutter" => {
|
|
||||||
self.rain_clutter = value.to_lowercase() == "on" || value == "true";
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SystemInteraction::Reset => {
|
|
||||||
self.range_nm = 12.0;
|
|
||||||
self.gain = "AUTO".to_string();
|
|
||||||
self.sea_clutter_db = -15;
|
|
||||||
self.rain_clutter = false;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
SystemInteraction::Toggle => {
|
|
||||||
self.status = match self.status {
|
|
||||||
SystemStatus::Active => SystemStatus::Inactive,
|
|
||||||
SystemStatus::Inactive => SystemStatus::Active,
|
|
||||||
_ => SystemStatus::Active,
|
|
||||||
};
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn status(&self) -> SystemStatus {
|
|
||||||
self.status.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// AIS (Automatic Identification System) implementation
|
|
||||||
pub struct AisSystem {
|
|
||||||
status: SystemStatus,
|
|
||||||
own_mmsi: u32,
|
|
||||||
receiving: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AisSystem {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
status: SystemStatus::Active,
|
|
||||||
own_mmsi: 123456789,
|
|
||||||
receiving: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VesselSystem for AisSystem {
|
|
||||||
fn id(&self) -> &'static str {
|
|
||||||
"ais"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_name(&self) -> &'static str {
|
|
||||||
"AIS System"
|
|
||||||
}
|
|
||||||
|
|
||||||
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: &VesselData) -> String {
|
|
||||||
format!(
|
|
||||||
"AIS - AUTOMATIC IDENTIFICATION SYSTEM\n\n\
|
|
||||||
Status: {}\n\
|
|
||||||
Own Ship MMSI: {}\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°",
|
|
||||||
if self.receiving { "RECEIVING" } else { "STANDBY" },
|
|
||||||
self.own_mmsi
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
|
||||||
match interaction {
|
|
||||||
SystemInteraction::Select => {
|
|
||||||
self.status = SystemStatus::Active;
|
|
||||||
self.receiving = true;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
SystemInteraction::Configure(key, value) => {
|
|
||||||
match key.as_str() {
|
|
||||||
"mmsi" => {
|
|
||||||
if let Ok(mmsi) = value.parse::<u32>() {
|
|
||||||
self.own_mmsi = mmsi;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SystemInteraction::Toggle => {
|
|
||||||
self.receiving = !self.receiving;
|
|
||||||
self.status = if self.receiving {
|
|
||||||
SystemStatus::Active
|
|
||||||
} else {
|
|
||||||
SystemStatus::Inactive
|
|
||||||
};
|
|
||||||
true
|
|
||||||
}
|
|
||||||
SystemInteraction::Reset => {
|
|
||||||
self.own_mmsi = 123456789;
|
|
||||||
self.receiving = true;
|
|
||||||
self.status = SystemStatus::Active;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn status(&self) -> SystemStatus {
|
|
||||||
self.status.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to create and register all yacht systems
|
|
||||||
pub fn create_vessel_systems() -> Vec<Box<dyn VesselSystem>> {
|
|
||||||
vec![
|
|
||||||
Box::new(GpsSystem::new()),
|
|
||||||
Box::new(RadarSystem::new()),
|
|
||||||
Box::new(AisSystem::new()),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gps_system() {
|
|
||||||
let gps = GpsSystem::new();
|
|
||||||
assert_eq!(gps.id(), "gps");
|
|
||||||
assert_eq!(gps.display_name(), "GPS Navigation");
|
|
||||||
assert_eq!(gps.status(), SystemStatus::Active);
|
|
||||||
|
|
||||||
let yacht_data = VesselData::default();
|
|
||||||
let display = gps.render_display(&yacht_data);
|
|
||||||
assert!(display.contains("GPS NAVIGATION SYSTEM"));
|
|
||||||
assert!(display.contains("Satellites: 12 connected"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_radar_system() {
|
|
||||||
let mut radar = RadarSystem::new();
|
|
||||||
assert_eq!(radar.id(), "radar");
|
|
||||||
assert_eq!(radar.display_name(), "Radar System");
|
|
||||||
|
|
||||||
// Test configuration
|
|
||||||
assert!(radar.handle_interaction(SystemInteraction::Configure("range".to_string(), "24".to_string())));
|
|
||||||
let display = radar.render_display(&VesselData::default());
|
|
||||||
assert!(display.contains("24 NM RANGE"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ais_system() {
|
|
||||||
let mut ais = AisSystem::new();
|
|
||||||
assert_eq!(ais.id(), "ais");
|
|
||||||
assert_eq!(ais.display_name(), "AIS System");
|
|
||||||
|
|
||||||
// Test toggle
|
|
||||||
assert!(ais.handle_interaction(SystemInteraction::Toggle));
|
|
||||||
assert_eq!(ais.status(), SystemStatus::Inactive);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_yacht_systems() {
|
|
||||||
let systems = create_vessel_systems();
|
|
||||||
assert_eq!(systems.len(), 3);
|
|
||||||
|
|
||||||
let ids: Vec<&str> = systems.iter().map(|s| s.id()).collect();
|
|
||||||
assert!(ids.contains(&"gps"));
|
|
||||||
assert!(ids.contains(&"radar"));
|
|
||||||
assert!(ids.contains(&"ais"));
|
|
||||||
}
|
|
||||||
}
|
|
1
crates/systems/src/radar/mod.rs
Normal file
1
crates/systems/src/radar/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod radar_system;
|
126
crates/systems/src/radar/radar_system.rs
Normal file
126
crates/systems/src/radar/radar_system.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use bevy::prelude::Time;
|
||||||
|
use components::VesselData;
|
||||||
|
use crate::{SystemInteraction, SystemStatus, VesselSystem};
|
||||||
|
|
||||||
|
/// Radar System implementation
|
||||||
|
pub struct RadarSystem {
|
||||||
|
status: SystemStatus,
|
||||||
|
range_nm: f32,
|
||||||
|
gain: String,
|
||||||
|
sea_clutter_db: i8,
|
||||||
|
rain_clutter: bool,
|
||||||
|
sweep_angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RadarSystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
status: SystemStatus::Active,
|
||||||
|
range_nm: 12.0,
|
||||||
|
gain: "AUTO".to_string(),
|
||||||
|
sea_clutter_db: -15,
|
||||||
|
rain_clutter: false,
|
||||||
|
sweep_angle: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VesselSystem for RadarSystem {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"radar"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_name(&self) -> &'static str {
|
||||||
|
"Radar System"
|
||||||
|
}
|
||||||
|
|
||||||
|
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: &VesselData) -> String {
|
||||||
|
format!(
|
||||||
|
"RADAR SYSTEM - {:.0} NM RANGE\n\n\
|
||||||
|
Status: {}\n\
|
||||||
|
Sweep: {:.0}°\n\
|
||||||
|
Gain: {}\n\
|
||||||
|
Sea Clutter: {} dB\n\
|
||||||
|
Rain Clutter: {}\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°",
|
||||||
|
self.range_nm,
|
||||||
|
match self.status {
|
||||||
|
SystemStatus::Active => "ACTIVE",
|
||||||
|
SystemStatus::Inactive => "STANDBY",
|
||||||
|
SystemStatus::Error(_) => "ERROR",
|
||||||
|
SystemStatus::Maintenance => "MAINTENANCE",
|
||||||
|
},
|
||||||
|
self.sweep_angle,
|
||||||
|
self.gain,
|
||||||
|
self.sea_clutter_db,
|
||||||
|
if self.rain_clutter { "ON" } else { "OFF" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_interaction(&mut self, interaction: SystemInteraction) -> bool {
|
||||||
|
match interaction {
|
||||||
|
SystemInteraction::Select => {
|
||||||
|
self.status = SystemStatus::Active;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
SystemInteraction::Configure(key, value) => {
|
||||||
|
match key.as_str() {
|
||||||
|
"range" => {
|
||||||
|
if let Ok(range) = value.parse::<f32>() {
|
||||||
|
self.range_nm = range.clamp(1.0, 48.0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"gain" => {
|
||||||
|
self.gain = value;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
"sea_clutter" => {
|
||||||
|
if let Ok(db) = value.parse::<i8>() {
|
||||||
|
self.sea_clutter_db = db.clamp(-30, 0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"rain_clutter" => {
|
||||||
|
self.rain_clutter = value.to_lowercase() == "on" || value == "true";
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemInteraction::Reset => {
|
||||||
|
self.range_nm = 12.0;
|
||||||
|
self.gain = "AUTO".to_string();
|
||||||
|
self.sea_clutter_db = -15;
|
||||||
|
self.rain_clutter = false;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
SystemInteraction::Toggle => {
|
||||||
|
self.status = match self.status {
|
||||||
|
SystemStatus::Active => SystemStatus::Inactive,
|
||||||
|
SystemStatus::Inactive => SystemStatus::Active,
|
||||||
|
_ => SystemStatus::Active,
|
||||||
|
};
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self) -> SystemStatus {
|
||||||
|
self.status.clone()
|
||||||
|
}
|
||||||
|
}
|
104
crates/systems/src/vessel/vessel_systems.rs
Normal file
104
crates/systems/src/vessel/vessel_systems.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
//! Concrete implementations of vessel systems using the SystemManager abstraction
|
||||||
|
//!
|
||||||
|
//! This module provides implementations of the VesselSystem trait for GPS, Radar, and AIS systems,
|
||||||
|
//! bridging the existing functionality with the new higher-level abstraction.
|
||||||
|
|
||||||
|
pub use crate::ais::ais_system::AisSystem;
|
||||||
|
pub use crate::gps::gps_system::GpsSystem;
|
||||||
|
pub use crate::radar::radar_system::RadarSystem;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use components::VesselData;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Common trait for all yacht systems
|
||||||
|
pub trait VesselSystem: Send + Sync {
|
||||||
|
fn id(&self) -> &'static str;
|
||||||
|
fn display_name(&self) -> &'static str;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Status of a vessel system
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum SystemStatus {
|
||||||
|
Active,
|
||||||
|
Inactive,
|
||||||
|
Error(String),
|
||||||
|
Maintenance,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interaction types for vessel systems
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SystemInteraction {
|
||||||
|
Select,
|
||||||
|
Toggle,
|
||||||
|
Reset,
|
||||||
|
Configure(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Helper function to create and register all vessel systems
|
||||||
|
pub fn create_vessel_systems() -> Vec<Box<dyn VesselSystem>> {
|
||||||
|
vec![
|
||||||
|
Box::new(GpsSystem::new()),
|
||||||
|
Box::new(RadarSystem::new()),
|
||||||
|
Box::new(AisSystem::new()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gps_system() {
|
||||||
|
let gps = GpsSystem::new();
|
||||||
|
assert_eq!(gps.id(), "gps");
|
||||||
|
assert_eq!(gps.display_name(), "GPS Navigation");
|
||||||
|
assert_eq!(gps.status(), SystemStatus::Active);
|
||||||
|
|
||||||
|
let vessle_data = VesselData::default();
|
||||||
|
let display = gps.render_display(&vessle_data);
|
||||||
|
assert!(display.contains("GPS NAVIGATION SYSTEM"));
|
||||||
|
assert!(display.contains("Satellites: 12 connected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_radar_system() {
|
||||||
|
let mut radar = RadarSystem::new();
|
||||||
|
assert_eq!(radar.id(), "radar");
|
||||||
|
assert_eq!(radar.display_name(), "Radar System");
|
||||||
|
|
||||||
|
// Test configuration
|
||||||
|
assert!(radar.handle_interaction(SystemInteraction::Configure("range".to_string(), "24".to_string())));
|
||||||
|
let display = radar.render_display(&VesselData::default());
|
||||||
|
assert!(display.contains("24 NM RANGE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ais_system() {
|
||||||
|
let mut ais = AisSystem::new();
|
||||||
|
assert_eq!(ais.id(), "ais");
|
||||||
|
assert_eq!(ais.display_name(), "AIS System");
|
||||||
|
|
||||||
|
// Test toggle
|
||||||
|
assert!(ais.handle_interaction(SystemInteraction::Toggle));
|
||||||
|
assert_eq!(ais.status(), SystemStatus::Inactive);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_vessel_systems() {
|
||||||
|
let systems = create_vessel_systems();
|
||||||
|
assert_eq!(systems.len(), 3);
|
||||||
|
|
||||||
|
let ids: Vec<&str> = systems.iter().map(|s| s.id()).collect();
|
||||||
|
assert!(ids.contains(&"gps"));
|
||||||
|
assert!(ids.contains(&"radar"));
|
||||||
|
assert!(ids.contains(&"ais"));
|
||||||
|
}
|
||||||
|
}
|
1
crates/systems/src/world/mod.rs
Normal file
1
crates/systems/src/world/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod player;
|
@@ -1,6 +1,6 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use components::{setup_instrument_cluster, VesselData, update_vessel_data, update_instrument_displays};
|
use components::{setup_instrument_cluster, VesselData, update_vessel_data, update_instrument_displays};
|
||||||
use super::vessel_systems::{create_vessel_systems, VesselSystem};
|
use crate::vessel::vessel_systems::{create_vessel_systems, VesselSystem};
|
||||||
|
|
||||||
pub struct PlayerPlugin;
|
pub struct PlayerPlugin;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ pub fn setup_instrument_cluster_system() -> impl Fn(Commands) {
|
|||||||
setup_instrument_cluster
|
setup_instrument_cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize marine systems - returns the systems for registration
|
/// Initialize vessel systems - returns the systems for registration
|
||||||
pub fn get_vessel_systems() -> Vec<Box<dyn VesselSystem>> {
|
pub fn get_vessel_systems() -> Vec<Box<dyn VesselSystem>> {
|
||||||
create_vessel_systems()
|
create_vessel_systems()
|
||||||
}
|
}
|
@@ -13,9 +13,7 @@ use crate::core::system_manager::SystemManager;
|
|||||||
use crate::ui::{LoadingPlugin, MenuPlugin};
|
use crate::ui::{LoadingPlugin, MenuPlugin};
|
||||||
use systems::{PlayerPlugin, setup_instrument_cluster, get_vessel_systems};
|
use systems::{PlayerPlugin, setup_instrument_cluster, get_vessel_systems};
|
||||||
|
|
||||||
// This game uses States to separate logic
|
|
||||||
// See https://bevy-cheatbook.github.io/programming/states.html
|
// See https://bevy-cheatbook.github.io/programming/states.html
|
||||||
// Or https://github.com/bevyengine/bevy/blob/main/examples/ecs/state.rs
|
|
||||||
#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]
|
#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]
|
||||||
enum GameState {
|
enum GameState {
|
||||||
// During the loading State the LoadingPlugin will load our assets
|
// During the loading State the LoadingPlugin will load our assets
|
||||||
@@ -29,8 +27,8 @@ enum GameState {
|
|||||||
|
|
||||||
pub struct GamePlugin;
|
pub struct GamePlugin;
|
||||||
|
|
||||||
/// Initialize yacht systems in the SystemManager
|
/// Initialize systems in the SystemManager
|
||||||
fn initialize_yacht_systems(mut system_manager: ResMut<SystemManager>) {
|
fn initialize_vessel_systems(mut system_manager: ResMut<SystemManager>) {
|
||||||
let systems = get_vessel_systems();
|
let systems = get_vessel_systems();
|
||||||
for system in systems {
|
for system in systems {
|
||||||
system_manager.register_system(system);
|
system_manager.register_system(system);
|
||||||
@@ -46,7 +44,7 @@ impl Plugin for GamePlugin {
|
|||||||
SystemManagerPlugin,
|
SystemManagerPlugin,
|
||||||
PlayerPlugin,
|
PlayerPlugin,
|
||||||
))
|
))
|
||||||
.add_systems(OnEnter(GameState::Playing), (setup_instrument_cluster, initialize_yacht_systems));
|
.add_systems(OnEnter(GameState::Playing), (setup_instrument_cluster, initialize_vessel_systems));
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user