use crate::common::get_fsd_booster_info; use crate::journal::*; use pyo3::conversion::ToPyObject; use pyo3::prelude::*; use pyo3::types::PyDict; use regex::Regex; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FSD { pub rating_val: f32, pub class_val: f32, pub opt_mass: f32, pub max_fuel: f32, pub boost: f32, pub guardian_booster: f32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Ship { pub name: String, pub ident: String, pub ship_type: String, pub base_mass: f32, pub fuel_mass: f32, pub fuel_capacity: f32, pub fsd: FSD, } impl Ship { pub fn new( name: String, ident: String, ship_type: String, base_mass: f32, fuel_mass: f32, fuel_capacity: f32, fsd_type: (char, u8), max_fuel: f32, opt_mass: f32, guardian_booster: usize, ) -> Result { let rating_val: f32 = match fsd_type.0 { 'A' => 12.0, 'B' => 10.0, 'C' => 8.0, 'D' => 10.0, 'E' => 11.0, err => { return Err(format!("Invalid rating: {}", err)); } }; if fsd_type.1 < 2 || fsd_type.1 > 8 { return Err(format!("Invalid class: {}", fsd_type.1)); }; if guardian_booster!=0 { return Err("Guardian booster not yet implemented!".to_owned()) } let ret = Self { name, ident, ship_type, fuel_capacity, fuel_mass, base_mass, fsd: FSD { rating_val, class_val: 2.0 + (0.15 * ((fsd_type.1 - 2) as f32)), opt_mass, max_fuel, boost: 1.0, guardian_booster: get_fsd_booster_info(guardian_booster)?, }, }; Ok(ret) } pub fn new_from_json(data: &str) -> Result { match serde_json::from_str::(&data) { Ok(Event { event: EventData::Unknown, }) => { return Err(format!("Invalid Loadout event: {}", data)); } Ok(ev) => { if let Some(loadout) = ev.get_loadout() { return loadout.try_into_ship(); } else { return Err(format!("Invalid Loadout event: {}", data)); } } Err(msg) => { return Err(format!("{}", msg)); } }; } pub fn new_from_journal() -> Result, String> { let mut ret = HashMap::new(); let re = Regex::new(r"^Journal\.\d{12}\.\d{2}\.log$").unwrap(); let mut journals: Vec = Vec::new(); let mut userprofile = PathBuf::from(std::env::var("Userprofile").unwrap()); userprofile.push("Saved Games"); userprofile.push("Frontier Developments"); userprofile.push("Elite Dangerous"); if let Ok(iter) = userprofile.read_dir() { for entry in iter { if let Ok(entry) = entry { if re.is_match(&entry.file_name().to_string_lossy()) { journals.push(entry.path()); }; } } } journals.sort(); for journal in &journals { let mut fh = BufReader::new(File::open(journal).unwrap()); let mut line = String::new(); while let Ok(n) = fh.read_line(&mut line) { if n == 0 { break; } match serde_json::from_str::(&line) { Ok(Event { event: EventData::Unknown, }) => {} Ok(ev) => { if let Some(loadout) = ev.get_loadout() { let mut ship = loadout.try_into_ship()?; if ship.name == "" { ship.name = "".to_owned(); } let key = format!( "[{}] {} ({})", ship.ident, ship.name, ship.ship_type.to_ascii_lowercase() ); ret.insert(key, ship); } } Err(_) => {} }; line.clear(); } } if ret.is_empty() { return Err("No ships loaded!".to_owned()); } return Ok(ret); } pub fn can_jump(&self, d: f32) -> bool { self.fuel_cost(d) <= self.fsd.max_fuel.min(self.fuel_mass) } pub fn boost(&mut self, boost: f32) { self.fsd.boost = boost; } pub fn refuel(&mut self) { self.fuel_mass = self.fuel_capacity; } pub fn make_jump(&mut self, d: f32) -> Option { let cost = self.fuel_cost(d); if cost > self.fsd.max_fuel.min(self.fuel_mass) { return None; } self.fuel_mass -= cost; self.fsd.boost = 1.0; Some(cost) } fn jump_range(&self, fuel: f32, booster: bool) -> f32 { let mass = self.base_mass + fuel; let mut fuel = self.fsd.max_fuel.min(fuel); if booster { fuel *= self.boost_fuel_mult(); } let opt_mass = self.fsd.opt_mass * self.fsd.boost; return opt_mass * ((1000.0 * fuel) / self.fsd.rating_val).powf(self.fsd.class_val.recip()) / mass; } pub fn max_range(&self) -> f32 { return self.jump_range(self.fsd.max_fuel, true); } pub fn range(&self) -> f32 { return self.jump_range(self.fuel_mass, true); } fn boost_fuel_mult(&self) -> f32 { if self.fsd.guardian_booster == 0.0 { return 1.0; } let base_range = self.jump_range(self.fuel_mass, false); // current range without booster return ((base_range + self.fsd.guardian_booster) / base_range).powf(self.fsd.class_val); } pub fn fuel_cost(&self, d: f32) -> f32 { if d == 0.0 { return 0.0; } let mass = self.base_mass + self.fuel_mass; let opt_mass = self.fsd.opt_mass * self.fsd.boost; let base_cost = (d * mass) / opt_mass; return (self.fsd.rating_val * 0.001 * base_cost.powf(self.fsd.class_val)) / self.boost_fuel_mult(); } } /* #[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)] pub struct FSD { pub rating_val: f32, pub class_val: f32, pub opt_mass: f32, pub max_fuel: f32, pub boost: f32, pub guardian_booster: f32, } #[derive(Debug,Clone, Serialize, Deserialize, ToPyObject)] pub struct Ship { pub name: String, pub ident: String, pub ship_type: String, pub base_mass: f32, pub fuel_mass: f32, pub fuel_capacity: f32, pub fsd: FSD, } */ impl FSD { pub fn to_object(&self, py: Python) -> PyResult { let elem = PyDict::new(py); elem.set_item("rating_val", self.rating_val)?; elem.set_item("class_val", self.class_val)?; elem.set_item("opt_mass", self.opt_mass)?; elem.set_item("max_fuel", self.max_fuel)?; elem.set_item("boost", self.boost)?; elem.set_item("guardian_booster", self.guardian_booster)?; Ok(elem.to_object(py)) } } impl Ship { pub fn to_object(&self, py: Python) -> PyResult { let elem = PyDict::new(py); elem.set_item("name", self.name.clone())?; elem.set_item("ident", self.ident.clone())?; elem.set_item("ship_type", self.ship_type.clone())?; elem.set_item("base_mass", self.base_mass)?; elem.set_item("fuel_mass", self.fuel_mass)?; elem.set_item("fuel_capacity", self.fuel_capacity)?; elem.set_item("fsd", self.fsd.to_object(py)?)?; Ok(elem.to_object(py)) } }