From 7f63ec5c10eb7e8dd4edaabd1a6a437328911d39 Mon Sep 17 00:00:00 2001 From: Jon Santmyer Date: Sun, 10 May 2026 13:29:56 -0400 Subject: fleets --- src/fleet.rs | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 src/fleet.rs (limited to 'src/fleet.rs') diff --git a/src/fleet.rs b/src/fleet.rs new file mode 100644 index 0000000..39558cd --- /dev/null +++ b/src/fleet.rs @@ -0,0 +1,255 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::collections::hash_map::Iter; +use std::error::Error; +use std::iter::Filter; + +use cgmath::Zero; + +use crate::GameState; +use crate::solar_system::body::BodyId; +use crate::solar_system::orbit::StaticOrbiter; +use crate::solar_system::{SystemId, GRAVITATIONAL_CONSTANT, Kilometers, SolarSystem, body::OrbitalBody, orbit::StaticOrbit}; +use crate::timeman::{self, Second}; +use crate::ui::fleet_window::NewFleet; + +pub type FleetId = usize; + +pub const FLEET_TICK_DURATION: Second = timeman::HOUR; + +pub struct Fleet +{ + id: FleetId, + name: String, + + origin: cgmath::Vector3, + offset: cgmath::Vector3, + heading: cgmath::Vector2>, + + system: Option, + baked_orbit: Option<(f64, StaticOrbit)>, +} + +pub struct FleetsManager +{ + next_id: FleetId, + fleets: HashMap +} + +impl StaticOrbiter for Fleet { + fn orbit(&self) -> Option<&StaticOrbit> { + match &self.baked_orbit { + Some((_, orbit)) => { + Some(orbit) + }, + None => None + } + } + + fn sgp(&self) -> f64 { + match &self.baked_orbit { + Some((sgp, _)) => { *sgp }, + None => { 0.0 } + } + } +} + +impl Fleet +{ + pub const TICK_DURATION: Second = FLEET_TICK_DURATION; + + pub fn new( + id: FleetId, + name: String) + -> Self + { + Self { + id, + name, + origin: cgmath::vec3(0.0, 0.0, 0.0), + offset: cgmath::vec3(0.0, 0.0, 0.0), + heading: cgmath::vec2( + cgmath::Rad::zero(), + cgmath::Rad::zero()), + + system: None, + baked_orbit: None, + } + } + + pub fn id(&self) -> FleetId { self.id } + pub fn name(&self) -> &String { &self.name } + pub fn origin_position(&self) -> &cgmath::Vector3 + { &self.origin } + pub fn offset_position(&self) -> &cgmath::Vector3 + { &self.offset } + pub fn heading(&self) -> &cgmath::Vector2> + { &self.heading } + pub fn system(&self) -> Option + { self.system } + + pub fn make_orbit( + &mut self, + star_system: &SolarSystem, + body: BodyId, + radius: Kilometers) + { + let body = star_system.body(body); + let sgp = body.mass() * GRAVITATIONAL_CONSTANT; + + self.baked_orbit = Some((sgp, StaticOrbit::new_circular(body, radius))); + self.system = Some(star_system.id()); + } + + fn subtick( + &mut self, + star_systems: &[SolarSystem], + time: Second) + -> Result<(), ()> + { + if let Some((_sgp, orbit)) = &self.baked_orbit { + let solar_system = &star_systems[self.system().unwrap()]; + let body = solar_system.body(orbit.parent()); + + self.origin = body.absolute_position(solar_system, time); + self.offset = orbit.calculate_position_at(self, time); + } + Ok(()) + } +} // impl Fleet + +impl FleetsManager +{ + pub fn new() + -> Self { + Self { + next_id: 0, + fleets: HashMap::new() + } + } + + fn reserve_new_id(&mut self) + -> FleetId + { + let new_id = self.next_id; + self.next_id += 1; + new_id + } + + pub fn all(&self) + -> Vec + { + self.fleets.keys().cloned().collect() + } + + pub fn all_in_system( + &self, + system: SystemId) + -> Vec + { + self.fleets.iter().filter_map(|v| { + if v.1.system().is_some_and(|id| { id == system }) { + Some(*v.0) + }else { + None + } + }).collect() + } + + pub fn entry_mut( + &mut self, + id: FleetId) + -> Option<&mut Fleet> + { self.fleets.get_mut(&id) } + + pub fn entry( + &self, + id: FleetId) + -> Option<&Fleet> + { self.fleets.get(&id) } + + fn subtick_fleet( + &mut self, + star_systems: &[SolarSystem], + time: Second) + -> Result<(), ()> + { + let mut tick_interrupt = Ok(()); + for (id, fleet) in &mut self.fleets { + let res = fleet.subtick(star_systems, time); + if res.is_err() { + tick_interrupt = Err(()); + } + } + tick_interrupt + } + + pub fn tick( + &mut self, + star_systems: &[SolarSystem], + time_then: Second, + time_now: Second) + -> Result<(), Second> + { + let tick_interval = Fleet::TICK_DURATION; + + let time_then_offset = time_then % tick_interval; + let time_now_offset = time_now % tick_interval; + + let tick_start = time_then + tick_interval - time_then_offset; + let tick_end = time_now - time_now_offset; + + let num_ticks = (tick_end - tick_start) / tick_interval; + let mut tick_interrupt: Option = None; + + for tick_i in 0..num_ticks+1 { + let tick_time = tick_start + tick_i * tick_interval; + if self.subtick_fleet(star_systems, tick_time).is_err() { + tick_interrupt = Some(tick_time); + } + } + + let final_tick = match tick_interrupt { + Some(v) => v, + None => tick_end + }; + + if final_tick != tick_end { + self.subtick_fleet(star_systems, final_tick); + } + + Ok(()) + } +} // impl FleetsManager + +impl GameState +{ + pub fn new_fleet_from_ui( + &mut self, + info: NewFleet) + -> Result> + { + let star_system = &self.solar_systems[info.system]; + + let fleet_id = self.fleets.reserve_new_id(); + let mut fleet = Fleet::new(fleet_id, info.name); + fleet.make_orbit(star_system, info.orbiting, info.sma); + + self.fleets.fleets.insert( + fleet_id, + fleet); + + Ok(fleet_id) + } +} + +impl SolarSystem +{ + pub fn fleets( + &self, + fleets_manager: &FleetsManager) + -> Vec + { + fleets_manager.all_in_system(self.id()) + } +} -- cgit v1.2.3