//! Elite: Dangerous Journal Loadout even parser use crate::common::get_fsd_info; use crate::ship::Ship; use eyre::Result; use regex::Regex; use serde::Deserialize; use std::collections::HashMap; #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct Event { #[serde(flatten)] pub event: EventData, } #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(tag = "event")] pub enum EventData { Loadout(Loadout), #[serde(other)] Unknown, } #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct Modifier { label: String, value: f32, } #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct Engineering { modifiers: Vec, } #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct Module { engineering: Option, item: String, slot: String, } #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct FuelCapacity { main: f32, reserve: f32, } #[derive(Clone, Debug, PartialEq, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct Loadout { #[serde(rename = "timestamp")] timestamp: String, ship: String, ship_name: String, ship_ident: String, fuel_capacity: FuelCapacity, unladen_mass: f32, modules: Vec, } impl Engineering { fn to_hashmap(&self) -> HashMap { let mut h = HashMap::new(); for v in &self.modifiers { h.insert(v.label.clone(), v.value); } return h; } } impl Loadout { fn get_booster(&self) -> Option { self.modules.iter().cloned().find_map(|m| { let Module { item, .. } = m; if item.starts_with("int_guardianfsdbooster") { return item .chars() .last() .unwrap() .to_digit(10) .map(|v| v as usize); } return None; }) } fn get_fsd(&self) -> Option<(String, Option)> { self.modules.iter().cloned().find_map(|m| { let Module { slot, engineering, item, } = m; if slot == "FrameShiftDrive" { return Some((item, engineering)); } return None; }) } pub fn try_into_ship(self) -> Result<(String, Ship), String> { let fsd = self.get_fsd().ok_or("No FSD found!")?; let booster = self.get_booster().unwrap_or(0); let fsd_type = Regex::new(r"^int_hyperdrive_size(\d+)_class(\d+)$") .unwrap() .captures(&fsd.0); let fsd_type: (usize, usize) = fsd_type .and_then(|m| { let s = m.get(1)?.as_str().to_owned().parse().ok()?; let c = m.get(2)?.as_str().to_owned().parse().ok()?; return Some((c, s)); }) .ok_or(format!("Invalid FSD found: {}", &fsd.0))?; let eng = fsd .1 .map(|eng| eng.to_hashmap()) .unwrap_or_else(HashMap::new); let mut fsd_info = get_fsd_info(fsd_type.0, fsd_type.1)?; let fsd_type = ( "_EDCBA" .chars() .nth(fsd_type.0) .ok_or(format!("Invalid FSD found: {}", &fsd.0))?, fsd_type.1 as u8, ); fsd_info.extend(eng); let max_fuel = fsd_info .get("MaxFuel") .ok_or(format!("Unknwon MaxFuelPerJump for FSD: {}", &fsd.0))?; let opt_mass = fsd_info .get("FSDOptimalMass") .ok_or(format!("Unknwon FSDOptimalMass for FSD: {}", &fsd.0))?; let key = format!( "[{}] {} ({})", if !self.ship_name.is_empty() { self.ship_name } else { "".to_owned() }, self.ship_ident, self.ship ); return Ok(( key, Ship::new( self.unladen_mass, self.fuel_capacity.main, self.fuel_capacity.main, fsd_type, *max_fuel, *opt_mass, booster, )?, )); } } impl Event { pub fn get_loadout(self) -> Option { if let Event { event: EventData::Loadout(loadout), } = self { return Some(loadout); } None } }